Skip to content

Commit fdf73d8

Browse files
authored
Implement type name resolution for ILLink analyzer
Adds Roslyn analyzer support for parsing type names passed to Type.GetType. Reapplies e5b1d02 which was reverted due to #106321. Now that Roslyn has updated to reference System.Reflection.Metadata 9.0, we could use `TypeName` from that assembly. However, the code was using `TypeNameHelpers.Unescape` which was moved to `TypeName.Unescape` in .NET 10. Instead of using the 9.0.0 version, this just source includes the sources for the TypeNameResolver to ensure we get the same behavior in the analyzer as we have in ILC/ILLink.
1 parent a3df2a8 commit fdf73d8

26 files changed

+320
-111
lines changed

src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ The System.Reflection.Metadata library is built-in as part of the shared framewo
123123
<Compile Include="System\Reflection\Metadata\TypeName.cs" />
124124
<Compile Include="System\Reflection\Metadata\TypeNameParser.cs" />
125125
<Compile Include="System\Reflection\Metadata\TypeNameParserHelpers.cs" />
126-
<Compile Include="System\Reflection\Metadata\TypeNameParserOptions.cs" />
126+
<Compile Include="System\Reflection\Metadata\TypeNameParseOptions.cs" />
127127
<Compile Include="System\Reflection\Metadata\BlobReader.cs" />
128128
<Compile Include="System\Reflection\Metadata\TypeSystem\Constant.cs" />
129129
<Compile Include="System\Reflection\Metadata\Signatures\ConstantTypeCode.cs" />

src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeName.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public string FullName
194194
private void AppendFullName(ref ValueStringBuilder builder)
195195
{
196196
// This is a recursive method over potentially hostile input. Protection against DoS is offered
197-
// via the [Try]Parse method and TypeNameParserOptions.MaxNodes property at construction time.
197+
// via the [Try]Parse method and TypeNameParseOptions.MaxNodes property at construction time.
198198
// This FullName property getter and related methods assume that this TypeName instance has an
199199
// acceptable node count.
200200
//

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics()
5757
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.UnrecognizedTypeNameInTypeGetType));
5858
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.UnrecognizedParameterInMethodCreateInstance));
5959
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.ParametersOfAssemblyCreateInstanceCannotBeAnalyzed));
60+
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.TypeNameIsNotAssemblyQualified));
6061
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.ReturnValueDoesNotMatchFeatureGuards));
6162
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.InvalidFeatureGuard));
6263
diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined));
@@ -130,13 +131,14 @@ public override void Initialize(AnalysisContext context)
130131

131132
var location = GetPrimaryLocation(type.Locations);
132133

134+
var typeNameResolver = new TypeNameResolver(context.Compilation);
133135
if (type.BaseType is INamedTypeSymbol baseType)
134-
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(location, baseType, context.ReportDiagnostic);
136+
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(typeNameResolver, location, baseType, context.ReportDiagnostic);
135137

136138
foreach (var interfaceType in type.Interfaces)
137-
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(location, interfaceType, context.ReportDiagnostic);
139+
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(typeNameResolver, location, interfaceType, context.ReportDiagnostic);
138140

139-
DynamicallyAccessedMembersTypeHierarchy.ApplyDynamicallyAccessedMembersToTypeHierarchy(location, type, context.ReportDiagnostic);
141+
DynamicallyAccessedMembersTypeHierarchy.ApplyDynamicallyAccessedMembersToTypeHierarchy(typeNameResolver, location, type, context.ReportDiagnostic);
140142
}, SymbolKind.NamedType);
141143
context.RegisterSymbolAction(context =>
142144
{

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@
1010

1111
namespace ILLink.RoslynAnalyzer
1212
{
13-
internal sealed class DynamicallyAccessedMembersTypeHierarchy
13+
sealed class DynamicallyAccessedMembersTypeHierarchy
1414
{
15-
public static void ApplyDynamicallyAccessedMembersToTypeHierarchy(Location typeLocation, INamedTypeSymbol type, Action<Diagnostic> reportDiagnostic)
15+
public static void ApplyDynamicallyAccessedMembersToTypeHierarchy(
16+
TypeNameResolver typeNameResolver,
17+
Location typeLocation,
18+
INamedTypeSymbol type,
19+
Action<Diagnostic> reportDiagnostic)
1620
{
1721
var annotation = FlowAnnotations.GetTypeAnnotation(type);
1822

1923
// We need to apply annotations to this type, and its base/interface types (recursively)
2024
// But the annotations on base/interfaces may already be applied so we don't need to apply those
2125
// again (and should avoid doing so as it would produce extra warnings).
22-
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(reportDiagnostic, type);
26+
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(reportDiagnostic, typeNameResolver, type);
2327
if (type.BaseType is INamedTypeSymbol baseType)
2428
{
2529
var baseAnnotation = FlowAnnotations.GetTypeAnnotation(baseType);

src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,37 @@
88
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
99
<AnalysisLevel>Latest</AnalysisLevel>
1010
<NoWarn Condition="'$(DotNetBuildSourceOnly)' == 'true'">$(NoWarn);CS8524</NoWarn>
11+
<!-- Type conflicts with imported type. Silence this for source build to allow building with the latest
12+
source-included type name parser, instead of the one that comes with Roslyn. -->
13+
<NoWarn Condition="'$(DotNetBuildSourceOnly)' == 'true'">$(NoWarn);CS0436</NoWarn>
1114
<AnalyzerLanguage>cs</AnalyzerLanguage>
1215
<!-- The analyzer needs to process deeply nested expressions in corelib.
1316
This can blow up the stack if using unoptimized code (due to large
1417
stack frames with many temporary locals for debugging support), so we
1518
optimize the analyzer even in Debug builds. Note: we still use the
1619
Debug configuration to get Debug asserts. -->
1720
<Optimize>true</Optimize>
21+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1822
</PropertyGroup>
1923

2024
<ItemGroup>
2125
<Compile Include="$(CoreLibSharedDir)System/Index.cs" />
26+
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\Text\ValueStringBuilder.cs" />
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\HexConverter.cs" />
31+
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\Reflection\AssemblyNameFormatter.cs" />
32+
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\Reflection\AssemblyNameParser.cs" />
33+
<Compile Include="$(LibrariesProjectRoot)\System.Reflection.Metadata\src\System\Reflection\Metadata\AssemblyNameInfo.cs" />
34+
<Compile Include="$(LibrariesProjectRoot)\System.Reflection.Metadata\src\System\Reflection\Metadata\TypeName.cs" />
35+
<Compile Include="$(LibrariesProjectRoot)\System.Reflection.Metadata\src\System\Reflection\Metadata\TypeNameParseOptions.cs" />
36+
<Compile Include="$(LibrariesProjectRoot)\System.Reflection.Metadata\src\System\Reflection\Metadata\TypeNameParser.cs" />
37+
<Compile Include="$(LibrariesProjectRoot)\System.Reflection.Metadata\src\System\Reflection\Metadata\TypeNameParserHelpers.cs" />
38+
<EmbeddedResource Include="$(LibrariesProjectRoot)\System.Reflection.Metadata\src\Resources\Strings.resx">
39+
<GenerateSource>true</GenerateSource>
40+
<ClassName>System.Reflection.Metadata.SR</ClassName>
41+
</EmbeddedResource>
2242
</ItemGroup>
2343

2444
<ItemGroup>

src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,38 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis
1212
{
1313
internal static class GenericArgumentDataFlow
1414
{
15-
public static void ProcessGenericArgumentDataFlow(Location location, INamedTypeSymbol type, Action<Diagnostic> reportDiagnostic)
15+
public static void ProcessGenericArgumentDataFlow(TypeNameResolver typeNameResolver, Location location, INamedTypeSymbol type, Action<Diagnostic>? reportDiagnostic)
1616
{
1717
while (type is { IsGenericType: true })
1818
{
19-
ProcessGenericArgumentDataFlow(location, type.TypeArguments, type.TypeParameters, reportDiagnostic);
19+
ProcessGenericArgumentDataFlow(typeNameResolver, location, type.TypeArguments, type.TypeParameters, reportDiagnostic);
2020
type = type.ContainingType;
2121
}
2222
}
2323

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

28-
ProcessGenericArgumentDataFlow(location, method.ContainingType, reportDiagnostic);
28+
ProcessGenericArgumentDataFlow(typeNameResolver, location, method.ContainingType, reportDiagnostic);
2929
}
3030

31-
public static void ProcessGenericArgumentDataFlow(Location location, IFieldSymbol field, Action<Diagnostic> reportDiagnostic)
31+
public static void ProcessGenericArgumentDataFlow(TypeNameResolver typeNameResolver, Location location, IFieldSymbol field, Action<Diagnostic>? reportDiagnostic)
3232
{
33-
ProcessGenericArgumentDataFlow(location, field.ContainingType, reportDiagnostic);
33+
ProcessGenericArgumentDataFlow(typeNameResolver, location, field.ContainingType, reportDiagnostic);
3434
}
3535

36-
public static void ProcessGenericArgumentDataFlow(Location location, IPropertySymbol property, Action<Diagnostic> reportDiagnostic)
36+
public static void ProcessGenericArgumentDataFlow(TypeNameResolver typeNameResolver, Location location, IPropertySymbol property, Action<Diagnostic> reportDiagnostic)
3737
{
38-
ProcessGenericArgumentDataFlow(location, property.ContainingType, reportDiagnostic);
38+
ProcessGenericArgumentDataFlow(typeNameResolver, location, property.ContainingType, reportDiagnostic);
3939
}
4040

4141
private static void ProcessGenericArgumentDataFlow(
42+
TypeNameResolver typeNameResolver,
4243
Location location,
4344
ImmutableArray<ITypeSymbol> typeArguments,
4445
ImmutableArray<ITypeParameterSymbol> typeParameters,
45-
Action<Diagnostic> reportDiagnostic)
46+
Action<Diagnostic>? reportDiagnostic)
4647
{
4748
var diagnosticContext = new DiagnosticContext(location, reportDiagnostic);
4849
for (int i = 0; i < typeArguments.Length; i++)
@@ -53,14 +54,14 @@ private static void ProcessGenericArgumentDataFlow(
5354
if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None)
5455
{
5556
SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol(typeArgument)!;
56-
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(reportDiagnostic, typeHierarchyType: null);
57-
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(diagnosticContext, reflectionAccessAnalyzer);
57+
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(reportDiagnostic, typeNameResolver, typeHierarchyType: null);
58+
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer);
5859
requireDynamicallyAccessedMembersAction.Invoke(genericArgumentValue, genericParameterValue);
5960
}
6061

6162
// Recursively process generic argument data flow on the generic argument if it itself is generic
6263
if (typeArgument is INamedTypeSymbol namedTypeArgument && namedTypeArgument.IsGenericType)
63-
ProcessGenericArgumentDataFlow(location, namedTypeArgument, reportDiagnostic);
64+
ProcessGenericArgumentDataFlow(typeNameResolver, location, namedTypeArgument, reportDiagnostic);
6465
}
6566
}
6667

src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal partial struct HandleCallAction
2828
private ValueSetLattice<SingleValue> _multiValueLattice;
2929

3030
public HandleCallAction(
31+
TypeNameResolver typeNameResolver,
3132
Location location,
3233
ISymbol owningSymbol,
3334
IOperation operation,
@@ -39,8 +40,8 @@ public HandleCallAction(
3940
_isNewObj = operation.Kind == OperationKind.ObjectCreation;
4041
_diagnosticContext = new DiagnosticContext(location, reportDiagnostic);
4142
_annotations = FlowAnnotations.Instance;
42-
_reflectionAccessAnalyzer = new(reportDiagnostic, typeHierarchyType: null);
43-
_requireDynamicallyAccessedMembersAction = new(_diagnosticContext, _reflectionAccessAnalyzer);
43+
_reflectionAccessAnalyzer = new(reportDiagnostic, typeNameResolver, typeHierarchyType: null);
44+
_requireDynamicallyAccessedMembersAction = new(typeNameResolver, location, reportDiagnostic, _reflectionAccessAnalyzer);
4445
_multiValueLattice = multiValueLattice;
4546
}
4647

src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,22 @@
1212

1313
namespace ILLink.RoslynAnalyzer.TrimAnalysis
1414
{
15-
internal readonly struct ReflectionAccessAnalyzer
15+
readonly struct ReflectionAccessAnalyzer
1616
{
17-
private readonly Action<Diagnostic>? _reportDiagnostic;
18-
private readonly INamedTypeSymbol? _typeHierarchyType;
17+
readonly Action<Diagnostic>? _reportDiagnostic;
1918

20-
public ReflectionAccessAnalyzer(Action<Diagnostic>? reportDiagnostic, INamedTypeSymbol? typeHierarchyType)
19+
readonly INamedTypeSymbol? _typeHierarchyType;
20+
21+
readonly TypeNameResolver _typeNameResolver;
22+
23+
public ReflectionAccessAnalyzer(
24+
Action<Diagnostic>? reportDiagnostic,
25+
TypeNameResolver typeNameResolver,
26+
INamedTypeSymbol? typeHierarchyType)
2127
{
2228
_reportDiagnostic = reportDiagnostic;
2329
_typeHierarchyType = typeHierarchyType;
30+
_typeNameResolver = typeNameResolver;
2431
}
2532

2633
#pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods
@@ -212,5 +219,10 @@ private void GetDiagnosticsForField(Location location, IFieldSymbol fieldSymbol)
212219
diagnosticContext.AddDiagnostic(DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection, fieldSymbol.GetDisplayName());
213220
}
214221
}
222+
223+
internal bool TryResolveTypeNameAndMark(string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, [NotNullWhen(true)] out ITypeSymbol? type)
224+
{
225+
return _typeNameResolver.TryResolveTypeName(typeName, diagnosticContext, out type, needsAssemblyName);
226+
}
215227
}
216228
}
Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,56 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System;
5+
using System.Diagnostics;
46
using System.Diagnostics.CodeAnalysis;
7+
using System.Reflection.Metadata;
8+
using Microsoft.CodeAnalysis;
59
using ILLink.RoslynAnalyzer.TrimAnalysis;
610
using ILLink.Shared.TypeSystemProxy;
11+
using System.Collections.Immutable;
712

813
namespace ILLink.Shared.TrimAnalysis
914
{
1015
internal partial struct RequireDynamicallyAccessedMembersAction
1116
{
12-
private readonly ReflectionAccessAnalyzer _reflectionAccessAnalyzer;
17+
readonly Location _location;
18+
readonly Action<Diagnostic>? _reportDiagnostic;
19+
readonly ReflectionAccessAnalyzer _reflectionAccessAnalyzer;
20+
readonly TypeNameResolver _typeNameResolver;
1321
#pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods
1422
#pragma warning disable IDE0060 // Unused parameters - should be removed once methods are actually implemented
1523

1624
public RequireDynamicallyAccessedMembersAction(
17-
DiagnosticContext diagnosticContext,
25+
TypeNameResolver typeNameResolver,
26+
Location location,
27+
Action<Diagnostic>? reportDiagnostic,
1828
ReflectionAccessAnalyzer reflectionAccessAnalyzer)
1929
{
20-
_diagnosticContext = diagnosticContext;
30+
_typeNameResolver = typeNameResolver;
31+
_location = location;
32+
_reportDiagnostic = reportDiagnostic;
2133
_reflectionAccessAnalyzer = reflectionAccessAnalyzer;
34+
_diagnosticContext = new(location, reportDiagnostic);
2235
}
2336

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

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

3349
type = default;
3450
return false;
3551
}
3652

3753
private partial void MarkTypeForDynamicallyAccessedMembers(in TypeProxy type, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes) =>
38-
_reflectionAccessAnalyzer.GetReflectionAccessDiagnostics(_diagnosticContext.Location, type.Type, dynamicallyAccessedMemberTypes);
54+
_reflectionAccessAnalyzer.GetReflectionAccessDiagnostics(_location, type.Type, dynamicallyAccessedMemberTypes);
3955
}
4056
}

0 commit comments

Comments
 (0)