diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 3d6a155fa17e9..280f1908f79f0 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -35,9 +35,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection.Metadata.Ecma335; using System.Reflection.Runtime.TypeParsing; -using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using ILCompiler.DependencyAnalysisFramework; using ILLink.Shared; @@ -718,33 +716,25 @@ void MarkMethodIfNeededByBaseMethod (MethodDefinition method, MessageOrigin orig /// or if any marked interface implementations on are interfaces that implement and that interface implementation is marked /// bool IsInterfaceImplementationMarkedRecursively (TypeDefinition type, TypeDefinition interfaceType) + => IsInterfaceImplementationMarkedRecursively (type, interfaceType, Context); + + /// + /// Returns true if implements and the interface implementation is marked, + /// or if any marked interface implementations on are interfaces that implement and that interface implementation is marked + /// + internal static bool IsInterfaceImplementationMarkedRecursively (TypeDefinition type, TypeDefinition interfaceType, LinkContext context) { if (type.HasInterfaces) { foreach (var intf in type.Interfaces) { - TypeDefinition? resolvedInterface = Context.Resolve (intf.InterfaceType); + TypeDefinition? resolvedInterface = context.Resolve (intf.InterfaceType); if (resolvedInterface == null) continue; - - if (Annotations.IsMarked (intf) && RequiresInterfaceRecursively (resolvedInterface, interfaceType)) - return true; - } - } - - return false; - } - - bool RequiresInterfaceRecursively (TypeDefinition typeToExamine, TypeDefinition interfaceType) - { - if (typeToExamine == interfaceType) - return true; - - if (typeToExamine.HasInterfaces) { - foreach (var iface in typeToExamine.Interfaces) { - var resolved = Context.TryResolve (iface.InterfaceType); - if (resolved == null) + if (!context.Annotations.IsMarked (intf)) continue; - if (RequiresInterfaceRecursively (resolved, interfaceType)) + if (resolvedInterface == interfaceType) + return true; + if (IsInterfaceImplementationMarkedRecursively (resolvedInterface, interfaceType, context)) return true; } } @@ -3197,8 +3187,12 @@ protected virtual void ProcessMethod (MethodDefinition method, in DependencyInfo Context.Resolve (@base) is MethodDefinition baseDefinition && baseDefinition.DeclaringType.IsInterface && baseDefinition.IsStatic && method.IsStatic) continue; + // Instance methods can have overrides on public implementation methods in IL, but C# will usually only have them for private explicit interface implementations. + // It is valid IL for a public method to override an interface method and only be called directly. In this case it would be safe to skip marking the .override method. + // However, in most cases, the C# compiler will only generate .override for instance methods when it's a private explicit interface implementations which can only be called through the interface. + // We can just take a short cut and mark all the overrides on instance methods. We shouldn't miss out on size savings for code generated by Roslyn. MarkMethod (@base, new DependencyInfo (DependencyKind.MethodImplOverride, method), methodOrigin); - MarkExplicitInterfaceImplementation (method, @base); + MarkRuntimeInterfaceImplementation (method, @base); } } @@ -3314,22 +3308,21 @@ protected virtual void MarkRequirementsForInstantiatedTypes (TypeDefinition type DoAdditionalInstantiatedTypeProcessing (type); } - void MarkExplicitInterfaceImplementation (MethodDefinition method, MethodReference ov) + void MarkRuntimeInterfaceImplementation (MethodDefinition method, MethodReference ov) { if (Context.Resolve (ov) is not MethodDefinition resolvedOverride) return; + if (!resolvedOverride.DeclaringType.IsInterface) + return; + var interfaceToBeImplemented = ov.DeclaringType; - if (resolvedOverride.DeclaringType.IsInterface) { - foreach (var ifaceImpl in method.DeclaringType.Interfaces) { - var resolvedInterfaceType = Context.Resolve (ifaceImpl.InterfaceType); - if (resolvedInterfaceType == null) { - continue; - } - - if (resolvedInterfaceType == resolvedOverride.DeclaringType) { - MarkInterfaceImplementation (ifaceImpl, new MessageOrigin (method.DeclaringType)); - return; - } + var ifaces = Annotations.GetRecursiveInterfaces (method.DeclaringType); + if (ifaces is null) + return; + foreach (var iface in ifaces) { + if (TypeReferenceEqualityComparer.AreEqual (iface.InterfaceType, interfaceToBeImplemented, Context)) { + MarkInterfaceImplementationList (iface.ImplementationChain, new MessageOrigin (method.DeclaringType)); + return; } } } diff --git a/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs b/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs index 794ef60ea19a7..eb3a8fa0f55fc 100644 --- a/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs @@ -456,14 +456,19 @@ void SweepOverrides (MethodDefinition method) // This can happen for a couple of reasons, but it indicates the method isn't in the final assembly. // Resolve also may return a removed value if method.Overrides[i] is a MethodDefinition. In this case, Resolve short circuits and returns `this`. // OR - // ov.DeclaringType is null + // ov.DeclaringType is null // ov.DeclaringType may be null if Resolve short circuited and returned a removed method. In this case, we want to remove the override. // OR - // ov is in a `link` scope and is unmarked + // ov is in a `link` scope and is unmarked // ShouldRemove returns true if the method is unmarked, but we also We need to make sure the override is in a link scope. // Only things in a link scope are marked, so ShouldRemove is only valid for items in a `link` scope. + // OR + // ov is an interface method and the interface is not implemented by the type #pragma warning disable RS0030 // Cecil's Resolve is banned - it's necessary when the metadata graph isn't stable - if (method.Overrides[i].Resolve () is not MethodDefinition ov || ov.DeclaringType is null || (IsLinkScope (ov.DeclaringType.Scope) && ShouldRemove (ov))) + if (method.Overrides[i].Resolve () is not MethodDefinition ov + || ov.DeclaringType is null + || (IsLinkScope (ov.DeclaringType.Scope) && ShouldRemove (ov)) + || (ov.DeclaringType.IsInterface && !MarkStep.IsInterfaceImplementationMarkedRecursively (method.DeclaringType, ov.DeclaringType, Context))) method.Overrides.RemoveAt (i); else i++; diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/StaticInterfaceMethods.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/StaticInterfaceMethods.cs index 77e6421a9572d..747c03965d5eb 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/StaticInterfaceMethods.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/StaticInterfaceMethods.cs @@ -16,6 +16,12 @@ public Task BaseProvidesInterfaceMethod () return RunTest (allowMissingWarnings: false); } + [Fact] + public Task RemovedInterfaceImplementationRemovedOverride() + { + return RunTest (allowMissingWarnings: false); + } + [Fact] public Task StaticAbstractInterfaceMethods () { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptOverrideAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptOverrideAttribute.cs index 3c02278ea9142..919e62ed423a4 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptOverrideAttribute.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptOverrideAttribute.cs @@ -15,12 +15,12 @@ namespace Mono.Linker.Tests.Cases.Expectations.Assertions [AttributeUsage (AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class KeptOverrideAttribute : KeptAttribute { - public Type TypeWithOverriddenMethodDeclaration; - public KeptOverrideAttribute (Type typeWithOverriddenMethod) { ArgumentNullException.ThrowIfNull (typeWithOverriddenMethod); - TypeWithOverriddenMethodDeclaration = typeWithOverriddenMethod; + } + public KeptOverrideAttribute (string typeWithOverriddenMethod) + { } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptOverrideOnMethodInAssemblyAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptOverrideOnMethodInAssemblyAttribute.cs new file mode 100644 index 0000000000000..22943070f94ad --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptOverrideOnMethodInAssemblyAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + [AttributeUsage (AttributeTargets.All, AllowMultiple = true)] + public class KeptOverrideOnMethodInAssemblyAttribute : BaseInAssemblyAttribute + { + public KeptOverrideOnMethodInAssemblyAttribute (string assemblyName, string typeName, string methodName, string overriddenMethodName) + { + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RemovedOverrideAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RemovedOverrideAttribute.cs index c15cf9a9e4148..99deecc3b7c25 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RemovedOverrideAttribute.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RemovedOverrideAttribute.cs @@ -14,12 +14,14 @@ namespace Mono.Linker.Tests.Cases.Expectations.Assertions [AttributeUsage (AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class RemovedOverrideAttribute : BaseInAssemblyAttribute { - public Type TypeWithOverriddenMethodDeclaration; public RemovedOverrideAttribute (Type typeWithOverriddenMethod) { if (typeWithOverriddenMethod == null) throw new ArgumentException ("Value cannot be null or empty.", nameof (typeWithOverriddenMethod)); - TypeWithOverriddenMethodDeclaration = typeWithOverriddenMethod; + } + + public RemovedOverrideAttribute (string nameOfOverriddenMethod) + { } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RemovedOverrideOnMethodInAssemblyAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RemovedOverrideOnMethodInAssemblyAttribute.cs new file mode 100644 index 0000000000000..776a496cfa60a --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RemovedOverrideOnMethodInAssemblyAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + [AttributeUsage (AttributeTargets.All, AllowMultiple = true, Inherited = false)] + public class RemovedOverrideOnMethodInAssemblyAttribute : BaseInAssemblyAttribute + { + public RemovedOverrideOnMethodInAssemblyAttribute (string library, string typeName, string methodName, string overriddenMethodFullName) + { + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlow.cs index bff8cbfe57acc..12ff0b8ca3694 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureCheckDataFlow.cs @@ -21,7 +21,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow // not as a separate substitution file, for it to work with NativeAot. // Related: https://github.com/dotnet/runtime/issues/88647 [SetupCompileBefore ("TestFeatures.dll", new[] { "Dependencies/TestFeatures.cs" }, - resources: new object[] { new [] { "FeatureCheckDataFlowTestSubstitutions.xml", "ILLink.Substitutions.xml" } })] + resources: new object[] { new[] { "FeatureCheckDataFlowTestSubstitutions.xml", "ILLink.Substitutions.xml" } })] [IgnoreSubstitutions (false)] public class FeatureCheckDataFlow { @@ -66,12 +66,9 @@ static void UnguardedIf () [ExpectedWarning ("IL3002", nameof (RequiresAssemblyFiles), Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific Warning")] static void UnguardedElse () { - if (TestFeatures.IsUnreferencedCodeSupported) - { + if (TestFeatures.IsUnreferencedCodeSupported) { throw new Exception (); - } - else - { + } else { RequiresUnreferencedCode (); RequiresDynamicCode (); RequiresAssemblyFiles (); @@ -103,8 +100,7 @@ static void UnguardedTernary () [ExpectedWarning ("IL3002", nameof (RequiresAssemblyFiles), Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific Warning")] static void UnguardedThrow () { - if (TestFeatures.IsUnreferencedCodeSupported) - { + if (TestFeatures.IsUnreferencedCodeSupported) { throw new Exception (); } @@ -118,8 +114,7 @@ static void UnguardedThrow () [ExpectedWarning ("IL3002", nameof (RequiresAssemblyFiles), Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific Warning")] static void UnguardedReturn () { - if (TestFeatures.IsUnreferencedCodeSupported) - { + if (TestFeatures.IsUnreferencedCodeSupported) { return; } @@ -220,12 +215,9 @@ static void GuardedIf () [ExpectedWarning ("IL3002", nameof (RequiresAssemblyFiles), Tool.Analyzer, "Trimmer and NativeAOT eliminate the entire problematic branch. Analyzer can only guarantee UnreferencedCode will be supported")] static void GuardedElse () { - if (!TestFeatures.IsUnreferencedCodeSupported) - { + if (!TestFeatures.IsUnreferencedCodeSupported) { throw new Exception (); - } - else - { + } else { RequiresUnreferencedCode (); RequiresDynamicCode (); RequiresAssemblyFiles (); @@ -252,8 +244,7 @@ static void GuardedTernary () [ExpectedWarning ("IL3002", nameof (RequiresAssemblyFiles), Tool.Analyzer, "Trimmer and NativeAOT eliminate the entire problematic branch. Analyzer can only guarantee UnreferencedCode will be supported")] static void GuardedThrow () { - if (!TestFeatures.IsUnreferencedCodeSupported) - { + if (!TestFeatures.IsUnreferencedCodeSupported) { throw new Exception (); } @@ -266,8 +257,7 @@ static void GuardedThrow () [ExpectedWarning ("IL3002", nameof (RequiresAssemblyFiles), Tool.Analyzer, "Trimmer and NativeAOT eliminate the entire problematic branch. Analyzer can only guarantee UnreferencedCode will be supported")] static void GuardedReturn () { - if (!TestFeatures.IsUnreferencedCodeSupported) - { + if (!TestFeatures.IsUnreferencedCodeSupported) { return; } @@ -696,8 +686,7 @@ class ExceptionalDataFlow static void GuardedTryCatchFinally () { - if (TestFeatures.IsUnreferencedCodeSupported) - { + if (TestFeatures.IsUnreferencedCodeSupported) { try { RequiresUnreferencedCode0 (); } catch { @@ -788,7 +777,8 @@ static void NestedTryInCheckInFinally () [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode0))] // Trimmer/NativeAot don't optimize branches away based on DoesNotReturnIfAttribute [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode1), Tool.Trimmer | Tool.NativeAot, "ILLink and NativeAOT should not respect DoesNotReturnAttribute")] - static void AssertInTryNoCatch () { + static void AssertInTryNoCatch () + { try { Debug.Assert (TestFeatures.IsUnreferencedCodeSupported); } finally { @@ -800,7 +790,8 @@ static void AssertInTryNoCatch () { [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode0))] [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode1))] [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode2))] - static void AssertInTryWithCatch () { + static void AssertInTryWithCatch () + { try { Debug.Assert (TestFeatures.IsUnreferencedCodeSupported); } catch { @@ -814,7 +805,8 @@ static void AssertInTryWithCatch () { [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode0))] [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode1))] [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode2))] - static void AssertInCatch () { + static void AssertInCatch () + { try { RequiresUnreferencedCode0 (); } catch { @@ -829,7 +821,8 @@ static void AssertInCatch () { [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode1))] // Trimmer/NativeAot don't optimize branches away based on DoesNotReturnIfAttribute [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode2), Tool.Trimmer | Tool.NativeAot, "ILLink and NativeAOT should not respect DoesNotReturnAttribute")] - static void AssertInFinally () { + static void AssertInFinally () + { try { RequiresUnreferencedCode0 (); } catch { @@ -927,7 +920,8 @@ static void AssertInTryWithCatchNestedInFinally () // Trimmer/NativeAot don't optimize branches away based on DoesNotReturnIfAttribute [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode1), Tool.Trimmer | Tool.NativeAot, "ILLink and NativeAOT should not respect DoesNotReturnAttribute")] [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode3), Tool.Trimmer | Tool.NativeAot, "ILLink and NativeAOT should not respect DoesNotReturnAttribute")] - static void AssertInFinallyNestedInTry () { + static void AssertInFinallyNestedInTry () + { try { try { RequiresUnreferencedCode0 (); @@ -946,7 +940,8 @@ static void AssertInFinallyNestedInTry () { // Trimmer/NativeAot don't optimize branches away based on DoesNotReturnIfAttribute [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode1), Tool.Trimmer | Tool.NativeAot, "ILLink and NativeAOT should not respect DoesNotReturnAttribute")] [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode3), Tool.Trimmer | Tool.NativeAot, "ILLink and NativeAOT should not respect DoesNotReturnAttribute")] - static void AssertInFinallyWithCatchNestedInTry () { + static void AssertInFinallyWithCatchNestedInTry () + { try { try { RequiresUnreferencedCode0 (); @@ -1021,7 +1016,8 @@ static void AssertInTryWithTryFinallyInFinally () } } - public static void Test () { + public static void Test () + { GuardedTryCatchFinally (); CheckInTry (); NestedTryInCheckInTry (); @@ -1056,7 +1052,7 @@ static IEnumerable GuardInIterator () } } - [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode), Tool.Trimmer, "https://github.com/dotnet/linker/issues/3087", CompilerGeneratedCode = true)] + [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCode), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/linker/issues/3087", CompilerGeneratedCode = true)] static IEnumerable StateFlowsAcrossYield () { if (!TestFeatures.IsUnreferencedCodeSupported) @@ -1133,7 +1129,7 @@ static void GuardedLambda () if (TestFeatures.IsUnreferencedCodeSupported) { a = [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode))] - () => RequiresUnreferencedCode (); + () => RequiresUnreferencedCode (); } if (TestFeatures.IsUnreferencedCodeSupported) { @@ -1153,7 +1149,7 @@ static void GuardedLocalFunction () public static void Test () { // Use the IEnumerable to mark the IEnumerable methods - GuardInIterator (); + foreach (var x in GuardInIterator ()) ; StateFlowsAcrossYield (); GuardInAsync (); StateFlowsAcrossAwait (); @@ -1167,45 +1163,45 @@ public static void Test () } [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode))] - static void RequiresUnreferencedCode () {} + static void RequiresUnreferencedCode () { } [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode0))] - static void RequiresUnreferencedCode0 () {} + static void RequiresUnreferencedCode0 () { } [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode1))] - static void RequiresUnreferencedCode1 () {} + static void RequiresUnreferencedCode1 () { } [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode2))] - static void RequiresUnreferencedCode2 () {} + static void RequiresUnreferencedCode2 () { } [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode3))] - static void RequiresUnreferencedCode3 () {} + static void RequiresUnreferencedCode3 () { } [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode4))] - static void RequiresUnreferencedCode4 () {} + static void RequiresUnreferencedCode4 () { } [RequiresUnreferencedCode (nameof (RequiresUnreferencedCodeBool))] static bool RequiresUnreferencedCodeBool () => true; [RequiresDynamicCode (nameof (RequiresUnreferencedCode))] - static void RequiresDynamicCode () {} + static void RequiresDynamicCode () { } [RequiresAssemblyFiles (nameof (RequiresAssemblyFiles))] - static void RequiresAssemblyFiles () {} + static void RequiresAssemblyFiles () { } - static void DoesNotReturnIfTrue ([DoesNotReturnIf (true)] bool condition) {} + static void DoesNotReturnIfTrue ([DoesNotReturnIf (true)] bool condition) { } - static void DoesNotReturnIfFalse ([DoesNotReturnIf (false)] bool condition) {} + static void DoesNotReturnIfFalse ([DoesNotReturnIf (false)] bool condition) { } class DoesNotReturnIfFalseCtor { - public DoesNotReturnIfFalseCtor ([DoesNotReturnIf (false)] bool condition) {} + public DoesNotReturnIfFalseCtor ([DoesNotReturnIf (false)] bool condition) { } } [DoesNotReturn] - static void DoesNotReturn() {} + static void DoesNotReturn () { } - static void RequiresAll([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type t) {} + static void RequiresAll ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] Type t) { } [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] static Type RequiresAllField; @@ -1216,6 +1212,6 @@ class ClassWithRequires public static int StaticField = 0; } - class RequiresAllGeneric<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T> {} + class RequiresAllGeneric<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] T> { } } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/OverrideOfRecursiveInterfaceIsRemoved.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/OverrideOfRecursiveInterfaceIsRemoved.il new file mode 100644 index 0000000000000..43af0e6b95792 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/OverrideOfRecursiveInterfaceIsRemoved.il @@ -0,0 +1,168 @@ +// 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. + +.assembly extern mscorlib { } + +.assembly 'library' { } + +.class public auto ansi beforefieldinit Program + extends [mscorlib]System.Object +{ + // Nested Types + .class interface nested public auto ansi abstract beforefieldinit IBaseUnused + { + // Methods + .method public hidebysig abstract virtual static + void M () cil managed + { + } // end of method IBaseUnused::M + + } // end of class IBaseUnused + + .class interface nested public auto ansi abstract beforefieldinit IBaseUsed + { + // Methods + .method public hidebysig abstract virtual static + void M () cil managed + { + } // end of method IBaseUsed::M + + } // end of class IBaseUsed + + .class interface nested private auto ansi abstract beforefieldinit IMiddleUnused + implements Program/IBaseUnused, + Program/IBaseUsed + { + // Methods + .method public hidebysig abstract virtual static + void O () cil managed + { + } // end of method IMiddleUnused::O + + } // end of class IMiddleUnused + + .class interface nested private auto ansi abstract beforefieldinit IDerived + implements Program/IMiddleUnused + { + // Methods + .method public hidebysig abstract virtual static + void N () cil managed + { + } // end of method IDerived::N + + } // end of class IDerived + + .class nested private auto ansi beforefieldinit A + extends [mscorlib]System.Object + implements Program/IDerived + { + // Methods + .method public hidebysig static + void M () cil managed + { + .override method void Program/IBaseUnused::M() + .override method void Program/IBaseUsed::M() + // Method begins at RVA 0x2083 + // Code size 1 (0x1) + .maxstack 8 + + IL_0000: ret + } // end of method A::M + + .method public hidebysig static + void N () cil managed + { + .override method void Program/IDerived::N() + // Method begins at RVA 0x2083 + // Code size 1 (0x1) + .maxstack 8 + + IL_0000: ret + } // end of method A::N + + .method public hidebysig static + void O () cil managed + { + .override method void Program/IMiddleUnused::O() + // Method begins at RVA 0x2083 + // Code size 1 (0x1) + .maxstack 8 + + IL_0000: ret + } // end of method A::O + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x207b + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method A::.ctor + + } // end of class A + + + // Methods + .method public hidebysig static + void MyTest () cil managed + { + // Method begins at RVA 0x2050 + // Code size 16 (0x10) + .maxstack 8 + + IL_0000: call void Program::UseNThroughIDerived() + IL_0005: call void Program::UseMThroughIDerived() + IL_000a: call void Program/A::M() + IL_000f: call void Program/A::O() + IL_0014: ret + } // end of method Program::MyTest + + .method private hidebysig static + void UseNThroughIDerived<(Program/IDerived) T> () cil managed + { + .param constraint T, Program/IDerived + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + // Method begins at RVA 0x2061 + // Code size 12 (0xc) + .maxstack 8 + + IL_0000: constrained. !!T + IL_0006: call void Program/IDerived::N() + IL_000b: ret + } // end of method Program::UseNThroughIDerived + + .method private hidebysig static + void UseMThroughIDerived<(Program/IBaseUsed) T> () cil managed + { + .param constraint T, Program/IBaseUnused + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + // Method begins at RVA 0x206e + // Code size 12 (0xc) + .maxstack 8 + + IL_0000: constrained. !!T + IL_0006: call void Program/IBaseUsed::M() + IL_000b: ret + } // end of method Program::UseMThroughIDerived + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x207b + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method Program::.ctor + +} // end of class Program diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/GenericInterfaceImplementedRecursively.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/GenericInterfaceImplementedRecursively.cs index bb0d318cac1a1..6e703f79d4650 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/GenericInterfaceImplementedRecursively.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/GenericInterfaceImplementedRecursively.cs @@ -19,18 +19,18 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces #if IL_ASSEMBLY_AVAILABLE [KeptTypeInAssembly ("library.dll", typeof(Program.IBase<>))] [KeptTypeInAssembly ("library.dll", typeof(Program.IMiddle<>))] - [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (Program.IMiddle<>), "library.dll", typeof (Program.IBase<>))] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IMiddle`1", "library.dll", "Program/IBase`1")] [KeptTypeInAssembly ("library.dll", typeof(Program.IDerived<>))] - [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (Program.IDerived<>), "library.dll", typeof (Program.IMiddle<>))] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IDerived`1", "library.dll", "Program/IMiddle`1")] [KeptTypeInAssembly ("library.dll", typeof(Program.C))] - [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (Program.C), "library.dll", typeof (Program.IDerived))] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/C", "library.dll", "Program/IDerived`1")] #endif /// /// This test case is to verify that the linker will keep all the metadata necessary for C to implement IBase when an interfaceImpl isn't directly on C. /// class GenericInterfaceImplementedRecursively { - public static void Main() + public static void Main () { #if IL_ASSEMBLY_AVAILABLE diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/InterfaceImplementedRecursively.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/InterfaceImplementedRecursively.cs index 89f59777c5fcd..ce0995821a254 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/InterfaceImplementedRecursively.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/InterfaceImplementedRecursively.cs @@ -30,13 +30,12 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces /// class InterfaceImplementedRecursively { - public static void Main() + public static void Main () { #if IL_ASSEMBLY_AVAILABLE Program.IBase b = null; object c = new Program.C(); - #endif } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/OverrideOfRecursiveInterfaceIsRemoved.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/OverrideOfRecursiveInterfaceIsRemoved.cs new file mode 100644 index 0000000000000..4beafaa1d9d96 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/OverrideOfRecursiveInterfaceIsRemoved.cs @@ -0,0 +1,87 @@ +// 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.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.Inheritance.Interfaces.RecursiveInterfaces +{ + [SetupLinkerArgument ("--skip-unresolved", "true")] + [Define ("IL_ASSEMBLY_AVAILABLE")] + [SetupCompileBefore ("library.dll", new string[] { "Dependencies/OverrideOfRecursiveInterfaceIsRemoved.il" })] + [Kept] + [KeptTypeInAssembly ("library.dll", "Program/A")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/A", "library.dll", "Program/IDerived")] + [KeptTypeInAssembly ("library.dll", "Program/IDerived")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IDerived", "library.dll", "Program/IMiddleUnused")] + [KeptTypeInAssembly ("library.dll", "Program/IMiddleUnused")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IMiddleUnused", "library.dll", "Program/IBaseUsed")] + [KeptTypeInAssembly ("library.dll", "Program/IBaseUsed")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IMiddleUnused", "library.dll", "Program/IBaseUnused")] + [KeptTypeInAssembly ("library.dll", "Program/IBaseUnused")] + [KeptOverrideOnMethodInAssembly ("library.dll", "Program/A", "N", "System.Void Program/IDerived::N()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "Program/A", "M", "System.Void Program/IBaseUsed::M()")] + [RemovedOverrideOnMethodInAssembly ("library.dll", "Program/A", "M", "System.Void Program/IBaseUnused::M()")] + [RemovedOverrideOnMethodInAssembly ("library.dll", "Program/A", "O", "System.Void Program/IMiddleUnused::O()")] + [RemovedMemberInAssembly ("library.dll", "Program/IBaseUnused", "M()")] + [RemovedMemberInAssembly ("library.dll", "Program/IMiddleUnused", "O()")] + public class OverrideOfRecursiveInterfaceIsRemoved + { + [Kept] + public static void Main () + { +#if IL_ASSEMBLY_AVAILABLE + Program.MyTest(); + _ = typeof(Program.IBaseUnused); +#endif + } + } + //public class Program + //{ + // public static void MyTest() + // { + // UseNThroughIDerived(); + // A.M(); + // A.O(); + // } + + // static void UseNThroughIDerived() where T : IDerived { + // T.N(); + // } + + // static void UseMThroughIDerived() where T : IBaseUsed { + // T.M(); + // } + + // interface IBaseUnused + // { + // static abstract void M(); + // } + + // interface IBaseUsed + // { + // static abstract void M(); + // } + + // interface IMiddleUnused : IBaseUnused, IBaseUsed + // { + // static abstract void O(); + // } + + // interface IDerived : IMiddleUnused + // { + // static abstract void N(); + // } + + // class A : IDerived { + // public static void M() {} + // public static void N() {} + // public static void O() {} + // } + //} +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/Dependencies/InstanceMethods.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/Dependencies/InstanceMethods.il new file mode 100644 index 0000000000000..3679addefcbbd --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/Dependencies/InstanceMethods.il @@ -0,0 +1,303 @@ +// 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. + +.assembly extern mscorlib { } + +.assembly 'library' { } + +.class public auto ansi abstract sealed beforefieldinit InstanceMethods + extends [mscorlib]System.Object +{ + // Nested Types + .class nested public auto ansi beforefieldinit TypeWithMethodAccessedViaInterface + extends [mscorlib]System.Object + implements InstanceMethods/IInt + { + // Methods + .method private final hidebysig newslot virtual + instance int32 GetInt () cil managed + { + .override method instance int32 InstanceMethods/IInt::GetInt() + // Method begins at RVA 0x2070 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldc.i4.0 + IL_0001: ret + } // end of method TypeWithMethodAccessedViaInterface::GetInt + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2068 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method TypeWithMethodAccessedViaInterface::.ctor + + } // end of class TypeWithMethodAccessedViaInterface + + .class nested public auto ansi beforefieldinit TypeWithMethodAccessedDirectly + extends [mscorlib]System.Object + implements InstanceMethods/IInt + { + // Methods + .method public final hidebysig newslot virtual + instance int32 GetInt () cil managed + { + .override method instance int32 InstanceMethods/IInt::GetInt() + // Method begins at RVA 0x2070 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldc.i4.0 + IL_0001: ret + } // end of method TypeWithMethodAccessedDirectly::GetInt + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2068 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method TypeWithMethodAccessedDirectly::.ctor + + } // end of class TypeWithMethodAccessedDirectly + + .class nested public auto ansi beforefieldinit TypeWithMethodAccessedViaReflection + extends [mscorlib]System.Object + implements InstanceMethods/IInt + { + // Methods + .method public final hidebysig newslot virtual + instance int32 GetInt () cil managed + { + .override method instance int32 InstanceMethods/IInt::GetInt() + // Method begins at RVA 0x2070 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldc.i4.0 + IL_0001: ret + } // end of method TypeWithMethodAccessedViaReflection::GetInt + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2068 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method TypeWithMethodAccessedViaReflection::.ctor + + } // end of class TypeWithMethodAccessedViaReflection + + .class nested public auto ansi beforefieldinit TypeWithMethodKeptByDynamicDependency + extends [mscorlib]System.Object + implements InstanceMethods/IInt + { + // Methods + .method public final hidebysig newslot virtual + instance int32 GetInt () cil managed + { + .override method instance int32 InstanceMethods/IInt::GetInt() + // Method begins at RVA 0x2070 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldc.i4.0 + IL_0001: ret + } // end of method TypeWithMethodKeptByDynamicDependency::GetInt + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2068 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method TypeWithMethodKeptByDynamicDependency::.ctor + + } // end of class TypeWithMethodKeptByDynamicDependency + + .class nested public auto ansi beforefieldinit TypeWithMethodCalledDirectlyAndInterfaceUnreferenced + extends [mscorlib]System.Object + implements InstanceMethods/IIntUnreferenced + { + // Methods + .method public final hidebysig newslot virtual + instance int32 GetInt () cil managed + { + .override method instance int32 InstanceMethods/IIntUnreferenced::GetInt() + // Method begins at RVA 0x2070 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldc.i4.0 + IL_0001: ret + } // end of method TypeWithMethodCalledDirectlyAndInterfaceUnreferenced::GetInt + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2068 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method TypeWithMethodCalledDirectlyAndInterfaceUnreferenced::.ctor + + } // end of class TypeWithMethodCalledDirectlyAndInterfaceUnreferenced + + .class nested public auto ansi beforefieldinit TypeWithMethodCalledDirectlyAndRecursiveInterfaceUnreferenced + extends [mscorlib]System.Object + implements InstanceMethods/IIntDerived + { + // Methods + .method public final hidebysig newslot virtual + instance int32 GetInt () cil managed + { + .override method instance int32 InstanceMethods/IIntBase::GetInt() + // Method begins at RVA 0x2070 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldc.i4.0 + IL_0001: ret + } // end of method TypeWithMethodCalledDirectlyAndRecursiveInterfaceUnreferenced::GetInt + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2068 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method TypeWithMethodCalledDirectlyAndRecursiveInterfaceUnreferenced::.ctor + + } // end of class TypeWithMethodCalledDirectlyAndRecursiveInterfaceUnreferenced + + .class nested public auto ansi beforefieldinit TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced + extends [mscorlib]System.Object + implements class InstanceMethods/IGeneric`1, + class InstanceMethods/IGeneric`1 + { + // Methods + .method public final hidebysig newslot virtual + instance int32 GetIntInt () cil managed + { + .override method instance int32 class InstanceMethods/IGeneric`1::GetInt() + // Method begins at RVA 0x2070 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldc.i4.0 + IL_0001: ret + } // end of method TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced::GetIntInt + + .method public final hidebysig newslot virtual + instance int32 GetIntFloat () cil managed + { + .override method instance int32 class InstanceMethods/IGeneric`1::GetInt() + // Method begins at RVA 0x2070 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldc.i4.0 + IL_0001: ret + } // end of method TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced::GetIntFloat + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2068 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced::.ctor + + } // end of class TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced + + .class interface nested public auto ansi abstract beforefieldinit IInt + { + // Methods + .method public hidebysig newslot abstract virtual + instance int32 GetInt () cil managed + { + } // end of method IInt::GetInt + + } // end of class IInt + + .class interface nested public auto ansi abstract beforefieldinit IIntUnreferenced + { + // Methods + .method public hidebysig newslot abstract virtual + instance int32 GetInt () cil managed + { + } // end of method IIntUnreferenced::GetInt + + } // end of class IIntUnreferenced + + .class interface nested public auto ansi abstract beforefieldinit IIntBase + { + // Methods + .method public hidebysig newslot abstract virtual + instance int32 GetInt () cil managed + { + } // end of method IIntBase::GetInt + + } // end of class IIntBase + + .class interface nested public auto ansi abstract beforefieldinit IIntDerived + implements InstanceMethods/IIntBase + { + } // end of class IIntDerived + + .class interface nested public auto ansi abstract beforefieldinit IGeneric`1 + { + // Methods + .method public hidebysig newslot abstract virtual + instance int32 GetInt () cil managed + { + } // end of method IGeneric`1::GetInt + } // end of class IGeneric`1 + + // Methods + .method public hidebysig static + void Test () cil managed + { + // Method begins at RVA 0x2050 + // Code size 23 (0x17) + .maxstack 8 + + IL_0000: newobj instance void InstanceMethods/TypeWithMethodAccessedViaInterface::.ctor() + IL_0005: callvirt instance int32 InstanceMethods/IInt::GetInt() + IL_000a: pop + IL_000b: newobj instance void InstanceMethods/TypeWithMethodAccessedDirectly::.ctor() + IL_0010: call instance int32 InstanceMethods/TypeWithMethodAccessedDirectly::GetInt() + IL_0015: pop + IL_0016: ret + } // end of method InstanceMethods::Test +} // end of class InstanceMethods + diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/InstanceMethodsWithOverridesSwept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/InstanceMethodsWithOverridesSwept.cs new file mode 100644 index 0000000000000..40033f6c7e33c --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/InstanceMethodsWithOverridesSwept.cs @@ -0,0 +1,80 @@ +// 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.Inheritance.Interfaces.StaticInterfaceMethods +{ + /// + /// This test exercises the case where a public instance method has a .override directive pointing to an interface method when the interface method is not directly used for the type + /// Currently, the linker will always mark the .override method for instance methods, so there is not much testing required here. + /// However, if that were to change, this test should be updated to verify that the .override is removed if the .interfaceImpl is kept. + /// + [SetupLinkerArgument ("--skip-unresolved", "true")] + [Define ("IL_ASSEMBLY_AVAILABLE")] + [SetupCompileBefore ("library.dll", new string[] { "Dependencies/InstanceMethods.il" })] + [Kept] +#if IL_ASSEMBLY_AVAILABLE + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodAccessedViaInterface))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodAccessedDirectly))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodAccessedViaReflection))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodKeptByDynamicDependency))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodCalledDirectlyAndInterfaceUnreferenced))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodCalledDirectlyAndRecursiveInterfaceUnreferenced))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.IInt))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.IIntUnreferenced))] // Kept only because of the .override on the public implementation method + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.IIntBase))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.IIntDerived))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.IGeneric<>))] + [KeptTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced))] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodAccessedViaInterface), ["GetInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodAccessedDirectly), ["GetInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodAccessedViaReflection), ["GetInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodKeptByDynamicDependency), ["GetInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodCalledDirectlyAndInterfaceUnreferenced), ["GetInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodCalledDirectlyAndRecursiveInterfaceUnreferenced), ["GetInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.IInt), ["GetInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.IIntUnreferenced), ["GetInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.IIntBase), ["GetInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced), ["GetIntInt()"])] + [KeptMemberInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced), ["GetIntFloat()"])] // Could be removed + [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodAccessedViaInterface), "library.dll", typeof (InstanceMethods.IInt))] + [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodAccessedDirectly), "library.dll", typeof (InstanceMethods.IInt))] + [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodAccessedViaReflection), "library.dll", typeof (InstanceMethods.IInt))] + [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodKeptByDynamicDependency), "library.dll", typeof (InstanceMethods.IInt))] + [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodCalledDirectlyAndInterfaceUnreferenced), "library.dll", typeof (InstanceMethods.IIntUnreferenced))] + [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (InstanceMethods.TypeWithMethodCalledDirectlyAndRecursiveInterfaceUnreferenced), "library.dll", typeof (InstanceMethods.IIntDerived))] + [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (InstanceMethods.IIntDerived), "library.dll", typeof (InstanceMethods.IIntBase))] + [KeptInterfaceOnTypeInAssembly ("library.dll", "InstanceMethods/TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced", "library.dll", "InstanceMethods/IGeneric`1")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "InstanceMethods/TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced", "library.dll", "InstanceMethods/IGeneric`1")] +#endif + public class InstanceMethodsWithOverridesSwept + { + [Kept] + public static void Main () + { +#if IL_ASSEMBLY_AVAILABLE + InstanceMethods.Test (); + typeof (InstanceMethods.TypeWithMethodAccessedViaReflection).GetMethod ("GetInt").Invoke (null, null); + new InstanceMethods.TypeWithMethodCalledDirectlyAndInterfaceUnreferenced ().GetInt (); + new InstanceMethods.TypeWithMethodCalledDirectlyAndRecursiveInterfaceUnreferenced ().GetInt (); + new InstanceMethods.TypeWithMethodCalledDirectlyAndTwoGenericInterfacesUnreferenced ().GetIntInt (); +#endif + KeepTypeThroughDynamicDependency (); + } + +#if IL_ASSEMBLY_AVAILABLE + [DynamicDependency ("GetInt()", typeof (InstanceMethods.TypeWithMethodKeptByDynamicDependency))] +#endif + [Kept] + public static void KeepTypeThroughDynamicDependency () + { } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/RemovedInterfaceImplementationRemovedOverride.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/RemovedInterfaceImplementationRemovedOverride.cs new file mode 100644 index 0000000000000..01541dd73a17b --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/RemovedInterfaceImplementationRemovedOverride.cs @@ -0,0 +1,278 @@ +// 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.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.Inheritance.Interfaces.StaticInterfaceMethods +{ + [ExpectedNoWarnings] + public class RemovedInterfaceImplementationRemovedOverride + { + [Kept] + public static void Main () + { + Basic.Test (); + GenericInterface.Test (); + GenericInterfaceGenericType.Test (); + PrivateExplicitImplementationReflectedOver.Test (); + InheritedInterfaces.Test (); + } + + [Kept] + public class Basic + { + [Kept] + public static void Test () + { + // Get message via generic method + var message = MessageFactory.GetMessage (); + Console.WriteLine (message); + + // Get message directly from type + var message2 = TypeWithMethodAccessedDirectly.GetMessage (); + Console.WriteLine (message2); + } + + [Kept] + public static class MessageFactory + { + [Kept] + public static string GetMessage () where T : IStaticMethod + { + return T.GetMessage (); + } + } + + [Kept] + [KeptInterface (typeof (IStaticMethod))] + public class TypeWithMethodAccessedViaInterface : IStaticMethod + { + // Force the string to have dynamic element so that it doesn't get compiled in as a literal + [Kept] + [KeptOverride (typeof (IStaticMethod))] + public static string GetMessage () => $"Hello from {nameof (TypeWithMethodAccessedViaInterface)}, the time is {TimeOnly.FromDateTime (DateTime.Now)}"; + } + + [Kept] + public class TypeWithMethodAccessedDirectly : IStaticMethod + { + // Force the string to have dynamic element so that it doesn't get compiled in as a literal + [Kept] + [RemovedOverride (typeof (IStaticMethod))] + public static string GetMessage () => $"Hello from {nameof (TypeWithMethodAccessedDirectly)}, the time is {TimeOnly.FromDateTime (DateTime.Now)}"; + } + + [Kept] + public interface IStaticMethod + { + [Kept] + abstract static string GetMessage (); + } + } + + [Kept] + public class GenericInterface + { + [Kept] + public static void Test () + { + // Get message via generic method + var message = MessageFactory.GetMessage (); + Console.WriteLine (message); + + // Get message directly from type + var message2 = TypeWithMethodAccessedDirectly.GetMessage (); + Console.WriteLine (message2); + } + + [Kept] + public static class MessageFactory + { + [Kept] + public static U GetMessage () where T : IStaticAbstractMethodGeneric + { + return T.GetMessage (); + } + } + + [Kept] + [KeptInterface (typeof (IStaticAbstractMethodGeneric))] + public class TypeWithMethodAccessedViaInterface : IStaticAbstractMethodGeneric + { + // Force the string to have dynamic element so that it doesn't get compiled in as a literal + [Kept] + [KeptOverride (typeof (IStaticAbstractMethodGeneric))] + public static int GetMessage () => 0; + } + + [Kept] + public class TypeWithMethodAccessedDirectly : IStaticAbstractMethodGeneric + { + // Force the string to have dynamic element so that it doesn't get compiled in as a literal + [Kept] + [RemovedOverride (typeof (IStaticAbstractMethodGeneric))] + public static int GetMessage () => 0; + } + + [Kept] + public interface IStaticAbstractMethodGeneric + { + [Kept] + abstract static T GetMessage (); + } + } + + [Kept] + public class GenericInterfaceGenericType + { + [Kept] + public static void Test () + { + // Get message via generic method + var message = MessageFactory.GetMessage, int> (); + Console.WriteLine (message); + + // Get message directly from type + var message2 = TypeWithMethodAccessedDirectly.GetMessage (); + Console.WriteLine (message2); + } + + [Kept] + public static class MessageFactory + { + [Kept] + public static U GetMessage () where T : IStaticAbstractMethodGeneric + { + return T.GetMessage (); + } + } + + [Kept] + [KeptInterface (typeof (IStaticAbstractMethodGeneric<>), "T")] + public class TypeWithMethodAccessedViaInterface : IStaticAbstractMethodGeneric + { + // Force the string to have dynamic element so that it doesn't get compiled in as a literal + [Kept] + [KeptOverride ("Mono.Linker.Tests.Cases.Inheritance.Interfaces.StaticInterfaceMethods.RemovedInterfaceImplementationRemovedOverride/" + + "GenericInterfaceGenericType/IStaticAbstractMethodGeneric`1")] + public static T GetMessage () => default; + } + + [Kept] + public class TypeWithMethodAccessedDirectly : IStaticAbstractMethodGeneric + { + // Force the string to have dynamic element so that it doesn't get compiled in as a literal + [Kept] + [RemovedOverride ("Mono.Linker.Tests.Cases.Inheritance.Interfaces.StaticInterfaceMethods.RemovedInterfaceImplementationRemovedOverride/" + + "GenericInterfaceGenericType/IStaticAbstractMethodGeneric`1")] + public static T GetMessage () => default; + } + + [Kept] + public interface IStaticAbstractMethodGeneric + { + [Kept] + abstract static T GetMessage (); + } + } + + [Kept] + public class PrivateExplicitImplementationReflectedOver + { + [Kept] + public static void Test () + { + IInstanceMethod i = new MyType (); + Console.WriteLine (i.GetMessage ()); + var type = typeof (MyTypeReflected); + var method = type.GetMethod ("GetMessage"); + method.Invoke (null, null); + } + + [Kept] + public interface IInstanceMethod + { + [Kept] + public string GetMessage (); + } + + [Kept] + [KeptInterface (typeof (IInstanceMethod))] + public class MyType : IInstanceMethod + { + [Kept] + public MyType () { } + + [Kept] + [KeptOverride (typeof (IInstanceMethod))] + string IInstanceMethod.GetMessage () => "hello"; + } + + [Kept] + // Reflecting over MyTypeReflected to get the GetMessage method makes MyTypeReflected 'RelevantToVariantCasting' which marks the interface + // It's hard to think of a scenario where the method would be kept but not the interface implementation + [KeptInterface (typeof (IInstanceMethod))] + public class MyTypeReflected : IInstanceMethod + { + public MyTypeReflected () { } + + [Kept] + [KeptOverride (typeof (IInstanceMethod))] + [ExpectBodyModified] + string IInstanceMethod.GetMessage () => "hello"; + } + } + + [Kept] + public static class InheritedInterfaces + { + [Kept] + public static void Test () + { + UseInterface (); + UsedDirectly.M (); + } + + [Kept] + static void UseInterface () where T : IDerived + { + T.M (); + } + + [Kept] + interface IBase + { + [Kept] + static abstract void M (); + } + + [Kept] + [KeptInterface (typeof (IBase))] + interface IDerived : IBase { } + + [Kept] + [KeptInterface (typeof (IDerived))] + [KeptInterface (typeof (IBase))] + class UsedThroughInterface : IDerived + { + [Kept] + [KeptOverride (typeof (IBase))] + public static void M () { } + } + + [Kept] + class UsedDirectly : IDerived + { + [Kept] + [RemovedOverride (typeof (IBase))] + public static void M () { } + } + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs index 467c2272c1bed..6e45fbadcdb52 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -5,12 +5,9 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Linq.Expressions; using System.Text; using Mono.Cecil; using Mono.Cecil.Cil; -using Mono.Linker.Dataflow; -using Mono.Linker.Tests.Cases.CppCLI; using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Extensions; using NUnit.Framework; @@ -345,54 +342,38 @@ IEnumerable VerifyInterfaces (TypeDefinition src, TypeDefinition linked) } if (expectedInterfaces.Any ()) { - yield return $"Expected interfaces were not found on {src}. Expected to find: \n{string.Join(Environment.NewLine, expectedInterfaces)}\n"; + yield return $"Expected interfaces were not found on {src}. Expected to find: \n{string.Join (Environment.NewLine, expectedInterfaces)}\n"; } } } - void VerifyOverrides (MethodDefinition original, MethodDefinition linked) + IEnumerable VerifyOverrides (MethodDefinition original, MethodDefinition linked) { if (linked is null) - return; + yield break; var expectedBaseTypesOverridden = new HashSet (original.CustomAttributes .Where (ca => ca.AttributeType.Name == nameof (KeptOverrideAttribute)) - .Select (ca => (ca.ConstructorArguments[0].Value as TypeReference).FullName)); + .Select (ca => (ca.ConstructorArguments[0].Value as TypeReference)?.FullName ?? (string) ca.ConstructorArguments[0].Value)); var originalBaseTypesOverridden = new HashSet (original.Overrides.Select (ov => ov.DeclaringType.FullName)); var linkedBaseTypesOverridden = new HashSet (linked.Overrides.Select (ov => ov.DeclaringType.FullName)); foreach (var expectedBaseType in expectedBaseTypesOverridden) { - Assert.IsTrue (originalBaseTypesOverridden.Contains (expectedBaseType), - $"Method {linked.FullName} was expected to keep override {expectedBaseType}::{linked.Name}, " + - "but it wasn't in the unlinked assembly"); - Assert.IsTrue (linkedBaseTypesOverridden.Contains (expectedBaseType), - $"Method {linked.FullName} was expected to override {expectedBaseType}::{linked.Name}"); + if (!originalBaseTypesOverridden.Contains (expectedBaseType)) { + yield return $"Method {linked.FullName} was expected to keep override {expectedBaseType}::{linked.Name}, " + + "but it wasn't in the unlinked assembly" + string.Join (Environment.NewLine, originalBaseTypesOverridden); + } else if (!linkedBaseTypesOverridden.Contains (expectedBaseType)) { + yield return $"Method {linked.FullName} was expected to override {expectedBaseType}::{linked.Name}"; + } } var expectedBaseTypesNotOverridden = new HashSet (original.CustomAttributes .Where (ca => ca.AttributeType.Name == nameof (RemovedOverrideAttribute)) - .Select (ca => (ca.ConstructorArguments[0].Value as TypeReference).FullName)); + .Select (ca => (ca.ConstructorArguments[0].Value as TypeReference)?.FullName ?? (string) ca.ConstructorArguments[0].Value)); foreach (var expectedRemovedBaseType in expectedBaseTypesNotOverridden) { - Assert.IsTrue (originalBaseTypesOverridden.Contains (expectedRemovedBaseType), - $"Method {linked.FullName} was expected to remove override {expectedRemovedBaseType}::{linked.Name}, " + - $"but it wasn't in the unlinked assembly"); - Assert.IsFalse (linkedBaseTypesOverridden.Contains (expectedRemovedBaseType), - $"Method {linked.FullName} was expected to not override {expectedRemovedBaseType}::{linked.Name}"); - } - - foreach (var overriddenMethod in linked.Overrides) { - if (overriddenMethod.Resolve () is not MethodDefinition overriddenDefinition) { - Assert.Fail ($"Method {linked.GetDisplayName ()} overrides method {overriddenMethod} which does not exist"); - } else if (overriddenDefinition.DeclaringType.IsInterface) { - Assert.True (linked.DeclaringType.Interfaces.Select (i => i.InterfaceType).Contains (overriddenMethod.DeclaringType), - $"Method {linked} overrides method {overriddenMethod}, but {linked.DeclaringType} does not implement interface {overriddenMethod.DeclaringType}"); - } else { - TypeDefinition baseType = linked.DeclaringType; - TypeReference overriddenType = overriddenMethod.DeclaringType; - while (baseType is not null) { - if (baseType.Equals (overriddenType)) - break; - if (baseType.Resolve ()?.BaseType is null) - Assert.Fail ($"Method {linked} overrides method {overriddenMethod} from, but {linked.DeclaringType} does not inherit from type {overriddenMethod.DeclaringType}"); - } + if (!originalBaseTypesOverridden.Contains (expectedRemovedBaseType)) { + yield return $"Method {linked.FullName} was expected to remove override {expectedRemovedBaseType}::{linked.Name}, " + + $"but it wasn't in the unlinked assembly"; + } else if (linkedBaseTypesOverridden.Contains (expectedRemovedBaseType)) { + yield return $"Method {linked.FullName} was expected to not override {expectedRemovedBaseType}::{linked.Name}"; } } } @@ -551,6 +532,9 @@ IEnumerable VerifyMethodInternal (MethodDefinition src, MethodDefinition yield return $"Method `{src.FullName}' should have been removed"; } + foreach (var err in VerifyOverrides (src, linked)) + yield return err; + foreach (var err in VerifyMethodKept (src, linked, compilerGenerated)) yield return err; } @@ -935,8 +919,7 @@ IEnumerable VerifyPrivateImplementationDetails (TypeDefinition original, TypeDefinition srcImplementationDetails; TypeDefinition linkedImplementationDetails; - if (VerifyPrivateImplementationDetailsType (original.Module, linked.Module, out srcImplementationDetails, out linkedImplementationDetails, out var errors)) - { + if (VerifyPrivateImplementationDetailsType (original.Module, linked.Module, out srcImplementationDetails, out linkedImplementationDetails, out var errors)) { foreach (var err in errors) yield return err; } @@ -958,13 +941,13 @@ static bool VerifyPrivateImplementationDetailsType (ModuleDefinition src, Module errs = []; srcImplementationDetails = src.Types.FirstOrDefault (t => IsPrivateImplementationDetailsType (t)); if (srcImplementationDetails == null) - errs.Append("Could not locate in the original assembly. Does your test use initializers?"); + errs.Append ("Could not locate in the original assembly. Does your test use initializers?"); linkedImplementationDetails = linked.Types.FirstOrDefault (t => IsPrivateImplementationDetailsType (t)); if (linkedImplementationDetails == null) - errs.Append("Could not locate in the linked assembly"); + errs.Append ("Could not locate in the linked assembly"); - return !errs.Any(); + return !errs.Any (); } protected virtual IEnumerable VerifyArrayInitializers (MethodDefinition src, MethodDefinition linked) @@ -982,9 +965,8 @@ protected virtual IEnumerable VerifyArrayInitializers (MethodDefinition yield return $"`{nameof (KeptInitializerData)}` cannot be used on methods that don't have bodies"; TypeDefinition srcImplementationDetails; TypeDefinition linkedImplementationDetails; - if (VerifyPrivateImplementationDetailsType (src.Module, linked.Module, out srcImplementationDetails, out linkedImplementationDetails, out var errs)) - { - foreach(var err in errs) + if (VerifyPrivateImplementationDetailsType (src.Module, linked.Module, out srcImplementationDetails, out linkedImplementationDetails, out var errs)) { + foreach (var err in errs) yield return err; } diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs index aff0d6ba1e6b6..e9e9b94de191e 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -10,16 +10,13 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; 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 Mono.Linker.Tests.TestCases; using Mono.Linker.Tests.TestCasesRunner.ILVerification; using NUnit.Framework; -using WellKnownType = ILLink.Shared.TypeSystemProxy.WellKnownType; namespace Mono.Linker.Tests.TestCasesRunner { @@ -69,13 +66,12 @@ protected static void ValidateTypeRefsHaveValidAssemblyRefs (AssemblyDefinition throw new NotImplementedException ($"Unexpected scope type '{exportedType.Scope.GetType ()}' for exported type '{exportedType.FullName}'"); } continue; - case AssemblyNameReference: - { - // There should be an AssemblyRef row for this assembly - var assemblyRef = linked.MainModule.AssemblyReferences.Single (ar => ar.Name == typeRef.Scope.Name); - Assert.IsNotNull (assemblyRef, $"Type reference '{typeRef.FullName}' has a reference to assembly '{typeRef.Scope.Name}' which is not a reference of '{linked.FullName}'"); - continue; - } + case AssemblyNameReference: { + // There should be an AssemblyRef row for this assembly + var assemblyRef = linked.MainModule.AssemblyReferences.Single (ar => ar.Name == typeRef.Scope.Name); + Assert.IsNotNull (assemblyRef, $"Type reference '{typeRef.FullName}' has a reference to assembly '{typeRef.Scope.Name}' which is not a reference of '{linked.FullName}'"); + continue; + } default: throw new NotImplementedException ($"Unexpected scope type '{typeRef.Scope.GetType ()}' for type reference '{typeRef.FullName}'"); } @@ -101,10 +97,10 @@ public virtual void Check (TrimmedTestCaseResult linkResult) PerformOutputAssemblyChecks (original, linkResult.OutputAssemblyPath.Parent); PerformOutputSymbolChecks (original, linkResult.OutputAssemblyPath.Parent); - if (!HasActiveSkipKeptItemsValidationAttribute(linkResult.TestCase.FindTypeDefinition (original))) { + if (!HasActiveSkipKeptItemsValidationAttribute (linkResult.TestCase.FindTypeDefinition (original))) { CreateAssemblyChecker (original, linked, linkResult).Verify (); } - CreateILChecker ().Check(linkResult, original); + CreateILChecker ().Check (linkResult, original); VerifyExpectedDependencyTrace (linkResult.MetadataProvider, linkResult.OutputAssemblyPath); } @@ -223,12 +219,12 @@ void PerformOutputSymbolChecks (AssemblyDefinition original, NPath outputDirecto void VerifyExitCode (TrimmedTestCaseResult linkResult, AssemblyDefinition original) { - if (TryGetCustomAttribute (original, nameof(ExpectNonZeroExitCodeAttribute), out var attr)) { + if (TryGetCustomAttribute (original, nameof (ExpectNonZeroExitCodeAttribute), out var attr)) { var expectedExitCode = (int) attr.ConstructorArguments[0].Value; - Assert.AreEqual (expectedExitCode, linkResult.ExitCode, $"Expected exit code {expectedExitCode} but got {linkResult.ExitCode}. Output was:\n{FormatLinkerOutput()}"); + Assert.AreEqual (expectedExitCode, linkResult.ExitCode, $"Expected exit code {expectedExitCode} but got {linkResult.ExitCode}. Output was:\n{FormatLinkerOutput ()}"); } else { if (linkResult.ExitCode != 0) { - Assert.Fail ($"Linker exited with an unexpected non-zero exit code of {linkResult.ExitCode} and output:\n{FormatLinkerOutput()}"); + Assert.Fail ($"Linker exited with an unexpected non-zero exit code of {linkResult.ExitCode} and output:\n{FormatLinkerOutput ()}"); } } @@ -390,6 +386,12 @@ void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original) Assert.Fail ($"Type `{expectedTypeName}` should have been kept in assembly {assemblyName}"); VerifyExpectedInstructionSequenceOnMemberInAssembly (checkAttrInAssembly, linkedType); break; + case nameof (RemovedOverrideOnMethodInAssemblyAttribute): + VerifyRemovedOverrideOnMethodInAssembly (checkAttrInAssembly, linkedType); + break; + case nameof (KeptOverrideOnMethodInAssemblyAttribute): + VerifyKeptOverrideOnMethodInAssembly (checkAttrInAssembly, linkedType); + break; default: UnhandledOtherAssemblyAssertion (expectedTypeName, checkAttrInAssembly, linkedType); break; @@ -532,18 +534,18 @@ void VerifyKeptInterfaceOnTypeInAssembly (CustomAttribute inAssemblyAttribute, T var interfaceAssemblyName = inAssemblyAttribute.ConstructorArguments[2].Value.ToString (); var interfaceType = inAssemblyAttribute.ConstructorArguments[3].Value; + string originalInterfaceName = interfaceType as string ?? GetOriginalTypeFromInAssemblyAttribute (interfaceAssemblyName, interfaceType).FullName; - 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); + var originalInterfaceImpl = GetMatchingInterfaceImplementationOnType (originalType, originalInterfaceName); if (originalInterfaceImpl == null) - Assert.Fail ($"Invalid assertion. Original type never had an interface of type `{originalInterface}`"); + Assert.Fail ($"Invalid assertion. Original type never had an interface of type `{interfaceType}`"); - var linkedInterfaceImpl = GetMatchingInterfaceImplementationOnType (linkedType, originalInterface.FullName); + var linkedInterfaceImpl = GetMatchingInterfaceImplementationOnType (linkedType, originalInterfaceName); if (linkedInterfaceImpl == null) - Assert.Fail ($"Expected `{linkedType}` to have interface of type {originalInterface.FullName}"); + Assert.Fail ($"Expected `{linkedType}` to have interface of type {originalInterfaceName}"); } void VerifyKeptBaseOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) @@ -564,12 +566,7 @@ void VerifyKeptBaseOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDe protected 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; + return impl.InterfaceType.FullName == expectedInterfaceTypeName; }); } @@ -636,6 +633,26 @@ void VerifyKeptMemberInAssembly (CustomAttribute inAssemblyAttribute, TypeDefini } } + void VerifyRemovedOverrideOnMethodInAssembly (CustomAttribute attr, TypeDefinition type) + { + var methodname = (string) attr.ConstructorArguments[2].Value; + var method = type.Methods.FirstOrDefault (m => m.Name == methodname); + var overriddenMethodName = (string) attr.ConstructorArguments[3].Value; + if (method.Overrides.Any (m => m.FullName == overriddenMethodName)) { + Assert.Fail ($"Expected method {method.FullName} to not have .override for {overriddenMethodName}"); + } + } + + void VerifyKeptOverrideOnMethodInAssembly (CustomAttribute attr, TypeDefinition type) + { + var methodname = (string) attr.ConstructorArguments[2].Value; + var method = type.Methods.FirstOrDefault (m => m.Name == methodname); + var overriddenMethodName = (string) attr.ConstructorArguments[3].Value; + if (!method.Overrides.Any (m => m.FullName == overriddenMethodName)) { + Assert.Fail ($"Expected method {method.FullName} to have .override for {overriddenMethodName}"); + } + } + protected virtual bool TryVerifyKeptMemberInAssemblyAsField (string memberName, TypeDefinition originalType, TypeDefinition linkedType) { var originalFieldMember = originalType.Fields.FirstOrDefault (m => m.Name == memberName); @@ -740,7 +757,7 @@ void VerifyKeptAllTypesAndMembersInAssembly (AssemblyDefinition linked) static bool IsProducedByLinker (CustomAttribute attr) { if (attr.Constructor.Parameters.Count > 2 && attr.ConstructorArguments[^2].Type.Name == "Tool") { - return ((Tool)attr.ConstructorArguments[^2].Value).HasFlag (Tool.Trimmer) == true; + return ((Tool) attr.ConstructorArguments[^2].Value).HasFlag (Tool.Trimmer) == true; } var producedBy = attr.GetPropertyValue ("ProducedBy"); return producedBy is null ? true : ((Tool) producedBy).HasFlag (Tool.Trimmer); @@ -764,7 +781,7 @@ static IEnumerable GetAttributeProviders (AssemblyDefi void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logger, bool checkRemainingErrors) { ImmutableArray allMessages = logger.GetLoggedMessages (); - List unmatchedMessages = [..allMessages]; + List unmatchedMessages = [.. allMessages]; List<(ICustomAttributeProvider, CustomAttribute)> expectedNoWarningsAttributes = new (); List missingMessageWarnings = []; List unexpectedMessageWarnings = []; @@ -805,28 +822,27 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge } break; - case nameof (ExpectedWarningAttribute) or nameof(UnexpectedWarningAttribute): { + case nameof (ExpectedWarningAttribute) or nameof (UnexpectedWarningAttribute): { var expectedWarningCode = (string) attr.GetConstructorArgumentValue (0); if (!expectedWarningCode.StartsWith ("IL")) { Assert.Fail ($"The warning code specified in {attr.AttributeType.Name} must start with the 'IL' prefix. Specified value: '{expectedWarningCode}'."); } - IEnumerable expectedMessageContains = attr.Constructor.Parameters switch - { - // ExpectedWarningAttribute(string warningCode, params string[] expectedMessages) - // ExpectedWarningAttribute(string warningCode, string[] expectedMessages, Tool producedBy, string issueLink) - [_, { ParameterType.IsArray: true }, ..] - => ((CustomAttributeArgument[])attr.ConstructorArguments[1].Value) - .Select(caa => (string)caa.Value), - // ExpectedWarningAttribute(string warningCode, string expectedMessage1, string expectedMessage2, Tool producedBy, string issueLink) - [_, { ParameterType.Name: "String" }, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _] - => [(string)attr.GetConstructorArgumentValue(1), (string)attr.GetConstructorArgumentValue(2)], - // ExpectedWarningAttribute(string warningCode, string expectedMessage, Tool producedBy, string issueLink) - [_, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _] - => [(string)attr.GetConstructorArgumentValue(1)], - // ExpectedWarningAttribute(string warningCode, Tool producedBy, string issueLink) - [_, { ParameterType.Name: "Tool" }, _] - => [], - _ => throw new UnreachableException(), + IEnumerable expectedMessageContains = attr.Constructor.Parameters switch { + // ExpectedWarningAttribute(string warningCode, params string[] expectedMessages) + // ExpectedWarningAttribute(string warningCode, string[] expectedMessages, Tool producedBy, string issueLink) + [_, { ParameterType.IsArray: true }, ..] + => ((CustomAttributeArgument[]) attr.ConstructorArguments[1].Value) + .Select (caa => (string) caa.Value), + // ExpectedWarningAttribute(string warningCode, string expectedMessage1, string expectedMessage2, Tool producedBy, string issueLink) + [_, { ParameterType.Name: "String" }, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _] + => [(string) attr.GetConstructorArgumentValue (1), (string) attr.GetConstructorArgumentValue (2)], + // ExpectedWarningAttribute(string warningCode, string expectedMessage, Tool producedBy, string issueLink) + [_, { ParameterType.Name: "String" }, { ParameterType.Name: "Tool" }, _] + => [(string) attr.GetConstructorArgumentValue (1)], + // ExpectedWarningAttribute(string warningCode, Tool producedBy, string issueLink) + [_, { ParameterType.Name: "Tool" }, _] + => [], + _ => throw new UnreachableException (), }; string fileName = (string) attr.GetPropertyValue ("FileName"); int? sourceLine = (int?) attr.GetPropertyValue ("SourceLine"); @@ -991,14 +1007,12 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge break; } - if (unexpectedWarningMessage is not null) - { - unexpectedMessageWarnings.Add($"Unexpected warning found: {unexpectedWarningMessage}"); + if (unexpectedWarningMessage is not null) { + unexpectedMessageWarnings.Add ($"Unexpected warning found: {unexpectedWarningMessage}"); } } - if (missingMessageWarnings.Any ()) - { + if (missingMessageWarnings.Any ()) { missingMessageWarnings.Add ("Unmatched Messages:" + Environment.NewLine); missingMessageWarnings.AddRange (unmatchedMessages.Select (m => m.ToString ())); missingMessageWarnings.Add (Environment.NewLine + "All Messages:" + Environment.NewLine); @@ -1006,8 +1020,7 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge Assert.Fail (string.Join (Environment.NewLine, missingMessageWarnings)); } - if (unexpectedMessageWarnings.Any()) - { + if (unexpectedMessageWarnings.Any ()) { Assert.Fail (string.Join (Environment.NewLine, unexpectedMessageWarnings)); }