├── src └── Pose │ ├── Properties │ └── AssemblyInfo.cs │ ├── Is.cs │ ├── IL │ ├── ExceptionHandler.cs │ ├── MethodDisassembler.cs │ ├── DebugHelpers │ │ ├── DynamicMethodBody.cs │ │ ├── BrowsableDynamicMethod.cs │ │ └── DynamicModule.cs │ ├── MethodRewriter.cs │ └── Stubs.cs │ ├── Extensions │ ├── DictionaryExtensions.cs │ ├── ILGeneratorExtensions.cs │ └── MethodBaseExtensions.cs │ ├── Exceptions │ └── InvalidShimSignatureException.cs │ ├── PoseContext.cs │ ├── Pose.csproj │ ├── Delegates │ ├── ActionRef.cs │ └── FuncRef.cs │ ├── Shim.cs │ ├── Helpers │ ├── StubHelper.cs │ └── ShimHelper.cs │ └── Shim.Delegates.cs ├── test └── Pose.Tests │ ├── Extensions │ ├── MethodBaseExtensionsTests.cs │ └── DictionaryExtensionsTests.cs │ ├── Pose.Tests.csproj │ ├── IL │ ├── MethodDisassemblerTests.cs │ ├── MethodRewriterTests.cs │ └── StubsTests.cs │ ├── Helpers │ ├── ShimHelperTests.cs │ └── StubHelperTests.cs │ └── ShimTests.cs ├── appveyor.yml ├── LICENSE ├── Pose.sln ├── README.md └── .gitignore /src/Pose/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Pose.Tests")] -------------------------------------------------------------------------------- /src/Pose/Is.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Pose 4 | { 5 | public static class Is 6 | { 7 | public static T A() => default(T); 8 | } 9 | } -------------------------------------------------------------------------------- /test/Pose.Tests/Extensions/MethodBaseExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | using Pose.Extensions; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace Pose.Tests 8 | { 9 | [TestClass] 10 | public class MethodBaseExtensionsTests 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '1.2.0.{build}' 2 | configuration: 3 | - Debug 4 | - Release 5 | build_script: 6 | - echo "Building for %CONFIGURATION%" 7 | - dotnet restore 8 | - dotnet build -c %CONFIGURATION% 9 | test_script: 10 | - ps: dotnet test .\test\Pose.Tests\Pose.Tests.csproj 11 | - ps: if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 12 | cache: 13 | - '%USERPROFILE%\.nuget\packages' 14 | -------------------------------------------------------------------------------- /src/Pose/IL/ExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Pose.IL 5 | { 6 | internal class ExceptionHandler 7 | { 8 | public Type CatchType; 9 | 10 | public ExceptionHandlingClauseOptions Flags; 11 | 12 | public int TryStart; 13 | 14 | public int TryEnd; 15 | 16 | public int FilterStart; 17 | 18 | public int HandlerStart; 19 | 20 | public int HandlerEnd; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Pose/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Pose.Extensions 4 | { 5 | internal static class DictionaryExtensions 6 | { 7 | public static bool TryAdd(this Dictionary dictionary, T key, U value) 8 | { 9 | try 10 | { 11 | dictionary.Add(key, value); 12 | return true; 13 | } 14 | catch 15 | { 16 | return false; 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Pose/Extensions/ILGeneratorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Reflection.Emit; 3 | 4 | namespace Pose.Extensions 5 | { 6 | internal static class ILGeneratorExtensions 7 | { 8 | public static byte[] GetILBytes(this ILGenerator ilGenerator) 9 | { 10 | var bakeByteArray = typeof(ILGenerator).GetMethod("BakeByteArray", BindingFlags.Instance | BindingFlags.NonPublic); 11 | byte[] ilBytes = (byte[])bakeByteArray.Invoke(ilGenerator, null); 12 | return ilBytes; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /test/Pose.Tests/Extensions/DictionaryExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using Pose.Extensions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace Pose.Tests 7 | { 8 | [TestClass] 9 | public class DictionaryExtensionsTests 10 | { 11 | [TestMethod] 12 | public void TestTryAdd() 13 | { 14 | Dictionary dictionary = new Dictionary(); 15 | Assert.AreEqual(true, dictionary.TryAdd(0, "0")); 16 | Assert.AreEqual(false, dictionary.TryAdd(0, "1")); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Pose.Tests/Pose.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/Pose.Tests/IL/MethodDisassemblerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | using Pose.IL; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Pose.Tests 9 | { 10 | [TestClass] 11 | public class MethodDisassemblerTests 12 | { 13 | [TestMethod] 14 | public void TestGetILInstructions() 15 | { 16 | MethodDisassembler methodDisassembler 17 | = new MethodDisassembler(typeof(Console).GetMethod("Clear")); 18 | Assert.AreNotEqual(0, methodDisassembler.GetILInstructions().Count); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Pose/Exceptions/InvalidShimSignatureException.cs: -------------------------------------------------------------------------------- 1 | namespace Pose.Exceptions 2 | { 3 | [System.Serializable] 4 | internal class InvalidShimSignatureException : System.Exception 5 | { 6 | public InvalidShimSignatureException() : base() { } 7 | public InvalidShimSignatureException(string message) : base(message) { } 8 | public InvalidShimSignatureException(string message, System.Exception inner) : base(message, inner) { } 9 | protected InvalidShimSignatureException( 10 | System.Runtime.Serialization.SerializationInfo info, 11 | System.Runtime.Serialization.StreamingContext context) : base(info, context) { } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Pose/Extensions/MethodBaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Pose.Extensions 5 | { 6 | internal static class MethodBaseExtensions 7 | { 8 | public static bool InCoreLibrary(this MethodBase methodBase) 9 | { 10 | return methodBase.DeclaringType.Assembly == typeof(Exception).Assembly; 11 | } 12 | 13 | public static bool IsOverride(this MethodBase methodBase) 14 | { 15 | if (!(methodBase is MethodInfo methodInfo)) 16 | return false; 17 | 18 | return methodInfo.GetBaseDefinition().DeclaringType != methodInfo.DeclaringType; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Pose/IL/MethodDisassembler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | using Mono.Reflection; 6 | 7 | namespace Pose.IL 8 | { 9 | internal class MethodDisassembler 10 | { 11 | private MethodBase _method; 12 | 13 | public MethodDisassembler(MethodBase method) 14 | { 15 | _method = method; 16 | } 17 | 18 | public List GetILInstructions() 19 | { 20 | return _method.GetInstructions().ToList(); 21 | } 22 | 23 | public List GetMethodDependencies() 24 | { 25 | var methodDependencies = GetILInstructions() 26 | .Where(i => (i.Operand as MethodInfo) != null || (i.Operand as ConstructorInfo) != null) 27 | .Select(i => (i.Operand as MethodBase)); 28 | 29 | return methodDependencies.ToList(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Pose/IL/DebugHelpers/DynamicMethodBody.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace Pose.IL.DebugHelpers 6 | { 7 | internal class DynamicMethodBody : MethodBody 8 | { 9 | private readonly byte[] m_ilBytes; 10 | 11 | private readonly IList m_locals; 12 | 13 | public DynamicMethodBody(byte[] ilBytes, IList locals) 14 | { 15 | m_ilBytes = ilBytes; 16 | m_locals = locals; 17 | } 18 | 19 | public override int LocalSignatureMetadataToken => throw new NotImplementedException(); 20 | 21 | public override IList LocalVariables => m_locals; 22 | 23 | public override int MaxStackSize => throw new NotImplementedException(); 24 | 25 | public override bool InitLocals => throw new NotImplementedException(); 26 | 27 | public override byte[] GetILAsByteArray() => m_ilBytes; 28 | 29 | public override IList ExceptionHandlingClauses => throw new NotImplementedException(); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Pose/PoseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | using Pose.IL; 7 | 8 | namespace Pose 9 | { 10 | public static class PoseContext 11 | { 12 | internal static Shim[] Shims { private set; get; } 13 | internal static Dictionary StubCache { private set; get; } 14 | 15 | public static void Isolate(Action entryPoint, params Shim[] shims) 16 | { 17 | if (shims == null || shims.Length == 0) 18 | { 19 | entryPoint.Invoke(); 20 | return; 21 | } 22 | 23 | Shims = shims; 24 | StubCache = new Dictionary(); 25 | 26 | Type delegateType = typeof(Action<>).MakeGenericType(entryPoint.Target.GetType()); 27 | MethodRewriter rewriter = MethodRewriter.CreateRewriter(entryPoint.Method, false); 28 | ((MethodInfo)(rewriter.Rewrite())).CreateDelegate(delegateType).DynamicInvoke(entryPoint.Target); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Pose/Pose.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pose 4 | Replace any .NET method (including static and non-virtual) with a delegate 5 | 1.2.1 6 | netstandard2.1 7 | portable 8 | Pose 9 | Pose 10 | pose;mocking;testing;unit-test;isolation-framework;test-framework 11 | https://github.com/tonerdo/pose 12 | https://github.com/tonerdo/pose/blob/master/LICENSE 13 | git 14 | https://github.com/tonerdo/pose 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Toni Solarin-Sodara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/Pose/Delegates/ActionRef.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Pose.Delegates 4 | { 5 | public delegate void ActionRef(ref T arg); 6 | public delegate void ActionRef(ref T1 arg1, T2 arg2); 7 | public delegate void ActionRef(ref T1 arg1, T2 arg2, T3 arg3); 8 | public delegate void ActionRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4); 9 | public delegate void ActionRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); 10 | public delegate void ActionRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); 11 | public delegate void ActionRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); 12 | public delegate void ActionRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); 13 | public delegate void ActionRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9); 14 | public delegate void ActionRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10); 15 | } -------------------------------------------------------------------------------- /src/Pose/Delegates/FuncRef.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Pose.Delegates 4 | { 5 | public delegate TResult FuncRef(ref T1 arg1); 6 | public delegate TResult FuncRef(ref T1 arg1, T2 arg2); 7 | public delegate TResult FuncRef(ref T1 arg1, T2 arg2, T3 arg3); 8 | public delegate TResult FuncRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4); 9 | public delegate TResult FuncRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); 10 | public delegate TResult FuncRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); 11 | public delegate TResult FuncRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); 12 | public delegate TResult FuncRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); 13 | public delegate TResult FuncRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9); 14 | public delegate TResult FuncRef(ref T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10); 15 | } -------------------------------------------------------------------------------- /src/Pose/IL/DebugHelpers/BrowsableDynamicMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | 6 | namespace Pose.IL.DebugHelpers 7 | { 8 | internal class BrowsableDynamicMethod : MethodInfo 9 | { 10 | private readonly DynamicMethod m_method; 11 | 12 | private readonly MethodBody m_methodBody; 13 | 14 | public BrowsableDynamicMethod(DynamicMethod method, MethodBody methodBody) 15 | { 16 | m_method = method; 17 | m_methodBody = methodBody; 18 | } 19 | 20 | public override ICustomAttributeProvider ReturnTypeCustomAttributes => throw new NotImplementedException(); 21 | 22 | public override MethodAttributes Attributes => MethodAttributes.Static; 23 | 24 | public override RuntimeMethodHandle MethodHandle => throw new NotImplementedException(); 25 | 26 | public override Module Module => new DynamicModule(m_method.GetILGenerator()); 27 | 28 | public override Type DeclaringType => m_method.DeclaringType; 29 | 30 | public override string Name => m_method.Name; 31 | 32 | public override Type ReflectedType => throw new NotImplementedException(); 33 | 34 | public override MethodInfo GetBaseDefinition() => throw new NotImplementedException(); 35 | 36 | public override object[] GetCustomAttributes(bool inherit) => throw new NotImplementedException(); 37 | 38 | public override object[] GetCustomAttributes(Type attributeType, bool inherit) => throw new NotImplementedException(); 39 | 40 | public override Type[] GetGenericArguments() => Array.Empty(); 41 | 42 | public override MethodBody GetMethodBody() => m_methodBody; 43 | 44 | public override MethodImplAttributes GetMethodImplementationFlags() => throw new NotImplementedException(); 45 | 46 | public override ParameterInfo[] GetParameters() => m_method.GetParameters(); 47 | 48 | public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) 49 | => throw new NotImplementedException(); 50 | 51 | public override bool IsDefined(Type attributeType, bool inherit) => throw new NotImplementedException(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/Pose.Tests/IL/MethodRewriterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | 6 | using Pose.Helpers; 7 | using Pose.IL; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | 10 | namespace Pose.Tests 11 | { 12 | [TestClass] 13 | public class MethodRewriterTests 14 | { 15 | [TestMethod] 16 | public void TestStaticMethodRewrite() 17 | { 18 | MethodInfo methodInfo = typeof(DateTime).GetMethod("get_Now"); 19 | MethodRewriter methodRewriter = MethodRewriter.CreateRewriter(methodInfo, false); 20 | DynamicMethod dynamicMethod = methodRewriter.Rewrite() as DynamicMethod; 21 | 22 | Delegate func = dynamicMethod.CreateDelegate(typeof(Func)); 23 | Assert.AreEqual(DateTime.Now.ToString("yyyyMMdd_HHmm"), ((DateTime)func.DynamicInvoke()).ToString("yyyyMMdd_HHmm")); 24 | } 25 | 26 | [TestMethod] 27 | public void TestInstanceMethodRewrite() 28 | { 29 | string item = "Item 1"; 30 | List list = new List(); 31 | MethodInfo methodInfo = typeof(List).GetMethod("Add"); 32 | MethodRewriter methodRewriter = MethodRewriter.CreateRewriter(methodInfo, false); 33 | DynamicMethod dynamicMethod = methodRewriter.Rewrite() as DynamicMethod; 34 | 35 | Delegate func = dynamicMethod.CreateDelegate(typeof(Action, string>)); 36 | func.DynamicInvoke(list, item); 37 | 38 | Assert.AreEqual(1, list.Count); 39 | Assert.AreEqual(item, list[0]); 40 | } 41 | 42 | [TestMethod] 43 | public void TestConstructorRewrite() 44 | { 45 | List list = new List(); 46 | ConstructorInfo constructorInfo = typeof(List).GetConstructor(Type.EmptyTypes); 47 | MethodRewriter methodRewriter = MethodRewriter.CreateRewriter(constructorInfo, false); 48 | DynamicMethod dynamicMethod = methodRewriter.Rewrite() as DynamicMethod; 49 | 50 | Assert.AreEqual(typeof(void), dynamicMethod.ReturnType); 51 | Assert.AreEqual(typeof(List), dynamicMethod.GetParameters()[0].ParameterType); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Pose/Shim.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | using Pose.Helpers; 7 | 8 | namespace Pose 9 | { 10 | public partial class Shim 11 | { 12 | private MethodBase _original; 13 | private Delegate _replacement; 14 | private Object _instance; 15 | private Type _type; 16 | private bool _setter; 17 | 18 | internal MethodBase Original 19 | { 20 | get 21 | { 22 | return _original; 23 | } 24 | } 25 | 26 | internal Delegate Replacement 27 | { 28 | get 29 | { 30 | return _replacement; 31 | } 32 | } 33 | 34 | internal Object Instance 35 | { 36 | get 37 | { 38 | return _instance; 39 | } 40 | } 41 | 42 | internal Type Type 43 | { 44 | get 45 | { 46 | return _type; 47 | } 48 | } 49 | 50 | private Shim(MethodBase original, object instanceOrType) 51 | { 52 | _original = original; 53 | if (instanceOrType is Type type) 54 | _type = type; 55 | else 56 | _instance = instanceOrType; 57 | } 58 | 59 | public static Shim Replace(Expression expression, bool setter = false) 60 | => ReplaceImpl(expression, setter); 61 | 62 | public static Shim Replace(Expression> expression, bool setter = false) 63 | => ReplaceImpl(expression, setter); 64 | 65 | private static Shim ReplaceImpl(Expression expression, bool setter) 66 | { 67 | MethodBase methodBase = ShimHelper.GetMethodFromExpression(expression.Body, setter, out object instance); 68 | return new Shim(methodBase, instance) { _setter = setter }; 69 | } 70 | 71 | private Shim WithImpl(Delegate replacement) 72 | { 73 | _replacement = replacement; 74 | ShimHelper.ValidateReplacementMethodSignature(this._original, this._replacement.Method, _instance?.GetType() ?? _type, _setter); 75 | return this; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /test/Pose.Tests/Helpers/ShimHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | using Pose.Helpers; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | using static System.Console; 9 | 10 | namespace Pose.Tests 11 | { 12 | [TestClass] 13 | public class ShimHelperTests 14 | { 15 | [TestMethod] 16 | public void TestGetMethodFromExpressionThrowNotImplemented() 17 | { 18 | Expression> expr = () => true; 19 | Expression> expr1 = () => DateTime.MaxValue; 20 | 21 | Assert.ThrowsException( 22 | () => ShimHelper.GetMethodFromExpression(expr.Body, false, out Object instance)); 23 | 24 | Assert.ThrowsException( 25 | () => ShimHelper.GetMethodFromExpression(expr1.Body, false, out Object instance)); 26 | } 27 | 28 | [TestMethod] 29 | public void TestGetMethodFromExpressionValid() 30 | { 31 | Expression> expr = () => DateTime.Now; 32 | Expression> expr1 = () => ReadLine(); 33 | 34 | Assert.AreEqual(typeof(DateTime).GetMethod("get_Now"), 35 | ShimHelper.GetMethodFromExpression(expr.Body, false, out Object instance)); 36 | 37 | Assert.AreEqual(typeof(Console).GetMethod("ReadLine"), 38 | ShimHelper.GetMethodFromExpression(expr1.Body, false, out instance)); 39 | } 40 | 41 | [TestMethod] 42 | public void TestGetObjectInstanceFromExpressionValueType() 43 | { 44 | DateTime dateTime = new DateTime(); 45 | Expression> expression = () => dateTime.AddDays(2); 46 | 47 | Assert.ThrowsException( 48 | () => ShimHelper.GetObjectInstanceOrType((expression.Body as MethodCallExpression).Object)); 49 | } 50 | 51 | [TestMethod] 52 | public void TestGetObjectInstanceFromExpression() 53 | { 54 | ShimHelperTests shimHelperTests = new ShimHelperTests(); 55 | Expression expression = () => shimHelperTests.TestGetObjectInstanceFromExpression(); 56 | var instance = ShimHelper.GetObjectInstanceOrType((expression.Body as MethodCallExpression).Object); 57 | 58 | Assert.IsNotNull(instance); 59 | Assert.AreEqual(typeof(ShimHelperTests), instance.GetType()); 60 | Assert.AreSame(shimHelperTests, instance); 61 | Assert.AreNotSame(new ShimHelperTests(), instance); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/Pose.Tests/IL/StubsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | 6 | using Pose.Helpers; 7 | using Pose.IL; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | 10 | namespace Pose.Tests 11 | { 12 | [TestClass] 13 | public class StubsTests 14 | { 15 | [TestMethod] 16 | public void TestGenerateStubForStaticMethod() 17 | { 18 | MethodInfo methodInfo = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }); 19 | DynamicMethod dynamicMethod = Stubs.GenerateStubForDirectCall(methodInfo); 20 | int count = dynamicMethod.GetParameters().Length; 21 | 22 | Assert.AreEqual(methodInfo.GetParameters().Length, dynamicMethod.GetParameters().Length); 23 | Assert.AreEqual(methodInfo.GetParameters()[0].ParameterType, dynamicMethod.GetParameters()[0].ParameterType); 24 | } 25 | 26 | [TestMethod] 27 | public void TestGenerateStubForInstanceMethod() 28 | { 29 | Type thisType = typeof(List); 30 | MethodInfo methodInfo = thisType.GetMethod("Add"); 31 | DynamicMethod dynamicMethod = Stubs.GenerateStubForDirectCall(methodInfo); 32 | int count = dynamicMethod.GetParameters().Length; 33 | 34 | Assert.AreEqual(methodInfo.GetParameters().Length, dynamicMethod.GetParameters().Length - 1); 35 | Assert.AreEqual(thisType, dynamicMethod.GetParameters()[0].ParameterType); 36 | } 37 | 38 | [TestMethod] 39 | public void TestGenerateStubForVirtualMethod() 40 | { 41 | Type thisType = typeof(List); 42 | MethodInfo methodInfo = thisType.GetMethod("Add"); 43 | DynamicMethod dynamicMethod = Stubs.GenerateStubForVirtualCall(methodInfo); 44 | int count = dynamicMethod.GetParameters().Length; 45 | 46 | Assert.AreEqual(methodInfo.GetParameters().Length, dynamicMethod.GetParameters().Length - 1); 47 | Assert.AreEqual(thisType, dynamicMethod.GetParameters()[0].ParameterType); 48 | } 49 | 50 | [TestMethod] 51 | public void TestGenerateStubForReferenceTypeConstructor() 52 | { 53 | Type thisType = typeof(List); 54 | ConstructorInfo constructorInfo = thisType.GetConstructor(Type.EmptyTypes); 55 | DynamicMethod dynamicMethod = Stubs.GenerateStubForObjectInitialization(constructorInfo); 56 | int count = dynamicMethod.GetParameters().Length; 57 | 58 | Assert.AreEqual(constructorInfo.GetParameters().Length, dynamicMethod.GetParameters().Length); 59 | Assert.AreEqual(thisType, dynamicMethod.ReturnType); 60 | } 61 | 62 | [TestMethod] 63 | public void TestGenerateStubForMethodPointer() 64 | { 65 | MethodInfo methodInfo = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }); 66 | DynamicMethod dynamicMethod = Stubs.GenerateStubForDirectLoad(methodInfo); 67 | int count = dynamicMethod.GetParameters().Length; 68 | 69 | Assert.AreEqual(0, dynamicMethod.GetParameters().Length); 70 | Assert.AreEqual(typeof(IntPtr), dynamicMethod.ReturnType); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /Pose.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27004.2002 4 | MinimumVisualStudioVersion = 15.0.26124.0 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3D859125-47BB-49F0-ACB4-42CEE47FB562}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pose", "src\Pose\Pose.csproj", "{BBC1C0DC-ECA2-48C3-A233-0873428203AF}" 8 | EndProject 9 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C2484D4C-4BED-46C2-BD61-2508BD8BD3C5}" 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pose.Tests", "test\Pose.Tests\Pose.Tests.csproj", "{AE06C68C-032F-45CC-888C-54B0B98F860C}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Debug|x64 = Debug|x64 17 | Debug|x86 = Debug|x86 18 | Release|Any CPU = Release|Any CPU 19 | Release|x64 = Release|x64 20 | Release|x86 = Release|x86 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Debug|x64.Build.0 = Debug|Any CPU 27 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Debug|x86.Build.0 = Debug|Any CPU 29 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Release|x64.ActiveCfg = Release|Any CPU 32 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Release|x64.Build.0 = Release|Any CPU 33 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Release|x86.ActiveCfg = Release|Any CPU 34 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF}.Release|x86.Build.0 = Release|Any CPU 35 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Debug|x64.Build.0 = Debug|Any CPU 39 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Debug|x86.Build.0 = Debug|Any CPU 41 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Release|x64.ActiveCfg = Release|Any CPU 44 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Release|x64.Build.0 = Release|Any CPU 45 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Release|x86.ActiveCfg = Release|Any CPU 46 | {AE06C68C-032F-45CC-888C-54B0B98F860C}.Release|x86.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | GlobalSection(NestedProjects) = preSolution 52 | {BBC1C0DC-ECA2-48C3-A233-0873428203AF} = {3D859125-47BB-49F0-ACB4-42CEE47FB562} 53 | {AE06C68C-032F-45CC-888C-54B0B98F860C} = {C2484D4C-4BED-46C2-BD61-2508BD8BD3C5} 54 | EndGlobalSection 55 | GlobalSection(ExtensibilityGlobals) = postSolution 56 | SolutionGuid = {FE2B0751-A19A-4621-A703-18607E69D31A} 57 | EndGlobalSection 58 | EndGlobal 59 | -------------------------------------------------------------------------------- /test/Pose.Tests/Helpers/StubHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | 6 | using Pose.Helpers; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | 9 | namespace Pose.Tests 10 | { 11 | [TestClass] 12 | public class StubHelperTests 13 | { 14 | [TestMethod] 15 | public void TestGetMethodPointer() 16 | { 17 | MethodInfo methodInfo = typeof(Console).GetMethod("Clear"); 18 | DynamicMethod dynamicMethod 19 | = new DynamicMethod("Method", typeof(void), Type.EmptyTypes); 20 | ILGenerator ilGenerator = dynamicMethod.GetILGenerator(); 21 | ilGenerator.Emit(OpCodes.Ret); 22 | 23 | Assert.AreNotEqual(IntPtr.Zero, StubHelper.GetMethodPointer(methodInfo)); 24 | Assert.AreNotEqual(IntPtr.Zero, StubHelper.GetMethodPointer(dynamicMethod)); 25 | } 26 | 27 | [TestMethod] 28 | public void TestGetShimDelegateTarget() 29 | { 30 | Action action = new Action(() => Console.Clear()); 31 | Shim shim = Shim.Replace(() => Console.Clear()).With(action); 32 | PoseContext.Isolate(() => { }, shim); 33 | 34 | Assert.AreEqual(action.Target, StubHelper.GetShimDelegateTarget(0)); 35 | Assert.AreSame(action.Target, StubHelper.GetShimDelegateTarget(0)); 36 | } 37 | 38 | [TestMethod] 39 | public void TestGetShimReplacementMethod() 40 | { 41 | Action action = new Action(() => Console.Clear()); 42 | Shim shim = Shim.Replace(() => Console.Clear()).With(action); 43 | PoseContext.Isolate(() => { }, shim); 44 | 45 | Assert.AreEqual(action.Method, StubHelper.GetShimReplacementMethod(0)); 46 | Assert.AreSame(action.Method, StubHelper.GetShimReplacementMethod(0)); 47 | } 48 | 49 | [TestMethod] 50 | public void TestGetIndexOfMatchingShim() 51 | { 52 | StubHelperTests stubHelperTests = new StubHelperTests(); 53 | Action staticAction = new Action(() => { }); 54 | Action instanceAction = new Action((@this) => { }); 55 | 56 | Shim shim = Shim.Replace(() => Console.Clear()).With(staticAction); 57 | Shim shim1 = Shim.Replace(() => Is.A().TestGetIndexOfMatchingShim()).With(instanceAction); 58 | Shim shim2 = Shim.Replace(() => stubHelperTests.TestGetIndexOfMatchingShim()).With(instanceAction); 59 | PoseContext.Isolate(() => { }, shim, shim1, shim2); 60 | 61 | MethodInfo consoleMethodInfo = typeof(Console).GetMethod("Clear"); 62 | MethodInfo stubMethodInfo = typeof(StubHelperTests).GetMethod("TestGetIndexOfMatchingShim"); 63 | 64 | Assert.AreEqual(0, StubHelper.GetIndexOfMatchingShim(consoleMethodInfo, null)); 65 | Assert.AreEqual(1, StubHelper.GetIndexOfMatchingShim(stubMethodInfo, new StubHelperTests())); 66 | Assert.AreEqual(2, StubHelper.GetIndexOfMatchingShim(stubMethodInfo, stubHelperTests)); 67 | } 68 | 69 | [TestMethod] 70 | public void TestGetRuntimeMethodForVirtual() 71 | { 72 | Type type = typeof(StubHelperTests); 73 | MethodInfo methodInfo = type.GetMethod("TestGetRuntimeMethodForVirtual"); 74 | Assert.AreEqual(methodInfo, StubHelper.DevirtualizeMethod(type, methodInfo)); 75 | } 76 | 77 | [TestMethod] 78 | public void TestGetOwningModule() 79 | { 80 | Assert.AreEqual(typeof(StubHelper).Module, StubHelper.GetOwningModule()); 81 | Assert.AreNotEqual(typeof(StubHelperTests).Module, StubHelper.GetOwningModule()); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Pose/IL/DebugHelpers/DynamicModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | 6 | namespace Pose.IL.DebugHelpers 7 | { 8 | internal class DynamicModule: Module 9 | { 10 | private static FieldInfo s_scopeField; 11 | 12 | private readonly ILGenerator m_ilGenerator; 13 | 14 | public DynamicModule(ILGenerator ilGenerator) 15 | { 16 | m_ilGenerator = ilGenerator; 17 | 18 | if (s_scopeField == null) 19 | { 20 | s_scopeField = m_ilGenerator.GetType().GetField("m_scope", BindingFlags.Instance | BindingFlags.NonPublic); 21 | } 22 | } 23 | 24 | public override string ResolveString(int metadataToken) 25 | { 26 | var dynamicScope = s_scopeField.GetValue(m_ilGenerator); 27 | Debug.Assert(dynamicScope != null); 28 | 29 | return (string)dynamicScope.GetType().GetMethod("GetString", BindingFlags.Instance | BindingFlags.NonPublic) 30 | .Invoke(dynamicScope, new object[] { metadataToken }); 31 | } 32 | 33 | public override MemberInfo ResolveMember(int metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments) 34 | { 35 | var dynamicScope = s_scopeField.GetValue(m_ilGenerator); 36 | Debug.Assert(dynamicScope != null); 37 | 38 | var handle = dynamicScope.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.NonPublic) 39 | .Invoke(dynamicScope, new object[] { metadataToken }); 40 | 41 | Debug.Assert(handle != null); 42 | 43 | if (handle is RuntimeTypeHandle typeHandle) 44 | { 45 | return TypeInfo.GetTypeFromHandle(typeHandle).GetTypeInfo(); 46 | } 47 | 48 | if (handle is RuntimeMethodHandle methodHandle) 49 | { 50 | return MethodBase.GetMethodFromHandle(methodHandle); 51 | } 52 | 53 | if (handle.GetType().ToString() == "System.Reflection.Emit.GenericMethodInfo") 54 | { 55 | var methodHandleFieldInfo = handle.GetType().GetField("m_methodHandle", BindingFlags.Instance | BindingFlags.NonPublic); 56 | var typeHandleFieldInfo = handle.GetType().GetField("m_context", BindingFlags.Instance | BindingFlags.NonPublic); 57 | return MethodBase.GetMethodFromHandle((RuntimeMethodHandle)methodHandleFieldInfo.GetValue(handle), (RuntimeTypeHandle)typeHandleFieldInfo.GetValue(handle)); 58 | } 59 | 60 | if (handle is RuntimeFieldHandle fieldHandle) 61 | { 62 | return FieldInfo.GetFieldFromHandle(fieldHandle); 63 | } 64 | 65 | if (handle.GetType().ToString() == "System.Reflection.Emit.GenericFieldInfo") 66 | { 67 | var fieldHandleFieldInfo = handle.GetType().GetField("m_fieldHandle", BindingFlags.Instance | BindingFlags.NonPublic); 68 | var typeHandleFieldInfo = handle.GetType().GetField("m_context", BindingFlags.Instance | BindingFlags.NonPublic); 69 | return FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)fieldHandleFieldInfo.GetValue(handle), (RuntimeTypeHandle)typeHandleFieldInfo.GetValue(handle)); 70 | } 71 | 72 | if (handle is DynamicMethod dynamicMethod) 73 | { 74 | return dynamicMethod; 75 | } 76 | 77 | throw new NotSupportedException(handle.ToString()); 78 | } 79 | 80 | public override byte[] ResolveSignature(int metadataToken) 81 | { 82 | var dynamicScope = s_scopeField.GetValue(m_ilGenerator); 83 | Debug.Assert(dynamicScope != null); 84 | 85 | return (byte[])dynamicScope.GetType().GetMethod("ResolveSignature", BindingFlags.Instance | BindingFlags.NonPublic) 86 | .Invoke(dynamicScope, new object[] { metadataToken, 0 }); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/Pose/Helpers/StubHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | 6 | using Pose.Extensions; 7 | 8 | namespace Pose.Helpers 9 | { 10 | internal static class StubHelper 11 | { 12 | public static IntPtr GetMethodPointer(MethodBase method) 13 | { 14 | if (method is DynamicMethod) 15 | { 16 | var methodDescriptior = typeof(DynamicMethod).GetMethod("GetMethodDescriptor", BindingFlags.Instance | BindingFlags.NonPublic); 17 | return ((RuntimeMethodHandle)methodDescriptior.Invoke(method as DynamicMethod, null)).GetFunctionPointer(); 18 | } 19 | 20 | return method.MethodHandle.GetFunctionPointer(); 21 | } 22 | 23 | public static object GetShimDelegateTarget(int index) 24 | => PoseContext.Shims[index].Replacement.Target; 25 | 26 | public static MethodInfo GetShimReplacementMethod(int index) 27 | => PoseContext.Shims[index].Replacement.Method; 28 | 29 | public static int GetIndexOfMatchingShim(MethodBase methodBase, Type type, object obj) 30 | { 31 | if (methodBase.IsStatic || obj == null) 32 | return Array.FindIndex(PoseContext.Shims, s => s.Original == methodBase); 33 | 34 | int index = Array.FindIndex(PoseContext.Shims, 35 | s => Object.ReferenceEquals(obj, s.Instance) && s.Original == methodBase); 36 | 37 | if (index == -1) 38 | return Array.FindIndex(PoseContext.Shims, 39 | s => SignatureEquals(s, type, methodBase) && s.Instance == null); 40 | 41 | return index; 42 | } 43 | 44 | public static int GetIndexOfMatchingShim(MethodBase methodBase, object obj) 45 | => GetIndexOfMatchingShim(methodBase, methodBase.DeclaringType, obj); 46 | 47 | public static MethodInfo DevirtualizeMethod(object obj, MethodInfo virtualMethod) 48 | { 49 | return DevirtualizeMethod(obj.GetType(), virtualMethod); 50 | } 51 | 52 | public static MethodInfo DevirtualizeMethod(Type thisType, MethodInfo virtualMethod) 53 | { 54 | if (thisType == virtualMethod.DeclaringType) return virtualMethod; 55 | BindingFlags bindingFlags = BindingFlags.Instance | (virtualMethod.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic); 56 | Type[] types = virtualMethod.GetParameters().Select(p => p.ParameterType).ToArray(); 57 | return thisType.GetMethod(virtualMethod.Name, bindingFlags, null, types, null); 58 | } 59 | 60 | public static Module GetOwningModule() => typeof(StubHelper).Module; 61 | 62 | public static bool IsIntrinsic(MethodBase method) 63 | { 64 | return method.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.IntrinsicAttribute") || 65 | method.DeclaringType.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.IntrinsicAttribute") || 66 | method.DeclaringType.FullName.StartsWith("System.Runtime.Intrinsics"); 67 | } 68 | 69 | public static string CreateStubNameFromMethod(string prefix, MethodBase method) 70 | { 71 | string name = prefix; 72 | name += "_"; 73 | name += method.DeclaringType.ToString(); 74 | name += "_"; 75 | name += method.Name; 76 | 77 | if (!method.IsConstructor) 78 | { 79 | var genericArguments = method.GetGenericArguments(); 80 | if (genericArguments.Length > 0) 81 | { 82 | name += "["; 83 | name += string.Join(',', genericArguments.Select(g => g.Name)); 84 | name += "]"; 85 | } 86 | } 87 | 88 | return name; 89 | } 90 | 91 | private static bool SignatureEquals(Shim shim, Type type, MethodBase method) 92 | { 93 | if (shim.Type == null || type == shim.Type) 94 | return $"{shim.Type}::{shim.Original.ToString()}" == $"{type}::{method.ToString()}"; 95 | 96 | if (type.IsSubclassOf(shim.Type)) 97 | { 98 | if ((shim.Original.IsAbstract || !shim.Original.IsVirtual) 99 | || (shim.Original.IsVirtual && !method.IsOverride())) 100 | { 101 | return $"{shim.Original.ToString()}" == $"{method.ToString()}"; 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /test/Pose.Tests/ShimTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using System.Threading; 6 | 7 | using Pose.Exceptions; 8 | using Pose.Helpers; 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 10 | 11 | using static System.Console; 12 | 13 | namespace Pose.Tests 14 | { 15 | [TestClass] 16 | public class ShimTests 17 | { 18 | [TestMethod] 19 | public void TestReplace() 20 | { 21 | Shim shim = Shim.Replace(() => Console.WriteLine("")); 22 | 23 | Assert.AreEqual(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), shim.Original); 24 | Assert.IsNull(shim.Replacement); 25 | } 26 | 27 | [TestMethod] 28 | public void TestReplaceWithInstanceVariable() 29 | { 30 | ShimTests shimTests = new ShimTests(); 31 | Shim shim = Shim.Replace(() => shimTests.TestReplace()); 32 | 33 | Assert.AreEqual(typeof(ShimTests).GetMethod("TestReplace"), shim.Original); 34 | Assert.AreSame(shimTests, shim.Instance); 35 | Assert.IsNull(shim.Replacement); 36 | } 37 | 38 | [TestMethod] 39 | public void TestShimReplaceWithInvalidSignature() 40 | { 41 | ShimTests shimTests = new ShimTests(); 42 | Shim shim = Shim.Replace(() => shimTests.TestReplace()); 43 | Assert.ThrowsException( 44 | () => Shim.Replace(() => shimTests.TestReplace()).With(() => { })); 45 | Assert.ThrowsException( 46 | () => Shim.Replace(() => Console.WriteLine(Is.A())).With(() => { })); 47 | } 48 | 49 | [TestMethod] 50 | public void TestShimReplaceWith() 51 | { 52 | ShimTests shimTests = new ShimTests(); 53 | Action action = new Action(() => { }); 54 | Action actionInstance = new Action((s) => { }); 55 | 56 | Shim shim = Shim.Replace(() => Console.WriteLine()).With(action); 57 | Shim shim1 = Shim.Replace(() => shimTests.TestReplace()).With(actionInstance); 58 | 59 | Assert.AreEqual(typeof(Console).GetMethod("WriteLine", Type.EmptyTypes), shim.Original); 60 | Assert.AreEqual(action, shim.Replacement); 61 | 62 | Assert.AreEqual(typeof(ShimTests).GetMethod("TestReplace"), shim1.Original); 63 | Assert.AreSame(shimTests, shim1.Instance); 64 | Assert.AreEqual(actionInstance, shim1.Replacement); 65 | } 66 | 67 | [TestMethod] 68 | public void TestReplacePropertyGetter() 69 | { 70 | Shim shim = Shim.Replace(() => Thread.CurrentThread.CurrentCulture); 71 | 72 | Assert.AreEqual(typeof(Thread).GetProperty(nameof(Thread.CurrentCulture), typeof(CultureInfo)).GetMethod, shim.Original); 73 | Assert.IsNull(shim.Replacement); 74 | } 75 | 76 | [TestMethod] 77 | public void TestReplacePropertySetter() 78 | { 79 | Shim shim = Shim.Replace(() => Is.A().CurrentCulture, true); 80 | 81 | Assert.AreEqual(typeof(Thread).GetProperty(nameof(Thread.CurrentCulture), typeof(CultureInfo)).SetMethod, shim.Original); 82 | Assert.IsNull(shim.Replacement); 83 | } 84 | 85 | 86 | [TestMethod] 87 | [Ignore] 88 | public void TestReplacePropertySetterAction() 89 | { 90 | var getterExecuted = false; 91 | var getterShim = Shim.Replace(() => Is.A().CurrentCulture) 92 | .With((Thread t) => 93 | { 94 | getterExecuted = true; 95 | return t.CurrentCulture; 96 | }); 97 | var setterExecuted = false; 98 | var setterShim = Shim.Replace(() => Is.A().CurrentCulture, true) 99 | .With((Thread t, CultureInfo value) => 100 | { 101 | setterExecuted = true; 102 | t.CurrentCulture = value; 103 | }); 104 | 105 | var currentCultureProperty = typeof(Thread).GetProperty(nameof(Thread.CurrentCulture), typeof(CultureInfo)); 106 | Assert.AreEqual(currentCultureProperty.GetMethod, getterShim.Original); 107 | Assert.AreEqual(currentCultureProperty.SetMethod, setterShim.Original); 108 | 109 | PoseContext.Isolate(() => 110 | { 111 | var oldCulture = Thread.CurrentThread.CurrentCulture; 112 | Thread.CurrentThread.CurrentCulture = oldCulture; 113 | }, getterShim, setterShim); 114 | 115 | Assert.IsTrue(getterExecuted, "Getter not executed"); 116 | Assert.IsTrue(setterExecuted, "Setter not executed"); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Pose/Shim.Delegates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Pose.Delegates; 3 | 4 | namespace Pose 5 | { 6 | public partial class Shim 7 | { 8 | public Shim With(Delegate replacement) => WithImpl(replacement); 9 | 10 | public Shim With(Action replacement) => WithImpl(replacement); 11 | 12 | public Shim With(Action replacement) => WithImpl(replacement); 13 | 14 | public Shim With(ActionRef replacement) => WithImpl(replacement); 15 | 16 | public Shim With(Action replacement) => WithImpl(replacement); 17 | 18 | public Shim With(ActionRef replacement) => WithImpl(replacement); 19 | 20 | public Shim With(Action replacement) => WithImpl(replacement); 21 | 22 | public Shim With(ActionRef replacement) => WithImpl(replacement); 23 | 24 | public Shim With(Action replacement) 25 | => WithImpl(replacement); 26 | 27 | public Shim With(ActionRef replacement) 28 | => WithImpl(replacement); 29 | 30 | public Shim With(Action replacement) 31 | => WithImpl(replacement); 32 | 33 | public Shim With(ActionRef replacement) 34 | => WithImpl(replacement); 35 | 36 | public Shim With(Action replacement) 37 | => WithImpl(replacement); 38 | 39 | public Shim With(ActionRef replacement) 40 | => WithImpl(replacement); 41 | 42 | public Shim With(Action replacement) 43 | => WithImpl(replacement); 44 | 45 | public Shim With(ActionRef replacement) 46 | => WithImpl(replacement); 47 | 48 | public Shim With(Action replacement) 49 | => WithImpl(replacement); 50 | 51 | public Shim With(ActionRef replacement) 52 | => WithImpl(replacement); 53 | 54 | public Shim With(Action replacement) 55 | => WithImpl(replacement); 56 | 57 | public Shim With(ActionRef replacement) 58 | => WithImpl(replacement); 59 | 60 | public Shim With(Action replacement) 61 | => WithImpl(replacement); 62 | 63 | public Shim With(ActionRef replacement) 64 | => WithImpl(replacement); 65 | 66 | public Shim With(Func replacement) => WithImpl(replacement); 67 | 68 | public Shim With(Func replacement) => WithImpl(replacement); 69 | 70 | public Shim With(FuncRef replacement) => WithImpl(replacement); 71 | 72 | public Shim With(Func replacement) => WithImpl(replacement); 73 | 74 | public Shim With(FuncRef replacement) => WithImpl(replacement); 75 | 76 | public Shim With(Func replacement) => WithImpl(replacement); 77 | 78 | public Shim With(FuncRef replacement) => WithImpl(replacement); 79 | 80 | public Shim With(Func replacement) 81 | => WithImpl(replacement); 82 | 83 | public Shim With(FuncRef replacement) 84 | => WithImpl(replacement); 85 | 86 | public Shim With(Func replacement) 87 | => WithImpl(replacement); 88 | 89 | public Shim With(FuncRef replacement) 90 | => WithImpl(replacement); 91 | 92 | public Shim With(Func replacement) 93 | => WithImpl(replacement); 94 | 95 | public Shim With(FuncRef replacement) 96 | => WithImpl(replacement); 97 | 98 | public Shim With(Func replacement) 99 | => WithImpl(replacement); 100 | 101 | public Shim With(FuncRef replacement) 102 | => WithImpl(replacement); 103 | 104 | public Shim With(Func replacement) 105 | => WithImpl(replacement); 106 | 107 | public Shim With(FuncRef replacement) 108 | => WithImpl(replacement); 109 | 110 | public Shim With(Func replacement) 111 | => WithImpl(replacement); 112 | 113 | public Shim With(FuncRef replacement) 114 | => WithImpl(replacement); 115 | 116 | public Shim With(Func replacement) 117 | => WithImpl(replacement); 118 | 119 | public Shim With(FuncRef replacement) 120 | => WithImpl(replacement); 121 | } 122 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Windows build status](https://ci.appveyor.com/api/projects/status/github/tonerdo/pose?branch=master&svg=true)](https://ci.appveyor.com/project/tonerdo/pose) 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 3 | [![NuGet version](https://badge.fury.io/nu/Pose.svg)](https://www.nuget.org/packages/Pose) 4 | # Pose 5 | 6 | Pose allows you to replace any .NET method (including static and non-virtual) with a delegate. It is similar to [Microsoft Fakes](https://msdn.microsoft.com/en-us/library/hh549175.aspx) but unlike it Pose is implemented _entirely_ in managed code (Reflection Emit API). Everything occurs at runtime and in-memory, no unmanaged Profiling APIs and no file system pollution with re-written assemblies. 7 | 8 | Pose is cross platform and runs anywhere .NET is supported. It targets .NET Standard 2.0 so it can be used across .NET platforms including .NET Framework, .NET Core, Mono and Xamarin. See version compatibility table [here](https://docs.microsoft.com/en-us/dotnet/standard/net-standard). 9 | 10 | ## Installation 11 | 12 | Available on [NuGet](https://www.nuget.org/packages/Pose/) 13 | 14 | Visual Studio: 15 | 16 | ```powershell 17 | PM> Install-Package Pose 18 | ``` 19 | 20 | .NET Core CLI: 21 | 22 | ```bash 23 | dotnet add package Pose 24 | ``` 25 | 26 | ## Usage 27 | 28 | Pose gives you the ability to create shims by way of the `Shim` class. Shims are basically objects that let you specify the method you want to replace as well as the replacement delegate. Delegate signatures (arguments and return type) must match that of the methods they replace. The `Is` class is used to create instances of a type and all code you want to apply your shims to is isolated using the `PoseContext` class. 29 | 30 | 31 | ### Shim static method 32 | 33 | ```csharp 34 | using Pose; 35 | 36 | Shim consoleShim = Shim.Replace(() => Console.WriteLine(Is.A())).With( 37 | delegate (string s) { Console.WriteLine("Hijacked: {0}", s); }); 38 | ``` 39 | 40 | ### Shim static property getter 41 | 42 | ```csharp 43 | using Pose; 44 | 45 | Shim dateTimeShim = Shim.Replace(() => DateTime.Now).With(() => new DateTime(2004, 4, 4)); 46 | ``` 47 | 48 | ### Shim static property setter 49 | 50 | ```csharp 51 | using Pose; 52 | 53 | Shim setterShim = Shim.Replace(() => Console.Title, true).With((string title) => { Console.Title = "My Title"; }); 54 | ``` 55 | 56 | ### Shim instance property getter 57 | 58 | ```csharp 59 | using Pose; 60 | 61 | class MyClass 62 | { 63 | public int MyProperty { get; set; } 64 | public void DoSomething() => Console.WriteLine("doing someting"); 65 | } 66 | 67 | Shim classPropShim = Shim.Replace(() => Is.A().MyProperty).With((MyClass @this) => 100); 68 | ``` 69 | 70 | ### Shim instance property setter 71 | 72 | ```csharp 73 | using Pose; 74 | 75 | Shim classPropShim = Shim.Replace(() => Is.A().MyProperty, true).With((MyClass @this, int prop) => { @this.MyProperty = prop * 10; }); 76 | ``` 77 | 78 | ### Shim constructor 79 | 80 | ```csharp 81 | using Pose; 82 | 83 | Shim ctorShim = Shim.Replace(() => new MyClass()).With(() => new MyClass() { MyProperty = 10 }); 84 | ``` 85 | 86 | ### Shim instance method of a Reference Type 87 | 88 | ```csharp 89 | using Pose; 90 | 91 | Shim classShim = Shim.Replace(() => Is.A().DoSomething()).With( 92 | delegate (MyClass @this) { Console.WriteLine("doing someting else"); }); 93 | ``` 94 | 95 | _Note: The first argument to an instance method replacement delegate is always the instance of the class_ 96 | 97 | ### Shim method of specific instance of a Reference Type 98 | 99 | ```csharp 100 | using Pose; 101 | 102 | MyClass myClass = new MyClass(); 103 | Shim myClassShim = Shim.Replace(() => myClass.DoSomething()).With( 104 | delegate (MyClass @this) { Console.WriteLine("doing someting else with myClass"); }); 105 | ``` 106 | 107 | ### Shim instance method of a Value Type 108 | 109 | ```csharp 110 | using Pose; 111 | 112 | Shim structShim = Shim.Replace(() => Is.A().DoSomething()).With( 113 | delegate (ref MyStruct @this) { Console.WriteLine("doing someting else"); }); 114 | ``` 115 | 116 | _Note: You cannot shim methods on specific instances of Value Types_ 117 | 118 | ### Isolating your code 119 | 120 | ```csharp 121 | // This block executes immediately 122 | PoseContext.Isolate(() => 123 | { 124 | // All code that executes within this block 125 | // is isolated and shimmed methods are replaced 126 | 127 | // Outputs "Hijacked: Hello World!" 128 | Console.WriteLine("Hello World!"); 129 | 130 | // Outputs "4/4/04 12:00:00 AM" 131 | Console.WriteLine(DateTime.Now); 132 | 133 | // Outputs "doing someting else" 134 | new MyClass().DoSomething(); 135 | 136 | // Outputs "doing someting else with myClass" 137 | myClass.DoSomething(); 138 | 139 | }, consoleShim, dateTimeShim, classPropShim, classShim, myClassShim, structShim); 140 | ``` 141 | 142 | ## Caveats & Limitations 143 | 144 | * **Breakpoints** - At this time any breakpoints set anywhere in the isolated code and its execution path will not be hit. However, breakpoints set within a shim replacement delegate are hit. 145 | * **Exceptions** - At this time all unhandled exceptions thrown in isolated code and its execution path are always wrapped in `System.Reflection.TargetInvocationException`. 146 | 147 | ## Roadmap 148 | 149 | * **Performance Improvements** - Pose can be used outside the context of unit tests. Better performance would make it suitable for use in production code, possibly to override legacy functionality. 150 | * **Exceptions Stack Trace** - Currently when exceptions are thrown in your own code under isolation, the supplied exception stack trace is quite confusing. Providing an undiluted exception stack trace is needed. 151 | 152 | ## Issues & Contributions 153 | 154 | If you find a bug or have a feature request, please report them at this repository's issues section. Contributions are highly welcome, however, except for very small changes kindly file an issue and let's have a discussion before you open a pull request. 155 | 156 | ## License 157 | 158 | This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. 159 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Csharp ### 2 | ## Ignore Visual Studio temporary files, build results, and 3 | ## files generated by popular Visual Studio add-ons. 4 | ## 5 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # .NET Core 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | **/Properties/launchSettings.json 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # Visual Studio code coverage results 118 | *.coverage 119 | *.coveragexml 120 | 121 | # NCrunch 122 | _NCrunch_* 123 | .*crunch*.local.xml 124 | nCrunchTemp_* 125 | 126 | # MightyMoose 127 | *.mm.* 128 | AutoTest.Net/ 129 | 130 | # Web workbench (sass) 131 | .sass-cache/ 132 | 133 | # Installshield output folder 134 | [Ee]xpress/ 135 | 136 | # DocProject is a documentation generator add-in 137 | DocProject/buildhelp/ 138 | DocProject/Help/*.HxT 139 | DocProject/Help/*.HxC 140 | DocProject/Help/*.hhc 141 | DocProject/Help/*.hhk 142 | DocProject/Help/*.hhp 143 | DocProject/Help/Html2 144 | DocProject/Help/html 145 | 146 | # Click-Once directory 147 | publish/ 148 | 149 | # Publish Web Output 150 | *.[Pp]ublish.xml 151 | *.azurePubxml 152 | # TODO: Uncomment the next line to ignore your web deploy settings. 153 | # By default, sensitive information, such as encrypted password 154 | # should be stored in the .pubxml.user file. 155 | #*.pubxml 156 | *.pubxml.user 157 | *.publishproj 158 | 159 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 160 | # checkin your Azure Web App publish settings, but sensitive information contained 161 | # in these scripts will be unencrypted 162 | PublishScripts/ 163 | 164 | # NuGet Packages 165 | *.nupkg 166 | # The packages folder can be ignored because of Package Restore 167 | **/packages/* 168 | # except build/, which is used as an MSBuild target. 169 | !**/packages/build/ 170 | # Uncomment if necessary however generally it will be regenerated when needed 171 | #!**/packages/repositories.config 172 | # NuGet v3's project.json files produces more ignorable files 173 | *.nuget.props 174 | *.nuget.targets 175 | 176 | # Microsoft Azure Build Output 177 | csx/ 178 | *.build.csdef 179 | 180 | # Microsoft Azure Emulator 181 | ecf/ 182 | rcf/ 183 | 184 | # Windows Store app package directories and files 185 | AppPackages/ 186 | BundleArtifacts/ 187 | Package.StoreAssociation.xml 188 | _pkginfo.txt 189 | 190 | # Visual Studio cache files 191 | # files ending in .cache can be ignored 192 | *.[Cc]ache 193 | # but keep track of directories ending in .cache 194 | !*.[Cc]ache/ 195 | 196 | # Others 197 | ClientBin/ 198 | ~$* 199 | *~ 200 | *.dbmdl 201 | *.dbproj.schemaview 202 | *.jfm 203 | *.pfx 204 | *.publishsettings 205 | orleans.codegen.cs 206 | 207 | # Since there are multiple workflows, uncomment next line to ignore bower_components 208 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 209 | #bower_components/ 210 | 211 | # RIA/Silverlight projects 212 | Generated_Code/ 213 | 214 | # Backup & report files from converting an old project file 215 | # to a newer Visual Studio version. Backup files are not needed, 216 | # because we have git ;-) 217 | _UpgradeReport_Files/ 218 | Backup*/ 219 | UpgradeLog*.XML 220 | UpgradeLog*.htm 221 | 222 | # SQL Server files 223 | *.mdf 224 | *.ldf 225 | *.ndf 226 | 227 | # Business Intelligence projects 228 | *.rdl.data 229 | *.bim.layout 230 | *.bim_*.settings 231 | 232 | # Microsoft Fakes 233 | FakesAssemblies/ 234 | 235 | # GhostDoc plugin setting file 236 | *.GhostDoc.xml 237 | 238 | # Node.js Tools for Visual Studio 239 | .ntvs_analysis.dat 240 | node_modules/ 241 | 242 | # Typescript v1 declaration files 243 | typings/ 244 | 245 | # Visual Studio 6 build log 246 | *.plg 247 | 248 | # Visual Studio 6 workspace options file 249 | *.opt 250 | 251 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 252 | *.vbw 253 | 254 | # Visual Studio LightSwitch build output 255 | **/*.HTMLClient/GeneratedArtifacts 256 | **/*.DesktopClient/GeneratedArtifacts 257 | **/*.DesktopClient/ModelManifest.xml 258 | **/*.Server/GeneratedArtifacts 259 | **/*.Server/ModelManifest.xml 260 | _Pvt_Extensions 261 | 262 | # Paket dependency manager 263 | .paket/paket.exe 264 | paket-files/ 265 | 266 | # FAKE - F# Make 267 | .fake/ 268 | 269 | # JetBrains Rider 270 | .idea/ 271 | *.sln.iml 272 | 273 | # CodeRush 274 | .cr/ 275 | 276 | # Python Tools for Visual Studio (PTVS) 277 | __pycache__/ 278 | *.pyc 279 | 280 | # Cake - Uncomment if you are using it 281 | # tools/** 282 | # !tools/packages.config 283 | 284 | # Telerik's JustMock configuration file 285 | *.jmconfig 286 | 287 | # BizTalk build output 288 | *.btp.cs 289 | *.btm.cs 290 | *.odx.cs 291 | *.xsd.cs 292 | 293 | # VSCode 294 | .vscode 295 | 296 | # OSX 297 | .DS_Store 298 | -------------------------------------------------------------------------------- /src/Pose/Helpers/ShimHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | using Pose.Exceptions; 8 | using Pose.Extensions; 9 | 10 | namespace Pose.Helpers 11 | { 12 | internal static class ShimHelper 13 | { 14 | public static MethodBase GetMethodFromExpression(Expression expression, bool setter, out Object instanceOrType) 15 | { 16 | switch (expression.NodeType) 17 | { 18 | case ExpressionType.MemberAccess: 19 | { 20 | MemberExpression memberExpression = expression as MemberExpression; 21 | MemberInfo memberInfo = memberExpression.Member; 22 | if (memberInfo.MemberType == MemberTypes.Property) 23 | { 24 | PropertyInfo propertyInfo = memberInfo as PropertyInfo; 25 | instanceOrType = GetObjectInstanceOrType(memberExpression.Expression); 26 | return setter ? propertyInfo.GetSetMethod() : propertyInfo.GetGetMethod(); 27 | } 28 | else 29 | throw new NotImplementedException("Unsupported expression"); 30 | } 31 | case ExpressionType.Call: 32 | MethodCallExpression methodCallExpression = expression as MethodCallExpression; 33 | instanceOrType = GetObjectInstanceOrType(methodCallExpression.Object); 34 | return methodCallExpression.Method; 35 | case ExpressionType.New: 36 | NewExpression newExpression = expression as NewExpression; 37 | instanceOrType = null; 38 | return newExpression.Constructor; 39 | default: 40 | throw new NotImplementedException("Unsupported expression"); 41 | } 42 | } 43 | 44 | public static void ValidateReplacementMethodSignature(MethodBase original, MethodInfo replacement, Type type, bool setter) 45 | { 46 | bool isValueType = original.DeclaringType.IsValueType; 47 | bool isStatic = original.IsStatic; 48 | bool isConstructor = original.IsConstructor; 49 | bool isStaticOrConstructor = isStatic || isConstructor; 50 | 51 | Type vaildReturnType = isConstructor ? original.DeclaringType : (original as MethodInfo).ReturnType; 52 | vaildReturnType = setter ? typeof(void) : vaildReturnType; 53 | Type shimReturnType = replacement.ReturnType; 54 | 55 | Type validOwningType = type; 56 | Type shimOwningType = isStaticOrConstructor 57 | ? validOwningType : replacement.GetParameters().Select(p => p.ParameterType).FirstOrDefault(); 58 | 59 | var validParameterTypes = original.GetParameters().Select(p => p.ParameterType); 60 | var shimParameterTypes = replacement.GetParameters() 61 | .Select(p => p.ParameterType) 62 | .Skip(isStaticOrConstructor ? 0 : 1); 63 | 64 | if (vaildReturnType != shimReturnType) 65 | throw new InvalidShimSignatureException("Mismatched return types"); 66 | 67 | if (!isStaticOrConstructor) 68 | { 69 | if (isValueType && !shimOwningType.IsByRef) 70 | throw new InvalidShimSignatureException("ValueType instances must be passed by ref"); 71 | } 72 | 73 | if ((isValueType && !isStaticOrConstructor ? validOwningType.MakeByRefType() : validOwningType) != shimOwningType) 74 | throw new InvalidShimSignatureException("Mismatched instance types"); 75 | 76 | if (validParameterTypes.Count() != shimParameterTypes.Count()) 77 | throw new InvalidShimSignatureException("Parameters count do not match"); 78 | 79 | for (int i = 0; i < validParameterTypes.Count(); i++) 80 | { 81 | if (validParameterTypes.ElementAt(i) != shimParameterTypes.ElementAt(i)) 82 | throw new InvalidShimSignatureException($"Parameter types at {i} do not match"); 83 | } 84 | } 85 | 86 | public static object GetObjectInstanceOrType(Expression expression) 87 | { 88 | object instanceOrType = null; 89 | switch (expression?.NodeType) 90 | { 91 | case ExpressionType.MemberAccess: 92 | { 93 | MemberExpression memberExpression = expression as MemberExpression; 94 | ConstantExpression constantExpression = memberExpression.Expression as ConstantExpression; 95 | if (memberExpression.Member.MemberType == MemberTypes.Field) 96 | { 97 | FieldInfo fieldInfo = (memberExpression.Member as FieldInfo); 98 | var obj = fieldInfo.IsStatic ? null : constantExpression.Value; 99 | instanceOrType = fieldInfo.GetValue(obj); 100 | } 101 | else if (memberExpression.Member.MemberType == MemberTypes.Property) 102 | { 103 | PropertyInfo propertyInfo = (memberExpression.Member as PropertyInfo); 104 | var obj = propertyInfo.GetMethod.IsStatic ? null : constantExpression.Value; 105 | instanceOrType = propertyInfo.GetValue(obj); 106 | } 107 | EnsureInstanceNotValueType(instanceOrType); 108 | break; 109 | } 110 | case ExpressionType.Call: 111 | { 112 | MethodCallExpression methodCallExpression = expression as MethodCallExpression; 113 | MethodInfo methodInfo = methodCallExpression.Method; 114 | instanceOrType = methodInfo.GetGenericArguments().FirstOrDefault(); 115 | break; 116 | } 117 | default: 118 | return null; 119 | } 120 | 121 | return instanceOrType; 122 | } 123 | 124 | private static void EnsureInstanceNotValueType(object instance) 125 | { 126 | if (instance.GetType().IsSubclassOf(typeof(ValueType))) 127 | throw new NotSupportedException("You cannot replace methods on specific value type instances"); 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /src/Pose/IL/MethodRewriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Reflection; 6 | using System.Reflection.Emit; 7 | using System.Runtime.CompilerServices; 8 | 9 | using Mono.Reflection; 10 | 11 | using Pose.Extensions; 12 | using Pose.Helpers; 13 | using Pose.IL.DebugHelpers; 14 | 15 | namespace Pose.IL 16 | { 17 | internal class MethodRewriter 18 | { 19 | private MethodBase m_method; 20 | 21 | private Type m_owningType; 22 | 23 | private bool m_isInterfaceDispatch; 24 | 25 | private int m_exceptionBlockLevel; 26 | 27 | private TypeInfo m_constrainedType; 28 | 29 | private static List s_IngoredOpCodes = new List { OpCodes.Endfilter, OpCodes.Endfinally }; 30 | 31 | public static MethodRewriter CreateRewriter(MethodBase method, bool isInterfaceDispatch) 32 | { 33 | return new MethodRewriter { m_method = method, m_owningType = method.DeclaringType, m_isInterfaceDispatch = isInterfaceDispatch }; 34 | } 35 | 36 | public MethodBase Rewrite() 37 | { 38 | List parameterTypes = new List(); 39 | if (!m_method.IsStatic) 40 | { 41 | Type thisType = m_isInterfaceDispatch ? typeof(object) : m_owningType; 42 | if (!m_isInterfaceDispatch && m_owningType.IsValueType) 43 | { 44 | thisType = thisType.MakeByRefType(); 45 | } 46 | 47 | parameterTypes.Add(thisType); 48 | } 49 | 50 | parameterTypes.AddRange(m_method.GetParameters().Select(p => p.ParameterType)); 51 | Type returnType = m_method.IsConstructor ? typeof(void) : (m_method as MethodInfo).ReturnType; 52 | 53 | DynamicMethod dynamicMethod = new DynamicMethod( 54 | StubHelper.CreateStubNameFromMethod("impl", m_method), 55 | returnType, 56 | parameterTypes.ToArray(), 57 | StubHelper.GetOwningModule(), 58 | true); 59 | 60 | var methodBody = m_method.GetMethodBody(); 61 | var locals = methodBody.LocalVariables; 62 | var targetInstructions = new Dictionary(); 63 | var handlers = new List(); 64 | 65 | var ilGenerator = dynamicMethod.GetILGenerator(); 66 | var instructions = m_method.GetInstructions(); 67 | 68 | foreach (var clause in methodBody.ExceptionHandlingClauses) 69 | { 70 | ExceptionHandler handler = new ExceptionHandler(); 71 | handler.Flags = clause.Flags; 72 | handler.CatchType = clause.Flags == ExceptionHandlingClauseOptions.Clause ? clause.CatchType : null; 73 | handler.TryStart = clause.TryOffset; 74 | handler.TryEnd = clause.TryOffset + clause.TryLength; 75 | handler.FilterStart = clause.Flags == ExceptionHandlingClauseOptions.Filter ? clause.FilterOffset : -1; 76 | handler.HandlerStart = clause.HandlerOffset; 77 | handler.HandlerEnd = clause.HandlerOffset + clause.HandlerLength; 78 | handlers.Add(handler); 79 | } 80 | 81 | foreach (var local in locals) 82 | ilGenerator.DeclareLocal(local.LocalType, local.IsPinned); 83 | 84 | var ifTargets = instructions 85 | .Where(i => (i.Operand as Instruction) != null) 86 | .Select(i => (i.Operand as Instruction)); 87 | 88 | foreach (Instruction instruction in ifTargets) 89 | targetInstructions.TryAdd(instruction.Offset, ilGenerator.DefineLabel()); 90 | 91 | var switchTargets = instructions 92 | .Where(i => (i.Operand as Instruction[]) != null) 93 | .Select(i => (i.Operand as Instruction[])); 94 | 95 | foreach (Instruction[] _instructions in switchTargets) 96 | { 97 | foreach (Instruction _instruction in _instructions) 98 | targetInstructions.TryAdd(_instruction.Offset, ilGenerator.DefineLabel()); 99 | } 100 | 101 | #if DEBUG 102 | Debug.WriteLine("\n" + m_method); 103 | #endif 104 | 105 | foreach (var instruction in instructions) 106 | { 107 | #if DEBUG 108 | Debug.WriteLine(instruction); 109 | #endif 110 | 111 | EmitILForExceptionHandlers(ilGenerator, instruction, handlers); 112 | 113 | if (targetInstructions.TryGetValue(instruction.Offset, out Label label)) 114 | ilGenerator.MarkLabel(label); 115 | 116 | if (s_IngoredOpCodes.Contains(instruction.OpCode)) continue; 117 | 118 | switch (instruction.OpCode.OperandType) 119 | { 120 | case OperandType.InlineNone: 121 | EmitILForInlineNone(ilGenerator, instruction); 122 | break; 123 | case OperandType.InlineI: 124 | EmitILForInlineI(ilGenerator, instruction); 125 | break; 126 | case OperandType.InlineI8: 127 | EmitILForInlineI8(ilGenerator, instruction); 128 | break; 129 | case OperandType.ShortInlineI: 130 | EmitILForShortInlineI(ilGenerator, instruction); 131 | break; 132 | case OperandType.InlineR: 133 | EmitILForInlineR(ilGenerator, instruction); 134 | break; 135 | case OperandType.ShortInlineR: 136 | EmitILForShortInlineR(ilGenerator, instruction); 137 | break; 138 | case OperandType.InlineString: 139 | EmitILForInlineString(ilGenerator, instruction); 140 | break; 141 | case OperandType.ShortInlineBrTarget: 142 | case OperandType.InlineBrTarget: 143 | EmitILForInlineBrTarget(ilGenerator, instruction, targetInstructions); 144 | break; 145 | case OperandType.InlineSwitch: 146 | EmitILForInlineSwitch(ilGenerator, instruction, targetInstructions); 147 | break; 148 | case OperandType.ShortInlineVar: 149 | case OperandType.InlineVar: 150 | EmitILForInlineVar(ilGenerator, instruction); 151 | break; 152 | case OperandType.InlineTok: 153 | case OperandType.InlineType: 154 | case OperandType.InlineField: 155 | case OperandType.InlineMethod: 156 | EmitILForInlineMember(ilGenerator, instruction); 157 | break; 158 | default: 159 | throw new NotSupportedException(instruction.OpCode.OperandType.ToString()); 160 | } 161 | } 162 | 163 | #if DEBUG 164 | var ilBytes = ilGenerator.GetILBytes(); 165 | var browsableDynamicMethod = new BrowsableDynamicMethod(dynamicMethod, new DynamicMethodBody(ilBytes, locals)); 166 | Debug.WriteLine("\n" + dynamicMethod); 167 | 168 | foreach (var instruction in browsableDynamicMethod.GetInstructions()) 169 | { 170 | Debug.WriteLine(instruction); 171 | } 172 | #endif 173 | return dynamicMethod; 174 | } 175 | 176 | private void EmitILForExceptionHandlers(ILGenerator ilGenerator, Instruction instruction, List handlers) 177 | { 178 | var tryBlocks = handlers.Where(h => h.TryStart == instruction.Offset).GroupBy(h => h.TryEnd); 179 | foreach (var tryBlock in tryBlocks) 180 | { 181 | ilGenerator.BeginExceptionBlock(); 182 | m_exceptionBlockLevel++; 183 | } 184 | 185 | var filterBlock = handlers.FirstOrDefault(h => h.FilterStart == instruction.Offset); 186 | if (filterBlock != null) 187 | { 188 | ilGenerator.BeginExceptFilterBlock(); 189 | } 190 | 191 | var handler = handlers.FirstOrDefault(h => h.HandlerEnd == instruction.Offset); 192 | if (handler != null) 193 | { 194 | if (handler.Flags == ExceptionHandlingClauseOptions.Finally) 195 | { 196 | // Finally blocks are always the last handler 197 | ilGenerator.EndExceptionBlock(); 198 | m_exceptionBlockLevel--; 199 | } 200 | else if (handler.HandlerEnd == handlers.Where(h => h.TryStart == handler.TryStart && h.TryEnd == handler.TryEnd).Max(h => h.HandlerEnd)) 201 | { 202 | // We're dealing with the last catch block 203 | ilGenerator.EndExceptionBlock(); 204 | m_exceptionBlockLevel--; 205 | } 206 | } 207 | 208 | var catchOrFinallyBlock = handlers.FirstOrDefault(h => h.HandlerStart == instruction.Offset); 209 | if (catchOrFinallyBlock != null) 210 | { 211 | if (catchOrFinallyBlock.Flags == ExceptionHandlingClauseOptions.Clause) 212 | { 213 | ilGenerator.BeginCatchBlock(catchOrFinallyBlock.CatchType); 214 | } 215 | else if (catchOrFinallyBlock.Flags == ExceptionHandlingClauseOptions.Filter) 216 | { 217 | ilGenerator.BeginCatchBlock(null); 218 | } 219 | else if (catchOrFinallyBlock.Flags == ExceptionHandlingClauseOptions.Finally) 220 | { 221 | ilGenerator.BeginFinallyBlock(); 222 | } 223 | else 224 | { 225 | // No support for fault blocks 226 | throw new NotSupportedException(); 227 | } 228 | } 229 | } 230 | 231 | private void EmitThisPointerAccessForBoxedValueType(ILGenerator ilGenerator) 232 | { 233 | ilGenerator.Emit(OpCodes.Call, typeof(Unsafe).GetMethod("Unbox").MakeGenericMethod(m_method.DeclaringType)); 234 | } 235 | 236 | private void EmitILForInlineNone(ILGenerator ilGenerator, Instruction instruction) 237 | { 238 | ilGenerator.Emit(instruction.OpCode); 239 | if (m_isInterfaceDispatch && m_owningType.IsValueType && instruction.OpCode == OpCodes.Ldarg_0) 240 | { 241 | EmitThisPointerAccessForBoxedValueType(ilGenerator); 242 | } 243 | } 244 | 245 | private void EmitILForInlineI(ILGenerator ilGenerator, Instruction instruction) 246 | => ilGenerator.Emit(instruction.OpCode, (int)instruction.Operand); 247 | 248 | private void EmitILForInlineI8(ILGenerator ilGenerator, Instruction instruction) 249 | => ilGenerator.Emit(instruction.OpCode, (long)instruction.Operand); 250 | 251 | private void EmitILForShortInlineI(ILGenerator ilGenerator, Instruction instruction) 252 | { 253 | if (instruction.OpCode == OpCodes.Ldc_I4_S) 254 | ilGenerator.Emit(instruction.OpCode, (sbyte)instruction.Operand); 255 | else 256 | ilGenerator.Emit(instruction.OpCode, (byte)instruction.Operand); 257 | } 258 | 259 | private void EmitILForInlineR(ILGenerator ilGenerator, Instruction instruction) 260 | => ilGenerator.Emit(instruction.OpCode, (double)instruction.Operand); 261 | 262 | private void EmitILForShortInlineR(ILGenerator ilGenerator, Instruction instruction) 263 | => ilGenerator.Emit(instruction.OpCode, (float)instruction.Operand); 264 | 265 | private void EmitILForInlineString(ILGenerator ilGenerator, Instruction instruction) 266 | => ilGenerator.Emit(instruction.OpCode, (string)instruction.Operand); 267 | 268 | private void EmitILForInlineBrTarget(ILGenerator ilGenerator, 269 | Instruction instruction, Dictionary targetInstructions) 270 | { 271 | Label targetLabel = targetInstructions[(instruction.Operand as Instruction).Offset]; 272 | 273 | OpCode opCode = instruction.OpCode; 274 | 275 | // Offset values could change and not be short form anymore 276 | if (opCode == OpCodes.Br_S) opCode = OpCodes.Br; 277 | else if (opCode == OpCodes.Brfalse_S) opCode = OpCodes.Brfalse; 278 | else if (opCode == OpCodes.Brtrue_S) opCode = OpCodes.Brtrue; 279 | else if (opCode == OpCodes.Beq_S) opCode = OpCodes.Beq; 280 | else if (opCode == OpCodes.Bge_S) opCode = OpCodes.Bge; 281 | else if (opCode == OpCodes.Bgt_S) opCode = OpCodes.Bgt; 282 | else if (opCode == OpCodes.Ble_S) opCode = OpCodes.Ble; 283 | else if (opCode == OpCodes.Blt_S) opCode = OpCodes.Blt; 284 | else if (opCode == OpCodes.Bne_Un_S) opCode = OpCodes.Bne_Un; 285 | else if (opCode == OpCodes.Bge_Un_S) opCode = OpCodes.Bge_Un; 286 | else if (opCode == OpCodes.Bgt_Un_S) opCode = OpCodes.Bgt_Un; 287 | else if (opCode == OpCodes.Ble_Un_S) opCode = OpCodes.Ble_Un; 288 | else if (opCode == OpCodes.Blt_Un_S) opCode = OpCodes.Blt_Un; 289 | else if (opCode == OpCodes.Leave_S) opCode = OpCodes.Leave; 290 | 291 | // Check if 'Leave' opcode is being used in an exception block, 292 | // only emit it if that's not the case 293 | if (opCode == OpCodes.Leave && m_exceptionBlockLevel > 0) return; 294 | 295 | ilGenerator.Emit(opCode, targetLabel); 296 | } 297 | 298 | private void EmitILForInlineSwitch(ILGenerator ilGenerator, 299 | Instruction instruction, Dictionary targetInstructions) 300 | { 301 | Instruction[] switchInstructions = (Instruction[])instruction.Operand; 302 | Label[] targetLabels = new Label[switchInstructions.Length]; 303 | for (int i = 0; i < switchInstructions.Length; i++) 304 | targetLabels[i] = targetInstructions[switchInstructions[i].Offset]; 305 | ilGenerator.Emit(instruction.OpCode, targetLabels); 306 | } 307 | 308 | private void EmitILForInlineVar(ILGenerator ilGenerator, Instruction instruction) 309 | { 310 | int index = 0; 311 | if (instruction.OpCode.Name.Contains("loc")) 312 | { 313 | index = ((LocalVariableInfo)instruction.Operand).LocalIndex; 314 | } 315 | else 316 | { 317 | index = ((ParameterInfo)instruction.Operand).Position; 318 | index += m_method.IsStatic ? 0 : 1; 319 | } 320 | 321 | if (instruction.OpCode.OperandType == OperandType.ShortInlineVar) 322 | ilGenerator.Emit(instruction.OpCode, (byte)index); 323 | else 324 | ilGenerator.Emit(instruction.OpCode, (ushort)index); 325 | 326 | if (m_isInterfaceDispatch && m_owningType.IsValueType && instruction.OpCode.Name.StartsWith("ldarg") && index == 0) 327 | { 328 | EmitThisPointerAccessForBoxedValueType(ilGenerator); 329 | } 330 | } 331 | 332 | private void EmitILForType(ILGenerator ilGenerator, Instruction instruction, TypeInfo typeInfo) 333 | { 334 | if (instruction.OpCode == OpCodes.Constrained) 335 | { 336 | m_constrainedType = typeInfo; 337 | return; 338 | } 339 | 340 | ilGenerator.Emit(instruction.OpCode, typeInfo); 341 | } 342 | 343 | private void EmitILForConstructor(ILGenerator ilGenerator, Instruction instruction, ConstructorInfo constructorInfo) 344 | { 345 | if (constructorInfo.InCoreLibrary()) 346 | { 347 | // Don't attempt to rewrite unaccessible constructors in System.Private.CoreLib/mscorlib 348 | if (!constructorInfo.DeclaringType.IsPublic) goto forward; 349 | if (!constructorInfo.IsPublic && !constructorInfo.IsFamily && !constructorInfo.IsFamilyOrAssembly) goto forward; 350 | } 351 | 352 | if (instruction.OpCode == OpCodes.Call) 353 | { 354 | ilGenerator.Emit(OpCodes.Call, Stubs.GenerateStubForDirectCall(constructorInfo)); 355 | return; 356 | } 357 | 358 | if (instruction.OpCode == OpCodes.Newobj) 359 | { 360 | ilGenerator.Emit(OpCodes.Call, Stubs.GenerateStubForObjectInitialization(constructorInfo)); 361 | return; 362 | } 363 | 364 | if (instruction.OpCode == OpCodes.Ldftn) 365 | { 366 | ilGenerator.Emit(OpCodes.Call, Stubs.GenerateStubForDirectLoad(constructorInfo)); 367 | return; 368 | } 369 | 370 | // If we get here, then we haven't accounted for an opcode. 371 | // Throw exception to make this obvious. 372 | throw new NotSupportedException(instruction.OpCode.Name); 373 | 374 | forward: 375 | ilGenerator.Emit(instruction.OpCode, constructorInfo); 376 | } 377 | 378 | private void EmitILForMethod(ILGenerator ilGenerator, Instruction instruction, MethodInfo methodInfo) 379 | { 380 | if (methodInfo.InCoreLibrary()) 381 | { 382 | // Don't attempt to rewrite unaccessible methods in System.Private.CoreLib/mscorlib 383 | if (!methodInfo.DeclaringType.IsPublic) goto forward; 384 | if (!methodInfo.IsPublic && !methodInfo.IsFamily && !methodInfo.IsFamilyOrAssembly) goto forward; 385 | } 386 | 387 | if (instruction.OpCode == OpCodes.Call) 388 | { 389 | ilGenerator.Emit(OpCodes.Call, Stubs.GenerateStubForDirectCall(methodInfo)); 390 | return; 391 | } 392 | 393 | if (instruction.OpCode == OpCodes.Callvirt) 394 | { 395 | if (m_constrainedType != null) 396 | { 397 | ilGenerator.Emit(OpCodes.Call, Stubs.GenerateStubForVirtualCall(methodInfo, m_constrainedType)); 398 | m_constrainedType = null; 399 | return; 400 | } 401 | 402 | ilGenerator.Emit(OpCodes.Call, Stubs.GenerateStubForVirtualCall(methodInfo)); 403 | return; 404 | } 405 | 406 | if (instruction.OpCode == OpCodes.Ldftn) 407 | { 408 | ilGenerator.Emit(OpCodes.Call, Stubs.GenerateStubForDirectLoad(methodInfo)); 409 | return; 410 | } 411 | 412 | if (instruction.OpCode == OpCodes.Ldvirtftn) 413 | { 414 | ilGenerator.Emit(OpCodes.Call, Stubs.GenerateStubForVirtualLoad(methodInfo)); 415 | return; 416 | } 417 | 418 | forward: 419 | ilGenerator.Emit(instruction.OpCode, methodInfo); 420 | } 421 | 422 | private void EmitILForInlineMember(ILGenerator ilGenerator, Instruction instruction) 423 | { 424 | MemberInfo memberInfo = (MemberInfo)instruction.Operand; 425 | if (memberInfo.MemberType == MemberTypes.Field) 426 | { 427 | ilGenerator.Emit(instruction.OpCode, memberInfo as FieldInfo); 428 | } 429 | else if (memberInfo.MemberType == MemberTypes.TypeInfo 430 | || memberInfo.MemberType == MemberTypes.NestedType) 431 | { 432 | EmitILForType(ilGenerator, instruction, memberInfo as TypeInfo); 433 | } 434 | else if (memberInfo.MemberType == MemberTypes.Constructor) 435 | { 436 | EmitILForConstructor(ilGenerator, instruction, memberInfo as ConstructorInfo); 437 | } 438 | else if (memberInfo.MemberType == MemberTypes.Method) 439 | { 440 | EmitILForMethod(ilGenerator, instruction, memberInfo as MethodInfo); 441 | } 442 | else 443 | { 444 | throw new NotSupportedException(); 445 | } 446 | } 447 | } 448 | } -------------------------------------------------------------------------------- /src/Pose/IL/Stubs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | using System.Runtime.CompilerServices; 7 | using Pose.Helpers; 8 | 9 | namespace Pose.IL 10 | { 11 | internal static class Stubs 12 | { 13 | private static MethodInfo s_getMethodFromHandleMethod; 14 | 15 | private static MethodInfo s_createRewriterMethod; 16 | 17 | private static MethodInfo s_rewriteMethod; 18 | 19 | private static MethodInfo s_getMethodPointerMethod; 20 | 21 | private static MethodInfo s_devirtualizeMethodMethod; 22 | 23 | private static MethodInfo s_getTypeFromHandleMethod; 24 | 25 | private static MethodInfo s_getUninitializedObjectMethod; 26 | 27 | static Stubs() 28 | { 29 | s_getMethodFromHandleMethod = typeof(MethodBase).GetMethod("GetMethodFromHandle", new Type[] { typeof(RuntimeMethodHandle), typeof(RuntimeTypeHandle) }); 30 | s_createRewriterMethod = typeof(MethodRewriter).GetMethod("CreateRewriter", new Type[] { typeof(MethodBase), typeof(bool) }); 31 | s_rewriteMethod = typeof(MethodRewriter).GetMethod("Rewrite"); 32 | s_getMethodPointerMethod = typeof(StubHelper).GetMethod("GetMethodPointer"); 33 | s_devirtualizeMethodMethod = typeof(StubHelper).GetMethod("DevirtualizeMethod", new Type[] { typeof(object), typeof(MethodInfo) }); 34 | s_getTypeFromHandleMethod = typeof(Type).GetMethod("GetTypeFromHandle"); 35 | s_getUninitializedObjectMethod = typeof(RuntimeHelpers).GetMethod("GetUninitializedObject"); 36 | } 37 | 38 | public static DynamicMethod GenerateStubForDirectCall(MethodBase method) 39 | { 40 | Type returnType = method.IsConstructor ? typeof(void) : (method as MethodInfo).ReturnType; 41 | List signatureParamTypes = new List(); 42 | if (!method.IsStatic) 43 | { 44 | Type thisType = method.DeclaringType; 45 | if (thisType.IsValueType) 46 | { 47 | thisType = thisType.MakeByRefType(); 48 | } 49 | 50 | signatureParamTypes.Add(thisType); 51 | } 52 | 53 | signatureParamTypes.AddRange(method.GetParameters().Select(p => p.ParameterType)); 54 | 55 | DynamicMethod stub = new DynamicMethod( 56 | StubHelper.CreateStubNameFromMethod("stub_call", method), 57 | returnType, 58 | signatureParamTypes.ToArray(), 59 | StubHelper.GetOwningModule(), 60 | true); 61 | 62 | ILGenerator ilGenerator = stub.GetILGenerator(); 63 | 64 | if (method.GetMethodBody() == null || StubHelper.IsIntrinsic(method)) 65 | { 66 | // Method has no body or is a compiler intrinsic, 67 | // simply forward arguments to original or shim 68 | for (int i = 0; i < signatureParamTypes.Count; i++) 69 | { 70 | ilGenerator.Emit(OpCodes.Ldarg, i); 71 | } 72 | 73 | if (method.IsConstructor) 74 | ilGenerator.Emit(OpCodes.Call, (ConstructorInfo)method); 75 | else 76 | ilGenerator.Emit(OpCodes.Call, (MethodInfo)method); 77 | 78 | ilGenerator.Emit(OpCodes.Ret); 79 | return stub; 80 | } 81 | 82 | ilGenerator.DeclareLocal(typeof(IntPtr)); 83 | 84 | Label rewriteLabel = ilGenerator.DefineLabel(); 85 | Label returnLabel = ilGenerator.DefineLabel(); 86 | 87 | // Inject method info into instruction stream 88 | if (method.IsConstructor) 89 | ilGenerator.Emit(OpCodes.Ldtoken, (ConstructorInfo)method); 90 | else 91 | ilGenerator.Emit(OpCodes.Ldtoken, (MethodInfo)method); 92 | 93 | ilGenerator.Emit(OpCodes.Ldtoken, method.DeclaringType); 94 | ilGenerator.Emit(OpCodes.Call, s_getMethodFromHandleMethod); 95 | 96 | // Rewrite method 97 | ilGenerator.MarkLabel(rewriteLabel); 98 | ilGenerator.Emit(OpCodes.Ldc_I4_0); 99 | ilGenerator.Emit(OpCodes.Call, s_createRewriterMethod); 100 | ilGenerator.Emit(OpCodes.Call, s_rewriteMethod); 101 | 102 | // Retrieve pointer to rewritten method 103 | ilGenerator.Emit(OpCodes.Call, s_getMethodPointerMethod); 104 | ilGenerator.Emit(OpCodes.Stloc_0); 105 | 106 | // Setup stack and make indirect call 107 | for (int i = 0; i < signatureParamTypes.Count; i++) 108 | { 109 | ilGenerator.Emit(OpCodes.Ldarg, i); 110 | } 111 | ilGenerator.Emit(OpCodes.Ldloc_0); 112 | ilGenerator.EmitCalli(OpCodes.Calli, CallingConventions.Standard, returnType, signatureParamTypes.ToArray(), null); 113 | 114 | ilGenerator.MarkLabel(returnLabel); 115 | ilGenerator.Emit(OpCodes.Ret); 116 | 117 | return stub; 118 | } 119 | 120 | public static DynamicMethod GenerateStubForVirtualCall(MethodInfo method, TypeInfo constrainedType) 121 | { 122 | Type thisType = constrainedType.MakeByRefType(); 123 | MethodInfo actualMethod = StubHelper.DevirtualizeMethod(constrainedType, method); 124 | 125 | List signatureParamTypes = new List(); 126 | signatureParamTypes.Add(thisType); 127 | signatureParamTypes.AddRange(method.GetParameters().Select(p => p.ParameterType)); 128 | 129 | DynamicMethod stub = new DynamicMethod( 130 | StubHelper.CreateStubNameFromMethod("stub_callvirt", method), 131 | method.ReturnType, 132 | signatureParamTypes.ToArray(), 133 | StubHelper.GetOwningModule(), 134 | true); 135 | 136 | ILGenerator ilGenerator = stub.GetILGenerator(); 137 | 138 | if ((actualMethod.GetMethodBody() == null && !actualMethod.IsAbstract) || StubHelper.IsIntrinsic(actualMethod)) 139 | { 140 | // Method has no body or is a compiler intrinsic, 141 | // simply forward arguments to original or shim 142 | for (int i = 0; i < signatureParamTypes.Count; i++) 143 | { 144 | ilGenerator.Emit(OpCodes.Ldarg, i); 145 | } 146 | 147 | ilGenerator.Emit(OpCodes.Call, actualMethod); 148 | ilGenerator.Emit(OpCodes.Ret); 149 | return stub; 150 | } 151 | 152 | ilGenerator.DeclareLocal(typeof(IntPtr)); 153 | 154 | Label rewriteLabel = ilGenerator.DefineLabel(); 155 | Label returnLabel = ilGenerator.DefineLabel(); 156 | 157 | // Inject method info into instruction stream 158 | ilGenerator.Emit(OpCodes.Ldtoken, actualMethod); 159 | ilGenerator.Emit(OpCodes.Ldtoken, actualMethod.DeclaringType); 160 | ilGenerator.Emit(OpCodes.Call, s_getMethodFromHandleMethod); 161 | ilGenerator.Emit(OpCodes.Castclass, typeof(MethodInfo)); 162 | 163 | // Rewrite method 164 | ilGenerator.MarkLabel(rewriteLabel); 165 | ilGenerator.Emit(OpCodes.Ldc_I4_0); 166 | ilGenerator.Emit(OpCodes.Call, s_createRewriterMethod); 167 | ilGenerator.Emit(OpCodes.Call, s_rewriteMethod); 168 | ilGenerator.Emit(OpCodes.Castclass, typeof(MethodInfo)); 169 | 170 | // Retrieve pointer to rewritten method 171 | ilGenerator.Emit(OpCodes.Call, s_getMethodPointerMethod); 172 | ilGenerator.Emit(OpCodes.Stloc_0); 173 | 174 | // Setup stack and make indirect call 175 | for (int i = 0; i < signatureParamTypes.Count; i++) 176 | { 177 | ilGenerator.Emit(OpCodes.Ldarg, i); 178 | if (i == 0) 179 | { 180 | if (!constrainedType.IsValueType) 181 | { 182 | ilGenerator.Emit(OpCodes.Ldind_Ref); 183 | signatureParamTypes[i] = constrainedType; 184 | } 185 | else 186 | { 187 | if (actualMethod.DeclaringType != constrainedType) 188 | { 189 | ilGenerator.Emit(OpCodes.Ldobj, constrainedType); 190 | ilGenerator.Emit(OpCodes.Box, constrainedType); 191 | signatureParamTypes[i] = actualMethod.DeclaringType; 192 | } 193 | } 194 | } 195 | } 196 | ilGenerator.Emit(OpCodes.Ldloc_0); 197 | ilGenerator.EmitCalli(OpCodes.Calli, CallingConventions.Standard, method.ReturnType, signatureParamTypes.ToArray(), null); 198 | 199 | ilGenerator.MarkLabel(returnLabel); 200 | ilGenerator.Emit(OpCodes.Ret); 201 | 202 | return stub; 203 | } 204 | 205 | public static DynamicMethod GenerateStubForVirtualCall(MethodInfo method) 206 | { 207 | Type thisType = method.DeclaringType.IsInterface ? typeof(object) : method.DeclaringType; 208 | 209 | List signatureParamTypes = new List(); 210 | signatureParamTypes.Add(thisType); 211 | signatureParamTypes.AddRange(method.GetParameters().Select(p => p.ParameterType)); 212 | 213 | DynamicMethod stub = new DynamicMethod( 214 | StubHelper.CreateStubNameFromMethod("stub_callvirt", method), 215 | method.ReturnType, 216 | signatureParamTypes.ToArray(), 217 | StubHelper.GetOwningModule(), 218 | true); 219 | 220 | ILGenerator ilGenerator = stub.GetILGenerator(); 221 | 222 | if ((method.GetMethodBody() == null && !method.IsAbstract) || StubHelper.IsIntrinsic(method)) 223 | { 224 | // Method has no body or is a compiler intrinsic, 225 | // simply forward arguments to original or shim 226 | for (int i = 0; i < signatureParamTypes.Count; i++) 227 | { 228 | ilGenerator.Emit(OpCodes.Ldarg, i); 229 | } 230 | 231 | ilGenerator.Emit(OpCodes.Callvirt, method); 232 | ilGenerator.Emit(OpCodes.Ret); 233 | return stub; 234 | } 235 | 236 | ilGenerator.DeclareLocal(typeof(MethodInfo)); 237 | ilGenerator.DeclareLocal(typeof(IntPtr)); 238 | 239 | Label rewriteLabel = ilGenerator.DefineLabel(); 240 | Label returnLabel = ilGenerator.DefineLabel(); 241 | 242 | // Inject method info into instruction stream 243 | ilGenerator.Emit(OpCodes.Ldtoken, method); 244 | ilGenerator.Emit(OpCodes.Ldtoken, method.DeclaringType); 245 | ilGenerator.Emit(OpCodes.Call, s_getMethodFromHandleMethod); 246 | ilGenerator.Emit(OpCodes.Castclass, typeof(MethodInfo)); 247 | ilGenerator.Emit(OpCodes.Stloc_0); 248 | 249 | // Resolve virtual method to object type 250 | ilGenerator.Emit(OpCodes.Ldarg_0); 251 | ilGenerator.Emit(OpCodes.Ldloc_0); 252 | ilGenerator.Emit(OpCodes.Call, s_devirtualizeMethodMethod); 253 | 254 | // Rewrite resolved method 255 | ilGenerator.MarkLabel(rewriteLabel); 256 | ilGenerator.Emit(method.DeclaringType.IsInterface ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); 257 | ilGenerator.Emit(OpCodes.Call, s_createRewriterMethod); 258 | ilGenerator.Emit(OpCodes.Call, s_rewriteMethod); 259 | ilGenerator.Emit(OpCodes.Castclass, typeof(MethodInfo)); 260 | 261 | // Retrieve pointer to rewritten method 262 | ilGenerator.Emit(OpCodes.Call, s_getMethodPointerMethod); 263 | ilGenerator.Emit(OpCodes.Stloc_1); 264 | 265 | // Setup stack and make indirect call 266 | for (int i = 0; i < signatureParamTypes.Count; i++) 267 | { 268 | ilGenerator.Emit(OpCodes.Ldarg, i); 269 | } 270 | ilGenerator.Emit(OpCodes.Ldloc_1); 271 | ilGenerator.EmitCalli(OpCodes.Calli, CallingConventions.Standard, method.ReturnType, signatureParamTypes.ToArray(), null); 272 | 273 | ilGenerator.MarkLabel(returnLabel); 274 | ilGenerator.Emit(OpCodes.Ret); 275 | 276 | return stub; 277 | } 278 | 279 | public static DynamicMethod GenerateStubForObjectInitialization(ConstructorInfo constructor) 280 | { 281 | Type thisType = constructor.DeclaringType; 282 | if (thisType.IsValueType) 283 | { 284 | thisType = thisType.MakeByRefType(); 285 | } 286 | 287 | List signatureParamTypes = new List(); 288 | signatureParamTypes.Add(thisType); 289 | signatureParamTypes.AddRange(constructor.GetParameters().Select(p => p.ParameterType)); 290 | 291 | DynamicMethod stub = new DynamicMethod( 292 | StubHelper.CreateStubNameFromMethod("stub_newobj", constructor), 293 | constructor.DeclaringType, 294 | signatureParamTypes.Skip(1).ToArray(), 295 | StubHelper.GetOwningModule(), 296 | true); 297 | 298 | ILGenerator ilGenerator = stub.GetILGenerator(); 299 | 300 | if (constructor.GetMethodBody() == null || StubHelper.IsIntrinsic(constructor)) 301 | { 302 | // Constructor has no body or is a compiler intrinsic, 303 | // simply forward arguments to original or shim 304 | for (int i = 0; i < signatureParamTypes.Count - 1; i++) 305 | { 306 | ilGenerator.Emit(OpCodes.Ldarg, i); 307 | } 308 | 309 | ilGenerator.Emit(OpCodes.Newobj, constructor); 310 | ilGenerator.Emit(OpCodes.Ret); 311 | return stub; 312 | } 313 | 314 | ilGenerator.DeclareLocal(typeof(IntPtr)); 315 | ilGenerator.DeclareLocal(constructor.DeclaringType); 316 | 317 | Label rewriteLabel = ilGenerator.DefineLabel(); 318 | Label returnLabel = ilGenerator.DefineLabel(); 319 | 320 | // Inject method info into instruction stream 321 | ilGenerator.Emit(OpCodes.Ldtoken, constructor); 322 | ilGenerator.Emit(OpCodes.Ldtoken, constructor.DeclaringType); 323 | ilGenerator.Emit(OpCodes.Call, s_getMethodFromHandleMethod); 324 | 325 | // Rewrite method 326 | ilGenerator.MarkLabel(rewriteLabel); 327 | ilGenerator.Emit(OpCodes.Ldc_I4_0); 328 | ilGenerator.Emit(OpCodes.Call, s_createRewriterMethod); 329 | ilGenerator.Emit(OpCodes.Call, s_rewriteMethod); 330 | 331 | // Retrieve pointer to rewritten method 332 | ilGenerator.Emit(OpCodes.Call, s_getMethodPointerMethod); 333 | ilGenerator.Emit(OpCodes.Stloc_0); 334 | 335 | if (constructor.DeclaringType.IsValueType) 336 | { 337 | ilGenerator.Emit(OpCodes.Ldloca_S, (byte)1); 338 | ilGenerator.Emit(OpCodes.Dup); 339 | ilGenerator.Emit(OpCodes.Initobj, constructor.DeclaringType); 340 | } 341 | else 342 | { 343 | ilGenerator.Emit(OpCodes.Ldtoken, constructor.DeclaringType); 344 | ilGenerator.Emit(OpCodes.Call, s_getTypeFromHandleMethod); 345 | ilGenerator.Emit(OpCodes.Call, s_getUninitializedObjectMethod); 346 | ilGenerator.Emit(OpCodes.Dup); 347 | ilGenerator.Emit(OpCodes.Stloc_1); 348 | } 349 | 350 | // Setup stack and make indirect call 351 | for (int i = 0; i < signatureParamTypes.Count - 1; i++) 352 | { 353 | ilGenerator.Emit(OpCodes.Ldarg, i); 354 | } 355 | ilGenerator.Emit(OpCodes.Ldloc_0); 356 | ilGenerator.EmitCalli(OpCodes.Calli, CallingConventions.Standard, typeof(void), signatureParamTypes.ToArray(), null); 357 | 358 | ilGenerator.MarkLabel(returnLabel); 359 | ilGenerator.Emit(OpCodes.Ldloc_1); 360 | ilGenerator.Emit(OpCodes.Ret); 361 | 362 | return stub; 363 | } 364 | 365 | public static DynamicMethod GenerateStubForDirectLoad(MethodBase method) 366 | { 367 | DynamicMethod stub = new DynamicMethod( 368 | StubHelper.CreateStubNameFromMethod("stub_ldftn", method), 369 | typeof(IntPtr), 370 | Array.Empty(), 371 | StubHelper.GetOwningModule(), 372 | true); 373 | 374 | ILGenerator ilGenerator = stub.GetILGenerator(); 375 | 376 | if (method.GetMethodBody() == null || StubHelper.IsIntrinsic(method)) 377 | { 378 | // Method has no body or is a compiler intrinsic, 379 | // simply forward arguments to original or shim 380 | if (method.IsConstructor) 381 | ilGenerator.Emit(OpCodes.Ldftn, (ConstructorInfo)method); 382 | else 383 | ilGenerator.Emit(OpCodes.Ldftn, (MethodInfo)method); 384 | 385 | ilGenerator.Emit(OpCodes.Ret); 386 | return stub; 387 | } 388 | 389 | Label rewriteLabel = ilGenerator.DefineLabel(); 390 | Label returnLabel = ilGenerator.DefineLabel(); 391 | 392 | // Inject method info into instruction stream 393 | if (method.IsConstructor) 394 | ilGenerator.Emit(OpCodes.Ldtoken, (ConstructorInfo)method); 395 | else 396 | ilGenerator.Emit(OpCodes.Ldtoken, (MethodInfo)method); 397 | 398 | ilGenerator.Emit(OpCodes.Ldtoken, method.DeclaringType); 399 | ilGenerator.Emit(OpCodes.Call, s_getMethodFromHandleMethod); 400 | 401 | // Rewrite method 402 | ilGenerator.MarkLabel(rewriteLabel); 403 | ilGenerator.Emit(OpCodes.Ldc_I4_0); 404 | ilGenerator.Emit(OpCodes.Call, s_createRewriterMethod); 405 | ilGenerator.Emit(OpCodes.Call, s_rewriteMethod); 406 | 407 | // Retrieve pointer to rewritten method 408 | ilGenerator.Emit(OpCodes.Call, s_getMethodPointerMethod); 409 | 410 | ilGenerator.MarkLabel(returnLabel); 411 | ilGenerator.Emit(OpCodes.Ret); 412 | 413 | return stub; 414 | } 415 | 416 | public static DynamicMethod GenerateStubForVirtualLoad(MethodInfo method) 417 | { 418 | DynamicMethod stub = new DynamicMethod( 419 | StubHelper.CreateStubNameFromMethod("stub_ldvirtftn", method), 420 | typeof(IntPtr), 421 | new Type[] { method.DeclaringType.IsInterface ? typeof(object) : method.DeclaringType }, 422 | StubHelper.GetOwningModule(), 423 | true); 424 | 425 | ILGenerator ilGenerator = stub.GetILGenerator(); 426 | 427 | if ((method.GetMethodBody() == null && !method.IsAbstract) || StubHelper.IsIntrinsic(method)) 428 | { 429 | // Method has no body or is a compiler intrinsic, 430 | // simply forward arguments to original or shim 431 | ilGenerator.Emit(OpCodes.Ldarg, 0); 432 | ilGenerator.Emit(OpCodes.Ldvirtftn, method); 433 | ilGenerator.Emit(OpCodes.Ret); 434 | return stub; 435 | } 436 | 437 | ilGenerator.DeclareLocal(typeof(MethodInfo)); 438 | 439 | Label rewriteLabel = ilGenerator.DefineLabel(); 440 | Label returnLabel = ilGenerator.DefineLabel(); 441 | 442 | // Inject method info into instruction stream 443 | ilGenerator.Emit(OpCodes.Ldtoken, method); 444 | ilGenerator.Emit(OpCodes.Ldtoken, method.DeclaringType); 445 | ilGenerator.Emit(OpCodes.Call, s_getMethodFromHandleMethod); 446 | ilGenerator.Emit(OpCodes.Castclass, typeof(MethodInfo)); 447 | ilGenerator.Emit(OpCodes.Stloc_0); 448 | 449 | // Resolve virtual method to object type 450 | ilGenerator.Emit(OpCodes.Ldarg_0); 451 | ilGenerator.Emit(OpCodes.Ldloc_0); 452 | ilGenerator.Emit(OpCodes.Call, s_devirtualizeMethodMethod); 453 | 454 | // Rewrite resolved method 455 | ilGenerator.MarkLabel(rewriteLabel); 456 | ilGenerator.Emit(method.DeclaringType.IsInterface ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); 457 | ilGenerator.Emit(OpCodes.Call, s_createRewriterMethod); 458 | ilGenerator.Emit(OpCodes.Call, s_rewriteMethod); 459 | 460 | // Retrieve pointer to rewritten method 461 | ilGenerator.Emit(OpCodes.Call, s_getMethodPointerMethod); 462 | 463 | ilGenerator.MarkLabel(returnLabel); 464 | ilGenerator.Emit(OpCodes.Ret); 465 | 466 | return stub; 467 | } 468 | } 469 | } --------------------------------------------------------------------------------