Skip to content

Commit

Permalink
Implement type name resolution for ILLink analyzer (dotnet#106209)
Browse files Browse the repository at this point in the history
Adds support for parsing type names passed to Type.GetType.
Fixes dotnet#95118
  • Loading branch information
sbomer committed Aug 12, 2024
1 parent d7a3f76 commit e5b1d02
Show file tree
Hide file tree
Showing 22 changed files with 393 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics ()
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedTypeNameInTypeGetType));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedParameterInMethodCreateInstance));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ParametersOfAssemblyCreateInstanceCannotBeAnalyzed));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.TypeNameIsNotAssemblyQualified));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ReturnValueDoesNotMatchFeatureGuards));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.InvalidFeatureGuard));

Expand Down Expand Up @@ -117,11 +118,12 @@ public override void Initialize (AnalysisContext context)
var location = GetPrimaryLocation (type.Locations);
var typeNameResolver = new TypeNameResolver (context.Compilation);
if (type.BaseType is INamedTypeSymbol baseType)
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, baseType, context.ReportDiagnostic);
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, baseType, context.ReportDiagnostic);
foreach (var interfaceType in type.Interfaces)
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, interfaceType, context.ReportDiagnostic);
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, interfaceType, context.ReportDiagnostic);
}, SymbolKind.NamedType);
context.RegisterSymbolAction (context => {
VerifyMemberOnlyApplyToTypesOrStrings (context, context.Symbol);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
optimize the analyzer even in Debug builds. Note: we still use the
Debug configuration to get Debug asserts. -->
<Optimize>true</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(CoreLibSharedDir)System/Index.cs" />
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\Reflection\Metadata\TypeNameHelpers.cs" />
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\Text\ValueStringBuilder.cs" />
</ItemGroup>

<ItemGroup>
Expand All @@ -28,6 +31,7 @@
<PrivateAssets>all</PrivateAssets>
<ExcludeAssets>contentfiles</ExcludeAssets> <!-- We include our own copy of the ClosedAttribute to work in source build -->
</PackageReference>
<PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
</ItemGroup>

<Import Project="..\ILLink.Shared\ILLink.Shared.projitems" Label="Shared" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,29 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis
{
internal static class GenericArgumentDataFlow
{
public static void ProcessGenericArgumentDataFlow (Location location, INamedTypeSymbol type, Action<Diagnostic> reportDiagnostic)
public static void ProcessGenericArgumentDataFlow (TypeNameResolver typeNameResolver, Location location, INamedTypeSymbol type, Action<Diagnostic>? reportDiagnostic)
{
ProcessGenericArgumentDataFlow (location, type.TypeArguments, type.TypeParameters, reportDiagnostic);
ProcessGenericArgumentDataFlow (typeNameResolver, location, type.TypeArguments, type.TypeParameters, reportDiagnostic);
}

public static void ProcessGenericArgumentDataFlow (Location location, IMethodSymbol method, Action<Diagnostic> reportDiagnostic)
public static void ProcessGenericArgumentDataFlow (TypeNameResolver typeNameResolver, Location location, IMethodSymbol method, Action<Diagnostic>? reportDiagnostic)
{
ProcessGenericArgumentDataFlow (location, method.TypeArguments, method.TypeParameters, reportDiagnostic);
ProcessGenericArgumentDataFlow (typeNameResolver, location, method.TypeArguments, method.TypeParameters, reportDiagnostic);

ProcessGenericArgumentDataFlow (location, method.ContainingType, reportDiagnostic);
ProcessGenericArgumentDataFlow (typeNameResolver, location, method.ContainingType, reportDiagnostic);
}

public static void ProcessGenericArgumentDataFlow (Location location, IFieldSymbol field, Action<Diagnostic> reportDiagnostic)
public static void ProcessGenericArgumentDataFlow (TypeNameResolver typeNameResolver, Location location, IFieldSymbol field, Action<Diagnostic>? reportDiagnostic)
{
ProcessGenericArgumentDataFlow (location, field.ContainingType, reportDiagnostic);
ProcessGenericArgumentDataFlow (typeNameResolver, location, field.ContainingType, reportDiagnostic);
}

static void ProcessGenericArgumentDataFlow (
TypeNameResolver typeNameResolver,
Location location,
ImmutableArray<ITypeSymbol> typeArguments,
ImmutableArray<ITypeParameterSymbol> typeParameters,
Action<Diagnostic> reportDiagnostic)
Action<Diagnostic>? reportDiagnostic)
{
var diagnosticContext = new DiagnosticContext (location, reportDiagnostic);
for (int i = 0; i < typeArguments.Length; i++) {
Expand All @@ -42,14 +43,14 @@ static void ProcessGenericArgumentDataFlow (
var genericParameterValue = new GenericParameterValue (typeParameters[i]);
if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) {
SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol (typeArgument)!;
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic);
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, reflectionAccessAnalyzer);
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeNameResolver);
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (typeNameResolver, location, reportDiagnostic, 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 (location, namedTypeArgument, reportDiagnostic);
ProcessGenericArgumentDataFlow (typeNameResolver, location, namedTypeArgument, reportDiagnostic);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal partial struct HandleCallAction
ValueSetLattice<SingleValue> _multiValueLattice;

public HandleCallAction (
TypeNameResolver typeNameResolver,
Location location,
ISymbol owningSymbol,
IOperation operation,
Expand All @@ -39,8 +40,8 @@ public HandleCallAction (
_isNewObj = operation.Kind == OperationKind.ObjectCreation;
_diagnosticContext = new DiagnosticContext (location, reportDiagnostic);
_annotations = FlowAnnotations.Instance;
_reflectionAccessAnalyzer = new (reportDiagnostic);
_requireDynamicallyAccessedMembersAction = new (_diagnosticContext, _reflectionAccessAnalyzer);
_reflectionAccessAnalyzer = new (reportDiagnostic, typeNameResolver);
_requireDynamicallyAccessedMembersAction = new (typeNameResolver, location, reportDiagnostic, _reflectionAccessAnalyzer);
_multiValueLattice = multiValueLattice;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis
readonly struct ReflectionAccessAnalyzer
{
readonly Action<Diagnostic>? _reportDiagnostic;
readonly TypeNameResolver _typeNameResolver;

public ReflectionAccessAnalyzer (Action<Diagnostic>? reportDiagnostic) => _reportDiagnostic = reportDiagnostic;
public ReflectionAccessAnalyzer (
Action<Diagnostic>? reportDiagnostic,
TypeNameResolver typeNameResolver)
{
_reportDiagnostic = reportDiagnostic;
_typeNameResolver = typeNameResolver;
}

#pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods
internal void GetReflectionAccessDiagnostics (Location location, ITypeSymbol typeSymbol, DynamicallyAccessedMemberTypes requiredMemberTypes, bool declaredOnly = false)
Expand Down Expand Up @@ -138,5 +145,10 @@ void GetDiagnosticsForField (Location location, IFieldSymbol fieldSymbol)
diagnosticContext.AddDiagnostic (DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection, fieldSymbol.GetDisplayName ());
}
}

internal bool TryResolveTypeNameAndMark (string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, [NotNullWhen (true)] out ITypeSymbol? type)
{
return _typeNameResolver.TryResolveTypeName (typeName, diagnosticContext, out type, needsAssemblyName);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,55 @@
// 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;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis;
using ILLink.RoslynAnalyzer.TrimAnalysis;
using ILLink.Shared.TypeSystemProxy;
using System.Collections.Immutable;

namespace ILLink.Shared.TrimAnalysis
{
internal partial struct RequireDynamicallyAccessedMembersAction
{
readonly Location _location;
readonly Action<Diagnostic>? _reportDiagnostic;
readonly ReflectionAccessAnalyzer _reflectionAccessAnalyzer;
readonly TypeNameResolver _typeNameResolver;
#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 (
DiagnosticContext diagnosticContext,
TypeNameResolver typeNameResolver,
Location location,
Action<Diagnostic>? reportDiagnostic,
ReflectionAccessAnalyzer reflectionAccessAnalyzer)
{
_diagnosticContext = diagnosticContext;
_typeNameResolver = typeNameResolver;
_location = location;
_reportDiagnostic = reportDiagnostic;
_reflectionAccessAnalyzer = reflectionAccessAnalyzer;
_diagnosticContext = new (location, reportDiagnostic);
}

public partial bool TryResolveTypeNameAndMark (string typeName, bool needsAssemblyName, out TypeProxy type)
{
// TODO: Implement type name resolution to type symbol
// https://github.com/dotnet/runtime/issues/95118
var diagnosticContext = new DiagnosticContext (_location, _reportDiagnostic);
if (_reflectionAccessAnalyzer.TryResolveTypeNameAndMark (typeName, diagnosticContext, needsAssemblyName, out ITypeSymbol? foundType)) {
if (foundType is INamedTypeSymbol namedType && namedType.IsGenericType)
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (_typeNameResolver, _location, namedType, _reportDiagnostic);

// Important corner cases:
// IL2105 (see it's occurences in the tests) - non-assembly qualified type name which doesn't resolve warns
// - will need to figure out what analyzer should do around this.
type = new TypeProxy (foundType);
return true;
}

type = default;
return false;
}

private partial void MarkTypeForDynamicallyAccessedMembers (in TypeProxy type, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes) =>
_reflectionAccessAnalyzer.GetReflectionAccessDiagnostics (_diagnosticContext.Location, type.Type, dynamicallyAccessedMemberTypes);
_reflectionAccessAnalyzer.GetReflectionAccessDiagnostics (_location, type.Type, dynamicallyAccessedMemberTypes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public TrimAnalysisAssignmentPattern Merge (

public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnostic> reportDiagnostic)
{
var diagnosticContext = new DiagnosticContext (Operation.Syntax.GetLocation (), reportDiagnostic);
var location = Operation.Syntax.GetLocation ();
if (context.EnableTrimAnalyzer &&
!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) {
Expand All @@ -66,8 +66,9 @@ public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnosti
if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers)
throw new NotImplementedException ();

var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic);
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, reflectionAccessAnalyzer);
var typeNameResolver = new TypeNameResolver (context.Compilation);
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeNameResolver);
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer);
requireDynamicallyAccessedMembersAction.Invoke (sourceValue, targetWithDynamicallyAccessedMembers);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,18 @@ public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnosti
!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) {
var location = Operation.Syntax.GetLocation ();
var typeNameResolver = new TypeNameResolver (context.Compilation);
switch (GenericInstantiation) {
case INamedTypeSymbol type:
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, type, reportDiagnostic);
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, type, reportDiagnostic);
break;

case IMethodSymbol method:
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, method, reportDiagnostic);
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, method, reportDiagnostic);
break;

case IFieldSymbol field:
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, field, reportDiagnostic);
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, field, reportDiagnostic);
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnosti
{
var location = Operation.Syntax.GetLocation ();
if (context.EnableTrimAnalyzer &&
!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) &&
!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute))
{
TrimAnalysisVisitor.HandleCall(Operation, OwningSymbol, CalledMethod, Instance, Arguments, location, reportDiagnostic, default, out var _);
var typeNameResolver = new TypeNameResolver (context.Compilation);
TrimAnalysisVisitor.HandleCall (typeNameResolver, Operation, OwningSymbol, CalledMethod, Instance, Arguments, location, reportDiagnostic, default, out var _);
}

var diagnosticContext = new DiagnosticContext (location, reportDiagnostic);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public TrimAnalysisReflectionAccessPattern Merge (
public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnostic> reportDiagnostic)
{
var location = Operation.Syntax.GetLocation ();
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic);
var typeNameResolver = new TypeNameResolver (context.Compilation);
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeNameResolver);
if (context.EnableTrimAnalyzer &&
!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ internal sealed class TrimAnalysisVisitor : LocalDataFlowVisitor<

FeatureChecksVisitor _featureChecksVisitor;

readonly TypeNameResolver _typeNameResolver;

public TrimAnalysisVisitor (
Compilation compilation,
LocalStateAndContextLattice<MultiValue, FeatureContext, ValueSetLattice<SingleValue>, FeatureContextLattice> lattice,
Expand All @@ -57,6 +59,7 @@ public TrimAnalysisVisitor (
_multiValueLattice = lattice.LocalStateLattice.Lattice.ValueLattice;
TrimAnalysisPatterns = trimAnalysisPatterns;
_featureChecksVisitor = new FeatureChecksVisitor (dataFlowAnalyzerContext);
_typeNameResolver = new TypeNameResolver (compilation);
}

public override FeatureChecksValue GetConditionValue (IOperation branchValueOperation, StateValue state)
Expand Down Expand Up @@ -291,7 +294,7 @@ public override MultiValue HandleMethodCall (
// Especially with DAM on type, this can lead to incorrectly analyzed code (as in unknown type which leads
// to noise). ILLink has the same problem currently: https://github.com/dotnet/linker/issues/1952

HandleCall (operation, OwningSymbol, calledMethod, instance, arguments, Location.None, null, _multiValueLattice, out MultiValue methodReturnValue);
HandleCall (_typeNameResolver, operation, OwningSymbol, calledMethod, instance, arguments, Location.None, null, _multiValueLattice, out MultiValue methodReturnValue);

// This will copy the values if necessary
TrimAnalysisPatterns.Add (new TrimAnalysisMethodCallPattern (
Expand All @@ -315,6 +318,7 @@ public override MultiValue HandleMethodCall (
}

internal static void HandleCall(
TypeNameResolver typeNameResolver,
IOperation operation,
ISymbol owningSymbol,
IMethodSymbol calledMethod,
Expand All @@ -325,7 +329,7 @@ internal static void HandleCall(
ValueSetLattice<SingleValue> multiValueLattice,
out MultiValue methodReturnValue)
{
var handleCallAction = new HandleCallAction (location, owningSymbol, operation, multiValueLattice, reportDiagnostic);
var handleCallAction = new HandleCallAction (typeNameResolver, location, owningSymbol, operation, multiValueLattice, reportDiagnostic);
MethodProxy method = new (calledMethod);
var intrinsicId = Intrinsics.GetIntrinsicIdForMethod (method);
if (!handleCallAction.Invoke (method, instance, arguments, intrinsicId, out methodReturnValue))
Expand Down
Loading

0 comments on commit e5b1d02

Please sign in to comment.