dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.48k stars 4.77k forks source link

System.Reflection.CustomAttributeFormatException thrown on a retrieval of derived attribute with overridden property getter #110412

Open SENya1990 opened 8 hours ago

SENya1990 commented 8 hours ago

Description

Hi .Net team,

I have recently encountered a strange behavior of the derived attributes which looks like a bug to me. On an attempt to retrieve a derived attribute that overrides just a property getter of the base attribute the system throws the System.Reflection.CustomAttributeFormatException exception (see details in the Actual Behavior section):

I didn't manage to find a similar bug, and the same time there is no compiler error about it. At the same time this looks like a bug to me. I provided more reasons in the expected behavior.

If there already exists such bug, or this is not a bug but a designed behavior, then I ask sorry for the disturbance in advance. If this is not a proper place to report this issue, please direct me to a proper one.

Reproduction Steps

Here is the minimal code snippet:

using System;

namespace Test
{
    public class BaseAttribute : Attribute
    {
        public virtual int P
                {
                 get;
                 set;
                }
    }

    public class DerivedAttribute : BaseAttribute
    {
        public override int P
        {
            get => 1;
        }
    }

    [Derived(P = 2)]
    public class Foo
    { }

    public class Program
    {
        static void Main(string[] args)
        {
            var attr = typeof(Foo).GetCustomAttributes(inherit: true);   // this line throws the exception

            Console.WriteLine(attr.Length);
            Console.ReadLine();
        }
    }
}

Notice, that if I add an empty setter to the overridden property, then the code works as expected without an error:

public class DerivedAttribute : BaseAttribute
{
    public override int P
    {
        get => 1;
                set { }
    }
}

Expected behavior

The code works without throwing an error.

At first I thought that this is a problem of the C# compiler, that it doesn't display an error for this case. But for C# compiler it is perfectly fine to have only one property accessor overridden. This is discussed in this SO question: https://stackoverflow.com/questions/64595478/why-is-there-no-compiler-error-when-setting-an-overridden-property-that-is-read

Moreover, the following code works fine:

using System;

namespace Test
{
    public class BaseAttribute : Attribute
    {
        public virtual int P
        {
            get;
            set;
        }
    }
    public class DerivedAttribute : BaseAttribute
    {
        public override int P
        {
            get => 1;
        }
    }

    [Derived(P = 2)]
    public class Foo
    { }

    public class Program
    {
        static void Main(string[] args)
        {
            var attr = new DerivedAttribute();
            attr.P = 1;

            Console.WriteLine(attr.P);
            Console.ReadLine();
        }
    }
}

So, my guess is that something is wrong with attributes.

Actual behavior

The line var attr = typeof(Foo).GetCustomAttributes(inherit: true); throws the following exception:

System.Reflection.CustomAttributeFormatException
  HResult=0x80131605
  Message='P' property specified was not found.
  Source=System.Private.CoreLib
  StackTrace:
   at System.Reflection.CustomAttribute.AddCustomAttributes(ListBuilder`1& attributes, RuntimeModule decoratedModule, Int32 decoratedMetadataToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, ListBuilder`1 derivedAttributes) in /_/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs:line 1272

  This exception was originally thrown at this call stack:
    System.Reflection.CustomAttribute.AddCustomAttributes(ref System.RuntimeType.ListBuilder<object>, System.Reflection.RuntimeModule, int, System.RuntimeType, bool, System.RuntimeType.ListBuilder<object>) in RuntimeCustomAttributeData.cs

Inner Exception 1:
NullReferenceException: Object reference not set to an instance of an object.

Regression?

On my machine this issue reproduces both for .Net Framework 4.8.1 and .Net 8.

Known Workarounds

Just add an empty property setter to the overridden property.

Configuration

Configuration: .Net Framework 4.8.1 or .Net 8.

OS: Windows 10 Pro 64 bit, x64-based processor, Version 22H2, OS build 19045.5131 Architecture: AnyCpu

I don't know for sure, if this is specific to this configuration, but it doesn't look like this due to NRE thrown in the .Net code which can be debugged. This isn't related to Blazor, just a simple console app.

Other information

Simple debug shows that the real error is an NRE thrown in the System.Reflection.CustomAttribute type in the AddCustomAttributes method in the following line: https://github.com/dotnet/runtime/blob/11031c4171b5a46eedcae777fe805e7d29a94411/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs#L1583

The NRE is caused because setMethod is not checked for null before the access to its IsPublic property. My guess is that the previous line RuntimeMethodInfo setMethod = property.GetSetMethod(true)!; obtains the property setter method only from the current type.

I believe, that a fix that just checks the setMethod for null and skips the property wouldn't make a good fix. In my example, the property has a public setter in a base type. Nothing prevents the property value from being set when the attribute is applied. In my opinion, the proper fix should support my scenario by making a lookup for the setter in the type hierarchy of the type containing the property.

dotnet-policy-service[bot] commented 8 hours ago

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