Skip to content

Conversation

pedrobsaila
Copy link
Contributor

@pedrobsaila pedrobsaila commented Jul 13, 2025

Fixes #110412

Sorry for the delay, I've been busy these last weeks

…l of derived attribute with overridden property getter
@@ -107,7 +107,7 @@ public static Attribute Instantiate(this CustomAttributeData cad)
for (; ; )
{
PropertyInfo? propertyInfo = walk.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
if (propertyInfo != null)
if (propertyInfo?.SetMethod is not null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this logic equivalent to the others, or just equivalent enough to pass the test? What corner cases are we missing in the test that necessitate the more complex logic elsewhere?

Copy link
Contributor Author

@pedrobsaila pedrobsaila Jul 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was going to be off for a few days so I did rapidely just enough to pass the test and not let the PR hanging, I fixed it now. The missing condition stops the lookup in base classes early if someone used the new keyword to hide the property instead of overriding it.

@pedrobsaila pedrobsaila requested a review from marek-safar as a code owner July 20, 2025 14:41
@dotnet-policy-service dotnet-policy-service bot added the linkable-framework Issues associated with delivering a linker friendly framework label Jul 20, 2025
@jeffhandley
Copy link
Member

@AaronRobinsonMSFT Can you review this when time permits please?

PropertyDefinition? property = GetProperty(attribute, namedArgument.Name);
if (property != null)
MarkMethod(property.SetMethod, reason, origin);
TypeDefinition? type = attribute;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implements this for PublishTrimmed; to implement for PublishAot, the new semantic needs to be implemented here too:

private static bool AddDependenciesFromPropertySetter(DependencyList dependencies, NodeFactory factory, TypeDesc attributeType, string propertyName)
{
EcmaType attributeTypeDefinition = (EcmaType)attributeType.GetTypeDefinition();
MetadataReader reader = attributeTypeDefinition.MetadataReader;
var typeDefinition = reader.GetTypeDefinition(attributeTypeDefinition.Handle);
foreach (PropertyDefinitionHandle propDefHandle in typeDefinition.GetProperties())
{
PropertyDefinition propDef = reader.GetPropertyDefinition(propDefHandle);
if (reader.StringComparer.Equals(propDef.Name, propertyName))
{
PropertyAccessors accessors = propDef.GetAccessors();
if (!accessors.Setter.IsNil)
{
MethodDesc setterMethod = (MethodDesc)attributeTypeDefinition.EcmaModule.GetObject(accessors.Setter);
if (factory.MetadataManager.IsReflectionBlocked(setterMethod))
return false;
// Method on a generic attribute
if (attributeType != attributeTypeDefinition)
{
setterMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(setterMethod, (InstantiatedType)attributeType);
}
dependencies.Add(factory.ReflectedMethod(setterMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Custom attribute blob");
}
return true;
}
}
// Haven't found it in current type. Check the base type.
TypeDesc baseType = attributeType.BaseType;
if (baseType != null)
return AddDependenciesFromPropertySetter(dependencies, factory, baseType, propertyName);
// Not found. This is bad metadata that will result in a runtime failure, but we shouldn't fail the compilation.
return true;
}

@AaronRobinsonMSFT AaronRobinsonMSFT added the needs-author-action An issue or pull request that requires more info or actions from the author. label Sep 2, 2025
@dotnet-policy-service dotnet-policy-service bot removed the needs-author-action An issue or pull request that requires more info or actions from the author. label Sep 5, 2025
Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

@MichalStrehovsky Any further feedback?

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky Any further feedback?

If we really want this, it looks reasonable. For posterity though, this is implementing some of C# language semantics into the runtime reflection stack (the runtime reflection stack doesn't consider property inheritance in general, like I pointed out in #110945 (comment), or how Type.InvokeMember(BindingFlags.SetProperty) works).

IL is more expressible than what C# allows. C# can sometimes get into IL-level corner cases due to NuGet versioning and compile-time seeing different things than runtime.

What is implemented here will allow things like:

[Derived(Prop = 123)]
class Program;

class BaseAttribute : Attribute
{
    public virtual int Prop { get; set; }
}

class DerivedAttribute : BaseAttribute
{
    // Note `new` keyword
    public new virtual int Prop { get => base.Prop; }
}

to work even though the C# compiler would not accept this.

Hopefully this is sufficiently leafy within the reflection stack that it won't affect many things. I'm not worried about this breaking existing apps (because we're just fixing a throw to not throw). However there's is an aspect that if someone does take advantage of this not throwing, they may run into new issues that we didn't consider.

Like I wrote in #110945 (comment) this needed a bit more design discussion to consider these things before we decide whether we really want to fix this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Reflection community-contribution Indicates that the PR has been added by a community member linkable-framework Issues associated with delivering a linker friendly framework
Projects
None yet
Development

Successfully merging this pull request may close these issues.

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