From 31c854ff887f503c2b775325834597aaf9261d82 Mon Sep 17 00:00:00 2001 From: Robert Coltheart <13191652+robertcoltheart@users.noreply.github.com> Date: Mon, 16 Dec 2024 08:25:05 +1100 Subject: [PATCH] Move fakes to main repo (#536) * Move fakes to main repo * target frameworks --- Machine.Specifications.sln | 12 + .../DeepFake.cs | 273 ++++++++++++++++++ .../Machine.Specifications.Fakes.Specs.csproj | 20 ++ .../TestSpec.cs | 59 ++++ .../Machine.Specifications.Fakes.csproj | 12 + .../Proxy/IInterceptor.cs | 7 + .../Proxy/IInvocation.cs | 13 + .../Proxy/IProxyFactory.cs | 9 + .../Proxy/Invocation.cs | 19 ++ .../Proxy/ProxyFactory.cs | 56 ++++ .../Proxy/ProxyInvocation.cs | 19 ++ .../Proxy/Reflection/ClassProxyVisitor.cs | 12 + .../Proxy/Reflection/DelegateProxyVisitor.cs | 7 + .../Proxy/Reflection/ILGeneratorExtensions.cs | 35 +++ .../Proxy/Reflection/IProxyDefinition.cs | 11 + .../Proxy/Reflection/ITypeEmitter.cs | 16 + .../Proxy/Reflection/ITypeEmitterFactory.cs | 9 + .../Proxy/Reflection/InterfaceProxyVisitor.cs | 16 + .../Proxy/Reflection/MemberInfoExtensions.cs | 34 +++ .../Proxy/Reflection/ProxyDefinition.cs | 19 ++ .../Proxy/Reflection/ProxyVisitor.cs | 33 +++ .../Proxy/Reflection/TypeEmitter.cs | 187 ++++++++++++ .../Proxy/Reflection/TypeEmitterFactory.cs | 36 +++ 23 files changed, 914 insertions(+) create mode 100644 src/Machine.Specifications.Fakes.Specs/DeepFake.cs create mode 100644 src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj create mode 100644 src/Machine.Specifications.Fakes.Specs/TestSpec.cs create mode 100644 src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj create mode 100644 src/Machine.Specifications.Fakes/Proxy/IInterceptor.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/IInvocation.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/IProxyFactory.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Invocation.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/ProxyFactory.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/ProxyInvocation.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ClassProxyVisitor.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/DelegateProxyVisitor.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ILGeneratorExtensions.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/IProxyDefinition.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitter.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitterFactory.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/InterfaceProxyVisitor.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/MemberInfoExtensions.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyDefinition.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyVisitor.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitter.cs create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitterFactory.cs diff --git a/Machine.Specifications.sln b/Machine.Specifications.sln index afd25678..a252273e 100644 --- a/Machine.Specifications.sln +++ b/Machine.Specifications.sln @@ -25,6 +25,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Anal EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Analyzers.Tests", "src\Machine.Specifications.Analyzers.Tests\Machine.Specifications.Analyzers.Tests.csproj", "{402EF260-6140-4633-880B-18BEBF7B9DA2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Fakes", "src\Machine.Specifications.Fakes\Machine.Specifications.Fakes.csproj", "{2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Fakes.Specs", "src\Machine.Specifications.Fakes.Specs\Machine.Specifications.Fakes.Specs.csproj", "{B897126F-BF01-4A97-965D-C2DE1BC63460}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -75,6 +79,14 @@ Global {402EF260-6140-4633-880B-18BEBF7B9DA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {402EF260-6140-4633-880B-18BEBF7B9DA2}.Release|Any CPU.ActiveCfg = Release|Any CPU {402EF260-6140-4633-880B-18BEBF7B9DA2}.Release|Any CPU.Build.0 = Release|Any CPU + {2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}.Release|Any CPU.Build.0 = Release|Any CPU + {B897126F-BF01-4A97-965D-C2DE1BC63460}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B897126F-BF01-4A97-965D-C2DE1BC63460}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B897126F-BF01-4A97-965D-C2DE1BC63460}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B897126F-BF01-4A97-965D-C2DE1BC63460}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Machine.Specifications.Fakes.Specs/DeepFake.cs b/src/Machine.Specifications.Fakes.Specs/DeepFake.cs new file mode 100644 index 00000000..0ba30e7e --- /dev/null +++ b/src/Machine.Specifications.Fakes.Specs/DeepFake.cs @@ -0,0 +1,273 @@ +using System; +using System.Reflection; +using Machine.Specifications.Fakes.Proxy; + +namespace Machine.Specifications.Fakes.Specs +{ + public interface IMyInterface + { + bool ReadProp { get; } + + bool WriteProp { set; } + + bool ReadWriteProp { get; set; } + + event EventHandler Event; + + bool Method(int arg1, ref int arg2, out int arg3); + } + + public class MyClass + { + public MyClass(int arg1, int arg2) + { + } + + public virtual bool ReadProp { get; } + + public virtual bool WriteProp + { + set + { + } + } + + public virtual bool ReadWriteProp { get; set; } + + public virtual event EventHandler Event; + + public virtual bool Method(int arg1, ref int arg2, out int arg3) + { + arg3 = default; + + return default; + } + } + + public delegate bool MyDelegate(int arg1, ref int arg2, out int arg3); + + public class MyInterfaceFake : IMyInterface + { + private static readonly MethodInfo MethodHandle = typeof(IMyInterface).GetMethod("Method"); + private static readonly MethodInfo ReadPropGetHandle = typeof(IMyInterface).GetMethod("get_ReadProp"); + private static readonly MethodInfo WritePropSetHandle = typeof(IMyInterface).GetMethod("set_WriteProp"); + private static readonly MethodInfo ReadWritePropGetHandle = typeof(IMyInterface).GetMethod("get_ReadWriteProp"); + private static readonly MethodInfo ReadWritePropSetHandle = typeof(IMyInterface).GetMethod("set_ReadWriteProp"); + private static readonly MethodInfo EventAddHandle = typeof(IMyInterface).GetMethod("add_Event"); + private static readonly MethodInfo EventRemoveHandle = typeof(IMyInterface).GetMethod("remove_Event"); + + private readonly IInterceptor interceptor; + + public MyInterfaceFake(IInterceptor interceptor) + { + this.interceptor = interceptor; + } + + public bool ReadProp + { + get + { + var arguments = new object[0]; + var invocation = new Invocation(ReadPropGetHandle, arguments); + + interceptor.Intercept(invocation); + + return (bool) invocation.ReturnValue; + } + } + + public bool WriteProp + { + set + { + var arguments = new object[] {value}; + var invocation = new Invocation(WritePropSetHandle, arguments); + + interceptor.Intercept(invocation); + } + } + + public bool ReadWriteProp + { + get + { + var arguments = new object[0]; + var invocation = new Invocation(ReadWritePropGetHandle, arguments); + + interceptor.Intercept(invocation); + + return (bool) invocation.ReturnValue; + } + set + { + var arguments = new object[] {value}; + var invocation = new Invocation(ReadWritePropSetHandle, arguments); + + interceptor.Intercept(invocation); + } + } + + public event EventHandler Event + { + add + { + var arguments = new object[] {value}; + var invocation = new Invocation(EventAddHandle, arguments); + + interceptor.Intercept(invocation); + } + remove + { + var arguments = new object[] {value}; + var invocation = new Invocation(EventRemoveHandle, arguments); + + interceptor.Intercept(invocation); + } + } + + public bool Method(int arg1, ref int arg2, out int arg3) + { + arg3 = default; + + var arguments = new object[] {arg1, arg2, arg3}; + var invocation = new Invocation(MethodHandle, arguments); + + interceptor.Intercept(invocation); + + arg2 = (int) arguments[1]; + arg3 = (int) arguments[2]; + + return (bool) invocation.ReturnValue; + } + } + + public class MyClassFake : MyClass + { + private static readonly MethodInfo MethodHandle = typeof(MyClass).GetMethod("Method"); + private static readonly MethodInfo ReadPropGetHandle = typeof(MyClass).GetMethod("get_ReadProp"); + private static readonly MethodInfo WritePropSetHandle = typeof(MyClass).GetMethod("set_WriteProp"); + private static readonly MethodInfo ReadWritePropGetHandle = typeof(MyClass).GetMethod("get_ReadWriteProp"); + private static readonly MethodInfo ReadWritePropSetHandle = typeof(MyClass).GetMethod("set_ReadWriteProp"); + private static readonly MethodInfo EventAddHandle = typeof(MyClass).GetMethod("add_Event"); + private static readonly MethodInfo EventRemoveHandle = typeof(MyClass).GetMethod("remove_Event"); + + private readonly IInterceptor interceptor; + + public MyClassFake(IInterceptor interceptor, int arg1, int arg2) + : base(arg1, arg2) + { + this.interceptor = interceptor; + } + + public override bool ReadProp + { + get + { + var arguments = new object[0]; + var invocation = new Invocation(ReadPropGetHandle, arguments); + + interceptor.Intercept(invocation); + + return (bool) invocation.ReturnValue; + } + } + + public override bool WriteProp + { + set + { + var arguments = new object[] {value}; + var invocation = new Invocation(WritePropSetHandle, arguments); + + interceptor.Intercept(invocation); + } + } + + public override bool ReadWriteProp + { + get + { + var arguments = new object[0]; + var invocation = new Invocation(ReadWritePropGetHandle, arguments); + + interceptor.Intercept(invocation); + + return (bool) invocation.ReturnValue; + } + set + { + var arguments = new object[] {value}; + var invocation = new Invocation(ReadWritePropSetHandle, arguments); + + interceptor.Intercept(invocation); + } + } + + public override event EventHandler Event + { + add + { + var arguments = new object[] {value}; + var invocation = new Invocation(EventAddHandle, arguments); + + interceptor.Intercept(invocation); + } + remove + { + var arguments = new object[] {value}; + var invocation = new Invocation(EventRemoveHandle, arguments); + + interceptor.Intercept(invocation); + } + } + + public override bool Method(int arg1, ref int arg2, out int arg3) + { + arg3 = default; + + var arguments = new object[] {arg1, arg2, arg3}; + var invocation = new Invocation(MethodHandle, arguments); + + interceptor.Intercept(invocation); + + arg2 = (int) arguments[1]; + arg3 = (int) arguments[2]; + + return (bool) invocation.ReturnValue; + } + } + + public class MyDelegateFake + { + private static readonly MethodInfo MethodHandle = typeof(MyDelegate).GetMethod("Invoke"); + + private readonly IInterceptor interceptor; + + public MyDelegateFake(IInterceptor interceptor) + { + this.interceptor = interceptor; + } + + public bool Invoke(int arg1, ref int arg2, out int arg3) + { + arg3 = default; + + var arguments = new object[] {arg1, arg2, arg3}; + var invocation = new Invocation(MethodHandle, arguments); + + interceptor.Intercept(invocation); + + arg2 = (int) arguments[1]; + arg3 = (int) arguments[2]; + + return (bool) invocation.ReturnValue; + } + + public static MyDelegate Create(IInterceptor interceptor) + { + var target = new MyDelegateFake(interceptor); + + return (MyDelegate) Delegate.CreateDelegate(typeof(MyDelegate), target, "Invoke"); + } + } +} diff --git a/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj b/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj new file mode 100644 index 00000000..ee9fcb60 --- /dev/null +++ b/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + + false + + + + + + + + + + + + + + diff --git a/src/Machine.Specifications.Fakes.Specs/TestSpec.cs b/src/Machine.Specifications.Fakes.Specs/TestSpec.cs new file mode 100644 index 00000000..2f8b7b3f --- /dev/null +++ b/src/Machine.Specifications.Fakes.Specs/TestSpec.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using System.Reflection; +using Machine.Specifications.Fakes.Proxy; +using Machine.Specifications.Fakes.Proxy.Reflection; + +namespace Machine.Specifications.Fakes.Specs +{ + public interface IRob + { + object Execute(int arg1); + } + + public class Interceptor : IInterceptor + { + public void Intercept(IInvocation invocation) + { + invocation.ReturnValue = null; + } + } + + class TestSpec + { + static TypeEmitterFactory factory; + + static MethodInfo execute_method; + + static Type type; + + static Interceptor interceptor; + + Establish context = () => + { + factory = new TypeEmitterFactory(); + interceptor = new Interceptor(); + execute_method = typeof(IRob).GetMethod("Execute", BindingFlags.Instance | BindingFlags.Public); + }; + + Because of = () => + { + var emitter = factory.DefineType(typeof(object)); + + emitter.EmitInterface(typeof(IRob)); + emitter.EmitConstructor(typeof(object).GetConstructors().First()); + emitter.EmitMethod(execute_method); + + type = emitter.CreateType(); + }; + + It should = () => + { + var value = Activator.CreateInstance(type, interceptor); + + var ret = execute_method.Invoke(value, new object[] {4}); + + int r = 1; + }; + } +} diff --git a/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj b/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj new file mode 100644 index 00000000..b26d9a24 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj @@ -0,0 +1,12 @@ + + + + net472;netstandard2.0 + false + + + + + + + diff --git a/src/Machine.Specifications.Fakes/Proxy/IInterceptor.cs b/src/Machine.Specifications.Fakes/Proxy/IInterceptor.cs new file mode 100644 index 00000000..246d45fe --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/IInterceptor.cs @@ -0,0 +1,7 @@ +namespace Machine.Specifications.Fakes.Proxy +{ + public interface IInterceptor + { + void Intercept(IInvocation invocation); + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/IInvocation.cs b/src/Machine.Specifications.Fakes/Proxy/IInvocation.cs new file mode 100644 index 00000000..50e1410a --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/IInvocation.cs @@ -0,0 +1,13 @@ +using System.Reflection; + +namespace Machine.Specifications.Fakes.Proxy +{ + public interface IInvocation + { + MethodInfo Method { get; } + + object[] Arguments { get; } + + object ReturnValue { get; set; } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/IProxyFactory.cs b/src/Machine.Specifications.Fakes/Proxy/IProxyFactory.cs new file mode 100644 index 00000000..7dd08ef0 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/IProxyFactory.cs @@ -0,0 +1,9 @@ +using System; + +namespace Machine.Specifications.Fakes.Proxy +{ + public interface IProxyFactory + { + object CreateProxy(Type type, IInterceptor interceptor, params object[] arguments); + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Invocation.cs b/src/Machine.Specifications.Fakes/Proxy/Invocation.cs new file mode 100644 index 00000000..37217ab5 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Invocation.cs @@ -0,0 +1,19 @@ +using System.Reflection; + +namespace Machine.Specifications.Fakes.Proxy +{ + public class Invocation : IInvocation + { + public Invocation(MethodInfo method, object[] arguments) + { + Method = method; + Arguments = arguments; + } + + public MethodInfo Method { get; } + + public object[] Arguments { get; } + + public object ReturnValue { get; set; } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/ProxyFactory.cs b/src/Machine.Specifications.Fakes/Proxy/ProxyFactory.cs new file mode 100644 index 00000000..a434af7c --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/ProxyFactory.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Concurrent; +using Machine.Specifications.Fakes.Proxy.Reflection; + +namespace Machine.Specifications.Fakes.Proxy +{ + public class ProxyFactory : IProxyFactory + { + private readonly ITypeEmitterFactory typeEmitterFactory; + + private readonly ConcurrentDictionary> proxies = + new ConcurrentDictionary>(); + + public ProxyFactory(ITypeEmitterFactory typeEmitterFactory) + { + this.typeEmitterFactory = typeEmitterFactory; + } + + public object CreateProxy(Type type, IInterceptor interceptor, params object[] arguments) + { + var proxy = proxies.GetOrAdd(type, CreateProxy); + + return proxy.Value.Create(interceptor, arguments); + } + + private Lazy CreateProxy(Type type) + { + return new Lazy(() => GetProxyDefinition(type)); + } + + private IProxyDefinition GetProxyDefinition(Type type) + { + var emitter = typeEmitterFactory.DefineType(type); + + var visitor = GetProxyVisitor(type, emitter); + visitor.Visit(type); + + return new ProxyDefinition(emitter.CreateType()); + } + + private ProxyVisitor GetProxyVisitor(Type type, ITypeEmitter emitter) + { + if (type.IsInterface) + { + return new InterfaceProxyVisitor(type); + } + + if (typeof(Delegate).IsAssignableFrom(type)) + { + return new DelegateProxyVisitor(); + } + + return new ClassProxyVisitor(emitter); + } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/ProxyInvocation.cs b/src/Machine.Specifications.Fakes/Proxy/ProxyInvocation.cs new file mode 100644 index 00000000..9092b795 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/ProxyInvocation.cs @@ -0,0 +1,19 @@ +using System.Reflection; + +namespace Machine.Specifications.Fakes.Proxy +{ + public abstract class ProxyInvocation : IInvocation + { + protected ProxyInvocation(MethodInfo method, object[] arguments) + { + Method = method; + Arguments = arguments; + } + + public MethodInfo Method { get; } + + public object[] Arguments { get; } + + public object ReturnValue { get; set; } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ClassProxyVisitor.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ClassProxyVisitor.cs new file mode 100644 index 00000000..62368934 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ClassProxyVisitor.cs @@ -0,0 +1,12 @@ +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public class ClassProxyVisitor : ProxyVisitor + { + private ITypeEmitter typeEmitter; + + public ClassProxyVisitor(ITypeEmitter typeEmitter) + { + this.typeEmitter = typeEmitter; + } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/DelegateProxyVisitor.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/DelegateProxyVisitor.cs new file mode 100644 index 00000000..56943d25 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/DelegateProxyVisitor.cs @@ -0,0 +1,7 @@ +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public class DelegateProxyVisitor : ProxyVisitor + { + + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ILGeneratorExtensions.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ILGeneratorExtensions.cs new file mode 100644 index 00000000..a92359bb --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ILGeneratorExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Reflection.Emit; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public static class ILGeneratorExtensions + { + public static void EmitBox(this ILGenerator generator, Type type) + { + if (type.IsValueType || type.IsGenericParameter) + generator.Emit(OpCodes.Box, type); + } + + public static void EmitLoadArgument(this ILGenerator generator, LocalBuilder variable) + { + generator.Emit(OpCodes.Ldarg, variable); + } + + public static void EmitLoadValue(this ILGenerator generator, int value) + { + generator.Emit(OpCodes.Ldc_I4, value); + } + + public static LocalBuilder EmitNewArray(this ILGenerator generator, Type elementType, int size) + { + var variable = generator.DeclareLocal(elementType.MakeArrayType()); + + generator.EmitLoadValue(size); + generator.Emit(OpCodes.Newarr, elementType); + generator.Emit(OpCodes.Stloc, variable); + + return variable; + } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/IProxyDefinition.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/IProxyDefinition.cs new file mode 100644 index 00000000..11bcafc0 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/IProxyDefinition.cs @@ -0,0 +1,11 @@ +using System; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public interface IProxyDefinition + { + Type Type { get; } + + object Create(IInterceptor interceptor, params object[] arguments); + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitter.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitter.cs new file mode 100644 index 00000000..1949845e --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitter.cs @@ -0,0 +1,16 @@ +using System; +using System.Reflection; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public interface ITypeEmitter + { + void EmitConstructor(ConstructorInfo constructor); + + void EmitInterface(Type type); + + void EmitMethod(MethodInfo method); + + Type CreateType(); + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitterFactory.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitterFactory.cs new file mode 100644 index 00000000..85d06175 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitterFactory.cs @@ -0,0 +1,9 @@ +using System; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public interface ITypeEmitterFactory + { + ITypeEmitter DefineType(Type type); + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/InterfaceProxyVisitor.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/InterfaceProxyVisitor.cs new file mode 100644 index 00000000..6e72d4af --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/InterfaceProxyVisitor.cs @@ -0,0 +1,16 @@ +using System; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public class InterfaceProxyVisitor : ProxyVisitor + { + public InterfaceProxyVisitor(Type type) + { + } + + public override void VisitType(Type type) + { + base.VisitType(type); + } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/MemberInfoExtensions.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/MemberInfoExtensions.cs new file mode 100644 index 00000000..5b978eab --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/MemberInfoExtensions.cs @@ -0,0 +1,34 @@ +using System; +using System.Reflection; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public static class MemberInfoExtensions + { + public static void Accept(this MemberInfo member, ProxyVisitor visitor) + { + switch (member) + { + case Type type: + visitor.VisitType(type); + break; + + case ConstructorInfo constructorInfo: + visitor.VisitConstructor(constructorInfo); + break; + + case PropertyInfo propertyInfo: + visitor.VisitProperty(propertyInfo); + break; + + case EventInfo eventInfo: + visitor.VisitEvent(eventInfo); + break; + + case MethodInfo methodInfo: + visitor.VisitMethod(methodInfo); + break; + } + } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyDefinition.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyDefinition.cs new file mode 100644 index 00000000..f4c22b04 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyDefinition.cs @@ -0,0 +1,19 @@ +using System; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public class ProxyDefinition : IProxyDefinition + { + public ProxyDefinition(Type type) + { + Type = type; + } + + public Type Type { get; } + + public object Create(IInterceptor interceptor, params object[] arguments) + { + return Activator.CreateInstance(Type, arguments); + } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyVisitor.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyVisitor.cs new file mode 100644 index 00000000..6632ff4b --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyVisitor.cs @@ -0,0 +1,33 @@ +using System; +using System.Reflection; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public abstract class ProxyVisitor + { + public virtual void Visit(MemberInfo memberInfo) + { + memberInfo.Accept(this); + } + + public virtual void VisitType(Type type) + { + } + + public virtual void VisitConstructor(ConstructorInfo constructorInfo) + { + } + + public virtual void VisitProperty(PropertyInfo propertyInfo) + { + } + + public virtual void VisitEvent(EventInfo eventInfo) + { + } + + public virtual void VisitMethod(MethodInfo methodInfo) + { + } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitter.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitter.cs new file mode 100644 index 00000000..a2dc8170 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitter.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public class TypeEmitter : ITypeEmitter + { + private static readonly MethodInfo GetterMethod = typeof(MethodBase).GetMethod( + "GetMethodFromHandle", + new[] {typeof(RuntimeMethodHandle), typeof(RuntimeTypeHandle)}); + + private static readonly MethodInfo InterceptMethod = typeof(IInterceptor).GetMethod( + "Intercept", + new[] {typeof(IInvocation)}); + + private static readonly ConstructorInfo InvocationConstructor = typeof(Invocation).GetConstructor( + new[] {typeof(MethodInfo), typeof(object[])}); + + private static readonly MethodInfo ReturnProperty = typeof(Invocation).GetMethod("get_ReturnValue"); + + private readonly TypeBuilder typeBuilder; + + private readonly FieldInfo interceptorField; + + private readonly List<(MethodInfo, FieldBuilder)> fields = new List<(MethodInfo, FieldBuilder)>(); + + public TypeEmitter(TypeBuilder typeBuilder) + { + this.typeBuilder = typeBuilder; + + interceptorField = typeBuilder.DefineField( + "interceptor", + typeof(IInterceptor), + FieldAttributes.Private); + } + + public void EmitConstructor(ConstructorInfo constructor) + { + var constructorParameters = constructor + .GetParameters() + .Select(x => x.ParameterType) + .ToArray(); + + var parameters = new List { typeof(IInterceptor)}; + parameters.AddRange(constructorParameters); + + var builder = typeBuilder.DefineConstructor( + MethodAttributes.Public, + CallingConventions.Standard, + parameters.ToArray()); + + var generator = builder.GetILGenerator(); + + generator.Emit(OpCodes.Ldarg_0); + + for (var i = 0; i < constructorParameters.Length; i++) + { + generator.Emit(OpCodes.Ldarg, i + 2); + } + + generator.Emit(OpCodes.Call, constructor); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Stfld, interceptorField); + + generator.Emit(OpCodes.Ret); + } + + public void EmitInterface(Type type) + { + typeBuilder.AddInterfaceImplementation(type); + } + + public void EmitMethod(MethodInfo method) + { + var parameters = method + .GetParameters() + .Select(x => x.ParameterType) + .ToArray(); + + var methodField = typeBuilder.DefineField( + method.Name + "Field", + typeof(MethodInfo), + FieldAttributes.Static); + + var builder = typeBuilder.DefineMethod( + method.Name, + MethodAttributes.Public | MethodAttributes.Virtual, + method.CallingConvention, + method.ReturnType, + parameters); + + var generator = builder.GetILGenerator(); + + // Create arguments array + var arguments = generator.DeclareLocal(typeof(object).MakeArrayType()); + + generator.Emit(OpCodes.Ldc_I4, parameters.Length); + generator.Emit(OpCodes.Newarr, typeof(object)); + generator.Emit(OpCodes.Stloc, arguments); + + // Set method arguments into arguments array + for (var i = 0; i < parameters.Length; i++) + { + generator.Emit(OpCodes.Ldloc, arguments); + generator.Emit(OpCodes.Ldc_I4, i); + generator.Emit(OpCodes.Ldarg, i + 1); + + if (parameters[i].IsValueType || parameters[i].IsGenericParameter) + { + generator.Emit(OpCodes.Box, parameters[i]); + } + + generator.Emit(OpCodes.Stelem_Ref); + } + + // Create new invocation object + var invocation = generator.DeclareLocal(typeof(Invocation)); + + generator.Emit(OpCodes.Ldsfld, methodField); + generator.Emit(OpCodes.Ldloc, arguments); + generator.Emit(OpCodes.Newobj, InvocationConstructor); + generator.Emit(OpCodes.Stloc, invocation); + + // Call intercept method + generator.BeginExceptionBlock(); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldfld, interceptorField); + generator.Emit(OpCodes.Ldloc, invocation); + generator.Emit(OpCodes.Callvirt, InterceptMethod); + generator.BeginFinallyBlock(); + // todo: set out/ref arguments + generator.EndExceptionBlock(); + + if (method.ReturnType != typeof(void)) + { + generator.Emit(OpCodes.Ldloc, invocation); + generator.Emit(OpCodes.Callvirt, ReturnProperty); + + if (method.ReturnType.IsValueType) + { + generator.Emit(OpCodes.Unbox_Any, method.ReturnType); + } + else + { + generator.Emit(OpCodes.Castclass, method.ReturnType); + } + } + + generator.Emit(OpCodes.Ret); + + fields.Add((method, methodField)); + } + + public Type CreateType() + { + CreateStaticConstructor(); + + return typeBuilder.CreateTypeInfo(); + } + + private void CreateStaticConstructor() + { + var constructor = typeBuilder.DefineConstructor( + MethodAttributes.Static, + CallingConventions.Standard, + Type.EmptyTypes); + + var generator = constructor.GetILGenerator(); + + foreach (var (method, field) in fields) + { + generator.Emit(OpCodes.Ldtoken, method); + generator.Emit(OpCodes.Ldtoken, method.DeclaringType); + generator.Emit(OpCodes.Call, GetterMethod); + generator.Emit(OpCodes.Castclass, typeof(MethodInfo)); + generator.Emit(OpCodes.Stsfld, field); + } + + generator.Emit(OpCodes.Ret); + } + } +} diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitterFactory.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitterFactory.cs new file mode 100644 index 00000000..4d9fb474 --- /dev/null +++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitterFactory.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; + +namespace Machine.Specifications.Fakes.Proxy.Reflection +{ + public class TypeEmitterFactory : ITypeEmitterFactory + { + private const string Name = "Machine.Specifications.Fakes.Proxy"; + + private const string ModuleName = Name + ".dll"; + + private readonly ModuleBuilder moduleBuilder; + + private int count; + + public TypeEmitterFactory() + { + var name = new AssemblyName(Name); + + moduleBuilder = AssemblyBuilder + .DefineDynamicAssembly(name, AssemblyBuilderAccess.Run) + .DefineDynamicModule(ModuleName); + } + + public ITypeEmitter DefineType(Type type) + { + var value = Interlocked.Increment(ref count); + + var typeBuilder = moduleBuilder.DefineType(type.Name + $"Proxy_{value}", TypeAttributes.Class); + + return new TypeEmitter(typeBuilder); + } + } +}