From 7cd8459e7bd3883f0aa86961c518948630dc2e49 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:55:54 -0700 Subject: [PATCH] ILLink: Sweep .override for interface method if the .interfaceImpl is removed (#102857) When static interface methods are kept but an implementation of the method is only accessed via a direct call on the implementing type, the implementation method and the .override pointing to the interface method are both kept, but not the .interfaceImpl. This causes a TypeLoadException when the .override points to a method on an interface that the type doesn't implement. To fix this, we needed to update the condition for sweeping overrides to include the case where both the interface and the interface method are kept, but the .interfaceImpl is not kept. Tests are for static interface methods, but concrete and generic to test type resolution in SweepStep, which can be finicky. Instance methods shouldn't hit this issue since public interface methods don't have a .override and private methods can't be marked without instantiating the type or reflecting over the type, both of which mark the type's .interfaceImpls, and making the .override valid. For instance methods, we will always mark any methods referenced in a .override as well as their corresponding interface implementation, so we won't end up in a scenario where an instance method would have dangling references in a .override. I did fix up the interface implementation marking (MarkRuntimeInterfaceImplementation) to make sure it can find a recursive interface, and marks the .interfaceImpl with the exact same TypeReference rather than just the same TypeDefinition (for example, IGeneric should be marked, not just the first IGeneric<> interfaceImpl). This was one of the examples where the trimmer would end up doing the right thing (all IGeneric<> implementations would end up marked anyway), but it would do it for a different reason than we expect. --- .../src/linker/Linker.Steps/MarkStep.cs | 63 ++-- .../src/linker/Linker.Steps/SweepStep.cs | 11 +- .../StaticInterfaceMethods.cs | 6 + .../Assertions/KeptOverrideAttribute.cs | 8 +- ...KeptOverrideOnMethodInAssemblyAttribute.cs | 15 + .../Assertions/RemovedOverrideAttribute.cs | 8 +- ...ovedOverrideOnMethodInAssemblyAttribute.cs | 15 + .../DataFlow/FeatureCheckDataFlow.cs | 86 +++-- .../OverrideOfRecursiveInterfaceIsRemoved.il | 168 ++++++++++ .../GenericInterfaceImplementedRecursively.cs | 8 +- .../InterfaceImplementedRecursively.cs | 3 +- .../OverrideOfRecursiveInterfaceIsRemoved.cs | 87 +++++ .../Dependencies/InstanceMethods.il | 303 ++++++++++++++++++ .../InstanceMethodsWithOverridesSwept.cs | 80 +++++ ...dInterfaceImplementationRemovedOverride.cs | 278 ++++++++++++++++ .../TestCasesRunner/AssemblyChecker.cs | 68 ++-- .../TestCasesRunner/ResultChecker.cs | 119 ++++--- 17 files changed, 1134 insertions(+), 192 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptOverrideOnMethodInAssemblyAttribute.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RemovedOverrideOnMethodInAssemblyAttribute.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/OverrideOfRecursiveInterfaceIsRemoved.il create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/OverrideOfRecursiveInterfaceIsRemoved.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/Dependencies/InstanceMethods.il create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/InstanceMethodsWithOverridesSwept.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/RemovedInterfaceImplementationRemovedOverride.cs 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)); }