From 126feb1a7a54d751d69a75f547ff793da2386491 Mon Sep 17 00:00:00 2001 From: Eduardo Velarde Date: Tue, 21 Apr 2026 16:05:21 -0700 Subject: [PATCH 1/5] Remove ILC duplicate warnings --- .../CustomAttributeBasedDependencyAlgorithm.cs | 12 ++++++++++-- .../Compiler/UsageBasedMetadataManager.cs | 8 ++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs index 1b0b36055287f9..0dba5a8e7c10e5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs @@ -65,6 +65,7 @@ public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyLi if ((methodDef.Attributes & MethodAttributes.SpecialName) != 0) { TypeDefinition declaringType = reader.GetTypeDefinition(methodDef.GetDeclaringType()); + var mdManager = (UsageBasedMetadataManager)factory.MetadataManager; foreach (PropertyDefinitionHandle propertyHandle in declaringType.GetProperties()) { @@ -72,7 +73,11 @@ public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyLi PropertyAccessors accessors = property.GetAccessors(); if (accessors.Getter == methodHandle || accessors.Setter == methodHandle) - AddDependenciesDueToCustomAttributes(ref dependencies, propertyCondition, factory, method.Module, property.GetCustomAttributes(), new PropertyPseudoDesc(method.OwningType, propertyHandle)); + { + // Avoid analyzing the same custom attributes (and producing the same diagnostics) twice. + if (mdManager.TryRegisterPropertyCustomAttributesProcessed(method.Module, propertyHandle)) + AddDependenciesDueToCustomAttributes(ref dependencies, propertyCondition, factory, method.Module, property.GetCustomAttributes(), new PropertyPseudoDesc(method.OwningType, propertyHandle)); + } } object eventCondition = GetMetadataApiDependency(factory, "Event"u8); @@ -82,7 +87,10 @@ public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyLi EventAccessors accessors = @event.GetAccessors(); if (accessors.Adder == methodHandle || accessors.Remover == methodHandle || accessors.Raiser == methodHandle) - AddDependenciesDueToCustomAttributes(ref dependencies, eventCondition, factory, method.Module, @event.GetCustomAttributes(), new EventPseudoDesc(method.OwningType, eventHandle)); + { + if (mdManager.TryRegisterEventCustomAttributesProcessed(method.Module, eventHandle)) + AddDependenciesDueToCustomAttributes(ref dependencies, eventCondition, factory, method.Module, @event.GetCustomAttributes(), new EventPseudoDesc(method.OwningType, eventHandle)); + } } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index c4b05b20c524c4..4256d9f9702bd1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -61,11 +61,19 @@ private static (string AttributeName, DiagnosticId Id)[] _requiresAttributeMisma internal IReadOnlyDictionary FeatureSwitches { get; } private readonly HashSet _rootEntireAssembliesExaminedModules = new HashSet(); + private readonly HashSet<(EcmaModule, PropertyDefinitionHandle)> _propertyCustomAttributesProcessed = new(); + private readonly HashSet<(EcmaModule, EventDefinitionHandle)> _eventCustomAttributesProcessed = new(); private readonly HashSet _rootEntireAssembliesModules; private readonly HashSet _trimmedAssemblies; private readonly List _satelliteAssemblyFiles; + internal bool TryRegisterPropertyCustomAttributesProcessed(EcmaModule module, PropertyDefinitionHandle handle) + => _propertyCustomAttributesProcessed.Add((module, handle)); + + internal bool TryRegisterEventCustomAttributesProcessed(EcmaModule module, EventDefinitionHandle handle) + => _eventCustomAttributesProcessed.Add((module, handle)); + internal Logger Logger { get; } public UsageBasedMetadataManager( From 479d868ee58fde12fafb7d107a88f68d1a955037 Mon Sep 17 00:00:00 2001 From: Eduardo Velarde Date: Tue, 21 Apr 2026 17:30:03 -0700 Subject: [PATCH 2/5] Remove active issue --- .../DataFlow/AttributePropertyDataflow.cs | 3 --- .../RequiresCapability/RequiresOnAttribute.cs | 6 ------ 2 files changed, 9 deletions(-) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs index f87e1c8603a62c..b3c301044eafa8 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs @@ -145,7 +145,6 @@ class AttributesOnProperty [Kept] [KeptAttributeAttribute(typeof(KeepsPublicMethodsAttribute))] [ExpectedWarning("IL2026", "--ClassWithKeptPublicMethods--")] - [UnexpectedWarning("IL2026", "--ClassWithKeptPublicMethods--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [KeepsPublicMethods(Type = typeof(ClassWithKeptPublicMethods))] public static bool Property { [Kept] get; [Kept] set; } @@ -175,7 +174,6 @@ class AttributesOnEvent [KeptEventRemoveMethod] [KeptAttributeAttribute(typeof(KeepsPublicMethodsAttribute))] [ExpectedWarning("IL2026", "--ClassWithKeptPublicMethods--")] - [UnexpectedWarning("IL2026", "--ClassWithKeptPublicMethods--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [KeepsPublicMethods(Type = typeof(ClassWithKeptPublicMethods))] public static event EventHandler Event_FieldSyntax; @@ -185,7 +183,6 @@ class AttributesOnEvent [KeptEventRemoveMethod] [KeptAttributeAttribute(typeof(KeepsPublicMethodsAttribute))] [ExpectedWarning("IL2026", "--ClassWithKeptPublicMethods--")] - [UnexpectedWarning("IL2026", "--ClassWithKeptPublicMethods--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [KeepsPublicMethods(Type = typeof(ClassWithKeptPublicMethods))] public static event EventHandler Event_PropertySyntax { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnAttribute.cs index b22b50370af4fd..f6c92bf0113815 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnAttribute.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnAttribute.cs @@ -116,17 +116,11 @@ static void MethodWithAttributeWhichRequires() { } static int _fieldWithAttributeWhichRequires; [ExpectedWarning("IL2026", "--AttributeWhichRequiresAttribute.ctor--")] - [UnexpectedWarning("IL2026", "--AttributeWhichRequiresAttribute.ctor--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning("IL3002", "--AttributeWhichRequiresAttribute.ctor--", Tool.Analyzer | Tool.NativeAot, "NativeAOT-specific warning")] - [UnexpectedWarning("IL3002", "--AttributeWhichRequiresAttribute.ctor--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning("IL3050", "--AttributeWhichRequiresAttribute.ctor--", Tool.Analyzer | Tool.NativeAot, "NativeAOT-specific warning")] - [UnexpectedWarning("IL3050", "--AttributeWhichRequiresAttribute.ctor--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning("IL2026", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--")] - [UnexpectedWarning("IL2026", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning("IL3002", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT-specific warning")] - [UnexpectedWarning("IL3002", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning("IL3050", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT-specific warning")] - [UnexpectedWarning("IL3050", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [AttributeWhichRequires] [AttributeWhichRequiresOnProperty(PropertyWhichRequires = true)] static bool PropertyWithAttributeWhichRequires { get; set; } From ef4e45edc3a8770dfa041fc17247bee1fb5a8b40 Mon Sep 17 00:00:00 2001 From: Eduardo Velarde Date: Mon, 27 Apr 2026 12:21:17 -0700 Subject: [PATCH 3/5] Add PropertyMetadataNode --- .../Common/Compiler/PropertyPseudoDesc.cs | 2 + ...CustomAttributeBasedDependencyAlgorithm.cs | 32 +++++------- .../DependencyAnalysis/MethodMetadataNode.cs | 24 +++++++++ .../DependencyAnalysis/NodeFactory.cs | 15 ++++++ .../PropertyMetadataNode.cs | 50 +++++++++++++++++++ .../Compiler/UsageBasedMetadataManager.cs | 4 -- .../ILCompiler.Compiler.csproj | 1 + 7 files changed, 104 insertions(+), 24 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/PropertyMetadataNode.cs diff --git a/src/coreclr/tools/Common/Compiler/PropertyPseudoDesc.cs b/src/coreclr/tools/Common/Compiler/PropertyPseudoDesc.cs index 2c1b021e93e02c..2ceb2d9eda75de 100644 --- a/src/coreclr/tools/Common/Compiler/PropertyPseudoDesc.cs +++ b/src/coreclr/tools/Common/Compiler/PropertyPseudoDesc.cs @@ -86,6 +86,8 @@ public PropertyPseudoDesc(EcmaType type, PropertyDefinitionHandle handle) public override int GetHashCode() => _type.GetHashCode() ^ _handle.GetHashCode(); + public override string ToString() => $"{_type}.{Name}"; + public static bool operator ==(PropertyPseudoDesc a, PropertyPseudoDesc b) => a._type == b._type && a._handle == b._handle; public static bool operator !=(PropertyPseudoDesc a, PropertyPseudoDesc b) => !(a == b); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs index 0dba5a8e7c10e5..e53dd5ea4e0f9b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs @@ -54,32 +54,19 @@ public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyLi AddDependenciesDueToCustomAttributes(ref dependencies, genericParameterCondition, factory, method.Module, parameter.GetCustomAttributes(), method); } - // We don't model properties and events as separate entities within the compiler, so ensuring - // we can generate custom attributes for the associated events and properties from here - // is as good as any other place. + // Events are not modeled as separate entities within the compiler, so ensuring + // we can generate custom attributes for the associated events from here is as good + // as any other place. Properties are modeled via PropertyMetadataNode, so the + // associated property's custom attributes are pulled in via that node. // - // As a performance optimization, we look for associated events and properties only - // if the method is SpecialName. This is required for CLS compliance and compilers we - // care about emit accessors like this. - object propertyCondition = GetMetadataApiDependency(factory, "Property"u8); + // As a performance optimization, we look for associated events only if the method is + // SpecialName. This is required for CLS compliance and compilers we care about emit + // accessors like this. if ((methodDef.Attributes & MethodAttributes.SpecialName) != 0) { TypeDefinition declaringType = reader.GetTypeDefinition(methodDef.GetDeclaringType()); var mdManager = (UsageBasedMetadataManager)factory.MetadataManager; - foreach (PropertyDefinitionHandle propertyHandle in declaringType.GetProperties()) - { - PropertyDefinition property = reader.GetPropertyDefinition(propertyHandle); - PropertyAccessors accessors = property.GetAccessors(); - - if (accessors.Getter == methodHandle || accessors.Setter == methodHandle) - { - // Avoid analyzing the same custom attributes (and producing the same diagnostics) twice. - if (mdManager.TryRegisterPropertyCustomAttributesProcessed(method.Module, propertyHandle)) - AddDependenciesDueToCustomAttributes(ref dependencies, propertyCondition, factory, method.Module, property.GetCustomAttributes(), new PropertyPseudoDesc(method.OwningType, propertyHandle)); - } - } - object eventCondition = GetMetadataApiDependency(factory, "Event"u8); foreach (EventDefinitionHandle eventHandle in declaringType.GetEvents()) { @@ -116,6 +103,11 @@ public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyLi AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "Field"u8), factory, field.Module, fieldDef.GetCustomAttributes(), field); } + public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, PropertyPseudoDesc property) + { + AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "Property"u8), factory, property.OwningType.Module, property.GetCustomAttributes, property); + } + public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, EcmaAssembly assembly) { AssemblyDefinition asmDef = assembly.MetadataReader.GetAssemblyDefinition(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs index f145aa9504eaaa..160eb642c72647 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Metadata; using ILCompiler.Dataflow; using ILCompiler.DependencyAnalysisFramework; @@ -11,6 +13,7 @@ using Debug = System.Diagnostics.Debug; using EcmaMethod = Internal.TypeSystem.Ecma.EcmaMethod; +using EcmaType = Internal.TypeSystem.Ecma.EcmaType; namespace ILCompiler.DependencyAnalysis { @@ -85,6 +88,27 @@ public override IEnumerable GetStaticDependencies(NodeFacto { dependencies.Add(factory.AnalysisCharacteristic("StackTraceHiddenMetadataPresent"), "Method is StackTraceHidden"); } + + // If this method is a property accessor, ensure metadata for the associated + // property is generated as well. Properties are not modeled as first-class + // entities in the type system, so we discover them here from their accessors. + // As a performance optimization, only SpecialName methods can be accessors. + if ((_method.Attributes & MethodAttributes.SpecialName) != 0) + { + MetadataReader reader = _method.MetadataReader; + MethodDefinitionHandle methodHandle = _method.Handle; + TypeDefinition declaringType = reader.GetTypeDefinition(reader.GetMethodDefinition(methodHandle).GetDeclaringType()); + foreach (PropertyDefinitionHandle propertyHandle in declaringType.GetProperties()) + { + PropertyAccessors accessors = reader.GetPropertyDefinition(propertyHandle).GetAccessors(); + if (accessors.Getter == methodHandle || accessors.Setter == methodHandle) + { + dependencies.Add( + factory.PropertyMetadata(new PropertyPseudoDesc((EcmaType)owningType, propertyHandle)), + "Property associated with reflectable accessor"); + } + } + } } return dependencies; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index bdec43b6ce9873..2dcd9a0eb3a5d0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -575,6 +575,11 @@ private void CreateNodeCaches() return new FieldMetadataNode(field); }); + _propertiesWithMetadata = new NodeCache(property => + { + return new PropertyMetadataNode(property); + }); + _modulesWithMetadata = new NodeCache(module => { return new ModuleMetadataNode(module); @@ -1462,6 +1467,16 @@ internal FieldMetadataNode FieldMetadata(FieldDesc field) return _fieldsWithMetadata.GetOrAdd(field); } + private NodeCache _propertiesWithMetadata; + + internal PropertyMetadataNode PropertyMetadata(PropertyPseudoDesc property) + { + // These are only meaningful for UsageBasedMetadataManager. We should not have them + // in the dependency graph otherwise. + Debug.Assert(MetadataManager is UsageBasedMetadataManager); + return _propertiesWithMetadata.GetOrAdd(property); + } + private NodeCache _modulesWithMetadata; internal ModuleMetadataNode ModuleMetadata(ModuleDesc module) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/PropertyMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/PropertyMetadataNode.cs new file mode 100644 index 00000000000000..6a8fedbec05ee4 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/PropertyMetadataNode.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +using ILCompiler.DependencyAnalysisFramework; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// Represents a property that has metadata generated in the current compilation. + /// This corresponds to an ECMA-335 Property record. Unlike fields and methods, + /// properties are not first-class entities in the compiler's type system (there is no + /// interned PropertyDesc). + /// + /// + /// Only expected to be used during ILScanning when scanning for reflection. + /// + internal sealed class PropertyMetadataNode : DependencyNodeCore + { + private readonly PropertyPseudoDesc _property; + + public PropertyMetadataNode(PropertyPseudoDesc property) + { + _property = property; + } + + public PropertyPseudoDesc Property => _property; + + public override IEnumerable GetStaticDependencies(NodeFactory factory) => null; + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory factory) + { + var dependencies = new List(); + CustomAttributeBasedDependencyAlgorithm.AddDependenciesDueToCustomAttributes(ref dependencies, factory, _property); + return dependencies; + } + + protected override string GetName(NodeFactory factory) + { + return "Property metadata: " + _property.ToString(); + } + + public override bool InterestingForDynamicDependencyAnalysis => false; + public override bool HasDynamicDependencies => false; + public override bool HasConditionalStaticDependencies => true; + public override bool StaticDependenciesAreComputed => true; + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory factory) => null; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index 4256d9f9702bd1..41ef6efe5f293e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -61,16 +61,12 @@ private static (string AttributeName, DiagnosticId Id)[] _requiresAttributeMisma internal IReadOnlyDictionary FeatureSwitches { get; } private readonly HashSet _rootEntireAssembliesExaminedModules = new HashSet(); - private readonly HashSet<(EcmaModule, PropertyDefinitionHandle)> _propertyCustomAttributesProcessed = new(); private readonly HashSet<(EcmaModule, EventDefinitionHandle)> _eventCustomAttributesProcessed = new(); private readonly HashSet _rootEntireAssembliesModules; private readonly HashSet _trimmedAssemblies; private readonly List _satelliteAssemblyFiles; - internal bool TryRegisterPropertyCustomAttributesProcessed(EcmaModule module, PropertyDefinitionHandle handle) - => _propertyCustomAttributesProcessed.Add((module, handle)); - internal bool TryRegisterEventCustomAttributesProcessed(EcmaModule module, EventDefinitionHandle handle) => _eventCustomAttributesProcessed.Add((module, handle)); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index a0e5f77c7c533b..eb79a97cbd5eac 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -495,6 +495,7 @@ + From 9ca22f7ae242d788b1df59f6e5afbeee753e600f Mon Sep 17 00:00:00 2001 From: Eduardo Velarde Date: Mon, 27 Apr 2026 13:08:52 -0700 Subject: [PATCH 4/5] Add EventMetadataNode --- .../tools/Common/Compiler/EventPseudoDesc.cs | 2 + ...CustomAttributeBasedDependencyAlgorithm.cs | 31 ++---------- .../DependencyAnalysis/EventMetadataNode.cs | 50 +++++++++++++++++++ .../DependencyAnalysis/MethodMetadataNode.cs | 11 ++++ .../DependencyAnalysis/NodeFactory.cs | 15 ++++++ .../Compiler/UsageBasedMetadataManager.cs | 4 -- .../ILCompiler.Compiler.csproj | 1 + 7 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EventMetadataNode.cs diff --git a/src/coreclr/tools/Common/Compiler/EventPseudoDesc.cs b/src/coreclr/tools/Common/Compiler/EventPseudoDesc.cs index 1bfee2d0196f45..287d86cdee7687 100644 --- a/src/coreclr/tools/Common/Compiler/EventPseudoDesc.cs +++ b/src/coreclr/tools/Common/Compiler/EventPseudoDesc.cs @@ -91,6 +91,8 @@ public EventPseudoDesc(EcmaType type, EventDefinitionHandle handle) public override int GetHashCode() => _type.GetHashCode() ^ _handle.GetHashCode(); + public override string ToString() => $"{_type}.{Name}"; + public static bool operator ==(EventPseudoDesc a, EventPseudoDesc b) => a._type == b._type && a._handle == b._handle; public static bool operator !=(EventPseudoDesc a, EventPseudoDesc b) => !(a == b); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs index e53dd5ea4e0f9b..7fd15f692c993a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs @@ -13,7 +13,6 @@ using DependencyListEntry = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyListEntry; using CombinedDependencyList = System.Collections.Generic.List.CombinedDependencyListEntry>; using CombinedDependencyListEntry = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.CombinedDependencyListEntry; -using MethodAttributes = System.Reflection.MethodAttributes; namespace ILCompiler.DependencyAnalysis { @@ -54,32 +53,7 @@ public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyLi AddDependenciesDueToCustomAttributes(ref dependencies, genericParameterCondition, factory, method.Module, parameter.GetCustomAttributes(), method); } - // Events are not modeled as separate entities within the compiler, so ensuring - // we can generate custom attributes for the associated events from here is as good - // as any other place. Properties are modeled via PropertyMetadataNode, so the - // associated property's custom attributes are pulled in via that node. - // - // As a performance optimization, we look for associated events only if the method is - // SpecialName. This is required for CLS compliance and compilers we care about emit - // accessors like this. - if ((methodDef.Attributes & MethodAttributes.SpecialName) != 0) - { - TypeDefinition declaringType = reader.GetTypeDefinition(methodDef.GetDeclaringType()); - var mdManager = (UsageBasedMetadataManager)factory.MetadataManager; - - object eventCondition = GetMetadataApiDependency(factory, "Event"u8); - foreach (EventDefinitionHandle eventHandle in declaringType.GetEvents()) - { - EventDefinition @event = reader.GetEventDefinition(eventHandle); - EventAccessors accessors = @event.GetAccessors(); - if (accessors.Adder == methodHandle || accessors.Remover == methodHandle || accessors.Raiser == methodHandle) - { - if (mdManager.TryRegisterEventCustomAttributesProcessed(method.Module, eventHandle)) - AddDependenciesDueToCustomAttributes(ref dependencies, eventCondition, factory, method.Module, @event.GetCustomAttributes(), new EventPseudoDesc(method.OwningType, eventHandle)); - } - } - } } public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, EcmaType type) @@ -108,6 +82,11 @@ public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyLi AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "Property"u8), factory, property.OwningType.Module, property.GetCustomAttributes, property); } + public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, EventPseudoDesc @event) + { + AddDependenciesDueToCustomAttributes(ref dependencies, GetMetadataApiDependency(factory, "Event"u8), factory, @event.OwningType.Module, @event.GetCustomAttributes, @event); + } + public static void AddDependenciesDueToCustomAttributes(ref CombinedDependencyList dependencies, NodeFactory factory, EcmaAssembly assembly) { AssemblyDefinition asmDef = assembly.MetadataReader.GetAssemblyDefinition(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EventMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EventMetadataNode.cs new file mode 100644 index 00000000000000..349b43d253ded7 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/EventMetadataNode.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +using ILCompiler.DependencyAnalysisFramework; + +namespace ILCompiler.DependencyAnalysis +{ + /// + /// Represents an event that has metadata generated in the current compilation. + /// This corresponds to an ECMA-335 Event record. Unlike fields and methods, + /// events are not first-class entities in the compiler's type system (there is no + /// interned EventDesc). + /// + /// + /// Only expected to be used during ILScanning when scanning for reflection. + /// + internal sealed class EventMetadataNode : DependencyNodeCore + { + private readonly EventPseudoDesc _event; + + public EventMetadataNode(EventPseudoDesc @event) + { + _event = @event; + } + + public EventPseudoDesc Event => _event; + + public override IEnumerable GetStaticDependencies(NodeFactory factory) => null; + + public override IEnumerable GetConditionalStaticDependencies(NodeFactory factory) + { + var dependencies = new List(); + CustomAttributeBasedDependencyAlgorithm.AddDependenciesDueToCustomAttributes(ref dependencies, factory, _event); + return dependencies; + } + + protected override string GetName(NodeFactory factory) + { + return "Event metadata: " + _event.ToString(); + } + + public override bool InterestingForDynamicDependencyAnalysis => false; + public override bool HasDynamicDependencies => false; + public override bool HasConditionalStaticDependencies => true; + public override bool StaticDependenciesAreComputed => true; + public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory factory) => null; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs index 160eb642c72647..258c8c9513e25e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs @@ -108,6 +108,17 @@ public override IEnumerable GetStaticDependencies(NodeFacto "Property associated with reflectable accessor"); } } + + foreach (EventDefinitionHandle eventHandle in declaringType.GetEvents()) + { + EventAccessors accessors = reader.GetEventDefinition(eventHandle).GetAccessors(); + if (accessors.Adder == methodHandle || accessors.Remover == methodHandle || accessors.Raiser == methodHandle) + { + dependencies.Add( + factory.EventMetadata(new EventPseudoDesc((EcmaType)owningType, eventHandle)), + "Event associated with reflectable accessor"); + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 2dcd9a0eb3a5d0..5081a1b8ead9f7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -580,6 +580,11 @@ private void CreateNodeCaches() return new PropertyMetadataNode(property); }); + _eventsWithMetadata = new NodeCache(@event => + { + return new EventMetadataNode(@event); + }); + _modulesWithMetadata = new NodeCache(module => { return new ModuleMetadataNode(module); @@ -1477,6 +1482,16 @@ internal PropertyMetadataNode PropertyMetadata(PropertyPseudoDesc property) return _propertiesWithMetadata.GetOrAdd(property); } + private NodeCache _eventsWithMetadata; + + internal EventMetadataNode EventMetadata(EventPseudoDesc @event) + { + // These are only meaningful for UsageBasedMetadataManager. We should not have them + // in the dependency graph otherwise. + Debug.Assert(MetadataManager is UsageBasedMetadataManager); + return _eventsWithMetadata.GetOrAdd(@event); + } + private NodeCache _modulesWithMetadata; internal ModuleMetadataNode ModuleMetadata(ModuleDesc module) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index 41ef6efe5f293e..c4b05b20c524c4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -61,15 +61,11 @@ private static (string AttributeName, DiagnosticId Id)[] _requiresAttributeMisma internal IReadOnlyDictionary FeatureSwitches { get; } private readonly HashSet _rootEntireAssembliesExaminedModules = new HashSet(); - private readonly HashSet<(EcmaModule, EventDefinitionHandle)> _eventCustomAttributesProcessed = new(); private readonly HashSet _rootEntireAssembliesModules; private readonly HashSet _trimmedAssemblies; private readonly List _satelliteAssemblyFiles; - internal bool TryRegisterEventCustomAttributesProcessed(EcmaModule module, EventDefinitionHandle handle) - => _eventCustomAttributesProcessed.Add((module, handle)); - internal Logger Logger { get; } public UsageBasedMetadataManager( diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index eb79a97cbd5eac..a03e3003f7417c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -494,6 +494,7 @@ + From 2fb30f4b9bae7a40bcffa2c92d32a92f7bf1bfc5 Mon Sep 17 00:00:00 2001 From: Eduardo Velarde <32459232+eduardo-vp@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:50:31 -0700 Subject: [PATCH 5/5] Update src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Strehovský --- .../Compiler/DependencyAnalysis/MethodMetadataNode.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs index 258c8c9513e25e..ab49aca5b7c407 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs @@ -89,8 +89,8 @@ public override IEnumerable GetStaticDependencies(NodeFacto dependencies.Add(factory.AnalysisCharacteristic("StackTraceHiddenMetadataPresent"), "Method is StackTraceHidden"); } - // If this method is a property accessor, ensure metadata for the associated - // property is generated as well. Properties are not modeled as first-class + // If this method is a property or event accessor, ensure metadata for the associated + // property or event is generated as well. Properties/events are not modeled as first-class // entities in the type system, so we discover them here from their accessors. // As a performance optimization, only SpecialName methods can be accessors. if ((_method.Attributes & MethodAttributes.SpecialName) != 0)