Skip to content

Commit bb418ad

Browse files
Propagate Type.GetInterfaces through dataflow analysis (#114149)
Fixes dotnet/linker#1731 We currently maintain two invariants: 1. The types returned by the API will have `.Interfaces` annotations at minimum 2. If the parent type was annotated `.All`, the types returned by the API will also be `.All` We do this in the logic that keeps things, but we don't do this in terms of dataflow analysis warnings (the types retrieved from the array are not annotated as such, as far as the analysis is concerned). Because of the lacking annotation, we have warning suppressions in multiple places within the framework. This fixes it.
1 parent d0e6ce8 commit bb418ad

File tree

13 files changed

+257
-32
lines changed

13 files changed

+257
-32
lines changed

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,10 @@ private void HandleCall(
13051305
{
13061306
MarkArrayValuesAsUnknown(arr, curBasicBlock);
13071307
}
1308+
else if (v is ArrayOfAnnotatedSystemTypeValue arrayOfAnnotated)
1309+
{
1310+
arrayOfAnnotated.MarkModified();
1311+
}
13081312
}
13091313
}
13101314
}
@@ -1354,6 +1358,10 @@ private void ScanStelem(
13541358
StoreMethodLocalValue(arrValue.IndexValues, ArrayValue.SanitizeArrayElementValue(valueToStore.Value), indexToStoreAtInt.Value, curBasicBlock, MaxTrackedArrayValues);
13551359
}
13561360
}
1361+
else if (array is ArrayOfAnnotatedSystemTypeValue arrayOfAnnotated)
1362+
{
1363+
arrayOfAnnotated.MarkModified();
1364+
}
13571365
}
13581366
}
13591367

@@ -1366,13 +1374,27 @@ private void ScanLdelem(
13661374
{
13671375
StackSlot indexToLoadFrom = PopUnknown(currentStack, 1, methodBody, offset);
13681376
StackSlot arrayToLoadFrom = PopUnknown(currentStack, 1, methodBody, offset);
1377+
1378+
bool isByRef = opcode == ILOpcode.ldelema;
1379+
1380+
if (arrayToLoadFrom.Value.AsSingleValue() is ArrayOfAnnotatedSystemTypeValue arrayOfAnnotated)
1381+
{
1382+
if (isByRef)
1383+
{
1384+
arrayOfAnnotated.MarkModified();
1385+
}
1386+
else if (!arrayOfAnnotated.IsModified)
1387+
{
1388+
currentStack.Push(new StackSlot(arrayOfAnnotated.GetAnyElementValue()));
1389+
return;
1390+
}
1391+
}
1392+
13691393
if (arrayToLoadFrom.Value.AsSingleValue() is not ArrayValue arr)
13701394
{
13711395
PushUnknown(currentStack);
13721396
return;
13731397
}
1374-
// We don't yet handle arrays of references or pointers
1375-
bool isByRef = opcode == ILOpcode.ldelema;
13761398

13771399
int? index = indexToLoadFrom.Value.AsConstInt();
13781400
if (index == null)

src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/Symbols.vb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ Namespace Microsoft.VisualBasic.CompilerServices
524524
Return System.Linq.Enumerable.ToArray(genericParameter.GetInterfaces)
525525
End Function
526526

527-
Friend Shared Function GetClassConstraint(ByVal genericParameter As Type) As Type
527+
Friend Shared Function GetClassConstraint(<DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)> ByVal genericParameter As Type) As <DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)> Type
528528
'Returns the class constraint for the type parameter, Nothing if it has
529529
'no class constraint.
530530
Debug.Assert(IsGenericParameter(genericParameter), "expected type parameter")
@@ -931,9 +931,9 @@ Namespace Microsoft.VisualBasic.CompilerServices
931931
' For a WinRT object, we want to treat members of it's collection interfaces as members of the object
932932
' itself. So GetMembers calls here to find the member in all the collection interfaces that this object
933933
' implements.
934-
<UnconditionalSuppressMessage("ReflectionAnalysis", "IL2065:UnrecognizedReflectionPattern",
934+
<SuppressMessage("ReflectionAnalysis", "IL2065:UnrecognizedReflectionPattern",
935935
Justification:="_type is annotated with .All, so it's Interfaces will be annotated as well and it is safe to call GetMember on the Interfaces.
936-
We should be able to remove once https://github.com/mono/linker/issues/1731 is fixed.")>
936+
We should be able to remove once https://github.com/dotnet/runtime/issues/114425 is fixed.")>
937937
Friend Function LookupWinRTCollectionInterfaceMembers(ByVal memberName As String) As List(Of MemberInfo)
938938
Debug.Assert(Me.IsWindowsRuntimeObject(), "Expected a Windows Runtime Object")
939939

@@ -950,9 +950,6 @@ Namespace Microsoft.VisualBasic.CompilerServices
950950
Return result
951951
End Function
952952

953-
<UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
954-
Justification:="_type is annotated with .All, so it's BaseType will be annotated as well and it is safe to call GetMember on the BaseType.
955-
We should be able to remove once https://github.com/mono/linker/issues/1731 is fixed.")>
956953
Friend Function LookupNamedMembers(ByVal memberName As String) As MemberInfo()
957954
'Returns an array of members matching MemberName sorted by inheritance (most derived first).
958955
'If no members match MemberName, returns an empty array.

src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.ReflectedTypeData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ internal ReflectedTypeData(Type type, bool isRegisteredType)
4545
/// Retrieves custom attributes.
4646
/// </summary>
4747
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062:UnrecognizedReflectionPattern",
48-
Justification = "_type is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.")]
48+
Justification = "_type is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well.")]
4949
internal AttributeCollection GetAttributes()
5050
{
5151
// Worst case collision scenario: we don't want the perf hit

src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/CollectionDataContract.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -984,17 +984,19 @@ internal static bool TryCreateGetOnlyCollectionDataContract(Type type, [NotNullW
984984
}
985985
}
986986

987-
// Once https://github.com/mono/linker/issues/1731 is fixed we can remove the suppression from here as it won't be needed any longer.
988-
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:GetMethod",
989-
Justification = "The DynamicallyAccessedMembers declarations will ensure the interface methods will be preserved.")]
990987
internal static MethodInfo? GetTargetMethodWithName(string name,
991988
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
992989
Type type,
993990
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
994991
Type interfaceType)
995992
{
996-
Type? t = type.GetInterfaces().Where(it => it.Equals(interfaceType)).FirstOrDefault();
997-
return t?.GetMethod(name);
993+
Type[] interfaces = type.GetInterfaces();
994+
for (int i = 0; i < interfaces.Length; i++)
995+
{
996+
if (interfaces[i].Equals(interfaceType))
997+
return interfaces[i].GetMethod(name);
998+
}
999+
return null;
9981000
}
9991001

10001002
private static bool IsArraySegment(Type t)
@@ -1280,21 +1282,22 @@ private static string GetInvalidCollectionMessage(string message, string nestedM
12801282
return (param == null) ? SR.Format(message, nestedMessage) : SR.Format(message, nestedMessage, param);
12811283
}
12821284

1283-
// Once https://github.com/mono/linker/issues/1731 is fixed we can remove the suppression from here as it won't be needed any longer.
1284-
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:GetMethod",
1285-
Justification = "The DynamicallyAccessedMembers declarations will ensure the interface methods will be preserved.")]
12861285
private static void FindCollectionMethodsOnInterface(
12871286
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
12881287
Type type,
12891288
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
12901289
Type interfaceType,
12911290
ref MethodInfo? addMethod, ref MethodInfo? getEnumeratorMethod)
12921291
{
1293-
Type? t = type.GetInterfaces().Where(it => it.Equals(interfaceType)).FirstOrDefault();
1294-
if (t != null)
1292+
Type[] interfaces = type.GetInterfaces();
1293+
for (int i = 0; i < interfaces.Length; i++)
12951294
{
1296-
addMethod = t.GetMethod(Globals.AddMethodName) ?? addMethod;
1297-
getEnumeratorMethod = t.GetMethod(Globals.GetEnumeratorMethodName) ?? getEnumeratorMethod;
1295+
if (interfaces[i].Equals(interfaceType))
1296+
{
1297+
addMethod = interfaces[i].GetMethod(Globals.AddMethodName) ?? addMethod;
1298+
getEnumeratorMethod = interfaces[i].GetMethod(Globals.GetEnumeratorMethodName) ?? getEnumeratorMethod;
1299+
break;
1300+
}
12981301
}
12991302
}
13001303

src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,6 @@ public GeneratedTypeInfo GetProxyType(
164164
}
165165
}
166166

167-
// Unconditionally generates a new proxy type derived from 'baseType' and implements 'interfaceType'
168-
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062:UnrecognizedReflectionPattern",
169-
Justification = "interfaceType is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.")]
170167
private GeneratedTypeInfo GenerateProxyType(
171168
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
172169
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType,
@@ -202,11 +199,9 @@ private GeneratedTypeInfo GenerateProxyType(
202199
// Create a type that derives from 'baseType' provided by caller
203200
ProxyBuilder pb = CreateProxy("generatedProxy", baseType);
204201

205-
foreach (Type t in interfaceType.GetInterfaces())
206-
// interfaceType is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.
207-
#pragma warning disable IL2072
208-
pb.AddInterfaceImpl(t);
209-
#pragma warning restore IL2072
202+
Type[] interfacesOnInterfaceType = interfaceType.GetInterfaces();
203+
for (int i = 0; i < interfacesOnInterfaceType.Length; i++)
204+
pb.AddInterfaceImpl(interfacesOnInterfaceType[i]);
210205

211206
pb.AddInterfaceImpl(interfaceType);
212207

src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/TypeBuilderImpl.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,6 @@ public override FieldInfo[] GetFields(BindingFlags bindingAttr)
939939
return fields.ToArray();
940940
}
941941

942-
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2063:UnrecognizedReflectionPattern")]
943942
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
944943
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
945944
public override Type? GetInterface(string name, bool ignoreCase)
@@ -967,7 +966,10 @@ public override FieldInfo[] GetFields(BindingFlags bindingAttr)
967966
}
968967
}
969968

969+
// Analyzer is not able to propagate `.Interfaces` on `this`.
970+
#pragma warning disable IL2063
970971
return match;
972+
#pragma warning restore IL2063
971973
}
972974

973975
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@ public override void HandleAssignment (MultiValue source, MultiValue target, IOp
243243

244244
public override MultiValue HandleArrayElementRead (MultiValue arrayValue, MultiValue indexValue, IOperation operation)
245245
{
246+
if (arrayValue.AsSingleValue() is ArrayOfAnnotatedSystemTypeValue arrayOfAnnotated && !arrayOfAnnotated.IsModified) {
247+
return arrayOfAnnotated.GetAnyElementValue ();
248+
}
249+
246250
if (indexValue.AsConstInt () is not int index)
247251
return UnknownValue.Instance;
248252

@@ -270,6 +274,8 @@ public override void HandleArrayElementWrite (MultiValue arrayValue, MultiValue
270274
? _multiValueLattice.Meet (arr.IndexValues[index.Value], sanitizedValue)
271275
: sanitizedValue;
272276
}
277+
} else if (arraySingleValue is ArrayOfAnnotatedSystemTypeValue arrayOfAnnotated) {
278+
arrayOfAnnotated.MarkModified ();
273279
}
274280
}
275281
}
@@ -308,6 +314,8 @@ public override MultiValue HandleMethodCall (
308314
foreach (var argumentValue in argument.AsEnumerable ()) {
309315
if (argumentValue is ArrayValue arrayValue)
310316
arrayValue.IndexValues.Clear ();
317+
else if (argumentValue is ArrayOfAnnotatedSystemTypeValue arrayOfAnnotated)
318+
arrayOfAnnotated.MarkModified ();
311319
}
312320
}
313321

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Diagnostics.CodeAnalysis;
8+
9+
using ILLink.Shared.DataFlow;
10+
11+
// This is needed due to NativeAOT which doesn't enable nullable globally yet
12+
#nullable enable
13+
14+
namespace ILLink.Shared.TrimAnalysis
15+
{
16+
/// <summary>
17+
/// Represents an array of <see cref="System.Type"/> where initially each element of the array has the same DynamicallyAccessedMembers annotation.
18+
/// </summary>
19+
internal sealed record ArrayOfAnnotatedSystemTypeValue : SingleValue
20+
{
21+
private readonly ValueWithDynamicallyAccessedMembers _initialValue;
22+
23+
public bool IsModified { get; private set; }
24+
25+
public ArrayOfAnnotatedSystemTypeValue (ValueWithDynamicallyAccessedMembers value) => _initialValue = value;
26+
27+
public override SingleValue DeepCopy ()
28+
{
29+
return new ArrayOfAnnotatedSystemTypeValue (this);
30+
}
31+
32+
public SingleValue GetAnyElementValue()
33+
{
34+
Debug.Assert (!IsModified);
35+
return _initialValue;
36+
}
37+
38+
public void MarkModified () => IsModified = true;
39+
40+
public override string ToString () => this.ValueToString (_initialValue.DynamicallyAccessedMemberTypes);
41+
}
42+
}

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,36 @@ ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers
250250
}
251251
break;
252252

253+
//
254+
// GetInterfaces
255+
//
256+
case IntrinsicId.Type_GetInterfaces: {
257+
if (instanceValue.IsEmpty ()) {
258+
returnValue = MultiValueLattice.Top;
259+
break;
260+
}
261+
262+
var targetValue = _annotations.GetMethodThisParameterValue (calledMethod, DynamicallyAccessedMemberTypes.Interfaces);
263+
foreach (var value in instanceValue.AsEnumerable ()) {
264+
// Require Interfaces annotation
265+
_requireDynamicallyAccessedMembersAction.Invoke (value, targetValue);
266+
267+
// Interfaces is transitive, so the return values will always have at least Interfaces annotation
268+
DynamicallyAccessedMemberTypes returnMemberTypes = DynamicallyAccessedMemberTypes.Interfaces;
269+
270+
// Propagate All annotation across the call - All is a superset of Interfaces
271+
if (value is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers
272+
&& valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.All)
273+
returnMemberTypes = DynamicallyAccessedMemberTypes.All;
274+
275+
// We model this as if each individual element of the returned array was returned from the GetInterfaces method call.
276+
// It makes diagnostics fall out nicer because we now know where the Type comes from and where to assign blame, should the requirements not match
277+
AddReturnValue (new ArrayOfAnnotatedSystemTypeValue(_annotations.GetMethodReturnValue (calledMethod, isNewObj: false, returnMemberTypes)));
278+
}
279+
}
280+
break;
281+
282+
253283
//
254284
// AssemblyQualifiedName
255285
//

src/tools/illink/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ internal enum IntrinsicId
172172
/// </summary>
173173
Type_GetInterface,
174174
/// <summary>
175+
/// <see cref="System.Type.GetInterfaces"/>
176+
/// </summary>
177+
Type_GetInterfaces,
178+
/// <summary>
175179
/// <see cref="System.Type.AssemblyQualifiedName"/>
176180
/// </summary>
177181
Type_get_AssemblyQualifiedName,

0 commit comments

Comments
 (0)