diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs index 5693bd5079814..952d6ecbc5cd5 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs @@ -22,6 +22,19 @@ internal static bool HasAttribute (this ISymbol symbol, string attributeName) return false; } + internal static bool TryGetAttribute (this ISymbol member, string attributeName, [NotNullWhen (returnValue: true)] out AttributeData? attribute) + { + attribute = null; + foreach (var attr in member.GetAttributes ()) { + if (attr.AttributeClass is { } attrClass && attrClass.HasName (attributeName)) { + attribute = attr; + return true; + } + } + + return false; + } + internal static bool TryGetOverriddenMember (this ISymbol? symbol, [NotNullWhen (returnValue: true)] out ISymbol? overridenMember) { overridenMember = symbol switch { diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index 7befde2450a53..2ed43d286d021 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -8,6 +8,7 @@ using System.Linq; using ILLink.Shared; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -27,6 +28,8 @@ public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer private protected virtual ImmutableArray<(Action Action, OperationKind[] OperationKind)> ExtraOperationActions { get; } = ImmutableArray<(Action Action, OperationKind[] OperationKind)>.Empty; + private protected virtual ImmutableArray<(Action Action, SyntaxKind[] SyntaxKind)> ExtraSyntaxNodeActions { get; } = ImmutableArray<(Action Action, SyntaxKind[] SyntaxKind)>.Empty; + public override void Initialize (AnalysisContext context) { context.EnableConcurrentExecution (); @@ -35,8 +38,8 @@ public override void Initialize (AnalysisContext context) var compilation = context.Compilation; if (!IsAnalyzerEnabled (context.Options, compilation)) return; - var incompatibleMembers = GetSpecialIncompatibleMembers (compilation); + var incompatibleMembers = GetSpecialIncompatibleMembers (compilation); context.RegisterSymbolAction (symbolAnalysisContext => { var methodSymbol = (IMethodSymbol) symbolAnalysisContext.Symbol; CheckMatchingAttributesInOverrides (symbolAnalysisContext, methodSymbol); @@ -119,6 +122,9 @@ public override void Initialize (AnalysisContext context) foreach (var extraOperationAction in ExtraOperationActions) context.RegisterOperationAction (extraOperationAction.Action, extraOperationAction.OperationKind); + foreach (var extraSyntaxNodeAction in ExtraSyntaxNodeActions) + context.RegisterSyntaxNodeAction (extraSyntaxNodeAction.Action, extraSyntaxNodeAction.SyntaxKind); + void CheckStaticConstructors (OperationAnalysisContext operationContext, ImmutableArray staticConstructors) { @@ -263,7 +269,7 @@ private void ReportMismatchInAttributesDiagnostic (SymbolAnalysisContext symbolA protected abstract string GetMessageFromAttribute (AttributeData requiresAttribute); - private string GetUrlFromAttribute (AttributeData? requiresAttribute) + public static string GetUrlFromAttribute (AttributeData? requiresAttribute) { var url = requiresAttribute?.NamedArguments.FirstOrDefault (na => na.Key == "Url").Value.Value?.ToString (); return MessageFormat.FormatRequiresAttributeUrlArg (url); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs index 0beae2c75988b..052c560e05b86 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs @@ -4,8 +4,11 @@ using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using ILLink.Shared; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace ILLink.RoslynAnalyzer @@ -31,6 +34,56 @@ public sealed class RequiresUnreferencedCodeAnalyzer : RequiresAnalyzerBase operationContext.Operation.Syntax.GetLocation ())); }; + [SuppressMessage ("MicrosoftCodeAnalysisPerformance", "RS1008", + Justification = "Storing per-compilation data inside a diagnostic analyzer might cause stale compilations to remain alive." + + "This action is registered through a compilation start action, so that instances that register this syntax" + + " node action will not outlive a compilation's lifetime, avoiding the possibility of the locals stored in" + + " this function to cause for any stale compilations to remain in memory.")] + static readonly Action s_constructorConstraint = syntaxNodeAnalysisContext => { + var model = syntaxNodeAnalysisContext.SemanticModel; + if (syntaxNodeAnalysisContext.ContainingSymbol is not ISymbol containingSymbol || containingSymbol.HasAttribute (RequiresUnreferencedCodeAttribute)) + 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) + continue; + + var typeArgCtors = ((INamedTypeSymbol) typeArg).InstanceConstructors; + foreach (var instanceCtor in typeArgCtors) { + if (instanceCtor.Arity > 0) + continue; + + if (instanceCtor.TryGetAttribute (RequiresUnreferencedCodeAttribute, out var requiresUnreferencedCodeAttribute)) { + syntaxNodeAnalysisContext.ReportDiagnostic (Diagnostic.Create (s_requiresUnreferencedCodeRule, + syntaxNodeAnalysisContext.Node.GetLocation (), + containingSymbol.GetDisplayName (), + (string) requiresUnreferencedCodeAttribute.ConstructorArguments[0].Value!, + GetUrlFromAttribute (requiresUnreferencedCodeAttribute))); + } + } + } + }; + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create (s_dynamicTypeInvocationRule, s_requiresUnreferencedCodeRule, s_requiresUnreferencedCodeAttributeMismatch); @@ -50,6 +103,9 @@ protected override bool IsAnalyzerEnabled (AnalyzerOptions options, Compilation private protected override ImmutableArray<(Action Action, OperationKind[] OperationKind)> ExtraOperationActions => ImmutableArray.Create ((s_dynamicTypeInvocation, new OperationKind[] { OperationKind.DynamicInvocation })); + private protected override ImmutableArray<(Action Action, SyntaxKind[] SyntaxKind)> ExtraSyntaxNodeActions => + ImmutableArray.Create ((s_constructorConstraint, new SyntaxKind[] { SyntaxKind.GenericName })); + protected override bool VerifyAttributeArguments (AttributeData attribute) => attribute.ConstructorArguments.Length >= 1 && attribute.ConstructorArguments[0] is { Type: { SpecialType: SpecialType.System_String } } ctorArg; diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresUnreferencedCodeCapability.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresUnreferencedCodeCapability.cs index ac6dab4e052d0..d0fdf744fe44f 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresUnreferencedCodeCapability.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresUnreferencedCodeCapability.cs @@ -80,6 +80,8 @@ public static void Main () AccessThroughPInvoke.Test (); OnEventMethod.Test (); AccessThroughNewConstraint.Test (); + AccessThroughNewConstraint.TestNewConstraintOnTypeParameter (); + AccessThroughNewConstraint.TestNewConstraintOnTypeParameterOfStaticType (); AccessThroughLdToken.Test (); RequiresOnClass.Test (); } @@ -770,19 +772,39 @@ public static void Test () class AccessThroughNewConstraint { - class NewConstrainTestType + class NewConstraintTestType { - [RequiresUnreferencedCode ("Message for --NewConstrainTestType.ctor--")] - public NewConstrainTestType () { } + [RequiresUnreferencedCode ("Message for --NewConstraintTestType.ctor--")] + public NewConstraintTestType () { } } static void GenericMethod () where T : new() { } - // https://github.com/mono/linker/issues/2117 - [ExpectedWarning ("IL2026", "--NewConstrainTestType.ctor--", GlobalAnalysisOnly = true)] + [ExpectedWarning ("IL2026", "--NewConstraintTestType.ctor--")] public static void Test () { - GenericMethod (); + GenericMethod (); + } + + static class NewConstraintOnTypeParameterOfStaticType where T : new() + { + public static void DoNothing () { } + } + + class NewConstaintOnTypeParameter where T : new() + { + } + + [ExpectedWarning ("IL2026", "--NewConstraintTestType.ctor--")] + public static void TestNewConstraintOnTypeParameter () + { + _ = new NewConstaintOnTypeParameter (); + } + + [ExpectedWarning ("IL2026", "--NewConstraintTestType.ctor--")] + public static void TestNewConstraintOnTypeParameterOfStaticType () + { + NewConstraintOnTypeParameterOfStaticType.DoNothing (); } }