-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
This is related to #111003 and #119935 and somewhere between a bug report and an enhancement suggestion. In short, the issue is that function pointer types constructed from the C# typeof operator as well as those retrieved from reflection calls like FieldInfo.FieldType or MethodInfo.ReturnType lose all information about unmanaged calling conventions and other custom modifiers. This has implications specifically in the realm of runtime code generation with System.Reflection.Emit (SRE). Here is an illustrative example:
using System.Reflection;
using System.Reflection.Emit;
PersistedAssemblyBuilder ab = new(new("Assembly"), typeof(object).Assembly);
ModuleBuilder mb = ab.DefineDynamicModule("Assembly.dll");
TypeBuilder tb = mb.DefineType("Program", TypeAttributes.Public);
MethodBuilder meth = tb.DefineMethod("Main", MethodAttributes.Public | MethodAttributes.Static);
meth.SetReturnType(typeof(void));
ILGenerator il = meth.GetILGenerator();
il.Emit(OpCodes.Ldsfld, typeof(Container).GetField("Field"));
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ret);
tb.CreateType();
ab.Save("Assembly.dll");
Assembly loadedAssembly = Assembly.LoadFrom("Assembly.dll");
loadedAssembly.GetType("Program").GetMethod("Main").Invoke(null, null);
public unsafe class Container
{
public static delegate* unmanaged[Cdecl]<int> Field;
}Note: This example wouldn't currently work anyway due to the aforementioned issue with function pointer support in SRE. This issue has been addressed by me in PR #119935.
When the above program is ran, which attempts to load the field Container.Field1 using SRE, the following exception will be thrown:
Unhandled exception. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
+ ---> System.MissingFieldException: Field not found: 'Container.Field'.
at Program.Main()
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
--- End of inner exception stack trace ---
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Program.<Main>$(String[] args)This exception stems from the fact that SRE works with non-modified types internally to build CIL metadata, such as in ModuleBuilderImpl.GetMemberReferenceHandle(). This causes the generated ldsfld instruction to reference an invalid, non-existent field with all modifiers stripped away:
// Correct:
ldsfld method unmanaged cdecl int32 *() [test]Container::Field
// Invalid (missing cdecl specifier):
ldsfld method unmanaged int32 *() [test]Container::FieldNow, one possible fix for this would be to rewrite SRE to explicitly use the modified types everywhere this applies (by calling the appropriate methods such as FieldInfo.GetModifiedFieldType(). I tried doing this and actually managed to get this simple case to work correctly, but rewriting everything would take a lot of effort since not all Type methods are supported by modified types.
A better solution in my opinion (although it might be a more invasive change) would be to treat function pointers in a special way where they would always preserve calling conventions and modifiers in their Type representation, even without an explicit field reference or similar. This seems like a more sustainable solution to me, especially since it also mitigates the unintuitive behavior of the C# typeof operator (in this comment I mention a possible workaround).
I can't say what the objectively better solution to this problem would be and I don't have any concrete ideas on how to implement it, so this issue shall act more as a general bug report.