diff --git a/src/libraries/System.Runtime/tests/System.Reflection.Tests/CustomAttributeTests.cs b/src/libraries/System.Runtime/tests/System.Reflection.Tests/CustomAttributeTests.cs index 6b102884c0d2c..ce6e5ecddf560 100644 --- a/src/libraries/System.Runtime/tests/System.Reflection.Tests/CustomAttributeTests.cs +++ b/src/libraries/System.Runtime/tests/System.Reflection.Tests/CustomAttributeTests.cs @@ -129,5 +129,113 @@ public void StringAttributeValueRefEqualsStringEmpty () { Assert.Same(string.Empty, attr.NamedField); } + + [AttributeUsage(AttributeTargets.Parameter)] + internal class MyParameterAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Property)] + internal class MyPropertyAttribute : Attribute {} + + internal sealed class PropertyAsParameterInfo : ParameterInfo + { + private readonly PropertyInfo _underlyingProperty; + private readonly ParameterInfo? _constructionParameterInfo; + + public PropertyAsParameterInfo(PropertyInfo property, ParameterInfo parameterInfo) + { + _underlyingProperty = property; + _constructionParameterInfo = parameterInfo; + MemberImpl = _underlyingProperty; + } + + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + var constructorAttributes = _constructionParameterInfo?.GetCustomAttributes(attributeType, inherit); + + if (constructorAttributes == null || constructorAttributes is { Length: 0 }) + { + return _underlyingProperty.GetCustomAttributes(attributeType, inherit); + } + + var propertyAttributes = _underlyingProperty.GetCustomAttributes(attributeType, inherit); + + var mergedAttributes = new Attribute[constructorAttributes.Length + propertyAttributes.Length]; + Array.Copy(constructorAttributes, mergedAttributes, constructorAttributes.Length); + Array.Copy(propertyAttributes, 0, mergedAttributes, constructorAttributes.Length, propertyAttributes.Length); + + return mergedAttributes; + } + + public override object[] GetCustomAttributes(bool inherit) + { + var constructorAttributes = _constructionParameterInfo?.GetCustomAttributes(inherit); + + if (constructorAttributes == null || constructorAttributes is { Length: 0 }) + { + return _underlyingProperty.GetCustomAttributes(inherit); + } + + var propertyAttributes = _underlyingProperty.GetCustomAttributes(inherit); + + var mergedAttributes = new object[constructorAttributes.Length + propertyAttributes.Length]; + Array.Copy(constructorAttributes, mergedAttributes, constructorAttributes.Length); + Array.Copy(propertyAttributes, 0, mergedAttributes, constructorAttributes.Length, propertyAttributes.Length); + + return mergedAttributes; + } + + public override IList GetCustomAttributesData() + { + var attributes = new List( + _constructionParameterInfo?.GetCustomAttributesData() ?? Array.Empty()); + attributes.AddRange(_underlyingProperty.GetCustomAttributesData()); + + return attributes.AsReadOnly(); + } + } + + internal class CustomAttributeProviderTestClass + { + public CustomAttributeProviderTestClass([MyParameter] int integerProperty) + { + IntegerProperty = integerProperty; + } + + [MyProperty] + public int IntegerProperty { get; set; } + } + + [Fact] + public void CustomAttributeProvider () + { + var type = typeof(CustomAttributeProviderTestClass); + var propertyInfo = type.GetProperty(nameof(CustomAttributeProviderTestClass.IntegerProperty)); + var ctorInfo = type.GetConstructor(new Type[] { typeof(int) }); + var ctorParamInfo = ctorInfo.GetParameters()[0]; + var propertyAndParamInfo = new PropertyAsParameterInfo(propertyInfo, ctorParamInfo); + + // check GetCustomAttribute API + var cattrObjects = propertyAndParamInfo.GetCustomAttributes(true); + Assert.Equal(2, cattrObjects.Length); + Assert.Equal(typeof(MyParameterAttribute), cattrObjects[0].GetType()); + Assert.Equal(typeof(MyPropertyAttribute), cattrObjects[1].GetType()); + + cattrObjects = propertyAndParamInfo.GetCustomAttributes(typeof(Attribute), true); + Assert.Equal(2, cattrObjects.Length); + Assert.Equal(typeof(MyParameterAttribute), cattrObjects[0].GetType()); + Assert.Equal(typeof(MyPropertyAttribute), cattrObjects[1].GetType()); + + var cattrsEnumerable = propertyAndParamInfo.GetCustomAttributes(); + Attribute[] cattrs = cattrsEnumerable.Cast().ToArray(); + Assert.Equal(2, cattrs.Length); + Assert.Equal(typeof(MyParameterAttribute), cattrs[0].GetType()); + Assert.Equal(typeof(MyPropertyAttribute), cattrs[1].GetType()); + + // check GetCustomAttributeData API + var customAttributesData = propertyAndParamInfo.GetCustomAttributesData(); + Assert.Equal(2, customAttributesData.Count); + Assert.Equal(typeof(MyParameterAttribute), customAttributesData[0].AttributeType); + Assert.Equal(typeof(MyPropertyAttribute), customAttributesData[1].AttributeType); + } } } diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/CustomAttribute.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/CustomAttribute.cs index 19c814cc5c72d..a024884e065d8 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/CustomAttribute.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/CustomAttribute.cs @@ -114,13 +114,7 @@ private static bool IsUserCattrProvider(object obj) // FIXME: Callers are explicitly passing in null for attributeType, but GetCustomAttributes prohibits null attributeType arguments internal static object[] GetCustomAttributesBase(ICustomAttributeProvider obj, Type? attributeType, bool inheritedOnly) { - object[] attrs; - - if (IsUserCattrProvider(obj)) - attrs = obj.GetCustomAttributes(attributeType!, true); - else - attrs = GetCustomAttributesInternal(obj, attributeType!, false); - + object[] attrs = GetCustomAttributesInternal(obj, attributeType!, pseudoAttrs: false); // // All pseudo custom attributes are Inherited = false hence we can avoid // building attributes array which would be discarded by inherited checks @@ -156,6 +150,9 @@ internal static object[] GetCustomAttributes(ICustomAttributeProvider obj, Type && attributeType != typeof(Attribute) && attributeType != typeof(CustomAttribute) && attributeType != typeof(object)) throw new ArgumentException(SR.Argument_MustHaveAttributeBaseClass + " " + attributeType.FullName); + if (IsUserCattrProvider(obj)) + return obj.GetCustomAttributes(attributeType, inherit); + // FIXME: GetCustomAttributesBase doesn't like being passed a null attributeType if (attributeType == typeof(CustomAttribute)) attributeType = null!; @@ -306,6 +303,9 @@ internal static object[] GetCustomAttributes(ICustomAttributeProvider obj, bool { ArgumentNullException.ThrowIfNull(obj); + if (IsUserCattrProvider(obj)) + return obj.GetCustomAttributes(typeof(Attribute), inherit); + if (!inherit) return (object[])GetCustomAttributesBase(obj, null, false).Clone();