Description
Description
It isn't possible to emit a dynamic assembly using System.Reflection.Emit
that emits a call to a generic method having a function pointer as an argument. The code fails with a ArgumentNullException
Reproduction Steps
using System;
using System.Reflection;
using System.Reflection.Emit;
public static class Program
{
public static void Main()
{
var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("test"), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("test");
var type = module.DefineType("TestType", TypeAttributes.Class | TypeAttributes.Public);
var method = type.DefineMethod("TestMethod", MethodAttributes.Public | MethodAttributes.Static, typeof(void), null);
var m1 = typeof(C).GetMethod("M1")!;
var m2 = typeof(C).GetMethod("M2")!;
// void TestMethod() => C.M1<string>(&C.M2);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldftn, m2);
il.EmitCall(OpCodes.Call, m1.MakeGenericMethod(typeof(string)), null);
il.Emit(OpCodes.Ret);
var runtimeType = type.CreateType();
var runtimeMethod = runtimeType.GetMethod("TestMethod")!;
runtimeMethod.Invoke(null, null);
}
}
public static class C
{
public static unsafe void M1<T>(delegate*<void> action) => action();
public static void M2() => Console.WriteLine("Called");
}
This program emits a method equivalent to the following C# code:
void TestMethod() => C.M1<string>(&C.M2);
Expected behavior
The method is correctly emitted and runs.
Actual behavior
The EmitCall()
fails with a ArgumentNullException
.
Stack trace:
System.ArgumentNullException: String reference not set to an instance of a String.
at System.Reflection.Emit.RuntimeModuleBuilder.GetTypeRefNested(Type type, Module refedModule)
at System.Reflection.Emit.RuntimeModuleBuilder.GetTypeTokenWorkerNoLock(Type type, Boolean getGenericDefinition)
at System.Reflection.Emit.RuntimeModuleBuilder.GetTypeTokenInternal(Type type, Boolean getGenericDefinition)
at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
at System.Reflection.Emit.SignatureHelper.AddArguments(Type[] arguments, Type[][] requiredCustomModifiers, Type[][] optionalCustomModifiers)
at System.Reflection.Emit.SignatureHelper.GetMethodSigHelper(Module scope, CallingConventions callingConvention, Int32 cGenericParam, Type returnType, Type[] requiredReturnTypeCustomModifiers, Type[] optionalReturnTypeCustomModifiers, Type[] parameterTypes, Type[][] requiredParameterTypeCustomModifiers, Type[][] optionalParameterTypeCustomModifiers)
at System.Reflection.Emit.RuntimeModuleBuilder.GetMemberRefSignature(MethodBase method, Int32 cGenericParameters)
at System.Reflection.Emit.RuntimeModuleBuilder.GetMemberRefToken(MethodBase method, Type[] optionalParameterTypes)
at System.Reflection.Emit.RuntimeModuleBuilder.GetMethodTokenInternal(MethodBase method, Type[] optionalParameterTypes, Boolean useMethodDef)
at System.Reflection.Emit.RuntimeILGenerator.EmitCall(OpCode opcode, MethodInfo methodInfo, Type[] optionalParameterTypes)
at Program.Main() in C:\Dev\EmitCallGenericFuncPtrArgBug\Program.cs:line 20
Regression?
Technically in .NET 7 the emit phase works, which now fails in .NET 8.
That doesn't really matter since the emitted code can't run in .NET 7: it searches for a C.M1(IntPtr)
overload, which doesn't exist.
Known Workarounds
Using IntPtr
instead of a function pointer parameter works.
Configuration
.NET 8.0.2
Windows 11 22631.3296
x64
Other information
Remarks:
- This works if the called method isn't generic.
- This works with a
DynamicMethod
instead of a dynamic assembly.
The GetTypeRefNested
method probably isn't meant to be called for function pointers, as it seems to be assuming that a proper type (not a function pointer) was passed, here:
FullName
is null
for a function pointer, causing the GetTypeRef
to fail:
runtime/src/coreclr/vm/commodule.cpp
Line 42 in ffe7e26