Skip to content

System.Reflection.Emit can't call a generic method having a function pointer parameter #100020

Open
@MrJul

Description

@MrJul

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:

COMPlusThrow(kArgumentNullException, W("ArgumentNull_String"));

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions