-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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
- Loading branch information
Showing
12 changed files
with
340 additions
and
259 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Microsoft.CodeAnalysis; | ||
using ILLink.Shared.DataFlow; | ||
using ILLink.Shared.TrimAnalysis; | ||
using ILLink.Shared.TypeSystemProxy; | ||
|
||
namespace ILLink.RoslynAnalyzer.TrimAnalysis | ||
{ | ||
internal static class GenericArgumentDataFlow | ||
{ | ||
public static void ProcessGenericArgumentDataFlow (DiagnosticContext diagnosticContext, INamedTypeSymbol type) | ||
{ | ||
ProcessGenericArgumentDataFlow (diagnosticContext, type.TypeArguments, type.TypeParameters); | ||
} | ||
|
||
public static void ProcessGenericArgumentDataFlow (DiagnosticContext diagnosticContext, IMethodSymbol method) | ||
{ | ||
ProcessGenericArgumentDataFlow (diagnosticContext, method.TypeArguments, method.TypeParameters); | ||
|
||
ProcessGenericArgumentDataFlow (diagnosticContext, method.ContainingType); | ||
} | ||
|
||
public static void ProcessGenericArgumentDataFlow (DiagnosticContext diagnosticContext, IFieldSymbol field) | ||
{ | ||
ProcessGenericArgumentDataFlow (diagnosticContext, field.ContainingType); | ||
} | ||
|
||
static void ProcessGenericArgumentDataFlow ( | ||
DiagnosticContext diagnosticContext, | ||
ImmutableArray<ITypeSymbol> typeArguments, | ||
ImmutableArray<ITypeParameterSymbol> typeParameters) | ||
{ | ||
for (int i = 0; i < typeArguments.Length; i++) { | ||
var typeArgument = typeArguments[i]; | ||
// Apply annotations to the generic argument | ||
var genericParameterValue = new GenericParameterValue (typeParameters[i]); | ||
if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) { | ||
SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol (typeArgument)!; | ||
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, default (ReflectionAccessAnalyzer)); | ||
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 (diagnosticContext, namedTypeArgument); | ||
} | ||
} | ||
} | ||
|
||
public static bool RequiresGenericArgumentDataFlow (INamedTypeSymbol type) | ||
{ | ||
if (type.IsGenericType) { | ||
if (RequiresGenericArgumentDataFlow (type.TypeParameters)) | ||
return true; | ||
|
||
foreach (var typeArgument in type.TypeArguments) { | ||
if (typeArgument is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType | ||
&& RequiresGenericArgumentDataFlow (namedTypeSymbol)) | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public static bool RequiresGenericArgumentDataFlow (IMethodSymbol method) | ||
{ | ||
if (method.IsGenericMethod) { | ||
if (RequiresGenericArgumentDataFlow (method.TypeParameters)) | ||
return true; | ||
|
||
foreach (var typeArgument in method.TypeArguments) { | ||
if (typeArgument is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType | ||
&& RequiresGenericArgumentDataFlow (namedTypeSymbol)) | ||
return true; | ||
} | ||
} | ||
|
||
return RequiresGenericArgumentDataFlow (method.ContainingType); | ||
} | ||
|
||
public static bool RequiresGenericArgumentDataFlow (IFieldSymbol field) | ||
{ | ||
return RequiresGenericArgumentDataFlow (field.ContainingType); | ||
} | ||
|
||
static bool RequiresGenericArgumentDataFlow (ImmutableArray<ITypeParameterSymbol> typeParameters) | ||
{ | ||
foreach (var typeParameter in typeParameters) { | ||
var genericParameterValue = new GenericParameterValue (typeParameter); | ||
if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
.../illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisGenericInstantiationPattern.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Copyright (c) .NET Foundation and contributors. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using ILLink.RoslynAnalyzer.DataFlow; | ||
using ILLink.Shared.TrimAnalysis; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace ILLink.RoslynAnalyzer.TrimAnalysis | ||
{ | ||
public readonly record struct TrimAnalysisGenericInstantiationPattern | ||
{ | ||
public ISymbol GenericInstantiation { get; init; } | ||
public IOperation Operation { get; init; } | ||
public ISymbol OwningSymbol { get; init; } | ||
public FeatureContext FeatureContext { get; init; } | ||
|
||
public TrimAnalysisGenericInstantiationPattern ( | ||
ISymbol genericInstantiation, | ||
IOperation operation, | ||
ISymbol owningSymbol, | ||
FeatureContext featureContext) | ||
{ | ||
GenericInstantiation = genericInstantiation; | ||
Operation = operation; | ||
OwningSymbol = owningSymbol; | ||
FeatureContext = featureContext.DeepCopy (); | ||
} | ||
|
||
public TrimAnalysisGenericInstantiationPattern Merge ( | ||
FeatureContextLattice featureContextLattice, | ||
TrimAnalysisGenericInstantiationPattern other) | ||
{ | ||
Debug.Assert (Operation == other.Operation); | ||
Debug.Assert (SymbolEqualityComparer.Default.Equals (GenericInstantiation, other.GenericInstantiation)); | ||
Debug.Assert (SymbolEqualityComparer.Default.Equals (OwningSymbol, other.OwningSymbol)); | ||
|
||
return new TrimAnalysisGenericInstantiationPattern ( | ||
GenericInstantiation, | ||
Operation, | ||
OwningSymbol, | ||
featureContextLattice.Meet (FeatureContext, other.FeatureContext)); | ||
} | ||
|
||
public IEnumerable<Diagnostic> CollectDiagnostics (DataFlowAnalyzerContext context) | ||
{ | ||
DiagnosticContext diagnosticContext = new (Operation.Syntax.GetLocation ()); | ||
if (context.EnableTrimAnalyzer && | ||
!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) && | ||
!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.UnreferencedCode)) { | ||
switch (GenericInstantiation) { | ||
case INamedTypeSymbol type: | ||
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (diagnosticContext, type); | ||
break; | ||
|
||
case IMethodSymbol method: | ||
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (diagnosticContext, method); | ||
break; | ||
|
||
case IFieldSymbol field: | ||
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (diagnosticContext, field); | ||
break; | ||
} | ||
} | ||
|
||
return diagnosticContext.Diagnostics; | ||
} | ||
} | ||
} |
Oops, something went wrong.