runtime: P/Invoke function returning SafeSocketHandle throws "System.MissingMethodException: .ctor"
Description
On .NET 5, P/Invoke function returning SafeSocketHandle
throws “System.MissingMethodException: .ctor”. See MissingSafeSocketHandleCtor.zip for a full repro project.
public static class Program
{
private const int AF_INET = 2;
private const int SOCK_STREAM = 1;
public static void Main()
{
// System.MissingMethodException: '.ctor'
// (This returns -1 because we have not initialized WinSock2; that does not matter, though.)
var sock = socket(AF_INET, SOCK_STREAM, 0);
sock.Dispose();
}
[DllImport("Ws2_32.dll")]
public static extern SafeSocketHandle socket(int af, int type, int protocol);
}
Configuration
- .NET SDK 5.0.100
- Windows 10 Pro x64 (20H2, 19042.630)
Regression?
Yes. Worked on 3.1.404.
Other information
The SafeHandle doc says “You should also provide a parameterless constructor”.
A parameterless ctor is defined in the SafeSocketHandle
source file but missing from the released assembly at “C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.0\System.Net.Sockets.dll”.
I do not think this is an intended behavior. I do now know the root cause, though.
Stack traces:
Unhandled exception. System.MissingMethodException: .ctor
at ConsoleAppSafeSocketHandle.WinSock2.socket(Int32 af, Int32 type, Int32 protocol)
at ConsoleAppSafeSocketHandle.Program.Main()
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 1
- Comments: 50 (48 by maintainers)
Commits related to this issue
- P/Invoke function returning SafeSocketHandle throws MissingMethodException The ILLinker is trimming the private default constructors of SafeHandle classes if they are not used by the library. Adding ... — committed to eerhardt/runtime by eerhardt 4 years ago
- P/Invoke function returning SafeSocketHandle throws MissingMethodException (#45911) * P/Invoke function returning SafeSocketHandle throws MissingMethodException The ILLinker is trimming the privat... — committed to dotnet/runtime by eerhardt 4 years ago
- P/Invoke function returning SafeSocketHandle throws MissingMethodException (#45911) * P/Invoke function returning SafeSocketHandle throws MissingMethodException The ILLinker is trimming the private ... — committed to eerhardt/runtime by eerhardt 4 years ago
- [release/5.0] P/Invoke function returning SafeSocketHandle throws MissingMethodException (#45911) (#45942) * P/Invoke function returning SafeSocketHandle throws MissingMethodException (#45911) The... — committed to dotnet/runtime by eerhardt 4 years ago
For the 5.0 fix, I looked through all SafeHandle types in the shared framework with the following code:
That spit out 2 types that would have this issue:
So to fix this in 5.0 servicing, my plan is to introduce 2 “Library_Build” XML descriptors for the 2 SafeHandles above. That will ensure their private constructors are preserved.
Going forward, I agree that we need a better long term strategy here - manually inspecting/verifying that every SafeHandle we create has the right constructors after building isn’t a sustainable solution.
Looking through the rest of the SafeHandle types that aren’t public, one jumps out that may be in trouble:
System.Reflection.Emit.PunkSafeHandle
.https://github.com/dotnet/runtime/blob/53c16198a8417e87e62c9df7c5e2e2602f9aa01d/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/ISymWrapperCore.cs#L368-L374
This SafeHandle is used as an
Out
of a delegate to a native function. Looking through the code, it appears this is only ever used if someone callsAssemblyBuilder::DefineDynamicModule(name, emitSymbolInfo: true)
. However, that API is not public in .NET Standard or .NET Core - only .NET Framework and Mono. So there is no way for a test, or user code to call intoPunkSafeHandle
, from what I can tell.I don’t think that’s the case, the setup for this is complicated and requires several advanced options to be set correctly. It’d also question if it has any noticeable impact as you are not really trimming but more like tweaking compiler output.
5.0.2 should go out next week, you can test the fix by installing https://github.com/dotnet/installer#installers-and-binaries 5.0.2 preview version
This is now fixed in master (PR #45911) and release/5.0 (PR #45942)
I understand this isn’t the “trim my app” mode. But at the same time, we trim the libraries we ship for a reason, to reduce the size of the shared framework distribution, and that reason could be meaningful for any library builder. If memory serves, this reduced the size of our libraries on average by 5-10%. Who wouldn’t want their nuget package to be 5-10% smaller? (Rhetorical question.) I get this isn’t the primary use case for the linker, and that’s fine. But I don’t think we should be discounting the use case nor the bugs that end up silently introduced when it’s used in this supported fashion.
Regardless, this makes me wonder what other issues are lurking, with things being trimmed away we weren’t expecting. I definitely agree with @GrabYourPitchforks earlier suggestion:
If we want to define new guidelines moving forward for new stuff, and retroactively make sure all our SafeHandles conform, that’s fine. But it doesn’t change the fact that a) the linker is a public tool for use in the ecosystem and should work with standard .NET patterns, and b) private ctors are supported as a standard pattern with SafeHandle.
As a follow-up, we should diff all (incl. non-public) members of the runtime + BCL’s pre-trimmed DLLs and post-trimmed DLLs. Could help identify other places where we’ve inadvertently made a break.