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

Open
#110,412 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
45/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
csharp
Domain
backend

Research direction

Start with the minimal reproduction and inspect src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs, especially AddCustomAttributes around the cited line. Trace how overridden attribute properties and inherited setters are resolved, then add regression coverage showing that GetCustomAttributes(inherit: true) handles the getter-only override without throwing.

Written by the indexing model from the issue text.

Description

area-System.Reflection bug help wanted in-pr
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.

Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/runtime

All issues in dotnet/runtime

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.