Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.")]
Expand Down Expand Up @@ -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<object> argsArr = new GenericArray<object>(il, parameters.Length);
GenericArray<object> argsArr = new GenericArray<object>(_assembly, il, parameters.Length);

for (int i = 0; i < parameters.Length; i++)
{
Expand Down Expand Up @@ -510,7 +525,7 @@ private MethodBuilder AddMethodImpl(MethodInfo mi, int methodInfoIndex)
il.Emit(OpCodes.Ldloc, methodInfoLocal);

Type[] genericTypes = mi.GetGenericArguments();
GenericArray<Type> typeArr = new GenericArray<Type>(il, genericTypes.Length);
GenericArray<Type> typeArr = new GenericArray<Type>(_assembly, il, genericTypes.Length);
for (int i = 0; i < genericTypes.Length; ++i)
{
typeArr.BeginSet(i);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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))
{
Expand All @@ -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))
{
Expand All @@ -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;
}
Expand All @@ -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<T>
{
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[]));

Expand Down Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,226 @@ private static void TestRefOutInInvocation(Action<TestType_IOut_Ref> 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<TestType_IPointerService, TestDispatchProxy>(useGenericCreate);

object received = null;
((TestDispatchProxy)proxy).CallOnInvoke = (method, args) =>
{
received = args[0];
return null;
};

proxy.Echo(&value);
Assert.IsType<Pointer>(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<TestType_IPointerService, TestDispatchProxy>(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<InvalidCastException>(() => { proxy.Echo(null); });

result = "42";
Assert.Throws<InvalidCastException>(() => { 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<TestType_IPointerService, TestDispatchProxy>(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<InvalidCastException>(() => { 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<TestType_IPointerService, TestDispatchProxy>(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<TestType_IPointerService, TestDispatchProxy>(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<Pointer>(received)));

result = Pointer.Box(&p, typeof(int**));
Assert.Equal((nint)(&p), (nint)proxy.EchoPointerPointer(&p));
Assert.Equal((nint)(&p), (nint)Pointer.Unbox(Assert.IsType<Pointer>(received)));

result = null;
proxy.In(in p);
Assert.Equal((nint)(&value), (nint)Pointer.Unbox(Assert.IsType<Pointer>(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<TestType_IPointerService, TestDispatchProxy>(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<TestType_IPointerArgumentService, TestDispatchProxy>(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<TestType_IPointerReturnService, TestDispatchProxy>(useGenericCreate);
((TestDispatchProxy)proxy).CallOnInvoke = (method, args) => result;
Assert.Equal((nint)(&value), (nint)proxy.Get());
}

private static TestType_IHelloService CreateTestHelloProxy(bool useGenericCreate) =>
CreateHelper<TestType_IHelloService, TestDispatchProxy>(useGenericCreate);

Expand Down
Loading
Loading