diff --git a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs index a4a2a239203c4a..4de61abfe2d8ea 100644 --- a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs +++ b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs @@ -52,6 +52,21 @@ internal static class DispatchProxyGenerator private static readonly MethodInfo s_getTypeFromHandleMethod = typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) })!; private static readonly MethodInfo s_makeGenericMethodMethod = GetGenericMethodMethodInfo(); + private static MethodInfo BoxPointerMethod => + field ??= typeof(DispatchProxyGenerator).GetMethod(nameof(BoxPointer), BindingFlags.NonPublic | BindingFlags.Static)!; + + private static MethodInfo UnboxPointerMethod => + field ??= typeof(DispatchProxyGenerator).GetMethod(nameof(UnboxPointer), BindingFlags.NonPublic | BindingFlags.Static)!; + + private static unsafe object? BoxPointer(IntPtr pointer, Type type) => + pointer == IntPtr.Zero ? null : Pointer.Box((void*)pointer, type); + + // A Pointer's type is not checked against the target type: Pointer exposes it only internally. + private static unsafe IntPtr UnboxPointer(object? value) => + value is Pointer pointer ? (IntPtr)Pointer.Unbox(pointer) : + value is null ? IntPtr.Zero : + (IntPtr)value; + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "MakeGenericMethod is safe here because the user code invoking the generic method will reference " + "the GenericTypes being used, which will guarantee the requirements of the generic method.")] @@ -477,11 +492,11 @@ private MethodBuilder AddMethodImpl(MethodInfo mi, int methodInfoIndex) return mdb; } - ParametersArray args = new ParametersArray(il, paramTypes); + ParametersArray args = new ParametersArray(_assembly, il, paramTypes); // object[] args = new object[paramCount]; il.Emit(OpCodes.Nop); - GenericArray argsArr = new GenericArray(il, parameters.Length); + GenericArray argsArr = new GenericArray(_assembly, il, parameters.Length); for (int i = 0; i < parameters.Length; i++) { @@ -510,7 +525,7 @@ private MethodBuilder AddMethodImpl(MethodInfo mi, int methodInfoIndex) il.Emit(OpCodes.Ldloc, methodInfoLocal); Type[] genericTypes = mi.GetGenericArguments(); - GenericArray typeArr = new GenericArray(il, genericTypes.Length); + GenericArray typeArr = new GenericArray(_assembly, il, genericTypes.Length); for (int i = 0; i < genericTypes.Length; ++i) { typeArr.BeginSet(i); @@ -557,7 +572,7 @@ private MethodBuilder AddMethodImpl(MethodInfo mi, int methodInfoIndex) { // return (mi.ReturnType)result; il.Emit(OpCodes.Ldloc, resultLocal); - Convert(il, typeof(object), mi.ReturnType, false); + Convert(_assembly, il, typeof(object), mi.ReturnType, false); } il.Emit(OpCodes.Ret); @@ -634,7 +649,7 @@ private MethodBuilder AddMethodImpl(MethodInfo mi, int methodInfoIndex) OpCodes.Stind_Ref, //String = 18, }; - private static void Convert(ILGenerator il, Type source, Type target, bool isAddress) + private static void Convert(ProxyAssembly assembly, ILGenerator il, Type source, Type target, bool isAddress) { Debug.Assert(!target.IsByRef); if (target == source) @@ -645,7 +660,19 @@ private static void Convert(ILGenerator il, Type source, Type target, bool isAdd Debug.Assert(!isAddress); Type argType = source.GetElementType()!; Ldind(il, argType); - Convert(il, argType, target, isAddress); + Convert(assembly, il, argType, target, isAddress); + return; + } + if (source.IsPointer) + { + Debug.Assert(target == typeof(object)); + EmitBoxPointer(assembly, il, source); + return; + } + if (target.IsPointer) + { + Debug.Assert(source == typeof(object)); + EmitUnboxPointer(assembly, il); return; } if (target.IsValueType) @@ -687,8 +714,30 @@ private static void Convert(ILGenerator il, Type source, Type target, bool isAdd } } + // The proxy assembly can call BoxPointer and UnboxPointer only through an IgnoresAccessChecksTo + // attribute for System.Reflection.DispatchProxy, which EnsureTypeIsVisible adds. + private static void EmitBoxPointer(ProxyAssembly assembly, ILGenerator il, Type type) + { + assembly.EnsureTypeIsVisible(typeof(DispatchProxyGenerator)); + il.Emit(OpCodes.Ldtoken, type); + il.Emit(OpCodes.Call, s_getTypeFromHandleMethod); + il.Emit(OpCodes.Call, BoxPointerMethod); + } + + private static void EmitUnboxPointer(ProxyAssembly assembly, ILGenerator il) + { + assembly.EnsureTypeIsVisible(typeof(DispatchProxyGenerator)); + il.Emit(OpCodes.Call, UnboxPointerMethod); + } + private static void Ldind(ILGenerator il, Type type) { + if (type.IsPointer) + { + il.Emit(OpCodes.Ldind_I); + return; + } + OpCode opCode = s_ldindOpCodes[GetTypeCode(type)]; if (!opCode.Equals(OpCodes.Nop)) { @@ -702,6 +751,12 @@ private static void Ldind(ILGenerator il, Type type) private static void Stind(ILGenerator il, Type type) { + if (type.IsPointer) + { + il.Emit(OpCodes.Stind_I); + return; + } + OpCode opCode = s_stindOpCodes[GetTypeCode(type)]; if (!opCode.Equals(OpCodes.Nop)) { @@ -715,10 +770,12 @@ private static void Stind(ILGenerator il, Type type) private sealed class ParametersArray { + private readonly ProxyAssembly _assembly; private readonly ILGenerator _il; private readonly Type[] _paramTypes; - internal ParametersArray(ILGenerator il, Type[] paramTypes) + internal ParametersArray(ProxyAssembly assembly, ILGenerator il, Type[] paramTypes) { + _assembly = assembly; _il = il; _paramTypes = paramTypes; } @@ -737,17 +794,19 @@ internal void EndSet(int i, Type stackType) { Debug.Assert(_paramTypes[i].IsByRef); Type argType = _paramTypes[i].GetElementType()!; - Convert(_il, stackType, argType, false); + Convert(_assembly, _il, stackType, argType, false); Stind(_il, argType); } } private sealed class GenericArray { + private readonly ProxyAssembly _assembly; private readonly ILGenerator _il; private readonly LocalBuilder _lb; - internal GenericArray(ILGenerator il, int len) + internal GenericArray(ProxyAssembly assembly, ILGenerator il, int len) { + _assembly = assembly; _il = il; _lb = il.DeclareLocal(typeof(T[])); @@ -776,7 +835,7 @@ internal void BeginSet(int i) internal void EndSet(Type stackType) { - Convert(_il, stackType, typeof(T), false); + Convert(_assembly, _il, stackType, typeof(T), false); _il.Emit(OpCodes.Stelem_Ref); } } diff --git a/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs b/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs index 3cf8746aa9eee1..a24370b36f8ac7 100644 --- a/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs +++ b/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs @@ -999,6 +999,226 @@ private static void TestRefOutInInvocation(Action invocation, Assert.Equal(expected, result); } + [Theory] + [InlineData(false)] + [InlineData(true)] + [SkipOnMono("Mono reads a dynamic assembly's IgnoresAccessChecksTo attributes only once")] + public static unsafe void Invoke_Pointer_Parameter_Receives_Pointer(bool useGenericCreate) + { + int value = 42; + TestType_IPointerService proxy = CreateHelper(useGenericCreate); + + object received = null; + ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => + { + received = args[0]; + return null; + }; + + proxy.Echo(&value); + Assert.IsType(received); + Assert.Equal((nint)(&value), (nint)Pointer.Unbox(received)); + + received = "Failed"; + proxy.Echo(null); + Assert.Null(received); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + [SkipOnMono("Mono reads a dynamic assembly's IgnoresAccessChecksTo attributes only once")] + public static unsafe void Invoke_Pointer_Return_Accepts_Pointer_IntPtr_And_Null(bool useGenericCreate) + { + int value = 42; + int* p = &value; + TestType_IPointerService proxy = CreateHelper(useGenericCreate); + + object result = null; + ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => result; + + result = Pointer.Box(p, typeof(int*)); + Assert.Equal((nint)p, (nint)proxy.Echo(null)); + + result = (IntPtr)p; + Assert.Equal((nint)p, (nint)proxy.Echo(null)); + + result = null; + Assert.Equal(0, (nint)proxy.Echo(null)); + + // A Pointer of another pointer type is accepted: the proxy can't read its type to check it. + result = Pointer.Box((long*)p, typeof(long*)); + Assert.Equal((nint)p, (nint)proxy.Echo(null)); + + result = 42; + Assert.Throws(() => { proxy.Echo(null); }); + + result = "42"; + Assert.Throws(() => { proxy.Echo(null); }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + [SkipOnMono("Mono reads a dynamic assembly's IgnoresAccessChecksTo attributes only once")] + public static unsafe void Invoke_Ref_Out_Pointer_Parameter(bool useGenericCreate) + { + int* values = stackalloc int[3]; + TestType_IPointerService proxy = CreateHelper(useGenericCreate); + + object received = "Failed"; + object written = null; + ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => + { + received = args[0]; + args[0] = written; + return null; + }; + + int* p = values; + written = Pointer.Box(values + 1, typeof(int*)); + proxy.Ref(ref p); + Assert.Equal((nint)values, (nint)Pointer.Unbox(received)); + Assert.Equal((nint)(values + 1), (nint)p); + + written = (IntPtr)(values + 2); + proxy.Ref(ref p); + Assert.Equal((nint)(values + 1), (nint)Pointer.Unbox(received)); + Assert.Equal((nint)(values + 2), (nint)p); + + written = null; + proxy.Ref(ref p); + Assert.Equal(0, (nint)p); + + proxy.Ref(ref p); + Assert.Null(received); + + // A Pointer of another pointer type is accepted: the proxy can't read its type to check it. + written = Pointer.Box((long*)values, typeof(long*)); + proxy.Ref(ref p); + Assert.Equal((nint)values, (nint)p); + + written = 42; + Assert.Throws(() => { proxy.Ref(ref p); }); + + written = (IntPtr)(values + 1); + proxy.Out(out p); + Assert.Equal((nint)(values + 1), (nint)p); + + written = Pointer.Box(values, typeof(int*)); + proxy.Out(out p); + Assert.Null(received); + Assert.Equal((nint)values, (nint)p); + + written = null; + proxy.Out(out p); + Assert.Equal(0, (nint)p); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + [SkipOnMono("Mono reads a dynamic assembly's IgnoresAccessChecksTo attributes only once")] + public static unsafe void Invoke_Pointer_Method_Forwarded_To_MethodInfo_Invoke(bool useGenericCreate) + { + int* ints = stackalloc int[2]; + long* longs = stackalloc long[2]; + var target = new TestType_PointerService(); + TestType_IPointerService proxy = CreateHelper(useGenericCreate); + ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => method.Invoke(target, args); + + Assert.Equal((nint)(ints + 1), (nint)proxy.Echo(ints)); + Assert.Equal((nint)(longs + 1), (nint)proxy.EchoGeneric(longs)); + Assert.Equal(sizeof(int), (nint)proxy.Echo(null)); + Assert.Equal((nint)((byte*)ints + 1), (nint)proxy.EchoVoid(ints)); + Assert.Equal((nint)(&ints + 1), (nint)proxy.EchoPointerPointer(&ints)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + [SkipOnMono("Mono reads a dynamic assembly's IgnoresAccessChecksTo attributes only once")] + public static unsafe void Invoke_Void_Pointer_Double_Pointer_And_In_Pointer(bool useGenericCreate) + { + int value = 42; + int* p = &value; + TestType_IPointerService proxy = CreateHelper(useGenericCreate); + + object received = null; + object result = null; + ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => + { + received = args[0]; + return result; + }; + + result = Pointer.Box(p, typeof(void*)); + Assert.Equal((nint)p, (nint)proxy.EchoVoid(p)); + Assert.Equal((nint)p, (nint)Pointer.Unbox(Assert.IsType(received))); + + result = Pointer.Box(&p, typeof(int**)); + Assert.Equal((nint)(&p), (nint)proxy.EchoPointerPointer(&p)); + Assert.Equal((nint)(&p), (nint)Pointer.Unbox(Assert.IsType(received))); + + result = null; + proxy.In(in p); + Assert.Equal((nint)(&value), (nint)Pointer.Unbox(Assert.IsType(received))); + Assert.Equal((nint)(&value), (nint)p); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.HasAssemblyFiles))] + [InlineData(false)] + [InlineData(true)] + [SkipOnMono("Mono reads a dynamic assembly's IgnoresAccessChecksTo attributes only once")] + public static void Invoke_Pointer_Parameter_In_Another_AssemblyLoadContext(bool useGenericCreate) + { + EchoPointerThroughProxy(useGenericCreate); + + // Each new load context gets its own proxy assembly, which needs its own access to BoxPointer and + // UnboxPointer. One proxy here only boxes a pointer and the other only unboxes one, so each conversion + // must add that access by itself. + InvokeInNewAssemblyLoadContext(nameof(PassPointerThroughProxy), useGenericCreate); + InvokeInNewAssemblyLoadContext(nameof(ReturnPointerThroughProxy), useGenericCreate); + } + + private static void InvokeInNewAssemblyLoadContext(string methodName, bool useGenericCreate) => + new AssemblyLoadContext(methodName) + .LoadFromAssemblyPath(typeof(DispatchProxyTests).Assembly.Location) + .GetType(typeof(DispatchProxyTests).FullName, true) + .GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic) + .Invoke(null, new object[] { useGenericCreate }); + + private static unsafe void EchoPointerThroughProxy(bool useGenericCreate) + { + int value = 42; + TestType_IPointerService proxy = CreateHelper(useGenericCreate); + ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => args[0]; + Assert.Equal((nint)(&value), (nint)proxy.Echo(&value)); + } + + private static unsafe void PassPointerThroughProxy(bool useGenericCreate) + { + int value = 42; + object received = null; + TestType_IPointerArgumentService proxy = CreateHelper(useGenericCreate); + ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => + { + received = args[0]; + return null; + }; + proxy.Set(&value); + Assert.Equal((nint)(&value), (nint)Pointer.Unbox(received)); + } + + private static unsafe void ReturnPointerThroughProxy(bool useGenericCreate) + { + int value = 42; + object result = Pointer.Box(&value, typeof(int*)); + TestType_IPointerReturnService proxy = CreateHelper(useGenericCreate); + ((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => result; + Assert.Equal((nint)(&value), (nint)proxy.Get()); + } + private static TestType_IHelloService CreateTestHelloProxy(bool useGenericCreate) => CreateHelper(useGenericCreate); diff --git a/src/libraries/System.Reflection.DispatchProxy/tests/System.Reflection.DispatchProxy.Tests.csproj b/src/libraries/System.Reflection.DispatchProxy/tests/System.Reflection.DispatchProxy.Tests.csproj index 941967a335138e..f35e37e6069ed7 100644 --- a/src/libraries/System.Reflection.DispatchProxy/tests/System.Reflection.DispatchProxy.Tests.csproj +++ b/src/libraries/System.Reflection.DispatchProxy/tests/System.Reflection.DispatchProxy.Tests.csproj @@ -2,6 +2,7 @@ $(NetCoreAppCurrent) + true false false diff --git a/src/libraries/System.Reflection.DispatchProxy/tests/TestTypes.cs b/src/libraries/System.Reflection.DispatchProxy/tests/TestTypes.cs index 6982e81fa726e7..f4d8b9a1f00fc8 100644 --- a/src/libraries/System.Reflection.DispatchProxy/tests/TestTypes.cs +++ b/src/libraries/System.Reflection.DispatchProxy/tests/TestTypes.cs @@ -142,6 +142,38 @@ public interface TypeType_GenericMethod T Echo(T messages); } +public unsafe interface TestType_IPointerService +{ + int* Echo(int* p); + void Ref(ref int* p); + void Out(out int* p); + T* EchoGeneric(T* p) where T : unmanaged; + void* EchoVoid(void* p); + int** EchoPointerPointer(int** p); + void In(in int* p); +} + +public unsafe class TestType_PointerService : TestType_IPointerService +{ + public int* Echo(int* p) => p + 1; + public void Ref(ref int* p) => p++; + public void Out(out int* p) => p = null; + public T* EchoGeneric(T* p) where T : unmanaged => p + 1; + public void* EchoVoid(void* p) => (byte*)p + 1; + public int** EchoPointerPointer(int** p) => p + 1; + public void In(in int* p) { } +} + +public unsafe interface TestType_IPointerReturnService +{ + int* Get(); +} + +public unsafe interface TestType_IPointerArgumentService +{ + void Set(int* p); +} + // Negative -- demonstrates trying to use a class for the interface type for the proxy public class TestType_ConcreteClass { diff --git a/src/libraries/System.Reflection.DispatchProxy/tests/TrimmingTests/DispatchProxyTest.cs b/src/libraries/System.Reflection.DispatchProxy/tests/TrimmingTests/DispatchProxyTest.cs index 55177e1c5ae5ec..0f53af628989a3 100644 --- a/src/libraries/System.Reflection.DispatchProxy/tests/TrimmingTests/DispatchProxyTest.cs +++ b/src/libraries/System.Reflection.DispatchProxy/tests/TrimmingTests/DispatchProxyTest.cs @@ -9,7 +9,7 @@ /// class Program { - static int Main() + static unsafe int Main() { IFoo foo = CountingProxy.Wrap(new Foo()); @@ -17,7 +17,13 @@ static int Main() foo.Method1(); foo.Method2(); - if (((CountingProxy)foo).InvocationCount != 3) + int value = 42; + if (foo.PointerMethod(&value) != &value) + { + return -2; + } + + if (((CountingProxy)foo).InvocationCount != 4) { return -1; } @@ -34,6 +40,7 @@ public interface IFoo public void Method1(); public void Method2(); public void UnusedMethod3(); + public unsafe int* PointerMethod(int* p); } class Foo : IFoo @@ -44,6 +51,7 @@ class Foo : IFoo public void Method1() { } public void Method2() { } public void UnusedMethod3() { } + public unsafe int* PointerMethod(int* p) => p; } public class CountingProxy : DispatchProxy