From 139203dc1903d9a19993f649fff0103f22207ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 15 Sep 2026 16:31:27 +0900 Subject: [PATCH 1/4] Unify custom attribute list inputs across runtimes Introduce runtime-specific QCustomAttributeList wrappers and remove repeated NATIVEAOT conditionals from shared custom attribute callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../RuntimeCustomAttributeData.CoreCLR.cs | 50 ++++- .../RuntimeCustomAttributeData.NativeAot.cs | 54 ++++- .../Reflection/RuntimeCustomAttributeData.cs | 199 +++--------------- 3 files changed, 123 insertions(+), 180 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs index 294653800662b3..8891dad7e2ddc9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs @@ -11,12 +11,46 @@ namespace System.Reflection { + internal readonly struct QCustomAttributeList(RuntimeModule module, int metadataToken) + { + public RuntimeModule Module { get; } = module; + public int MetadataToken { get; } = metadataToken; + + public QCustomAttributeList(RuntimeType target) + : this(target.GetRuntimeModule(), target.MetadataToken) { } + + public QCustomAttributeList(RuntimeFieldInfo target) + : this(target.GetRuntimeModule(), target.MetadataToken) { } + + public QCustomAttributeList(RuntimeMethodInfo target) + : this(target.GetRuntimeModule(), target.MetadataToken) { } + + public QCustomAttributeList(RuntimeConstructorInfo target) + : this(target.GetRuntimeModule(), target.MetadataToken) { } + + public QCustomAttributeList(RuntimeEventInfo target) + : this(target.GetRuntimeModule(), target.MetadataToken) { } + + public QCustomAttributeList(RuntimePropertyInfo target) + : this(target.GetRuntimeModule(), target.MetadataToken) { } + + public QCustomAttributeList(RuntimeModule target) + : this(target, target.MetadataToken) { } + + public QCustomAttributeList(RuntimeAssembly target) + : this((RuntimeModule)target.ManifestModule, RuntimeAssembly.GetToken(target)) { } + + public QCustomAttributeList(RuntimeParameterInfo target) + : this(target.GetRuntimeModule()!, target.MetadataToken) { } + } + internal sealed partial class RuntimeCustomAttributeData : CustomAttributeData { #region Private Static Methods - private static IList GetCustomAttributes(RuntimeModule module, int tkTarget) + private static IList GetCustomAttributes(QCustomAttributeList attributeList) { - CustomAttributeRecord[] records = GetCustomAttributeRecords(module, tkTarget); + RuntimeModule module = attributeList.Module; + CustomAttributeRecord[] records = GetCustomAttributeRecords(module, attributeList.MetadataToken); if (records.Length == 0) { return Array.Empty(); @@ -340,18 +374,20 @@ internal static unsafe partial class RuntimeCustomAttribute { internal static bool IsAttributeDefined(RuntimeModule decoratedModule, int decoratedMetadataToken, int attributeCtorToken) { - return IsCustomAttributeDefined(decoratedModule, decoratedMetadataToken, null, attributeCtorToken, false); + return IsCustomAttributeDefined(new QCustomAttributeList(decoratedModule, decoratedMetadataToken), null, attributeCtorToken: attributeCtorToken); } internal static bool IsCustomAttributeDefined( RuntimeModule decoratedModule, int decoratedMetadataToken, RuntimeType? attributeFilterType) { - return IsCustomAttributeDefined(decoratedModule, decoratedMetadataToken, attributeFilterType, 0, false); + return IsCustomAttributeDefined(new QCustomAttributeList(decoratedModule, decoratedMetadataToken), attributeFilterType); } private static bool IsCustomAttributeDefined( - RuntimeModule decoratedModule, int decoratedMetadataToken, RuntimeType? attributeFilterType, int attributeCtorToken, bool mustBeInheritable) + QCustomAttributeList customAttributes, RuntimeType? attributeFilterType, bool mustBeInheritable = false, int attributeCtorToken = 0) { + RuntimeModule decoratedModule = customAttributes.Module; + int decoratedMetadataToken = customAttributes.MetadataToken; MetadataImport scope = decoratedModule.MetadataImport; scope.EnumCustomAttributes(decoratedMetadataToken, out MetadataEnumResult attributeTokens); @@ -408,11 +444,13 @@ private static bool IsCustomAttributeDefined( "As such the reflection usage in this method will never fail as those methods/fields will be present.")] private static void AddCustomAttributes( ref ListBuilder attributes, - RuntimeModule decoratedModule, int decoratedMetadataToken, + QCustomAttributeList customAttributes, RuntimeType? attributeFilterType, bool mustBeInheritable, // The derivedAttributes list must be passed by value so that it is not modified with the discovered attributes ListBuilder derivedAttributes) { + RuntimeModule decoratedModule = customAttributes.Module; + int decoratedMetadataToken = customAttributes.MetadataToken; CustomAttributeRecord[] car = RuntimeCustomAttributeData.GetCustomAttributeRecords(decoratedModule, decoratedMetadataToken); if (attributeFilterType is null && car.Length == 0) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs index 89ce6d47c4dcfb..0ddcddd80c669e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs @@ -7,10 +7,14 @@ using System.Reflection; using System.Runtime.InteropServices; +using System.Reflection.Runtime.EventInfos; using System.Reflection.Runtime.FieldInfos; using System.Reflection.Runtime.General; using System.Reflection.Runtime.MethodInfos; using System.Reflection.Runtime.MethodInfos.NativeFormat; +using System.Reflection.Runtime.Modules; +using System.Reflection.Runtime.ParameterInfos; +using System.Reflection.Runtime.PropertyInfos; using System.Reflection.Runtime.TypeInfos; using System.Reflection.Runtime.TypeInfos.NativeFormat; @@ -18,11 +22,45 @@ namespace System.Reflection { + internal readonly struct QCustomAttributeList(MetadataReader? reader, CustomAttributeHandleCollection handles) + { + public MetadataReader? Reader { get; } = reader; + public CustomAttributeHandleCollection Handles { get; } = handles; + + public QCustomAttributeList(RuntimeType target) + : this(target.GetMetadataReader(), target.GetCustomAttributeHandles()) { } + + public QCustomAttributeList(RuntimeFieldInfo target) + : this(target.GetMetadataReader(), target.GetCustomAttributeHandles()) { } + + public QCustomAttributeList(RuntimeMethodInfo target) + : this(target.GetMetadataReader(), target.GetCustomAttributeHandles()) { } + + public QCustomAttributeList(RuntimeConstructorInfo target) + : this(target.GetMetadataReader(), target.GetCustomAttributeHandles()) { } + + public QCustomAttributeList(RuntimeEventInfo target) + : this(target.GetMetadataReader(), target.GetCustomAttributeHandles()) { } + + public QCustomAttributeList(RuntimePropertyInfo target) + : this(target.GetMetadataReader(), target.GetCustomAttributeHandles()) { } + + public QCustomAttributeList(RuntimeModule target) + : this(target.GetMetadataReader(), target.GetCustomAttributeHandles()) { } + + public QCustomAttributeList(RuntimeAssembly target) + : this(target.GetMetadataReader(), target.GetCustomAttributeHandles()) { } + + public QCustomAttributeList(RuntimeParameterInfo target) + : this(target.GetMetadataReader(), target.GetCustomAttributeHandles()) { } + } + internal sealed partial class RuntimeCustomAttributeData : CustomAttributeData { - internal static IList GetCustomAttributes( - MetadataReader? reader, CustomAttributeHandleCollection customAttributeHandles) + private static IList GetCustomAttributes(QCustomAttributeList attributeList) { + MetadataReader? reader = attributeList.Reader; + CustomAttributeHandleCollection customAttributeHandles = attributeList.Handles; if (reader is null || customAttributeHandles.Count == 0) return Array.Empty(); @@ -506,16 +544,16 @@ private static CustomAttributeType CreateArrayType(CustomAttributeEncoding eleme internal static partial class RuntimeCustomAttribute { private static bool IsCustomAttributeDefined( - MetadataReader? reader, - CustomAttributeHandleCollection customAttributeHandles, + QCustomAttributeList customAttributes, RuntimeType attributeFilterType, bool mustBeInheritable = false) { + MetadataReader? reader = customAttributes.Reader; if (reader is null) return false; ListBuilder derivedAttributes = default; - foreach (CustomAttributeHandle customAttributeHandle in customAttributeHandles) + foreach (CustomAttributeHandle customAttributeHandle in customAttributes.Handles) { CustomAttribute customAttribute = customAttributeHandle.GetCustomAttribute(reader); if (FilterCustomAttributeRecord( @@ -558,16 +596,16 @@ private static bool FilterCustomAttributeRecord( "attribute instantiation which is present in the code linker has analyzed.")] private static void AddCustomAttributes( ref ListBuilder attributes, - MetadataReader? reader, - CustomAttributeHandleCollection customAttributeHandles, + QCustomAttributeList customAttributes, RuntimeType? attributeFilterType, bool mustBeInheritable, ListBuilder derivedAttributes) { + MetadataReader? reader = customAttributes.Reader; if (reader is null) return; - foreach (CustomAttributeHandle customAttributeHandle in customAttributeHandles) + foreach (CustomAttributeHandle customAttributeHandle in customAttributes.Handles) { CustomAttribute customAttribute = customAttributeHandle.GetCustomAttribute(reader); diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs index 6840604bdd2e5d..e8cfc54d5c846d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs @@ -34,11 +34,7 @@ internal static IList GetCustomAttributesInternal(RuntimeTy { Debug.Assert(target is not null); -#if NATIVEAOT - IList cad = GetCustomAttributes(target.GetMetadataReader(), target.GetCustomAttributeHandles()); -#else - IList cad = GetCustomAttributes(target.GetRuntimeModule(), target.MetadataToken); -#endif + IList cad = GetCustomAttributes(new QCustomAttributeList(target)); ListBuilder pcas = default; PseudoCustomAttribute.GetCustomAttributes(target, (RuntimeType)typeof(object), ref pcas); return pcas.Count > 0 ? GetCombinedList(cad, ref pcas) : cad; @@ -48,11 +44,7 @@ internal static IList GetCustomAttributesInternal(RuntimeFi { Debug.Assert(target is not null); -#if NATIVEAOT - IList cad = GetCustomAttributes(target.GetMetadataReader(), target.GetCustomAttributeHandles()); -#else - IList cad = GetCustomAttributes(target.GetRuntimeModule(), target.MetadataToken); -#endif + IList cad = GetCustomAttributes(new QCustomAttributeList(target)); ListBuilder pcas = default; PseudoCustomAttribute.GetCustomAttributes(target, (RuntimeType)typeof(object), ref pcas); return pcas.Count > 0 ? GetCombinedList(cad, ref pcas) : cad; @@ -62,11 +54,7 @@ internal static IList GetCustomAttributesInternal(RuntimeMe { Debug.Assert(target is not null); -#if NATIVEAOT - IList cad = GetCustomAttributes(target.GetMetadataReader(), target.GetCustomAttributeHandles()); -#else - IList cad = GetCustomAttributes(target.GetRuntimeModule(), target.MetadataToken); -#endif + IList cad = GetCustomAttributes(new QCustomAttributeList(target)); ListBuilder pcas = default; PseudoCustomAttribute.GetCustomAttributes(target, (RuntimeType)typeof(object), ref pcas); return pcas.Count > 0 ? GetCombinedList(cad, ref pcas) : cad; @@ -76,33 +64,21 @@ internal static IList GetCustomAttributesInternal(RuntimeCo { Debug.Assert(target is not null); -#if NATIVEAOT - return GetCustomAttributes(target.GetMetadataReader(), target.GetCustomAttributeHandles()); -#else - return GetCustomAttributes(target.GetRuntimeModule(), target.MetadataToken); -#endif + return GetCustomAttributes(new QCustomAttributeList(target)); } internal static IList GetCustomAttributesInternal(RuntimeEventInfo target) { Debug.Assert(target is not null); -#if NATIVEAOT - return GetCustomAttributes(target.GetMetadataReader(), target.GetCustomAttributeHandles()); -#else - return GetCustomAttributes(target.GetRuntimeModule(), target.MetadataToken); -#endif + return GetCustomAttributes(new QCustomAttributeList(target)); } internal static IList GetCustomAttributesInternal(RuntimePropertyInfo target) { Debug.Assert(target is not null); -#if NATIVEAOT - return GetCustomAttributes(target.GetMetadataReader(), target.GetCustomAttributeHandles()); -#else - return GetCustomAttributes(target.GetRuntimeModule(), target.MetadataToken); -#endif + return GetCustomAttributes(new QCustomAttributeList(target)); } internal static IList GetCustomAttributesInternal(RuntimeModule target) @@ -112,11 +88,7 @@ internal static IList GetCustomAttributesInternal(RuntimeMo if (target.IsResource()) return new List(); -#if NATIVEAOT - return GetCustomAttributes(target.GetMetadataReader(), target.GetCustomAttributeHandles()); -#else - return GetCustomAttributes(target, target.MetadataToken); -#endif + return GetCustomAttributes(new QCustomAttributeList(target)); } internal static IList GetCustomAttributesInternal(RuntimeAssembly target) @@ -125,11 +97,7 @@ internal static IList GetCustomAttributesInternal(RuntimeAs // No pseudo attributes for RuntimeAssembly -#if NATIVEAOT - return GetCustomAttributes(target.GetMetadataReader(), target.GetCustomAttributeHandles()); -#else - return GetCustomAttributes((RuntimeModule)target.ManifestModule, RuntimeAssembly.GetToken(target)); -#endif + return GetCustomAttributes(new QCustomAttributeList(target)); } internal static IList GetCustomAttributesInternal(RuntimeParameterInfo target) @@ -137,11 +105,7 @@ internal static IList GetCustomAttributesInternal(RuntimePa Debug.Assert(target is not null); ListBuilder pcas = default; -#if NATIVEAOT - IList cad = GetCustomAttributes(target.GetMetadataReader(), target.GetCustomAttributeHandles()); -#else - IList cad = GetCustomAttributes(target.GetRuntimeModule()!, target.MetadataToken); -#endif + IList cad = GetCustomAttributes(new QCustomAttributeList(target)); PseudoCustomAttribute.GetCustomAttributes(target, (RuntimeType)typeof(object), ref pcas); return pcas.Count > 0 ? GetCombinedList(cad, ref pcas) : cad; } @@ -925,11 +889,7 @@ internal static bool IsDefined(RuntimeType type, RuntimeType? caType, bool inher if (PseudoCustomAttribute.IsDefined(type, caType)) return true; -#if NATIVEAOT - if (IsCustomAttributeDefined(type.GetMetadataReader(), type.GetCustomAttributeHandles(), caType)) -#else - if (IsCustomAttributeDefined(type.GetRuntimeModule(), type.MetadataToken, caType)) -#endif + if (IsCustomAttributeDefined(new QCustomAttributeList(type), caType)) return true; if (!inherit) @@ -939,11 +899,7 @@ internal static bool IsDefined(RuntimeType type, RuntimeType? caType, bool inher while (type is not null) { -#if NATIVEAOT - if (IsCustomAttributeDefined(type.GetMetadataReader(), type.GetCustomAttributeHandles(), caType, inherit)) -#else - if (IsCustomAttributeDefined(type.GetRuntimeModule(), type.MetadataToken, caType, 0, inherit)) -#endif + if (IsCustomAttributeDefined(new QCustomAttributeList(type), caType, inherit)) return true; type = (type.BaseType as RuntimeType)!; @@ -960,11 +916,7 @@ internal static bool IsDefined(RuntimeMethodInfo method, RuntimeType caType, boo if (PseudoCustomAttribute.IsDefined(method, caType)) return true; -#if NATIVEAOT - if (IsCustomAttributeDefined(method.GetMetadataReader(), method.GetCustomAttributeHandles(), caType)) -#else - if (IsCustomAttributeDefined(method.GetRuntimeModule(), method.MetadataToken, caType)) -#endif + if (IsCustomAttributeDefined(new QCustomAttributeList(method), caType)) return true; if (!inherit) @@ -974,11 +926,7 @@ internal static bool IsDefined(RuntimeMethodInfo method, RuntimeType caType, boo while (method is not null) { -#if NATIVEAOT - if (IsCustomAttributeDefined(method.GetMetadataReader(), method.GetCustomAttributeHandles(), caType, inherit)) -#else - if (IsCustomAttributeDefined(method.GetRuntimeModule(), method.MetadataToken, caType, 0, inherit)) -#endif + if (IsCustomAttributeDefined(new QCustomAttributeList(method), caType, inherit)) return true; method = method.GetParentDefinition()!; @@ -994,11 +942,7 @@ internal static bool IsDefined(RuntimeConstructorInfo ctor, RuntimeType caType) // No pseudo attributes for RuntimeConstructorInfo -#if NATIVEAOT - return IsCustomAttributeDefined(ctor.GetMetadataReader(), ctor.GetCustomAttributeHandles(), caType); -#else - return IsCustomAttributeDefined(ctor.GetRuntimeModule(), ctor.MetadataToken, caType); -#endif + return IsCustomAttributeDefined(new QCustomAttributeList(ctor), caType); } internal static bool IsDefined(RuntimePropertyInfo property, RuntimeType caType) @@ -1008,11 +952,7 @@ internal static bool IsDefined(RuntimePropertyInfo property, RuntimeType caType) // No pseudo attributes for RuntimePropertyInfo -#if NATIVEAOT - return IsCustomAttributeDefined(property.GetMetadataReader(), property.GetCustomAttributeHandles(), caType); -#else - return IsCustomAttributeDefined(property.GetRuntimeModule(), property.MetadataToken, caType); -#endif + return IsCustomAttributeDefined(new QCustomAttributeList(property), caType); } internal static bool IsDefined(RuntimeEventInfo e, RuntimeType caType) @@ -1022,11 +962,7 @@ internal static bool IsDefined(RuntimeEventInfo e, RuntimeType caType) // No pseudo attributes for RuntimeEventInfo -#if NATIVEAOT - return IsCustomAttributeDefined(e.GetMetadataReader(), e.GetCustomAttributeHandles(), caType); -#else - return IsCustomAttributeDefined(e.GetRuntimeModule(), e.MetadataToken, caType); -#endif + return IsCustomAttributeDefined(new QCustomAttributeList(e), caType); } internal static bool IsDefined(RuntimeFieldInfo field, RuntimeType caType) @@ -1037,11 +973,7 @@ internal static bool IsDefined(RuntimeFieldInfo field, RuntimeType caType) if (PseudoCustomAttribute.IsDefined(field, caType)) return true; -#if NATIVEAOT - return IsCustomAttributeDefined(field.GetMetadataReader(), field.GetCustomAttributeHandles(), caType); -#else - return IsCustomAttributeDefined(field.GetRuntimeModule(), field.MetadataToken, caType); -#endif + return IsCustomAttributeDefined(new QCustomAttributeList(field), caType); } internal static bool IsDefined(RuntimeParameterInfo parameter, RuntimeType caType) @@ -1052,11 +984,7 @@ internal static bool IsDefined(RuntimeParameterInfo parameter, RuntimeType caTyp if (PseudoCustomAttribute.IsDefined(parameter, caType)) return true; -#if NATIVEAOT - return IsCustomAttributeDefined(parameter.GetMetadataReader(), parameter.GetCustomAttributeHandles(), caType); -#else - return IsCustomAttributeDefined(parameter.GetRuntimeModule()!, parameter.MetadataToken, caType); -#endif + return IsCustomAttributeDefined(new QCustomAttributeList(parameter), caType); } internal static bool IsDefined(RuntimeAssembly assembly, RuntimeType caType) @@ -1065,11 +993,7 @@ internal static bool IsDefined(RuntimeAssembly assembly, RuntimeType caType) Debug.Assert(caType is not null); // No pseudo attributes for RuntimeAssembly -#if NATIVEAOT - return IsCustomAttributeDefined(assembly.GetMetadataReader(), assembly.GetCustomAttributeHandles(), caType); -#else - return IsCustomAttributeDefined((assembly.ManifestModule as RuntimeModule)!, RuntimeAssembly.GetToken(assembly), caType); -#endif + return IsCustomAttributeDefined(new QCustomAttributeList(assembly), caType); } internal static bool IsDefined(RuntimeModule module, RuntimeType caType) @@ -1079,11 +1003,7 @@ internal static bool IsDefined(RuntimeModule module, RuntimeType caType) // No pseudo attributes for RuntimeModule -#if NATIVEAOT - return IsCustomAttributeDefined(module.GetMetadataReader(), module.GetCustomAttributeHandles(), caType); -#else - return IsCustomAttributeDefined(module, module.MetadataToken, caType); -#endif + return IsCustomAttributeDefined(new QCustomAttributeList(module), caType); } internal static object[] GetCustomAttributes(RuntimeType type, RuntimeType caType, bool inherit) @@ -1105,11 +1025,7 @@ internal static object[] GetCustomAttributes(RuntimeType type, RuntimeType caTyp // ... however if the attribute is sealed we can rely on the attribute usage if (!inherit || (caType.IsSealed && !GetAttributeUsage(caType).Inherited)) { -#if NATIVEAOT - object[] attributes = GetCustomAttributes(type.GetMetadataReader(), type.GetCustomAttributeHandles(), pcas.Count, caType); -#else - object[] attributes = GetCustomAttributes(type.GetRuntimeModule(), type.MetadataToken, pcas.Count, caType); -#endif + object[] attributes = GetCustomAttributes(new QCustomAttributeList(type), pcas.Count, caType); if (pcas.Count > 0) pcas.CopyTo(attributes, attributes.Length - pcas.Count); return attributes; } @@ -1122,11 +1038,7 @@ internal static object[] GetCustomAttributes(RuntimeType type, RuntimeType caTyp do { -#if NATIVEAOT - AddCustomAttributes(ref result, type.GetMetadataReader(), type.GetCustomAttributeHandles(), caType, mustBeInheritable, result); -#else - AddCustomAttributes(ref result, type.GetRuntimeModule(), type.MetadataToken, caType, mustBeInheritable, result); -#endif + AddCustomAttributes(ref result, new QCustomAttributeList(type), caType, mustBeInheritable, result); mustBeInheritable = true; type = (type.BaseType as RuntimeType)!; } while (type != (RuntimeType)typeof(object) && type != null); @@ -1155,11 +1067,7 @@ internal static object[] GetCustomAttributes(RuntimeMethodInfo method, RuntimeTy // ... however if the attribute is sealed we can rely on the attribute usage if (!inherit || (caType.IsSealed && !GetAttributeUsage(caType).Inherited)) { -#if NATIVEAOT - object[] attributes = GetCustomAttributes(method.GetMetadataReader(), method.GetCustomAttributeHandles(), pcas.Count, caType); -#else - object[] attributes = GetCustomAttributes(method.GetRuntimeModule(), method.MetadataToken, pcas.Count, caType); -#endif + object[] attributes = GetCustomAttributes(new QCustomAttributeList(method), pcas.Count, caType); if (pcas.Count > 0) pcas.CopyTo(attributes, attributes.Length - pcas.Count); return attributes; } @@ -1172,11 +1080,7 @@ internal static object[] GetCustomAttributes(RuntimeMethodInfo method, RuntimeTy while (method != null) { -#if NATIVEAOT - AddCustomAttributes(ref result, method.GetMetadataReader(), method.GetCustomAttributeHandles(), caType, mustBeInheritable, result); -#else - AddCustomAttributes(ref result, method.GetRuntimeModule(), method.MetadataToken, caType, mustBeInheritable, result); -#endif + AddCustomAttributes(ref result, new QCustomAttributeList(method), caType, mustBeInheritable, result); mustBeInheritable = true; method = method.GetParentDefinition()!; } @@ -1196,11 +1100,7 @@ internal static object[] GetCustomAttributes(RuntimeConstructorInfo ctor, Runtim // No pseudo attributes for RuntimeConstructorInfo -#if NATIVEAOT - return GetCustomAttributes(ctor.GetMetadataReader(), ctor.GetCustomAttributeHandles(), 0, caType); -#else - return GetCustomAttributes(ctor.GetRuntimeModule(), ctor.MetadataToken, 0, caType); -#endif + return GetCustomAttributes(new QCustomAttributeList(ctor), 0, caType); } internal static object[] GetCustomAttributes(RuntimePropertyInfo property, RuntimeType caType) @@ -1210,11 +1110,7 @@ internal static object[] GetCustomAttributes(RuntimePropertyInfo property, Runti // No pseudo attributes for RuntimePropertyInfo -#if NATIVEAOT - return GetCustomAttributes(property.GetMetadataReader(), property.GetCustomAttributeHandles(), 0, caType); -#else - return GetCustomAttributes(property.GetRuntimeModule(), property.MetadataToken, 0, caType); -#endif + return GetCustomAttributes(new QCustomAttributeList(property), 0, caType); } internal static object[] GetCustomAttributes(RuntimeEventInfo e, RuntimeType caType) @@ -1224,11 +1120,7 @@ internal static object[] GetCustomAttributes(RuntimeEventInfo e, RuntimeType caT // No pseudo attributes for RuntimeEventInfo -#if NATIVEAOT - return GetCustomAttributes(e.GetMetadataReader(), e.GetCustomAttributeHandles(), 0, caType); -#else - return GetCustomAttributes(e.GetRuntimeModule(), e.MetadataToken, 0, caType); -#endif + return GetCustomAttributes(new QCustomAttributeList(e), 0, caType); } internal static object[] GetCustomAttributes(RuntimeFieldInfo field, RuntimeType caType) @@ -1238,11 +1130,7 @@ internal static object[] GetCustomAttributes(RuntimeFieldInfo field, RuntimeType ListBuilder pcas = default; PseudoCustomAttribute.GetCustomAttributes(field, caType, ref pcas); -#if NATIVEAOT - object[] attributes = GetCustomAttributes(field.GetMetadataReader(), field.GetCustomAttributeHandles(), pcas.Count, caType); -#else - object[] attributes = GetCustomAttributes(field.GetRuntimeModule(), field.MetadataToken, pcas.Count, caType); -#endif + object[] attributes = GetCustomAttributes(new QCustomAttributeList(field), pcas.Count, caType); if (pcas.Count > 0) pcas.CopyTo(attributes, attributes.Length - pcas.Count); return attributes; } @@ -1254,11 +1142,7 @@ internal static object[] GetCustomAttributes(RuntimeParameterInfo parameter, Run ListBuilder pcas = default; PseudoCustomAttribute.GetCustomAttributes(parameter, caType, ref pcas); -#if NATIVEAOT - object[] attributes = GetCustomAttributes(parameter.GetMetadataReader(), parameter.GetCustomAttributeHandles(), pcas.Count, caType); -#else - object[] attributes = GetCustomAttributes(parameter.GetRuntimeModule()!, parameter.MetadataToken, pcas.Count, caType); -#endif + object[] attributes = GetCustomAttributes(new QCustomAttributeList(parameter), pcas.Count, caType); if (pcas.Count > 0) pcas.CopyTo(attributes, attributes.Length - pcas.Count); return attributes; } @@ -1270,12 +1154,7 @@ internal static object[] GetCustomAttributes(RuntimeAssembly assembly, RuntimeTy // No pseudo attributes for RuntimeAssembly -#if NATIVEAOT - return GetCustomAttributes(assembly.GetMetadataReader(), assembly.GetCustomAttributeHandles(), 0, caType); -#else - int assemblyToken = RuntimeAssembly.GetToken(assembly); - return GetCustomAttributes((assembly.ManifestModule as RuntimeModule)!, assemblyToken, 0, caType); -#endif + return GetCustomAttributes(new QCustomAttributeList(assembly), 0, caType); } internal static object[] GetCustomAttributes(RuntimeModule module, RuntimeType caType) @@ -1285,28 +1164,16 @@ internal static object[] GetCustomAttributes(RuntimeModule module, RuntimeType c // No pseudo attributes for RuntimeModule -#if NATIVEAOT - return GetCustomAttributes(module.GetMetadataReader(), module.GetCustomAttributeHandles(), 0, caType); -#else - return GetCustomAttributes(module, module.MetadataToken, 0, caType); -#endif + return GetCustomAttributes(new QCustomAttributeList(module), 0, caType); } private static object[] GetCustomAttributes( -#if NATIVEAOT - MetadataReader? reader, CustomAttributeHandleCollection customAttributeHandles, -#else - RuntimeModule decoratedModule, int decoratedMetadataToken, -#endif + QCustomAttributeList customAttributes, int pcaCount, RuntimeType attributeFilterType) { ListBuilder attributes = default; -#if NATIVEAOT - AddCustomAttributes(ref attributes, reader, customAttributeHandles, attributeFilterType, false, default); -#else - AddCustomAttributes(ref attributes, decoratedModule, decoratedMetadataToken, attributeFilterType, false, default); -#endif + AddCustomAttributes(ref attributes, customAttributes, attributeFilterType, false, default); object[] result = CreateAttributeArrayHelper(attributeFilterType, attributes.Count + pcaCount); for (int i = 0; i < attributes.Count; i++) From 7552bde135ce461209b1533afb6ed54e2a77df14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 17 Sep 2026 11:23:04 +0900 Subject: [PATCH 2/4] Unify custom attribute argument parsing across runtimes Keep the CoreCLR parsing flow shared, move NativeAOT argument enumeration into the parser, and isolate named argument target decoding by runtime. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../RuntimeCustomAttributeData.CoreCLR.cs | 16 +++++ .../RuntimeCustomAttributeData.NativeAot.cs | 58 ++++++++++++++++++- .../Reflection/RuntimeCustomAttributeData.cs | 42 ++------------ 3 files changed, 76 insertions(+), 40 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs index 8891dad7e2ddc9..6a62f1b02fb612 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs @@ -198,6 +198,22 @@ private static CustomAttributeEncodedArgument ParseCustomAttributeValue( return arg; } + private static CustomAttributeType ParseNamedArgumentTarget( + ref CustomAttributeDataParser parser, RuntimeModule module, out string? argumentName) + { + // Determine if a field or property. + CustomAttributeEncoding namedArgFieldOrProperty = parser.GetTag(); + if (namedArgFieldOrProperty is not CustomAttributeEncoding.Field + && namedArgFieldOrProperty is not CustomAttributeEncoding.Property) + { + throw new BadImageFormatException(SR.Arg_CustomAttributeFormatException); + } + + CustomAttributeType argumentType = ParseCustomAttributeType(ref parser, module); + argumentName = parser.GetString(); + return argumentType; + } + private static CustomAttributeType ParseCustomAttributeType(ref CustomAttributeDataParser parser, RuntimeModule module) { CustomAttributeEncoding arrayTag = CustomAttributeEncoding.Undefined; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs index 0ddcddd80c669e..41e0c72afa086c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs @@ -183,6 +183,22 @@ public readonly partial struct CustomAttributeTypedArgument internal sealed partial class CustomAttributeEncodedArgument { + private static CustomAttributeEncodedArgument ParseCustomAttributeValue( + ref CustomAttributeDataParser parser, CustomAttributeType type, MetadataReader module) + { + return parser.ParseArgument(type); + } + + private static CustomAttributeType ParseNamedArgumentTarget( + ref CustomAttributeDataParser parser, MetadataReader module, out string? argumentName) + { + NamedArgument namedArgument = parser.GetNamedArgument(); + RuntimeType argumentType = (RuntimeType)namedArgument.Type.Resolve(module, default).ToType(); + CustomAttributeType type = new CustomAttributeType(argumentType); + argumentName = namedArgument.Name.GetString(module); + return type; + } + internal static object? ParseValue(MetadataReader reader, Handle value, RuntimeType argumentType) { try @@ -200,10 +216,14 @@ internal sealed partial class CustomAttributeEncodedArgument /// /// Used to parse NativeFormat custom attribute data. /// - private readonly struct CustomAttributeDataParser + private struct CustomAttributeDataParser { private readonly CustomAttribute _attribute; private readonly MetadataReader _reader; + private HandleCollection.Enumerator _fixedArguments; + private NamedArgumentHandleCollection.Enumerator _namedArguments; + private Handle _namedArgumentValue; + private bool _parsingNamedArguments; public CustomAttributeDataParser(CustomAttribute attribute, MetadataReader reader) { @@ -213,7 +233,41 @@ public CustomAttributeDataParser(CustomAttribute attribute, MetadataReader reade public CustomAttribute Attribute => _attribute; - public bool ValidateProlog() => _reader is not null; + public bool ValidateProlog() + { + if (_reader is null) + return false; + + _fixedArguments = _attribute.FixedArguments.GetEnumerator(); + return true; + } + + public NamedArgument GetNamedArgument() + { + if (!_parsingNamedArguments) + { + _namedArguments = _attribute.NamedArguments.GetEnumerator(); + _parsingNamedArguments = true; + } + + if (!_namedArguments.MoveNext()) + throw new BadImageFormatException(); + + NamedArgument namedArgument = _namedArguments.Current.GetNamedArgument(_reader); + _namedArgumentValue = namedArgument.Value; + return namedArgument; + } + + public CustomAttributeEncodedArgument ParseArgument(CustomAttributeType type) + { + if (_parsingNamedArguments) + return ParseValue(_namedArgumentValue, type); + + if (!_fixedArguments.MoveNext()) + throw new BadImageFormatException(); + + return ParseValue(_fixedArguments.Current, type); + } public CustomAttributeEncodedArgument ParseValue(Handle value, CustomAttributeType type) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs index e8cfc54d5c846d..a0dcf01c64d467 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs @@ -687,24 +687,12 @@ private static void ParseCtorArgs( CustomAttributeCtorParameter[] customAttributeCtorParameters, ResolutionScope module) { -#if NATIVEAOT - HandleCollection.Enumerator fixedArguments = parser.Attribute.FixedArguments.GetEnumerator(); -#endif foreach (CustomAttributeCtorParameter p in customAttributeCtorParameters) { -#if NATIVEAOT - if (!fixedArguments.MoveNext()) - { - throw new BadImageFormatException(); - } - - p.EncodedArgument = parser.ParseValue(fixedArguments.Current, p.CustomAttributeType); -#else p.EncodedArgument = ParseCustomAttributeValue( ref parser, p.CustomAttributeType, module); -#endif } } @@ -713,35 +701,17 @@ private static void ParseNamedArgs( CustomAttributeNamedParameter[] customAttributeNamedParameters, ResolutionScope module) { + // Parse the named arguments in the custom attribute. #if NATIVEAOT - foreach (NamedArgumentHandle namedArgumentHandle in parser.Attribute.NamedArguments) - { - NamedArgument namedArgument = namedArgumentHandle.GetNamedArgument(module); + int argCount = parser.Attribute.NamedArguments.Count; #else - // Parse the named arguments in the custom attribute. int argCount = parser.GetI2(); +#endif for (int i = 0; i < argCount; ++i) { - // Determine if a field or property. - CustomAttributeEncoding namedArgFieldOrProperty = parser.GetTag(); - if (namedArgFieldOrProperty is not CustomAttributeEncoding.Field - && namedArgFieldOrProperty is not CustomAttributeEncoding.Property) - { - throw new BadImageFormatException(SR.Arg_CustomAttributeFormatException); - } -#endif - // Parse the encoded type for the named argument. -#if NATIVEAOT - RuntimeType argumentType = (RuntimeType)namedArgument.Type.Resolve(module, default).ToType(); - CustomAttributeType argType = new CustomAttributeType(argumentType); - string? argName = namedArgument.Name.GetString(module); -#else - CustomAttributeType argType = ParseCustomAttributeType(ref parser, module); - - string? argName = parser.GetString(); -#endif + CustomAttributeType argType = ParseNamedArgumentTarget(ref parser, module, out string? argName); // Argument name must be non-null and non-empty. if (string.IsNullOrEmpty(argName)) @@ -804,11 +774,7 @@ private static void ParseNamedArgs( throw new BadImageFormatException(SR.Arg_CustomAttributeDuplicateNamedArgument); } -#if NATIVEAOT - parameterToUpdate.EncodedArgument = parser.ParseValue(namedArgument.Value, argType); -#else parameterToUpdate.EncodedArgument = ParseCustomAttributeValue(ref parser, argType, module); -#endif } } From 013fa67040a13139ecd8a91645ccab56c9498cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 17 Sep 2026 12:09:34 +0900 Subject: [PATCH 3/4] Update RuntimeCustomAttributeData.NativeAot.cs --- .../System/Reflection/RuntimeCustomAttributeData.NativeAot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs index 41e0c72afa086c..e25b2e37b6ae63 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs @@ -184,7 +184,7 @@ public readonly partial struct CustomAttributeTypedArgument internal sealed partial class CustomAttributeEncodedArgument { private static CustomAttributeEncodedArgument ParseCustomAttributeValue( - ref CustomAttributeDataParser parser, CustomAttributeType type, MetadataReader module) + ref CustomAttributeDataParser parser, CustomAttributeType type, MetadataReader _) { return parser.ParseArgument(type); } From d43664ecd63ca534eb64c03a825068e9a5c3da79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 25 Sep 2026 13:29:51 +0900 Subject: [PATCH 4/4] Encapsulate named argument count in custom attribute parsers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ab64e674-0ade-44b7-a8da-9a03733251ed --- .../Reflection/RuntimeCustomAttributeData.CoreCLR.cs | 2 ++ .../Reflection/RuntimeCustomAttributeData.NativeAot.cs | 5 ++--- .../src/System/Reflection/RuntimeCustomAttributeData.cs | 7 +------ 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs index 6a62f1b02fb612..00bbcea5275fcb 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.CoreCLR.cs @@ -328,6 +328,8 @@ public bool ValidateProlog() return val == 0x0001; } + public int GetNamedArgumentCount() => GetI2(); + public string? GetString() { byte packedLengthBegin = PeekData(sizeof(byte))[0]; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs index e25b2e37b6ae63..21b795d60ad15a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.NativeAot.cs @@ -13,7 +13,6 @@ using System.Reflection.Runtime.MethodInfos; using System.Reflection.Runtime.MethodInfos.NativeFormat; using System.Reflection.Runtime.Modules; -using System.Reflection.Runtime.ParameterInfos; using System.Reflection.Runtime.PropertyInfos; using System.Reflection.Runtime.TypeInfos; using System.Reflection.Runtime.TypeInfos.NativeFormat; @@ -231,8 +230,6 @@ public CustomAttributeDataParser(CustomAttribute attribute, MetadataReader reade _reader = reader; } - public CustomAttribute Attribute => _attribute; - public bool ValidateProlog() { if (_reader is null) @@ -242,6 +239,8 @@ public bool ValidateProlog() return true; } + public int GetNamedArgumentCount() => _attribute.NamedArguments.Count; + public NamedArgument GetNamedArgument() { if (!_parsingNamedArguments) diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs index 80be448fc684b9..d6b23844f3ef69 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs @@ -700,12 +700,7 @@ private static void ParseNamedArgs( CustomAttributeNamedParameter[] customAttributeNamedParameters, ResolutionScope module) { - // Parse the named arguments in the custom attribute. -#if NATIVEAOT - int argCount = parser.Attribute.NamedArguments.Count; -#else - int argCount = parser.GetI2(); -#endif + int argCount = parser.GetNamedArgumentCount(); for (int i = 0; i < argCount; ++i) {