diff options
author | Sven Boemer <sbomer@gmail.com> | 2022-06-15 19:28:50 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-15 19:28:50 +0300 |
commit | 226107b9117b9d166e38dd00e30f9ab54399e658 (patch) | |
tree | 410de8da2b0f1555d977b78938f7ced082a32c72 /test | |
parent | 2abcee1070e38d2994a25777023dfa61615ea498 (diff) |
Analyze implicit indexer reference operations (#2839)
Adds support for `IImplicitIndexerReferenceOperation`, which
represents an implicit access to an indexer that uses
`System.Index`. Implicit means that there is no `System.Index`
accessor in IL, but the compiler supports `System.Index` access
via an existing indexer (for example one that takes int) for
types that satisfy certain criteria. See
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/ranges#implicit-index-support
for details.
This operation only showed up in the CFG with a more recent
version of the Roslyn APIs, so this includes an update to match
the one used in dotnet/runtime. This resulted in a few changes to
the generated code that required tweaking some of the test
validation:
- Delegate cache fields are emitted. This reuses an existing
attribute originally designed for mcs, but it has been updated to
check for these compiler-generated fields.
- `<PrivateImplementationDetails>.ThrowSwitchExpressionException`
is emitted for an implicit unhandled case in switch
expressions. This change includes a new attribute to check that
this is kept in a few of the tests.
Diffstat (limited to 'test')
18 files changed, 181 insertions, 34 deletions
diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptDelegateCacheFieldAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptDelegateCacheFieldAttribute.cs index 52fe49618..b3638b01a 100644 --- a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptDelegateCacheFieldAttribute.cs +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptDelegateCacheFieldAttribute.cs @@ -8,10 +8,12 @@ namespace Mono.Linker.Tests.Cases.Expectations.Assertions [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = true)] public class KeptDelegateCacheFieldAttribute : KeptAttribute { - public KeptDelegateCacheFieldAttribute (string uniquePartOfName) + public KeptDelegateCacheFieldAttribute (string classIndex, string fieldName) { - if (string.IsNullOrEmpty (uniquePartOfName)) - throw new ArgumentNullException (nameof (uniquePartOfName)); + if (string.IsNullOrEmpty (classIndex)) + throw new ArgumentNullException (nameof (classIndex)); + if (string.IsNullOrEmpty (fieldName)) + throw new ArgumentNullException (nameof (fieldName)); } } } diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptPrivateImplementationDetails.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptPrivateImplementationDetails.cs new file mode 100644 index 000000000..a410eb2f5 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptPrivateImplementationDetails.cs @@ -0,0 +1,17 @@ +// 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; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + [AttributeUsage (AttributeTargets.Class)] + public sealed class KeptPrivateImplementationDetailsAttribute : KeptAttribute + { + public KeptPrivateImplementationDetailsAttribute (string methodName) + { + if (string.IsNullOrEmpty (methodName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (methodName)); + } + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent.cs b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent.cs index 6b9c5ff8e..eaf9a4866 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent.cs @@ -14,7 +14,7 @@ namespace Mono.Linker.Tests.Cases.Attributes.Csc [KeptTypeInAssembly ("LibraryWithType.dll", typeof (TypeDefinedInReference))] [RemovedMemberInAssembly ("LibraryWithType.dll", typeof (TypeDefinedInReference), "Unused()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), ".ctor(System.Type)")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (FooOnMyEvent))] public class OnlyTypeUsedInAssemblyIsTypeOnAttributeCtorOnEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent.cs b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent.cs index 9026e6048..d069585c0 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent.cs @@ -15,7 +15,7 @@ namespace Mono.Linker.Tests.Cases.Attributes.Csc [RemovedMemberInAssembly ("LibraryWithType.dll", typeof (TypeDefinedInReference), "Unused()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), ".ctor()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), "FieldType")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (FooOnMyEvent))] public class OnlyTypeUsedInAssemblyIsTypeOnAttributeFieldOnEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent.cs b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent.cs index 5f1791948..83466e847 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/Csc/OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent.cs @@ -15,7 +15,7 @@ namespace Mono.Linker.Tests.Cases.Attributes.Csc [RemovedMemberInAssembly ("LibraryWithType.dll", typeof (TypeDefinedInReference), "Unused()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), ".ctor()")] [KeptMemberInAssembly ("LibraryWithAttribute.dll", typeof (AttributeDefinedInReference), "set_PropertyType(System.Type)")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (FooOnMyEvent))] public class OnlyTypeUsedInAssemblyIsTypeOnAttributePropertyOnEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UnusedAttributeTypeOnEventIsRemoved.cs b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UnusedAttributeTypeOnEventIsRemoved.cs index ff8775974..c05e9ad30 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UnusedAttributeTypeOnEventIsRemoved.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UnusedAttributeTypeOnEventIsRemoved.cs @@ -5,7 +5,7 @@ using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Attributes.OnlyKeepUsed { [SetupLinkerArgument ("--used-attrs-only", "true")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (Tmp_Something))] class UnusedAttributeTypeOnEventIsRemoved { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UsedAttributeTypeOnEventIsKept.cs b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UsedAttributeTypeOnEventIsKept.cs index 27d50d3cd..0a5e3cfe3 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UsedAttributeTypeOnEventIsKept.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/UsedAttributeTypeOnEventIsKept.cs @@ -5,7 +5,7 @@ using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Attributes.OnlyKeepUsed { [SetupLinkerArgument ("--used-attrs-only", "true")] - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (Tmp_Something))] class UsedAttributeTypeOnEventIsKept { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Basic/DelegateBeginInvokeEndInvokePair.cs b/test/Mono.Linker.Tests.Cases/Basic/DelegateBeginInvokeEndInvokePair.cs index d39ab89d9..99e8e90ae 100644 --- a/test/Mono.Linker.Tests.Cases/Basic/DelegateBeginInvokeEndInvokePair.cs +++ b/test/Mono.Linker.Tests.Cases/Basic/DelegateBeginInvokeEndInvokePair.cs @@ -5,6 +5,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Basic { + [KeptDelegateCacheField ("0", nameof (Method))] + [KeptDelegateCacheField ("1", nameof (Method))] + [KeptDelegateCacheField ("2", nameof (Method))] class DelegateBeginInvokeEndInvokePair { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Basic/UsedEventIsKept.cs b/test/Mono.Linker.Tests.Cases/Basic/UsedEventIsKept.cs index 8453f1624..490ffa348 100644 --- a/test/Mono.Linker.Tests.Cases/Basic/UsedEventIsKept.cs +++ b/test/Mono.Linker.Tests.Cases/Basic/UsedEventIsKept.cs @@ -3,8 +3,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Basic { - [KeptDelegateCacheField ("0")] - [KeptDelegateCacheField ("1")] + [KeptDelegateCacheField ("0", nameof (Tmp_Bar))] class UsedEventIsKept { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsKept.cs b/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsKept.cs index 099b92ab2..47e45e75e 100644 --- a/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsKept.cs +++ b/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsKept.cs @@ -3,7 +3,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Basic { - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (Bar_Ping))] class UsedEventOnInterfaceIsKept { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsRemovedWhenUsedFromClass.cs b/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsRemovedWhenUsedFromClass.cs index 464db7008..10555f041 100644 --- a/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsRemovedWhenUsedFromClass.cs +++ b/test/Mono.Linker.Tests.Cases/Basic/UsedEventOnInterfaceIsRemovedWhenUsedFromClass.cs @@ -3,7 +3,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Basic { - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (Bar_Ping))] class UsedEventOnInterfaceIsRemovedWhenUsedFromClass { static void Main () diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs b/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs index d19a1fb82..90c24fd9f 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/NullableAnnotations.cs @@ -11,6 +11,7 @@ using DAMT = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; namespace Mono.Linker.Tests.Cases.DataFlow { [ExpectedNoWarnings] + [KeptPrivateImplementationDetails ("ThrowSwitchExpressionException")] class NullableAnnotations { [Kept] diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs index 2128b8e41..15508baca 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs @@ -45,6 +45,9 @@ namespace Mono.Linker.Tests.Cases.DataFlow BasePropertyAccess.Test (); AccessReturnedInstanceProperty.Test (); + + ExplicitIndexerAccess.Test (); + ImplicitIndexerAccess.Test (); } [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)] @@ -653,6 +656,85 @@ namespace Mono.Linker.Tests.Cases.DataFlow } } + class ExplicitIndexerAccess + { + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + Type this[Index idx] { + get => throw new NotImplementedException (); + set => throw new NotImplementedException (); + } + + [ExpectedWarning ("IL2072", "this[Index].get", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", "Item.get", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestRead (ExplicitIndexerAccess instance = null) + { + instance[new Index (1)].RequiresAll (); + } + + [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "this[Index].set", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "Item.set", ProducedBy = ProducedBy.Trimmer)] + static void TestWrite (ExplicitIndexerAccess instance = null) + { + instance[^1] = GetTypeWithPublicConstructors (); + } + + public static void Test () + { + TestRead (); + TestWrite (); + } + } + + class ImplicitIndexerAccess + { + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + Type this[int idx] { + get => throw new NotImplementedException (); + set => throw new NotImplementedException (); + } + + int Length => throw new NotImplementedException (); + + [ExpectedWarning ("IL2072", "this[Int32].get", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", "Item.get", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = ProducedBy.Trimmer)] + static void TestRead (ImplicitIndexerAccess instance = null) + { + instance[new Index (1)].RequiresAll (); + } + + [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "this[Int32].set", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), "Item.set", ProducedBy = ProducedBy.Trimmer)] + static void TestWrite (ImplicitIndexerAccess instance = null) + { + instance[^1] = GetTypeWithPublicConstructors (); + } + + [ExpectedWarning ("IL2072", nameof (GetUnknownType), "this[Int32].set", ProducedBy = ProducedBy.Analyzer)] + [ExpectedWarning ("IL2072", nameof (GetUnknownType), "Item.set", ProducedBy = ProducedBy.Trimmer)] + static void TestNullCoalescingAssignment (ImplicitIndexerAccess instance = null) + { + instance[new Index (1)] ??= GetUnknownType (); + } + + [ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll))] + static void TestSpanIndexerAccess (int start = 0, int end = 3) + { + Span<byte> bytes = stackalloc byte[4] { 1, 2, 3, 4 }; + bytes[^4] = 0; // This calls the get indexer which has a ref return. + int index = bytes[0]; + Type[] types = new Type[] { GetUnknownType () }; + types[index].RequiresAll (); + } + + public static void Test () + { + TestRead (); + TestWrite (); + TestNullCoalescingAssignment (); + TestSpanIndexerAccess (); + } + } + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] private static Type GetTypeWithPublicParameterlessConstructor () { diff --git a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithEvent.cs b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithEvent.cs index e8856be3b..ec4a75670 100644 --- a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithEvent.cs @@ -2,7 +2,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.OnReferenceType.BaseProvidesInterfaceMember { - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (EventMethod))] public class GenericInterfaceWithEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleEvent.cs b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleEvent.cs index 11e5860a6..98597a4b2 100644 --- a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleEvent.cs +++ b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleEvent.cs @@ -2,7 +2,7 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.OnReferenceType.BaseProvidesInterfaceMember { - [KeptDelegateCacheField ("0")] + [KeptDelegateCacheField ("0", nameof (EventMethod))] public class SimpleEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs b/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs index 10ac90435..5489497a8 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs @@ -10,6 +10,7 @@ namespace Mono.Linker.Tests.Cases.Reflection [SetupCSharpCompilerToUse ("csc")] [Reference ("System.Core.dll")] [ExpectedNoWarnings] + [KeptPrivateImplementationDetails ("ThrowSwitchExpressionException")] public class ExpressionCallString { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs b/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs index 906f56ab4..5bec60d50 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs @@ -9,6 +9,8 @@ namespace Mono.Linker.Tests.Cases.Reflection { [KeptMember (".cctor()")] [ExpectedNoWarnings ()] + [KeptDelegateCacheField ("0", nameof (AssemblyResolver))] + [KeptDelegateCacheField ("1", nameof (GetTypeFromAssembly))] public class TypeUsedViaReflection { public static void Main () diff --git a/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs index f7e6df662..96c037778 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -146,6 +146,10 @@ namespace Mono.Linker.Tests.TestCasesRunner VerifyFixedBufferFields (original, linked); + // Need to check delegate cache fields before the normal field check + VerifyDelegateBackingFields (original, linked); + VerifyPrivateImplementationDetails (original, linked); + foreach (var td in original.NestedTypes) { VerifyTypeDefinition (td, linked?.NestedTypes.FirstOrDefault (l => td.FullName == l.FullName)); linkedMembers.Remove (td.FullName); @@ -162,9 +166,6 @@ namespace Mono.Linker.Tests.TestCasesRunner linkedMembers.Remove (e.FullName); } - // Need to check delegate cache fields before the normal field check - VerifyDelegateBackingFields (original, linked); - foreach (var f in original.Fields) { if (verifiedGeneratedFields.Contains (f.FullName)) continue; @@ -712,6 +713,41 @@ namespace Mono.Linker.Tests.TestCasesRunner Assert.That (linkedAttrs, Is.EquivalentTo (expectedAttrs), $"Security attributes on `{src}' are not matching"); } + void VerifyPrivateImplementationDetails (TypeDefinition original, TypeDefinition linked) + { + var expectedImplementationDetailsMethods = GetCustomAttributeCtorValues<string> (original, nameof (KeptPrivateImplementationDetailsAttribute)) + .Select (attr => attr.ToString ()) + .ToList (); + + if (expectedImplementationDetailsMethods.Count == 0) + return; + + VerifyPrivateImplementationDetailsType (original.Module, linked.Module, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails); + foreach (var methodName in expectedImplementationDetailsMethods) { + var originalMethod = srcImplementationDetails.Methods.FirstOrDefault (m => m.Name == methodName); + if (originalMethod == null) + Assert.Fail ($"Could not locate original private implementation details method {methodName}"); + + var linkedMethod = linkedImplementationDetails.Methods.FirstOrDefault (m => m.Name == methodName); + VerifyMethodKept (originalMethod, linkedMethod); + linkedMembers.Remove (linkedMethod.FullName); + } + verifiedGeneratedTypes.Add (srcImplementationDetails.FullName); + } + + static void VerifyPrivateImplementationDetailsType (ModuleDefinition src, ModuleDefinition linked, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails) + { + srcImplementationDetails = src.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("<PrivateImplementationDetails>")); + + if (srcImplementationDetails == null) + Assert.Fail ("Could not locate <PrivateImplementationDetails> in the original assembly. Does your test use initializers?"); + + linkedImplementationDetails = linked.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("<PrivateImplementationDetails>")); + + if (linkedImplementationDetails == null) + Assert.Fail ("Could not locate <PrivateImplementationDetails> in the linked assembly"); + } + protected virtual void VerifyArrayInitializers (MethodDefinition src, MethodDefinition linked) { var expectedIndicies = GetCustomAttributeCtorValues<object> (src, nameof (KeptInitializerData)) @@ -726,15 +762,7 @@ namespace Mono.Linker.Tests.TestCasesRunner if (!src.HasBody) Assert.Fail ($"`{nameof (KeptInitializerData)}` cannot be used on methods that don't have bodies"); - var srcImplementationDetails = src.Module.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("<PrivateImplementationDetails>")); - - if (srcImplementationDetails == null) - Assert.Fail ("Could not locate <PrivateImplementationDetails> in the original assembly. Does your test use initializers?"); - - var linkedImplementationDetails = linked.Module.Types.FirstOrDefault (t => string.IsNullOrEmpty (t.Namespace) && t.Name.StartsWith ("<PrivateImplementationDetails>")); - - if (linkedImplementationDetails == null) - Assert.Fail ("Could not locate <PrivateImplementationDetails> in the linked assembly"); + VerifyPrivateImplementationDetailsType (src.Module, linked.Module, out TypeDefinition srcImplementationDetails, out TypeDefinition linkedImplementationDetails); var possibleInitializerFields = src.Body.Instructions .Where (ins => IsLdtokenOnPrivateImplementationDetails (srcImplementationDetails, ins)) @@ -874,21 +902,33 @@ namespace Mono.Linker.Tests.TestCasesRunner void VerifyDelegateBackingFields (TypeDefinition src, TypeDefinition linked) { - var expectedFieldNames = GetCustomAttributeCtorValues<string> (src, nameof (KeptDelegateCacheFieldAttribute)) - .Select (unique => $"<>f__mg$cache{unique}") + var expectedFieldNames = src.CustomAttributes + .Where (a => a.AttributeType.Name == nameof (KeptDelegateCacheFieldAttribute)) + .Select (a => (a.ConstructorArguments[0].Value as string, a.ConstructorArguments[1].Value as string)) + .Select (indexAndField => $"<{indexAndField.Item1}>__{indexAndField.Item2}") .ToList (); if (expectedFieldNames.Count == 0) return; - foreach (var srcField in src.Fields) { - if (!expectedFieldNames.Contains (srcField.Name)) + foreach (var nestedType in src.NestedTypes) { + if (nestedType.Name != "<>O") continue; - var linkedField = linked?.Fields.FirstOrDefault (l => l.Name == srcField.Name); - VerifyFieldKept (srcField, linkedField); - verifiedGeneratedFields.Add (srcField.FullName); - linkedMembers.Remove (srcField.FullName); + var linkedNestedType = linked.NestedTypes.FirstOrDefault (t => t.Name == nestedType.Name); + foreach (var expectedFieldName in expectedFieldNames) { + var originalField = nestedType.Fields.FirstOrDefault (f => f.Name == expectedFieldName); + if (originalField is null) + Assert.Fail ($"Invalid expected delegate backing field {expectedFieldName} in {src}. This member was not in the unlinked assembly"); + + var linkedField = linkedNestedType?.Fields.FirstOrDefault (f => f.Name == expectedFieldName); + VerifyFieldKept (originalField, linkedField); + verifiedGeneratedFields.Add (linkedField.FullName); + linkedMembers.Remove (linkedField.FullName); + } + + VerifyTypeDefinitionKept (nestedType, linkedNestedType); + verifiedGeneratedTypes.Add (linkedNestedType.FullName); } } |