Skip to content

Commit

Permalink
Added unit test for extern instance method native detour patching.
Browse files Browse the repository at this point in the history
  • Loading branch information
CptMoore authored and ManlyMarco committed Jan 11, 2025
1 parent c96ca55 commit af69e0f
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 8 deletions.
49 changes: 48 additions & 1 deletion HarmonyTests/Patching/Assets/NativeDetourClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,54 @@

namespace HarmonyTests.Patching.Assets;

public class ExternalMethod_Patch
public class ExternalInstanceMethod_StringIsInterned_Patch
{
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) =>
instructions;

private const string UniqueString = nameof(ExternalInstanceMethod_StringIsInterned_Patch);

public const string PrefixInput = $"{UniqueString} {nameof(PrefixInput)}";
public const string PrefixOutput = $"{UniqueString} {nameof(PrefixOutput)}";
public static void Prefix(ref string __instance, ref string __result, ref bool __runOriginal)
{
if (__instance == PrefixInput)
{
__result = PrefixOutput;
__runOriginal = false;
}
}

public const string PostfixInput = $"{UniqueString} {nameof(PostfixInput)}";
public const string PostfixOutput = $"{UniqueString} {nameof(PostfixOutput)}";
public static void Postfix(ref string __instance, ref string __result)
{
if (__instance == PostfixInput)
{
__result = PostfixOutput;
}
}

public static readonly Type TranspiledException = typeof(UnauthorizedAccessException);
public static IEnumerable<CodeInstruction> TranspilerThrow(IEnumerable<CodeInstruction> instructions)
{
yield return new CodeInstruction(OpCodes.Newobj, TranspiledException.GetConstructor([]));
yield return new CodeInstruction(OpCodes.Throw);
}

public const string FinalizerInput = $"{UniqueString} {nameof(FinalizerInput)}";
public const string FinalizerOutput = $"{UniqueString} {nameof(FinalizerOutput)}";
public static Exception Finalizer(ref string __instance, ref string __result)
{
if (__instance == FinalizerInput)
{
__result = FinalizerOutput;
}
return null;
}
}

public class ExternalStaticMethod_MathCos_Patch
{
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) =>
instructions;
Expand Down
68 changes: 61 additions & 7 deletions HarmonyTests/Patching/NativeDetourPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,68 @@
using HarmonyTests.Patching.Assets;
using NUnit.Framework;
using System;
using System.IO;
using System.Reflection;
using System.Text;

namespace HarmonyLibTests.Patching;

using EIP = ExternalInstanceMethod_StringIsInterned_Patch;

[TestFixture]
public class NativeDetourPatches : TestLogger
{
[Test]
public void Test_PatchExternalMethod()
public void Test_PatchInstanceExternalMethod()
{
var target = typeof(string).GetMethod("Intern", BindingFlags.Instance|BindingFlags.NonPublic);

if(target == null)
Assert.Inconclusive("string.Intern is missing in current runtime");

#if !NET35
if((target.MethodImplementationFlags & MethodImplAttributes.InternalCall) == 0)
Assert.Inconclusive("string.Intern is not an InternalCall (extern) in current runtime ");
#endif

if(target.GetMethodBody() != null)
Assert.Inconclusive("string.Intern has IL body in current runtime");

var str1 = new StringBuilder().Append('o').Append('k').Append('4').Append('1').ToString();
Assert.IsNull(string.IsInterned(str1));
var internedStr1 = string.Intern(str1);
Assert.AreEqual(internedStr1, string.IsInterned(str1));

var instance = new Harmony("test-patch-external-instance-method");
Assert.NotNull(instance, "Harmony instance");

instance.Patch(target, transpiler: typeof(EIP).Method("Transpiler"));
var str2 = new StringBuilder().Append('o').Append('k').Append('4').Append('2').ToString();
Assert.IsNull(string.IsInterned(str2));
var internedStr2 = string.Intern(str2);
Assert.AreEqual(internedStr2, string.IsInterned(str2));

instance.Patch(target, prefix: typeof(EIP).Method("Prefix"));
Assert.AreEqual(EIP.PrefixOutput, string.Intern(EIP.PrefixInput));

instance.Patch(target, postfix: typeof(EIP).Method("Postfix"));
Assert.AreEqual(EIP.PostfixOutput, string.Intern(EIP.PostfixInput));

instance.Patch(target, transpiler: typeof(EIP).Method("TranspilerThrow"));
Assert.Throws(EIP.TranspiledException, () => string.Intern("does not matter"));

instance.Patch(target, finalizer: typeof(EIP).Method("Finalizer"));
Assert.AreEqual(EIP.FinalizerOutput, string.Intern(EIP.FinalizerInput));

instance.UnpatchSelf();
var str3 = new StringBuilder().Append('o').Append('k').Append('4').Append('3').ToString();
Assert.IsNull(string.IsInterned(str3));
Assert.AreEqual(internedStr1, string.IsInterned(str1));
Assert.AreEqual(internedStr2, string.IsInterned(str2));
}

[Test]
public void Test_PatchStaticExternalMethod()
{
var target = SymbolExtensions.GetMethodInfo(() => Math.Cos(0));

Expand All @@ -20,22 +74,22 @@ public void Test_PatchExternalMethod()
var cos = Math.Cos;
Assert.AreEqual(1d, cos(0d));

var instance = new Harmony("test-patch-external-method");
var instance = new Harmony("test-patch-external-static-method");
Assert.NotNull(instance, "Harmony instance");

instance.Patch(target, transpiler: typeof(ExternalMethod_Patch).Method("Transpiler"));
instance.Patch(target, transpiler: typeof(ExternalStaticMethod_MathCos_Patch).Method("Transpiler"));
Assert.AreEqual(1d, cos(0d));

instance.Patch(target, prefix: typeof(ExternalMethod_Patch).Method("Prefix"));
instance.Patch(target, prefix: typeof(ExternalStaticMethod_MathCos_Patch).Method("Prefix"));
Assert.AreEqual(1d, cos(2d));

instance.Patch(target, postfix: typeof(ExternalMethod_Patch).Method("Postfix"));
instance.Patch(target, postfix: typeof(ExternalStaticMethod_MathCos_Patch).Method("Postfix"));
Assert.AreEqual(2d, cos(0d));

instance.Patch(target, transpiler: typeof(ExternalMethod_Patch).Method("TranspilerThrow"));
instance.Patch(target, transpiler: typeof(ExternalStaticMethod_MathCos_Patch).Method("TranspilerThrow"));
Assert.Throws<UnauthorizedAccessException>(() => cos(0d));

instance.Patch(target, finalizer: typeof(ExternalMethod_Patch).Method("Finalizer"));
instance.Patch(target, finalizer: typeof(ExternalStaticMethod_MathCos_Patch).Method("Finalizer"));
Assert.AreEqual(-2d, cos(0d));

instance.UnpatchSelf();
Expand Down

0 comments on commit af69e0f

Please sign in to comment.