From d51f9d0a4b26cb77a72b45d0ce59aa5f296562db Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 2 Sep 2025 15:42:28 -0700 Subject: [PATCH 01/12] Fix new constraint warning --- .../DynamicallyAccessedMembersAnalyzer.cs | 5 +- .../RequiresAnalyzerBase.cs | 50 ---------------- .../TrimAnalysis/GenericArgumentDataFlow.cs | 60 ++++++++++++++----- .../TrimAnalysis/HandleCallAction.cs | 4 +- ...RequireDynamicallyAccessedMembersAction.cs | 15 ++++- .../TrimAnalysisAssignmentPattern.cs | 2 +- ...TrimAnalysisGenericInstantiationPattern.cs | 32 +++++----- .../TrimAnalysisMethodCallPattern.cs | 2 +- .../TrimAnalysis/TrimAnalysisVisitor.cs | 8 ++- .../RequiresCapability/RequiresOnClass.cs | 3 +- 10 files changed, 89 insertions(+), 92 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 6f327374ee2cef..5b6f12f012c4f5 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using ILLink.RoslynAnalyzer.TrimAnalysis; +using ILLink.RoslynAnalyzer.DataFlow; using ILLink.Shared; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; @@ -143,10 +144,10 @@ public override void Initialize(AnalysisContext context) var typeNameResolver = new TypeNameResolver(context.Compilation); if (type.BaseType is INamedTypeSymbol baseType) - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(typeNameResolver, location, baseType, context.ReportDiagnostic); + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(dataFlowAnalyzerContext, FeatureContext.None, typeNameResolver, type, location, baseType, context.ReportDiagnostic); foreach (var interfaceType in type.Interfaces) - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(typeNameResolver, location, interfaceType, context.ReportDiagnostic); + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(dataFlowAnalyzerContext, FeatureContext.None, typeNameResolver, type, location, interfaceType, context.ReportDiagnostic); DynamicallyAccessedMembersTypeHierarchy.ApplyDynamicallyAccessedMembersToTypeHierarchy(typeNameResolver, location, type, context.ReportDiagnostic); }, SymbolKind.NamedType); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index b501fe4e6480a5..3b0c0cd93a8626 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -74,56 +74,6 @@ public override void Initialize(AnalysisContext context) CheckMatchingAttributesInInterfaces(symbolAnalysisContext, typeSymbol); }, SymbolKind.NamedType); - context.RegisterSyntaxNodeAction(syntaxNodeAnalysisContext => - { - var model = syntaxNodeAnalysisContext.SemanticModel; - if (syntaxNodeAnalysisContext.ContainingSymbol is not ISymbol containingSymbol || containingSymbol.IsInRequiresScope(RequiresAttributeName, out _)) - return; - - GenericNameSyntax genericNameSyntaxNode = (GenericNameSyntax)syntaxNodeAnalysisContext.Node; - var typeParams = ImmutableArray.Empty; - var typeArgs = ImmutableArray.Empty; - switch (model.GetSymbolInfo(genericNameSyntaxNode).Symbol) - { - case INamedTypeSymbol typeSymbol: - typeParams = typeSymbol.TypeParameters; - typeArgs = typeSymbol.TypeArguments; - break; - - case IMethodSymbol methodSymbol: - typeParams = methodSymbol.TypeParameters; - typeArgs = methodSymbol.TypeArguments; - break; - - default: - return; - } - - for (int i = 0; i < typeParams.Length; i++) - { - var typeParam = typeParams[i]; - var typeArg = typeArgs[i]; - if (!typeParam.HasConstructorConstraint || - typeArg is not INamedTypeSymbol { InstanceConstructors: { } typeArgCtors }) - continue; - - foreach (var instanceCtor in typeArgCtors) - { - if (instanceCtor.Arity > 0) - continue; - - if (instanceCtor.DoesMemberRequire(RequiresAttributeName, out var requiresAttribute) && - VerifyAttributeArguments(requiresAttribute)) - { - syntaxNodeAnalysisContext.ReportDiagnostic(Diagnostic.Create(RequiresDiagnosticRule, - syntaxNodeAnalysisContext.Node.GetLocation(), - instanceCtor.GetDisplayName(), - (string)requiresAttribute.ConstructorArguments[0].Value!, - GetUrlFromAttribute(requiresAttribute))); - } - } - } - }, SyntaxKind.GenericName); foreach (var extraSyntaxNodeAction in ExtraSyntaxNodeActions) context.RegisterSyntaxNodeAction(extraSyntaxNodeAction.Action, extraSyntaxNodeAction.SyntaxKind); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs index 95879fa90f91f6..a4ca521e18464c 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs @@ -4,42 +4,48 @@ using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using Microsoft.CodeAnalysis; +using ILLink.Shared; +using ILLink.RoslynAnalyzer.DataFlow; namespace ILLink.RoslynAnalyzer.TrimAnalysis { internal static class GenericArgumentDataFlow { - public static void ProcessGenericArgumentDataFlow(TypeNameResolver typeNameResolver, Location location, INamedTypeSymbol type, Action? reportDiagnostic) + public static void ProcessGenericArgumentDataFlow(DataFlowAnalyzerContext context, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, Location location, INamedTypeSymbol type, Action? reportDiagnostic) { while (type is { IsGenericType: true }) { - ProcessGenericArgumentDataFlow(typeNameResolver, location, type.TypeArguments, type.TypeParameters, reportDiagnostic); + ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, type.TypeArguments, type.TypeParameters, reportDiagnostic); type = type.ContainingType; } } - public static void ProcessGenericArgumentDataFlow(TypeNameResolver typeNameResolver, Location location, IMethodSymbol method, Action? reportDiagnostic) + public static void ProcessGenericArgumentDataFlow(DataFlowAnalyzerContext context, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, Location location, IMethodSymbol method, Action? reportDiagnostic) { - ProcessGenericArgumentDataFlow(typeNameResolver, location, method.TypeArguments, method.TypeParameters, reportDiagnostic); + ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, method.TypeArguments, method.TypeParameters, reportDiagnostic); - ProcessGenericArgumentDataFlow(typeNameResolver, location, method.ContainingType, reportDiagnostic); + ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, method.ContainingType, reportDiagnostic); } - public static void ProcessGenericArgumentDataFlow(TypeNameResolver typeNameResolver, Location location, IFieldSymbol field, Action? reportDiagnostic) + public static void ProcessGenericArgumentDataFlow(DataFlowAnalyzerContext context, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, Location location, IFieldSymbol field, Action? reportDiagnostic) { - ProcessGenericArgumentDataFlow(typeNameResolver, location, field.ContainingType, reportDiagnostic); + ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, field.ContainingType, reportDiagnostic); } - public static void ProcessGenericArgumentDataFlow(TypeNameResolver typeNameResolver, Location location, IPropertySymbol property, Action reportDiagnostic) + public static void ProcessGenericArgumentDataFlow(DataFlowAnalyzerContext context, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, Location location, IPropertySymbol property, Action reportDiagnostic) { - ProcessGenericArgumentDataFlow(typeNameResolver, location, property.ContainingType, reportDiagnostic); + ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, property.ContainingType, reportDiagnostic); } private static void ProcessGenericArgumentDataFlow( + DataFlowAnalyzerContext context, + FeatureContext featureContext, TypeNameResolver typeNameResolver, + ISymbol owningSymbol, Location location, ImmutableArray typeArguments, ImmutableArray typeParameters, @@ -49,19 +55,45 @@ private static void ProcessGenericArgumentDataFlow( for (int i = 0; i < typeArguments.Length; i++) { var typeArgument = typeArguments[i]; + var typeParameter = typeParameters[i]; + + // Process new() constraint: if present, check Requires* on the public parameterless constructor + // And that also takes care of any DynamicallyAccessedMembers.PublicParameterlessConstructor. + if (typeParameter.HasConstructorConstraint && typeArgument is INamedTypeSymbol namedTypeArg && namedTypeArg.InstanceConstructors.Length > 0) + { + var paramlessPublicCtor = namedTypeArg.InstanceConstructors.FirstOrDefault(ctor => ctor.Parameters.IsEmpty && ctor.DeclaredAccessibility == Accessibility.Public); + if (paramlessPublicCtor is not null) + { + foreach (var analyzer in context.EnabledRequiresAnalyzers) + { + var attrName = analyzer.RequiresAttributeFullyQualifiedName; + + if (featureContext.IsEnabled(attrName)) + continue; + + analyzer.CheckAndCreateRequiresDiagnostic(paramlessPublicCtor, owningSymbol, incompatibleMembers: ImmutableArray.Empty, diagnosticContext); + } + } + } + + // TODO: avoid duplicate warnings for new() and DAMT.PublicParameterlessConstructor. + // Apply annotations to the generic argument - var genericParameterValue = new GenericParameterValue(typeParameters[i]); - if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) + var genericParameterValue = new GenericParameterValue(typeParameter); + if (context.EnableTrimAnalyzer && + !owningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) && + !featureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute) && + genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) { SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol(typeArgument)!; var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(reportDiagnostic, typeNameResolver, typeHierarchyType: null); - var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(context, featureContext, typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer, owningSymbol); requireDynamicallyAccessedMembersAction.Invoke(genericArgumentValue, genericParameterValue); } // Recursively process generic argument data flow on the generic argument if it itself is generic if (typeArgument is INamedTypeSymbol namedTypeArgument && namedTypeArgument.IsGenericType) - ProcessGenericArgumentDataFlow(typeNameResolver, location, namedTypeArgument, reportDiagnostic); + ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, namedTypeArgument, reportDiagnostic); } } @@ -118,7 +150,7 @@ private static bool RequiresGenericArgumentDataFlow(ImmutableArray _multiValueLattice; public HandleCallAction( + DataFlowAnalyzerContext context, + FeatureContext featureContext, TypeNameResolver typeNameResolver, Location location, ISymbol owningSymbol, @@ -41,7 +43,7 @@ public HandleCallAction( _diagnosticContext = new DiagnosticContext(location, reportDiagnostic); _annotations = FlowAnnotations.Instance; _reflectionAccessAnalyzer = new(reportDiagnostic, typeNameResolver, typeHierarchyType: null); - _requireDynamicallyAccessedMembersAction = new(typeNameResolver, location, reportDiagnostic, _reflectionAccessAnalyzer); + _requireDynamicallyAccessedMembersAction = new(context, featureContext, typeNameResolver, location, reportDiagnostic, _reflectionAccessAnalyzer, _owningSymbol); _multiValueLattice = multiValueLattice; } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs index e8bc38a03dcc98..16680461743c01 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs @@ -7,8 +7,10 @@ using System.Reflection.Metadata; using Microsoft.CodeAnalysis; using ILLink.RoslynAnalyzer.TrimAnalysis; +using ILLink.RoslynAnalyzer.DataFlow; using ILLink.Shared.TypeSystemProxy; using System.Collections.Immutable; +using ILLink.RoslynAnalyzer; namespace ILLink.Shared.TrimAnalysis { @@ -18,19 +20,28 @@ internal partial struct RequireDynamicallyAccessedMembersAction readonly Action? _reportDiagnostic; readonly ReflectionAccessAnalyzer _reflectionAccessAnalyzer; readonly TypeNameResolver _typeNameResolver; + readonly ISymbol _owningSymbol; + readonly DataFlowAnalyzerContext _context; + readonly FeatureContext _featureContext; #pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods #pragma warning disable IDE0060 // Unused parameters - should be removed once methods are actually implemented public RequireDynamicallyAccessedMembersAction( + DataFlowAnalyzerContext context, + FeatureContext featureContext, TypeNameResolver typeNameResolver, Location location, Action? reportDiagnostic, - ReflectionAccessAnalyzer reflectionAccessAnalyzer) + ReflectionAccessAnalyzer reflectionAccessAnalyzer, + ISymbol owningSymbol) { + _context = context; + _featureContext = featureContext; _typeNameResolver = typeNameResolver; _location = location; _reportDiagnostic = reportDiagnostic; _reflectionAccessAnalyzer = reflectionAccessAnalyzer; + _owningSymbol = owningSymbol; _diagnosticContext = new(location, reportDiagnostic); } @@ -40,7 +51,7 @@ public partial bool TryResolveTypeNameAndMark(string typeName, bool needsAssembl if (_reflectionAccessAnalyzer.TryResolveTypeNameAndMark(typeName, diagnosticContext, needsAssemblyName, out ITypeSymbol? foundType)) { if (foundType is INamedTypeSymbol namedType && namedType.IsGenericType) - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(_typeNameResolver, _location, namedType, _reportDiagnostic); + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(_context, _featureContext, _typeNameResolver, _owningSymbol, _location, namedType, _reportDiagnostic); type = new TypeProxy(foundType); return true; diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs index 4f5c6f06baa777..26aa45c8f1eb57 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs @@ -70,7 +70,7 @@ public void ReportDiagnostics(DataFlowAnalyzerContext context, Action reportDiagnostic) { - if (context.EnableTrimAnalyzer && - !OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) && - !FeatureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) + var location = Operation.Syntax.GetLocation(); + var typeNameResolver = new TypeNameResolver(context.Compilation); + + switch (GenericInstantiation) { - var location = Operation.Syntax.GetLocation(); - var typeNameResolver = new TypeNameResolver(context.Compilation); - switch (GenericInstantiation) - { - case INamedTypeSymbol type: - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(typeNameResolver, location, type, reportDiagnostic); - break; + case INamedTypeSymbol type: + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(context, FeatureContext, typeNameResolver, OwningSymbol, location, type, reportDiagnostic); + break; - case IMethodSymbol method: - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(typeNameResolver, location, method, reportDiagnostic); - break; + case IMethodSymbol method: + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(context, FeatureContext, typeNameResolver, OwningSymbol, location, method, reportDiagnostic); + break; - case IFieldSymbol field: - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(typeNameResolver, location, field, reportDiagnostic); - break; - } + case IFieldSymbol field: + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(context, FeatureContext, typeNameResolver, OwningSymbol, location, field, reportDiagnostic); + break; } } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs index 9fc024f46c8f35..adc4e78f29c631 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs @@ -83,7 +83,7 @@ public void ReportDiagnostics(DataFlowAnalyzerContext context, Action multiValueLattice, out MultiValue methodReturnValue) { - var handleCallAction = new HandleCallAction(typeNameResolver, location, owningSymbol, operation, multiValueLattice, reportDiagnostic); + var handleCallAction = new HandleCallAction(dataFlowAnalyzerContext, featureContext, typeNameResolver, location, owningSymbol, operation, multiValueLattice, reportDiagnostic); MethodProxy method = new(calledMethod); var intrinsicId = Intrinsics.GetIntrinsicIdForMethod(method); if (!handleCallAction.Invoke(method, instance, arguments, intrinsicId, out methodReturnValue)) 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 8c6ab3a16c0b1a..469321509fd8f5 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 @@ -1407,8 +1407,7 @@ public class ClassWithWarningWithRequires : RequiresAll [ExpectedWarning("IL2026", "ClassWithRequires()", "--ClassWithRequires--")] 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", "--ClassWithRequires--")] public ClassWithWarningOnGenericArgumentConstructor() { } From a6e0e83b832bfb7406f4fc3fe990f86088dd25e7 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 2 Sep 2025 16:12:49 -0700 Subject: [PATCH 02/12] Avoid duplicate warning, add test --- .../TrimAnalysis/GenericArgumentDataFlow.cs | 7 +++++-- .../RequiresCapability/RequiresOnClass.cs | 20 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs index a4ca521e18464c..9bef0c91307262 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs @@ -76,10 +76,13 @@ private static void ProcessGenericArgumentDataFlow( } } - // TODO: avoid duplicate warnings for new() and DAMT.PublicParameterlessConstructor. + var parameterRequirements = typeParameter.GetDynamicallyAccessedMemberTypes(); + // Avoid duplicate warnings for new() and DAMT.PublicParameterlessConstructor + if (typeParameter.HasConstructorConstraint) + parameterRequirements &= ~DynamicallyAccessedMemberTypes.PublicParameterlessConstructor; // Apply annotations to the generic argument - var genericParameterValue = new GenericParameterValue(typeParameter); + var genericParameterValue = new GenericParameterValue(typeParameter, parameterRequirements); if (context.EnableTrimAnalyzer && !owningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) && !featureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute) && 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 469321509fd8f5..439a665cf0f5dd 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 @@ -1347,6 +1347,10 @@ public class RequiresAll<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTy { } + class RequiresNewAndConstructors<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T> where T : new() + { + } + [RequiresUnreferencedCode("--ClassWithRequires--")] public class ClassWithRequires { @@ -1413,6 +1417,17 @@ public ClassWithWarningOnGenericArgumentConstructor() } } + [ExpectedWarning("IL2026", "ClassWithRequires()", "--ClassWithRequires--")] + [ExpectedWarning("IL2026", "ClassWithRequires()", "--ClassWithRequires--", Tool.Trimmer, "https://github.com/dotnet/runtime/issues/119290")] + class ClassWithWarningOnGenericArgumentConstructor_NewAndAnnotation : RequiresNewAndConstructors + { + [ExpectedWarning("IL2026", "--ClassWithRequires--")] + [ExpectedWarning("IL2026", "--ClassWithRequires--", Tool.Trimmer, "https://github.com/dotnet/runtime/issues/119290")] + public ClassWithWarningOnGenericArgumentConstructor_NewAndAnnotation() + { + } + } + [UnexpectedWarning("IL2026", Tool.All, "https://github.com/dotnet/runtime/issues/108507")] [RequiresUnreferencedCode("--ClassWithWarningOnGenericArgumentConstructorWithRequires--")] class ClassWithWarningOnGenericArgumentConstructorWithRequires : RequiresNew @@ -1448,8 +1463,9 @@ public static void Test(ClassWithRequires inst = null) var g = new GenericClassWithWarningWithRequires(); var h = new ClassWithWarningWithRequires(); var j = new ClassWithWarningOnGenericArgumentConstructor(); - var k = new ClassWithWarningOnGenericArgumentConstructorWithRequires(); - var l = new GenericAnnotatedWithWarningWithRequires(); + var k = new ClassWithWarningOnGenericArgumentConstructor_NewAndAnnotation(); + var l = new ClassWithWarningOnGenericArgumentConstructorWithRequires(); + var m = new GenericAnnotatedWithWarningWithRequires(); } } From 354ee1d5a6f9b937520521dd8289e3b4c66541f6 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 2 Sep 2025 16:23:11 -0700 Subject: [PATCH 03/12] Move arguments to instance fields in GenericArgumentDataFlow --- .../DynamicallyAccessedMembersAnalyzer.cs | 5 +- .../TrimAnalysis/GenericArgumentDataFlow.cs | 73 ++++++++++++------- ...RequireDynamicallyAccessedMembersAction.cs | 5 +- ...TrimAnalysisGenericInstantiationPattern.cs | 7 +- 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 5b6f12f012c4f5..610a1af73aea21 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -143,11 +143,12 @@ public override void Initialize(AnalysisContext context) var location = GetPrimaryLocation(type.Locations); var typeNameResolver = new TypeNameResolver(context.Compilation); + var genericArgumentDataFlow = new GenericArgumentDataFlow(dataFlowAnalyzerContext, FeatureContext.None, typeNameResolver, type, location, context.ReportDiagnostic); if (type.BaseType is INamedTypeSymbol baseType) - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(dataFlowAnalyzerContext, FeatureContext.None, typeNameResolver, type, location, baseType, context.ReportDiagnostic); + genericArgumentDataFlow.ProcessGenericArgumentDataFlow(baseType); foreach (var interfaceType in type.Interfaces) - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(dataFlowAnalyzerContext, FeatureContext.None, typeNameResolver, type, location, interfaceType, context.ReportDiagnostic); + genericArgumentDataFlow.ProcessGenericArgumentDataFlow(interfaceType); DynamicallyAccessedMembersTypeHierarchy.ApplyDynamicallyAccessedMembersToTypeHierarchy(typeNameResolver, location, type, context.ReportDiagnostic); }, SymbolKind.NamedType); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs index 9bef0c91307262..29b9267c5dfa6c 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs @@ -13,45 +13,62 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis { - internal static class GenericArgumentDataFlow + internal readonly struct GenericArgumentDataFlow { - public static void ProcessGenericArgumentDataFlow(DataFlowAnalyzerContext context, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, Location location, INamedTypeSymbol type, Action? reportDiagnostic) + private readonly DataFlowAnalyzerContext _context; + private readonly FeatureContext _featureContext; + private readonly TypeNameResolver _typeNameResolver; + private readonly ISymbol _owningSymbol; + private readonly Location _location; + private readonly Action? _reportDiagnostic; + + public GenericArgumentDataFlow( + DataFlowAnalyzerContext context, + FeatureContext featureContext, + TypeNameResolver typeNameResolver, + ISymbol owningSymbol, + Location location, + Action? reportDiagnostic) + { + _context = context; + _featureContext = featureContext; + _typeNameResolver = typeNameResolver; + _owningSymbol = owningSymbol; + _location = location; + _reportDiagnostic = reportDiagnostic; + } + + public void ProcessGenericArgumentDataFlow(INamedTypeSymbol type) { while (type is { IsGenericType: true }) { - ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, type.TypeArguments, type.TypeParameters, reportDiagnostic); + ProcessGenericArgumentDataFlow(type.TypeArguments, type.TypeParameters); type = type.ContainingType; } } - public static void ProcessGenericArgumentDataFlow(DataFlowAnalyzerContext context, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, Location location, IMethodSymbol method, Action? reportDiagnostic) + public void ProcessGenericArgumentDataFlow(IMethodSymbol method) { - ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, method.TypeArguments, method.TypeParameters, reportDiagnostic); + ProcessGenericArgumentDataFlow(method.TypeArguments, method.TypeParameters); - ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, method.ContainingType, reportDiagnostic); + ProcessGenericArgumentDataFlow(method.ContainingType); } - public static void ProcessGenericArgumentDataFlow(DataFlowAnalyzerContext context, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, Location location, IFieldSymbol field, Action? reportDiagnostic) + public void ProcessGenericArgumentDataFlow(IFieldSymbol field) { - ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, field.ContainingType, reportDiagnostic); + ProcessGenericArgumentDataFlow(field.ContainingType); } - public static void ProcessGenericArgumentDataFlow(DataFlowAnalyzerContext context, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, Location location, IPropertySymbol property, Action reportDiagnostic) + public void ProcessGenericArgumentDataFlow(IPropertySymbol property) { - ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, property.ContainingType, reportDiagnostic); + ProcessGenericArgumentDataFlow(property.ContainingType); } - private static void ProcessGenericArgumentDataFlow( - DataFlowAnalyzerContext context, - FeatureContext featureContext, - TypeNameResolver typeNameResolver, - ISymbol owningSymbol, - Location location, + private void ProcessGenericArgumentDataFlow( ImmutableArray typeArguments, - ImmutableArray typeParameters, - Action? reportDiagnostic) + ImmutableArray typeParameters) { - var diagnosticContext = new DiagnosticContext(location, reportDiagnostic); + var diagnosticContext = new DiagnosticContext(_location, _reportDiagnostic); for (int i = 0; i < typeArguments.Length; i++) { var typeArgument = typeArguments[i]; @@ -64,14 +81,14 @@ private static void ProcessGenericArgumentDataFlow( var paramlessPublicCtor = namedTypeArg.InstanceConstructors.FirstOrDefault(ctor => ctor.Parameters.IsEmpty && ctor.DeclaredAccessibility == Accessibility.Public); if (paramlessPublicCtor is not null) { - foreach (var analyzer in context.EnabledRequiresAnalyzers) + foreach (var analyzer in _context.EnabledRequiresAnalyzers) { var attrName = analyzer.RequiresAttributeFullyQualifiedName; - if (featureContext.IsEnabled(attrName)) + if (_featureContext.IsEnabled(attrName)) continue; - analyzer.CheckAndCreateRequiresDiagnostic(paramlessPublicCtor, owningSymbol, incompatibleMembers: ImmutableArray.Empty, diagnosticContext); + analyzer.CheckAndCreateRequiresDiagnostic(paramlessPublicCtor, _owningSymbol, incompatibleMembers: ImmutableArray.Empty, diagnosticContext); } } } @@ -83,20 +100,20 @@ private static void ProcessGenericArgumentDataFlow( // Apply annotations to the generic argument var genericParameterValue = new GenericParameterValue(typeParameter, parameterRequirements); - if (context.EnableTrimAnalyzer && - !owningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) && - !featureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute) && + if (_context.EnableTrimAnalyzer && + !_owningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) && + !_featureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute) && genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) { SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol(typeArgument)!; - var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(reportDiagnostic, typeNameResolver, typeHierarchyType: null); - var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(context, featureContext, typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer, owningSymbol); + var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(_reportDiagnostic, _typeNameResolver, typeHierarchyType: null); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(_context, _featureContext, _typeNameResolver, _location, _reportDiagnostic, reflectionAccessAnalyzer, _owningSymbol); requireDynamicallyAccessedMembersAction.Invoke(genericArgumentValue, genericParameterValue); } // Recursively process generic argument data flow on the generic argument if it itself is generic if (typeArgument is INamedTypeSymbol namedTypeArgument && namedTypeArgument.IsGenericType) - ProcessGenericArgumentDataFlow(context, featureContext, typeNameResolver, owningSymbol, location, namedTypeArgument, reportDiagnostic); + ProcessGenericArgumentDataFlow(namedTypeArgument); } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs index 16680461743c01..95550b015254f8 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs @@ -51,7 +51,10 @@ public partial bool TryResolveTypeNameAndMark(string typeName, bool needsAssembl if (_reflectionAccessAnalyzer.TryResolveTypeNameAndMark(typeName, diagnosticContext, needsAssemblyName, out ITypeSymbol? foundType)) { if (foundType is INamedTypeSymbol namedType && namedType.IsGenericType) - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(_context, _featureContext, _typeNameResolver, _owningSymbol, _location, namedType, _reportDiagnostic); + { + var genericArgumentDataFlow = new GenericArgumentDataFlow(_context, _featureContext, _typeNameResolver, _owningSymbol, _location, _reportDiagnostic); + genericArgumentDataFlow.ProcessGenericArgumentDataFlow(namedType); + } type = new TypeProxy(foundType); return true; diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisGenericInstantiationPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisGenericInstantiationPattern.cs index 1de1732bc66d79..7926ca77f964bf 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisGenericInstantiationPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisGenericInstantiationPattern.cs @@ -49,19 +49,20 @@ public void ReportDiagnostics(DataFlowAnalyzerContext context, Action Date: Sat, 6 Sep 2025 00:20:35 +0000 Subject: [PATCH 04/12] Remove analyzer DAM warning on types --- .../DataFlowAnalyzerContext.cs | 17 +++++-- .../DynamicallyAccessedMembersAnalyzer.cs | 8 +-- .../RequiresAnalyzerBase.cs | 39 +++++++++++++- .../RequiresUnreferencedCodeAnalyzer.cs | 36 ++++++++++++- .../FeatureCheckReturnValuePattern.cs | 2 +- .../TrimAnalysis/GenericArgumentDataFlow.cs | 51 ++++--------------- .../TrimAnalysis/HandleCallAction.cs | 4 +- ...RequireDynamicallyAccessedMembersAction.cs | 8 +-- .../TrimAnalysisAssignmentPattern.cs | 4 +- ...TrimAnalysisGenericInstantiationPattern.cs | 26 ++++++---- .../TrimAnalysisMethodCallPattern.cs | 4 +- .../TrimAnalysisReflectionAccessPattern.cs | 2 +- .../TrimAnalysis/TrimAnalysisVisitor.cs | 10 ++-- .../DataFlow/GenericParameterDataFlow.cs | 8 +-- .../GenericParameterWarningLocation.cs | 1 - .../RequiresCapability/RequiresOnClass.cs | 10 ---- 16 files changed, 130 insertions(+), 100 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlowAnalyzerContext.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlowAnalyzerContext.cs index 2d22221d609e59..e6a9af80c1deaf 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlowAnalyzerContext.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlowAnalyzerContext.cs @@ -23,34 +23,41 @@ public ImmutableArray GetSpecialIncompatibleMembers(RequiresAnalyzerBas public Compilation Compilation { get; } - public readonly bool EnableTrimAnalyzer { get; } + public readonly RequiresUnreferencedCodeAnalyzer? TrimAnalyzer { get; } - public readonly bool AnyAnalyzersEnabled => EnableTrimAnalyzer || _enabledAnalyzers.Count > 0; + public readonly bool AnyAnalyzersEnabled => TrimAnalyzer is not null || _enabledAnalyzers.Count > 0; private DataFlowAnalyzerContext( Dictionary> enabledAnalyzers, - bool enableTrimAnalyzer, + RequiresUnreferencedCodeAnalyzer? trimAnalyzer, Compilation compilation) { _enabledAnalyzers = enabledAnalyzers; - EnableTrimAnalyzer = enableTrimAnalyzer; + TrimAnalyzer = trimAnalyzer; Compilation = compilation; } public static DataFlowAnalyzerContext Create(AnalyzerOptions options, Compilation compilation, ImmutableArray requiresAnalyzers) { var enabledAnalyzers = new Dictionary>(); + RequiresUnreferencedCodeAnalyzer? trimAnalyzer = null; + foreach (var analyzer in requiresAnalyzers) { if (analyzer.IsAnalyzerEnabled(options)) { var incompatibleMembers = analyzer.GetSpecialIncompatibleMembers(compilation); enabledAnalyzers.Add(analyzer, incompatibleMembers); + + if (analyzer is RequiresUnreferencedCodeAnalyzer rucAnalyzer) + { + trimAnalyzer = rucAnalyzer; + } } } return new DataFlowAnalyzerContext( enabledAnalyzers, - options.IsMSBuildPropertyValueTrue(MSBuildPropertyOptionNames.EnableTrimAnalyzer), + trimAnalyzer, compilation); } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 610a1af73aea21..13d84874e791de 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -127,7 +127,7 @@ public override void Initialize(AnalysisContext context) }); // Remaining actions are only for DynamicallyAccessedMembers analysis. - if (!dataFlowAnalyzerContext.EnableTrimAnalyzer) + if (dataFlowAnalyzerContext.TrimAnalyzer is null) return; // Examine generic instantiations in base types and interface list @@ -143,12 +143,6 @@ public override void Initialize(AnalysisContext context) var location = GetPrimaryLocation(type.Locations); var typeNameResolver = new TypeNameResolver(context.Compilation); - var genericArgumentDataFlow = new GenericArgumentDataFlow(dataFlowAnalyzerContext, FeatureContext.None, typeNameResolver, type, location, context.ReportDiagnostic); - if (type.BaseType is INamedTypeSymbol baseType) - genericArgumentDataFlow.ProcessGenericArgumentDataFlow(baseType); - - foreach (var interfaceType in type.Interfaces) - genericArgumentDataFlow.ProcessGenericArgumentDataFlow(interfaceType); DynamicallyAccessedMembersTypeHierarchy.ApplyDynamicallyAccessedMembersToTypeHierarchy(typeNameResolver, location, type, context.ReportDiagnostic); }, SymbolKind.NamedType); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index 3b0c0cd93a8626..ba93565dcb25dd 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using ILLink.RoslynAnalyzer.DataFlow; +using ILLink.RoslynAnalyzer.TrimAnalysis; using ILLink.Shared; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; @@ -34,6 +35,34 @@ public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer private protected abstract DiagnosticDescriptor RequiresOnStaticCtor { get; } private protected abstract DiagnosticDescriptor RequiresOnEntryPoint { get; } + internal virtual void ProcessGenericInstantiation( + ITypeSymbol typeArgument, + ITypeParameterSymbol typeParameter, + FeatureContext featureContext, + TypeNameResolver typeNameResolver, + ISymbol owningSymbol, + Location location, + Action? reportDiagnostic) + { + // Check constructor constraint (new()) + if (typeParameter.HasConstructorConstraint) + { + // Check if this type has a public parameterless constructor + var namedTypeSymbol = typeArgument as INamedTypeSymbol; + var publicParameterlessConstructor = namedTypeSymbol?.InstanceConstructors.FirstOrDefault(c => c.Parameters.IsEmpty && c.DeclaredAccessibility == Accessibility.Public); + + if (publicParameterlessConstructor != null) + { + var diagnosticContext = new DiagnosticContext(location, reportDiagnostic); + CheckAndCreateRequiresDiagnostic( + publicParameterlessConstructor, + owningSymbol, + ImmutableArray.Empty, + diagnosticContext); + } + } + } + private protected virtual ImmutableArray<(Action Action, SyntaxKind[] SyntaxKind)> ExtraSyntaxNodeActions { get; } = ImmutableArray<(Action Action, SyntaxKind[] SyntaxKind)>.Empty; private protected virtual ImmutableArray<(Action Action, SymbolKind[] SymbolKind)> ExtraSymbolActions { get; } = ImmutableArray<(Action Action, SymbolKind[] SymbolKind)>.Empty; private protected virtual ImmutableArray> ExtraCompilationActions { get; } = ImmutableArray>.Empty; @@ -74,7 +103,6 @@ public override void Initialize(AnalysisContext context) CheckMatchingAttributesInInterfaces(symbolAnalysisContext, typeSymbol); }, SymbolKind.NamedType); - foreach (var extraSyntaxNodeAction in ExtraSyntaxNodeActions) context.RegisterSyntaxNodeAction(extraSyntaxNodeAction.Action, extraSyntaxNodeAction.SyntaxKind); @@ -175,6 +203,15 @@ private void AnalyzeImplicitBaseCtor(SymbolAnalysisContext context) implicitCtor, ImmutableArray.Empty, diagnosticContext); + + var dataFlowAnalyzerContext = DataFlowAnalyzerContext.Create(context.Options, context.Compilation, ImmutableArray.Create(this)); + var typeNameResolver = new TypeNameResolver(context.Compilation); + var genericArgumentDataFlow = new GenericArgumentDataFlow(this, FeatureContext.None, typeNameResolver, implicitCtor, typeSymbol.Locations[0], context.ReportDiagnostic); + if (typeSymbol.BaseType is INamedTypeSymbol baseType) + genericArgumentDataFlow.ProcessGenericArgumentDataFlow(baseType); + + foreach (var interfaceType in typeSymbol.Interfaces) + genericArgumentDataFlow.ProcessGenericArgumentDataFlow(interfaceType); } [Flags] diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs index 09d2625d56b0aa..ed1dfe1f069dae 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs @@ -3,8 +3,12 @@ using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using ILLink.RoslynAnalyzer.DataFlow; +using ILLink.RoslynAnalyzer.TrimAnalysis; using ILLink.Shared; +using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; using Microsoft.CodeAnalysis; @@ -27,8 +31,10 @@ public sealed class RequiresUnreferencedCodeAnalyzer : RequiresAnalyzerBase private static readonly DiagnosticDescriptor s_referenceNotMarkedIsTrimmableRule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.ReferenceNotMarkedIsTrimmable); + private static readonly DiagnosticDescriptor s_dynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameterRule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter); + public override ImmutableArray SupportedDiagnostics => - ImmutableArray.Create(s_makeGenericMethodRule, s_makeGenericTypeRule, s_requiresUnreferencedCodeRule, s_requiresUnreferencedCodeAttributeMismatch, s_requiresUnreferencedCodeOnStaticCtor, s_requiresUnreferencedCodeOnEntryPoint, s_referenceNotMarkedIsTrimmableRule); + ImmutableArray.Create(s_makeGenericMethodRule, s_makeGenericTypeRule, s_requiresUnreferencedCodeRule, s_requiresUnreferencedCodeAttributeMismatch, s_requiresUnreferencedCodeOnStaticCtor, s_requiresUnreferencedCodeOnEntryPoint, s_referenceNotMarkedIsTrimmableRule, s_dynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameterRule); private protected override string RequiresAttributeName => RequiresUnreferencedCodeAttribute; @@ -93,5 +99,33 @@ protected override bool VerifyAttributeArguments(AttributeData attribute) => protected override string GetMessageFromAttribute(AttributeData? requiresAttribute) => RequiresUnreferencedCodeUtils.GetMessageFromAttribute(requiresAttribute); + + internal override void ProcessGenericInstantiation( + ITypeSymbol typeArgument, + ITypeParameterSymbol typeParameter, + FeatureContext featureContext, + TypeNameResolver typeNameResolver, + ISymbol owningSymbol, + Location location, + Action? reportDiagnostic) + { + base.ProcessGenericInstantiation(typeArgument, typeParameter, featureContext, typeNameResolver, owningSymbol, location, reportDiagnostic); + + var parameterRequirements = typeParameter.GetDynamicallyAccessedMemberTypes(); + // Avoid duplicate warnings for new() and DAMT.PublicParameterlessConstructor + if (typeParameter.HasConstructorConstraint) + parameterRequirements &= ~DynamicallyAccessedMemberTypes.PublicParameterlessConstructor; + + var genericParameterValue = new GenericParameterValue(typeParameter, parameterRequirements); + if (!owningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) && + !featureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute) && + genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) + { + SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol(typeArgument)!; + var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(reportDiagnostic, typeNameResolver, typeHierarchyType: null); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(this, featureContext, typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer, owningSymbol); + requireDynamicallyAccessedMembersAction.Invoke(genericArgumentValue, genericParameterValue); + } + } } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs index c725339282cb13..ed69b463b7a67f 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs @@ -34,7 +34,7 @@ public void ReportDiagnostics(DataFlowAnalyzerContext context, Action? _reportDiagnostic; public GenericArgumentDataFlow( - DataFlowAnalyzerContext context, + RequiresAnalyzerBase? analyzer, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, Location location, Action? reportDiagnostic) { - _context = context; + _analyzer = analyzer; _featureContext = featureContext; _typeNameResolver = typeNameResolver; _owningSymbol = owningSymbol; @@ -68,48 +68,19 @@ private void ProcessGenericArgumentDataFlow( ImmutableArray typeArguments, ImmutableArray typeParameters) { - var diagnosticContext = new DiagnosticContext(_location, _reportDiagnostic); for (int i = 0; i < typeArguments.Length; i++) { var typeArgument = typeArguments[i]; var typeParameter = typeParameters[i]; - // Process new() constraint: if present, check Requires* on the public parameterless constructor - // And that also takes care of any DynamicallyAccessedMembers.PublicParameterlessConstructor. - if (typeParameter.HasConstructorConstraint && typeArgument is INamedTypeSymbol namedTypeArg && namedTypeArg.InstanceConstructors.Length > 0) - { - var paramlessPublicCtor = namedTypeArg.InstanceConstructors.FirstOrDefault(ctor => ctor.Parameters.IsEmpty && ctor.DeclaredAccessibility == Accessibility.Public); - if (paramlessPublicCtor is not null) - { - foreach (var analyzer in _context.EnabledRequiresAnalyzers) - { - var attrName = analyzer.RequiresAttributeFullyQualifiedName; - - if (_featureContext.IsEnabled(attrName)) - continue; - - analyzer.CheckAndCreateRequiresDiagnostic(paramlessPublicCtor, _owningSymbol, incompatibleMembers: ImmutableArray.Empty, diagnosticContext); - } - } - } - - var parameterRequirements = typeParameter.GetDynamicallyAccessedMemberTypes(); - // Avoid duplicate warnings for new() and DAMT.PublicParameterlessConstructor - if (typeParameter.HasConstructorConstraint) - parameterRequirements &= ~DynamicallyAccessedMemberTypes.PublicParameterlessConstructor; - - // Apply annotations to the generic argument - var genericParameterValue = new GenericParameterValue(typeParameter, parameterRequirements); - if (_context.EnableTrimAnalyzer && - !_owningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) && - !_featureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute) && - genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) - { - SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol(typeArgument)!; - var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(_reportDiagnostic, _typeNameResolver, typeHierarchyType: null); - var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(_context, _featureContext, _typeNameResolver, _location, _reportDiagnostic, reflectionAccessAnalyzer, _owningSymbol); - requireDynamicallyAccessedMembersAction.Invoke(genericArgumentValue, genericParameterValue); - } + _analyzer?.ProcessGenericInstantiation( + typeArgument, + typeParameter, + _featureContext, + _typeNameResolver, + _owningSymbol, + _location, + _reportDiagnostic); // Recursively process generic argument data flow on the generic argument if it itself is generic if (typeArgument is INamedTypeSymbol namedTypeArgument && namedTypeArgument.IsGenericType) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs index d9d5f59464d529..02045b840fdedf 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs @@ -28,7 +28,7 @@ internal partial struct HandleCallAction private ValueSetLattice _multiValueLattice; public HandleCallAction( - DataFlowAnalyzerContext context, + RequiresUnreferencedCodeAnalyzer? trimAnalyzer, FeatureContext featureContext, TypeNameResolver typeNameResolver, Location location, @@ -43,7 +43,7 @@ public HandleCallAction( _diagnosticContext = new DiagnosticContext(location, reportDiagnostic); _annotations = FlowAnnotations.Instance; _reflectionAccessAnalyzer = new(reportDiagnostic, typeNameResolver, typeHierarchyType: null); - _requireDynamicallyAccessedMembersAction = new(context, featureContext, typeNameResolver, location, reportDiagnostic, _reflectionAccessAnalyzer, _owningSymbol); + _requireDynamicallyAccessedMembersAction = new(trimAnalyzer, featureContext, typeNameResolver, location, reportDiagnostic, _reflectionAccessAnalyzer, _owningSymbol); _multiValueLattice = multiValueLattice; } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs index 95550b015254f8..3a7ff369c0df8f 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs @@ -16,18 +16,18 @@ namespace ILLink.Shared.TrimAnalysis { internal partial struct RequireDynamicallyAccessedMembersAction { + readonly RequiresUnreferencedCodeAnalyzer? _analyzer; readonly Location _location; readonly Action? _reportDiagnostic; readonly ReflectionAccessAnalyzer _reflectionAccessAnalyzer; readonly TypeNameResolver _typeNameResolver; readonly ISymbol _owningSymbol; - readonly DataFlowAnalyzerContext _context; readonly FeatureContext _featureContext; #pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods #pragma warning disable IDE0060 // Unused parameters - should be removed once methods are actually implemented public RequireDynamicallyAccessedMembersAction( - DataFlowAnalyzerContext context, + RequiresUnreferencedCodeAnalyzer? analyzer, FeatureContext featureContext, TypeNameResolver typeNameResolver, Location location, @@ -35,7 +35,7 @@ public RequireDynamicallyAccessedMembersAction( ReflectionAccessAnalyzer reflectionAccessAnalyzer, ISymbol owningSymbol) { - _context = context; + _analyzer = analyzer; _featureContext = featureContext; _typeNameResolver = typeNameResolver; _location = location; @@ -52,7 +52,7 @@ public partial bool TryResolveTypeNameAndMark(string typeName, bool needsAssembl { if (foundType is INamedTypeSymbol namedType && namedType.IsGenericType) { - var genericArgumentDataFlow = new GenericArgumentDataFlow(_context, _featureContext, _typeNameResolver, _owningSymbol, _location, _reportDiagnostic); + var genericArgumentDataFlow = new GenericArgumentDataFlow(_analyzer, _featureContext, _typeNameResolver, _owningSymbol, _location, _reportDiagnostic); genericArgumentDataFlow.ProcessGenericArgumentDataFlow(namedType); } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs index 26aa45c8f1eb57..d85dc6286105de 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs @@ -55,7 +55,7 @@ public TrimAnalysisAssignmentPattern Merge( public void ReportDiagnostics(DataFlowAnalyzerContext context, Action reportDiagnostic) { var location = Operation.Syntax.GetLocation(); - if (context.EnableTrimAnalyzer && + if (context.TrimAnalyzer is not null && !OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) && !FeatureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) { @@ -70,7 +70,7 @@ public void ReportDiagnostics(DataFlowAnalyzerContext context, Action reportDiagnostic) { Location location = Operation.Syntax.GetLocation(); - if (context.EnableTrimAnalyzer && + if (context.TrimAnalyzer is not null && !OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) && !FeatureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) { var typeNameResolver = new TypeNameResolver(context.Compilation); - TrimAnalysisVisitor.HandleCall(context, FeatureContext, typeNameResolver, Operation, OwningSymbol, CalledMethod, Instance, Arguments, location, reportDiagnostic, default, out var _); + TrimAnalysisVisitor.HandleCall(context.TrimAnalyzer, FeatureContext, typeNameResolver, Operation, OwningSymbol, CalledMethod, Instance, Arguments, location, reportDiagnostic, default, out var _); } // For Requires, make the location the reference to the method, not the entire invocation. // The parameters are not part of the issue, and including them in the location can be misleading. diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs index be78a2ff710cae..281e93e06f542f 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs @@ -48,7 +48,7 @@ public void ReportDiagnostics(DataFlowAnalyzerContext context, Action multiValueLattice, out MultiValue methodReturnValue) { - var handleCallAction = new HandleCallAction(dataFlowAnalyzerContext, featureContext, typeNameResolver, location, owningSymbol, operation, multiValueLattice, reportDiagnostic); + var handleCallAction = new HandleCallAction(trimAnalyzer, featureContext, typeNameResolver, location, owningSymbol, operation, multiValueLattice, reportDiagnostic); MethodProxy method = new(calledMethod); var intrinsicId = Intrinsics.GetIntrinsicIdForMethod(method); if (!handleCallAction.Invoke(method, instance, arguments, intrinsicId, out methodReturnValue)) 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 ce27dd4a4965af..acff82d1fdc4ac 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 @@ -202,7 +202,6 @@ class DerivedTypeWithInstantiationOverSelfOnBase : GenericBaseTypeWithRequiremen { } - [ExpectedWarning("IL2091", nameof(GenericBaseTypeWithRequirements))] class DerivedTypeWithOpenGenericOnBase : GenericBaseTypeWithRequirements { // Analyzer does not see the base class constructor @@ -215,8 +214,6 @@ static void TestDerivedTypeWithOpenGenericOnBaseWithRUCOnBase() new DerivedTypeWithOpenGenericOnBaseWithRUCOnBase(); } - [ExpectedWarning("IL2091", nameof(BaseTypeWithOpenGenericDAMTAndRUC))] - [ExpectedWarning("IL2091", nameof(IGenericInterfaceTypeWithRequirements))] class DerivedTypeWithOpenGenericOnBaseWithRUCOnBase : BaseTypeWithOpenGenericDAMTAndRUC, IGenericInterfaceTypeWithRequirements { [ExpectedWarning("IL2091", nameof(DerivedTypeWithOpenGenericOnBaseWithRUCOnBase))] @@ -233,8 +230,7 @@ static void TestDerivedTypeWithOpenGenericOnBaseWithRUCOnDerived() { new DerivedTypeWithOpenGenericOnBaseWithRUCOnDerived(); } - [ExpectedWarning("IL2091", nameof(BaseTypeWithOpenGenericDAMT))] - [ExpectedWarning("IL2091", nameof(IGenericInterfaceTypeWithRequirements))] + [RequiresUnreferencedCode("RUC")] class DerivedTypeWithOpenGenericOnBaseWithRUCOnDerived : BaseTypeWithOpenGenericDAMT, IGenericInterfaceTypeWithRequirements { @@ -467,7 +463,6 @@ class FullyInstantiatedOverPartiallyInstantiatedFields { } - [ExpectedWarning("IL2091", nameof(BaseForPartialInstantiation), "'TMethods'")] class PartialyInstantiatedMethods<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TOuter> : BaseForPartialInstantiation { @@ -905,7 +900,6 @@ public void UseNestedTypeArgument() new GenericTypeArgument.Nested>(); } - [ExpectedWarning("IL2091")] public class DerivedFromNestedType : GenericRequires.Nested { [ExpectedWarning("IL2091")] diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs index 020d9252030b85..d5964ced3faebc 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs @@ -62,7 +62,6 @@ class DerivedWithMatchingAnnotation<[DynamicallyAccessedMembers(DynamicallyAcces : BaseWithPublicMethods { } - [ExpectedWarning("IL2091")] class DerivedWithNoAnnotations : BaseWithPublicMethods { 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 439a665cf0f5dd..da9c5068efb54f 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 @@ -1377,7 +1377,6 @@ public static void MethodWithAttribute() { } public void InstanceMethodWithAttribute() { } // NOTE: The enclosing RUC does not apply to nested types. - [ExpectedWarning("IL2091")] public class ClassWithWarning : RequiresAll { [ExpectedWarning("IL2091")] @@ -1394,21 +1393,16 @@ 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 { } - [ExpectedWarning("IL2026", "ClassWithRequires()", "--ClassWithRequires--")] class ClassWithWarningOnGenericArgumentConstructor : RequiresNew { [ExpectedWarning("IL2026", "--ClassWithRequires--")] @@ -1417,8 +1411,6 @@ public ClassWithWarningOnGenericArgumentConstructor() } } - [ExpectedWarning("IL2026", "ClassWithRequires()", "--ClassWithRequires--")] - [ExpectedWarning("IL2026", "ClassWithRequires()", "--ClassWithRequires--", Tool.Trimmer, "https://github.com/dotnet/runtime/issues/119290")] class ClassWithWarningOnGenericArgumentConstructor_NewAndAnnotation : RequiresNewAndConstructors { [ExpectedWarning("IL2026", "--ClassWithRequires--")] @@ -1428,13 +1420,11 @@ public ClassWithWarningOnGenericArgumentConstructor_NewAndAnnotation() } } - [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 { From c54b8afd6316ba320629640430bfc734e5f40e3c Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 8 Sep 2025 18:28:20 +0000 Subject: [PATCH 05/12] Add back interface warning Moving this warning is more complicated than that for base types because we would either need to warn on conversion to interfaces, or warn on every constructor (for kept interface implementations). --- .../DynamicallyAccessedMembersAnalyzer.cs | 4 ++++ .../illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs | 3 --- .../DataFlow/GenericParameterDataFlow.cs | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 13d84874e791de..5424d1e56edf05 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -143,6 +143,10 @@ public override void Initialize(AnalysisContext context) var location = GetPrimaryLocation(type.Locations); var typeNameResolver = new TypeNameResolver(context.Compilation); + var genericArgumentDataFlow = new GenericArgumentDataFlow(dataFlowAnalyzerContext.TrimAnalyzer, FeatureContext.None, typeNameResolver, type, location, context.ReportDiagnostic); + + foreach (var interfaceType in type.Interfaces) + genericArgumentDataFlow.ProcessGenericArgumentDataFlow(interfaceType); DynamicallyAccessedMembersTypeHierarchy.ApplyDynamicallyAccessedMembersToTypeHierarchy(typeNameResolver, location, type, context.ReportDiagnostic); }, SymbolKind.NamedType); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index ba93565dcb25dd..b79f016f66bc12 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -209,9 +209,6 @@ private void AnalyzeImplicitBaseCtor(SymbolAnalysisContext context) var genericArgumentDataFlow = new GenericArgumentDataFlow(this, FeatureContext.None, typeNameResolver, implicitCtor, typeSymbol.Locations[0], context.ReportDiagnostic); if (typeSymbol.BaseType is INamedTypeSymbol baseType) genericArgumentDataFlow.ProcessGenericArgumentDataFlow(baseType); - - foreach (var interfaceType in typeSymbol.Interfaces) - genericArgumentDataFlow.ProcessGenericArgumentDataFlow(interfaceType); } [Flags] 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 acff82d1fdc4ac..839692962ac625 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 @@ -214,9 +214,10 @@ static void TestDerivedTypeWithOpenGenericOnBaseWithRUCOnBase() new DerivedTypeWithOpenGenericOnBaseWithRUCOnBase(); } + [ExpectedWarning("IL2091", nameof(IGenericInterfaceTypeWithRequirements))] class DerivedTypeWithOpenGenericOnBaseWithRUCOnBase : BaseTypeWithOpenGenericDAMTAndRUC, IGenericInterfaceTypeWithRequirements { - [ExpectedWarning("IL2091", nameof(DerivedTypeWithOpenGenericOnBaseWithRUCOnBase))] + [ExpectedWarning("IL2091", nameof(BaseTypeWithOpenGenericDAMTAndRUC))] [ExpectedWarning("IL2026", nameof(BaseTypeWithOpenGenericDAMTAndRUC))] public DerivedTypeWithOpenGenericOnBaseWithRUCOnBase() { } } @@ -231,6 +232,7 @@ static void TestDerivedTypeWithOpenGenericOnBaseWithRUCOnDerived() new DerivedTypeWithOpenGenericOnBaseWithRUCOnDerived(); } + [ExpectedWarning("IL2091", nameof(IGenericInterfaceTypeWithRequirements))] [RequiresUnreferencedCode("RUC")] class DerivedTypeWithOpenGenericOnBaseWithRUCOnDerived : BaseTypeWithOpenGenericDAMT, IGenericInterfaceTypeWithRequirements { From a2a7bcb02c53111ce98efc9b39f2054a594aac1b Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 8 Sep 2025 18:47:13 +0000 Subject: [PATCH 06/12] Remove base warning for ILLink --- .../TestCasesRunner/ResultChecker.cs | 5 ++-- .../src/linker/Linker.Steps/MarkStep.cs | 1 - .../GenericParameterDataFlowMarking.cs | 13 ++++++----- .../GenericParameterWarningLocation.cs | 23 +++++++++++-------- .../TestCasesRunner/ResultChecker.cs | 5 ++-- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs index 1ab13b8ee2efca..077232b2bdf601 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs @@ -464,8 +464,9 @@ attrMember.DeclaringType is TypeDefinition declaringType && { missingMessageWarnings.Add("Unmatched Messages:" + Environment.NewLine); missingMessageWarnings.AddRange(unmatchedMessages.Select(m => m.ToString())); - missingMessageWarnings.Add(Environment.NewLine + "All Messages:" + Environment.NewLine); - missingMessageWarnings.AddRange(allMessages.Select(m => m.ToString())); + // Uncomment to show all messages in the log + // missingMessageWarnings.Add(Environment.NewLine + "All Messages:" + Environment.NewLine); + // missingMessageWarnings.AddRange(allMessages.Select(m => m.ToString())); Assert.Fail(string.Join(Environment.NewLine, missingMessageWarnings)); } diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 9ae7f7b9cea5c9..d1fbd930a0b074 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -2158,7 +2158,6 @@ internal void MarkStaticConstructorVisibleToReflection(TypeDefinition type, in D handleMarkType(type); MarkType(type.BaseType, new DependencyInfo(DependencyKind.BaseType, type), typeOrigin); - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in typeOrigin, this, Context, type.BaseType); // The DynamicallyAccessedMembers hierarchy processing must be done after the base type was marked // (to avoid inconsistencies in the cache), but before anything else as work done below diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlowMarking.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlowMarking.cs index 36a94e0c572884..8bca0375107200 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlowMarking.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlowMarking.cs @@ -272,6 +272,7 @@ public static void Test() class BaseTypeGenericNesting { [Kept] + [KeptMember(".ctor()")] class Base { } @@ -285,6 +286,7 @@ public static void PublicMethod() { } } [Kept] + [KeptMember(".ctor()")] [KeptBaseTypeAttribute(typeof(Base>>>), By = Tool.Trimmer)] class DerivedWithTarget : Base>>> @@ -293,8 +295,7 @@ class DerivedWithTarget [Kept] public static void Test() { - Type a; - a = typeof(DerivedWithTarget); + new DerivedWithTarget(); } } @@ -302,7 +303,7 @@ public static void Test() class InterfaceGenericNesting { [Kept] - class IBase + interface IBase { } @@ -315,7 +316,8 @@ public static void PublicMethod() { } } [Kept] - [KeptBaseTypeAttribute(typeof(IBase>>>), By = Tool.Trimmer)] + [KeptMember(".ctor()")] + [KeptInterfaceAttribute(typeof(IBase>>>), By = Tool.Trimmer)] class DerivedWithTarget : IBase>>> { } @@ -323,8 +325,7 @@ class DerivedWithTarget [Kept] public static void Test() { - Type a; - a = typeof(DerivedWithTarget); + IBase>>> i = new DerivedWithTarget(); } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs index d5964ced3faebc..883a9dc9490289 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs @@ -69,12 +69,14 @@ class DerivedWithNoAnnotations public DerivedWithNoAnnotations() { } } - [ExpectedWarning("IL2091")] + [ExpectedWarning("IL2091", Tool.Analyzer, "")] + [ExpectedWarning("IL2091", Tool.Trimmer | Tool.NativeAot, "", CompilerGeneratedCode = true)] class DerivedWithMismatchAnnotation<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TPublicFields> : BaseWithPublicMethods { } - [ExpectedWarning("IL2091", nameof(DynamicallyAccessedMemberTypes.PublicMethods))] + [ExpectedWarning("IL2091", nameof(DynamicallyAccessedMemberTypes.PublicMethods), Tool.Analyzer, "")] + [ExpectedWarning("IL2091", Tool.Trimmer | Tool.NativeAot, "", CompilerGeneratedCode = true)] class DerivedWithOneMismatch<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TPublicFields> : BaseWithTwo { } @@ -85,7 +87,7 @@ class DerivedWithTwoMatching< : BaseWithTwo { } - [ExpectedWarning("IL2091")] + [ExpectedWarning("IL2091", Tool.Analyzer, "Analyzer sees declarations")] class DerivedWithOnlyStaticMethodReference : BaseWithPublicMethods { // The method body in this case looks like: @@ -112,7 +114,9 @@ public static void Test() t = typeof(DerivedWithMatchingAnnotation<>); t = typeof(DerivedWithNoAnnotations<>); t = typeof(DerivedWithMismatchAnnotation<>); + new DerivedWithMismatchAnnotation(); t = typeof(DerivedWithOneMismatch<>); + new DerivedWithOneMismatch(); t = typeof(DerivedWithTwoMatching<,>); // Also try exact instantiations @@ -1549,7 +1553,8 @@ class DerivedWithNothing : Base>> { } - [ExpectedWarning("IL2091", "TUnknown", "RequiresFields", nameof(DynamicallyAccessedMemberTypes.PublicFields))] + [ExpectedWarning("IL2091", ["TUnknown", "RequiresFields", nameof(DynamicallyAccessedMemberTypes.PublicFields)], Tool.Analyzer, "")] + [ExpectedWarning("IL2091", ["TUnknown", "RequiresFields", nameof(DynamicallyAccessedMemberTypes.PublicFields)], Tool.Trimmer | Tool.NativeAot, "", CompilerGeneratedCode = true)] class DerivedWithFields : Base>>> { @@ -1558,17 +1563,17 @@ static DerivedWithFields() } } - [ExpectedWarning("IL2026", "--RUCMethod--")] + [ExpectedWarning("IL2026", "--RUCMethod--", Tool.Analyzer, "")] + [ExpectedWarning("IL2026", "--RUCMethod--", Tool.Trimmer | Tool.NativeAot, "", CompilerGeneratedCode = true)] class DerivedWithRUC : Base>>> { } public static void Test() { - Type a; - a = typeof(DerivedWithNothing); - a = typeof(DerivedWithFields); - a = typeof(DerivedWithRUC); + new DerivedWithNothing(); + new DerivedWithFields(); + new DerivedWithRUC(); } } diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs index 3f57b237025fb6..f8f36b45e43cc4 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -1182,8 +1182,9 @@ attrMember.DeclaringType is TypeDefinition declaringType && { missingMessageWarnings.Add("Unmatched Messages:" + Environment.NewLine); missingMessageWarnings.AddRange(unmatchedMessages.Select(m => m.ToString())); - missingMessageWarnings.Add(Environment.NewLine + "All Messages:" + Environment.NewLine); - missingMessageWarnings.AddRange(allMessages.Select(m => m.ToString())); + // Uncomment to show all messages in the log + // missingMessageWarnings.Add(Environment.NewLine + "All Messages:" + Environment.NewLine); + // missingMessageWarnings.AddRange(allMessages.Select(m => m.ToString())); Assert.Fail(string.Join(Environment.NewLine, missingMessageWarnings)); } From 1bdcc9dc7e71554b8313bb6a40f3154c979acc8e Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 8 Sep 2025 18:55:41 +0000 Subject: [PATCH 07/12] Same for ILC --- .../DependencyAnalysis/DataflowAnalyzedTypeDefinitionNode.cs | 5 ----- .../DataFlow/GenericParameterDataFlowMarking.cs | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedTypeDefinitionNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedTypeDefinitionNode.cs index 4b283cb22c3542..027709244940a5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedTypeDefinitionNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedTypeDefinitionNode.cs @@ -67,11 +67,6 @@ public override IEnumerable GetStaticDependencies(NodeFacto { DependencyList dependencies = null; - if (_typeDefinition.HasBaseType) - { - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(ref dependencies, factory, new MessageOrigin(_typeDefinition), _typeDefinition.BaseType, _typeDefinition); - } - if (_typeDefinition is MetadataType metadataType) { foreach (var interfaceType in metadataType.ExplicitlyImplementedInterfaces) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlowMarking.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlowMarking.cs index 8bca0375107200..358bf2cbc83427 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlowMarking.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlowMarking.cs @@ -325,7 +325,8 @@ class DerivedWithTarget [Kept] public static void Test() { - IBase>>> i = new DerivedWithTarget(); + object o = new DerivedWithTarget(); + var i = typeof(IBase>>>); } } From ccd41ff40a319193037bc99bca158dd9068a6940 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 8 Sep 2025 21:32:53 +0000 Subject: [PATCH 08/12] Fix ILLink generic behavior - Move new constraint processing to generic dataflow - Take new constraint into account for requires dataflow check --- .../Dataflow/GenericArgumentDataFlow.cs | 4 ++ .../linker/Linker.Dataflow/FlowAnnotations.cs | 34 ++++++++++++++++ .../GenericArgumentDataFlow.cs | 19 ++++++++- .../src/linker/Linker.Steps/MarkStep.cs | 9 +---- ...traintOnClass.cs => GenericConstraints.cs} | 39 ++++++++++++++++++- .../RequiresCapability/RequiresOnClass.cs | 1 - 6 files changed, 95 insertions(+), 11 deletions(-) rename src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/{NewConstraintOnClass.cs => GenericConstraints.cs} (78%) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs index 7ac0815a77c74c..0a3c0d8ac918aa 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs @@ -117,6 +117,8 @@ public static bool RequiresGenericArgumentDataFlow(FlowAnnotations flowAnnotatio if (flowAnnotations.HasGenericParameterAnnotation(method)) return true; + // No need to check for new constraint, because we handle that as DAMT.PublicParameterlessConstructor. + foreach (TypeDesc typeParameter in method.Instantiation) { if (RequiresGenericArgumentDataFlow(flowAnnotations, typeParameter)) @@ -142,6 +144,8 @@ public static bool RequiresGenericArgumentDataFlow(FlowAnnotations flowAnnotatio if (flowAnnotations.HasGenericParameterAnnotation(type)) return true; + // No need to check for new constraint, because we handle that as DAMT.PublicParameterlessConstructor. + if (type.HasInstantiation) { foreach (TypeDesc typeParameter in type.Instantiation) diff --git a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs index b03346959a2328..ac4b5d634bf8b4 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs @@ -50,6 +50,23 @@ public bool HasGenericParameterAnnotation(TypeReference type) return GetAnnotations(typeDefinition).HasGenericParameterAnnotation(); } + public bool HasGenericParameterNewConstraint(TypeReference type) + { + if (type.ResolveToTypeDefinition(_context) is not TypeDefinition typeDefinition) + return false; + + if (typeDefinition.HasGenericParameters) + { + foreach (var genericParameter in typeDefinition.GenericParameters) + { + if (genericParameter.HasDefaultConstructorConstraint) + return true; + } + } + + return false; + } + public bool HasGenericParameterAnnotation(MethodReference method) { if (_context.TryResolve(method) is not MethodDefinition methodDefinition) @@ -58,6 +75,23 @@ public bool HasGenericParameterAnnotation(MethodReference method) return GetAnnotations(methodDefinition.DeclaringType).TryGetAnnotation(methodDefinition, out var annotation) && annotation.GenericParameterAnnotations != null; } + public bool HasGenericParameterNewConstraint(MethodReference method) + { + if (_context.TryResolve(method) is not MethodDefinition methodDefinition) + return false; + + if (methodDefinition.HasGenericParameters) + { + foreach (var genericParameter in methodDefinition.GenericParameters) + { + if (genericParameter.HasDefaultConstructorConstraint) + return true; + } + } + + return false; + } + public bool RequiresGenericArgumentDataFlow(GenericParameter genericParameter) => GetGenericParameterAnnotation(genericParameter) != DynamicallyAccessedMemberTypes.None; diff --git a/src/tools/illink/src/linker/Linker.Dataflow/GenericArgumentDataFlow.cs b/src/tools/illink/src/linker/Linker.Dataflow/GenericArgumentDataFlow.cs index b7852fa800fb1e..638a853fdcb71e 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/GenericArgumentDataFlow.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/GenericArgumentDataFlow.cs @@ -53,7 +53,16 @@ private static void ProcessGenericInstantiation(in DiagnosticContext diagnosticC var genericArgument = arguments[i]; var genericParameter = parameters[i]; - var genericParameterValue = context.Annotations.FlowAnnotations.GetGenericParameterValue(genericParameter); + var parameterRequirements = context.Annotations.FlowAnnotations.GetGenericParameterAnnotation(genericParameter); + + if (genericParameter.HasDefaultConstructorConstraint) + { + reflectionMarker.MarkTypeForDynamicallyAccessedMembers(diagnosticContext.Origin, genericArgument, DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, DependencyKind.DefaultCtorForNewConstrainedGenericArgument); + // Avoid duplicate warnings for new() and DAMT.PublicParameterlessConstructor + parameterRequirements &= ~DynamicallyAccessedMemberTypes.PublicParameterlessConstructor; + } + + var genericParameterValue = context.Annotations.FlowAnnotations.GetGenericParameterValue(genericParameter, parameterRequirements); if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) { MultiValue genericArgumentValue = context.Annotations.FlowAnnotations.GetTypeValueFromGenericArgument(genericArgument); @@ -82,6 +91,9 @@ internal static bool RequiresGenericArgumentDataFlow(FlowAnnotations flowAnnotat if (flowAnnotations.HasGenericParameterAnnotation(method)) return true; + if (flowAnnotations.HasGenericParameterNewConstraint(method)) + return true; + foreach (var genericArgument in genericInstanceMethod.GenericArguments) { if (RequiresGenericArgumentDataFlow(flowAnnotations, genericArgument)) @@ -104,6 +116,11 @@ internal static bool RequiresGenericArgumentDataFlow(FlowAnnotations flowAnnotat return true; } + if (flowAnnotations.HasGenericParameterNewConstraint(type)) + { + return true; + } + if (type is GenericInstanceType genericInstanceType) { foreach (var genericArgument in genericInstanceType.GenericArguments) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index d1fbd930a0b074..67be3dc3be6cbf 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -2921,23 +2921,16 @@ void MarkGenericArguments(IGenericInstance instance, MessageOrigin origin) { var arguments = instance.GenericArguments; - IGenericParameterProvider? generic_element = GetGenericProviderFromInstance(instance); - Collection? parameters = generic_element?.GenericParameters; - for (int i = 0; i < arguments.Count; i++) { var argument = arguments[i]; - var parameter = parameters?[i]; var argumentTypeDef = MarkType(argument, new DependencyInfo(DependencyKind.GenericArgumentType, instance), origin); if (argumentTypeDef == null) continue; MarkRelevantToVariantCasting(argumentTypeDef); - - if (parameter?.HasDefaultConstructorConstraint == true) - MarkDefaultConstructor(argumentTypeDef, new DependencyInfo(DependencyKind.DefaultCtorForNewConstrainedGenericArgument, instance), origin); - } + } } IGenericParameterProvider? GetGenericProviderFromInstance(IGenericInstance instance) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/NewConstraintOnClass.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/GenericConstraints.cs similarity index 78% rename from src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/NewConstraintOnClass.cs rename to src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/GenericConstraints.cs index 6ecbdd662604cc..78f514d6f4c92c 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/NewConstraintOnClass.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/GenericConstraints.cs @@ -5,11 +5,12 @@ namespace Mono.Linker.Tests.Cases.Generics { [ExpectedNoWarnings] - public class NewConstraintOnClass + public class GenericConstraints { public static void Main() { NewConstraint.Test(); + NewConstraintOnMethod.Test(); StructConstraint.Test(); UnmanagedConstraint.Test(); } @@ -51,6 +52,42 @@ public static void Test() } } + [Kept] + class NewConstraintOnMethod + { + class TestClass + { + static readonly int field = 1; + + [Kept] + public TestClass() + { + } + + public TestClass(int a) + { + } + + public void Foo() + { + } + } + + [Kept] + static void WithConstraint< + [KeptGenericParamAttributes(GenericParameterAttributes.DefaultConstructorConstraint)] + T + >() where T : new() + { + } + + [Kept] + public static void Test() + { + WithConstraint(); + } + } + [Kept] class StructConstraint { 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 da9c5068efb54f..7b0ab5a7d67d4d 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 @@ -1414,7 +1414,6 @@ public ClassWithWarningOnGenericArgumentConstructor() class ClassWithWarningOnGenericArgumentConstructor_NewAndAnnotation : RequiresNewAndConstructors { [ExpectedWarning("IL2026", "--ClassWithRequires--")] - [ExpectedWarning("IL2026", "--ClassWithRequires--", Tool.Trimmer, "https://github.com/dotnet/runtime/issues/119290")] public ClassWithWarningOnGenericArgumentConstructor_NewAndAnnotation() { } From e74bceae26b37aa9eee2448c8d5f51cb5450ce91 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 8 Sep 2025 21:37:11 +0000 Subject: [PATCH 09/12] PR feedback --- .../TrimAnalysis/GenericArgumentDataFlow.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs index 29b9267c5dfa6c..d19aa964dd2b2c 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs @@ -169,8 +169,11 @@ private static bool RequiresGenericArgumentDataFlow(ImmutableArray Date: Mon, 8 Sep 2025 21:51:32 +0000 Subject: [PATCH 10/12] Add test to analyzer tests --- .../test/ILLink.RoslynAnalyzer.Tests/GenericsTests.cs | 7 +++++++ .../GenericsTests.g.cs | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/GenericsTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/GenericsTests.cs index 59385d4c0a6142..fe77d5431e377e 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/GenericsTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/GenericsTests.cs @@ -13,5 +13,12 @@ public Task InstantiatedGenericEquality() { return RunTest(); } + + [Fact] + public Task GenericConstraints() + { + return RunTest(); + } + } } diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/GenericsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/GenericsTests.g.cs index 065c4b9bb96b60..240965b410ac82 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/GenericsTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/GenericsTests.g.cs @@ -61,12 +61,6 @@ public Task MethodWithParameterWhichHasGenericParametersAndOverrideUsesADifferen return RunTest(allowMissingWarnings: true); } - [Fact] - public Task NewConstraintOnClass() - { - return RunTest(allowMissingWarnings: true); - } - [Fact] public Task OverrideWithAnotherVirtualMethodOfSameNameWithDifferentParameterType() { From b2ad90ed28cba84d901970bcaecd7880375b6d62 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 12 Sep 2025 23:50:13 +0000 Subject: [PATCH 11/12] Fix offsets when rewriting IL MethodBodyScanner tracks known stacks by IL offset, so these need to be correct when analyzing IL. We can't just wait for cecil to update offsets for us when writing to disk. --- .../UnreachableBlocksOptimizer.cs | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs b/src/tools/illink/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs index 5c97985f0e0886..96b8669f62529b 100644 --- a/src/tools/illink/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs +++ b/src/tools/illink/src/linker/Linker.Steps/UnreachableBlocksOptimizer.cs @@ -493,9 +493,13 @@ public bool RewriteBody() Collection instrs = body.Instructions; #pragma warning restore RS0030 + int offset = 0; + for (int i = 0; i < instrs.Count; ++i) { Instruction instr = instrs[i]; + instr.Offset = offset; + switch (instr.OpCode.Code) { @@ -503,10 +507,10 @@ public bool RewriteBody() case Code.Callvirt: MethodDefinition? md = optimizer._context.TryResolve((MethodReference)instr.Operand); if (md == null) - continue; + break; if (md.IsVirtual) - continue; + break; if (md.CallingConvention == MethodCallingConvention.VarArg) break; @@ -529,40 +533,63 @@ public bool RewriteBody() { if (!md.HasMetadataParameters() && CanInlineInstanceCall(instrs, i)) { - processor.Replace(i - 1, Instruction.Create(OpCodes.Nop)); - processor.Replace(i, result.GetPrototype()); + offset -= instrs[i - 1].GetSize(); + + var nop = Instruction.Create(OpCodes.Nop); + nop.Offset = offset; + processor.Replace(i - 1, nop); + offset += nop.GetSize(); + + instr = result.GetPrototype(); + instr.Offset = offset; + processor.Replace(i, instr); changed = true; } - continue; + break; } if (md.HasMetadataParameters()) { if (!IsCalledWithoutSideEffects(md, instrs, i)) - continue; + break; + + for (int p = 1; p <= md.GetMetadataParametersCount(); ++p) + { + offset -= instrs[i - p].GetSize(); + } for (int p = 1; p <= md.GetMetadataParametersCount(); ++p) { - processor.Replace(i - p, Instruction.Create(OpCodes.Nop)); + var nop = Instruction.Create(OpCodes.Nop); + nop.Offset = offset; + processor.Replace(i - p, nop); + offset += nop.GetSize(); } } - processor.Replace(i, result.GetPrototype()); + instr = result.GetPrototype(); + instr.Offset = offset; + processor.Replace(i, instr); changed = true; - continue; + break; case Code.Sizeof: var operand = (TypeReference)instr.Operand; Instruction? value = optimizer.GetSizeOfResult(operand); if (value != null) { - processor.Replace(i, value.GetPrototype()); + instr = value.GetPrototype(); + instr.Offset = offset; + processor.Replace(i, instr); changed = true; } - continue; + break; + default: + break; } + offset += instr.GetSize(); } return changed; From 3f36272109d8b071e4e6788c8678876b676f6ae3 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 16 Sep 2025 14:14:13 -0700 Subject: [PATCH 12/12] PR feedback - Use interface for generic dataflow - Remove outdated comment - Clarify expected warning reason - Add Kept attributes for clarity --- .../IGenericInstantiationAnalysis.cs.cs | 20 +++++++++++++++++++ .../RequiresAnalyzerBase.cs | 4 ++-- .../RequiresUnreferencedCodeAnalyzer.cs | 2 +- .../TrimAnalysis/GenericArgumentDataFlow.cs | 4 ++-- .../TrimAnalysis/TypeNameResolver.cs | 2 +- .../DataFlow/GenericParameterDataFlow.cs | 1 - .../GenericParameterWarningLocation.cs | 2 +- .../Generics/GenericConstraints.cs | 2 ++ 8 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 src/tools/illink/src/ILLink.RoslynAnalyzer/IGenericInstantiationAnalysis.cs.cs diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/IGenericInstantiationAnalysis.cs.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/IGenericInstantiationAnalysis.cs.cs new file mode 100644 index 00000000000000..79aad9432bba68 --- /dev/null +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/IGenericInstantiationAnalysis.cs.cs @@ -0,0 +1,20 @@ +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using ILLink.RoslynAnalyzer.DataFlow; +using ILLink.Shared.TrimAnalysis; + +namespace ILLink.RoslynAnalyzer +{ + internal interface IGenericInstantiationAnalysis + { + void ProcessGenericInstantiation( + ITypeSymbol typeArgument, + ITypeParameterSymbol typeParameter, + FeatureContext featureContext, + TypeNameResolver typeNameResolver, + ISymbol owningSymbol, + Location location, + Action? reportDiagnostic); + } +} diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index b79f016f66bc12..cdb720c2660584 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -19,7 +19,7 @@ namespace ILLink.RoslynAnalyzer { - public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer + public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer, IGenericInstantiationAnalysis { private protected abstract string RequiresAttributeName { get; } @@ -35,7 +35,7 @@ public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer private protected abstract DiagnosticDescriptor RequiresOnStaticCtor { get; } private protected abstract DiagnosticDescriptor RequiresOnEntryPoint { get; } - internal virtual void ProcessGenericInstantiation( + public virtual void ProcessGenericInstantiation( ITypeSymbol typeArgument, ITypeParameterSymbol typeParameter, FeatureContext featureContext, diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs index ed1dfe1f069dae..3e0aa9cddcc673 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs @@ -100,7 +100,7 @@ protected override bool VerifyAttributeArguments(AttributeData attribute) => protected override string GetMessageFromAttribute(AttributeData? requiresAttribute) => RequiresUnreferencedCodeUtils.GetMessageFromAttribute(requiresAttribute); - internal override void ProcessGenericInstantiation( + public override void ProcessGenericInstantiation( ITypeSymbol typeArgument, ITypeParameterSymbol typeParameter, FeatureContext featureContext, diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs index 180b5243146c4e..b73efbbfa78924 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs @@ -15,7 +15,7 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis { internal readonly struct GenericArgumentDataFlow { - private readonly RequiresAnalyzerBase? _analyzer; + private readonly IGenericInstantiationAnalysis? _analyzer; private readonly FeatureContext _featureContext; private readonly TypeNameResolver _typeNameResolver; private readonly ISymbol _owningSymbol; @@ -23,7 +23,7 @@ internal readonly struct GenericArgumentDataFlow private readonly Action? _reportDiagnostic; public GenericArgumentDataFlow( - RequiresAnalyzerBase? analyzer, + IGenericInstantiationAnalysis? analyzer, FeatureContext featureContext, TypeNameResolver typeNameResolver, ISymbol owningSymbol, diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs index 77400e898a5529..49209efa6a695f 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs @@ -12,7 +12,7 @@ namespace ILLink.Shared.TrimAnalysis { - internal struct TypeNameResolver + public struct TypeNameResolver { readonly Compilation _compilation; 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 839692962ac625..5aa92d6cc98382 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 @@ -204,7 +204,6 @@ class DerivedTypeWithInstantiationOverSelfOnBase : GenericBaseTypeWithRequiremen class DerivedTypeWithOpenGenericOnBase : GenericBaseTypeWithRequirements { - // Analyzer does not see the base class constructor [ExpectedWarning("IL2091", nameof(GenericBaseTypeWithRequirements))] public DerivedTypeWithOpenGenericOnBase() { } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs index 883a9dc9490289..cbc7c1f6b23f57 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs @@ -87,7 +87,7 @@ class DerivedWithTwoMatching< : BaseWithTwo { } - [ExpectedWarning("IL2091", Tool.Analyzer, "Analyzer sees declarations")] + [ExpectedWarning("IL2091", Tool.Analyzer, "Analyzer warns on declaration for implicit constructor")] class DerivedWithOnlyStaticMethodReference : BaseWithPublicMethods { // The method body in this case looks like: diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/GenericConstraints.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/GenericConstraints.cs index 78f514d6f4c92c..8d41b113edb252 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/GenericConstraints.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/GenericConstraints.cs @@ -18,6 +18,7 @@ public static void Main() [Kept] class NewConstraint { + [Kept] class TestClass { static readonly int field = 1; @@ -55,6 +56,7 @@ public static void Test() [Kept] class NewConstraintOnMethod { + [Kept] class TestClass { static readonly int field = 1;