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

Attribute argument of a type containing function pointer fails with TypeLoadException during reflection #80138

Closed
jjonescz opened this issue Jan 2, 2023 · 7 comments

Comments

@jjonescz
Copy link
Member

jjonescz commented Jan 2, 2023

Version Used: dotnet/roslyn@34180c3 (2022-12-20)

Discovered while working on dotnet/roslyn#66073.

Steps to Reproduce:

[Theory, CombinatorialData, WorkItem(65594, "https://github.com/dotnet/roslyn/issues/65594")]
public void Attribute_TypedDefault_Enum([CombinatorialValues("class", "struct")] string kind)
{
    var source = $$"""
        using System;
        using System.Linq;
        
        var attr = typeof(C).CustomAttributes.Single(d => d.AttributeType == typeof(A));
        var arg = attr.ConstructorArguments.Single();
        Console.WriteLine((int)arg.Value == 0);
        
        class A : Attribute
        {
            public A(B<delegate*<void>[]>.E e) { }
        }

        {{kind}} B<T>
        {
            public enum E { }
        }

        [A(default)]
        class C { }
        """;

    var verifier = CompileAndVerify(source, expectedOutput: "True", symbolValidator: static module =>
    {
        var c = module.GlobalNamespace.GetTypeMember("C");
        var attr = c.GetAttributes().Single(d => d.AttributeClass?.Name == "A");
        Assert.Empty(attr.ConstructorArguments);
    });
    verifier.VerifyDiagnostics();
}

Expected Behavior: The test succeeds.

Actual Behavior: At runtime, it fails with:

System.TypeLoadException: Could not load type '(fnptr)' from assembly 'System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'.
   at System.RuntimeTypeHandle.GetTypeByNameUsingCARules(String name, RuntimeModule scope)
   at System.Reflection.CustomAttributeTypedArgument.ResolveType(RuntimeModule scope, String typeName)
   at System.Reflection.CustomAttributeTypedArgument..ctor(RuntimeModule scope, CustomAttributeEncodedArgument encodedArg)
   at System.Reflection.RuntimeCustomAttributeData.get_ConstructorArguments()
   at Program.<Main>$(String[] args)

Changing A's constructor to take parameter of a different type, e.g., B<int>.E instead of B<delegate*<void>[]>.E, makes the test pass. (Although I wouldn't expect the symbol validator to discover empty attribute constructor arguments, but that's a different issue - dotnet/roslyn#66370.)

Note This issue is mentioned in tests (dotnet/roslyn#66073).

@jjonescz jjonescz added the bug label Jan 2, 2023
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Jan 2, 2023
@jcouv
Copy link
Member

jcouv commented Jan 3, 2023

Assigned to @jjonescz for initial investigation (confirm whether this is a runtime/reflection issue). Thanks

@jcouv jcouv removed the untriaged New issue has not been triaged by the area owner label Jan 3, 2023
@jjonescz
Copy link
Member Author

jjonescz commented Jan 3, 2023

@jcouv Yes, this seems to be runtime issue. The emitted metadata look correct. Here is an IL repro:

[Fact]
public void Issue66187()
{
    var ilSource = """
        .class public auto ansi beforefieldinit A
            extends [mscorlib]System.Attribute
        {
            .method public hidebysig specialname rtspecialname 
                instance void .ctor (
                    valuetype B`1/E<method void *()[]> e
                ) cil managed 
            {
                .maxstack 8
                ret
            }
        }

        .class public auto ansi beforefieldinit B`1<T>
            extends System.Object
        {
            .class nested public auto ansi sealed E<T>
                extends [mscorlib]System.Enum
            {
                .param type T
                .field public specialname rtspecialname int32 value__
            }
        }

        .class public auto ansi beforefieldinit C
            extends System.Object
        {
            .custom instance void A::.ctor(valuetype B`1/E<method void *()[]>) = (
                01 00 00 00 00 00 00 00
            )
        }
        """;
    var source = """
        using System;
        using System.Linq;

        var attr = typeof(C).CustomAttributes.Single(d => d.AttributeType == typeof(A));
        var arg = attr.ConstructorArguments.Single();
        Console.WriteLine((int?)arg.Value == 0);
        """;
    var comp = CreateCompilationWithIL(source, ilSource).VerifyDiagnostics();
    CompileAndVerify(comp, expectedOutput: "True");
}

Fails with:

System.TypeLoadException : Could not load type '(fnptr)' from assembly 'System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'.
Stack Trace:
     at System.RuntimeTypeHandle.GetTypeByNameUsingCARules(String name, RuntimeModule scope)
     at System.Reflection.CustomAttributeTypedArgument.ResolveType(RuntimeModule scope, String typeName)
     at System.Reflection.CustomAttributeTypedArgument..ctor(RuntimeModule scope, CustomAttributeEncodedArgument encodedArg)
     at System.Reflection.RuntimeCustomAttributeData.get_ConstructorArguments()
  (4,0): at Program.<Main>$(String[] args)

@jcouv jcouv transferred this issue from dotnet/roslyn Jan 3, 2023
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jan 3, 2023
@ghost
Copy link

ghost commented Jan 3, 2023

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Issue Details

Version Used: 34180c3974c46fe354740d7f5fddd17244474612 (2022-12-20)

Discovered while working on dotnet/roslyn#66073.

Steps to Reproduce:

[Theory, CombinatorialData, WorkItem(65594, "https://github.com/dotnet/roslyn/issues/65594")]
public void Attribute_TypedDefault_Enum([CombinatorialValues("class", "struct")] string kind)
{
    var source = $$"""
        using System;
        using System.Linq;
        
        var attr = typeof(C).CustomAttributes.Single(d => d.AttributeType == typeof(A));
        var arg = attr.ConstructorArguments.Single();
        Console.WriteLine((int)arg.Value == 0);
        
        class A : Attribute
        {
            public A(B<delegate*<void>[]>.E e) { }
        }

        {{kind}} B<T>
        {
            public enum E { }
        }

        [A(default)]
        class C { }
        """;

    var verifier = CompileAndVerify(source, expectedOutput: "True", symbolValidator: static module =>
    {
        var c = module.GlobalNamespace.GetTypeMember("C");
        var attr = c.GetAttributes().Single(d => d.AttributeClass?.Name == "A");
        Assert.Empty(attr.ConstructorArguments);
    });
    verifier.VerifyDiagnostics();
}

Expected Behavior: The test succeeds.

Actual Behavior: At runtime, it fails with:

System.TypeLoadException: Could not load type '(fnptr)' from assembly 'System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'.
   at System.RuntimeTypeHandle.GetTypeByNameUsingCARules(String name, RuntimeModule scope)
   at System.Reflection.CustomAttributeTypedArgument.ResolveType(RuntimeModule scope, String typeName)
   at System.Reflection.CustomAttributeTypedArgument..ctor(RuntimeModule scope, CustomAttributeEncodedArgument encodedArg)
   at System.Reflection.RuntimeCustomAttributeData.get_ConstructorArguments()
   at Program.<Main>$(String[] args)

Changing A's constructor to take parameter of a different type, e.g., B<int>.E instead of B<delegate*<void>[]>.E, makes the test pass. (Although I wouldn't expect the symbol validator to discover empty attribute constructor arguments, but that's probably a different issue.)

Note This issue is mentioned in code.

Author: jjonescz
Assignees: -
Labels:

bug, area-System.Reflection, untriaged

Milestone: -

@steveharter
Copy link
Member

Function pointers are not currently supported as a reflectable type othan than IntPtr; see #11354.

We are working on adding support in 8.0, although resolving a function pointer from a string may not be supported in 8.0 either as shown in the provided call stack
System.Reflection.CustomAttributeTypedArgument.ResolveType(RuntimeModule scope, String typeName).

Keeping in 8.0 for re-assessment once the core function pointer work is in.

@steveharter steveharter self-assigned this Jan 5, 2023
@steveharter steveharter removed the untriaged New issue has not been triaged by the area owner label Jan 5, 2023
@steveharter steveharter added this to the 8.0.0 milestone Jan 5, 2023
@steveharter
Copy link
Member

This issue still occurs in v8 RC1:

namespace ConsoleApp172
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var attr = typeof(C).CustomAttributes.Single(d => d.AttributeType == typeof(A));

            // throws:
            var arg = attr.ConstructorArguments.Single();

            Console.WriteLine((int)arg.Value == 0);
        }
    }

    class A : Attribute
    {
        public A(B<delegate*<void>[]>.E e) { }
        
        // This works:
        // public A(B<void*[]>.E e) { }
    }

    class B<T>
    {
        public enum E { }
    }

    [A(default)]
    class C { }
}

Moving to v9 based on priority.

@steveharter steveharter modified the milestones: 8.0.0, 9.0.0 Aug 10, 2023
@steveharter
Copy link
Member

steveharter commented Feb 23, 2024

Closing; this works in latest v9 (Preview 1+). It did not work in v8.

I don't have the fix that allowed this to work - previously, the attribute data string could not be properly deserialized: 'Could not load type 'System.Void()' from assembly 'System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'.'

The enum type is based on int so arg.Value.GetType will be int\Int32, so this LOC could be added to the test above:

Console.WriteLine(arg.Value.GetType() == typeof(int));

The actual benefit from this is very low to none. The original failing test in the PR description is "combinatorial" and I can't think of a useful real-world scenario since:

  • A function pointer type can't be used in an attribute (an array of function pointers was selected in the combinatorial test data).
  • A function pointer cannot be used as a generic parameter (by design; no C# support anyway).

@github-actions github-actions bot locked and limited conversation to collaborators Mar 25, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants