Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs')
-rw-r--r--test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs746
1 files changed, 746 insertions, 0 deletions
diff --git a/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
new file mode 100644
index 000000000..9893a82a1
--- /dev/null
+++ b/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs
@@ -0,0 +1,746 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Extensions;
+using NUnit.Framework;
+
+namespace Mono.Linker.Tests.TestCasesRunner {
+ public class AssemblyChecker {
+ readonly AssemblyDefinition originalAssembly, linkedAssembly;
+
+ HashSet<string> linkedMembers;
+ HashSet<string> verifiedGeneratedFields = new HashSet<string> ();
+ HashSet<string> verifiedEventMethods = new HashSet<string>();
+ HashSet<string> verifiedGeneratedTypes = new HashSet<string> ();
+
+ public AssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked)
+ {
+ this.originalAssembly = original;
+ this.linkedAssembly = linked;
+ }
+
+ public void Verify ()
+ {
+ // TODO: Implement fully, probably via custom Kept attribute
+ Assert.IsFalse (linkedAssembly.MainModule.HasExportedTypes);
+
+ VerifyCustomAttributes (originalAssembly, linkedAssembly);
+ VerifySecurityAttributes (originalAssembly, linkedAssembly);
+
+ foreach (var originalModule in originalAssembly.Modules)
+ VerifyModule (originalModule, linkedAssembly.Modules.FirstOrDefault (m => m.Name == originalModule.Name));
+
+ VerifyResources (originalAssembly, linkedAssembly);
+ VerifyReferences (originalAssembly, linkedAssembly);
+
+ linkedMembers = new HashSet<string> (linkedAssembly.MainModule.AllMembers ().Select (s => {
+ return s.FullName;
+ }), StringComparer.Ordinal);
+
+ var membersToAssert = originalAssembly.MainModule.Types;
+ foreach (var originalMember in membersToAssert) {
+ var td = originalMember as TypeDefinition;
+ if (td != null) {
+ if (td.Name == "<Module>") {
+ linkedMembers.Remove (td.Name);
+ continue;
+ }
+
+ TypeDefinition linkedType = linkedAssembly.MainModule.GetType (originalMember.FullName);
+ VerifyTypeDefinition (td, linkedType);
+ linkedMembers.Remove (td.FullName);
+
+ continue;
+ }
+
+ throw new NotImplementedException ($"Don't know how to check member of type {originalMember.GetType ()}");
+ }
+
+ Assert.IsEmpty (linkedMembers, "Linked output includes unexpected member");
+ }
+
+ protected virtual void VerifyModule (ModuleDefinition original, ModuleDefinition linked)
+ {
+ // We never link away a module today so let's make sure the linked one isn't null
+ if (linked == null)
+ Assert.Fail ($"Linked assembly `{original.Assembly.Name.Name}` is missing module `{original.Name}`");
+
+ VerifyCustomAttributes (original, linked);
+ }
+
+ protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDefinition linked)
+ {
+ if (linked != null && verifiedGeneratedTypes.Contains (linked.FullName))
+ return;
+
+ ModuleDefinition linkedModule = linked?.Module;
+
+ //
+ // Little bit complex check to allow easier test writting to match
+ // - It has [Kept] attribute or any variation of it
+ // - It contains Main method
+ // - It contains at least one member which has [Kept] attribute (not recursive)
+ //
+ bool expectedKept =
+ original.HasAttributeDerivedFrom (nameof (KeptAttribute)) ||
+ (linked != null && linkedModule.Assembly.EntryPoint?.DeclaringType == linked) ||
+ original.AllMembers ().Any (l => l.HasAttribute (nameof (KeptAttribute)));
+
+ if (!expectedKept) {
+ if (linked != null)
+ Assert.Fail ($"Type `{original}' should have been removed");
+
+ return;
+ }
+
+ VerifyTypeDefinitionKept (original, linked);
+ }
+
+ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDefinition linked)
+ {
+ if (linked == null)
+ Assert.Fail ($"Type `{original}' should have been kept");
+
+ if (!original.IsInterface)
+ VerifyBaseType (original, linked);
+
+ VerifyInterfaces (original, linked);
+ VerifyPseudoAttributes (original, linked);
+ VerifyGenericParameters (original, linked);
+ VerifyCustomAttributes (original, linked);
+ VerifySecurityAttributes (original, linked);
+
+ VerifyFixedBufferFields (original, linked);
+
+ foreach (var td in original.NestedTypes) {
+ VerifyTypeDefinition (td, linked?.NestedTypes.FirstOrDefault (l => td.FullName == l.FullName));
+ linkedMembers.Remove (td.FullName);
+ }
+
+ // Need to check properties before fields so that the KeptBackingFieldAttribute is handled correctly
+ foreach (var p in original.Properties) {
+ VerifyProperty (p, linked?.Properties.FirstOrDefault (l => p.Name == l.Name), linked);
+ linkedMembers.Remove (p.FullName);
+ }
+ // Need to check events before fields so that the KeptBackingFieldAttribute is handled correctly
+ foreach (var e in original.Events) {
+ VerifyEvent (e, linked?.Events.FirstOrDefault (l => e.Name == l.Name), linked);
+ 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;
+ VerifyField (f, linked?.Fields.FirstOrDefault (l => f.Name == l.Name));
+ linkedMembers.Remove (f.FullName);
+ }
+
+ foreach (var m in original.Methods) {
+ if (verifiedEventMethods.Contains (m.FullName))
+ continue;
+ VerifyMethod (m, linked?.Methods.FirstOrDefault (l => m.GetSignature () == l.GetSignature ()));
+ linkedMembers.Remove (m.FullName);
+ }
+ }
+
+ void VerifyBaseType (TypeDefinition src, TypeDefinition linked)
+ {
+ string expectedBaseName;
+ var expectedBaseGenericAttr = src.CustomAttributes.FirstOrDefault (w => w.AttributeType.Name == nameof (KeptBaseTypeAttribute) && w.ConstructorArguments.Count > 1);
+ if (expectedBaseGenericAttr != null) {
+ StringBuilder builder = new StringBuilder ();
+ builder.Append (expectedBaseGenericAttr.ConstructorArguments [0].Value);
+ builder.Append ("<");
+ bool separator = false;
+ foreach (var caa in (CustomAttributeArgument[])expectedBaseGenericAttr.ConstructorArguments [1].Value) {
+ if (separator)
+ builder.Append (",");
+ else
+ separator = true;
+
+ var arg = (CustomAttributeArgument)caa.Value;
+ builder.Append (arg.Value);
+ }
+
+ builder.Append (">");
+ expectedBaseName = builder.ToString ();
+ } else {
+ var defaultBaseType = src.IsEnum ? "System.Enum" : src.IsValueType ? "System.ValueType" : "System.Object";
+ expectedBaseName = GetCustomAttributeCtorValues<object> (src, nameof (KeptBaseTypeAttribute)).FirstOrDefault ()?.ToString () ?? defaultBaseType;
+ }
+ Assert.AreEqual (expectedBaseName, linked.BaseType?.FullName, $"Incorrect base type on : {linked.Name}");
+ }
+
+ void VerifyInterfaces (TypeDefinition src, TypeDefinition linked)
+ {
+ var expectedInterfaces = new HashSet<string> (GetCustomAttributeCtorValues<object> (src, nameof (KeptInterfaceAttribute)).Select (val => val.ToString ()));
+ if (expectedInterfaces.Count == 0) {
+ Assert.IsFalse (linked.HasInterfaces, $"Type `{src}' has unexpected interfaces");
+ } else {
+ foreach (var iface in linked.Interfaces) {
+ Assert.IsTrue (expectedInterfaces.Remove (iface.InterfaceType.FullName), $"Type `{src}' interface `{iface.InterfaceType.FullName}' should have been removed");
+ }
+
+ Assert.IsEmpty (expectedInterfaces, $"Unexpected interfaces on {src}");
+ }
+ }
+
+ void VerifyField (FieldDefinition src, FieldDefinition linked)
+ {
+ bool expectedKept = ShouldBeKept (src);
+
+ if (!expectedKept) {
+ if (linked != null)
+ Assert.Fail ($"Field `{src}' should have been removed");
+
+ return;
+ }
+
+ VerifyFieldKept (src, linked);
+ }
+
+ void VerifyFieldKept (FieldDefinition src, FieldDefinition linked)
+ {
+ if (linked == null)
+ Assert.Fail ($"Field `{src}' should have been kept");
+
+ Assert.AreEqual (src?.Constant, linked?.Constant, $"Field `{src}' value");
+
+ VerifyPseudoAttributes (src, linked);
+ VerifyCustomAttributes (src, linked);
+ }
+
+ void VerifyProperty (PropertyDefinition src, PropertyDefinition linked, TypeDefinition linkedType)
+ {
+ VerifyMemberBackingField (src, linkedType);
+
+ bool expectedKept = ShouldBeKept (src);
+
+ if (!expectedKept) {
+ if (linked != null)
+ Assert.Fail ($"Property `{src}' should have been removed");
+
+ return;
+ }
+
+ if (linked == null)
+ Assert.Fail ($"Property `{src}' should have been kept");
+
+ Assert.AreEqual (src?.Constant, linked?.Constant, $"Property `{src}' value");
+
+ VerifyPseudoAttributes (src, linked);
+ VerifyCustomAttributes (src, linked);
+ }
+
+ void VerifyEvent (EventDefinition src, EventDefinition linked, TypeDefinition linkedType)
+ {
+ VerifyMemberBackingField (src, linkedType);
+
+ bool expectedKept = ShouldBeKept (src);
+
+ if (!expectedKept) {
+ if (linked != null)
+ Assert.Fail ($"Event `{src}' should have been removed");
+
+ return;
+ }
+
+ if (linked == null)
+ Assert.Fail ($"Event `{src}' should have been kept");
+
+ if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptEventAddMethodAttribute))) {
+ VerifyMethodInternal (src.AddMethod, linked.AddMethod, true);
+ verifiedEventMethods.Add (src.AddMethod.FullName);
+ linkedMembers.Remove (src.AddMethod.FullName);
+ }
+
+ if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptEventRemoveMethodAttribute))) {
+ VerifyMethodInternal (src.RemoveMethod, linked.RemoveMethod, true);
+ verifiedEventMethods.Add (src.RemoveMethod.FullName);
+ linkedMembers.Remove (src.RemoveMethod.FullName);
+ }
+
+ VerifyPseudoAttributes (src, linked);
+ VerifyCustomAttributes (src, linked);
+ }
+
+ void VerifyMethod (MethodDefinition src, MethodDefinition linked)
+ {
+ bool expectedKept = ShouldMethodBeKept (src);
+ VerifyMethodInternal (src, linked, expectedKept);
+ }
+
+
+ void VerifyMethodInternal (MethodDefinition src, MethodDefinition linked, bool expectedKept)
+ {
+ if (!expectedKept) {
+ if (linked != null)
+ Assert.Fail ($"Method `{src.FullName}' should have been removed");
+
+ return;
+ }
+
+ VerifyMethodKept (src, linked);
+ }
+
+ void VerifyMemberBackingField (IMemberDefinition src, TypeDefinition linkedType)
+ {
+ var keptBackingFieldAttribute = src.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.Name == nameof (KeptBackingFieldAttribute));
+ if (keptBackingFieldAttribute == null)
+ return;
+
+ var backingFieldName = src.MetadataToken.TokenType == TokenType.Property
+ ? $"<{src.Name}>k__BackingField" : src.Name;
+ var srcField = src.DeclaringType.Fields.FirstOrDefault (f => f.Name == backingFieldName);
+
+ if (srcField == null) {
+ // Can add more here if necessary
+ backingFieldName = backingFieldName.Replace ("System.Int32", "int");
+ backingFieldName = backingFieldName.Replace ("System.String", "string");
+ backingFieldName = backingFieldName.Replace ("System.Char", "char");
+
+ srcField = src.DeclaringType.Fields.FirstOrDefault (f => f.Name == backingFieldName);
+ }
+
+ if (srcField == null)
+ Assert.Fail ($"{src.MetadataToken.TokenType} `{src}', could not locate the expected backing field {backingFieldName}");
+
+ VerifyFieldKept (srcField, linkedType?.Fields.FirstOrDefault (l => srcField.Name == l.Name));
+ verifiedGeneratedFields.Add (srcField.FullName);
+ linkedMembers.Remove (srcField.FullName);
+ }
+
+ protected virtual void VerifyMethodKept (MethodDefinition src, MethodDefinition linked)
+ {
+ if (linked == null)
+ Assert.Fail ($"Method `{src.FullName}' should have been kept");
+
+ VerifyPseudoAttributes (src, linked);
+ VerifyGenericParameters (src, linked);
+ VerifyCustomAttributes (src, linked);
+ VerifyParameters (src, linked);
+ VerifySecurityAttributes (src, linked);
+ VerifyArrayInitializers (src, linked);
+ VerifyMethodBody (src, linked);
+ }
+
+ protected virtual void VerifyMethodBody (MethodDefinition src, MethodDefinition linked)
+ {
+ if (!src.HasBody)
+ return;
+
+ VerifyExceptionHandlers (src, linked);
+ VerifyInstructions (src, linked);
+ VerifyLocals (src, linked);
+ }
+
+ protected static void VerifyInstructions (MethodDefinition src, MethodDefinition linked)
+ {
+ VerifyBodyProperties (
+ src,
+ linked,
+ nameof (ExpectedInstructionSequenceAttribute),
+ nameof (ExpectBodyModifiedAttribute),
+ "instructions",
+ m => m.Body.Instructions.Select (ins => ins.OpCode.ToString ().ToLower()).ToArray (),
+ attr => GetStringArrayAttributeValue (attr).Select (v => v.ToLower ()).ToArray ());
+ }
+
+ static void VerifyExceptionHandlers (MethodDefinition src, MethodDefinition linked)
+ {
+ VerifyBodyProperties (
+ src,
+ linked,
+ nameof (ExpectedExceptionHandlerSequenceAttribute),
+ nameof (ExpectExceptionHandlersModifiedAttribute),
+ "exception handlers",
+ m => m.Body.ExceptionHandlers.Select (h => h.HandlerType.ToString ().ToLower ()).ToArray (),
+ attr => GetStringArrayAttributeValue (attr).Select (v => v.ToLower ()).ToArray ());
+ }
+
+ static void VerifyLocals (MethodDefinition src, MethodDefinition linked)
+ {
+ VerifyBodyProperties (
+ src,
+ linked,
+ nameof (ExpectedLocalsSequenceAttribute),
+ nameof (ExpectLocalsModifiedAttribute),
+ "locals",
+ m => m.Body.Variables.Select (v => v.VariableType.ToString ()).ToArray (),
+ attr => GetStringOrTypeArrayAttributeValue (attr).ToArray ());
+ }
+
+ protected static void VerifyBodyProperties (MethodDefinition src, MethodDefinition linked, string sequenceAttributeName, string expectModifiedAttributeName,
+ string propertyDescription,
+ Func<MethodDefinition, string []> valueCollector,
+ Func<CustomAttribute, string []> getExpectFromSequenceAttribute)
+ {
+ var expectedSequenceAttribute = src.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.Name == sequenceAttributeName);
+ var linkedValues = valueCollector (linked);
+ var srcValues = valueCollector (src);
+
+ if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == expectModifiedAttributeName)) {
+ Assert.That (
+ linkedValues,
+ Is.Not.EquivalentTo (srcValues),
+ $"Expected method `{src} to have {propertyDescription} modified, however, the {propertyDescription} were the same as the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}");
+ } else if (expectedSequenceAttribute != null) {
+ var expected = getExpectFromSequenceAttribute(expectedSequenceAttribute).ToArray();
+ Assert.That(
+ linkedValues,
+ Is.EquivalentTo (expected),
+ $"Expected method `{src} to have it's {propertyDescription} modified, however, the sequence of {propertyDescription} does not match the expected value\n{FormattingUtils.FormatSequenceCompareFailureMessage2 (linkedValues, expected, srcValues)}");
+ } else {
+ Assert.That(
+ linkedValues,
+ Is.EquivalentTo (srcValues),
+ $"Expected method `{src} to have it's {propertyDescription} unchanged, however, the {propertyDescription} differ from the original\n{FormattingUtils.FormatSequenceCompareFailureMessage (linkedValues, srcValues)}");
+ }
+ }
+
+ void VerifyReferences (AssemblyDefinition original, AssemblyDefinition linked)
+ {
+ var expected = original.MainModule.AllDefinedTypes ()
+ .SelectMany (t => GetCustomAttributeCtorValues<string> (t, nameof (KeptReferenceAttribute)))
+ .Select (ReduceAssemblyFileNameOrNameToNameOnly)
+ .ToArray ();
+
+ /*
+ - The test case will always need to have at least 1 reference.
+ - Forcing all tests to define their expected references seems tedious
+
+ Given the above, let's assume that when no [KeptReference] attributes are present,
+ the test case does not want to make any assertions regarding references.
+
+ Once 1 kept reference attribute is used, the test will need to define all of of it's expected references
+ */
+ if (expected.Length == 0)
+ return;
+
+ var actual = linked.MainModule.AssemblyReferences
+ .Select (name => name.Name)
+ .ToArray ();
+
+ Assert.That (actual, Is.EquivalentTo (expected));
+ }
+
+ string ReduceAssemblyFileNameOrNameToNameOnly (string fileNameOrAssemblyName)
+ {
+ if (fileNameOrAssemblyName.EndsWith (".dll") || fileNameOrAssemblyName.EndsWith (".exe") || fileNameOrAssemblyName.EndsWith (".winmd"))
+ return System.IO.Path.GetFileNameWithoutExtension (fileNameOrAssemblyName);
+
+ // It must already be just the assembly name
+ return fileNameOrAssemblyName;
+ }
+
+ void VerifyResources (AssemblyDefinition original, AssemblyDefinition linked)
+ {
+ var expectedResources = original.MainModule.AllDefinedTypes ()
+ .SelectMany (t => GetCustomAttributeCtorValues<string> (t, nameof (KeptResourceAttribute)));
+
+ Assert.That (linked.MainModule.Resources.Select (r => r.Name), Is.EquivalentTo (expectedResources));
+ }
+
+ protected virtual void VerifyPseudoAttributes (MethodDefinition src, MethodDefinition linked)
+ {
+ var expected = (MethodAttributes) GetExpectedPseudoAttributeValue(src, (uint) src.Attributes);
+ Assert.AreEqual (expected, linked.Attributes, $"Method `{src}' pseudo attributes did not match expected");
+ }
+
+ protected virtual void VerifyPseudoAttributes (TypeDefinition src, TypeDefinition linked)
+ {
+ var expected = (TypeAttributes) GetExpectedPseudoAttributeValue(src, (uint) src.Attributes);
+ Assert.AreEqual (expected, linked.Attributes, $"Type `{src}' pseudo attributes did not match expected");
+ }
+
+ protected virtual void VerifyPseudoAttributes (FieldDefinition src, FieldDefinition linked)
+ {
+ var expected = (FieldAttributes) GetExpectedPseudoAttributeValue(src, (uint) src.Attributes);
+ Assert.AreEqual (expected, linked.Attributes, $"Field `{src}' pseudo attributes did not match expected");
+ }
+
+ protected virtual void VerifyPseudoAttributes (PropertyDefinition src, PropertyDefinition linked)
+ {
+ var expected = (PropertyAttributes) GetExpectedPseudoAttributeValue(src, (uint) src.Attributes);
+ Assert.AreEqual (expected, linked.Attributes, $"Property `{src}' pseudo attributes did not match expected");
+ }
+
+ protected virtual void VerifyPseudoAttributes (EventDefinition src, EventDefinition linked)
+ {
+ var expected = (EventAttributes) GetExpectedPseudoAttributeValue(src, (uint) src.Attributes);
+ Assert.AreEqual (expected, linked.Attributes, $"Event `{src}' pseudo attributes did not match expected");
+ }
+
+ protected virtual void VerifyCustomAttributes (ICustomAttributeProvider src, ICustomAttributeProvider linked)
+ {
+ var expectedAttrs = GetExpectedAttributes (src).ToList ();
+ var linkedAttrs = FilterLinkedAttributes (linked).ToList ();
+
+ Assert.That (linkedAttrs, Is.EquivalentTo (expectedAttrs), $"Custom attributes on `{src}' are not matching");
+ }
+
+ protected virtual void VerifySecurityAttributes (ICustomAttributeProvider src, ISecurityDeclarationProvider linked)
+ {
+ var expectedAttrs = GetCustomAttributeCtorValues<object> (src, nameof (KeptSecurityAttribute))
+ .Select (attr => attr.ToString ())
+ .ToList ();
+
+ var linkedAttrs = FilterLinkedSecurityAttributes (linked).ToList ();
+
+ Assert.That (linkedAttrs, Is.EquivalentTo (expectedAttrs), $"Security attributes on `{src}' are not matching");
+ }
+
+ protected virtual void VerifyArrayInitializers (MethodDefinition src, MethodDefinition linked)
+ {
+ var expectedIndicies = GetCustomAttributeCtorValues<object> (src, nameof (KeptInitializerData))
+ .Cast<int> ()
+ .ToArray ();
+
+ var expectKeptAll = src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptInitializerData) && !attr.HasConstructorArguments);
+
+ if (expectedIndicies.Length == 0 && !expectKeptAll)
+ return;
+
+ 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");
+
+ var possibleInitializerFields = src.Body.Instructions
+ .Where (ins => IsLdtokenOnPrivateImplementationDetails (srcImplementationDetails, ins))
+ .Select (ins => ((FieldReference)ins.Operand).Resolve ())
+ .ToArray ();
+
+ if (possibleInitializerFields.Length == 0)
+ Assert.Fail ($"`{src}` does not make use of any initializers");
+
+ if (expectKeptAll) {
+ foreach (var srcField in possibleInitializerFields) {
+ var linkedField = linkedImplementationDetails.Fields.FirstOrDefault (f => f.Name == srcField.Name);
+ VerifyInitializerField (srcField, linkedField);
+ }
+ } else {
+ foreach (var index in expectedIndicies) {
+ if (index < 0 || index > possibleInitializerFields.Length)
+ Assert.Fail($"Invalid expected index `{index}` in {src}. Value must be between 0 and {expectedIndicies.Length}");
+
+ var srcField = possibleInitializerFields[index];
+ var linkedField = linkedImplementationDetails.Fields.FirstOrDefault (f => f.Name == srcField.Name);
+
+ VerifyInitializerField (srcField, linkedField);
+ }
+ }
+ }
+
+ void VerifyInitializerField (FieldDefinition src, FieldDefinition linked)
+ {
+ VerifyFieldKept (src, linked);
+ verifiedGeneratedFields.Add (linked.FullName);
+ linkedMembers.Remove (linked.FullName);
+ VerifyTypeDefinitionKept (src.FieldType.Resolve (), linked.FieldType.Resolve ());
+ linkedMembers.Remove (linked.FieldType.FullName);
+ linkedMembers.Remove (linked.DeclaringType.FullName);
+ verifiedGeneratedTypes.Add (linked.DeclaringType.FullName);
+ }
+
+ static bool IsLdtokenOnPrivateImplementationDetails (TypeDefinition privateImplementationDetails, Instruction instruction)
+ {
+ if (instruction.OpCode.Code == Code.Ldtoken && instruction.Operand is FieldReference field)
+ {
+ return field.DeclaringType.Resolve () == privateImplementationDetails;
+ }
+
+ return false;
+ }
+
+ protected static IEnumerable<string> GetExpectedAttributes (ICustomAttributeProvider original)
+ {
+ foreach (var expectedAttrs in GetCustomAttributeCtorValues<object> (original, nameof (KeptAttributeAttribute)))
+ yield return expectedAttrs.ToString ();
+
+ // The name of the generated fixed buffer type is a little tricky.
+ // Some versions of csc name it `<fieldname>e__FixedBuffer0`
+ // while mcs and other versions of csc name it `<fieldname>__FixedBuffer0`
+ if (original is TypeDefinition srcDefinition && srcDefinition.Name.Contains ("__FixedBuffer")) {
+ var name = srcDefinition.Name.Substring (1, srcDefinition.Name.IndexOf('>') - 1);
+ var fixedField = srcDefinition.DeclaringType.Fields.FirstOrDefault (f => f.Name == name);
+ if (fixedField == null)
+ Assert.Fail ($"Could not locate original fixed field for {srcDefinition}");
+
+ foreach (var additionalExpectedAttributesFromFixedField in GetCustomAttributeCtorValues<object> (fixedField, nameof (KeptAttributeOnFixedBufferTypeAttribute)))
+ yield return additionalExpectedAttributesFromFixedField.ToString ();
+
+ }
+ }
+
+ /// <summary>
+ /// Filters out some attributes that should not be taken into consideration when checking the linked result against the expected result
+ /// </summary>
+ /// <param name="linked"></param>
+ /// <returns></returns>
+ protected virtual IEnumerable<string> FilterLinkedAttributes (ICustomAttributeProvider linked)
+ {
+ foreach (var attr in linked.CustomAttributes) {
+ switch (attr.AttributeType.FullName) {
+ case "System.Runtime.CompilerServices.RuntimeCompatibilityAttribute":
+ case "System.Runtime.CompilerServices.CompilerGeneratedAttribute":
+ continue;
+
+ // When mcs is used to compile the test cases, backing fields end up with this attribute on them
+ case "System.Diagnostics.DebuggerBrowsableAttribute":
+ continue;
+
+ case "System.Runtime.CompilerServices.CompilationRelaxationsAttribute":
+ if (linked is AssemblyDefinition)
+ continue;
+ break;
+ }
+
+ yield return attr.AttributeType.FullName;
+ }
+ }
+
+ protected virtual IEnumerable<string> FilterLinkedSecurityAttributes (ISecurityDeclarationProvider linked)
+ {
+ return linked.SecurityDeclarations
+ .SelectMany (d => d.SecurityAttributes)
+ .Select (attr => attr.AttributeType.ToString ());
+ }
+
+ void VerifyFixedBufferFields (TypeDefinition src, TypeDefinition linked)
+ {
+ var fields = src.Fields.Where (f => f.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptFixedBufferAttribute)));
+
+ foreach (var field in fields) {
+ // The name of the generated fixed buffer type is a little tricky.
+ // Some versions of csc name it `<fieldname>e__FixedBuffer0`
+ // while mcs and other versions of csc name it `<fieldname>__FixedBuffer0`
+ var originalCompilerGeneratedBufferType = src.NestedTypes.FirstOrDefault (t => t.FullName.Contains ($"<{field.Name}>") && t.FullName.Contains ("__FixedBuffer"));
+ if (originalCompilerGeneratedBufferType == null)
+ Assert.Fail ($"Could not locate original compiler generated fixed buffer type for field {field}");
+
+ var linkedCompilerGeneratedBufferType = linked.NestedTypes.FirstOrDefault (t => t.Name == originalCompilerGeneratedBufferType.Name);
+ if (linkedCompilerGeneratedBufferType == null)
+ Assert.Fail ($"Missing expected type {originalCompilerGeneratedBufferType}");
+
+ // Have to verify the field before the type
+ var originalElementField = originalCompilerGeneratedBufferType.Fields.FirstOrDefault (f => f.Name == "FixedElementField");
+ if (originalElementField == null)
+ Assert.Fail ($"Could not locate original compiler generated FixedElementField on {originalCompilerGeneratedBufferType}");
+
+ var linkedField = linkedCompilerGeneratedBufferType?.Fields.FirstOrDefault (l => l.Name == originalElementField.Name);
+ VerifyFieldKept (originalElementField, linkedField);
+ verifiedGeneratedFields.Add (originalElementField.FullName);
+ linkedMembers.Remove (originalElementField.FullName);
+
+ VerifyTypeDefinitionKept(originalCompilerGeneratedBufferType, linkedCompilerGeneratedBufferType);
+ verifiedGeneratedTypes.Add(originalCompilerGeneratedBufferType.FullName);
+ }
+ }
+
+ void VerifyDelegateBackingFields (TypeDefinition src, TypeDefinition linked)
+ {
+ var expectedFieldNames = GetCustomAttributeCtorValues<string> (src, nameof (KeptDelegateCacheFieldAttribute))
+ .Select (unique => $"<>f__mg$cache{unique}")
+ .ToList ();
+
+ if (expectedFieldNames.Count == 0)
+ return;
+
+ foreach (var srcField in src.Fields) {
+ if (!expectedFieldNames.Contains (srcField.Name))
+ continue;
+
+ var linkedField = linked?.Fields.FirstOrDefault (l => l.Name == srcField.Name);
+ VerifyFieldKept (srcField, linkedField);
+ verifiedGeneratedFields.Add (srcField.FullName);
+ linkedMembers.Remove (srcField.FullName);
+ }
+ }
+
+ void VerifyGenericParameters (IGenericParameterProvider src, IGenericParameterProvider linked)
+ {
+ Assert.AreEqual (src.HasGenericParameters, linked.HasGenericParameters);
+ if (src.HasGenericParameters) {
+ for (int i = 0; i < src.GenericParameters.Count; ++i) {
+ // TODO: Verify constraints
+ VerifyCustomAttributes (src.GenericParameters [i], linked.GenericParameters [i]);
+ }
+ }
+ }
+
+ void VerifyParameters (IMethodSignature src, IMethodSignature linked)
+ {
+ Assert.AreEqual (src.HasParameters, linked.HasParameters);
+ if (src.HasParameters) {
+ for (int i = 0; i < src.Parameters.Count; ++i) {
+ VerifyCustomAttributes (src.Parameters [i], linked.Parameters [i]);
+ }
+ }
+ }
+
+ protected virtual bool ShouldMethodBeKept (MethodDefinition method)
+ {
+ var srcSignature = method.GetSignature ();
+ return ShouldBeKept (method, srcSignature) || method.DeclaringType.Module.EntryPoint == method;
+ }
+
+ protected virtual bool ShouldBeKept<T> (T member, string signature = null) where T : MemberReference, ICustomAttributeProvider
+ {
+ if (member.HasAttribute (nameof (KeptAttribute)))
+ return true;
+
+ ICustomAttributeProvider cap = (ICustomAttributeProvider)member.DeclaringType;
+ if (cap == null)
+ return false;
+
+ return GetCustomAttributeCtorValues<string> (cap, nameof (KeptMemberAttribute)).Any (a => a == (signature ?? member.Name));
+ }
+
+ protected static uint GetExpectedPseudoAttributeValue (ICustomAttributeProvider provider, uint sourceValue)
+ {
+ var removals = provider.CustomAttributes.Where (attr => attr.AttributeType.Name == nameof (RemovedPseudoAttributeAttribute)).ToArray ();
+ var adds = provider.CustomAttributes.Where (attr => attr.AttributeType.Name == nameof (AddedPseudoAttributeAttribute)).ToArray ();
+
+ return removals.Aggregate (sourceValue, (accum, item) => accum & ~((uint) item.ConstructorArguments [0].Value)) |
+ adds.Aggregate ((uint)0, (acum, item) => acum | (uint) item.ConstructorArguments [0].Value);
+ }
+
+ protected static IEnumerable<T> GetCustomAttributeCtorValues<T> (ICustomAttributeProvider provider, string attributeName) where T : class
+ {
+ return provider.CustomAttributes.
+ Where (w => w.AttributeType.Name == attributeName && w.Constructor.Parameters.Count == 1).
+ Select (l => l.ConstructorArguments [0].Value as T);
+ }
+
+ protected static IEnumerable<string> GetStringOrTypeArrayAttributeValue (CustomAttribute attribute)
+ {
+ foreach (var arg in ((CustomAttributeArgument[]) attribute.ConstructorArguments [0].Value)) {
+ if (arg.Value is TypeReference tRef)
+ yield return tRef.ToString ();
+ else
+ yield return (string) arg.Value;
+ }
+ }
+
+ protected static IEnumerable<string> GetStringArrayAttributeValue (CustomAttribute attribute)
+ {
+ return ((CustomAttributeArgument[]) attribute.ConstructorArguments [0].Value)?.Select (arg => arg.Value.ToString ());
+ }
+ }
+}