Added patching tests

Fixed error when reverting patches
Made the LoggingILGenerator not break without a logger
This commit is contained in:
Westin Miller
2017-09-09 23:30:49 -07:00
parent 4f84cd8963
commit 9a68ed6bd0
4 changed files with 405 additions and 17 deletions

386
Torch.Tests/PatchTest.cs Normal file
View File

@@ -0,0 +1,386 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL;
using Torch.Utils;
using Xunit;
// ReSharper disable UnusedMember.Local
namespace Torch.Tests
{
#pragma warning disable 414
public class PatchTest
{
#region TestRunner
private static readonly PatchManager _patchContext = new PatchManager(null);
[Theory]
[MemberData(nameof(Prefixes))]
public void TestPrefix(TestBootstrap runner)
{
runner.TestPrefix();
}
[Theory]
[MemberData(nameof(Transpilers))]
public void TestTranspile(TestBootstrap runner)
{
runner.TestTranspile();
}
[Theory]
[MemberData(nameof(Suffixes))]
public void TestSuffix(TestBootstrap runner)
{
runner.TestSuffix();
}
[Theory]
[MemberData(nameof(Combo))]
public void TestCombo(TestBootstrap runner)
{
runner.TestCombo();
}
public class TestBootstrap
{
public bool HasPrefix => _prefixMethod != null;
public bool HasTranspile => _transpileMethod != null;
public bool HasSuffix => _suffixMethod != null;
private readonly MethodInfo _prefixMethod, _prefixAssert;
private readonly MethodInfo _suffixMethod, _suffixAssert;
private readonly MethodInfo _transpileMethod, _transpileAssert;
private readonly MethodInfo _targetMethod, _targetAssert;
private readonly MethodInfo _resetMethod;
private readonly object _instance;
private readonly object[] _targetParams;
private readonly Type _type;
public TestBootstrap(Type t)
{
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
_type = t;
_prefixMethod = t.GetMethod("Prefix", flags);
_prefixAssert = t.GetMethod("AssertPrefix", flags);
_suffixMethod = t.GetMethod("Suffix", flags);
_suffixAssert = t.GetMethod("AssertSuffix", flags);
_transpileMethod = t.GetMethod("Transpile", flags);
_transpileAssert = t.GetMethod("AssertTranspile", flags);
_targetMethod = t.GetMethod("Target", flags);
_targetAssert = t.GetMethod("AssertNormal", flags);
_resetMethod = t.GetMethod("Reset", flags);
if (_targetMethod == null)
throw new Exception($"{t.FullName} must have a method named Target");
if (_targetAssert == null)
throw new Exception($"{t.FullName} must have a method named AssertNormal");
_instance = !_targetMethod.IsStatic ? Activator.CreateInstance(t) : null;
_targetParams = (object[])t.GetField("_targetParams", flags)?.GetValue(null) ?? new object[0];
}
private void Invoke(MethodBase i, params object[] args)
{
if (i == null) return;
i.Invoke(i.IsStatic ? null : _instance, args);
}
private void Invoke()
{
_targetMethod.Invoke(_instance, _targetParams);
Invoke(_targetAssert);
}
public void TestPrefix()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
context.GetPattern(_targetMethod).Prefixes.Add(_prefixMethod);
_patchContext.Commit();
Invoke();
Invoke(_prefixAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public void TestSuffix()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
context.GetPattern(_targetMethod).Suffixes.Add(_suffixMethod);
_patchContext.Commit();
Invoke();
Invoke(_suffixAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public void TestTranspile()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
context.GetPattern(_targetMethod).Transpilers.Add(_transpileMethod);
_patchContext.Commit();
Invoke();
Invoke(_transpileAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public void TestCombo()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
if (_prefixMethod != null)
context.GetPattern(_targetMethod).Prefixes.Add(_prefixMethod);
if (_transpileMethod != null)
context.GetPattern(_targetMethod).Transpilers.Add(_transpileMethod);
if (_suffixMethod != null)
context.GetPattern(_targetMethod).Suffixes.Add(_suffixMethod);
_patchContext.Commit();
Invoke();
Invoke(_prefixAssert);
Invoke(_transpileAssert);
Invoke(_suffixAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public override string ToString()
{
return _type.Name;
}
}
private class PatchTestAttribute : Attribute
{
}
private static readonly List<TestBootstrap> _patchTest;
static PatchTest()
{
TestUtils.Init();
foreach (Type type in typeof(PatchManager).Assembly.GetTypes())
if (type.Namespace?.StartsWith(typeof(PatchManager).Namespace ?? "") ?? false)
ReflectedManager.Process(type);
_patchTest = new List<TestBootstrap>();
foreach (Type type in typeof(PatchTest).GetNestedTypes(BindingFlags.NonPublic))
if (type.GetCustomAttribute(typeof(PatchTestAttribute)) != null)
_patchTest.Add(new TestBootstrap(type));
}
public static IEnumerable<object[]> Prefixes => _patchTest.Where(x => x.HasPrefix).Select(x => new object[] { x });
public static IEnumerable<object[]> Transpilers => _patchTest.Where(x => x.HasTranspile).Select(x => new object[] { x });
public static IEnumerable<object[]> Suffixes => _patchTest.Where(x => x.HasSuffix).Select(x => new object[] { x });
public static IEnumerable<object[]> Combo => _patchTest.Where(x => x.HasPrefix || x.HasTranspile || x.HasSuffix).Select(x => new object[] { x });
#endregion
#region PatchTests
[PatchTest]
private class StaticNoRetNoParm
{
private static bool _prefixHit, _normalHit, _suffixHit, _transpileHit;
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Prefix()
{
_prefixHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target()
{
_normalHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Suffix()
{
_suffixHit = true;
}
public static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> instructions)
{
yield return new MsilInstruction(OpCodes.Ldnull);
yield return new MsilInstruction(OpCodes.Ldc_I4_1);
yield return new MsilInstruction(OpCodes.Stfld).InlineValue(typeof(StaticNoRetNoParm).GetField("_transpileHit", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
foreach (MsilInstruction i in instructions)
yield return i;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = _transpileHit = false;
}
public static void AssertTranspile()
{
Assert.True(_transpileHit, "Failed to transpile");
}
public static void AssertSuffix()
{
Assert.True(_suffixHit, "Failed to suffix");
}
public static void AssertNormal()
{
Assert.True(_normalHit, "Failed to execute normally");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
}
}
[PatchTest]
private class StaticNoRetParam
{
private static bool _prefixHit, _normalHit, _suffixHit;
private static readonly object[] _targetParams = { "test", 1, new StringBuilder("test1") };
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Prefix(string str, int i, StringBuilder o)
{
Assert.Equal(_targetParams[0], str);
Assert.Equal(_targetParams[1], i);
Assert.Equal(_targetParams[2], o);
_prefixHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target(string str, int i, StringBuilder o)
{
_normalHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Suffix(string str, int i, StringBuilder o)
{
Assert.Equal(_targetParams[0], str);
Assert.Equal(_targetParams[1], i);
Assert.Equal(_targetParams[2], o);
_suffixHit = true;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = false;
}
public static void AssertSuffix()
{
Assert.True(_suffixHit, "Failed to suffix");
}
public static void AssertNormal()
{
Assert.True(_normalHit, "Failed to execute normally");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
}
}
[PatchTest]
private class StaticNoRetParamReplace
{
private static bool _prefixHit, _normalHit, _suffixHit;
private static readonly object[] _targetParams = { "test", 1, new StringBuilder("stest1") };
private static readonly object[] _replacedParams = { "test2", 2, new StringBuilder("stest2") };
private static object[] _calledParams;
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Prefix(ref string str, ref int i, ref StringBuilder o)
{
Assert.Equal(_targetParams[0], str);
Assert.Equal(_targetParams[1], i);
Assert.Equal(_targetParams[2], o);
str = (string)_replacedParams[0];
i = (int)_replacedParams[1];
o = (StringBuilder)_replacedParams[2];
_prefixHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target(string str, int i, StringBuilder o)
{
_calledParams = new object[] { str, i, o };
_normalHit = true;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = false;
}
public static void AssertNormal()
{
Assert.True(_normalHit, "Failed to execute normally");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
for (var i = 0; i < 3; i++)
Assert.Equal(_replacedParams[i], _calledParams[i]);
}
}
[PatchTest]
private class StaticCancelExec
{
private static bool _prefixHit, _normalHit, _suffixHit;
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool Prefix()
{
_prefixHit = true;
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target()
{
_normalHit = true;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = false;
}
public static void AssertNormal()
{
Assert.False(_normalHit, "Executed normally when canceled");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
}
}
#endregion
}
#pragma warning restore 414
}

View File

@@ -63,6 +63,7 @@
<Compile Include="..\Versioning\AssemblyVersion.cs"> <Compile Include="..\Versioning\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link> <Link>Properties\AssemblyVersion.cs</Link>
</Compile> </Compile>
<Compile Include="PatchTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ReflectionTestManager.cs" /> <Compile Include="ReflectionTestManager.cs" />
<Compile Include="ReflectionSystemTest.cs" /> <Compile Include="ReflectionSystemTest.cs" />

View File

@@ -49,6 +49,7 @@ namespace Torch.Managers.PatchManager
AssemblyMemory.WriteMemory(_revertAddress, _revertData); AssemblyMemory.WriteMemory(_revertAddress, _revertData);
_revertData = null; _revertData = null;
_pinnedPatch.Value.Free(); _pinnedPatch.Value.Free();
_pinnedPatch = null;
} }
} }

View File

@@ -37,7 +37,7 @@ namespace Torch.Managers.PatchManager.Transpile
public LocalBuilder DeclareLocal(Type localType, bool isPinned = false) public LocalBuilder DeclareLocal(Type localType, bool isPinned = false)
{ {
LocalBuilder res = Backing.DeclareLocal(localType, isPinned); LocalBuilder res = Backing.DeclareLocal(localType, isPinned);
_log.Trace($"DclLoc\t{res.LocalIndex}\t=> {res.LocalType} {res.IsPinned}"); _log?.Trace($"DclLoc\t{res.LocalIndex}\t=> {res.LocalType} {res.IsPinned}");
return res; return res;
} }
@@ -45,70 +45,70 @@ namespace Torch.Managers.PatchManager.Transpile
/// <inheritdoc cref="ILGenerator.Emit(OpCode)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode)"/>
public void Emit(OpCode op) public void Emit(OpCode op)
{ {
_log.Trace($"Emit\t{op,_opcodePadding}"); _log?.Trace($"Emit\t{op,_opcodePadding}");
Backing.Emit(op); Backing.Emit(op);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/>
public void Emit(OpCode op, LocalBuilder arg) public void Emit(OpCode op, LocalBuilder arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} L:{arg.LocalIndex} {arg.LocalType}"); _log?.Trace($"Emit\t{op,_opcodePadding} L:{arg.LocalIndex} {arg.LocalType}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, int)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, int)"/>
public void Emit(OpCode op, int arg) public void Emit(OpCode op, int arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, long)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, long)"/>
public void Emit(OpCode op, long arg) public void Emit(OpCode op, long arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, float)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, float)"/>
public void Emit(OpCode op, float arg) public void Emit(OpCode op, float arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, double)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, double)"/>
public void Emit(OpCode op, double arg) public void Emit(OpCode op, double arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, string)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, string)"/>
public void Emit(OpCode op, string arg) public void Emit(OpCode op, string arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Type)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, Type)"/>
public void Emit(OpCode op, Type arg) public void Emit(OpCode op, Type arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, FieldInfo)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, FieldInfo)"/>
public void Emit(OpCode op, FieldInfo arg) public void Emit(OpCode op, FieldInfo arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, MethodInfo)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, MethodInfo)"/>
public void Emit(OpCode op, MethodInfo arg) public void Emit(OpCode op, MethodInfo arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
@@ -121,35 +121,35 @@ namespace Torch.Managers.PatchManager.Transpile
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, Label)"/>
public void Emit(OpCode op, Label arg) public void Emit(OpCode op, Label arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding}\tL:{_labelID.Invoke(arg)}"); _log?.Trace($"Emit\t{op,_opcodePadding}\tL:{_labelID.Invoke(arg)}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label[])"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, Label[])"/>
public void Emit(OpCode op, Label[] arg) public void Emit(OpCode op, Label[] arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding}\t{string.Join(", ", arg.Select(x => "L:" + _labelID.Invoke(x)))}"); _log?.Trace($"Emit\t{op,_opcodePadding}\t{string.Join(", ", arg.Select(x => "L:" + _labelID.Invoke(x)))}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, SignatureHelper)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, SignatureHelper)"/>
public void Emit(OpCode op, SignatureHelper arg) public void Emit(OpCode op, SignatureHelper arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.Emit(OpCode, ConstructorInfo)"/> /// <inheritdoc cref="ILGenerator.Emit(OpCode, ConstructorInfo)"/>
public void Emit(OpCode op, ConstructorInfo arg) public void Emit(OpCode op, ConstructorInfo arg)
{ {
_log.Trace($"Emit\t{op,_opcodePadding} {arg}"); _log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg); Backing.Emit(op, arg);
} }
/// <inheritdoc cref="ILGenerator.MarkLabel(Label)"/> /// <inheritdoc cref="ILGenerator.MarkLabel(Label)"/>
public void MarkLabel(Label label) public void MarkLabel(Label label)
{ {
_log.Trace($"MkLbl\tL:{_labelID.Invoke(label)}"); _log?.Trace($"MkLbl\tL:{_labelID.Invoke(label)}");
Backing.MarkLabel(label); Backing.MarkLabel(label);
} }
@@ -166,7 +166,7 @@ namespace Torch.Managers.PatchManager.Transpile
[Conditional("DEBUG")] [Conditional("DEBUG")]
public void EmitComment(string comment) public void EmitComment(string comment)
{ {
_log.Trace($"// {comment}"); _log?.Trace($"// {comment}");
} }
} }
#pragma warning restore 162 #pragma warning restore 162