Skip to content
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

UnsafeAccessor does not work for generic constructors, .NET 9 #110054

Closed
mgravell opened this issue Nov 21, 2024 · 8 comments
Closed

UnsafeAccessor does not work for generic constructors, .NET 9 #110054

mgravell opened this issue Nov 21, 2024 · 8 comments
Labels
area-System.Runtime.CompilerServices needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration

Comments

@mgravell
Copy link
Member

mgravell commented Nov 21, 2024

Description

This ticket suggests that this should work from 9.0 Preview 4, however this fails in 9.0 GA:

using System.Runtime.CompilerServices;

// this works fine
Console.WriteLine(Hack.CreateFoo());

// this doesn't work
Console.WriteLine(Hack.CreateBar<int>());

// this doesn't work
Bar<int> evil = (Bar<int>)RuntimeHelpers.GetUninitializedObject(typeof(Bar<int>));
Hack.HacketyHackHack(evil);
Console.WriteLine(evil);

static class Hack
{
    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
    internal static extern Foo CreateFoo();

    [UnsafeAccessor(UnsafeAccessorKind.Method, Name = ".ctor")]
    internal extern static void HacketyHackHack<T>(Bar<T> obj);

    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
    internal static extern Bar<T> CreateBar<T>();
}
class Foo
{
    private readonly string s = ".ctor has run";
    public override string ToString() => s;
    private Foo() { }
}
class Bar<T>
{
    private readonly string s = ".ctor has run; " + typeof(T).Name;
    public override string ToString() => s;
    private Bar() { }
}

Reproduction Steps

as above

Expected behavior

it works

Actual behavior

Unhandled exception. System.InvalidProgramException: Generic type constraints do not match.
   at Hack.CreateBar[T]()
   at Program.<Main>$(String[] args) in C:\Users\marcg\source\repos\ConsoleApp4\ConsoleApp4\Program.cs:line 7

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Nov 21, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Nov 21, 2024
@vcsjones vcsjones added area-System.Runtime.CompilerServices and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Nov 21, 2024
@vcsjones
Copy link
Member

Where T is declared seems to matter. This works:

using System;
using System;
using System.Runtime.CompilerServices;

Hack<int>.CreateBar();

Bar<int> evil = (Bar<int>)RuntimeHelpers.GetUninitializedObject(typeof(Bar<int>));
Console.WriteLine(Hack<short>.CreateBar());
Hack<int>.HacketyHackHack(evil);
Console.WriteLine(evil);

static class Hack<T>
{

    [UnsafeAccessor(UnsafeAccessorKind.Method, Name = ".ctor")]
    internal extern static void HacketyHackHack(Bar<T> obj);

    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
    internal static extern Bar<T> CreateBar();
}

class Bar<T>
{
    private readonly string s = ".ctor has run; " + typeof(T).Name;
    public override string ToString() => s;
    private Bar() { }
}

@mgravell
Copy link
Member Author

Useful workaround @vcsjones ! (I haven't verified, but will do tomorrow)

@jkotas
Copy link
Member

jkotas commented Nov 22, 2024

cc @AaronRobinsonMSFT

@AaronRobinsonMSFT
Copy link
Member

AaronRobinsonMSFT commented Nov 22, 2024

Useful workaround @vcsjones ! (I haven't verified, but will do tomorrow)

@mgravell This isn't a "workaround", it is how the feature works. See the following comment in the Remarks section of the documentation.

Generic parameters are supported since .NET 9. Generic parameters must match the target in form and index (that is, type parameters must be type parameters and method parameters must be method parameters).

In your example the T for Bar<> is a type parameter, but in HacketyHackHack<T>(Bar<T> obj) and CreateBar<T>(), T is a method parameter. The approach demonstrated in the documentation and in #110054 (comment) makes the T a type parameter and therefore the look-up works.

Please let me know if the documentation could be updated to help clarify things.

@AaronRobinsonMSFT AaronRobinsonMSFT added needs-author-action An issue or pull request that requires more info or actions from the author. and removed untriaged New issue has not been triaged by the area owner labels Nov 22, 2024
@mgravell
Copy link
Member Author

Ok, this is reader error. There's even examples. I personally didn't find the exception cear, but I can't say that it is incorrect. Thanks for the assist.

@dotnet-policy-service dotnet-policy-service bot added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed needs-author-action An issue or pull request that requires more info or actions from the author. labels Nov 22, 2024
@teo-tsirpanis
Copy link
Contributor

We can't add analyzers about it because of reference assemblies, but I wonder if a build tool that has a whole-program view like ilc or crossgen could warn that an unsafe accessor does not have a valid target.

@AaronRobinsonMSFT
Copy link
Member

We can't add analyzers about it because of reference assemblies, but I wonder if a build tool that has a whole-program view like ilc or crossgen could warn that an unsafe accessor does not have a valid target.

Yes it can. It is what native AOT does for this scenario and should warn. Note that UnsafeAccessorAttribute is a very advanced scenario and wasn't designed to be consumed directly. It is in the same bucket as most uses of Unsafe APIs - a best effort on errors and we defer to documentation for guidance.

@jkotas
Copy link
Member

jkotas commented Nov 22, 2024

It is what native AOT does for this scenario and should warn.

Native AOT compilation does not and should not warn when the UnsafeAccessorAttribute target is missing. UnsafeAccessorAttribute is faster version of regular reflection. Native AOT compilation does not warn when regular reflection is used to find non-existing target either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Runtime.CompilerServices needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration
Projects
None yet
Development

No branches or pull requests

5 participants