Skip to content

Commit

Permalink
First (rough) implementation, not tested yet
Browse files Browse the repository at this point in the history
  • Loading branch information
u0035718 committed Apr 18, 2019
1 parent 60cb59e commit 4c7f7c7
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 18 deletions.
3 changes: 1 addition & 2 deletions Harmony/Internal/MethodCopier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,7 @@ internal void FinalizeILCodes(List<MethodInfo> transpilers, List<Label> endLabel
// TODO: we ignore the resulting label because we have no way to use it
//
codeInstruction.blocks.Do(block => {
Label? label;
Emitter.MarkBlockBefore(generator, block, out label);
Emitter.MarkBlockBefore(generator, block, out var label);
});

var code = codeInstruction.opcode;
Expand Down
117 changes: 106 additions & 11 deletions Harmony/Internal/MethodPatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal static class MethodPatcher
public static string ORIGINAL_METHOD_PARAM = "__originalMethod";
public static string RESULT_VAR = "__result";
public static string STATE_VAR = "__state";
public static string EXCEPTION_VAR = "__exception";
public static string PARAM_INDEX_PREFIX = "__";
public static string INSTANCE_FIELD_PREFIX = "___";

Expand All @@ -26,22 +27,23 @@ public static DynamicMethod CreatePatchedMethod(MethodBase original, List<Method
[UpgradeToLatestVersion(1)]
public static DynamicMethod CreatePatchedMethod(MethodBase original, string harmonyInstanceID, List<MethodInfo> prefixes, List<MethodInfo> postfixes, List<MethodInfo> transpilers, List<MethodInfo> finalizers)
{
Memory.MarkForNoInlining(original);

if (original == null)
throw new ArgumentNullException(nameof(original), "Original method is null. Did you specify it correctly?");

try
{
if (original == null)
throw new ArgumentNullException(nameof(original));

Memory.MarkForNoInlining(original);

if (HarmonyInstance.DEBUG)
{
FileLog.LogBuffered("### Patch " + original.DeclaringType + ", " + original);
FileLog.FlushBuffer();
}

var idx = prefixes.Count() + postfixes.Count();
var idx = prefixes.Count() + postfixes.Count() + finalizers.Count();
var firstArgIsReturnBuffer = NativeThisPointer.NeedsNativeThisPointerFix(original);
var returnType = AccessTools.GetReturnedType(original);
var hasFinalizers = finalizers.Any();
var patch = DynamicTools.CreateDynamicMethod(original, "_Patch" + idx);
if (patch == null)
return null;
Expand Down Expand Up @@ -72,8 +74,22 @@ public static DynamicMethod CreatePatchedMethod(MethodBase original, string harm
}
});

if (firstArgIsReturnBuffer)
Emitter.Emit(il, original.IsStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1);
LocalBuilder finalizedVariable = null;
LocalBuilder exceptionVariable = null;
if (hasFinalizers)
{
finalizedVariable = DynamicTools.DeclareLocalVariable(il, typeof(bool));

exceptionVariable = DynamicTools.DeclareLocalVariable(il, typeof(Exception));
privateVars[EXCEPTION_VAR] = exceptionVariable;

// begin try
Emitter.MarkBlockBefore(il, new ExceptionBlock(ExceptionBlockType.BeginExceptionBlock), out var label);
}

// TODO: verify correct usage
// if (firstArgIsReturnBuffer)
// Emitter.Emit(il, original.IsStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1);

var skipOriginalLabel = il.DefineLabel();
var canHaveJump = AddPrefixes(il, original, prefixes, privateVars, skipOriginalLabel);
Expand All @@ -99,10 +115,61 @@ public static DynamicMethod CreatePatchedMethod(MethodBase original, string harm

AddPostfixes(il, original, postfixes, privateVars, true);

if (firstArgIsReturnBuffer)
Emitter.Emit(il, OpCodes.Stobj, returnType);
if (hasFinalizers)
{
AddFinalizers(il, original, finalizers, privateVars, false);
Emitter.Emit(il, OpCodes.Ldc_I4_1);
Emitter.Emit(il, OpCodes.Stloc, finalizedVariable);
Emitter.Emit(il, OpCodes.Ldloc, exceptionVariable);
var noExceptionLabel = il.DefineLabel();
Emitter.Emit(il, OpCodes.Brfalse, noExceptionLabel);
Emitter.Emit(il, OpCodes.Ldloc, exceptionVariable);
Emitter.Emit(il, OpCodes.Throw);
il.MarkLabel(noExceptionLabel);

// end try (includes leave)
Emitter.MarkBlockAfter(il, new ExceptionBlock(ExceptionBlockType.EndExceptionBlock));
}
else
{
// TODO: verify correct usage
// if (firstArgIsReturnBuffer)
// Emitter.Emit(il, OpCodes.Stobj, returnType);

Emitter.Emit(il, OpCodes.Ret);
}

if (hasFinalizers)
{
// begin catch
Emitter.MarkBlockBefore(il, new ExceptionBlock(ExceptionBlockType.BeginCatchBlock), out var label);
Emitter.Emit(il, OpCodes.Stloc, exceptionVariable);

Emitter.Emit(il, OpCodes.Ldloc, finalizedVariable);
var endFinalizerLabel = il.DefineLabel();
Emitter.Emit(il, OpCodes.Brtrue, endFinalizerLabel);

var rethrowPossible = AddFinalizers(il, original, finalizers, privateVars, true);

Emitter.Emit(il, OpCodes.Ret);
il.MarkLabel(endFinalizerLabel);

if (rethrowPossible)
Emitter.Emit(il, OpCodes.Rethrow);
else
{
Emitter.Emit(il, OpCodes.Ldloc, exceptionVariable);
Emitter.Emit(il, OpCodes.Throw);
}

// end catch
Emitter.MarkBlockAfter(il, new ExceptionBlock(ExceptionBlockType.EndExceptionBlock));

// TODO: verify correct usage
// if (firstArgIsReturnBuffer)
// Emitter.Emit(il, OpCodes.Stobj, returnType);

Emitter.Emit(il, OpCodes.Ret);
}

if (HarmonyInstance.DEBUG)
{
Expand Down Expand Up @@ -445,6 +512,34 @@ static void AddPostfixes(ILGenerator il, MethodBase original, List<MethodInfo> p
}
});
}

static bool AddFinalizers(ILGenerator il, MethodBase original, List<MethodInfo> finalizers, Dictionary<string, LocalBuilder> variables, bool catchExceptions)
{
var rethrowPossible = true;
finalizers
.Do(fix =>
{
if (catchExceptions)
Emitter.MarkBlockBefore(il, new ExceptionBlock(ExceptionBlockType.BeginExceptionBlock), out var label);

EmitCallParameter(il, original, fix, variables, true);
Emitter.Emit(il, OpCodes.Call, fix);
if (fix.ReturnType != typeof(void))
{
Emitter.Emit(il, OpCodes.Stloc, variables[EXCEPTION_VAR]);
rethrowPossible = false;
}

if (catchExceptions)
{
Emitter.MarkBlockAfter(il, new ExceptionBlock(ExceptionBlockType.EndExceptionBlock));
Emitter.MarkBlockBefore(il, new ExceptionBlock(ExceptionBlockType.BeginCatchBlock), out var label);
Emitter.Emit(il, OpCodes.Pop);
Emitter.MarkBlockAfter(il, new ExceptionBlock(ExceptionBlockType.EndExceptionBlock));
}
});
return rethrowPossible;
}
}
}

4 changes: 2 additions & 2 deletions Harmony/Public/ExceptionBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ public class ExceptionBlock
/// <param name="blockType">Block type</param>
/// <param name="catchType">Catch type</param>
///
public ExceptionBlock(ExceptionBlockType blockType, Type catchType)
public ExceptionBlock(ExceptionBlockType blockType, Type catchType = null)
{
this.blockType = blockType;
this.catchType = catchType;
this.catchType = catchType ?? typeof(object);
}
}
}
21 changes: 20 additions & 1 deletion HarmonyTests/Patching/Assets/PatchClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ public static void Prefix(out object __state)
__state = null;
}

public static void Postfix(int __state)
public static void Postfix(object __state)
{

}
Expand Down Expand Up @@ -440,4 +440,23 @@ public static void Postfix(bool __result)
postfixed = true;
}
}

public class SimpleFinalizerClass
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void Method()
{
Console.WriteLine("Method");
}
}

public class SimpleFinalizerPatch
{
public static bool finalized = false;

public static void Finalizer()
{
finalized = true;
}
}
}
35 changes: 35 additions & 0 deletions HarmonyTests/Patching/FinalizerPatches.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Harmony;
using HarmonyTests.Assets;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Reflection;

namespace HarmonyTests
{
[TestFixture]
public class FinalizerPatches
{
[Test]
public void TestSimpleFinalizer()
{
var originalClass = typeof(SimpleFinalizerClass);
Assert.IsNotNull(originalClass);
var originalMethod = originalClass.GetMethod("Method");
Assert.IsNotNull(originalMethod);

var patchClass = typeof(SimpleFinalizerPatch);
var finalizer = patchClass.GetMethod("Finalizer");
Assert.IsNotNull(finalizer);

var instance = HarmonyInstance.Create("test");
Assert.IsNotNull(instance);
var patcher = new PatchProcessor(instance, new List<MethodBase> { originalMethod }, null, null, null, new HarmonyMethod(finalizer));
Assert.IsNotNull(patcher);
patcher.Patch();

new SimpleFinalizerClass().Method();
Assert.IsTrue(SimpleFinalizerPatch.finalized);
}
}
}
2 changes: 0 additions & 2 deletions HarmonyTests/Patching/StaticPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ public void TestMethod5()
Assert.IsTrue(Class5Patch.postfixed, "Prefix was not executed");
}

/* fails during travis-ci testing, so disabled for now
[Test]
public void TestPatchUnpatch()
{
Expand All @@ -213,7 +212,6 @@ public void TestPatchUnpatch()

instanceB.UnpatchAll("test");
}
*/

[Test]
public void TestAttributes()
Expand Down

0 comments on commit 4c7f7c7

Please sign in to comment.