diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs index 40fe1df4b26a18..dca75840d71cb4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs @@ -37,11 +37,14 @@ public AttributeDataFlow(Logger logger, NodeFactory factory, FlowAnnotations ann _logger = logger; _origin = origin; + // Warnings for attributes on type declarations are not suppressed by RUC on the type. + // This allows attribute warnings to still be reported when attributes are applied to RUC types. + bool isAttributeOnType = _origin.MemberDefinition is TypeDesc; _diagnosticContext = new DiagnosticContext( _origin, - _logger.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), - _logger.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), - _logger.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), + !isAttributeOnType && _logger.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + !isAttributeOnType && _logger.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), + !isAttributeOnType && _logger.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), _logger); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs index 056032d025bfe8..6ed2d39c9d5453 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs @@ -314,6 +314,11 @@ private void ReportWarningsForTypeHierarchyReflectionAccess(MessageOrigin origin Debug.Assert(_typeHierarchyDataFlowOrigin != null); + // Allow RUC on the type itself to suppress these warnings. + // This allows a type with RUC to suppress warnings about the type using other RUC types. + if (DiagnosticUtilities.TryGetRequiresAttribute(_typeHierarchyDataFlowOrigin, DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out _)) + return; + static bool IsDeclaredWithinType(TypeSystemEntity member, TypeDesc type) { TypeDesc owningType = member.GetOwningType(); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs index 12bd8b0e81aa12..e4110b806e5ad1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs @@ -236,6 +236,13 @@ internal bool ShouldSuppressAnalysisWarningsForRequires(TypeSystemEntity originM if (originMember is FieldDesc field) return field.DoesFieldRequire(requiresAttribute, out attribute); + // For type symbols, check if the type itself has the requires attribute + // This allows a type's RUC to suppress warnings about the type using other RUC types + // (e.g., in generic constraints, base types, etc.) + if (originMember is TypeDesc type && + DiagnosticUtilities.TryGetRequiresAttribute(type, requiresAttribute, out attribute)) + return true; + if (originMember.GetOwningType() == null) // Basically a way to test if the entity is a member (type, method, field, ...) { attribute = null; diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index b501fe4e6480a5..49b935882dcc39 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -174,16 +174,23 @@ void CheckMatchingAttributesInInterfaces( context.RegisterCompilationAction(extraCompilationAction); } - internal void CheckAndCreateRequiresDiagnostic( + private void CheckAndCreateRequiresDiagnostic( ISymbol member, ISymbol containingSymbol, ImmutableArray incompatibleMembers, - in DiagnosticContext diagnosticContext) + in DiagnosticContext diagnosticContext, + bool isAttributeOnType) { - // Do not emit any diagnostic if caller is annotated with the attribute too. + // Do not emit any diagnostic if caller is annotated with the attribute too if (containingSymbol.IsInRequiresScope(RequiresAttributeName, out _)) - return; - + { + // Warnings for attributes on type declarations are not suppressed by RUC on the type. + // This allows attribute warnings to still be reported when attributes are applied to RUC types. + if (!isAttributeOnType) + { + return; + } + } if (CreateSpecialIncompatibleMembersDiagnostic(incompatibleMembers, member, diagnosticContext)) return; @@ -224,7 +231,8 @@ private void AnalyzeImplicitBaseCtor(SymbolAnalysisContext context) baseCtor, implicitCtor, ImmutableArray.Empty, - diagnosticContext); + diagnosticContext, + isAttributeOnType: false); } [Flags] @@ -405,11 +413,45 @@ internal void CheckAndCreateRequiresDiagnostic( ISymbol containingSymbol = operation.FindContainingSymbol(owningSymbol); var incompatibleMembers = context.GetSpecialIncompatibleMembers(this); + + bool isAttributeOnType = containingSymbol is INamedTypeSymbol && IsAttributeOnType(operation, member); CheckAndCreateRequiresDiagnostic( member, containingSymbol, incompatibleMembers, - diagnosticContext); + diagnosticContext, + isAttributeOnType); + } + + private static bool IsAttributeOnType(IOperation operation, ISymbol member) + { + INamedTypeSymbol? potentialAttrType = null; + if (operation.Kind == OperationKind.ObjectCreation && + member is IMethodSymbol potentialAttrCtor && + potentialAttrCtor.IsConstructor()) + { + potentialAttrType = potentialAttrCtor.ContainingType; + } + else if (member is IMethodSymbol method && + method.MethodKind == MethodKind.PropertySet && + method.AssociatedSymbol is IPropertySymbol associatedProperty) + { + potentialAttrType = associatedProperty.ContainingType; + } + + return potentialAttrType != null && IsAttributeType(potentialAttrType); + } + + private static bool IsAttributeType(INamedTypeSymbol? type) + { + var current = type; + while (current != null) + { + if (current.ToDisplayString() == "System.Attribute") + return true; + current = current.BaseType; + } + return false; } internal virtual bool IsIntrinsicallyHandled( diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs index cf2e83f4b8f872..fe57ee9ac05313 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs @@ -57,11 +57,14 @@ private static bool ExcludeStatics(AttributeData attributeData) /// public static bool IsInRequiresScope(this ISymbol member, string attributeName, [NotNullWhen(true)] out AttributeData? requiresAttribute) { - // Requires attribute on a type does not silence warnings that originate - // from the type directly. We also only check the containing type for members - // below, not of nested types. - if (member is ITypeSymbol) + // For type symbols, check if the type itself has the requires attribute + // This allows a type's RUC to suppress warnings about the type using other RUC types + // (e.g., in generic constraints, base types, etc.) + if (member is ITypeSymbol typeSymbol) { + if (typeSymbol.TryGetAttribute(attributeName, out requiresAttribute)) + return true; + requiresAttribute = null; return false; } @@ -78,7 +81,7 @@ public static bool IsInRequiresScope(this ISymbol member, string attributeName, if (member.ContainingType is ITypeSymbol containingType && containingType.TryGetAttribute(attributeName, out requiresAttribute)) { if (!ExcludeStatics(requiresAttribute)) - return true; + return true; if (!member.IsStatic) return true; diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 6eda4e165992f0..1ab87a9f676023 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1713,7 +1713,13 @@ protected void MarkField(FieldReference reference, DependencyInfo reason, in Mes void ReportWarningsForReflectionAccess(in MessageOrigin origin, MethodDefinition method, DependencyKind dependencyKind) { if (Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(origin.Provider, out _)) - return; + { + // Warnings for attributes on type declarations are not suppressed by RUC on the type. + // This allows attribute warnings to still be reported when attributes are applied to RUC types. + if (dependencyKind is not (DependencyKind.AttributeConstructor or DependencyKind.AttributeProperty) || + origin.Provider is not TypeDefinition) + return; + } bool isReflectionAccessCoveredByRUC; bool isCompilerGenerated = CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(method); @@ -1791,6 +1797,11 @@ void ReportWarningsForTypeHierarchyReflectionAccess(IMemberDefinition member, Me var type = origin.Provider as TypeDefinition; Debug.Assert(type != null); + // Allow RUC on the type itself to suppress these warnings. + // This allows a type with RUC to suppress warnings about the type using other RUC types. + if (Annotations.TryGetLinkerAttribute(type, out _)) + return; + static bool IsDeclaredWithinType(IMemberDefinition member, TypeDefinition type) { while ((member = member.DeclaringType) != null) diff --git a/src/tools/illink/src/linker/Linker/Annotations.cs b/src/tools/illink/src/linker/Linker/Annotations.cs index 1518bd5407c088..c72e5adebf6c77 100644 --- a/src/tools/illink/src/linker/Linker/Annotations.cs +++ b/src/tools/illink/src/linker/Linker/Annotations.cs @@ -652,6 +652,10 @@ internal bool ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(ICustomA if (originMember is FieldDefinition field) return DoesFieldRequireUnreferencedCode(field, out attribute); + if (originMember is TypeDefinition type && + TryGetLinkerAttribute(type, out attribute)) + return true; + if (originMember is not IMemberDefinition member) return false; diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs index e18798ba61d412..b002740c64fea8 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs @@ -237,8 +237,6 @@ static void TestDerivedTypeWithOpenGenericOnBaseWithRUCOnDerived() { new DerivedTypeWithOpenGenericOnBaseWithRUCOnDerived(); } - [ExpectedWarning("IL2091", nameof(BaseTypeWithOpenGenericDAMT))] - [ExpectedWarning("IL2091", nameof(IGenericInterfaceTypeWithRequirements))] [RequiresUnreferencedCode("RUC")] class DerivedTypeWithOpenGenericOnBaseWithRUCOnDerived : BaseTypeWithOpenGenericDAMT, IGenericInterfaceTypeWithRequirements { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeHierarchyReflectionWarnings.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeHierarchyReflectionWarnings.cs index eb28b22136631a..2395e1b5f28d5a 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeHierarchyReflectionWarnings.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeHierarchyReflectionWarnings.cs @@ -627,16 +627,13 @@ public AnnotatedRUCPublicMethods() { } [Kept] [KeptAttributeAttribute(typeof(RequiresUnreferencedCodeAttribute))] - [ExpectedWarning("IL2112", "--RUC on AnnotatedRUCPublicMethods.RUCMethod--")] [RequiresUnreferencedCode("--RUC on AnnotatedRUCPublicMethods.RUCMethod--")] public void RUCMethod() { } [Kept] - [ExpectedWarning("IL2112", "--AnnotatedRUCPublicMethods--")] public void Method() { } [Kept] - [ExpectedWarning("IL2112", "--AnnotatedRUCPublicMethods--")] public static void StaticMethod() { } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnClass.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnClass.cs index 714bf9a2598fc2..50c37b416bfba8 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnClass.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnClass.cs @@ -36,6 +36,8 @@ public static void Main() AttributeParametersAndProperties.Test(); MembersOnClassWithRequires.Test(); ConstFieldsOnClassWithRequires.Test(); + RequiresOnClassWithAttributes.Test(); + RequiresOnAttributeTypeWithDAM.Test(); // Instantiate classes so linker warnings match analyzer warnings new TestUnconditionalSuppressMessage(); @@ -783,13 +785,10 @@ class BaseForDAMAnnotatedClass [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] [RequiresUnreferencedCode("This class is dangerous")] [RequiresDynamicCode("This class is dangerous")] - [ExpectedWarning("IL2113", "BaseForDAMAnnotatedClass.baseField")] class DAMAnnotatedClass : BaseForDAMAnnotatedClass { - [ExpectedWarning("IL2112", "DAMAnnotatedClass.publicField")] public static int publicField; - [ExpectedWarning("IL2112", "DAMAnnotatedClass.privatefield")] static int privatefield; } @@ -1213,23 +1212,17 @@ class BaseForDAMAnnotatedClass [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] [RequiresUnreferencedCode("This class is dangerous")] [RequiresDynamicCode("This class is dangerous")] - [ExpectedWarning("IL2113", "BaseForDAMAnnotatedClass.baseProperty.get")] - [ExpectedWarning("IL2113", "BaseForDAMAnnotatedClass.baseProperty.set")] class DAMAnnotatedClass : BaseForDAMAnnotatedClass { public static int publicProperty { - [ExpectedWarning("IL2112", "DAMAnnotatedClass.publicProperty.get")] get; - [ExpectedWarning("IL2112", "DAMAnnotatedClass.publicProperty.set")] set; } static int privateProperty { - [ExpectedWarning("IL2112", "DAMAnnotatedClass.privateProperty.get")] get; - [ExpectedWarning("IL2112", "DAMAnnotatedClass.privateProperty.set")] set; } } @@ -1391,15 +1384,11 @@ public class ClassWithAttribute } } - // This warning should ideally be suppressed by the RUC on the type: - [UnexpectedWarning("IL2091", Tool.All, "https://github.com/dotnet/runtime/issues/108523")] [RequiresUnreferencedCode("--GenericClassWithWarningWithRequires--")] public class GenericClassWithWarningWithRequires : RequiresAll { } - // This warning should ideally be suppressed by the RUC on the type: - [UnexpectedWarning("IL2091", Tool.All, "https://github.com/dotnet/runtime/issues/108523")] [RequiresUnreferencedCode("--ClassWithWarningWithRequires--")] public class ClassWithWarningWithRequires : RequiresAll { @@ -1409,19 +1398,17 @@ public class ClassWithWarningWithRequires : RequiresAll class ClassWithWarningOnGenericArgumentConstructor : RequiresNew { // Analyzer misses warning for implicit call to the base constructor, because the new constraint is not checked in dataflow. - [ExpectedWarning("IL2026", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/108507")] + [ExpectedWarning("IL2026", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/118869")] public ClassWithWarningOnGenericArgumentConstructor() { } } - [UnexpectedWarning("IL2026", Tool.All, "https://github.com/dotnet/runtime/issues/108507")] [RequiresUnreferencedCode("--ClassWithWarningOnGenericArgumentConstructorWithRequires--")] class ClassWithWarningOnGenericArgumentConstructorWithRequires : RequiresNew { } - [UnexpectedWarning("IL2091", Tool.All, "https://github.com/dotnet/runtime/issues/108523")] [RequiresUnreferencedCode("--GenericAnnotatedWithWarningWithRequires--")] public class GenericAnnotatedWithWarningWithRequires<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TFields> : RequiresAll { @@ -1500,5 +1487,78 @@ public static void Test() TestClassUsingFieldInAttribute(); } } + + class RequiresOnClassWithAttributes + { + public static void Test() + { + typeof(RUCTypeWithAttribute).GetCustomAttributes(); + } + + // Requires on the type should not suppress attribute warnings + [ExpectedWarning("IL2026", nameof(AttributeWithRUCAttribute))] + [ExpectedWarning("IL2026", nameof(AttributeWithRUCPropertyAttribute))] + [RequiresUnreferencedCode(nameof(RUCTypeWithAttribute))] + [AttributeWithRUCProperty(Property = 4)] + [AttributeWithRUC] + class RUCTypeWithAttribute + { + } + + class AttributeWithRUCPropertyAttribute : Attribute + { + public int Property { get; [RequiresUnreferencedCode(nameof(AttributeWithRUCPropertyAttribute.Property))] set; } + } + + [RequiresUnreferencedCode(nameof(AttributeWithRUCAttribute))] + class AttributeWithRUCAttribute : Attribute + { + } + } + + public class RequiresOnAttributeTypeWithDAM + { + public static void Test( + AttributeWithDAMAttribute instance = null, + DerivedAttributeWithDAMAttribute derivedInstance = null, + DerivedAttributePropertyRequiresAttribute derivedInstanceWithProperty = null) + { + instance.GetType().GetConstructors(); + derivedInstance.GetType().GetConstructors(); + derivedInstanceWithProperty.GetType().GetMethods(); + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [RequiresUnreferencedCode("Suppresses DAM type hierarchy warnings on the ctor")] + public class AttributeWithDAMAttribute : Attribute + { + [RequiresUnreferencedCode(nameof(AttributeWithDAMAttribute))] + public AttributeWithDAMAttribute() + { + } + } + + [RequiresUnreferencedCode(nameof(AttributeWithRequiresAttribute))] + public class AttributeWithRequiresAttribute : Attribute + { + } + + [RequiresUnreferencedCode("Suppresses DAM type hierarchy warnings on the type")] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public class DerivedAttributeWithDAMAttribute : AttributeWithRequiresAttribute + { + } + + public class AttributePropertyRequiresAttribute : Attribute + { + public int Property { get; [RequiresUnreferencedCode(nameof(AttributePropertyRequiresAttribute.Property))] set; } + } + + [RequiresUnreferencedCode("Suppresses DAM type hierarchy warnings on the type")] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + public class DerivedAttributePropertyRequiresAttribute : Attribute + { + } + } } }