Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -1573,19 +1573,42 @@ private static void AddCustomAttributes(
}
}

RuntimePropertyInfo? property = (RuntimePropertyInfo?)(type is null ?
attributeType.GetProperty(name) :
attributeType.GetProperty(name, type, [])) ??
throw new CustomAttributeFormatException(SR.Format(SR.RFLCT_InvalidPropFail, name));
RuntimeMethodInfo setMethod = property.GetSetMethod(true)!;

// Public properties may have non-public setter methods
if (!setMethod.IsPublic)
RuntimeMethodInfo? setMethod = null;
RuntimeMethodInfo? getMethod = null;
Type baseAttributeType = attributeType;

for (; ; )
{
continue;
}
RuntimePropertyInfo? property = (RuntimePropertyInfo?)(type is null ?
baseAttributeType.GetProperty(name) :
baseAttributeType.GetProperty(name, type, []));

if (property is not null)
{
setMethod = property.GetSetMethod(true);
getMethod = property.GetGetMethod(true);

if (setMethod is not null)
{
// Public properties may have non-public setter methods
if (setMethod.IsPublic)
{
setMethod.InvokePropertySetter(attribute, BindingFlags.Default, null, value, null);
}

break;
}
}
else
{
setMethod = null;
getMethod = null;
}

setMethod.InvokePropertySetter(attribute, BindingFlags.Default, null, value, null);
baseAttributeType = baseAttributeType.BaseType is null || (getMethod is not null && !getMethod.IsVirtual)
? throw new CustomAttributeFormatException(SR.Format(SR.RFLCT_InvalidPropFail, name))
: baseAttributeType.BaseType;
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,37 @@ public static Attribute Instantiate(this CustomAttributeData cad)
else
{
// Property
MethodInfo? getMethod = null;
MethodInfo? setMethod = null;

for (; ; )
{
PropertyInfo? propertyInfo = walk.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
if (propertyInfo != null)

if (propertyInfo is not null)
{
propertyInfo.SetValue(newAttribute, argumentValue);
break;
getMethod = propertyInfo.GetGetMethod(true);
setMethod = propertyInfo.GetSetMethod(true);

if (setMethod is not null)
{
// Public properties may have non-public setter methods
if (setMethod.IsPublic)
{
propertyInfo.SetValue(newAttribute, argumentValue);
}

break;
}
}
else
{
getMethod = null;
setMethod = null;
}

Type? baseType = walk.BaseType;
if (baseType == null)
if (baseType == null || (getMethod is not null && !getMethod.IsVirtual))
throw new CustomAttributeFormatException(SR.Format(SR.RFLCT_InvalidPropFail, name));
walk = baseType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ private static bool AddDependenciesFromPropertySetter(DependencyList dependencie

MetadataReader reader = attributeTypeDefinition.MetadataReader;
var typeDefinition = reader.GetTypeDefinition(attributeTypeDefinition.Handle);
MethodDesc getterMethod = null;

foreach (PropertyDefinitionHandle propDefHandle in typeDefinition.GetProperties())
{
Expand All @@ -263,14 +264,19 @@ private static bool AddDependenciesFromPropertySetter(DependencyList dependencie
dependencies.Add(factory.ReflectedMethod(setterMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)), "Custom attribute blob");
}

if (!accessors.Getter.IsNil)
{
getterMethod = (MethodDesc)attributeTypeDefinition.EcmaModule.GetObject(accessors.Getter);
}

return true;
}
}

// Haven't found it in current type. Check the base type.
TypeDesc baseType = attributeType.BaseType;

if (baseType != null)
if (baseType != null && (getterMethod is null || getterMethod.IsVirtual))
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,15 @@ public static void GetCustomAttributesGenericAttributeHasValues()
() => Assert.Equal(4, attribute.OptionalValue));
}

[Fact]
public static void GetCustomAttributesWithSettersDefinedOnBaseClass()
{
object[] attributes = typeof(ClassWithDerivedAttr).GetCustomAttributes(true);
object attribute = Assert.Single(attributes);
var derivedAttributeWithGetterAttr = Assert.IsType<DerivedAttributeWithGetter>(attribute);
Assert.Equal(2, derivedAttributeWithGetterAttr.P);
}

private static void GenericAttributesTestHelper<TGenericParameter>(Func<Type, Attribute[]> getCustomAttributes)
{
Attribute[] openGenericAttributes = getCustomAttributes(typeof(GenericAttribute<>));
Expand All @@ -326,6 +335,32 @@ private static void GenericAttributesTestHelper<TGenericParameter>(Func<Type, At
() => Assert.NotEmpty(closedGenericAttributes),
() => Assert.All(closedGenericAttributes, a => Assert.IsType<GenericAttribute<TGenericParameter>>(a)));
}

public class BaseAttributeWithGetterSetter : Attribute
{
protected int _p;

public virtual int P
{
get => _p;
set
{
_p = value;
}
}
}

public class DerivedAttributeWithGetter : BaseAttributeWithGetterSetter
{
public override int P
{
get => _p;
}
}

[DerivedAttributeWithGetter(P = 2)]
public class ClassWithDerivedAttr
{ }
}

public static class GetCustomAttribute
Expand Down
36 changes: 31 additions & 5 deletions src/mono/mono/metadata/custom-attrs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1245,21 +1245,47 @@ create_custom_attr (MonoImage *image, MonoMethod *method, const guchar *data, gu

mono_field_set_value_internal (MONO_HANDLE_RAW (attr), field, val); // FIXMEcoop
} else if (named_type == CATTR_TYPE_PROPERTY) {
MonoProperty *prop;
prop = mono_class_get_property_from_name_internal (mono_handle_class (attr), name);
if (!prop) {
MonoProperty *prop = NULL;
MonoProperty *firstNotNullPropInHierarchy = NULL;
MonoMethod *setMethod = NULL;
MonoMethod *getMethod = NULL;
MonoClass *attrBaseClass = mono_handle_class (attr);
while (!setMethod && attrBaseClass && (!prop || !getMethod || m_method_is_virtual(getMethod)))
{
prop = mono_class_get_property_from_name_internal (attrBaseClass, name);

if (prop)
{
if (!firstNotNullPropInHierarchy)
{
firstNotNullPropInHierarchy = prop;
}

setMethod = prop->set;
getMethod = prop->get;
}
else
{
setMethod = NULL;
getMethod = NULL;
}

attrBaseClass = m_class_get_parent(attrBaseClass);
}

if (!firstNotNullPropInHierarchy) {
mono_error_set_generic_error (error, "System.Reflection", "CustomAttributeFormatException", "Could not find a property with name %s", name);
goto fail;
}

if (!prop->set) {
if (!setMethod) {
mono_error_set_generic_error (error, "System.Reflection", "CustomAttributeFormatException", "Could not find the setter for %s", name);
goto fail;
}

/* can we have more that 1 arg in a custom attr named property? */
prop_type = prop->get? mono_method_signature_internal (prop->get)->ret :
mono_method_signature_internal (prop->set)->params [mono_method_signature_internal (prop->set)->param_count - 1];
mono_method_signature_internal (setMethod)->params [mono_method_signature_internal (setMethod)->param_count - 1];

MonoObject *param_obj;
pparams [0] = load_cattr_value (image, prop_type, &param_obj, named, data_end, &named, error);
Expand Down
37 changes: 30 additions & 7 deletions src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1317,16 +1317,39 @@ protected void MarkCustomAttributeProperties(ICustomAttribute ca, TypeDefinition

protected void MarkCustomAttributeProperty(CustomAttributeNamedArgument namedArgument, TypeDefinition attribute, ICustomAttribute ca, in DependencyInfo reason, MessageOrigin origin)
{
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;
}

MethodDefinition? setMethod = null;
MethodDefinition? getMethod = null;
PropertyDefinition? property = null;

MarkCustomAttributeArgument(namedArgument.Argument, ca, origin);
while (setMethod is null && type is not null && (property is null || getMethod is null || getMethod.IsVirtual))
{
property = type.Properties.FirstOrDefault(p => p.Name == namedArgument.Name);

if (property is not null)
{
setMethod = property.SetMethod;
getMethod = property.GetMethod;
}
else
{
setMethod = null;
getMethod = null;
}

type = Context.TryResolve(type.BaseType);
}

if (property != null && Annotations.FlowAnnotations.RequiresDataFlowAnalysis(property.SetMethod))
if (setMethod is not null)
{
var scanner = new AttributeDataFlow(Context, this, origin);
scanner.ProcessAttributeDataflow(property.SetMethod, new List<CustomAttributeArgument> { namedArgument.Argument });
MarkMethod(setMethod, reason, origin);
MarkCustomAttributeArgument(namedArgument.Argument, ca, origin);

if (Annotations.FlowAnnotations.RequiresDataFlowAnalysis(setMethod))
{
var scanner = new AttributeDataFlow(Context, this, origin);
scanner.ProcessAttributeDataflow(setMethod, new List<CustomAttributeArgument> { namedArgument.Argument });
}
}
}

Expand Down