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

No way to extract CustomAttribute from interfaces in SR.Metadata #30570

Closed
glennawatson opened this issue Aug 11, 2019 · 17 comments
Closed

No way to extract CustomAttribute from interfaces in SR.Metadata #30570

glennawatson opened this issue Aug 11, 2019 · 17 comments

Comments

@glennawatson
Copy link
Contributor

Hi,

I am making a public api approval tool for my application. I am adding support for C# 8 nullable contexts. I have one issue remaining which seems to be having bugs with SR.Metadata

I have the following class in my test suite:

 public class DerivedNullableInterface2 : IEnumerable<IEnumerable<string?>>
    {
        public IEnumerator<IEnumerable<string?>> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

It generates the following IL for the NullableAttribute:

.class auto ansi nested public beforefieldinit DerivedNullableInterface2
       extends [netstandard]System.Object
       implements class [netstandard]System.Collections.Generic.IEnumerable`1<class [netstandard]System.Collections.Generic.IEnumerable`1<string>>,
                  [netstandard]System.Collections.IEnumerable
{
  .interfaceimpl type class [netstandard]System.Collections.Generic.IEnumerable`1<class [netstandard]System.Collections.Generic.IEnumerable`1<string>>
  .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 03 00 00 00 00 00 02 00 00 ) 
} // end of class DerivedNullableInterface2

I would assume the .custom after the `interfaceimpl would be available in the InterfaceImplementation custom attributes.

However, although I can see the NullableAttribute generated in the MetadataReader main list of CustomAttributes I don't see it associated with the interface.

I made my own test code against the SR.Metadata to find the parent of the attribute (which in my example has row number 1).

public void GetParentTest(MetadataReader reader)
{
             var interestedAttribute = reader.CustomAttributes.First();
            var handle = reader.CustomAttributeTable.GetParent(interestedAttribute);           
}

The handle returned is a Parameter (despite being the valid .custom in the above generated IL), with a row number also of 1.

@jnm2
Copy link
Contributor

jnm2 commented Aug 11, 2019

/cc @tmat

@nguerrera
Copy link
Contributor

nguerrera commented Aug 11, 2019

There are two NullableAttribute applications. One is applied to return Parameter of GetEnumerator method and another is applied to InterfaceImplementation of IEnumerable<IEnumerable<IEnumerable<string>> .

reader.CustomAttributes.First()could be any attribute, why do you expect it to be a particular one?

using var peReader = new PEReader(File.OpenRead(Assembly.GetExecutingAssembly().Location));
var mdReader = peReader.GetMetadataReader();

foreach (var attributeHandle in mdReader.CustomAttributes)
{
    var attribute = mdReader.GetCustomAttribute(attributeHandle);
    var ctorHandle = attribute.Constructor;

    EntityHandle attributeTypeHandle = ctorHandle.Kind switch
    {
        HandleKind.MethodDefinition => mdReader.GetMethodDefinition((MethodDefinitionHandle)ctorHandle).GetDeclaringType(),
        HandleKind.MemberReference => mdReader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent,
        _ => throw new InvalidOperationException(),
    };

    StringHandle attributeTypeNameHandle = attributeTypeHandle.Kind switch
    {
        HandleKind.TypeDefinition => mdReader.GetTypeDefinition((TypeDefinitionHandle)attributeTypeHandle).Name,
        HandleKind.TypeReference => mdReader.GetTypeReference((TypeReferenceHandle)attributeTypeHandle).Name,
        _ => throw new InvalidOperationException(),
    };

    if (mdReader.StringComparer.Equals(attributeTypeNameHandle, "NullableAttribute"))
    {
        Console.WriteLine(attribute.Parent.Kind);
    }
}
Parameter
InterfaceImplementation

@glennawatson
Copy link
Contributor Author

That's solely because it's the one matching the .custom implementation above rather than a fixed test..

The point is I'm not getting inside the InterfaceImplementation any attributes coming through with the above sample.

@glennawatson
Copy link
Contributor Author

glennawatson commented Aug 11, 2019

so I am unable to get the IEnumerable<IEnumerable<IEnumerable<string?>> nullability.

@nguerrera
Copy link
Contributor

See my sample. I don't think it is correct that the first attribute is the one you are looking for. The one applied to a Parameter is applied to the return value of GetEnumerator.

From ILSpy decompilation:

	[return: System.Runtime.CompilerServices.Nullable(new byte[]
	{
		1,
		1,
		2
	})]
	public IEnumerator<IEnumerable<string>> GetEnumerator()

From IL:

  .method public hidebysig newslot virtual final 
          instance class [System.Runtime]System.Collections.Generic.IEnumerator`1<class [System.Runtime]System.Collections.Generic.IEnumerable`1<string>> 
          GetEnumerator() cil managed
  {
    .param [0]
    .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 03 00 00 00 01 01 02 00 00 ) 

I can find the one on the InterfaceImplementation, it's just a different attribute application.

@glennawatson
Copy link
Contributor Author

glennawatson commented Aug 11, 2019

Yes,

My original code was

Get interface implementations for type DerivedInterface2, Get custom attributes for InterfaceImplementation, InterfaceImplementation CustomAttribute is empty.

The first item is a red hearing and not related to this item.

I solely did it for @jnm2 suggestion on gitter.

@glennawatson
Copy link
Contributor Author

to be honest @nguerrera I have found this interaction extremely rude. Given that @terrajobst has been attempting to help me with the issue and @jnm2 without much success.

@nguerrera
Copy link
Contributor

nguerrera commented Aug 11, 2019

I apologize that this sounded rude. I am trying to help here too. I didn't have the prior context, and I took time out of my weekend to investigate here with the sample above.

@nguerrera
Copy link
Contributor

I'm digging further based on your updated information.

@glennawatson
Copy link
Contributor Author

yeah sorry about that. Just iteration upon iteration so the code has gotten away from my initial problem.

https://github.com/glennawatson/MetadataPublicApiGenerator/blob/4d24e8dcbc305aeeb378d55ac0f445d88fc1a9b4/src/MetadataPublicApiGenerator/Extensions/TypeExtensions.cs#L52

So that's my attempt so far, there is a bit of a wrapper around the InterfaceImplementation here

https://github.com/glennawatson/MetadataPublicApiGenerator/blob/master/src/LightweightMetadata/TypeWrappers/InterfaceImplementationWrapper.cs#L29

So that interface implementation is correctly the IEnumerable<....> but the custom attributes doesn't seem to be producing anything in the interface implementation

@nguerrera
Copy link
Contributor

nguerrera commented Aug 11, 2019

Get interface implementations for type DerivedInterface2, Get custom attributes for InterfaceImplementation, InterfaceImplementation CustomAttribute is empty.

This was my attempt to repro that path:

using var peReader = new PEReader(File.OpenRead(Assembly.GetExecutingAssembly().Location));
var mdReader = peReader.GetMetadataReader();

foreach (var typeDefinitionHandle in mdReader.TypeDefinitions)
{
    var typeDefinition = mdReader.GetTypeDefinition(typeDefinitionHandle);
            
    foreach (var interfaceImplementationHandle in typeDefinition.GetInterfaceImplementations())
    {
        var interfaceImplementation = mdReader.GetInterfaceImplementation(interfaceImplementationHandle);

        foreach (var attribute in interfaceImplementation.GetCustomAttributes())
        {
            var typeName = mdReader.GetString(typeDefinition.Name);

            Console.WriteLine($"Found attribute on InterfaceImplementation by {typeName}");
        }
    }
}

But it worked for me. Above prints:

Found attribute on InterfaceImplementation by DerivedNullableInterface2

I will look at the code links above next.

@nguerrera
Copy link
Contributor

I can't spot an obvious issue in the linked code. Are you able to reduce what you are seeing into steps that I can run? I can debug it from there.

@nguerrera nguerrera reopened this Aug 11, 2019
@nguerrera nguerrera self-assigned this Aug 11, 2019
@nguerrera
Copy link
Contributor

There are two InterfaceImplementations, one of IEnumerable<IEnumerable<string>> and one of non-generic IEnumerable. Is it possible you are looking at the second one?

@nguerrera
Copy link
Contributor

The point is I'm not getting inside the InterfaceImplementation any attributes coming through with the above sample.

Sorry, I missed this before. Are you saying that when you run my code snippets above, you don't get the same results?

What is dotnet --info in that case? I am currently using 3.0 preview 7 on my home machine.

@glennawatson
Copy link
Contributor Author

sorry, preview6, after upgrading I am now getting more consistent results with what you have above.

@glennawatson
Copy link
Contributor Author

Thanks for your help regardless, I appreciate you helping on your day off

@nguerrera
Copy link
Contributor

No problem, glad it worked out. :)

@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@msftgits msftgits added this to the 5.0 milestone Feb 1, 2020
@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 12, 2020
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

4 participants