Skip to content

Commit 2694613

Browse files
authored
Don't keep members of pointer or byref element types (#106215)
Fixes the following warnings in ILLink: ```csharp var type = Type.GetType ("ElementType&, test"); RequireConstructor(type); // IL2026 [RequiresUnreferencedCode("ElementType")] class ElementType {} static void RequireConstructor([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) { } ``` This warns for reflection access to the ElementType constructor, even though the byref type has no constructor. NativeAot doesn't produce this warning.
1 parent fbf4672 commit 2694613

File tree

6 files changed

+63
-49
lines changed

6 files changed

+63
-49
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,8 @@ private partial bool TryResolveTypeNameForCreateInstanceAndMark (in MethodProxy
245245
return false;
246246
}
247247

248-
if (!_reflectionMarker.TryResolveTypeNameAndMark (resolvedAssembly, typeName, _diagnosticContext, out TypeDefinition? resolvedTypeDefinition)
249-
|| resolvedTypeDefinition.IsTypeOf (WellKnownType.System_Array)) {
248+
if (!_reflectionMarker.TryResolveTypeNameAndMark (resolvedAssembly, typeName, _diagnosticContext, out TypeReference? foundType)
249+
|| foundType.IsTypeOf (WellKnownType.System_Array)) {
250250
// It's not wrong to have a reference to non-existing type - the code may well expect to get an exception in this case
251251
// Note that we did find the assembly, so it's not a ILLink config problem, it's either intentional, or wrong versions of assemblies
252252
// but ILLink can't know that. In case a user tries to create an array using System.Activator we should simply ignore it, the user
@@ -255,7 +255,7 @@ private partial bool TryResolveTypeNameForCreateInstanceAndMark (in MethodProxy
255255
return false;
256256
}
257257

258-
resolvedType = new TypeProxy (resolvedTypeDefinition, _context);
258+
resolvedType = new TypeProxy (foundType, _context);
259259
return true;
260260
}
261261

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

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,39 +58,32 @@ internal void MarkTypeForDynamicallyAccessedMembers (in MessageOrigin origin, Ty
5858

5959
// Resolve a (potentially assembly qualified) type name based on the current context (taken from DiagnosticContext) and mark the type for reflection.
6060
// This method will probe the current context assembly and if that fails CoreLib for the specified type. Emulates behavior of Type.GetType.
61-
internal bool TryResolveTypeNameAndMark (string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, [NotNullWhen (true)] out TypeDefinition? type)
61+
internal bool TryResolveTypeNameAndMark (string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, [NotNullWhen (true)] out TypeReference? type)
6262
{
63-
if (!_context.TypeNameResolver.TryResolveTypeName (typeName, diagnosticContext, out TypeReference? typeRef, out var typeResolutionRecords, needsAssemblyName)
64-
|| typeRef.ResolveToTypeDefinition (_context) is not TypeDefinition foundType) {
63+
if (!_context.TypeNameResolver.TryResolveTypeName (typeName, diagnosticContext, out type, out var typeResolutionRecords, needsAssemblyName)) {
6564
type = default;
6665
return false;
6766
}
6867

69-
MarkResolvedType (diagnosticContext, typeRef, foundType, typeResolutionRecords);
70-
71-
type = foundType;
68+
MarkType (diagnosticContext, type, typeResolutionRecords);
7269
return true;
7370
}
7471

7572
// Resolve a type from the specified assembly and mark it for reflection.
76-
internal bool TryResolveTypeNameAndMark (AssemblyDefinition assembly, string typeName, in DiagnosticContext diagnosticContext, [NotNullWhen (true)] out TypeDefinition? type)
73+
internal bool TryResolveTypeNameAndMark (AssemblyDefinition assembly, string typeName, in DiagnosticContext diagnosticContext, [NotNullWhen (true)] out TypeReference? type)
7774
{
78-
if (!_context.TypeNameResolver.TryResolveTypeName (assembly, typeName, out TypeReference? typeRef, out var typeResolutionRecords)
79-
|| typeRef.ResolveToTypeDefinition (_context) is not TypeDefinition foundType) {
75+
if (!_context.TypeNameResolver.TryResolveTypeName (assembly, typeName, out type, out var typeResolutionRecords)) {
8076
type = default;
8177
return false;
8278
}
8379

84-
MarkResolvedType (diagnosticContext, typeRef, foundType, typeResolutionRecords);
85-
86-
type = foundType;
80+
MarkType (diagnosticContext, type, typeResolutionRecords);
8781
return true;
8882
}
8983

90-
void MarkResolvedType (
84+
void MarkType (
9185
in DiagnosticContext diagnosticContext,
9286
TypeReference typeReference,
93-
TypeDefinition typeDefinition,
9487
List<TypeNameResolver.TypeResolutionRecord> typeResolutionRecords)
9588
{
9689
if (_enabled) {
@@ -100,9 +93,9 @@ void MarkResolvedType (
10093
// This is necessary because if the app's code contains the input string as literal (which is pretty much always the case)
10194
// that string has to work at runtime, and if it relies on type forwarders we need to preserve those as well.
10295
var origin = diagnosticContext.Origin;
103-
_markStep.MarkTypeVisibleToReflection (typeReference, typeDefinition, new DependencyInfo (DependencyKind.AccessedViaReflection, origin.Provider), origin);
96+
_markStep.MarkTypeVisibleToReflection (typeReference, new DependencyInfo (DependencyKind.AccessedViaReflection, origin.Provider), origin);
10497
foreach (var typeResolutionRecord in typeResolutionRecords) {
105-
_context.MarkingHelpers.MarkMatchingExportedType (typeResolutionRecord.ResolvedType, typeResolutionRecord.ReferringAssembly, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, typeDefinition), origin);
98+
_context.MarkingHelpers.MarkMatchingExportedType (typeResolutionRecord.ResolvedType, typeResolutionRecord.ReferringAssembly, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, typeReference), origin);
10699
}
107100
}
108101
}
@@ -115,7 +108,7 @@ internal void MarkType (in MessageOrigin origin, TypeReference typeRef, Dependen
115108
if (typeRef.ResolveToTypeDefinition (_context) is not TypeDefinition type)
116109
return;
117110

118-
_markStep.MarkTypeVisibleToReflection (type, type, new DependencyInfo (dependencyKind, origin.Provider), origin);
111+
_markStep.MarkTypeVisibleToReflection (type, new DependencyInfo (dependencyKind, origin.Provider), origin);
119112
}
120113

121114
internal void MarkMethod (in MessageOrigin origin, MethodReference methodRef, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public RequireDynamicallyAccessedMembersAction (
2626

2727
public partial bool TryResolveTypeNameAndMark (string typeName, bool needsAssemblyName, out TypeProxy type)
2828
{
29-
if (_reflectionMarker.TryResolveTypeNameAndMark (typeName, _diagnosticContext, needsAssemblyName, out TypeDefinition? foundType)) {
29+
if (_reflectionMarker.TryResolveTypeNameAndMark (typeName, _diagnosticContext, needsAssemblyName, out TypeReference? foundType)) {
3030
type = new (foundType, _resolver);
3131
return true;
3232
} else {

src/tools/illink/src/linker/Linker.Steps/MarkStep.cs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ internal void MarkEntireType (TypeDefinition type, in DependencyInfo reason, Mes
325325
MarkEntireType (nested, new DependencyInfo (DependencyKind.NestedType, type), origin);
326326
}
327327

328-
MarkTypeVisibleToReflection (type, type, reason, origin);
328+
MarkTypeVisibleToReflection (type, reason, origin);
329329
MarkCustomAttributes (type, new DependencyInfo (DependencyKind.CustomAttribute, type), origin);
330330
MarkTypeSpecialCustomAttributes (type, origin);
331331

@@ -914,7 +914,7 @@ void MarkMembersVisibleToReflection (IEnumerable<IMetadataTokenProvider> members
914914
foreach (var member in members) {
915915
switch (member) {
916916
case TypeDefinition type:
917-
MarkTypeVisibleToReflection (type, type, reason, origin);
917+
MarkTypeVisibleToReflection (type, reason, origin);
918918
break;
919919
case MethodDefinition method:
920920
MarkMethodVisibleToReflection (method, reason, origin);
@@ -1795,18 +1795,17 @@ protected virtual void MarkSerializable (TypeDefinition type, MessageOrigin orig
17951795
MarkMethodsIf (type.Methods, HasOnSerializeOrDeserializeAttribute, new DependencyInfo (DependencyKind.SerializationMethodForType, type), origin);
17961796
}
17971797

1798-
protected internal virtual TypeDefinition? MarkTypeVisibleToReflection (TypeReference type, TypeDefinition definition, in DependencyInfo reason, in MessageOrigin origin)
1798+
protected internal virtual void MarkTypeVisibleToReflection (TypeReference type, in DependencyInfo reason, in MessageOrigin origin)
17991799
{
1800-
// If a type is visible to reflection, we need to stop doing optimization that could cause observable difference
1801-
// in reflection APIs. This includes APIs like MakeGenericType (where variant castability of the produced type
1802-
// could be incorrect) or IsAssignableFrom (where assignability of unconstructed types might change).
1803-
Annotations.MarkRelevantToVariantCasting (definition);
1804-
1805-
Annotations.MarkReflectionUsed (definition);
1806-
1807-
MarkImplicitlyUsedFields (definition, origin);
1808-
1809-
return MarkType (type, reason, origin);
1800+
TypeDefinition? definition = MarkType (type, reason, origin);
1801+
if (definition is not null) {
1802+
// If a type is visible to reflection, we need to stop doing optimization that could cause observable difference
1803+
// in reflection APIs. This includes APIs like MakeGenericType (where variant castability of the produced type
1804+
// could be incorrect) or IsAssignableFrom (where assignability of unconstructed types might change).
1805+
Annotations.MarkRelevantToVariantCasting (definition);
1806+
Annotations.MarkReflectionUsed (definition);
1807+
MarkImplicitlyUsedFields (definition, origin);
1808+
}
18101809
}
18111810

18121811
internal void MarkMethodVisibleToReflection (MethodReference method, in DependencyInfo reason, in MessageOrigin origin)
@@ -3640,9 +3639,7 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio
36403639
origin = new MessageOrigin (origin, instruction.Offset);
36413640

36423641
if (token is TypeReference typeReference) {
3643-
// Error will be reported as part of MarkType
3644-
if (Context.TryResolve (typeReference) is TypeDefinition type)
3645-
MarkTypeVisibleToReflection (typeReference, type, reason, origin);
3642+
MarkTypeVisibleToReflection (typeReference, reason, origin);
36463643
} else if (token is MethodReference methodReference) {
36473644
MarkMethodVisibleToReflection (methodReference, reason, origin);
36483645
} else {

src/tools/illink/src/linker/Linker/TypeReferenceExtensions.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -432,13 +432,18 @@ public static bool IsNamedType (this TypeReference typeReference) {
432432
return true;
433433
}
434434

435-
// Array types that are dynamically accessed should resolve to System.Array instead of its element type - which is what Cecil resolves to.
436-
// Any data flow annotations placed on a type parameter which receives an array type apply to the array itself. None of the members in its
437-
// element type should be marked.
435+
/// <summary>
436+
/// Resolves a TypeReference to a TypeDefinition if possible. Non-named types other than arrays (pointers, byrefs, function pointers) return null.
437+
/// Array types that are dynamically accessed resolve to System.Array instead of its element type - which is what Cecil resolves to.
438+
/// Any data flow annotations placed on a type parameter which receives an array type apply to the array itself. None of the members in its
439+
/// element type should be marked.
440+
/// </summary>
438441
public static TypeDefinition? ResolveToTypeDefinition (this TypeReference typeReference, LinkContext context)
439442
=> typeReference is ArrayType
440443
? BCL.FindPredefinedType (WellKnownType.System_Array, context)
441-
: context.TryResolve (typeReference);
444+
: typeReference.IsNamedType ()
445+
? context.TryResolve (typeReference)
446+
: null;
442447

443448
public static bool IsByRefOrPointer (this TypeReference typeReference)
444449
{

src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public static void Main ()
6060
TestInvalidTypeCombination ();
6161
TestEscapedTypeName ();
6262
AssemblyTypeResolutionBehavior.Test ();
63+
InstantiatedGenericEquality.Test ();
6364
}
6465

6566
[Kept]
@@ -213,15 +214,11 @@ public static void TestType ()
213214
}
214215

215216
[Kept]
216-
#if !NATIVEAOT // https://github.com/dotnet/runtime/issues/106214
217-
[KeptMember (".ctor()")]
218-
#endif
219217
[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
220218
[RequiresUnreferencedCode (nameof (Pointer))]
221219
public class Pointer { }
222220

223221
[Kept]
224-
[UnexpectedWarning ("IL2026", nameof (Pointer), Tool.Trimmer, "https://github.com/dotnet/runtime/issues/106214")]
225222
public static void TestPointer ()
226223
{
227224
const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Pointer*";
@@ -230,15 +227,11 @@ public static void TestPointer ()
230227
}
231228

232229
[Kept]
233-
#if !NATIVEAOT // https://github.com/dotnet/runtime/issues/106214
234-
[KeptMember (".ctor()")]
235-
#endif
236230
[KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))]
237231
[RequiresUnreferencedCode (nameof (Reference))]
238232
public class Reference { }
239233

240234
[Kept]
241-
[UnexpectedWarning ("IL2026", nameof (Reference), Tool.Trimmer, "https://github.com/dotnet/runtime/issues/106214")]
242235
public static void TestReference ()
243236
{
244237
const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Reference&";
@@ -686,6 +679,32 @@ class PointerElementGenericArgumentType {}
686679
class ByRefElementGenericArgumentType {}
687680
}
688681

682+
[Kept]
683+
class InstantiatedGenericEquality
684+
{
685+
[Kept]
686+
class Generic<T> {
687+
[Kept]
688+
public void Method () { }
689+
}
690+
691+
// Regression test for an issue where ILLink's representation of a generic instantiated type
692+
// was using reference equality. The test uses a lambda to ensure that it goes through the
693+
// interprocedural analysis code path that merges patterns and relies on a correct implementation
694+
// of equality.
695+
[Kept]
696+
public static void Test ()
697+
{
698+
var type = Type.GetType("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+InstantiatedGenericEquality+Generic`1[[System.Int32]]");
699+
700+
var lambda = () => {
701+
type.GetMethod ("Method");
702+
};
703+
704+
lambda ();
705+
}
706+
}
707+
689708
[Kept]
690709
static void RequireConstructor (
691710
[KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))]

0 commit comments

Comments
 (0)