diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs index 8068c73c51be4c..6fe9c52329bb37 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using ILLink.RoslynAnalyzer.TrimAnalysis; using ILLink.Shared.DataFlow; using Microsoft.CodeAnalysis; @@ -16,7 +17,7 @@ namespace ILLink.RoslynAnalyzer.DataFlow // - field // - parameter // - method return - public abstract class LocalDataFlowVisitor : OperationWalker, TValue>, + public abstract partial class LocalDataFlowVisitor : OperationWalker, TValue>, ITransfer, LocalDataFlowState, LocalStateLattice> // This struct constraint prevents warnings due to possible null returns from the visitor methods. // Note that this assumes that default(TValue) is equal to the TopValue. @@ -27,6 +28,8 @@ public abstract class LocalDataFlowVisitor : OperationWal protected readonly InterproceduralStateLattice InterproceduralStateLattice; + protected readonly Compilation Compilation; + protected readonly ISymbol OwningSymbol; private readonly ControlFlowGraph ControlFlowGraph; @@ -44,12 +47,14 @@ bool IsRValueFlowCapture (CaptureId captureId) => !lValueFlowCaptures.TryGetValue (captureId, out var captureKind) || captureKind != FlowCaptureKind.LValueCapture; public LocalDataFlowVisitor ( + Compilation compilation, LocalStateLattice lattice, ISymbol owningSymbol, ControlFlowGraph cfg, ImmutableDictionary lValueFlowCaptures, InterproceduralState interproceduralState) { + Compilation = compilation; LocalStateLattice = lattice; InterproceduralStateLattice = default; OwningSymbol = owningSymbol; @@ -124,6 +129,30 @@ public override TValue VisitLocalReference (ILocalReferenceOperation operation, return GetLocal (operation, state); } + TValue ProcessBinderCall (IOperation operation, string methodName, LocalDataFlowState state) { + var assemblyType = Compilation.GetTypeByMetadataName ("Microsoft.CSharp.RuntimeBinder.Binder"); + Debug.Assert (assemblyType != null); + if (assemblyType == null) + return TopValue; + var method = assemblyType.GetMembers (methodName).OfType ().SingleOrDefault (); + Debug.Assert (method != null); + if (method == null) + return TopValue; + return ProcessMethodCall (operation, method, null, ImmutableArray.Empty, state); + } + + public override TValue VisitDynamicInvocation (IDynamicInvocationOperation operation, LocalDataFlowState state) + => ProcessBinderCall (operation, "InvokeMember", state); + + public override TValue VisitDynamicObjectCreation (IDynamicObjectCreationOperation operation, LocalDataFlowState state) + => ProcessBinderCall (operation, "InvokeConstructor", state); + + public override TValue VisitDynamicMemberReference (IDynamicMemberReferenceOperation operation, LocalDataFlowState state) + => ProcessBinderCall (operation, operation.GetValueUsageInfo (OwningSymbol).HasFlag (ValueUsageInfo.Write) ? "SetMember" : "GetMember", state); + + public override TValue VisitDynamicIndexerAccess (IDynamicIndexerAccessOperation operation, LocalDataFlowState state) + => ProcessBinderCall (operation, operation.GetValueUsageInfo (OwningSymbol).HasFlag (ValueUsageInfo.Write) ? "SetIndex" : "GetIndex", state); + bool IsReferenceToCapturedVariable (ILocalReferenceOperation localReference) { var local = localReference.Local; @@ -283,12 +312,11 @@ TValue ProcessSingleTargetAssignment (IOperation targetOperation, IAssignmentOpe // also produces warnings for ref params/locals/returns. // https://github.com/dotnet/linker/issues/2632 // https://github.com/dotnet/linker/issues/2158 - Visit (targetOperation, state); - break; case IDynamicMemberReferenceOperation: case IDynamicIndexerAccessOperation: - // Not yet implemented in analyzer: - // https://github.com/dotnet/runtime/issues/94057 + // Assignment to dynamic member/indexer will translate into a call to runtime binder methods + // which should produce warnings, but isn't relevant for dataflow. + Visit (targetOperation, state); break; // Keep these cases in sync with those in CapturedReferenceValue, for any that diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index 2cee966b2cf02a..6bfadce286c173 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -27,8 +27,6 @@ public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer private protected abstract DiagnosticDescriptor RequiresAttributeMismatch { get; } private protected abstract DiagnosticDescriptor RequiresOnStaticCtor { get; } - 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; private protected virtual ImmutableArray<(Action Action, SymbolKind[] SymbolKind)> ExtraSymbolActions { get; } = ImmutableArray<(Action Action, SymbolKind[] SymbolKind)>.Empty; @@ -103,10 +101,6 @@ public override void Initialize (AnalysisContext context) } }, SyntaxKind.GenericName); - // Register any extra operation actions supported by the analyzer. - foreach (var extraOperationAction in ExtraOperationActions) - context.RegisterOperationAction (extraOperationAction.Action, extraOperationAction.OperationKind); - foreach (var extraSyntaxNodeAction in ExtraSyntaxNodeActions) context.RegisterSyntaxNodeAction (extraSyntaxNodeAction.Action, extraSyntaxNodeAction.SyntaxKind); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs index a6cae197839fc6..ec0a435ac9967d 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs @@ -19,24 +19,12 @@ public sealed class RequiresUnreferencedCodeAnalyzer : RequiresAnalyzerBase static readonly DiagnosticDescriptor s_requiresUnreferencedCodeRule = DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.RequiresUnreferencedCode); static readonly DiagnosticDescriptor s_requiresUnreferencedCodeAttributeMismatch = DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.RequiresUnreferencedCodeAttributeMismatch); - static readonly DiagnosticDescriptor s_dynamicTypeInvocationRule = DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.RequiresUnreferencedCode, - new LocalizableResourceString (nameof (SharedStrings.DynamicTypeInvocationTitle), SharedStrings.ResourceManager, typeof (SharedStrings)), - new LocalizableResourceString (nameof (SharedStrings.DynamicTypeInvocationMessage), SharedStrings.ResourceManager, typeof (SharedStrings))); static readonly DiagnosticDescriptor s_makeGenericTypeRule = DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.MakeGenericType); static readonly DiagnosticDescriptor s_makeGenericMethodRule = DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.MakeGenericMethod); static readonly DiagnosticDescriptor s_requiresUnreferencedCodeOnStaticCtor = DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.RequiresUnreferencedCodeOnStaticConstructor); static readonly DiagnosticDescriptor s_typeDerivesFromRucClassRule = DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.RequiresUnreferencedCodeOnBaseClass); - static readonly Action s_dynamicTypeInvocation = operationContext => { - if (FindContainingSymbol (operationContext, DiagnosticTargets.All) is ISymbol containingSymbol && - containingSymbol.IsInRequiresScope (RequiresUnreferencedCodeAttribute, out _)) - return; - - operationContext.ReportDiagnostic (Diagnostic.Create (s_dynamicTypeInvocationRule, - operationContext.Operation.Syntax.GetLocation ())); - }; - private Action typeDerivesFromRucBase { get { return symbolAnalysisContext => { @@ -56,7 +44,7 @@ private Action typeDerivesFromRucBase { } public override ImmutableArray SupportedDiagnostics => - ImmutableArray.Create (s_dynamicTypeInvocationRule, s_makeGenericMethodRule, s_makeGenericTypeRule, s_requiresUnreferencedCodeRule, s_requiresUnreferencedCodeAttributeMismatch, s_typeDerivesFromRucClassRule, s_requiresUnreferencedCodeOnStaticCtor); + ImmutableArray.Create (s_makeGenericMethodRule, s_makeGenericTypeRule, s_requiresUnreferencedCodeRule, s_requiresUnreferencedCodeAttributeMismatch, s_typeDerivesFromRucClassRule, s_requiresUnreferencedCodeOnStaticCtor); private protected override string RequiresAttributeName => RequiresUnreferencedCodeAttribute; @@ -90,11 +78,6 @@ protected override bool CreateSpecialIncompatibleMembersDiagnostic ( private protected override ImmutableArray<(Action Action, SymbolKind[] SymbolKind)> ExtraSymbolActions => ImmutableArray.Create<(Action Action, SymbolKind[] SymbolKind)> ((typeDerivesFromRucBase, new SymbolKind[] { SymbolKind.NamedType })); - - - private protected override ImmutableArray<(Action Action, OperationKind[] OperationKind)> ExtraOperationActions => - ImmutableArray.Create ((s_dynamicTypeInvocation, new OperationKind[] { OperationKind.DynamicInvocation })); - protected override bool VerifyAttributeArguments (AttributeData attribute) => RequiresUnreferencedCodeUtils.VerifyRequiresUnreferencedCodeAttributeArguments (attribute); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs index 5dab06de44a4d4..451ef442b08520 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs @@ -33,13 +33,14 @@ public class TrimAnalysisVisitor : LocalDataFlowVisitor> lattice, ISymbol owningSymbol, ControlFlowGraph methodCFG, ImmutableDictionary lValueFlowCaptures, TrimAnalysisPatternStore trimAnalysisPatterns, InterproceduralState> interproceduralState - ) : base (lattice, owningSymbol, methodCFG, lValueFlowCaptures, interproceduralState) + ) : base (compilation, lattice, owningSymbol, methodCFG, lValueFlowCaptures, interproceduralState) { _multiValueLattice = lattice.Lattice.ValueLattice; TrimAnalysisPatterns = trimAnalysisPatterns; diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs index 8961405071afb9..323c64f0d1c244 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs @@ -39,7 +39,7 @@ protected override TrimAnalysisVisitor GetVisitor ( ControlFlowGraph methodCFG, ImmutableDictionary lValueFlowCaptures, InterproceduralState> interproceduralState) - => new (Lattice, owningSymbol, methodCFG, lValueFlowCaptures, TrimAnalysisPatterns, interproceduralState); + => new (Context.Compilation, Lattice, owningSymbol, methodCFG, lValueFlowCaptures, TrimAnalysisPatterns, interproceduralState); #if DEBUG #pragma warning disable CA1805 // Do not initialize unnecessarily diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/DynamicObjects.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/DynamicObjects.cs index 6dec20faecf548..a9f8f00296b5cc 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/DynamicObjects.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/DynamicObjects.cs @@ -14,6 +14,9 @@ namespace Mono.Linker.Tests.Cases.DataFlow [Reference ("Microsoft.CSharp.dll")] public class DynamicObjects { + // Note on discrepancies between analyzer and NativeAot: + // Analyzer doesn't produce RequiresDynamicCode warnings for dynamic invocations. + // Tracked by https://github.com/dotnet/runtime/issues/94427. public static void Main () { InvocationOnDynamicType.Test (); @@ -25,10 +28,8 @@ public static void Main () class InvocationOnDynamicType { - // Analyzer hole: https://github.com/dotnet/runtime/issues/94057 - [ExpectedWarning ("IL2026", "Invoking members on dynamic types is not trimming-compatible.", ProducedBy = Tool.Analyzer)] - [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember", ProducedBy = Tool.Trimmer | Tool.NativeAot)] - [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] + [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember")] + [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] // https://github.com/dotnet/runtime/issues/94427 static void DynamicArgument () { dynamic dynamicObject = "Some string"; @@ -46,18 +47,15 @@ static void MethodWithDynamicParameterDoNothing (dynamic arg) { } - // Analyzer hole: https://github.com/dotnet/runtime/issues/94057 - [ExpectedWarning ("IL2026", "Invoking members on dynamic types is not trimming-compatible.", ProducedBy = Tool.Analyzer)] - [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember", ProducedBy = Tool.Trimmer | Tool.NativeAot)] - [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] + [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember")] + [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] // https://github.com/dotnet/runtime/issues/94427 static void MethodWithDynamicParameter (dynamic arg) { arg.MethodWithDynamicParameter (arg); } - [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.InvokeConstructor", ProducedBy = Tool.Trimmer | Tool.NativeAot)] - [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] - // TODO: analyzer hole! + [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.InvokeConstructor")] + [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] // https://github.com/dotnet/runtime/issues/94427 static void ObjectCreationDynamicArgument () { dynamic dynamicObject = "Some string"; @@ -81,17 +79,15 @@ public static void Test () class DynamicMemberReference { - // Analyzer hole: https://github.com/dotnet/runtime/issues/94057 - [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.GetMember", ProducedBy = Tool.Trimmer | Tool.NativeAot)] - [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] + [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.GetMember")] + [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] // https://github.com/dotnet/runtime/issues/94427 static void Read (dynamic d) { var x = d.Member; } - // Analyzer hole: https://github.com/dotnet/runtime/issues/94057 - [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.SetMember", ProducedBy = Tool.Trimmer | Tool.NativeAot)] - [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] + [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.SetMember")] + [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] // https://github.com/dotnet/runtime/issues/94427 static void Write (dynamic d) { d.Member = 0; @@ -106,17 +102,15 @@ public static void Test () class DynamicIndexerAccess { - // Analyzer hole: https://github.com/dotnet/runtime/issues/94057 - [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.GetIndex", ProducedBy = Tool.Trimmer | Tool.NativeAot)] - [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] + [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.GetIndex")] + [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] // https://github.com/dotnet/runtime/issues/94427 static void Read (dynamic d) { var x = d[0]; } - // Analyzer hole: https://github.com/dotnet/runtime/issues/94057 - [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.SetIndex", ProducedBy = Tool.Trimmer | Tool.NativeAot)] - [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] + [ExpectedWarning ("IL2026", "Microsoft.CSharp.RuntimeBinder.Binder.SetIndex")] + [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] // https://github.com/dotnet/runtime/issues/94427 static void Write (dynamic d) { d[0] = 0; @@ -134,8 +128,7 @@ class DynamicInRequiresUnreferencedCodeClass [RequiresUnreferencedCode("message")] class ClassWithRequires { - // Analyzer hole: https://github.com/dotnet/runtime/issues/94057 - [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] + [ExpectedWarning ("IL3050", ProducedBy = Tool.NativeAot)] // https://github.com/dotnet/runtime/issues/94427 public static void MethodWithDynamicArg (dynamic arg) { arg.DynamicInvocation (); @@ -151,7 +144,6 @@ public static void Test () class InvocationOnDynamicTypeInMethodWithRUCDoesNotWarnTwoTimes () { - // Analyzer hole: https://github.com/dotnet/runtime/issues/94057 [RequiresUnreferencedCode ("We should only see the warning related to this annotation, and none about the dynamic type.")] [RequiresDynamicCode ("We should only see the warning related to this annotation, and none about the dynamic type.")] static void MethodWithRequires ()