-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Convert invalid C# uses of UnmanagedCallersOnly to IL. #42146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.IL"> | ||
| <PropertyGroup> | ||
| <OutputType>library</OutputType> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <Compile Include="InvalidCallbacks.il" /> | ||
| </ItemGroup> | ||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| .assembly extern System.Runtime { } | ||
| .assembly extern System.Runtime.InteropServices { } | ||
|
|
||
| .assembly InvalidCSharp.dll { } | ||
|
|
||
| .class public auto ansi beforefieldinit InvalidCSharp.GenericClass`1<T> | ||
| extends System.Object | ||
| { | ||
| .method public hidebysig static | ||
| void CallbackMethod ( | ||
| int32 n | ||
| ) cil managed preservesig | ||
| { | ||
| .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute::.ctor() = ( | ||
| 01 00 00 00 | ||
| ) | ||
| .maxstack 8 | ||
| IL_0000: ldstr "Functions with attribute UnmanagedCallersOnlyAttribute within a generic type are invalid" | ||
| IL_0005: newobj instance void [System.Runtime]System.Exception::.ctor(string) | ||
| IL_000a: throw | ||
| } | ||
|
|
||
| .method public hidebysig specialname rtspecialname | ||
| instance void .ctor () cil managed | ||
| { | ||
| .maxstack 8 | ||
| IL_0000: ldarg.0 | ||
| IL_0001: call instance void [System.Runtime]System.Object::.ctor() | ||
| IL_0006: ret | ||
| } | ||
| } | ||
|
|
||
| .class public auto ansi beforefieldinit InvalidCSharp.Callbacks | ||
| extends [System.Runtime]System.Object | ||
| { | ||
| .method public hidebysig static | ||
| int32 CallbackMethodGeneric<T> ( | ||
| !!T arg | ||
| ) cil managed preservesig | ||
| { | ||
| .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute::.ctor() = ( | ||
| 01 00 00 00 | ||
| ) | ||
| .maxstack 8 | ||
| IL_0000: ldstr "Functions with attribute UnmanagedCallersOnlyAttribute cannot have generic arguments" | ||
| IL_0005: newobj instance void [System.Runtime]System.Exception::.ctor(string) | ||
| IL_000a: throw | ||
| } | ||
|
|
||
| .method public hidebysig | ||
| instance int32 CallbackNonStatic ( | ||
| int32 val | ||
| ) cil managed preservesig | ||
| { | ||
| .custom instance void [System.Runtime.InteropServices]System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute::.ctor() = ( | ||
| 01 00 00 00 | ||
| ) | ||
| .maxstack 8 | ||
| IL_0000: ldstr "Instance functions with attribute UnmanagedCallersOnlyAttribute are invalid" | ||
| IL_0005: newobj instance void [System.Runtime]System.Exception::.ctor(string) | ||
| IL_000a: throw | ||
| } | ||
|
|
||
| .method public hidebysig specialname rtspecialname | ||
| instance void .ctor () cil managed | ||
| { | ||
| .maxstack 8 | ||
| IL_0000: ldarg.0 | ||
| IL_0001: call instance void [System.Runtime]System.Object::.ctor() | ||
| IL_0006: ret | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,9 +3,11 @@ | |||||||||||
| using System; | ||||||||||||
| using System.Collections.Generic; | ||||||||||||
| using System.Diagnostics; | ||||||||||||
| using System.Runtime.CompilerServices; | ||||||||||||
| using System.IO; | ||||||||||||
| using System.Reflection; | ||||||||||||
| using System.Reflection.Emit; | ||||||||||||
| using System.Runtime.CompilerServices; | ||||||||||||
| using System.Runtime.Loader; | ||||||||||||
| using System.Runtime.InteropServices; | ||||||||||||
| using System.Threading; | ||||||||||||
| using TestLibrary; | ||||||||||||
|
|
@@ -35,6 +37,20 @@ public static class UnmanagedCallersOnlyDll | |||||||||||
| public static extern int PInvokeMarkedWithUnmanagedCallersOnly(int n); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private const string InvalidCSharpAssemblyName = "InvalidCSharp.dll"; | ||||||||||||
|
|
||||||||||||
| public static Type GetCallbacksType() | ||||||||||||
| { | ||||||||||||
| var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(Environment.CurrentDirectory, InvalidCSharpAssemblyName)); | ||||||||||||
| return asm.GetType("InvalidCSharp.Callbacks"); | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ProjectReferences seems to be working fine for test projects. For example: https://github.com/dotnet/runtime/blob/master/src/tests/baseservices/callconvs/TestCallingConventions.csproj#L11 Project reference would be even better than
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, The reason the referenced test works is because of the following:
This is fine for now, but when we version to .NET 6, this will break. The current approach is the only way I have found that continue to work through time.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think this should be a problem. What is the csc error that you are seeing? I know that csc was picky about situations like these in the past, but these checks were removed a while ago. They did not make sense for .NET Core nor for .NET Framework with binding redirects. It is fairly normal to have multiple different System.Runtime references in .NET Core world. For example, it is fully supported for project targeting net6 (that has System.Runtime 6.0) to reference project targeting net5 (that has System.Runtime 5.0.). If you do not like hardcoding versions, you can just omit them. For example: https://github.com/jkotas/runtime/blob/5095f1f75f86c2aedaedf9e35457cb6635a3c989/src/tests/Regressions/coreclr/16355/boring.il#L4
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was hitting the error about
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, that works just fine throughout. Any idea how I could have made that error occur? That is frustrating beyond belief. I will push up a new PR with the IL updates as well.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This error is typically caused by missing System.Runtime reference, not by having incompatible System.Runtime references.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is what I am seeing now. Failure during build of C# test application: Call site in C#: InvalidCSharp.Test.Invoke(null, n);Snippet of IL code being called in C#:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe C# compiler needs the public key tokens to match? You can try adding
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the C# compiler command line have
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That did it. This is a very confusing issue. But it works now so yay.
It does. |
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public static Type GetGenericClassOfIntType() | ||||||||||||
| { | ||||||||||||
| var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(Environment.CurrentDirectory, InvalidCSharpAssemblyName)); | ||||||||||||
| return asm.GetType("InvalidCSharp.GenericClass`1").MakeGenericType(typeof(int)); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private delegate int IntNativeMethodInvoker(); | ||||||||||||
| private delegate void NativeMethodInvoker(); | ||||||||||||
|
|
||||||||||||
|
|
@@ -338,12 +354,6 @@ void CallAsDelegate() | |||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| [UnmanagedCallersOnly] | ||||||||||||
| public int CallbackNonStatic(int val) | ||||||||||||
| { | ||||||||||||
| Assert.Fail($"Instance functions with attribute {nameof(UnmanagedCallersOnlyAttribute)} are invalid"); | ||||||||||||
| return -1; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public static void NegativeTest_NonStaticMethod() | ||||||||||||
| { | ||||||||||||
|
|
@@ -354,7 +364,7 @@ void TestUnmanagedCallersOnlyNonStatic() | |||||||||||
| { | ||||||||||||
| .locals init ([0] native int ptr) | ||||||||||||
| nop | ||||||||||||
| ldftn int CallbackNonStatic(int) | ||||||||||||
| ldftn int GetCallbacksType().CallbackNonStatic(int) | ||||||||||||
| stloc.0 | ||||||||||||
|
|
||||||||||||
| ldloc.0 | ||||||||||||
|
|
@@ -371,7 +381,7 @@ ldftn int CallbackNonStatic(int) | |||||||||||
| il.Emit(OpCodes.Nop); | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The dynamic IL generation can be moved to the IL project too. It would improve the test coverage for AOT. It can be done as a separate PR. |
||||||||||||
|
|
||||||||||||
| // Get native function pointer of the callback | ||||||||||||
| il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod(nameof(CallbackNonStatic))); | ||||||||||||
| il.Emit(OpCodes.Ldftn, GetCallbacksType().GetMethod("CallbackNonStatic")); | ||||||||||||
| il.Emit(OpCodes.Stloc_0); | ||||||||||||
| il.Emit(OpCodes.Ldloc_0); | ||||||||||||
|
|
||||||||||||
|
|
@@ -436,13 +446,6 @@ ldftn int CallbackMethodNonBlittable(bool) | |||||||||||
| Assert.Throws<InvalidProgramException>(() => { testNativeMethod(); }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| [UnmanagedCallersOnly] | ||||||||||||
| public static int CallbackMethodGeneric<T>(T arg) | ||||||||||||
| { | ||||||||||||
| Assert.Fail($"Functions with attribute {nameof(UnmanagedCallersOnlyAttribute)} cannot have generic arguments"); | ||||||||||||
| return -1; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public static void NegativeTest_NonInstantiatedGenericArguments() | ||||||||||||
| { | ||||||||||||
| Console.WriteLine($"Running {nameof(NegativeTest_NonInstantiatedGenericArguments)}..."); | ||||||||||||
|
|
@@ -452,7 +455,7 @@ void TestUnmanagedCallersOnlyNonInstGenericArguments() | |||||||||||
| { | ||||||||||||
| .locals init ([0] native int ptr) | ||||||||||||
| IL_0000: nop | ||||||||||||
| IL_0001: ldftn void CallbackMethodGeneric(T) | ||||||||||||
| IL_0001: ldftn void InvalidCSharp.Callbacks.CallbackMethodGeneric(T) | ||||||||||||
| IL_0007: stloc.0 | ||||||||||||
| IL_0008: ret | ||||||||||||
| } | ||||||||||||
|
|
@@ -463,7 +466,7 @@ .locals init ([0] native int ptr) | |||||||||||
| il.Emit(OpCodes.Nop); | ||||||||||||
|
|
||||||||||||
| // Get native function pointer of the callback | ||||||||||||
| il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod(nameof(CallbackMethodGeneric))); | ||||||||||||
| il.Emit(OpCodes.Ldftn, GetCallbacksType().GetMethod("CallbackMethodGeneric")); | ||||||||||||
| il.Emit(OpCodes.Stloc_0); | ||||||||||||
|
|
||||||||||||
| il.Emit(OpCodes.Ret); | ||||||||||||
|
|
@@ -482,7 +485,7 @@ void TestUnmanagedCallersOnlyInstGenericArguments() | |||||||||||
| { | ||||||||||||
| .locals init ([0] native int ptr) | ||||||||||||
| nop | ||||||||||||
| ldftn void CallbackMethodGeneric(int) | ||||||||||||
| ldftn void InvalidCSharp.Callbacks.CallbackMethodGeneric(int) | ||||||||||||
| stloc.0 | ||||||||||||
|
|
||||||||||||
| ldloc.0 | ||||||||||||
|
|
@@ -499,7 +502,7 @@ ldftn void CallbackMethodGeneric(int) | |||||||||||
| il.Emit(OpCodes.Nop); | ||||||||||||
|
|
||||||||||||
| // Get native function pointer of the instantiated generic callback | ||||||||||||
| il.Emit(OpCodes.Ldftn, typeof(Program).GetMethod(nameof(CallbackMethodGeneric)).MakeGenericMethod(new [] { typeof(int) })); | ||||||||||||
| il.Emit(OpCodes.Ldftn, GetCallbacksType().GetMethod("CallbackMethodGeneric").MakeGenericMethod(new [] { typeof(int) })); | ||||||||||||
| il.Emit(OpCodes.Stloc_0); | ||||||||||||
| il.Emit(OpCodes.Ldloc_0); | ||||||||||||
|
|
||||||||||||
|
|
@@ -515,15 +518,6 @@ ldftn void CallbackMethodGeneric(int) | |||||||||||
| Assert.Throws<InvalidProgramException>(() => { testNativeMethod(); }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public class GenericClass<T> | ||||||||||||
| { | ||||||||||||
| [UnmanagedCallersOnly] | ||||||||||||
| public static void CallbackMethod(int n) | ||||||||||||
| { | ||||||||||||
| Assert.Fail($"Functions with attribute {nameof(UnmanagedCallersOnlyAttribute)} within a generic type are invalid"); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public static void NegativeTest_FromInstantiatedGenericClass() | ||||||||||||
| { | ||||||||||||
| Console.WriteLine($"Running {nameof(NegativeTest_FromInstantiatedGenericClass)}..."); | ||||||||||||
|
|
@@ -533,7 +527,7 @@ void TestUnmanagedCallersOnlyInstGenericType() | |||||||||||
| { | ||||||||||||
| .locals init ([0] native int ptr) | ||||||||||||
| nop | ||||||||||||
| ldftn int GenericClass<int>::CallbackMethod(int) | ||||||||||||
| ldftn int InvalidCSharp.GenericClass<int>::CallbackMethod(int) | ||||||||||||
| stloc.0 | ||||||||||||
|
|
||||||||||||
| ldloc.0 | ||||||||||||
|
|
@@ -550,7 +544,7 @@ .locals init ([0] native int ptr) | |||||||||||
| il.Emit(OpCodes.Nop); | ||||||||||||
|
|
||||||||||||
| // Get native function pointer of the callback from the instantiated generic class. | ||||||||||||
| il.Emit(OpCodes.Ldftn, typeof(GenericClass<int>).GetMethod(nameof(GenericClass<int>.CallbackMethod))); | ||||||||||||
| il.Emit(OpCodes.Ldftn, GetGenericClassOfIntType().GetMethod("CallbackMethod")); | ||||||||||||
| il.Emit(OpCodes.Stloc_0); | ||||||||||||
| il.Emit(OpCodes.Ldloc_0); | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
Uh oh!
There was an error while loading. Please reload this page.