Skip to content

Commit b025166

Browse files
sbomerdirecthex
authored andcommitted
Support field access on GetType() of T constrained to be Enum (#105351)
Adds trimming support for instance.GetType().GetFields(), where instance is a variable of type `T` that is constrained to System.Enum. This includes a change to have ILLink's TypeProxy track a TypeReference instead of TypeDefinition, which was necessary to allow TypeProxy to represent a generic parameter. Note that this only supports the specific case where `GetType()` is called on a variable of type `T` that is constrained to `Enum`. A variable of type `Enum` is not supported, so the following will still warn: ```csharp static void M(Enum v) { v.GetType().GetFields(); } ```
1 parent 2af5db2 commit b025166

File tree

13 files changed

+363
-53
lines changed

13 files changed

+363
-53
lines changed

src/coreclr/tools/Common/Compiler/Dataflow/ParameterProxy.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ namespace ILLink.Shared.TypeSystemProxy
1313
internal partial struct ParameterProxy
1414
{
1515
public partial ReferenceKind GetReferenceKind() => Method.Method.ParameterReferenceKind((int)Index);
16-
public TypeDesc ParameterType => IsImplicitThis ? Method.Method.OwningType : Method.Method.Signature[MetadataIndex];
16+
public TypeDesc ParameterType => IsImplicitThis
17+
? Method.Method.OwningType
18+
: Method.Method.Signature[MetadataIndex].InstantiateSignature (Method.Method.OwningType.Instantiation, Method.Method.Instantiation);
1719

1820
public partial string GetDisplayName() => IsImplicitThis ? "this"
1921
: (Method.Method is EcmaMethod ecmaMethod) ? ecmaMethod.GetParameterDisplayName(MetadataIndex)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Reflection;
78
using ILCompiler;
89
using ILCompiler.Dataflow;
@@ -377,8 +378,7 @@ private partial bool TryHandleIntrinsic (
377378
// Note that valueNode can be statically typed in IL as some generic argument type.
378379
// For example:
379380
// void Method<T>(T instance) { instance.GetType().... }
380-
// Currently this case will end up with null StaticType - since there's no typedef for the generic argument type.
381-
// But it could be that T is annotated with for example PublicMethods:
381+
// It could be that T is annotated with for example PublicMethods:
382382
// void Method<[DAM(PublicMethods)] T>(T instance) { instance.GetType().GetMethod("Test"); }
383383
// In this case it's in theory possible to handle it, by treating the T basically as a base class
384384
// for the actual type of "instance". But the analysis for this would be pretty complicated (as the marking
@@ -392,8 +392,28 @@ private partial bool TryHandleIntrinsic (
392392
TypeDesc? staticType = (valueNode as IValueWithStaticType)?.StaticType?.Type;
393393
if (staticType is null || (!staticType.IsDefType && !staticType.IsArray))
394394
{
395-
// We don't know anything about the type GetType was called on. Track this as a usual "result of a method call without any annotations"
396-
AddReturnValue(_reflectionMarker.Annotations.GetMethodReturnValue(calledMethod, _isNewObj));
395+
DynamicallyAccessedMemberTypes annotation = default;
396+
if (staticType is GenericParameterDesc genericParam)
397+
{
398+
foreach (TypeDesc constraint in genericParam.TypeConstraints)
399+
{
400+
if (constraint.IsWellKnownType(Internal.TypeSystem.WellKnownType.Enum))
401+
{
402+
annotation = DynamicallyAccessedMemberTypes.PublicFields;
403+
break;
404+
}
405+
}
406+
}
407+
408+
if (annotation != default)
409+
{
410+
AddReturnValue(_reflectionMarker.Annotations.GetMethodReturnValue(calledMethod, _isNewObj, annotation));
411+
}
412+
else
413+
{
414+
// We don't know anything about the type GetType was called on. Track this as a usual "result of a method call without any annotations"
415+
AddReturnValue(_reflectionMarker.Annotations.GetMethodReturnValue(calledMethod, _isNewObj));
416+
}
397417
}
398418
else if (staticType.IsSealed() || staticType.IsTypeOf("System", "Delegate"))
399419
{

src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyQualifiedToken.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ namespace Mono.Linker.Tests.TestCasesRunner
2020

2121
public AssemblyQualifiedToken (string? assemblyName, int token) => (AssemblyName, Token) = (assemblyName, token);
2222

23-
public AssemblyQualifiedToken (TypeSystemEntity entity) =>
23+
public AssemblyQualifiedToken (TypeSystemEntity entity) {
24+
if (entity is MethodForInstantiatedType instantiatedMethod)
25+
entity = instantiatedMethod.GetTypicalMethodDefinition ();
2426
(AssemblyName, Token) = entity switch {
2527
EcmaType type => (type.Module.Assembly.GetName ().Name, MetadataTokens.GetToken (type.Handle)),
2628
EcmaMethod method => (method.Module.Assembly.GetName ().Name, MetadataTokens.GetToken (method.Handle)),
@@ -31,6 +33,7 @@ public AssemblyQualifiedToken (TypeSystemEntity entity) =>
3133
MetadataType mt when mt.GetType().Name == "BoxedValueType" => (null, 0),
3234
_ => throw new NotSupportedException ($"The infra doesn't support getting a token for {entity} yet.")
3335
};
36+
}
3437

3538
public AssemblyQualifiedToken (IMemberDefinition member) =>
3639
(AssemblyName, Token) = member switch {

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Reflection;
78
using ILLink.RoslynAnalyzer;
89
using ILLink.RoslynAnalyzer.DataFlow;
@@ -78,16 +79,23 @@ private partial bool TryHandleIntrinsic (
7879
// In this case to get correct results, trimmer would have to mark all public methods on Derived. Which
7980
// currently it won't do.
8081

81-
// To emulate IL tools behavior (trimmer, NativeAOT compiler), we're going to intentionally "forget" the static type
82-
// if it is a generic argument type.
83-
8482
ITypeSymbol? staticType = (valueNode as IValueWithStaticType)?.StaticType?.Type;
85-
if (staticType?.TypeKind == TypeKind.TypeParameter)
86-
staticType = null;
87-
88-
if (staticType is null) {
89-
// We don't know anything about the type GetType was called on. Track this as a usual "result of a method call without any annotations"
90-
AddReturnValue (FlowAnnotations.Instance.GetMethodReturnValue (calledMethod, _isNewObj));
83+
ITypeSymbol? staticTypeDef = staticType?.OriginalDefinition;
84+
if (staticType is null || staticTypeDef is null || staticType is ITypeParameterSymbol) {
85+
DynamicallyAccessedMemberTypes annotation = default;
86+
if (staticType is ITypeParameterSymbol genericParam) {
87+
foreach (var constraintType in genericParam.ConstraintTypes) {
88+
if (constraintType.IsTypeOf ("System", "Enum"))
89+
annotation = DynamicallyAccessedMemberTypes.PublicFields;
90+
}
91+
}
92+
93+
if (annotation != default) {
94+
AddReturnValue (FlowAnnotations.Instance.GetMethodReturnValue (calledMethod, _isNewObj, annotation));
95+
} else {
96+
// We don't know anything about the type GetType was called on. Track this as a usual "result of a method call without any annotations"
97+
AddReturnValue (FlowAnnotations.Instance.GetMethodReturnValue (calledMethod, _isNewObj));
98+
}
9199
} else if (staticType.IsSealed || staticType.IsTypeOf ("System", "Delegate") || staticType.TypeKind == TypeKind.Array) {
92100
// We can treat this one the same as if it was a typeof() expression
93101

src/tools/illink/src/linker/Linker.Dataflow/DynamicallyAccessedMembersBinder.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static IEnumerable<IMetadataTokenProvider> GetDynamicallyAccessedMembers
6666
}
6767

6868
if (memberTypes.HasFlag (DynamicallyAccessedMemberTypes.NonPublicNestedTypes)) {
69-
foreach (var nested in typeDefinition.GetNestedTypesOnType (filter: null, bindingFlags: BindingFlags.NonPublic)) {
69+
foreach (var nested in typeDefinition.GetNestedTypesOnType (context, filter: null, bindingFlags: BindingFlags.NonPublic)) {
7070
yield return nested;
7171
var members = new List<IMetadataTokenProvider> ();
7272
nested.GetAllOnType (context, declaredOnly: false, members);
@@ -76,7 +76,7 @@ public static IEnumerable<IMetadataTokenProvider> GetDynamicallyAccessedMembers
7676
}
7777

7878
if (memberTypes.HasFlag (DynamicallyAccessedMemberTypes.PublicNestedTypes)) {
79-
foreach (var nested in typeDefinition.GetNestedTypesOnType (filter: null, bindingFlags: BindingFlags.Public)) {
79+
foreach (var nested in typeDefinition.GetNestedTypesOnType (context, filter: null, bindingFlags: BindingFlags.Public)) {
8080
yield return nested;
8181
var members = new List<IMetadataTokenProvider> ();
8282
nested.GetAllOnType (context, declaredOnly: false, members);
@@ -136,9 +136,9 @@ public static IEnumerable<MethodDefinition> GetConstructorsOnType (this TypeDefi
136136
}
137137
}
138138

139-
public static IEnumerable<MethodDefinition> GetMethodsOnTypeHierarchy (this TypeDefinition thisType, LinkContext context, Func<MethodDefinition, bool>? filter, BindingFlags? bindingFlags = null)
139+
public static IEnumerable<MethodDefinition> GetMethodsOnTypeHierarchy (this TypeReference thisType, LinkContext context, Func<MethodDefinition, bool>? filter, BindingFlags? bindingFlags = null)
140140
{
141-
TypeDefinition? type = thisType;
141+
TypeDefinition? type = thisType.ResolveToTypeDefinition (context);
142142
bool onBaseType = false;
143143
while (type != null) {
144144
foreach (var method in type.Methods) {
@@ -220,8 +220,11 @@ public static IEnumerable<FieldDefinition> GetFieldsOnTypeHierarchy (this TypeDe
220220
}
221221
}
222222

223-
public static IEnumerable<TypeDefinition> GetNestedTypesOnType (this TypeDefinition type, Func<TypeDefinition, bool>? filter, BindingFlags? bindingFlags = BindingFlags.Default)
223+
public static IEnumerable<TypeDefinition> GetNestedTypesOnType (this TypeReference typeRef, LinkContext context, Func<TypeDefinition, bool>? filter, BindingFlags? bindingFlags = BindingFlags.Default)
224224
{
225+
if (typeRef.ResolveToTypeDefinition (context) is not TypeDefinition type)
226+
yield break;
227+
225228
foreach (var nestedType in type.NestedTypes) {
226229
if (filter != null && !filter (nestedType))
227230
continue;

src/tools/illink/src/linker/Linker.Dataflow/FieldValue.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using ILLink.Shared.DataFlow;
77
using Mono.Linker;
88
using FieldDefinition = Mono.Cecil.FieldDefinition;
9-
using TypeDefinition = Mono.Cecil.TypeDefinition;
9+
using TypeReference = Mono.Cecil.TypeReference;
1010

1111

1212
namespace ILLink.Shared.TrimAnalysis
@@ -17,7 +17,7 @@ namespace ILLink.Shared.TrimAnalysis
1717
/// </summary>
1818
internal sealed partial record FieldValue
1919
{
20-
public FieldValue (TypeDefinition? staticType, FieldDefinition fieldToLoad, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
20+
public FieldValue (TypeReference? staticType, FieldDefinition fieldToLoad, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
2121
{
2222
StaticType = staticType == null ? null : new (staticType);
2323
Field = fieldToLoad;

src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -687,8 +687,10 @@ public FieldAnnotation (FieldDefinition field, DynamicallyAccessedMemberTypes an
687687
internal partial bool MethodRequiresDataFlowAnalysis (MethodProxy method)
688688
=> RequiresDataFlowAnalysis (method.Method);
689689

690+
#pragma warning disable CA1822 // Mark members as static - Should be an instance method for consistency
690691
internal partial MethodReturnValue GetMethodReturnValue (MethodProxy method, bool isNewObj, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
691-
=> MethodReturnValue.Create (method.Method, isNewObj, dynamicallyAccessedMemberTypes, _context);
692+
=> MethodReturnValue.Create (method.Method, isNewObj, dynamicallyAccessedMemberTypes);
693+
#pragma warning restore CA1822 // Mark members as static
692694

693695
internal partial MethodReturnValue GetMethodReturnValue (MethodProxy method, bool isNewObj)
694696
=> GetMethodReturnValue (method, isNewObj, GetReturnParameterAnnotation (method.Method));
@@ -701,8 +703,10 @@ internal partial GenericParameterValue GetGenericParameterValue (GenericParamete
701703
internal partial GenericParameterValue GetGenericParameterValue (GenericParameterProxy genericParameter)
702704
=> new GenericParameterValue (genericParameter.GenericParameter, GetGenericParameterAnnotation (genericParameter.GenericParameter));
703705

706+
#pragma warning disable CA1822 // Mark members as static - Should be an instance method for consistency
704707
internal partial MethodParameterValue GetMethodParameterValue (ParameterProxy param, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
705-
=> new (param.ParameterType.ResolveToTypeDefinition (_context), param, dynamicallyAccessedMemberTypes);
708+
=> new (param.ParameterType, param, dynamicallyAccessedMemberTypes);
709+
#pragma warning restore CA1822 // Mark members as static
706710

707711
internal partial MethodParameterValue GetMethodParameterValue (ParameterProxy param)
708712
=> GetMethodParameterValue (param, GetParameterAnnotation (param));
@@ -730,7 +734,7 @@ internal SingleValue GetFieldValue (FieldDefinition field)
730734
=> field.Name switch {
731735
"EmptyTypes" when field.DeclaringType.IsTypeOf (WellKnownType.System_Type) => ArrayValue.Create (0, field.DeclaringType),
732736
"Empty" when field.DeclaringType.IsTypeOf (WellKnownType.System_String) => new KnownStringValue (string.Empty),
733-
_ => new FieldValue (field.FieldType.ResolveToTypeDefinition (_context), field, GetFieldAnnotation (field))
737+
_ => new FieldValue (field.FieldType, field, GetFieldAnnotation (field))
734738
};
735739

736740
internal SingleValue GetTypeValueFromGenericArgument (TypeReference genericArgument)

src/tools/illink/src/linker/Linker.Dataflow/HandleCallAction.cs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ private partial bool TryHandleIntrinsic (
108108
// Note that valueNode can be statically typed in IL as some generic argument type.
109109
// For example:
110110
// void Method<T>(T instance) { instance.GetType().... }
111-
// Currently this case will end up with null StaticType - since there's no typedef for the generic argument type.
112-
// But it could be that T is annotated with for example PublicMethods:
111+
// It could be that T is annotated with for example PublicMethods:
113112
// void Method<[DAM(PublicMethods)] T>(T instance) { instance.GetType().GetMethod("Test"); }
114113
// In this case it's in theory possible to handle it, by treating the T basically as a base class
115114
// for the actual type of "instance". But the analysis for this would be pretty complicated (as the marking
@@ -120,11 +119,24 @@ private partial bool TryHandleIntrinsic (
120119
// In this case to get correct results, trimmer would have to mark all public methods on Derived. Which
121120
// currently it won't do.
122121

123-
TypeDefinition? staticType = (valueNode as IValueWithStaticType)?.StaticType?.Type;
124-
if (staticType is null) {
125-
// We don't know anything about the type GetType was called on. Track this as a usual result of a method call without any annotations
126-
AddReturnValue (_context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethod, _isNewObj));
127-
} else if (staticType.IsSealed || staticType.IsTypeOf ("System", "Delegate") || staticType.IsTypeOf ("System", "Array")) {
122+
TypeReference? staticType = (valueNode as IValueWithStaticType)?.StaticType?.Type;
123+
TypeDefinition? staticTypeDef = staticType?.ResolveToTypeDefinition (_context);
124+
if (staticType is null || staticTypeDef is null) {
125+
DynamicallyAccessedMemberTypes annotation = default;
126+
if (staticType is GenericParameter genericParam && genericParam.HasConstraints) {
127+
foreach (var constraint in genericParam.Constraints) {
128+
if (constraint.ConstraintType.IsTypeOf ("System", "Enum"))
129+
annotation = DynamicallyAccessedMemberTypes.PublicFields;
130+
}
131+
}
132+
133+
if (annotation != default) {
134+
AddReturnValue (_context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethod, _isNewObj, annotation));
135+
} else {
136+
// We don't know anything about the type GetType was called on. Track this as a usual result of a method call without any annotations
137+
AddReturnValue (_context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethod, _isNewObj));
138+
}
139+
} else if (staticTypeDef.IsSealed || staticTypeDef.IsTypeOf ("System", "Delegate") || staticTypeDef.IsTypeOf ("System", "Array")) {
128140
// We can treat this one the same as if it was a typeof() expression
129141

130142
// We can allow Object.GetType to be modeled as System.Delegate because we keep all methods
@@ -147,7 +159,7 @@ private partial bool TryHandleIntrinsic (
147159
_reflectionMarker.MarkType (_diagnosticContext.Origin, staticType);
148160

149161
var annotation = _markStep.DynamicallyAccessedMembersTypeHierarchy
150-
.ApplyDynamicallyAccessedMembersToTypeHierarchy (staticType);
162+
.ApplyDynamicallyAccessedMembersToTypeHierarchy (staticTypeDef);
151163

152164
// Return a value which is "unknown type" with annotation. For now we'll use the return value node
153165
// for the method, which means we're loosing the information about which staticType this
@@ -200,13 +212,13 @@ private partial IEnumerable<SystemReflectionMethodBaseValue> GetMethodsOnTypeHie
200212

201213
private partial IEnumerable<SystemTypeValue> GetNestedTypesOnType (TypeProxy type, string name, BindingFlags? bindingFlags)
202214
{
203-
foreach (var nestedType in type.Type.GetNestedTypesOnType (t => t.Name == name, bindingFlags))
215+
foreach (var nestedType in type.Type.GetNestedTypesOnType (_context, t => t.Name == name, bindingFlags))
204216
yield return new SystemTypeValue (new TypeProxy (nestedType));
205217
}
206218

207219
private partial bool TryGetBaseType (TypeProxy type, out TypeProxy? baseType)
208220
{
209-
if (type.Type.BaseType is TypeReference baseTypeRef && _context.TryResolve (baseTypeRef) is TypeDefinition baseTypeDefinition) {
221+
if (type.Type.ResolveToTypeDefinition (_context)?.BaseType is TypeReference baseTypeRef && _context.TryResolve (baseTypeRef) is TypeDefinition baseTypeDefinition) {
210222
baseType = new TypeProxy (baseTypeDefinition);
211223
return true;
212224
}

src/tools/illink/src/linker/Linker.Dataflow/MethodParameterValue.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
using System.Diagnostics.CodeAnalysis;
55
using ILLink.Shared.TypeSystemProxy;
6-
using Mono.Linker.Dataflow;
7-
using TypeDefinition = Mono.Cecil.TypeDefinition;
6+
using TypeReference = Mono.Cecil.TypeReference;
87

98

109
namespace ILLink.Shared.TrimAnalysis
@@ -15,7 +14,7 @@ namespace ILLink.Shared.TrimAnalysis
1514
/// </summary>
1615
internal partial record MethodParameterValue
1716
{
18-
public MethodParameterValue (TypeDefinition? staticType, ParameterProxy param, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
17+
public MethodParameterValue (TypeReference? staticType, ParameterProxy param, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
1918
{
2019
StaticType = staticType == null ? null : new (staticType);
2120
DynamicallyAccessedMemberTypes = dynamicallyAccessedMemberTypes;

src/tools/illink/src/linker/Linker.Dataflow/MethodReturnValue.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
using System.Diagnostics.CodeAnalysis;
77
using ILLink.Shared.DataFlow;
88
using Mono.Cecil;
9-
using Mono.Linker;
109
using Mono.Linker.Dataflow;
11-
using TypeDefinition = Mono.Cecil.TypeDefinition;
10+
using TypeReference = Mono.Cecil.TypeReference;
1211

1312

1413
namespace ILLink.Shared.TrimAnalysis
@@ -18,14 +17,14 @@ namespace ILLink.Shared.TrimAnalysis
1817
/// </summary>
1918
internal partial record MethodReturnValue
2019
{
21-
public static MethodReturnValue Create (MethodDefinition method, bool isNewObj, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes, LinkContext context)
20+
public static MethodReturnValue Create (MethodDefinition method, bool isNewObj, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
2221
{
2322
Debug.Assert (!isNewObj || method.IsConstructor, "isNewObj can only be true for constructors");
24-
var staticType = isNewObj ? method.DeclaringType : method.ReturnType.ResolveToTypeDefinition (context);
23+
var staticType = isNewObj ? method.DeclaringType : method.ReturnType;
2524
return new MethodReturnValue (staticType, method, dynamicallyAccessedMemberTypes);
2625
}
2726

28-
private MethodReturnValue (TypeDefinition? staticType, MethodDefinition method, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
27+
private MethodReturnValue (TypeReference? staticType, MethodDefinition method, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
2928
{
3029
StaticType = staticType == null ? null : new (staticType);
3130
Method = method;

0 commit comments

Comments
 (0)