diff --git a/src/coreclr/tools/Common/Compiler/DisplayNameHelpers.cs b/src/coreclr/tools/Common/Compiler/DisplayNameHelpers.cs index fde8c756edf874..df62b988f20e19 100644 --- a/src/coreclr/tools/Common/Compiler/DisplayNameHelpers.cs +++ b/src/coreclr/tools/Common/Compiler/DisplayNameHelpers.cs @@ -34,9 +34,9 @@ public static string GetDisplayName(this MethodDesc method) sb.Append(method.OwningType.GetDisplayName()); sb.Append('.'); - if (method.IsConstructor) + if (method.IsConstructor && method.OwningType is DefType defType) { - sb.Append(method.OwningType.GetDisplayNameWithoutNamespace()); + sb.Append(defType.Name); } #if !READYTORUN else if (method.GetPropertyForAccessor() is PropertyPseudoDesc property) @@ -238,12 +238,8 @@ protected override Unit AppendNameForNamespaceType(StringBuilder sb, DefType typ protected override Unit AppendNameForNestedType(StringBuilder sb, DefType nestedType, DefType containingType, FormatOptions options) { - if ((options & FormatOptions.NamespaceQualify) != 0) - { - AppendName(sb, containingType, options); - sb.Append('.'); - } - + AppendName(sb, containingType, options); + sb.Append('.'); sb.Append(nestedType.Name); return default; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/Origin.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/Origin.cs index 13b6cf85447e8c..bf8142ab853024 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/Origin.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/Origin.cs @@ -81,5 +81,7 @@ public GenericParameterOrigin(GenericParameterDesc genericParam) { GenericParameter = genericParam; } + + public string GetDisplayName() => GenericParameter.GetDisplayName(); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs index 07f34d58a18292..1b73665245936e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs @@ -44,7 +44,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto protected override string GetName(NodeFactory factory) { - return "Dataflow analysis for " + factory.NameMangler.GetMangledMethodName(_methodIL.OwningMethod).ToString(); + return "Dataflow analysis for " + _methodIL.OwningMethod.ToString(); } public override bool InterestingForDynamicDependencyAnalysis => false; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index 19b9c9c5594cdd..8144079a620415 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -305,6 +305,7 @@ protected override void GetMetadataDependenciesDueToReflectability(ref Dependenc bool fullyRoot; string reason; + // https://github.com/dotnet/runtime/issues/78752 // Compat with https://github.com/dotnet/linker/issues/1541 IL Linker bug: // Asking to root an assembly with entrypoint will not actually root things in the assembly. // We need to emulate this because the SDK injects a root for the entrypoint assembly right now diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs index d85c275c791da9..2e3000f40f5b50 100644 --- a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs +++ b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs @@ -7,7 +7,6 @@ using System.CommandLine.Help; using System.CommandLine.Parsing; using System.IO; -using System.Runtime.InteropServices; using Internal.TypeSystem; diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptAttribute.cs b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptAttribute.cs index 179ea77e5d7bd9..c6f87ceb27a8c2 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptAttribute.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptAttribute.cs @@ -8,5 +8,11 @@ namespace Mono.Linker.Tests.Cases.Expectations.Assertions [AttributeUsage (AttributeTargets.All, Inherited = false)] public class KeptAttribute : BaseExpectedLinkedBehaviorAttribute { + /// + /// By default the target should be kept by all platforms + /// This property can override that by setting only the platforms + /// which are expected to keep the target. + /// + public ProducedBy By { get; set; } = ProducedBy.TrimmerAnalyzerAndNativeAot; } } diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/ApplyTypeAnnotations.cs b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/ApplyTypeAnnotations.cs index 4576ba2baa7766..4791b8f938038c 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/ApplyTypeAnnotations.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/ApplyTypeAnnotations.cs @@ -128,7 +128,9 @@ private static void RequireCombinationOnString ( { } - [Kept] + // https://github.com/dotnet/runtime/issues/72833 + // NativeAOT doesn't implement full type name parser yet + [Kept (By = ProducedBy.Trimmer)] class FromStringConstantWithGenericInner { } @@ -141,10 +143,12 @@ class FromStringConstantWithGeneric public T GetValue () { return default (T); } } - [Kept] + // https://github.com/dotnet/runtime/issues/72833 + // NativeAOT doesn't implement full type name parser yet + [Kept (By = ProducedBy.Trimmer)] class FromStringConstantWithGenericInnerInner { - [Kept] + [Kept (By = ProducedBy.Trimmer)] public void Method () { } @@ -152,15 +156,17 @@ public void Method () int unusedField; } - [Kept] + [Kept (By = ProducedBy.Trimmer)] class FromStringConstantWithGenericInnerOne< [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute), By = ProducedBy.Trimmer)] T> { } - [Kept] + // https://github.com/dotnet/runtime/issues/72833 + // NativeAOT doesn't implement full type name parser yet + [Kept (By = ProducedBy.Trimmer)] class FromStringConstantWithGenericInnerTwo { void UnusedMethod () @@ -168,14 +174,22 @@ void UnusedMethod () } } - [Kept] + // https://github.com/dotnet/runtime/issues/72833 + // NativeAOT doesn't implement full type name parser yet + [Kept (By = ProducedBy.Trimmer)] class FromStringConstantWitGenericInnerMultiDimArray { } + // https://github.com/dotnet/runtime/issues/72833 + // NativeAOT actually preserves this, but for a slightly wrong reason - it completely ignores the array notations [Kept] + [KeptMember (".ctor()", By = ProducedBy.NativeAot)] class FromStringConstantWithMultiDimArray { + // https://github.com/dotnet/runtime/issues/72833 + // NativeAOT actually preserves this, but for a slightly wrong reason - it completely ignores the array notations + [Kept (By = ProducedBy.NativeAot)] public void UnusedMethod () { } } diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/ComplexTypeHandling.cs b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/ComplexTypeHandling.cs index 2e071edd796dcc..2ecaae4a2fe58b 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/ComplexTypeHandling.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/ComplexTypeHandling.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; using Mono.Linker.Tests.Cases.Expectations.Assertions; @@ -24,9 +25,12 @@ public static void Main () TestArrayTypeGetType (); TestArrayCreateInstanceByName (); TestArrayInAttributeParameter (); + TestArrayInAttributeParameter_ViaReflection (); } - [Kept] + // NativeAOT: No need to preserve the element type if it's never instantiated + // There will be a reflection record about it, but we don't validate that yet + [Kept (By = ProducedBy.Trimmer)] class ArrayElementType { public ArrayElementType () { } @@ -54,7 +58,9 @@ static void RequirePublicMethodsOnArrayOfGeneric () RequirePublicMethods (typeof (T[])); } - [Kept] + // NativeAOT: No need to preserve the element type if it's never instantiated + // There will be a reflection record about it, but we don't validate that yet + [Kept (By = ProducedBy.Trimmer)] class ArrayElementInGenericType { public ArrayElementInGenericType () { } @@ -91,7 +97,9 @@ static void RequirePublicMethodsOnArrayOfGenericParameter () _ = new RequirePublicMethodsGeneric (); } - [Kept] + // NativeAOT: No need to preserve the element type if it's never instantiated + // There will be a reflection record about it, but we don't validate that yet + [Kept (By = ProducedBy.Trimmer)] sealed class ArrayGetTypeFromMethodParamElement { // This method should not be marked, instead Array.* should be marked @@ -110,7 +118,9 @@ static void TestArrayGetTypeFromMethodParam () TestArrayGetTypeFromMethodParamHelper (null); } - [Kept] + // NativeAOT: No need to preserve the element type if it's never instantiated + // There will be a reflection record about it, but we don't validate that yet + [Kept (By = ProducedBy.Trimmer)] sealed class ArrayGetTypeFromFieldElement { // This method should not be marked, instead Array.* should be marked @@ -126,9 +136,15 @@ static void TestArrayGetTypeFromField () RequirePublicMethods (_arrayGetTypeFromField.GetType ()); } + + // https://github.com/dotnet/runtime/issues/72833 + // NativeAOT doesn't implement full type name parser yet - it ignores the [] and thus sees this as a direct type reference [Kept] sealed class ArrayTypeGetTypeElement { + // https://github.com/dotnet/runtime/issues/72833 + // NativeAOT doesn't implement full type name parser yet - it ignores the [] and thus sees this as a direct type reference + [Kept (By = ProducedBy.NativeAot)] // This method should not be marked, instead Array.* should be marked public void PublicMethod () { } } @@ -139,11 +155,13 @@ static void TestArrayTypeGetType () RequirePublicMethods (Type.GetType ("Mono.Linker.Tests.Cases.DataFlow.ComplexTypeHandling+ArrayTypeGetTypeElement[]")); } - // Technically there's no reason to mark this type since it's only used as an array element type and CreateInstance + // ILLink: Technically there's no reason to mark this type since it's only used as an array element type and CreateInstance // doesn't work on arrays, but the currently implementation will preserve it anyway due to how it processes // string -> Type resolution. This will only impact code which would have failed at runtime, so very unlikely to // actually occur in real apps (and even if it does happen, it just increases size, doesn't break behavior). - [Kept] + // NativeAOT: https://github.com/dotnet/runtime/issues/72833 + // NativeAOT doesn't implement full type name parser yet - it ignores the [] and thus sees this as a direct type reference + [Kept (By = ProducedBy.Trimmer)] class ArrayCreateInstanceByNameElement { public ArrayCreateInstanceByNameElement () @@ -157,7 +175,8 @@ static void TestArrayCreateInstanceByName () Activator.CreateInstance ("test", "Mono.Linker.Tests.Cases.DataFlow.ComplexTypeHandling+ArrayCreateInstanceByNameElement[]"); } - [Kept] + // NativeAOT doesn't keep attributes on non-reflectable methods + [Kept (By = ProducedBy.Trimmer)] class ArrayInAttributeParamElement { // This method should not be marked, instead Array.* should be marked @@ -165,12 +184,37 @@ public void PublicMethod () { } } [Kept] - [KeptAttributeAttribute (typeof (RequiresPublicMethodAttribute))] + // NativeAOT doesn't keep attributes on non-reflectable methods + [KeptAttributeAttribute (typeof (RequiresPublicMethodAttribute), By = ProducedBy.Trimmer)] [RequiresPublicMethod (typeof (ArrayInAttributeParamElement[]))] static void TestArrayInAttributeParameter () { } + // The usage of a type in attribute parameter is not enough to create NativeAOT EEType + // which is what the test infra looks for right now, so the type is not kept. + // There should be a reflection record of the type though (we just don't validate that yet). + [Kept (By = ProducedBy.Trimmer)] + class ArrayInAttributeParamElement_ViaReflection + { + // This method should not be marked, instead Array.* should be marked + public void PublicMethod () { } + } + + [Kept] + [KeptAttributeAttribute (typeof (RequiresPublicMethodAttribute))] + [RequiresPublicMethod (typeof (ArrayInAttributeParamElement_ViaReflection[]))] + static void TestArrayInAttributeParameter_ViaReflection_Inner () + { + } + + [Kept] + static void TestArrayInAttributeParameter_ViaReflection () + { + typeof (ComplexTypeHandling) + .GetMethod (nameof (TestArrayInAttributeParameter_ViaReflection_Inner), BindingFlags.NonPublic) + .Invoke (null, new object[] { }); + } [Kept] private static void RequirePublicMethods ( diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/IReflectDataflow.cs b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/IReflectDataflow.cs index 54ced51d123caf..230b67dfb5215d 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/IReflectDataflow.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/IReflectDataflow.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; namespace Mono.Linker.Tests.Cases.DataFlow { @@ -117,8 +118,10 @@ class MyReflect : IReflect public object InvokeMember (string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) => throw new NotImplementedException (); } - [Kept] - [KeptBaseType (typeof (MyReflect))] + // NativeAOT: Doesn't preserve this type because there's no need. The type itself is never instantiated + // there's only a field of that type and accessed to that field can be made without knowing it's type (just memory address access) + [Kept (By = ProducedBy.Trimmer)] + [KeptBaseType (typeof (MyReflect), By = ProducedBy.Trimmer)] class MyReflectDerived : MyReflect { } @@ -167,7 +170,15 @@ class TestType [Kept] public static void Test () { - new MyReflectOverType (typeof (TestType)).GetFields (BindingFlags.Instance | BindingFlags.Public); + var i = new MyReflectOverType (typeof (TestType)); + i.GetFields (BindingFlags.Instance | BindingFlags.Public); + +#if NATIVEAOT + // In Native AOT the test infra doesn't setup the compiler in a way where it will force preserve + // all external types. Like here, it will actually track usage of methods on IReflect + // and remove any which are not used. We don't want that for this test. + typeof (IReflect).RequiresAll (); +#endif } } } diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/MemberTypesAllOnCopyAssembly.cs b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/MemberTypesAllOnCopyAssembly.cs index 775bfd2681aea5..8bc09752164958 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/MemberTypesAllOnCopyAssembly.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/MemberTypesAllOnCopyAssembly.cs @@ -65,7 +65,9 @@ namespace Mono.Linker.Tests.Cases.DataFlow [KeptTypeInAssembly ("base.dll", "Mono.Linker.Tests.Cases.DataFlow.Dependencies.MemberTypesAllBaseType/PrivateNestedType")] [KeptMemberInAssembly ("base.dll", "Mono.Linker.Tests.Cases.DataFlow.Dependencies.MemberTypesAllBaseType/PrivateNestedType", new string[] { "PrivateMethod()" })] - [KeptMember (".ctor()")] + // NativeAOT: https://github.com/dotnet/runtime/issues/78752 + // Once the above issue is fixed the ctor should be preserved even in NativeAOT + [KeptMember (".ctor()", By = ProducedBy.Trimmer)] public class MemberTypesAllOnCopyAssembly { public static void Main () diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/UnresolvedMembers.cs b/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/UnresolvedMembers.cs deleted file mode 100644 index 400dd9af052bd5..00000000000000 --- a/src/coreclr/tools/aot/Mono.Linker.Tests.Cases/DataFlow/UnresolvedMembers.cs +++ /dev/null @@ -1,135 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Mono.Linker.Tests.Cases.Expectations.Assertions; -using Mono.Linker.Tests.Cases.Expectations.Metadata; - -namespace Mono.Linker.Tests.Cases.DataFlow -{ - // NativeAOT will not compile a method with unresolved types in it - it will instead replace it with a throwing method body - // So it doesn't produce any of these warnings - which is also correct, because the code at runtime would never get there - // it would fail to JIT/run anyway. - - [SkipPeVerify] - [SetupLinkerArgument ("--skip-unresolved", "true")] - [SetupCompileBefore ("UnresolvedLibrary.dll", new[] { "Dependencies/UnresolvedLibrary.cs" }, removeFromLinkerInput: true)] - [ExpectedNoWarnings] - class UnresolvedMembers - { - public static void Main () - { - UnresolvedGenericArgument (); - UnresolvedAttributeArgument (); - UnresolvedAttributePropertyValue (); - UnresolvedAttributeFieldValue (); - UnresolvedObjectGetType (); - UnresolvedMethodParameter (); - } - - [Kept] - [KeptMember (".ctor()")] - class TypeWithUnresolvedGenericArgument< - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] T> - { - } - - [Kept] - static void MethodWithUnresolvedGenericArgument< - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] T> () - { } - - [Kept] - [ExpectedWarning ("IL2066", "TypeWithUnresolvedGenericArgument", ProducedBy = ProducedBy.Trimmer | ProducedBy.Analyzer)] // Local variable type - [ExpectedWarning ("IL2066", "TypeWithUnresolvedGenericArgument", ProducedBy = ProducedBy.Trimmer | ProducedBy.Analyzer)] // Called method declaring type - [ExpectedWarning ("IL2066", nameof (MethodWithUnresolvedGenericArgument), ProducedBy = ProducedBy.Trimmer | ProducedBy.Analyzer)] - static void UnresolvedGenericArgument () - { - var a = new TypeWithUnresolvedGenericArgument (); - MethodWithUnresolvedGenericArgument (); - } - - [Kept] - [KeptBaseType (typeof (Attribute))] - class AttributeWithRequirements : Attribute - { - [Kept] - public AttributeWithRequirements ( - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) - { } - - [Kept] - [KeptBackingField] - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] - public Type PropertyWithRequirements { get; [Kept] set; } - - [Kept] - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] - public Type FieldWithRequirements; - } - - [Kept] - [ExpectedWarning ("IL2062", nameof (AttributeWithRequirements), ProducedBy = ProducedBy.Trimmer | ProducedBy.Analyzer)] - [KeptAttributeAttribute (typeof (AttributeWithRequirements))] - [AttributeWithRequirements (typeof (Dependencies.UnresolvedType))] - static void UnresolvedAttributeArgument () - { - } - - [Kept] - [ExpectedWarning ("IL2062", nameof (AttributeWithRequirements.PropertyWithRequirements), ProducedBy = ProducedBy.Trimmer | ProducedBy.Analyzer)] - [KeptAttributeAttribute (typeof (AttributeWithRequirements))] - [AttributeWithRequirements (typeof (EmptyType), PropertyWithRequirements = typeof (Dependencies.UnresolvedType))] - static void UnresolvedAttributePropertyValue () - { - } - - [Kept] - [ExpectedWarning ("IL2064", nameof (AttributeWithRequirements.FieldWithRequirements), ProducedBy = ProducedBy.Trimmer | ProducedBy.Analyzer)] - [KeptAttributeAttribute (typeof (AttributeWithRequirements))] - [AttributeWithRequirements (typeof (EmptyType), FieldWithRequirements = typeof (Dependencies.UnresolvedType))] - static void UnresolvedAttributeFieldValue () - { - } - - [Kept] - static Dependencies.UnresolvedType _unresolvedField; - - [Kept] - [ExpectedWarning ("IL2072", nameof (Object.GetType), ProducedBy = ProducedBy.Trimmer | ProducedBy.Analyzer)] - static void UnresolvedObjectGetType () - { - RequirePublicMethods (_unresolvedField.GetType ()); - } - - [Kept] - [ExpectedWarning ("IL2072", nameof (Object.GetType), ProducedBy = ProducedBy.Trimmer | ProducedBy.Analyzer)] - static void UnresolvedMethodParameter () - { - RequirePublicMethods (typeof (Dependencies.UnresolvedType)); - } - - [Kept] - class EmptyType - { - } - - [Kept] - static void RequirePublicMethods ( - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] - Type t) - { - } - } -} diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/Extensions/CecilExtensions.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/Extensions/CecilExtensions.cs index 763cec9c4c776d..5e4dc5ffa1e137 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/Extensions/CecilExtensions.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/Extensions/CecilExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using Mono.Cecil; @@ -310,6 +311,7 @@ public static StringBuilder GetDisplayNameWithoutNamespace (this TypeReference t break; } + type = type.GetElementType (); if (type.DeclaringType is not TypeReference declaringType) break; diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs index 1e2836d080e286..8d3e8efe8a09e6 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -3,30 +3,44 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using FluentAssertions; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Extensions; using Xunit; +using MetadataType = Internal.TypeSystem.MetadataType; namespace Mono.Linker.Tests.TestCasesRunner { public class AssemblyChecker { - private readonly AssemblyDefinition originalAssembly, linkedAssembly; - private HashSet linkedMembers; + private readonly BaseAssemblyResolver originalsResolver; + private readonly ReaderParameters originalReaderParameters; + private readonly AssemblyDefinition originalAssembly; + private readonly ILCompilerTestCaseResult testResult; + + private readonly Dictionary linkedMembers; private readonly HashSet verifiedGeneratedFields = new HashSet (); private readonly HashSet verifiedEventMethods = new HashSet (); private readonly HashSet verifiedGeneratedTypes = new HashSet (); private bool checkNames; - public AssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked) + public AssemblyChecker ( + BaseAssemblyResolver originalsResolver, + ReaderParameters originalReaderParameters, + AssemblyDefinition original, + ILCompilerTestCaseResult testResult) { + this.originalsResolver = originalsResolver; + this.originalReaderParameters = originalReaderParameters; this.originalAssembly = original; - this.linkedAssembly = linked; + this.testResult = testResult; this.linkedMembers = new (StringComparer.Ordinal); checkNames = original.MainModule.GetTypeReferences ().Any (attr => @@ -35,20 +49,37 @@ public AssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked) public void Verify () { - VerifyExportedTypes (originalAssembly, linkedAssembly); + // There are no type forwarders left after compilation in Native AOT + // VerifyExportedTypes (originalAssembly, linkedAssembly); - VerifyCustomAttributes (originalAssembly, linkedAssembly); - VerifySecurityAttributes (originalAssembly, linkedAssembly); + // TODO + // VerifyCustomAttributes (originalAssembly, linkedAssembly); + // VerifySecurityAttributes (originalAssembly, linkedAssembly); - foreach (var originalModule in originalAssembly.Modules) - VerifyModule (originalModule, linkedAssembly.Modules.FirstOrDefault (m => m.Name == originalModule.Name)); + // TODO - this is mostly attribute verification + // foreach (var originalModule in originalAssembly.Modules) + // VerifyModule (originalModule, linkedAssembly.Modules.FirstOrDefault (m => m.Name == originalModule.Name)); - VerifyResources (originalAssembly, linkedAssembly); - VerifyReferences (originalAssembly, linkedAssembly); + // TODO + // VerifyResources (originalAssembly, linkedAssembly); - foreach (var s in linkedAssembly.MainModule.AllMembers ().Select (s => s.FullName)) { - this.linkedMembers.Add (s); - } + // There are no assembly reference in Native AOT + // VerifyReferences (originalAssembly, linkedAssembly); + + PopulateLinkedMembers (); + + // Workaround for compiler injected attribute to describe the language version + linkedMembers.Remove ("Microsoft.CodeAnalysis.EmbeddedAttribute.EmbeddedAttribute()"); + linkedMembers.Remove ("System.Runtime.CompilerServices.RefSafetyRulesAttribute.Version"); + linkedMembers.Remove ("System.Runtime.CompilerServices.RefSafetyRulesAttribute.RefSafetyRulesAttribute(Int32)"); + + // Workaround for NativeAOT injected members + linkedMembers.Remove (".StartupCodeMain(Int32,IntPtr)"); + linkedMembers.Remove (".MainMethodWrapper()"); + + // Workaround for compiler injected attribute to describe the language version + verifiedGeneratedTypes.Add ("Microsoft.CodeAnalysis.EmbeddedAttribute"); + verifiedGeneratedTypes.Add ("System.Runtime.CompilerServices.RefSafetyRulesAttribute"); var membersToAssert = originalAssembly.MainModule.Types; foreach (var originalMember in membersToAssert) { @@ -58,8 +89,10 @@ public void Verify () continue; } - TypeDefinition linkedType = linkedAssembly.MainModule.GetType (originalMember.FullName); - VerifyTypeDefinition (td, linkedType); + linkedMembers.TryGetValue ( + NameUtils.GetExpectedOriginDisplayName (originalMember), + out TypeSystemEntity? linkedMember); + VerifyTypeDefinition (td, linkedMember as TypeDesc); linkedMembers.Remove (td.FullName); continue; @@ -68,7 +101,102 @@ public void Verify () throw new NotImplementedException ($"Don't know how to check member of type {originalMember.GetType ()}"); } - Assert.Empty (linkedMembers); + // Filter out all members which are not from the main assembly + // The Kept attributes are "optional" for non-main assemblies + string mainModuleName = originalAssembly.Name.Name; + List externalMembers = linkedMembers.Where (m => GetModuleName (m.Value) != mainModuleName).Select (m => m.Key).ToList (); + foreach (var externalMember in externalMembers) { + linkedMembers.Remove (externalMember); + } + + if (linkedMembers.Count != 0) + Assert.True ( + false, + "Linked output includes unexpected member:\n " + + string.Join ("\n ", linkedMembers.Keys)); + } + + private void PopulateLinkedMembers () + { + foreach (TypeDesc? constructedType in testResult.TrimmingResults.ConstructedEETypes) { + AddType (constructedType); + } + + foreach (MethodDesc? compiledMethod in testResult.TrimmingResults.CompiledMethodBodies) { + AddMethod (compiledMethod); + } + + void AddMethod (MethodDesc method) + { + MethodDesc methodDef = method.GetTypicalMethodDefinition (); + + if (!ShouldIncludeType (methodDef.OwningType)) + return; + + if (!AddMember (methodDef)) + return; + + if (methodDef.OwningType is { } owningType) + AddType (owningType); + } + + void AddType (TypeDesc type) + { + TypeDesc typeDef = type.GetTypeDefinition (); + + if (!ShouldIncludeType (typeDef)) + return; + + if (!AddMember (typeDef)) + return; + + if (typeDef is MetadataType { ContainingType: { } containingType }) { + AddType (containingType); + } + } + + bool AddMember (TypeSystemEntity entity) + { + if (NameUtils.GetActualOriginDisplayName (entity) is string fullName && + !linkedMembers.ContainsKey (fullName)) { + + linkedMembers.Add (fullName, entity); + return true; + } + + return false; + } + + bool ShouldIncludeType (TypeDesc type) + { + if (type is MetadataType metadataType) { + if (metadataType.ContainingType is { } containingType) { + if (!ShouldIncludeType (containingType)) + return false; + } + + if (metadataType.Namespace.StartsWith ("Internal")) + return false; + + // Simple way to filter out system assemblies - the best way would be to get a list + // of input/reference assemblies and filter on that, but it's tricky and this should work for basically everything + if (metadataType.Namespace.StartsWith ("System")) + return false; + + return true; + } + + return false; + } + } + + private static string? GetModuleName (TypeSystemEntity entity) + { + return entity switch { + MetadataType type => type.Module.ToString (), + MethodDesc { OwningType: MetadataType owningType } => owningType.Module.ToString (), + _ => null + }; } protected virtual void VerifyModule (ModuleDefinition original, ModuleDefinition? linked) @@ -92,12 +220,12 @@ protected virtual void VerifyModule (ModuleDefinition original, ModuleDefinition VerifyCustomAttributes (original, linked); } - protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDefinition? linked) + protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDesc? linked) { - if (linked != null && verifiedGeneratedTypes.Contains (linked.FullName)) + if (linked != null && NameUtils.GetActualOriginDisplayName (linked) is string linkedDisplayName && verifiedGeneratedTypes.Contains (linkedDisplayName)) return; - ModuleDefinition? linkedModule = linked?.Module; + EcmaModule? linkedModule = (linked as MetadataType)?.Module as EcmaModule; // // Little bit complex check to allow easier test writing to match @@ -106,9 +234,9 @@ protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDefini // - 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))); + HasActiveKeptDerivedAttribute (original) || + (linked != null && linkedModule?.EntryPoint?.OwningType == linked) || + original.AllMembers ().Any (HasActiveKeptDerivedAttribute); if (!expectedKept) { if (linked != null) @@ -128,19 +256,23 @@ protected virtual void VerifyTypeDefinition (TypeDefinition original, TypeDefini foreach (var attr in original.CustomAttributes.Where (l => l.AttributeType.Name == nameof (CreatedMemberAttribute))) { var newName = original.FullName + "::" + attr.ConstructorArguments[0].Value.ToString (); - if (linkedMembers!.RemoveWhere (l => l.Contains (newName)) != 1) + var linkedMemberName = linkedMembers.Keys.FirstOrDefault (l => l.Contains (newName)); + if (linkedMemberName == null) Assert.True (false, $"Newly created member '{newName}' was not found"); + + linkedMembers.Remove (linkedMemberName); } } } - protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDefinition? linked) + protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDesc? linked) { if (linked == null) { Assert.True (false, $"Type `{original}' should have been kept"); return; } +#if false if (!original.IsInterface) VerifyBaseType (original, linked); @@ -151,13 +283,20 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe VerifySecurityAttributes (original, linked); VerifyFixedBufferFields (original, linked); +#endif foreach (var td in original.NestedTypes) { - VerifyTypeDefinition (td, linked?.NestedTypes.FirstOrDefault (l => td.FullName == l.FullName)); - linkedMembers.Remove (td.FullName); + string originalFullName = NameUtils.GetExpectedOriginDisplayName (td); + linkedMembers.TryGetValue ( + originalFullName, + out TypeSystemEntity? linkedMember); + + VerifyTypeDefinition (td, linkedMember as TypeDesc); + linkedMembers.Remove (originalFullName); } - // Need to check properties before fields so that the KeptBackingFieldAttribute is handled correctly +#if false + //// 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); @@ -177,13 +316,19 @@ protected virtual void VerifyTypeDefinitionKept (TypeDefinition original, TypeDe VerifyField (f, linked.Fields.FirstOrDefault (l => f.Name == l.Name)); linkedMembers.Remove (f.FullName); } +#endif foreach (var m in original.Methods) { if (verifiedEventMethods.Contains (m.FullName)) continue; - var msign = m.GetSignature (); - VerifyMethod (m, linked.Methods.FirstOrDefault (l => msign == l.GetSignature ())); - linkedMembers.Remove (m.FullName); + + string originalFullName = NameUtils.GetExpectedOriginDisplayName (m); + linkedMembers.TryGetValue ( + originalFullName, + out TypeSystemEntity? linkedMember); + + VerifyMethod (m, linkedMember as MethodDesc); + linkedMembers.Remove (originalFullName); } } @@ -319,13 +464,13 @@ private void VerifyEvent (EventDefinition src, EventDefinition? linked, TypeDefi } if (src.CustomAttributes.Any (attr => attr.AttributeType.Name == nameof (KeptEventAddMethodAttribute))) { - VerifyMethodInternal (src.AddMethod, linked.AddMethod, true); + //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); + //VerifyMethodInternal (src.RemoveMethod, linked.RemoveMethod, true); verifiedEventMethods.Add (src.RemoveMethod.FullName); linkedMembers.Remove (src.RemoveMethod.FullName); } @@ -334,17 +479,17 @@ private void VerifyEvent (EventDefinition src, EventDefinition? linked, TypeDefi VerifyCustomAttributes (src, linked); } - private void VerifyMethod (MethodDefinition src, MethodDefinition? linked) + private void VerifyMethod (MethodDefinition src, MethodDesc? linked) { bool expectedKept = ShouldMethodBeKept (src); VerifyMethodInternal (src, linked, expectedKept); } - private void VerifyMethodInternal (MethodDefinition src, MethodDefinition? linked, bool expectedKept) + private void VerifyMethodInternal (MethodDefinition src, MethodDesc? linked, bool expectedKept) { if (!expectedKept) { if (linked != null) - Assert.True (false, $"Method `{src.FullName}' should have been removed"); + Assert.True (false, $"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been removed"); return; } @@ -381,13 +526,14 @@ private void VerifyMemberBackingField (IMemberDefinition src, TypeDefinition lin linkedMembers.Remove (srcField.FullName); } - protected virtual void VerifyMethodKept (MethodDefinition src, MethodDefinition? linked) + protected virtual void VerifyMethodKept (MethodDefinition src, MethodDesc? linked) { if (linked == null) { - Assert.True (false, $"Method `{src.FullName}' should have been kept"); + Assert.True (false, $"Method `{NameUtils.GetExpectedOriginDisplayName (src)}' should have been kept"); return; } +#if false VerifyPseudoAttributes (src, linked); VerifyGenericParameters (src, linked); VerifyCustomAttributes (src, linked); @@ -395,7 +541,10 @@ protected virtual void VerifyMethodKept (MethodDefinition src, MethodDefinition? VerifyParameters (src, linked); VerifySecurityAttributes (src, linked); VerifyArrayInitializers (src, linked); + + // Method bodies are not very different in Native AOT VerifyMethodBody (src, linked); +#endif } protected virtual void VerifyMethodBody (MethodDefinition src, MethodDefinition linked) @@ -735,7 +884,7 @@ private void VerifyInitializerField (FieldDefinition src, FieldDefinition? linke VerifyFieldKept (src, linked); verifiedGeneratedFields.Add (linked!.FullName); linkedMembers.Remove (linked.FullName); - VerifyTypeDefinitionKept (src.FieldType.Resolve (), linked.FieldType.Resolve ()); + //VerifyTypeDefinitionKept (src.FieldType.Resolve (), linked.FieldType.Resolve ()); linkedMembers.Remove (linked.FieldType.FullName); linkedMembers.Remove (linked.DeclaringType.FullName); verifiedGeneratedTypes.Add (linked.DeclaringType.FullName); @@ -840,7 +989,7 @@ private void VerifyFixedBufferFields (TypeDefinition src, TypeDefinition linked) verifiedGeneratedFields.Add (originalElementField.FullName); linkedMembers.Remove (linkedField!.FullName); - VerifyTypeDefinitionKept (originalCompilerGeneratedBufferType, linkedCompilerGeneratedBufferType); + //VerifyTypeDefinitionKept (originalCompilerGeneratedBufferType, linkedCompilerGeneratedBufferType); verifiedGeneratedTypes.Add (originalCompilerGeneratedBufferType.FullName); } } @@ -915,14 +1064,20 @@ protected virtual bool ShouldMethodBeKept (MethodDefinition method) protected virtual bool ShouldBeKept (T member, string? signature = null) where T : MemberReference, ICustomAttributeProvider { - if (member.HasAttribute (nameof (KeptAttribute))) + if (HasActiveKeptAttribute (member)) return true; ICustomAttributeProvider cap = (ICustomAttributeProvider) member.DeclaringType; if (cap == null) return false; - return GetCustomAttributeCtorValues (cap, nameof (KeptMemberAttribute)).Any (a => a == (signature ?? member.Name)); + return GetActiveKeptAttributes (cap, nameof (KeptMemberAttribute)).Any (ca => { + if (ca.Constructor.Parameters.Count != 1 || + ca.ConstructorArguments[0].Value is not string a) + return false; + + return a == (signature ?? member.Name); + }); } protected static uint GetExpectedPseudoAttributeValue (ICustomAttributeProvider provider, uint sourceValue) @@ -955,5 +1110,584 @@ protected static IEnumerable GetStringOrTypeArrayAttributeValue (CustomA { return ((CustomAttributeArgument[]) attribute.ConstructorArguments[0].Value)?.Select (arg => arg.Value.ToString ()!); } + + private static IEnumerable GetActiveKeptAttributes (ICustomAttributeProvider provider, string attributeName) + { + return provider.CustomAttributes.Where (ca => { + if (ca.AttributeType.Name != attributeName) { + return false; + } + + object? keptBy = ca.GetPropertyValue (nameof (KeptAttribute.By)); + return keptBy is null ? true : ((ProducedBy) keptBy).HasFlag (ProducedBy.NativeAot); + }); + } + + private static bool HasActiveKeptAttribute (ICustomAttributeProvider provider) + { + return GetActiveKeptAttributes (provider, nameof (KeptAttribute)).Any (); + } + + private static IEnumerable GetActiveKeptDerivedAttributes (ICustomAttributeProvider provider) + { + return provider.CustomAttributes.Where (ca => { + if (!ca.AttributeType.Resolve ().DerivesFrom (nameof (KeptAttribute))) { + return false; + } + + object? keptBy = ca.GetPropertyValue (nameof (KeptAttribute.By)); + return keptBy is null ? true : ((ProducedBy) keptBy).HasFlag (ProducedBy.NativeAot); + }); + } + + + private static bool HasActiveKeptDerivedAttribute (ICustomAttributeProvider provider) + { + return GetActiveKeptDerivedAttributes (provider).Any (); + } + + private void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original) + { + var checks = BuildOtherAssemblyCheckTable (original); + + // TODO + // For now disable the code below by simply removing all checks + checks.Clear (); + + try { + foreach (var assemblyName in checks.Keys) { + var linkedAssembly = ResolveLinkedAssembly (assemblyName); + foreach (var checkAttrInAssembly in checks[assemblyName]) { + var attributeTypeName = checkAttrInAssembly.AttributeType.Name; + + switch (attributeTypeName) { + case nameof (KeptAllTypesAndMembersInAssemblyAttribute): + VerifyKeptAllTypesAndMembersInAssembly (linkedAssembly); + continue; + case nameof (KeptAttributeInAssemblyAttribute): + VerifyKeptAttributeInAssembly (checkAttrInAssembly, linkedAssembly); + continue; + case nameof (RemovedAttributeInAssembly): + VerifyRemovedAttributeInAssembly (checkAttrInAssembly, linkedAssembly); + continue; + default: + break; + } + + var expectedTypeName = checkAttrInAssembly.ConstructorArguments[1].Value.ToString ()!; + TypeDefinition? linkedType = linkedAssembly.MainModule.GetType (expectedTypeName); + + if (linkedType == null && linkedAssembly.MainModule.HasExportedTypes) { + ExportedType? exportedType = linkedAssembly.MainModule.ExportedTypes + .FirstOrDefault (exported => exported.FullName == expectedTypeName); + + // Note that copied assemblies could have dangling references. + if (exportedType != null && original.EntryPoint.DeclaringType.CustomAttributes.FirstOrDefault ( + ca => ca.AttributeType.Name == nameof (RemovedAssemblyAttribute) + && ca.ConstructorArguments[0].Value.ToString () == exportedType.Scope.Name + ".dll") != null) + continue; + + linkedType = exportedType?.Resolve (); + } + + switch (attributeTypeName) { + case nameof (RemovedTypeInAssemblyAttribute): + if (linkedType != null) + Assert.Fail ($"Type `{expectedTypeName}' should have been removed from assembly {assemblyName}"); + GetOriginalTypeFromInAssemblyAttribute (checkAttrInAssembly); + break; + case nameof (KeptTypeInAssemblyAttribute): + if (linkedType == null) + Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + break; + case nameof (RemovedInterfaceOnTypeInAssemblyAttribute): + if (linkedType == null) + Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + VerifyRemovedInterfaceOnTypeInAssembly (checkAttrInAssembly, linkedType); + break; + case nameof (KeptInterfaceOnTypeInAssemblyAttribute): + if (linkedType == null) + Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + VerifyKeptInterfaceOnTypeInAssembly (checkAttrInAssembly, linkedType); + break; + case nameof (RemovedMemberInAssemblyAttribute): + if (linkedType == null) + continue; + + VerifyRemovedMemberInAssembly (checkAttrInAssembly, linkedType); + break; + case nameof (KeptBaseOnTypeInAssemblyAttribute): + if (linkedType == null) + Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + VerifyKeptBaseOnTypeInAssembly (checkAttrInAssembly, linkedType); + break; + case nameof (KeptMemberInAssemblyAttribute): + if (linkedType == null) + Assert.Fail ($"Type `{expectedTypeName}' should have been kept in assembly {assemblyName}"); + + VerifyKeptMemberInAssembly (checkAttrInAssembly, linkedType); + break; + case nameof (RemovedForwarderAttribute): + if (linkedAssembly.MainModule.ExportedTypes.Any (l => l.Name == expectedTypeName)) + Assert.Fail ($"Forwarder `{expectedTypeName}' should have been removed from assembly {assemblyName}"); + + break; + + case nameof (RemovedAssemblyReferenceAttribute): + Assert.False (linkedAssembly.MainModule.AssemblyReferences.Any (l => l.Name == expectedTypeName), + $"AssemblyRef '{expectedTypeName}' should have been removed from assembly {assemblyName}"); + break; + + case nameof (KeptResourceInAssemblyAttribute): + VerifyKeptResourceInAssembly (checkAttrInAssembly); + break; + case nameof (RemovedResourceInAssemblyAttribute): + VerifyRemovedResourceInAssembly (checkAttrInAssembly); + break; + case nameof (KeptReferencesInAssemblyAttribute): + VerifyKeptReferencesInAssembly (checkAttrInAssembly); + break; + case nameof (ExpectedInstructionSequenceOnMemberInAssemblyAttribute): + if (linkedType == null) + Assert.Fail ($"Type `{expectedTypeName}` should have been kept in assembly {assemblyName}"); + VerifyExpectedInstructionSequenceOnMemberInAssembly (checkAttrInAssembly, linkedType); + break; + default: + UnhandledOtherAssemblyAssertion (expectedTypeName, checkAttrInAssembly, linkedType); + break; + } + } + } + } catch (AssemblyResolutionException e) { + Assert.Fail ($"Failed to resolve linked assembly `{e.AssemblyReference.Name}`. It must not exist in the output."); + } + } + + private void VerifyKeptAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDefinition linkedAssembly) + { + VerifyAttributeInAssembly (inAssemblyAttribute, linkedAssembly, VerifyCustomAttributeKept); + } + + private void VerifyRemovedAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDefinition linkedAssembly) + { + VerifyAttributeInAssembly (inAssemblyAttribute, linkedAssembly, VerifyCustomAttributeRemoved); + } + + private void VerifyAttributeInAssembly (CustomAttribute inAssemblyAttribute, AssemblyDefinition linkedAssembly, Action assertExpectedAttribute) + { + var assemblyName = (string) inAssemblyAttribute.ConstructorArguments[0].Value!; + string expectedAttributeTypeName; + var attributeTypeOrTypeName = inAssemblyAttribute.ConstructorArguments[1].Value!; + if (attributeTypeOrTypeName is TypeReference typeReference) { + expectedAttributeTypeName = typeReference.FullName; + } else { + expectedAttributeTypeName = attributeTypeOrTypeName.ToString ()!; + } + + if (inAssemblyAttribute.ConstructorArguments.Count == 2) { + // Assembly + assertExpectedAttribute (linkedAssembly, expectedAttributeTypeName); + return; + } + + // We are asserting on type or member + var typeOrTypeName = inAssemblyAttribute.ConstructorArguments[2].Value; + var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!, typeOrTypeName); + if (originalType == null) + Assert.Fail ($"Invalid test assertion. The original `{assemblyName}` does not contain a type `{typeOrTypeName}`"); + + var linkedType = linkedAssembly.MainModule.GetType (originalType.FullName); + if (linkedType == null) + Assert.Fail ($"Missing expected type `{typeOrTypeName}` in `{assemblyName}`"); + + if (inAssemblyAttribute.ConstructorArguments.Count == 3) { + assertExpectedAttribute (linkedType, expectedAttributeTypeName); + return; + } + + // we are asserting on a member + string memberName = (string) inAssemblyAttribute.ConstructorArguments[3].Value; + + // We will find the matching type from the original assembly first that way we can confirm + // that the name defined in the attribute corresponds to a member that actually existed + var originalFieldMember = originalType.Fields.FirstOrDefault (m => m.Name == memberName); + if (originalFieldMember != null) { + var linkedField = linkedType.Fields.FirstOrDefault (m => m.Name == memberName); + if (linkedField == null) + Assert.Fail ($"Field `{memberName}` on Type `{originalType}` should have been kept"); + + assertExpectedAttribute (linkedField, expectedAttributeTypeName); + return; + } + + var originalPropertyMember = originalType.Properties.FirstOrDefault (m => m.Name == memberName); + if (originalPropertyMember != null) { + var linkedProperty = linkedType.Properties.FirstOrDefault (m => m.Name == memberName); + if (linkedProperty == null) + Assert.Fail ($"Property `{memberName}` on Type `{originalType}` should have been kept"); + + assertExpectedAttribute (linkedProperty, expectedAttributeTypeName); + return; + } + + var originalMethodMember = originalType.Methods.FirstOrDefault (m => m.GetSignature () == memberName); + if (originalMethodMember != null) { + var linkedMethod = linkedType.Methods.FirstOrDefault (m => m.GetSignature () == memberName); + if (linkedMethod == null) + Assert.Fail ($"Method `{memberName}` on Type `{originalType}` should have been kept"); + + assertExpectedAttribute (linkedMethod, expectedAttributeTypeName); + return; + } + + Assert.Fail ($"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"); + } + + private static void VerifyCopyAssemblyIsKeptUnmodified (NPath outputDirectory, string assemblyName) + { + string inputAssemblyPath = Path.Combine (Directory.GetParent (outputDirectory)!.ToString (), "input", assemblyName); + string outputAssemblyPath = Path.Combine (outputDirectory, assemblyName); + Assert.True (File.ReadAllBytes (inputAssemblyPath).SequenceEqual (File.ReadAllBytes (outputAssemblyPath)), + $"Expected assemblies\n" + + $"\t{inputAssemblyPath}\n" + + $"\t{outputAssemblyPath}\n" + + $"binaries to be equal, since the input assembly has copy action."); + } + + private void VerifyCustomAttributeKept (ICustomAttributeProvider provider, string expectedAttributeTypeName) + { + var match = provider.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.FullName == expectedAttributeTypeName); + if (match == null) + Assert.Fail ($"Expected `{provider}` to have an attribute of type `{expectedAttributeTypeName}`"); + } + + private void VerifyCustomAttributeRemoved (ICustomAttributeProvider provider, string expectedAttributeTypeName) + { + var match = provider.CustomAttributes.FirstOrDefault (attr => attr.AttributeType.FullName == expectedAttributeTypeName); + if (match != null) + Assert.Fail ($"Expected `{provider}` to no longer have an attribute of type `{expectedAttributeTypeName}`"); + } + + private void VerifyRemovedInterfaceOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + { + var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); + + var interfaceAssemblyName = inAssemblyAttribute.ConstructorArguments[2].Value.ToString ()!; + var interfaceType = inAssemblyAttribute.ConstructorArguments[3].Value; + + var originalInterface = GetOriginalTypeFromInAssemblyAttribute (interfaceAssemblyName, interfaceType); + if (!originalType.HasInterfaces) + Assert.Fail ("Invalid assertion. Original type does not have any interfaces"); + + var originalInterfaceImpl = GetMatchingInterfaceImplementationOnType (originalType, originalInterface.FullName); + if (originalInterfaceImpl == null) + Assert.Fail ($"Invalid assertion. Original type never had an interface of type `{originalInterface}`"); + + var linkedInterfaceImpl = GetMatchingInterfaceImplementationOnType (linkedType, originalInterface.FullName); + if (linkedInterfaceImpl != null) + Assert.Fail ($"Expected `{linkedType}` to no longer have an interface of type {originalInterface.FullName}"); + } + + private void VerifyKeptInterfaceOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + { + var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); + + var interfaceAssemblyName = inAssemblyAttribute.ConstructorArguments[2].Value.ToString ()!; + var interfaceType = inAssemblyAttribute.ConstructorArguments[3].Value; + + var originalInterface = GetOriginalTypeFromInAssemblyAttribute (interfaceAssemblyName, interfaceType); + if (!originalType.HasInterfaces) + Assert.Fail ("Invalid assertion. Original type does not have any interfaces"); + + var originalInterfaceImpl = GetMatchingInterfaceImplementationOnType (originalType, originalInterface.FullName); + if (originalInterfaceImpl == null) + Assert.Fail ($"Invalid assertion. Original type never had an interface of type `{originalInterface}`"); + + var linkedInterfaceImpl = GetMatchingInterfaceImplementationOnType (linkedType, originalInterface.FullName); + if (linkedInterfaceImpl == null) + Assert.Fail ($"Expected `{linkedType}` to have interface of type {originalInterface.FullName}"); + } + + private void VerifyKeptBaseOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + { + var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); + + var baseAssemblyName = inAssemblyAttribute.ConstructorArguments[2].Value.ToString ()!; + var baseType = inAssemblyAttribute.ConstructorArguments[3].Value; + + var originalBase = GetOriginalTypeFromInAssemblyAttribute (baseAssemblyName, baseType); + if (originalType.BaseType.Resolve () != originalBase) + Assert.Fail ("Invalid assertion. Original type's base does not match the expected base"); + + Assert.True (originalBase.FullName == linkedType.BaseType.FullName, + $"Incorrect base on `{linkedType.FullName}`. Expected `{originalBase.FullName}` but was `{linkedType.BaseType.FullName}`"); + } + + private static InterfaceImplementation? GetMatchingInterfaceImplementationOnType (TypeDefinition type, string expectedInterfaceTypeName) + { + return type.Interfaces.FirstOrDefault (impl => { + var resolvedImpl = impl.InterfaceType.Resolve (); + + if (resolvedImpl == null) + Assert.Fail ($"Failed to resolve interface : `{impl.InterfaceType}` on `{type}`"); + + return resolvedImpl.FullName == expectedInterfaceTypeName; + }); + } + + private void VerifyRemovedMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + { + var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); + foreach (var memberNameAttr in (CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[2].Value) { + string memberName = (string) memberNameAttr.Value; + + // We will find the matching type from the original assembly first that way we can confirm + // that the name defined in the attribute corresponds to a member that actually existed + var originalFieldMember = originalType.Fields.FirstOrDefault (m => m.Name == memberName); + if (originalFieldMember != null) { + var linkedField = linkedType.Fields.FirstOrDefault (m => m.Name == memberName); + if (linkedField != null) + Assert.Fail ($"Field `{memberName}` on Type `{originalType}` should have been removed"); + + continue; + } + + var originalPropertyMember = originalType.Properties.FirstOrDefault (m => m.Name == memberName); + if (originalPropertyMember != null) { + var linkedProperty = linkedType.Properties.FirstOrDefault (m => m.Name == memberName); + if (linkedProperty != null) + Assert.Fail ($"Property `{memberName}` on Type `{originalType}` should have been removed"); + + continue; + } + + var originalMethodMember = originalType.Methods.FirstOrDefault (m => m.GetSignature () == memberName); + if (originalMethodMember != null) { + var linkedMethod = linkedType.Methods.FirstOrDefault (m => m.GetSignature () == memberName); + if (linkedMethod != null) + Assert.Fail ($"Method `{memberName}` on Type `{originalType}` should have been removed"); + + continue; + } + + Assert.Fail ($"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"); + } + } + + private void VerifyKeptMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + { + var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); + var memberNames = (CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[2].Value; + Assert.True (memberNames.Length > 0, "Invalid KeptMemberInAssemblyAttribute. Expected member names."); + foreach (var memberNameAttr in memberNames) { + string memberName = (string) memberNameAttr.Value; + + // We will find the matching type from the original assembly first that way we can confirm + // that the name defined in the attribute corresponds to a member that actually existed + + if (TryVerifyKeptMemberInAssemblyAsField (memberName, originalType, linkedType)) + continue; + + if (TryVerifyKeptMemberInAssemblyAsProperty (memberName, originalType, linkedType)) + continue; + + if (TryVerifyKeptMemberInAssemblyAsMethod (memberName, originalType, linkedType)) + continue; + + Assert.Fail ($"Invalid test assertion. No member named `{memberName}` exists on the original type `{originalType}`"); + } + } + + protected virtual bool TryVerifyKeptMemberInAssemblyAsField (string memberName, TypeDefinition originalType, TypeDefinition linkedType) + { + var originalFieldMember = originalType.Fields.FirstOrDefault (m => m.Name == memberName); + if (originalFieldMember != null) { + var linkedField = linkedType.Fields.FirstOrDefault (m => m.Name == memberName); + if (linkedField == null) + Assert.Fail ($"Field `{memberName}` on Type `{originalType}` should have been kept"); + + return true; + } + + return false; + } + + protected virtual bool TryVerifyKeptMemberInAssemblyAsProperty (string memberName, TypeDefinition originalType, TypeDefinition linkedType) + { + var originalPropertyMember = originalType.Properties.FirstOrDefault (m => m.Name == memberName); + if (originalPropertyMember != null) { + var linkedProperty = linkedType.Properties.FirstOrDefault (m => m.Name == memberName); + if (linkedProperty == null) + Assert.Fail ($"Property `{memberName}` on Type `{originalType}` should have been kept"); + + return true; + } + + return false; + } + + protected virtual bool TryVerifyKeptMemberInAssemblyAsMethod (string memberName, TypeDefinition originalType, TypeDefinition linkedType) + { + return TryVerifyKeptMemberInAssemblyAsMethod (memberName, originalType, linkedType, out _, out _); + } + + protected virtual bool TryVerifyKeptMemberInAssemblyAsMethod (string memberName, TypeDefinition originalType, TypeDefinition linkedType, out MethodDefinition? originalMethod, out MethodDefinition? linkedMethod) + { + originalMethod = originalType.Methods.FirstOrDefault (m => m.GetSignature () == memberName); + if (originalMethod != null) { + linkedMethod = linkedType.Methods.FirstOrDefault (m => m.GetSignature () == memberName); + if (linkedMethod == null) + Assert.Fail ($"Method `{memberName}` on Type `{originalType}` should have been kept"); + + return true; + } + + linkedMethod = null; + return false; + } + + private void VerifyKeptReferencesInAssembly (CustomAttribute inAssemblyAttribute) + { + var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!); + var expectedReferenceNames = ((CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[1].Value).Select (attr => (string) attr.Value).ToList (); + for (int i = 0; i < expectedReferenceNames.Count; i++) + if (expectedReferenceNames[i].EndsWith (".dll")) + expectedReferenceNames[i] = expectedReferenceNames[i].Substring (0, expectedReferenceNames[i].LastIndexOf (".")); + + Assert.Equal (assembly.MainModule.AssemblyReferences.Select (asm => asm.Name), expectedReferenceNames); + } + + private void VerifyKeptResourceInAssembly (CustomAttribute inAssemblyAttribute) + { + var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!); + var resourceName = inAssemblyAttribute.ConstructorArguments[1].Value.ToString (); + + Assert.Contains (resourceName, assembly.MainModule.Resources.Select (r => r.Name)); + } + + private void VerifyRemovedResourceInAssembly (CustomAttribute inAssemblyAttribute) + { + var assembly = ResolveLinkedAssembly (inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!); + var resourceName = inAssemblyAttribute.ConstructorArguments[1].Value.ToString (); + + Assert.DoesNotContain (resourceName, assembly.MainModule.Resources.Select (r => r.Name)); + } + + private void VerifyKeptAllTypesAndMembersInAssembly (AssemblyDefinition linked) + { + var original = ResolveOriginalsAssembly (linked.MainModule.Assembly.Name.Name); + + if (original == null) + Assert.Fail ($"Failed to resolve original assembly {linked.MainModule.Assembly.Name.Name}"); + + var originalTypes = original.AllDefinedTypes ().ToDictionary (t => t.FullName); + var linkedTypes = linked.AllDefinedTypes ().ToDictionary (t => t.FullName); + + var missingInLinked = originalTypes.Keys.Except (linkedTypes.Keys); + + Assert.True (missingInLinked.Any (), $"Expected all types to exist in the linked assembly, but one or more were missing"); + + foreach (var originalKvp in originalTypes) { + var linkedType = linkedTypes[originalKvp.Key]; + + var originalMembers = originalKvp.Value.AllMembers ().Select (m => m.FullName); + var linkedMembers = linkedType.AllMembers ().Select (m => m.FullName); + + var missingMembersInLinked = originalMembers.Except (linkedMembers); + + Assert.True (missingMembersInLinked.Any (), $"Expected all members of `{originalKvp.Key}`to exist in the linked assembly, but one or more were missing"); + } + } + + private TypeDefinition GetOriginalTypeFromInAssemblyAttribute (CustomAttribute inAssemblyAttribute) + { + string assemblyName; + if (inAssemblyAttribute.HasProperties && inAssemblyAttribute.Properties[0].Name == "ExpectationAssemblyName") + assemblyName = inAssemblyAttribute.Properties[0].Argument.Value.ToString ()!; + else + assemblyName = inAssemblyAttribute.ConstructorArguments[0].Value.ToString ()!; + + return GetOriginalTypeFromInAssemblyAttribute (assemblyName, inAssemblyAttribute.ConstructorArguments[1].Value); + } + + private TypeDefinition GetOriginalTypeFromInAssemblyAttribute (string assemblyName, object typeOrTypeName) + { + if (typeOrTypeName is TypeReference attributeValueAsTypeReference) + return attributeValueAsTypeReference.Resolve (); + + var assembly = ResolveOriginalsAssembly (assemblyName); + + var expectedTypeName = typeOrTypeName.ToString (); + var originalType = assembly.MainModule.GetType (expectedTypeName); + if (originalType == null) + Assert.Fail ($"Invalid test assertion. Unable to locate the original type `{expectedTypeName}.`"); + return originalType; + } + + private static Dictionary> BuildOtherAssemblyCheckTable (AssemblyDefinition original) + { + var checks = new Dictionary> (); + + foreach (var typeWithRemoveInAssembly in original.AllDefinedTypes ()) { + foreach (var attr in typeWithRemoveInAssembly.CustomAttributes.Where (IsTypeInOtherAssemblyAssertion)) { + var assemblyName = (string) attr.ConstructorArguments[0].Value; + if (!checks.TryGetValue (assemblyName, out List? checksForAssembly)) + checks[assemblyName] = checksForAssembly = new List (); + + checksForAssembly.Add (attr); + } + } + + return checks; + } + + protected AssemblyDefinition ResolveLinkedAssembly (string assemblyName) + { + //var cleanAssemblyName = assemblyName; + //if (assemblyName.EndsWith (".exe") || assemblyName.EndsWith (".dll")) + //cleanAssemblyName = System.IO.Path.GetFileNameWithoutExtension (assemblyName); + //return _linkedResolver.Resolve (new AssemblyNameReference (cleanAssemblyName, null), _linkedReaderParameters); + // TODO - adapt to Native AOT + return ResolveOriginalsAssembly (assemblyName); + } + + protected AssemblyDefinition ResolveOriginalsAssembly (string assemblyName) + { + var cleanAssemblyName = assemblyName; + if (assemblyName.EndsWith (".exe") || assemblyName.EndsWith (".dll")) + cleanAssemblyName = Path.GetFileNameWithoutExtension (assemblyName); + return originalsResolver.Resolve (new AssemblyNameReference (cleanAssemblyName, null), originalReaderParameters); + } + + private static bool IsTypeInOtherAssemblyAssertion (CustomAttribute attr) + { + return attr.AttributeType.Resolve ()?.DerivesFrom (nameof (BaseInAssemblyAttribute)) ?? false; + } + + private void VerifyExpectedInstructionSequenceOnMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + { + var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); + var memberName = (string) inAssemblyAttribute.ConstructorArguments[2].Value; + + if (TryVerifyKeptMemberInAssemblyAsMethod (memberName, originalType, linkedType, out MethodDefinition? originalMethod, out MethodDefinition? linkedMethod)) { + static string[] valueCollector (MethodDefinition m) => AssemblyChecker.FormatMethodBody (m.Body); + var linkedValues = valueCollector (linkedMethod!); + var srcValues = valueCollector (originalMethod!); + + var expected = ((CustomAttributeArgument[]) inAssemblyAttribute.ConstructorArguments[3].Value)?.Select (arg => arg.Value.ToString ()).ToArray (); + Assert.Equal ( + linkedValues, + expected); + + return; + } + + Assert.Fail ($"Invalid test assertion. No method named `{memberName}` exists on the original type `{originalType}`"); + } + + protected virtual void UnhandledOtherAssemblyAssertion (string expectedTypeName, CustomAttribute checkAttrInAssembly, TypeDefinition? linkedType) + { + throw new NotImplementedException ($"Type {expectedTypeName}, has an unknown other assembly attribute of type {checkAttrInAssembly.AttributeType}"); + } } } diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs index e70aca84829c3f..f9a5dd676aaaf3 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs @@ -18,7 +18,7 @@ public class ILCompilerDriver { private const string DefaultSystemModule = "System.Private.CoreLib"; - public void Trim (ILCompilerOptions options, ILogWriter logWriter) + public ILScanResults Trim (ILCompilerOptions options, ILogWriter logWriter) { ComputeDefaultOptions (out var targetOS, out var targetArchitecture); var targetDetails = new TargetDetails (targetArchitecture, targetOS, TargetAbi.NativeAot); @@ -35,6 +35,11 @@ public void Trim (ILCompilerOptions options, ILogWriter logWriter) inputModules.Add (module); } + foreach (var trimAssembly in options.TrimAssemblies) { + EcmaModule module = typeSystemContext.GetModuleFromPath (trimAssembly); + inputModules.Add (module); + } + CompilationModuleGroup compilationGroup = new TestInfraMultiFileSharedCompilationModuleGroup (typeSystemContext, inputModules); List compilationRoots = new List (); @@ -74,7 +79,7 @@ public void Trim (ILCompilerOptions options, ILogWriter logWriter) logger, Array.Empty> (), Array.Empty (), - Array.Empty (), + options.AdditionalRootAssemblies.ToArray (), options.TrimAssemblies.ToArray ()); CompilationBuilder builder = new RyuJitCompilationBuilder (typeSystemContext, compilationGroup) @@ -87,7 +92,7 @@ public void Trim (ILCompilerOptions options, ILogWriter logWriter) .UseParallelism (System.Diagnostics.Debugger.IsAttached ? 1 : -1) .ToILScanner (); - _ = scanner.Scan (); + return scanner.Scan (); } public static void ComputeDefaultOptions (out TargetOS os, out TargetArchitecture arch) diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerOptions.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerOptions.cs index 7bba7ae4de334d..777f506b14d326 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerOptions.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerOptions.cs @@ -11,6 +11,7 @@ public class ILCompilerOptions public Dictionary ReferenceFilePaths = new Dictionary (); public List InitAssemblies = new List (); public List TrimAssemblies = new List (); + public List AdditionalRootAssemblies = new List (); public Dictionary FeatureSwitches = new Dictionary (); } } diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerOptionsBuilder.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerOptionsBuilder.cs index a370eff817d26a..a865e1d0ebb2c3 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerOptionsBuilder.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerOptionsBuilder.cs @@ -119,6 +119,11 @@ public virtual void AddKeepDebugMembers (string value) public virtual void AddAssemblyAction (string action, string assembly) { + switch (action) { + case "copy": + Options.AdditionalRootAssemblies.Add (assembly); + break; + } } public virtual void AddSkipUnresolved (bool skipUnresolved) diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerTestCaseResult.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerTestCaseResult.cs index 20039ead239a31..5229431b282e6e 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerTestCaseResult.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerTestCaseResult.cs @@ -1,6 +1,7 @@ // 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 ILCompiler; using Mono.Linker.Tests.Extensions; using Mono.Linker.Tests.TestCases; @@ -14,9 +15,10 @@ public class ILCompilerTestCaseResult public readonly TestCaseSandbox Sandbox; public readonly TestCaseMetadataProvider MetadataProvider; public readonly ManagedCompilationResult CompilationResult; + public readonly ILScanResults TrimmingResults; public readonly TestLogWriter LogWriter; - public ILCompilerTestCaseResult (TestCase testCase, NPath inputAssemblyPath, NPath expectationsAssemblyPath, TestCaseSandbox sandbox, TestCaseMetadataProvider metadataProvider, ManagedCompilationResult compilationResult, TestLogWriter logWriter) + public ILCompilerTestCaseResult (TestCase testCase, NPath inputAssemblyPath, NPath expectationsAssemblyPath, TestCaseSandbox sandbox, TestCaseMetadataProvider metadataProvider, ManagedCompilationResult compilationResult, ILScanResults trimmingResults, TestLogWriter logWriter) { TestCase = testCase; InputAssemblyPath = inputAssemblyPath; @@ -24,6 +26,7 @@ public ILCompilerTestCaseResult (TestCase testCase, NPath inputAssemblyPath, NPa Sandbox = sandbox; MetadataProvider = metadataProvider; CompilationResult = compilationResult; + TrimmingResults = trimmingResults; LogWriter = logWriter; } } diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/NameUtils.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/NameUtils.cs new file mode 100644 index 00000000000000..68c4006eb65303 --- /dev/null +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/NameUtils.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Text; +using ILCompiler; +using Internal.TypeSystem; +using Mono.Cecil; +using Mono.Linker.Tests.Extensions; + +namespace Mono.Linker.Tests.TestCasesRunner +{ + internal static class NameUtils + { + internal static string? GetActualOriginDisplayName (TypeSystemEntity? entity) => entity switch { + DefType defType => TrimAssemblyNamePrefix (defType.GetDisplayName ()), + MethodDesc method => TrimAssemblyNamePrefix (method.GetDisplayName ()), + FieldDesc field => TrimAssemblyNamePrefix (field.ToString ()), + ModuleDesc module => module.Assembly.GetName ().Name, + _ => null + }; + + private static string? TrimAssemblyNamePrefix (string? name) + { + if (name == null) + return null; + + if (name.StartsWith ('[')) { + int i = name.IndexOf (']'); + if (i > 0) { + return name.Substring (i + 1); + } + } + + return name; + } + + internal static string GetExpectedOriginDisplayName (ICustomAttributeProvider provider) => + ConvertSignatureToIlcFormat (provider switch { + MethodDefinition method => method.GetDisplayName (), + FieldDefinition field => field.GetDisplayName (), + TypeDefinition type => type.GetDisplayName (), + IMemberDefinition member => member.FullName, + AssemblyDefinition asm => asm.Name.Name, + _ => throw new NotImplementedException () + }); + + internal static string ConvertSignatureToIlcFormat (string value) + { + if (value.Contains ('(') || value.Contains ('<')) { + value = value.Replace (", ", ","); + } + + if (value.Contains ('/')) { + value = value.Replace ('/', '+'); + } + + // Split it into . separated parts and if one is ending with > rewrite it to `1 format + // ILC folows the reflection format which doesn't actually use generic instantiations on anything but the last type + // in nested hierarchy - it's difficult to replicate this with Cecil as it has different representation so just strip that info + var parts = value.Split ('.'); + StringBuilder sb = new StringBuilder (); + foreach (var part in parts) { + if (sb.Length > 0) + sb.Append ('.'); + + if (part.EndsWith ('>')) { + int i = part.LastIndexOf ('<'); + if (i >= 0) { + sb.Append (part.AsSpan (0, i)); + sb.Append ('`'); + sb.Append (part.Substring (i + 1).Where (c => c == ',').Count () + 1); + continue; + } + } + + sb.Append (part); + } + + return sb.ToString (); + } + } +} diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs index 89cfe820172811..090a3a1b039b97 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -4,17 +4,17 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using FluentAssertions; -using ILCompiler; using ILCompiler.Logging; using Internal.TypeSystem; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; using Mono.Linker.Tests.Extensions; using Xunit; @@ -45,18 +45,57 @@ public ResultChecker (BaseAssemblyResolver originalsResolver, _linkedReaderParameters = linkedReaderParameters; } - public virtual void Check (ILCompilerTestCaseResult trimmedResult) + private static bool ShouldValidateIL (AssemblyDefinition inputAssembly) { - InitializeResolvers (trimmedResult); + if (HasAttribute (inputAssembly, nameof (SkipPeVerifyAttribute))) + return false; + + var caaIsUnsafeFlag = (CustomAttributeArgument caa) => + (caa.Type.Name == "String" && caa.Type.Namespace == "System") + && (string) caa.Value == "/unsafe"; + var customAttributeHasUnsafeFlag = (CustomAttribute ca) => ca.ConstructorArguments.Any (caaIsUnsafeFlag); + if (GetCustomAttributes (inputAssembly, nameof (SetupCompileArgumentAttribute)) + .Any (customAttributeHasUnsafeFlag)) + return false; + + return true; + } + + public virtual void Check (ILCompilerTestCaseResult testResult) + { + InitializeResolvers (testResult); try { - var original = ResolveOriginalsAssembly (trimmedResult.ExpectationsAssemblyPath.FileNameWithoutExtension); - AdditionalChecking (trimmedResult, original); + var original = ResolveOriginalsAssembly (testResult.ExpectationsAssemblyPath.FileNameWithoutExtension); + + if (!HasAttribute (original, nameof (NoLinkedOutputAttribute))) { + // TODO Validate presence of the main assembly - if it makes sense (reflection only somehow) + + // IL verification is impossible for NativeAOT since there's no IL output + // if (ShouldValidateIL (original)) + // VerifyIL (); + + InitialChecking (testResult, original); + + PerformOutputAssemblyChecks (original, testResult); + PerformOutputSymbolChecks (original, testResult); + + if (!HasAttribute (original.MainModule.GetType (testResult.TestCase.ReconstructedFullTypeName), nameof (SkipKeptItemsValidationAttribute))) { + CreateAssemblyChecker (original, testResult).Verify (); + } + } + + AdditionalChecking (testResult, original); } finally { _originalsResolver.Dispose (); } } + protected virtual AssemblyChecker CreateAssemblyChecker (AssemblyDefinition original, ILCompilerTestCaseResult testResult) + { + return new AssemblyChecker (_originalsResolver, _originalReaderParameters, original, testResult); + } + private void InitializeResolvers (ILCompilerTestCaseResult linkedResult) { _originalsResolver.AddSearchDirectory (linkedResult.ExpectationsAssemblyPath.Parent.ToString ()); @@ -70,6 +109,56 @@ protected AssemblyDefinition ResolveOriginalsAssembly (string assemblyName) return _originalsResolver.Resolve (new AssemblyNameReference (cleanAssemblyName, null), _originalReaderParameters); } + private static void PerformOutputAssemblyChecks (AssemblyDefinition original, ILCompilerTestCaseResult testResult) + { + var assembliesToCheck = original.MainModule.Types.SelectMany (t => t.CustomAttributes).Where (ExpectationsProvider.IsAssemblyAssertion); + var actionAssemblies = new HashSet (); + //bool trimModeIsCopy = false; + + foreach (var assemblyAttr in assembliesToCheck) { + var name = (string) assemblyAttr.ConstructorArguments.First ().Value; + name = Path.GetFileNameWithoutExtension (name); + +#if false + if (assemblyAttr.AttributeType.Name == nameof (RemovedAssemblyAttribute)) + Assert.IsFalse (expectedPath.FileExists (), $"Expected the assembly {name} to not exist in {outputDirectory}, but it did"); + else if (assemblyAttr.AttributeType.Name == nameof (KeptAssemblyAttribute)) + Assert.IsTrue (expectedPath.FileExists (), $"Expected the assembly {name} to exist in {outputDirectory}, but it did not"); + else if (assemblyAttr.AttributeType.Name == nameof (SetupLinkerActionAttribute)) { + string assemblyName = (string) assemblyAttr.ConstructorArguments[1].Value; + if ((string) assemblyAttr.ConstructorArguments[0].Value == "copy") { + VerifyCopyAssemblyIsKeptUnmodified (outputDirectory, assemblyName + (assemblyName == "test" ? ".exe" : ".dll")); + } + + actionAssemblies.Add (assemblyName); + } else if (assemblyAttr.AttributeType.Name == nameof (SetupLinkerTrimModeAttribute)) { + // We delay checking that everything was copied after processing all assemblies + // with a specific action, since assembly action wins over trim mode. + if ((string) assemblyAttr.ConstructorArguments[0].Value == "copy") + trimModeIsCopy = true; + } else + throw new NotImplementedException ($"Unknown assembly assertion of type {assemblyAttr.AttributeType}"); +#endif + } + +#if false + if (trimModeIsCopy) { + foreach (string assemblyName in Directory.GetFiles (Directory.GetParent (outputDirectory).ToString (), "input")) { + var fileInfo = new FileInfo (assemblyName); + if (fileInfo.Extension == ".dll" && !actionAssemblies.Contains (assemblyName)) + VerifyCopyAssemblyIsKeptUnmodified (outputDirectory, assemblyName + (assemblyName == "test" ? ".exe" : ".dll")); + } + } +#endif + } + +#pragma warning disable IDE0060 // Remove unused parameter + private static void PerformOutputSymbolChecks (AssemblyDefinition original, ILCompilerTestCaseResult testResult) +#pragma warning restore IDE0060 // Remove unused parameter + { + // While NativeAOT has symbols, verifying them is rather difficult + } + protected virtual void AdditionalChecking (ILCompilerTestCaseResult linkResult, AssemblyDefinition original) { bool checkRemainingErrors = !HasAttribute (original.MainModule.GetType (linkResult.TestCase.ReconstructedFullTypeName), nameof (SkipRemainingErrorsValidationAttribute)); @@ -97,6 +186,11 @@ private static IEnumerable GetAttributeProviders (Asse yield return assembly; } + protected virtual void InitialChecking (ILCompilerTestCaseResult testResult, AssemblyDefinition original) + { + // PE verifier is done here in ILLinker, but that's not possible with NativeAOT + } + private void VerifyLoggedMessages (AssemblyDefinition original, TestLogWriter logger, bool checkRemainingErrors) { List loggedMessages = logger.GetLoggedMessages (); @@ -207,8 +301,8 @@ private void VerifyLoggedMessages (AssemblyDefinition original, TestLogWriter lo if (attrProvider is not IMemberDefinition expectedMember) continue; - string? actualName = GetActualOriginDisplayName (methodDesc); - string expectedTypeName = ConvertSignatureToIlcFormat (GetExpectedOriginDisplayName (expectedMember.DeclaringType)); + string? actualName = NameUtils.GetActualOriginDisplayName (methodDesc); + string expectedTypeName = NameUtils.GetExpectedOriginDisplayName (expectedMember.DeclaringType); if (actualName?.Contains (expectedTypeName) == true && actualName?.Contains ("<" + expectedMember.Name + ">") == true) { expectedWarningFound = true; @@ -245,7 +339,7 @@ private void VerifyLoggedMessages (AssemblyDefinition original, TestLogWriter lo } var expectedOriginString = fileName == null - ? GetExpectedOriginDisplayName (attrProvider) + ": " + ? NameUtils.GetExpectedOriginDisplayName (attrProvider) + ": " : ""; Assert.True (expectedWarningFound, @@ -304,7 +398,7 @@ static bool LogMessageHasSameOriginMember (MessageContainer mc, ICustomAttribute { var origin = mc.Origin; Debug.Assert (origin != null); - if (GetActualOriginDisplayName (origin?.MemberDefinition) == ConvertSignatureToIlcFormat (GetExpectedOriginDisplayName (expectedOriginProvider))) + if (NameUtils.GetActualOriginDisplayName (origin?.MemberDefinition) == NameUtils.GetExpectedOriginDisplayName (expectedOriginProvider)) return true; var actualMember = origin!.Value.MemberDefinition; @@ -331,87 +425,51 @@ static bool LogMessageHasSameOriginMember (MessageContainer mc, ICustomAttribute _ => null }; - static string? GetActualOriginDisplayName (TypeSystemEntity? entity) => entity switch { - DefType defType => TrimAssemblyNamePrefix (defType.ToString ()), - MethodDesc method => TrimAssemblyNamePrefix (method.GetDisplayName ()), - FieldDesc field => TrimAssemblyNamePrefix (field.ToString ()), - ModuleDesc module => module.Assembly.GetName ().Name, - _ => null - }; - - static string TrimAssemblyNamePrefix (string name) - { - if (name.StartsWith ('[')) { - int i = name.IndexOf (']'); - if (i > 0) { - return name.Substring (i + 1); - } - } - - return name; - } - - static string GetExpectedOriginDisplayName (ICustomAttributeProvider provider) => - provider switch { - MethodDefinition method => method.GetDisplayName (), - FieldDefinition field => field.GetDisplayName (), - TypeDefinition type => type.GetDisplayName (), - IMemberDefinition member => member.FullName, - AssemblyDefinition asm => asm.Name.Name, - _ => throw new NotImplementedException () - }; - static bool MessageTextContains (string message, string value) { // This is a workaround for different formatting of methods between ilc and linker/analyzer // Sometimes they're written with a space after comma and sometimes without // Method(String,String) - ilc // Method(String, String) - linker/analyzer - return message.Contains (value) || message.Contains (ConvertSignatureToIlcFormat (value)); + return message.Contains (value) || message.Contains (NameUtils.ConvertSignatureToIlcFormat (value)); } + } - static string ConvertSignatureToIlcFormat (string value) - { - if (value.Contains ('(') || value.Contains ('<')) { - value = value.Replace (", ", ","); - } - - // Split it into . separated parts and if one is ending with > rewrite it to `1 format - // ILC folows the reflection format which doesn't actually use generic instantiations on anything but the last type - // in nested hierarchy - it's difficult to replicate this with Cecil as it has different representation so just strip that info - var parts = value.Split ('.'); - StringBuilder sb = new StringBuilder (); - foreach (var part in parts) { - if (sb.Length > 0) - sb.Append ('.'); - - if (part.EndsWith ('>')) { - int i = part.LastIndexOf ('<'); - if (i >= 0) { - sb.Append (part.AsSpan (0, i)); - sb.Append ('`'); - sb.Append (part.Substring (i + 1).Where (c => c == ',').Count () + 1); - continue; - } - } + private static bool HasAttribute (ICustomAttributeProvider caProvider, string attributeName) + { + return TryGetCustomAttribute (caProvider, attributeName, out var _); + } - sb.Append (part); - } +#nullable enable + private static bool TryGetCustomAttribute (ICustomAttributeProvider caProvider, string attributeName, [NotNullWhen (true)] out CustomAttribute? customAttribute) + { + if (caProvider is AssemblyDefinition assembly && assembly.EntryPoint != null) { + customAttribute = assembly.EntryPoint.DeclaringType.CustomAttributes + .FirstOrDefault (attr => attr!.AttributeType.Name == attributeName, null); + return customAttribute is not null; + } - return sb.ToString (); + if (caProvider is TypeDefinition type) { + customAttribute = type.CustomAttributes + .FirstOrDefault (attr => attr!.AttributeType.Name == attributeName, null); + return customAttribute is not null; } + customAttribute = null; + return false; } - private static bool HasAttribute (ICustomAttributeProvider caProvider, string attributeName) + private static IEnumerable GetCustomAttributes (ICustomAttributeProvider caProvider, string attributeName) { if (caProvider is AssemblyDefinition assembly && assembly.EntryPoint != null) return assembly.EntryPoint.DeclaringType.CustomAttributes - .Any (attr => attr.AttributeType.Name == attributeName); + .Where (attr => attr!.AttributeType.Name == attributeName); if (caProvider is TypeDefinition type) - return type.CustomAttributes.Any (attr => attr.AttributeType.Name == attributeName); + return type.CustomAttributes + .Where (attr => attr!.AttributeType.Name == attributeName); - return false; + return Enumerable.Empty (); } +#nullable restore } } diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/TestRunner.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/TestRunner.cs index 6911201b8581a9..50442019768c01 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/TestRunner.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/TestRunner.cs @@ -112,9 +112,9 @@ private ILCompilerTestCaseResult Link (TestCase testCase, TestCaseSandbox sandbo AddLinkOptions (sandbox, compilationResult, builder, metadataProvider); var logWriter = new TestLogWriter (); - trimmer.Trim (builder.Options, logWriter); + var trimmingResults = trimmer.Trim (builder.Options, logWriter); - return new ILCompilerTestCaseResult (testCase, compilationResult.InputAssemblyPath, compilationResult.ExpectationsAssemblyPath, sandbox, metadataProvider, compilationResult, logWriter); + return new ILCompilerTestCaseResult (testCase, compilationResult.InputAssemblyPath, compilationResult.ExpectationsAssemblyPath, sandbox, metadataProvider, compilationResult, trimmingResults, logWriter); } protected virtual void AddLinkOptions (TestCaseSandbox sandbox, ManagedCompilationResult compilationResult, ILCompilerOptionsBuilder builder, TestCaseMetadataProvider metadataProvider)