Skip to content

Commit 0cf461b

Browse files
authored
[ILLink analyzer] Move generic parameter analysis to dataflow (#95482)
Move generic parameter analysis to dataflow and align behavior more closely with NativeAot. Roughly follows the implementation approach used by NativeAot in GenericArgumentDataFlow.cs to make it easier to share the logic (without actually sharing the code for now). Fixes #95121. Some notes on the behavior: - Gets rid of some analyzer warnings in cases where NativeAot doesn't warn either - Doesn't warn for generics in `typeof` - Doesn't warn for generics in signatures of reflectable members (ILC does this to work around an incorrect suppression in DI, as discussed in #81358). - Generics in base types or interface types are still analyzed outside of dataflow
1 parent e994165 commit 0cf461b

File tree

12 files changed

+340
-259
lines changed

12 files changed

+340
-259
lines changed

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -560,15 +560,15 @@ public override TValue VisitDelegateCreation (IDelegateCreationOperation operati
560560
targetMethodSymbol = lambda.Symbol;
561561
break;
562562
case IMethodReferenceOperation methodReference:
563-
IMethodSymbol method = methodReference.Method.OriginalDefinition;
564-
if (method.ContainingSymbol is IMethodSymbol) {
563+
IMethodSymbol methodDefinition = methodReference.Method.OriginalDefinition;
564+
if (methodDefinition.ContainingSymbol is IMethodSymbol) {
565565
// Track references to local functions
566-
var localFunction = method;
566+
var localFunction = methodDefinition;
567567
Debug.Assert (localFunction.MethodKind == MethodKind.LocalFunction);
568568
var localFunctionCFG = ControlFlowGraph.GetLocalFunctionControlFlowGraphInScope (localFunction);
569569
InterproceduralState.TrackMethod (new MethodBodyValue (localFunction, localFunctionCFG));
570570
}
571-
targetMethodSymbol = method;
571+
targetMethodSymbol = methodReference.Method;
572572
break;
573573
case IMemberReferenceOperation:
574574
case IInvocationOperation:
@@ -585,7 +585,7 @@ public override TValue VisitDelegateCreation (IDelegateCreationOperation operati
585585
return HandleDelegateCreation (targetMethodSymbol, operation, state.Current.Context);
586586
}
587587

588-
public abstract TValue HandleDelegateCreation (IMethodSymbol methodReference, IOperation operation, TContext context);
588+
public abstract TValue HandleDelegateCreation (IMethodSymbol methodReference, IOperation operation, in TContext context);
589589

590590
public override TValue VisitPropertyReference (IPropertyReferenceOperation operation, LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state)
591591
{

src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs

Lines changed: 8 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,9 @@
88
using System.Diagnostics.CodeAnalysis;
99
using ILLink.RoslynAnalyzer.TrimAnalysis;
1010
using ILLink.Shared;
11-
using ILLink.Shared.DataFlow;
1211
using ILLink.Shared.TrimAnalysis;
1312
using Microsoft.CodeAnalysis;
14-
using Microsoft.CodeAnalysis.CSharp;
15-
using Microsoft.CodeAnalysis.CSharp.Syntax;
1613
using Microsoft.CodeAnalysis.Diagnostics;
17-
using Microsoft.CodeAnalysis.Operations;
1814

1915
namespace ILLink.RoslynAnalyzer
2016
{
@@ -106,85 +102,24 @@ public override void Initialize (AnalysisContext context)
106102
// Examine generic instantiations in base types and interface list
107103
context.RegisterSymbolAction (context => {
108104
var type = (INamedTypeSymbol) context.Symbol;
109-
var location = GetPrimaryLocation (type.Locations);
110105
// RUC on type doesn't silence DAM warnings about generic base/interface types.
111106
// This knowledge lives in IsInRequiresUnreferencedCodeAttributeScope,
112107
// which we still call for consistency here, but it is expected to return false.
113108
if (type.IsInRequiresUnreferencedCodeAttributeScope (out _))
114109
return;
115110

116-
if (type.BaseType is INamedTypeSymbol baseType) {
117-
foreach (var diagnostic in ProcessGenericParameters (baseType, location))
118-
context.ReportDiagnostic (diagnostic);
119-
}
120-
121-
foreach (var interfaceType in type.Interfaces) {
122-
foreach (var diagnostic in ProcessGenericParameters (interfaceType, location))
123-
context.ReportDiagnostic (diagnostic);
124-
}
125-
}, SymbolKind.NamedType);
126-
// Examine generic instantiations in method return type and parameters.
127-
// This includes property getters and setters.
128-
context.RegisterSymbolAction (context => {
129-
var method = (IMethodSymbol) context.Symbol;
130-
if (method.IsInRequiresUnreferencedCodeAttributeScope (out _))
131-
return;
132-
133-
var returnType = method.ReturnType;
134-
foreach (var diagnostic in ProcessGenericParameters (returnType, GetPrimaryLocation (method.Locations)))
135-
context.ReportDiagnostic (diagnostic);
136-
137-
foreach (var parameter in method.Parameters) {
138-
foreach (var diagnostic in ProcessGenericParameters (parameter.Type, GetPrimaryLocation (parameter.Locations)))
139-
context.ReportDiagnostic (diagnostic);
140-
}
141-
}, SymbolKind.Method);
142-
// Examine generic instantiations in field type.
143-
context.RegisterSymbolAction (context => {
144-
var field = (IFieldSymbol) context.Symbol;
145-
if (field.IsInRequiresUnreferencedCodeAttributeScope (out _))
146-
return;
147-
148-
foreach (var diagnostic in ProcessGenericParameters (field.Type, GetPrimaryLocation (field.Locations)))
149-
context.ReportDiagnostic (diagnostic);
150-
}, SymbolKind.Field);
151-
// Examine generic instantiations in invocations of generically instantiated methods,
152-
// or methods on generically instantiated types.
153-
context.RegisterOperationAction (context => {
154-
if (context.ContainingSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _))
155-
return;
156-
157-
var invocation = (IInvocationOperation) context.Operation;
158-
var methodSymbol = invocation.TargetMethod;
159-
foreach (var diagnostic in ProcessMethodGenericParameters (methodSymbol, invocation.Syntax.GetLocation ()))
160-
context.ReportDiagnostic (diagnostic);
161-
}, OperationKind.Invocation);
162-
// Examine generic instantiations in delegate creation of generically instantiated methods.
163-
context.RegisterOperationAction (context => {
164-
if (context.ContainingSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _))
165-
return;
166-
167-
var delegateCreation = (IDelegateCreationOperation) context.Operation;
168-
if (delegateCreation.Target is not IMethodReferenceOperation methodReference)
169-
return;
111+
var location = GetPrimaryLocation (type.Locations);
112+
DiagnosticContext diagnosticContext = new (location);
170113

171-
if (methodReference.Method is not IMethodSymbol methodSymbol)
172-
return;
173-
foreach (var diagnostic in ProcessMethodGenericParameters (methodSymbol, delegateCreation.Syntax.GetLocation()))
174-
context.ReportDiagnostic (diagnostic);
175-
}, OperationKind.DelegateCreation);
176-
// Examine generic instantiations in object creation of generically instantiated types.
177-
context.RegisterOperationAction (context => {
178-
if (context.ContainingSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _))
179-
return;
114+
if (type.BaseType is INamedTypeSymbol baseType)
115+
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (diagnosticContext, baseType);
180116

181-
var objectCreation = (IObjectCreationOperation) context.Operation;
182-
if (objectCreation.Type is not ITypeSymbol typeSymbol)
183-
return;
117+
foreach (var interfaceType in type.Interfaces)
118+
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (diagnosticContext, interfaceType);
184119

185-
foreach (var diagnostic in ProcessGenericParameters (typeSymbol, objectCreation.Syntax.GetLocation()))
120+
foreach (var diagnostic in diagnosticContext.Diagnostics)
186121
context.ReportDiagnostic (diagnostic);
187-
}, OperationKind.ObjectCreation);
122+
}, SymbolKind.NamedType);
188123
context.RegisterSymbolAction (context => {
189124
VerifyMemberOnlyApplyToTypesOrStrings (context, context.Symbol);
190125
VerifyDamOnPropertyAndAccessorMatch (context, (IMethodSymbol) context.Symbol);
@@ -202,66 +137,6 @@ public override void Initialize (AnalysisContext context)
202137
});
203138
}
204139

205-
static IEnumerable<Diagnostic> ProcessMethodGenericParameters (IMethodSymbol methodSymbol, Location location)
206-
{
207-
foreach (var diagnostic in ProcessGenericParameters (methodSymbol, location))
208-
yield return diagnostic;
209-
210-
if (methodSymbol.IsStatic && methodSymbol.ContainingType is not null) {
211-
foreach (var diagnostic in ProcessGenericParameters (methodSymbol.ContainingType, location))
212-
yield return diagnostic;
213-
}
214-
}
215-
216-
static IEnumerable<Diagnostic> ProcessGenericParameters (ISymbol symbol, Location location)
217-
{
218-
// Avoid unnecessary execution if not NamedType or Method
219-
if (symbol is not INamedTypeSymbol && symbol is not IMethodSymbol)
220-
yield break;
221-
222-
ImmutableArray<ITypeParameterSymbol> typeParams = default;
223-
ImmutableArray<ITypeSymbol> typeArgs = default;
224-
switch (symbol) {
225-
case INamedTypeSymbol type:
226-
typeParams = type.TypeParameters;
227-
typeArgs = type.TypeArguments;
228-
break;
229-
case IMethodSymbol targetMethod:
230-
typeParams = targetMethod.TypeParameters;
231-
typeArgs = targetMethod.TypeArguments;
232-
break;
233-
}
234-
235-
if (typeParams != null) {
236-
Debug.Assert (typeParams.Length == typeArgs.Length);
237-
238-
for (int i = 0; i < typeParams.Length; i++) {
239-
// Syntax like typeof (Foo<>) will have an ErrorType as the type argument.
240-
// These uninstantiated generics should not produce warnings.
241-
if (typeArgs[i].Kind == SymbolKind.ErrorType)
242-
continue;
243-
var sourceValue = SingleValueExtensions.FromTypeSymbol (typeArgs[i])!;
244-
var targetValue = new GenericParameterValue (typeParams[i]);
245-
foreach (var diagnostic in GetDynamicallyAccessedMembersDiagnostics (sourceValue, targetValue, location))
246-
yield return diagnostic;
247-
}
248-
}
249-
}
250-
251-
static List<Diagnostic> GetDynamicallyAccessedMembersDiagnostics (SingleValue sourceValue, SingleValue targetValue, Location location)
252-
{
253-
// The target should always be an annotated value, but the visitor design currently prevents
254-
// declaring this in the type system.
255-
if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers)
256-
throw new NotImplementedException ();
257-
258-
var diagnosticContext = new DiagnosticContext (location);
259-
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, default (ReflectionAccessAnalyzer));
260-
requireDynamicallyAccessedMembersAction.Invoke (sourceValue, targetWithDynamicallyAccessedMembers);
261-
262-
return diagnosticContext.Diagnostics;
263-
}
264-
265140
static void VerifyMemberOnlyApplyToTypesOrStrings (SymbolAnalysisContext context, ISymbol member)
266141
{
267142
var location = GetPrimaryLocation (member.Locations);
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Diagnostics;
8+
using System.Diagnostics.CodeAnalysis;
9+
using Microsoft.CodeAnalysis;
10+
using ILLink.Shared.DataFlow;
11+
using ILLink.Shared.TrimAnalysis;
12+
using ILLink.Shared.TypeSystemProxy;
13+
14+
namespace ILLink.RoslynAnalyzer.TrimAnalysis
15+
{
16+
internal static class GenericArgumentDataFlow
17+
{
18+
public static void ProcessGenericArgumentDataFlow (DiagnosticContext diagnosticContext, INamedTypeSymbol type)
19+
{
20+
ProcessGenericArgumentDataFlow (diagnosticContext, type.TypeArguments, type.TypeParameters);
21+
}
22+
23+
public static void ProcessGenericArgumentDataFlow (DiagnosticContext diagnosticContext, IMethodSymbol method)
24+
{
25+
ProcessGenericArgumentDataFlow (diagnosticContext, method.TypeArguments, method.TypeParameters);
26+
27+
ProcessGenericArgumentDataFlow (diagnosticContext, method.ContainingType);
28+
}
29+
30+
public static void ProcessGenericArgumentDataFlow (DiagnosticContext diagnosticContext, IFieldSymbol field)
31+
{
32+
ProcessGenericArgumentDataFlow (diagnosticContext, field.ContainingType);
33+
}
34+
35+
static void ProcessGenericArgumentDataFlow (
36+
DiagnosticContext diagnosticContext,
37+
ImmutableArray<ITypeSymbol> typeArguments,
38+
ImmutableArray<ITypeParameterSymbol> typeParameters)
39+
{
40+
for (int i = 0; i < typeArguments.Length; i++) {
41+
var typeArgument = typeArguments[i];
42+
// Apply annotations to the generic argument
43+
var genericParameterValue = new GenericParameterValue (typeParameters[i]);
44+
if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) {
45+
SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol (typeArgument)!;
46+
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, default (ReflectionAccessAnalyzer));
47+
requireDynamicallyAccessedMembersAction.Invoke (genericArgumentValue, genericParameterValue);
48+
}
49+
50+
// Recursively process generic argument data flow on the generic argument if it itself is generic
51+
if (typeArgument is INamedTypeSymbol namedTypeArgument && namedTypeArgument.IsGenericType) {
52+
ProcessGenericArgumentDataFlow (diagnosticContext, namedTypeArgument);
53+
}
54+
}
55+
}
56+
57+
public static bool RequiresGenericArgumentDataFlow (INamedTypeSymbol type)
58+
{
59+
if (type.IsGenericType) {
60+
if (RequiresGenericArgumentDataFlow (type.TypeParameters))
61+
return true;
62+
63+
foreach (var typeArgument in type.TypeArguments) {
64+
if (typeArgument is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType
65+
&& RequiresGenericArgumentDataFlow (namedTypeSymbol))
66+
return true;
67+
}
68+
}
69+
70+
return false;
71+
}
72+
73+
public static bool RequiresGenericArgumentDataFlow (IMethodSymbol method)
74+
{
75+
if (method.IsGenericMethod) {
76+
if (RequiresGenericArgumentDataFlow (method.TypeParameters))
77+
return true;
78+
79+
foreach (var typeArgument in method.TypeArguments) {
80+
if (typeArgument is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType
81+
&& RequiresGenericArgumentDataFlow (namedTypeSymbol))
82+
return true;
83+
}
84+
}
85+
86+
return RequiresGenericArgumentDataFlow (method.ContainingType);
87+
}
88+
89+
public static bool RequiresGenericArgumentDataFlow (IFieldSymbol field)
90+
{
91+
return RequiresGenericArgumentDataFlow (field.ContainingType);
92+
}
93+
94+
static bool RequiresGenericArgumentDataFlow (ImmutableArray<ITypeParameterSymbol> typeParameters)
95+
{
96+
foreach (var typeParameter in typeParameters) {
97+
var genericParameterValue = new GenericParameterValue (typeParameter);
98+
if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None)
99+
return true;
100+
}
101+
102+
return false;
103+
}
104+
}
105+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using ILLink.RoslynAnalyzer.DataFlow;
7+
using ILLink.Shared.TrimAnalysis;
8+
using Microsoft.CodeAnalysis;
9+
10+
namespace ILLink.RoslynAnalyzer.TrimAnalysis
11+
{
12+
public readonly record struct TrimAnalysisGenericInstantiationPattern
13+
{
14+
public ISymbol GenericInstantiation { get; init; }
15+
public IOperation Operation { get; init; }
16+
public ISymbol OwningSymbol { get; init; }
17+
public FeatureContext FeatureContext { get; init; }
18+
19+
public TrimAnalysisGenericInstantiationPattern (
20+
ISymbol genericInstantiation,
21+
IOperation operation,
22+
ISymbol owningSymbol,
23+
FeatureContext featureContext)
24+
{
25+
GenericInstantiation = genericInstantiation;
26+
Operation = operation;
27+
OwningSymbol = owningSymbol;
28+
FeatureContext = featureContext.DeepCopy ();
29+
}
30+
31+
public TrimAnalysisGenericInstantiationPattern Merge (
32+
FeatureContextLattice featureContextLattice,
33+
TrimAnalysisGenericInstantiationPattern other)
34+
{
35+
Debug.Assert (Operation == other.Operation);
36+
Debug.Assert (SymbolEqualityComparer.Default.Equals (GenericInstantiation, other.GenericInstantiation));
37+
Debug.Assert (SymbolEqualityComparer.Default.Equals (OwningSymbol, other.OwningSymbol));
38+
39+
return new TrimAnalysisGenericInstantiationPattern (
40+
GenericInstantiation,
41+
Operation,
42+
OwningSymbol,
43+
featureContextLattice.Meet (FeatureContext, other.FeatureContext));
44+
}
45+
46+
public IEnumerable<Diagnostic> CollectDiagnostics (DataFlowAnalyzerContext context)
47+
{
48+
DiagnosticContext diagnosticContext = new (Operation.Syntax.GetLocation ());
49+
if (context.EnableTrimAnalyzer &&
50+
!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
51+
!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.UnreferencedCode)) {
52+
switch (GenericInstantiation) {
53+
case INamedTypeSymbol type:
54+
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (diagnosticContext, type);
55+
break;
56+
57+
case IMethodSymbol method:
58+
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (diagnosticContext, method);
59+
break;
60+
61+
case IFieldSymbol field:
62+
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (diagnosticContext, field);
63+
break;
64+
}
65+
}
66+
67+
return diagnosticContext.Diagnostics;
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)