From d6564fb504b29bab0dc2f363a9f062db284a80dc Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:39:12 -0700 Subject: [PATCH 01/27] Use recursive-interface-aware types and methods in TypeMapInfo - Rename InterfaceImplementor to RuntimeInterfaceImplementation - Use 'interface implementation chains' everywhere rather than a single interface implementation --- .../src/linker/Linker.Steps/MarkStep.cs | 50 +-- .../illink/src/linker/Linker/Annotations.cs | 4 +- .../src/linker/Linker/InterfaceImplementor.cs | 59 ---- .../src/linker/Linker/OverrideInformation.cs | 24 +- .../Linker/RuntimeInterfaceImplementation.cs | 56 +++ .../illink/src/linker/Linker/TypeMapInfo.cs | 117 +++---- .../linker/Linker/TypeReferenceExtensions.cs | 25 +- .../AttributesTests.g.cs | 6 + ...terfaces.DefaultInterfaceMethodsTests.g.cs | 6 + ...e.Interfaces.RecursiveInterfacesTests.g.cs | 12 + .../MetadataTests.g.cs | 30 ++ .../StaticDimOnDerivedInterface.cs | 57 ++++ .../RecursiveGenericInterfaces.il | 322 ++++++++++++++++++ .../RecursiveGenericInterfacesStatic.il | 322 ++++++++++++++++++ .../GenericInterfaceImplementedRecursively.cs | 6 +- .../RecursiveGenericInterfaces.cs | 94 +++++ .../RecursiveGenericInterfacesStatic.cs | 125 +++++++ 17 files changed, 1136 insertions(+), 179 deletions(-) delete mode 100644 src/tools/illink/src/linker/Linker/InterfaceImplementor.cs create mode 100644 src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/StaticDimOnDerivedInterface.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfaces.il create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfacesStatic.il create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 280f1908f79f0..b473064e59a9a 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -738,18 +738,19 @@ internal static bool IsInterfaceImplementationMarkedRecursively (TypeDefinition return true; } } - + if (type.BaseType is not null && context.Resolve (type.BaseType) is { } baseType) + return IsInterfaceImplementationMarkedRecursively (baseType, interfaceType, context); return false; } void ProcessDefaultImplementation (OverrideInformation ov, MessageOrigin origin) { Debug.Assert (ov.IsOverrideOfInterfaceMember); - if ((!ov.Override.IsStatic && !Annotations.IsInstantiated (ov.InterfaceImplementor.Implementor)) - || ov.Override.IsStatic && !Annotations.IsRelevantToVariantCasting (ov.InterfaceImplementor.Implementor)) + if ((!ov.Override.IsStatic && !Annotations.IsInstantiated (ov.RuntimeInterfaceImplementation.Implementor)) + || ov.Override.IsStatic && !Annotations.IsRelevantToVariantCasting (ov.RuntimeInterfaceImplementation.Implementor)) return; - MarkInterfaceImplementation (ov.InterfaceImplementor.InterfaceImplementation, origin); + MarkRuntimeInterfaceImplementation (ov.RuntimeInterfaceImplementation, origin); } void MarkMarshalSpec (IMarshalInfoProvider spec, in DependencyInfo reason, MessageOrigin origin) @@ -2398,35 +2399,38 @@ void MarkNamedProperty (TypeDefinition type, string property_name, in Dependency void MarkInterfaceImplementations (TypeDefinition type) { - var ifaces = Annotations.GetRecursiveInterfaces (type); + var ifaces = Annotations.GetRuntimeInterfaces (type); if (ifaces is null) return; - foreach (var (ifaceType, impls) in ifaces) { + foreach (var runtimeInterface in ifaces) { // Only mark interface implementations of interface types that have been marked. // This enables stripping of interfaces that are never used - if (ShouldMarkInterfaceImplementationList (type, impls, ifaceType)) - MarkInterfaceImplementationList (impls, new MessageOrigin (type)); + if (ShouldMarkRuntimeInterfaceImplementation (runtimeInterface)) + MarkRuntimeInterfaceImplementation (runtimeInterface, new MessageOrigin (type)); } } - protected virtual bool ShouldMarkInterfaceImplementationList (TypeDefinition type, List ifaces, TypeReference ifaceType) + protected virtual bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation) { + var type = runtimeInterfaceImplementation.Implementor; + var ifaces = runtimeInterfaceImplementation.InterfaceImplementation; + var ifaceType = runtimeInterfaceImplementation.InterfaceTypeDefinition; if (ifaces.All (Annotations.IsMarked)) return false; if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, type)) return true; - if (Context.Resolve (ifaceType) is not TypeDefinition resolvedInterfaceType) + if (ifaceType is null) return false; - if (Annotations.IsMarked (resolvedInterfaceType)) + if (Annotations.IsMarked (ifaceType)) return true; // It's hard to know if a com or windows runtime interface will be needed from managed code alone, // so as a precaution we will mark these interfaces once the type is instantiated - if (Context.KeepComInterfaces && (resolvedInterfaceType.IsImport || resolvedInterfaceType.IsWindowsRuntime)) + if (Context.KeepComInterfaces && (ifaceType.IsImport || ifaceType.IsWindowsRuntime)) return true; return IsFullyPreserved (type); @@ -2511,9 +2515,8 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat // If the interface implementation is not marked, do not mark the implementation method // A type that doesn't implement the interface isn't required to have methods that implement the interface. - InterfaceImplementation? iface = overrideInformation.InterfaceImplementor.InterfaceImplementation; - if (!((iface is not null && Annotations.IsMarked (iface)) - || IsInterfaceImplementationMarkedRecursively (method.DeclaringType, @base.DeclaringType))) + // We must check all possible ways the interface could be implemented by the type (through all recursive interface implementations, not just the primary one) + if (!(IsInterfaceImplementationMarkedRecursively (overrideInformation.RuntimeInterfaceImplementation.Implementor, @base.DeclaringType))) return false; // If the interface method is not marked and the interface doesn't come from a preserved scope, do not mark the implementation method @@ -2529,12 +2532,12 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat // If the method is static and the implementing type is relevant to variant casting, mark the implementation method. // A static method may only be called through a constrained call if the type is relevant to variant casting. if (@base.IsStatic) - return Annotations.IsRelevantToVariantCasting (overrideInformation.InterfaceImplementor.Implementor) + return Annotations.IsRelevantToVariantCasting (overrideInformation.RuntimeInterfaceImplementation.Implementor) || IgnoreScope (@base.DeclaringType.Scope); // If the implementing type is marked as instantiated, mark the implementation method. // If the type is not instantiated, do not mark the implementation method - return Annotations.IsInstantiated (overrideInformation.InterfaceImplementor.Implementor); + return Annotations.IsInstantiated (overrideInformation.RuntimeInterfaceImplementation.Implementor); } static bool IsSpecialSerializationConstructor (MethodDefinition method) @@ -3307,7 +3310,6 @@ protected virtual void MarkRequirementsForInstantiatedTypes (TypeDefinition type DoAdditionalInstantiatedTypeProcessing (type); } - void MarkRuntimeInterfaceImplementation (MethodDefinition method, MethodReference ov) { if (Context.Resolve (ov) is not MethodDefinition resolvedOverride) @@ -3315,13 +3317,13 @@ void MarkRuntimeInterfaceImplementation (MethodDefinition method, MethodReferenc if (!resolvedOverride.DeclaringType.IsInterface) return; var interfaceToBeImplemented = ov.DeclaringType; - - var ifaces = Annotations.GetRecursiveInterfaces (method.DeclaringType); + var ifaces = Annotations.GetRuntimeInterfaces (method.DeclaringType); if (ifaces is null) return; + Debug.Assert (ifaces.Value.SingleOrDefault (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceToBeImplemented, Context)) is not null); foreach (var iface in ifaces) { - if (TypeReferenceEqualityComparer.AreEqual (iface.InterfaceType, interfaceToBeImplemented, Context)) { - MarkInterfaceImplementationList (iface.ImplementationChain, new MessageOrigin (method.DeclaringType)); + if (TypeReferenceEqualityComparer.AreEqual (iface.InflatedInterfaceType, interfaceToBeImplemented, Context)) { + MarkRuntimeInterfaceImplementation (iface, new MessageOrigin (method.DeclaringType)); return; } } @@ -3753,9 +3755,9 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio } } - void MarkInterfaceImplementationList (List ifaces, MessageOrigin origin, DependencyInfo? reason = null) + void MarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterface, MessageOrigin origin, DependencyInfo? reason = null) { - foreach (var iface in ifaces) { + foreach (var iface in runtimeInterface.InterfaceImplementation) { MarkInterfaceImplementation (iface, origin, reason); } } diff --git a/src/tools/illink/src/linker/Linker/Annotations.cs b/src/tools/illink/src/linker/Linker/Annotations.cs index 68c8c32074996..cfcf350bb1804 100644 --- a/src/tools/illink/src/linker/Linker/Annotations.cs +++ b/src/tools/illink/src/linker/Linker/Annotations.cs @@ -31,10 +31,10 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection.Metadata.Ecma335; using ILLink.Shared.TrimAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; @@ -719,7 +719,7 @@ public void EnqueueVirtualMethod (MethodDefinition method) VirtualMethodsWithAnnotationsToValidate.Add (method); } - internal List<(TypeReference InterfaceType, List ImplementationChain)>? GetRecursiveInterfaces (TypeDefinition type) + internal ImmutableArray? GetRuntimeInterfaces (TypeDefinition type) { return TypeMapInfo.GetRecursiveInterfaces (type); } diff --git a/src/tools/illink/src/linker/Linker/InterfaceImplementor.cs b/src/tools/illink/src/linker/Linker/InterfaceImplementor.cs deleted file mode 100644 index e981ce872703f..0000000000000 --- a/src/tools/illink/src/linker/Linker/InterfaceImplementor.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using Mono.Cecil; - -namespace Mono.Linker -{ - public class InterfaceImplementor - { - /// - /// The type that implements . - /// - public TypeDefinition Implementor { get; } - /// - /// The .interfaceimpl on that points to - /// - public InterfaceImplementation InterfaceImplementation { get; } - /// - /// The type of the interface that is implemented by - /// - public TypeDefinition InterfaceType { get; } - - public InterfaceImplementor (TypeDefinition implementor, InterfaceImplementation interfaceImplementation, TypeDefinition interfaceType, IMetadataResolver resolver) - { - Implementor = implementor; - InterfaceImplementation = interfaceImplementation; - InterfaceType = interfaceType; - Debug.Assert(resolver.Resolve (interfaceImplementation.InterfaceType) == interfaceType); - } - - public static InterfaceImplementor Create(TypeDefinition implementor, TypeDefinition interfaceType, IMetadataResolver resolver) - { - foreach(InterfaceImplementation iface in implementor.Interfaces) { - if (resolver.Resolve(iface.InterfaceType) == interfaceType) { - return new InterfaceImplementor(implementor, iface, interfaceType, resolver); - } - } - - Queue ifacesToCheck = new (); - ifacesToCheck.Enqueue(implementor); - while (ifacesToCheck.Count > 0) { - var currentIface = ifacesToCheck.Dequeue (); - - foreach(InterfaceImplementation ifaceImpl in currentIface.Interfaces) { - var iface = resolver.Resolve (ifaceImpl.InterfaceType); - if (iface == interfaceType) { - return new InterfaceImplementor(implementor, ifaceImpl, interfaceType, resolver); - } - ifacesToCheck.Enqueue (iface); - } - } - throw new InvalidOperationException ($"Type '{implementor.FullName}' does not implement interface '{interfaceType.FullName}' directly or through any interfaces"); - } - } -} diff --git a/src/tools/illink/src/linker/Linker/OverrideInformation.cs b/src/tools/illink/src/linker/Linker/OverrideInformation.cs index 0727d5d25c19a..30075f7a697ed 100644 --- a/src/tools/illink/src/linker/Linker/OverrideInformation.cs +++ b/src/tools/illink/src/linker/Linker/OverrideInformation.cs @@ -1,9 +1,10 @@ // 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.Diagnostics; -using Mono.Cecil; using System.Diagnostics.CodeAnalysis; +using Mono.Cecil; namespace Mono.Linker { @@ -14,28 +15,29 @@ public class OverrideInformation public MethodDefinition Override { get; } - internal InterfaceImplementor? InterfaceImplementor { get; } + internal RuntimeInterfaceImplementation? RuntimeInterfaceImplementation { get; } - internal OverrideInformation (MethodDefinition @base, MethodDefinition @override, InterfaceImplementor? interfaceImplementor = null) + internal OverrideInformation (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null) { Base = @base; Override = @override; - InterfaceImplementor = interfaceImplementor; + RuntimeInterfaceImplementation = runtimeInterface; // Ensure we have an interface implementation if the base method is from an interface and the override method is on a class - Debug.Assert(@base.DeclaringType.IsInterface && interfaceImplementor != null - || !@base.DeclaringType.IsInterface && interfaceImplementor == null); + Debug.Assert (@base.DeclaringType.IsInterface && runtimeInterface != null + || !@base.DeclaringType.IsInterface && runtimeInterface == null); // Ensure the interfaceImplementor is for the interface we expect - Debug.Assert (@base.DeclaringType.IsInterface ? interfaceImplementor!.InterfaceType == @base.DeclaringType : true); + Debug.Assert (@base.DeclaringType.IsInterface ? runtimeInterface!.InterfaceTypeDefinition == @base.DeclaringType : true); } + [Obsolete ("Use RuntimeInterfaceImplementation instead to handle recursive interfaces")] public InterfaceImplementation? MatchingInterfaceImplementation - => InterfaceImplementor?.InterfaceImplementation; + => RuntimeInterfaceImplementation?.InterfaceImplementation[0]; public TypeDefinition? InterfaceType - => InterfaceImplementor?.InterfaceType; + => RuntimeInterfaceImplementation?.InterfaceTypeDefinition; - [MemberNotNullWhen (true, nameof (InterfaceImplementor), nameof (MatchingInterfaceImplementation))] + [MemberNotNullWhen (true, nameof (RuntimeInterfaceImplementation))] public bool IsOverrideOfInterfaceMember - => InterfaceImplementor != null; + => RuntimeInterfaceImplementation != null; } } diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs new file mode 100644 index 0000000000000..9328414f580ef --- /dev/null +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs @@ -0,0 +1,56 @@ +// 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.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Mono.Cecil; + +namespace Mono.Linker +{ + /// + /// Represents an implementation of an interface on a type that may be directly on the type or on a base type or implemented interface. + /// This type is considered to implement the interface at runtime, even though the interface may not be directly on the type. + /// + public class RuntimeInterfaceImplementation + { + /// + /// The type that implements . + /// + public TypeDefinition Implementor { get; } + + /// + /// The path of .interfaceimpl on or a base type that terminates with . + /// + public ImmutableArray InterfaceImplementation { get; } + + /// + /// The type that has the InterfaceImplementation - either the or a base type of it. + /// + public TypeReference TypeWithInterfaceImplementation { get; } + + /// + /// The type of the interface that is implemented by . + /// This type may be different from the corresponding InterfaceImplementation.InterfaceType if it is generic. + /// Generic parameters are replaces with generic arguments from the implementing type. + /// Because of this, do not use this for comparisons or resolving. Use or .Last().InterfaceType instead. + /// + public TypeReference InflatedInterfaceType { get; } + + /// + /// The resolved definition of the interface type implemented by . + /// + public TypeDefinition? InterfaceTypeDefinition { get; } + + public RuntimeInterfaceImplementation (TypeDefinition implementor, TypeReference typeWithFirstIfaceImpl, IEnumerable interfaceImplementation, TypeReference inflatedInterfaceType, LinkContext resolver) + { + Implementor = implementor; + TypeWithInterfaceImplementation = typeWithFirstIfaceImpl; + InterfaceImplementation = interfaceImplementation.ToImmutableArray (); + InflatedInterfaceType = inflatedInterfaceType; + InterfaceTypeDefinition = resolver.Resolve (InterfaceImplementation[InterfaceImplementation.Length - 1].InterfaceType); + } + } +} diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index 1d7b24e070d14..ec7206addba0f 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -29,8 +29,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -100,28 +100,28 @@ public void EnsureProcessed (AssemblyDefinition assembly) return ret; } - public void AddBaseMethod (MethodDefinition method, MethodDefinition @base, InterfaceImplementor? interfaceImplementor) + public void AddBaseMethod (MethodDefinition method, MethodDefinition @base, RuntimeInterfaceImplementation? runtimeInterface) { - base_methods.AddToList (method, new OverrideInformation (@base, method, interfaceImplementor)); + base_methods.AddToList (method, new OverrideInformation (@base, method, runtimeInterface)); } - public void AddOverride (MethodDefinition @base, MethodDefinition @override, InterfaceImplementor? interfaceImplementor = null) + public void AddOverride (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null) { - override_methods.AddToList (@base, new OverrideInformation (@base, @override, interfaceImplementor)); + override_methods.AddToList (@base, new OverrideInformation (@base, @override, runtimeInterface)); } - public void AddDefaultInterfaceImplementation (MethodDefinition @base, InterfaceImplementor interfaceImplementor, MethodDefinition defaultImplementationMethod) + public void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod) { - Debug.Assert(@base.DeclaringType.IsInterface); - default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, interfaceImplementor)); + Debug.Assert (@base.DeclaringType.IsInterface); + default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, runtimeInterface)); } - Dictionary)>> interfaces = new (); + Dictionary> interfaces = new (); protected virtual void MapType (TypeDefinition type) { + interfaces[type] = GetRecursiveInterfaceImplementations (type); MapVirtualMethods (type); MapInterfaceMethodsInTypeHierarchy (type); - interfaces[type] = GetRecursiveInterfaceImplementations (type); if (!type.HasNestedTypes) return; @@ -130,24 +130,24 @@ protected virtual void MapType (TypeDefinition type) MapType (nested); } - internal List<(TypeReference InterfaceType, List ImplementationChain)>? GetRecursiveInterfaces (TypeDefinition type) + internal ImmutableArray? GetRecursiveInterfaces (TypeDefinition type) { - EnsureProcessed(type.Module.Assembly); + EnsureProcessed (type.Module.Assembly); if (interfaces.TryGetValue (type, out var value)) return value; return null; } - List<(TypeReference InterfaceType, List ImplementationChain)> GetRecursiveInterfaceImplementations (TypeDefinition type) + ImmutableArray GetRecursiveInterfaceImplementations (TypeDefinition type) { - List<(TypeReference, List)> firstImplementationChain = new (); + // We'll need at least this many interfaces, so let's preallocate + var runtimeIfaces = ImmutableArray.CreateBuilder (type.Interfaces.Count); - AddRecursiveInterfaces (type, [], firstImplementationChain, context); - Debug.Assert (firstImplementationChain.All (kvp => context.Resolve (kvp.Item1) == context.Resolve (kvp.Item2.Last ().InterfaceType))); + AddRecursiveInterfaces (type, type, [], runtimeIfaces, context); - return firstImplementationChain; + return runtimeIfaces.ToImmutable (); - static void AddRecursiveInterfaces (TypeReference typeRef, IEnumerable pathToType, List<(TypeReference, List)> firstImplementationChain, LinkContext Context) + static void AddRecursiveInterfaces (TypeDefinition originalType, TypeReference typeRef, IEnumerable pathToType, ImmutableArray.Builder implementors, LinkContext Context) { var type = Context.TryResolve (typeRef); if (type is null) @@ -158,11 +158,16 @@ static void AddRecursiveInterfaces (TypeReference typeRef, IEnumerable TypeReferenceEqualityComparer.AreEqual (i.Item1, interfaceType, Context))) { - firstImplementationChain.Add ((interfaceType, pathToType.Append (iface).ToList ())); + if (!implementors.Any (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceType, Context))) { + implementors.Add (new RuntimeInterfaceImplementation (originalType, typeRef, pathToType.Append (iface), interfaceType, Context)); } } + if (type.BaseType is not null) { + var inflatedBaseType = type.BaseType.TryInflateFrom (typeRef, Context)!; + AddRecursiveInterfaces (originalType, inflatedBaseType, [], implementors, Context); + } + // Recursive interfaces after all direct interfaces to preserve Inherit/Implement tree order foreach (var iface in type.Interfaces) { // If we can't resolve the interface type we can't find recursive interfaces @@ -170,24 +175,18 @@ static void AddRecursiveInterfaces (TypeReference typeRef, IEnumerable TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, baseMethodRef.DeclaringType, context)); + AnnotateMethods (baseMethod, method, runtimeInterface); } else { AnnotateMethods (baseMethod, method); } } } - void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, InterfaceImplementor? interfaceImplementor = null) + void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null) { - AddBaseMethod (@override, @base, interfaceImplementor); - AddOverride (@base, @override, interfaceImplementor); + AddBaseMethod (@override, @base, runtimeInterface); + AddOverride (@base, @override, runtimeInterface); } MethodDefinition? GetBaseMethodInTypeHierarchy (MethodDefinition method) @@ -329,22 +333,29 @@ void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, Interf /// /// The InterfaceImplementation on that points to the DeclaringType of . /// - void FindAndAddDefaultInterfaceImplementations (TypeDefinition typeThatImplementsInterface, TypeDefinition typeThatMayHaveDIM, MethodDefinition interfaceMethodToBeImplemented, InterfaceImplementation originalInterfaceImpl) + void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference inflatedInterfaceReference, MethodDefinition interfaceMethodToBeImplemented) { + var typeThatImplementsInterface = originalInterfaceImpl.Implementor; // Go over all interfaces, trying to find a method that is an explicit MethodImpl of the // interface method in question. + var hasInterfaces = interfaces.TryGetValue (typeThatImplementsInterface, out var runtimeIfaces); + if (!hasInterfaces) + return; - foreach (var interfaceImpl in typeThatMayHaveDIM.Interfaces) { - var potentialImplInterface = context.TryResolve (interfaceImpl.InterfaceType); - if (potentialImplInterface == null) + bool findAllPossibleImplementations = inflatedInterfaceReference.DeclaringType is GenericInstanceType; + foreach (var interfaceImpl in runtimeIfaces!) { + var potentialImplInterface = interfaceImpl.InterfaceTypeDefinition; + if (potentialImplInterface is null) continue; + // We go by MethodDefinition here and may miss the correct implementation by exiting early after finding one. + // To correct for this, we'll look for all possible implementations and add them all if the inflatedIntefaceMethodReference is a genericInstance + // Otherwise, we're good to stop looking higher in the hierarchy after the first. Note we still need to look at other branches of the hierarchy to find any diamond cases. bool foundImpl = false; - foreach (var potentialImplMethod in potentialImplInterface.Methods) { if (potentialImplMethod == interfaceMethodToBeImplemented && !potentialImplMethod.IsAbstract) { - AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, new (typeThatImplementsInterface, originalInterfaceImpl, interfaceMethodToBeImplemented.DeclaringType, context), potentialImplMethod); + AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, originalInterfaceImpl, potentialImplMethod); foundImpl = true; break; } @@ -355,32 +366,22 @@ void FindAndAddDefaultInterfaceImplementations (TypeDefinition typeThatImplement // This method is an override of something. Let's see if it's the method we are looking for. foreach (var baseMethod in potentialImplMethod.Overrides) { if (context.TryResolve (baseMethod) == interfaceMethodToBeImplemented) { - AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, new (typeThatImplementsInterface, originalInterfaceImpl, interfaceMethodToBeImplemented.DeclaringType, context), @potentialImplMethod); - foundImpl = true; - break; + AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, originalInterfaceImpl, @potentialImplMethod); + foundImpl = true && !findAllPossibleImplementations; } } - if (foundImpl) { break; } } - - // We haven't found a MethodImpl on the current interface, but one of the interfaces - // this interface requires could still provide it. - if (!foundImpl) { - FindAndAddDefaultInterfaceImplementations (typeThatImplementsInterface, potentialImplInterface, interfaceMethodToBeImplemented, originalInterfaceImpl); - } } } MethodDefinition? TryMatchMethod (TypeReference type, MethodReference method) { - foreach (var candidate in type.GetMethods (context)) { - var md = context.TryResolve (candidate); - if (md?.IsVirtual != true) + foreach (var (candidate, md) in type.GetMethods (context)) { + if (md.IsVirtual != true) continue; - if (MethodMatch (candidate, method)) return md; } diff --git a/src/tools/illink/src/linker/Linker/TypeReferenceExtensions.cs b/src/tools/illink/src/linker/Linker/TypeReferenceExtensions.cs index 6189eae6d794c..b1c94aa57309e 100644 --- a/src/tools/illink/src/linker/Linker/TypeReferenceExtensions.cs +++ b/src/tools/illink/src/linker/Linker/TypeReferenceExtensions.cs @@ -158,25 +158,6 @@ void parseArrayDimensions (ArrayType at) return typeToInflate; } - public static IEnumerable<(TypeReference InflatedInterface, InterfaceImplementation OriginalImpl)> GetInflatedInterfaces (this TypeReference typeRef, ITryResolveMetadata resolver) - { - var typeDef = resolver.TryResolve (typeRef); - - if (typeDef?.HasInterfaces != true) - yield break; - - if (typeRef is GenericInstanceType genericInstance) { - foreach (var interfaceImpl in typeDef.Interfaces) { - // InflateGenericType only returns null when inflating generic parameters (and the generic instance type doesn't resolve). - // Here we are not inflating a generic parameter but an interface type reference. - yield return (InflateGenericType (genericInstance, interfaceImpl.InterfaceType, resolver), interfaceImpl)!; - } - } else { - foreach (var interfaceImpl in typeDef.Interfaces) - yield return (interfaceImpl.InterfaceType, interfaceImpl); - } - } - public static TypeReference? InflateGenericType (GenericInstanceType genericInstanceProvider, TypeReference typeToInflate, ITryResolveMetadata resolver) { if (typeToInflate is ArrayType arrayType) { @@ -276,7 +257,7 @@ private static GenericInstanceType MakeGenericType (GenericInstanceType genericI return result; } - public static IEnumerable GetMethods (this TypeReference type, ITryResolveMetadata resolver) + public static IEnumerable<(MethodReference, MethodDefinition)> GetMethods (this TypeReference type, ITryResolveMetadata resolver) { TypeDefinition? typeDef = resolver.TryResolve (type); if (typeDef?.HasMethods != true) @@ -284,10 +265,10 @@ public static IEnumerable GetMethods (this TypeReference type, if (type is GenericInstanceType genericInstanceType) { foreach (var methodDef in typeDef.Methods) - yield return MakeMethodReferenceForGenericInstanceType (genericInstanceType, methodDef); + yield return (MakeMethodReferenceForGenericInstanceType (genericInstanceType, methodDef), methodDef); } else { foreach (var method in typeDef.Methods) - yield return method; + yield return (method, method); } } diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/AttributesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/AttributesTests.g.cs index a912649e9cedc..dc69ebb930a72 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/AttributesTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/AttributesTests.g.cs @@ -9,6 +9,12 @@ public sealed partial class AttributesTests : LinkerTestBase protected override string TestSuiteName => "Attributes"; + [Fact] + public Task AssemblyAttributeAccessesMembers () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task AssemblyAttributeIsRemovedIfOnlyTypesUsedInAssembly () { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs index 19189bfcd0170..dc2440a552f56 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs @@ -63,6 +63,12 @@ public Task StaticDefaultInterfaceMethodOnStruct () return RunTest (allowMissingWarnings: true); } + [Fact] + public Task StaticDimOnDerivedInterface () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task StaticDimProvidedByUnreferencedIfaceInHierarchy () { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs index d436348e800bc..a1f78cc09b63c 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs @@ -21,6 +21,18 @@ public Task InterfaceImplementedRecursively () return RunTest (allowMissingWarnings: true); } + [Fact] + public Task RecursiveGenericInterfaces () + { + return RunTest (allowMissingWarnings: true); + } + + [Fact] + public Task RecursiveGenericInterfacesStatic () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task RecursiveInterfaceKept () { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/MetadataTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/MetadataTests.g.cs index 68e0654e2a445..9e958d384e9c5 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/MetadataTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/MetadataTests.g.cs @@ -9,6 +9,12 @@ public sealed partial class MetadataTests : LinkerTestBase protected override string TestSuiteName => "Metadata"; + [Fact] + public Task DebuggerDisplayNamesAreKept () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task NamesAreKept () { @@ -21,5 +27,29 @@ public Task NamesAreRemoved () return RunTest (allowMissingWarnings: true); } + [Fact] + public Task RootAllAssemblyNamesAreKept () + { + return RunTest (allowMissingWarnings: true); + } + + [Fact] + public Task RootDescriptorNamesAreKept () + { + return RunTest (allowMissingWarnings: true); + } + + [Fact] + public Task RootLibraryAssemblyNamesAreKept () + { + return RunTest (allowMissingWarnings: true); + } + + [Fact] + public Task RootVisibleAssemblyNamesAreKept () + { + return RunTest (allowMissingWarnings: true); + } + } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/StaticDimOnDerivedInterface.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/StaticDimOnDerivedInterface.cs new file mode 100644 index 0000000000000..0f45b8aec3aff --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/StaticDimOnDerivedInterface.cs @@ -0,0 +1,57 @@ +// 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; + +namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.DefaultInterfaceMethods +{ + class StaticDimOnDerivedInterface + { + [Kept] + public static void Main () + { + ITest testImpl = new TestClass (); + testImpl.Invoke (); + + CallStaticInvoke (); + } + + [Kept] + public interface ITestBase + { + [Kept] + static virtual string InvokeStatic () => throw new NotSupportedException (); + + [Kept] + string Invoke () => throw new NotSupportedException (); + } + + [Kept] + [KeptInterface (typeof (ITestBase))] + public interface ITest : ITestBase + { + [Kept] + static string ITestBase.InvokeStatic () => "Test"; + + [Kept] + string ITestBase.Invoke () => "Test"; + } + + [Kept] + [KeptInterface (typeof (ITest))] + [KeptInterface (typeof (ITestBase))] + public class TestClass : ITest + { + [Kept] + public TestClass () { } + } + + [Kept] + public static string CallStaticInvoke () where T : ITestBase => T.InvokeStatic (); + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfaces.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfaces.il new file mode 100644 index 0000000000000..b0cde69b93de9 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfaces.il @@ -0,0 +1,322 @@ +.assembly extern mscorlib { } + +.assembly 'library' { } + +.class interface public auto ansi abstract beforefieldinit IBase`3 +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .param type T + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + .param type U + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + .param type V + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + // Methods + .method public hidebysig newslot abstract virtual + instance !T GetT () cil managed + { + } // end of method IBase`3::GetT + + .method public hidebysig newslot virtual + instance !U GetU () cil managed + { + // Method begins at RVA 0x2050 + // Code size 10 (0xa) + .maxstack 1 + .locals init ( + [0] !U + ) + + IL_0000: ldloca.s 0 + IL_0002: initobj !U + IL_0008: ldloc.0 + IL_0009: ret + } // end of method IBase`3::GetU + + .method public hidebysig newslot virtual + instance !V GetV () cil managed + { + // Method begins at RVA 0x2068 + // Code size 10 (0xa) + .maxstack 1 + .locals init ( + [0] !V + ) + + IL_0000: ldloca.s 0 + IL_0002: initobj !V + IL_0008: ldloc.0 + IL_0009: ret + } // end of method IBase`3::GetV + +} // end of class IBase`3 + +.class interface public auto ansi abstract beforefieldinit IMiddle`2 + implements class IBase`3, + class IBase`3 +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + // Methods + .method private hidebysig virtual + instance int32 'IBase.GetV' () cil managed + { + .override method instance !2 class IBase`3::GetV() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IMiddle`2::'IBase.GetV' + + .method private hidebysig virtual + instance float32 'IBase.GetV' () cil managed + { + .override method instance !2 class IBase`3::GetV() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IMiddle`2::'IBase.GetV' + +} // end of class IMiddle`2 + +.class interface public auto ansi abstract beforefieldinit IDerived`1 + implements class IMiddle`2, + class IMiddle`2 + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + // Methods + .method private hidebysig virtual + instance int32 'IBase.GetV' () cil managed + { + .override method instance !2 class IBase`3::GetV() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetV' + + .method private hidebysig virtual + instance float32 'IBase.GetV' () cil managed + { + .override method instance !2 class IBase`3::GetV() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetV' + + .method private hidebysig virtual + instance int64 'IBase.GetU' () cil managed + { + .override method instance !1 class IBase`3::GetU() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetU' + + .method private hidebysig virtual + instance int64 'IBase.GetU' () cil managed + { + .override method instance !1 class IBase`3::GetU() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetU' + + .method private hidebysig virtual + instance float64 'IBase.GetU' () cil managed + { + .override method instance !1 class IBase`3::GetU() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetU' + + .method private hidebysig virtual + instance float64 'IBase.GetU' () cil managed + { + .override method instance !1 class IBase`3::GetU() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetU' + +} // end of class IDerived`1 + +.class public auto ansi beforefieldinit MyClass + extends [mscorlib]System.Object + implements class IDerived`1, + class IDerived`1 +{ + // Methods + .method private hidebysig newslot virtual + instance string 'IBase.GetT' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .override method instance !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot virtual + instance string 'IBase.GetT' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .override method instance !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot virtual + instance string 'IBase.GetT' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .override method instance !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot virtual + instance string 'IBase.GetT' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .override method instance !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot virtual + instance char 'IBase.GetT' () cil managed + { + .override method instance !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot virtual + instance char 'IBase.GetT' () cil managed + { + .override method instance !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot virtual + instance char 'IBase.GetT' () cil managed + { + .override method instance !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot virtual + instance char 'IBase.GetT' () cil managed + { + .override method instance !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2081 + // 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 MyClass::.ctor + +} // end of class MyClass + +.class public auto ansi beforefieldinit MyDerivedClass + extends MyClass +{ + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20c0 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void MyClass::.ctor() + IL_0006: ret + } // end of method MyDerivedClass::.ctor + +} // end of class MyDerivedClass diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfacesStatic.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfacesStatic.il new file mode 100644 index 0000000000000..9a37ab31140c3 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/Dependencies/RecursiveGenericInterfacesStatic.il @@ -0,0 +1,322 @@ +.assembly extern mscorlib { } + +.assembly 'library' { } + +.class interface public auto ansi abstract beforefieldinit IBase`3 +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .param type T + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + .param type U + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + .param type V + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + // Methods + .method public hidebysig newslot abstract virtual + static !T GetT () cil managed + { + } // end of method IBase`3::GetT + + .method public hidebysig newslot virtual + static !U GetU () cil managed + { + // Method begins at RVA 0x2050 + // Code size 10 (0xa) + .maxstack 1 + .locals init ( + [0] !U + ) + + IL_0000: ldloca.s 0 + IL_0002: initobj !U + IL_0008: ldloc.0 + IL_0009: ret + } // end of method IBase`3::GetU + + .method public hidebysig newslot virtual + static !V GetV () cil managed + { + // Method begins at RVA 0x2068 + // Code size 10 (0xa) + .maxstack 1 + .locals init ( + [0] !V + ) + + IL_0000: ldloca.s 0 + IL_0002: initobj !V + IL_0008: ldloc.0 + IL_0009: ret + } // end of method IBase`3::GetV + +} // end of class IBase`3 + +.class interface public auto ansi abstract beforefieldinit IMiddle`2 + implements class IBase`3, + class IBase`3 +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + // Methods + .method private hidebysig + static int32 'IBase.GetV' () cil managed + { + .override method !2 class IBase`3::GetV() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IMiddle`2::'IBase.GetV' + + .method private hidebysig + static float32 'IBase.GetV' () cil managed + { + .override method !2 class IBase`3::GetV() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IMiddle`2::'IBase.GetV' + +} // end of class IMiddle`2 + +.class interface public auto ansi abstract beforefieldinit IDerived`1 + implements class IMiddle`2, + class IMiddle`2 + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 02 00 00 + ) + // Methods + .method private hidebysig + static int32 'IBase.GetV' () cil managed + { + .override method !2 class IBase`3::GetV() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetV' + + .method private hidebysig + static float32 'IBase.GetV' () cil managed + { + .override method !2 class IBase`3::GetV() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetV' + + .method private hidebysig + static int64 'IBase.GetU' () cil managed + { + .override method !1 class IBase`3::GetU() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetU' + + .method private hidebysig + static int64 'IBase.GetU' () cil managed + { + .override method !1 class IBase`3::GetU() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetU' + + .method private hidebysig + static float64 'IBase.GetU' () cil managed + { + .override method !1 class IBase`3::GetU() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetU' + + .method private hidebysig + static float64 'IBase.GetU' () cil managed + { + .override method !1 class IBase`3::GetU() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method IDerived`1::'IBase.GetU' + +} // end of class IDerived`1 + +.class public auto ansi beforefieldinit MyClass + extends [mscorlib]System.Object + implements class IDerived`1, + class IDerived`1 +{ + // Methods + .method private hidebysig newslot + static string 'IBase.GetT' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .override method !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot + static string 'IBase.GetT' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .override method !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot + static string 'IBase.GetT' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .override method !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot + static string 'IBase.GetT' () cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .override method !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot + static char 'IBase.GetT' () cil managed + { + .override method !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot + static char 'IBase.GetT' () cil managed + { + .override method !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot + static char 'IBase.GetT' () cil managed + { + .override method !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method private hidebysig newslot + static char 'IBase.GetT' () cil managed + { + .override method !0 class IBase`3::GetT() + // Method begins at RVA 0x207e + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method MyClass::'IBase.GetT' + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2081 + // 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 MyClass::.ctor + +} // end of class MyClass + +.class public auto ansi beforefieldinit MyDerivedClass + extends MyClass +{ + // Methods + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x20c0 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void MyClass::.ctor() + IL_0006: ret + } // end of method MyDerivedClass::.ctor + +} // end of class MyDerivedClass 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 6e703f79d4650..71a772029587a 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 @@ -16,14 +16,14 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces [Define ("IL_ASSEMBLY_AVAILABLE")] [SetupCompileBefore ("library.dll", new[] { "Dependencies/GenericInterfaceImplementedRecursively.il" })] [SkipILVerify] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IMiddle`1", "library.dll", "Program/IBase`1")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IDerived`1", "library.dll", "Program/IMiddle`1")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/C", "library.dll", "Program/IDerived`1")] #if IL_ASSEMBLY_AVAILABLE [KeptTypeInAssembly ("library.dll", typeof(Program.IBase<>))] [KeptTypeInAssembly ("library.dll", typeof(Program.IMiddle<>))] - [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IMiddle`1", "library.dll", "Program/IBase`1")] [KeptTypeInAssembly ("library.dll", typeof(Program.IDerived<>))] - [KeptInterfaceOnTypeInAssembly ("library.dll", "Program/IDerived`1", "library.dll", "Program/IMiddle`1")] [KeptTypeInAssembly ("library.dll", typeof(Program.C))] - [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. diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs new file mode 100644 index 0000000000000..90c1a319c353f --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs @@ -0,0 +1,94 @@ +// 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")] + [TestCaseRequirements (TestRunCharacteristics.SupportsDefaultInterfaceMethods, "Requires support for default interface methods")] + [Define ("IL_ASSEMBLY_AVAILABLE")] + [SetupCompileBefore ("library.dll", new[] { "Dependencies/RecursiveGenericInterfaces.il" })] + [KeptAllTypesAndMembersInAssembly ("library.dll")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase.GetV", "V IBase`3::GetV()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase.GetV", "V IBase`3::GetV()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetV", "V IBase`3::GetV()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetV", "V IBase`3::GetV()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + + public class RecursiveGenericInterfaces + { + [Kept] + public static void Main () + { +#if IL_ASSEMBLY_AVAILABLE + UseIBase (new MyDerivedClass ()); + } + + [Kept] + public static void UseIBase (IBase myBase) + { + myBase.GetT (); + myBase.GetU (); + myBase.GetV (); +#endif + } + + //public interface IBase + //{ + // T GetT (); + // U GetU () => default; + // V GetV () => default; + //} + + //public interface IMiddle : IBase, IBase + //{ + // int IBase.GetV () => 12; + // float IBase.GetV () => 12.0f; + //} + + //public interface IDerived : IMiddle, IMiddle + //{ + // int IBase.GetV () => 12; + // float IBase.GetV () => 12.0f; + + // long IBase.GetU () => 12; + // long IBase.GetU () => 12; + + // double IBase.GetU () => 12; + // double IBase.GetU () => 12; + //} + + //public class MyClass : IDerived, IDerived + //{ + // string IBase.GetT () => throw new NotImplementedException (); + // string IBase.GetT () => throw new NotImplementedException (); + // string IBase.GetT () => throw new NotImplementedException (); + // string IBase.GetT () => throw new NotImplementedException (); + // char IBase.GetT () => throw new NotImplementedException (); + // char IBase.GetT () => throw new NotImplementedException (); + // char IBase.GetT () => throw new NotImplementedException (); + // char IBase.GetT () => throw new NotImplementedException (); + //} + + //public class MyDerivedClass : MyClass + //{ } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs new file mode 100644 index 0000000000000..28fa0a4f3e4c1 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs @@ -0,0 +1,125 @@ +// 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")] + [TestCaseRequirements (TestRunCharacteristics.SupportsDefaultInterfaceMethods | TestRunCharacteristics.SupportsStaticInterfaceMethods, "Requires support for default and static interface methods")] + [Define ("IL_ASSEMBLY_AVAILABLE")] + [SetupCompileBefore ("library.dll", new[] { "Dependencies/RecursiveGenericInterfacesStatic.il" })] + [KeptAllTypesAndMembersInAssembly ("library.dll")] + [KeptMemberInAssembly ("library.dll", "IBase`3", "GetT()", "GetU()", "GetV()")] + [KeptTypeInAssembly ("library.dll", "IMiddle`2")] + // Below isn't strictly necessary, but we keep them since the interface is generic and we haven't hardened generic interface handling to only keep the single closest DIM. + // We use method definition to match the .override to the required DIM. However, one DIM might be for a different generic instance than we are searching for. + // Because of this, we keep all generic interface DIMs that may be the DIM we need. + [KeptMemberInAssembly ("library.dll", "IMiddle`2", "IBase.GetV()", "IBase.GetV()")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "IMiddle`2", "library.dll", "IBase`3")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "IMiddle`2", "library.dll", "IBase`3")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase.GetV", "V IBase`3::GetV()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase.GetV", "V IBase`3::GetV()")] + [KeptTypeInAssembly ("library.dll", "IDerived`1")] + [KeptMemberInAssembly ("library.dll", "IDerived`1", + "IBase.GetV()", + "IBase.GetV()", + "IBase.GetU()", + "IBase.GetU()", + "IBase.GetU()", + "IBase.GetU()")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "IDerived`1", "library.dll", "IMiddle`2")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "IDerived`1", "library.dll", "IMiddle`2")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetV", "V IBase`3::GetV()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetV", "V IBase`3::GetV()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] + [KeptTypeInAssembly ("library.dll", "MyClass")] + [KeptMemberInAssembly ("library.dll", "MyClass", + "IBase.GetT()", + "IBase.GetT()", + "IBase.GetT()", + "IBase.GetT()", + "IBase.GetT()", + "IBase.GetT()", + "IBase.GetT()", + "IBase.GetT()")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "MyClass", "library.dll", "IDerived`1")] + [KeptInterfaceOnTypeInAssembly ("library.dll", "MyClass", "library.dll", "IDerived`1")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] + public class RecursiveGenericInterfacesStatic + { + [Kept] + public static void Main () + { + +#if IL_ASSEMBLY_AVAILABLE + UseIBase (); + _ = new MyDerivedClass (); + } + + [Kept] + public static void UseIBase () where TBase: IBase + { + TBase.GetT (); + TBase.GetU (); + TBase.GetV (); +#endif + } + + //public interface IBase + //{ + // static abstract T GetT (); + // static virtual U GetU () => default; + // static virtual V GetV () => default; + //} + + //public interface IMiddle : IBase, IBase + //{ + // static int IBase.GetV () => 12; + // static float IBase.GetV () => 12.0f; + //} + + //public interface IDerived : IMiddle, IMiddle + //{ + // static int IBase.GetV () => 12; + // static float IBase.GetV () => 12.0f; + + // static long IBase.GetU () => 12; + // static long IBase.GetU () => 12; + + // static double IBase.GetU () => 12; + // static double IBase.GetU () => 12; + //} + + //public class MyClass : IDerived, IDerived + //{ + // static string IBase.GetT () => throw new NotImplementedException (); + // static string IBase.GetT () => throw new NotImplementedException (); + // static string IBase.GetT () => throw new NotImplementedException (); + // static string IBase.GetT () => throw new NotImplementedException (); + // static char IBase.GetT () => throw new NotImplementedException (); + // static char IBase.GetT () => throw new NotImplementedException (); + // static char IBase.GetT () => throw new NotImplementedException (); + // static char IBase.GetT () => throw new NotImplementedException (); + //} + + //public class MyDerivedClass : MyClass + //{ } + } +} From 8e94b0ef8a40fda9ae12c0e895a4ca713084385f Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 11 Jun 2024 14:21:05 -0700 Subject: [PATCH 02/27] Use RuntimeInterfaceImplementations in MethodBodyScanner and ICustomMarshaler marking --- .../src/linker/Linker.Steps/MarkStep.cs | 36 +++++++------------ .../src/linker/Linker/MethodBodyScanner.cs | 31 +++++----------- 2 files changed, 20 insertions(+), 47 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index b473064e59a9a..dbb7cc87d7040 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -2600,31 +2600,18 @@ void MarkCustomMarshalerGetInstance (TypeDefinition type, in DependencyInfo reas void MarkICustomMarshalerMethods (TypeDefinition inputType, in DependencyInfo reason, MessageOrigin origin) { - TypeDefinition? type = inputType; - do { - if (!type.HasInterfaces) - continue; - - foreach (var iface in type.Interfaces) { - var iface_type = iface.InterfaceType; - if (!iface_type.IsTypeOf ("System.Runtime.InteropServices", "ICustomMarshaler")) - continue; - - // - // Instead of trying to guess where to find the interface declaration ILLink walks - // the list of implemented interfaces and resolve the declaration from there - // - var tdef = Context.Resolve (iface_type); - if (tdef == null) { - return; - } + var runtimeInterfaces = Annotations.GetRuntimeInterfaces (inputType); + if (runtimeInterfaces is null) + return; + foreach (var runtimeInterface in runtimeInterfaces) { - MarkMethodsIf (tdef.Methods, m => !m.IsStatic, reason, origin); + var iface_type = runtimeInterface.InterfaceTypeDefinition; + if (false == iface_type?.IsTypeOf ("System.Runtime.InteropServices", "ICustomMarshaler")) + continue; + MarkMethodsIf (iface_type!.Methods, m => !m.IsStatic, reason, origin); - MarkInterfaceImplementation (iface, new MessageOrigin (type)); - return; - } - } while ((type = Context.TryResolve (type.BaseType)) != null); + MarkRuntimeInterfaceImplementation (runtimeInterface, new MessageOrigin (Context.Resolve (runtimeInterface.TypeWithInterfaceImplementation))); + } } bool IsNonEmptyStaticConstructor (MethodDefinition method) @@ -3310,6 +3297,7 @@ protected virtual void MarkRequirementsForInstantiatedTypes (TypeDefinition type DoAdditionalInstantiatedTypeProcessing (type); } + void MarkRuntimeInterfaceImplementation (MethodDefinition method, MethodReference ov) { if (Context.Resolve (ov) is not MethodDefinition resolvedOverride) @@ -3647,7 +3635,7 @@ void MarkInterfacesNeededByBodyStack (MethodIL methodIL) return; foreach (var (implementation, type) in implementations) - MarkInterfaceImplementation (implementation, new MessageOrigin (type)); + MarkRuntimeInterfaceImplementation (implementation, new MessageOrigin (type)); } bool InstructionRequiresReflectionMethodBodyScannerForFieldAccess (Instruction instruction) diff --git a/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs b/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs index b3a230f194795..41e1f1464506b 100644 --- a/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs +++ b/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs @@ -63,7 +63,7 @@ public InterfacesOnStackScanner (LinkContext context) this.context = context; } - public IEnumerable<(InterfaceImplementation, TypeDefinition)>? GetReferencedInterfaces (MethodIL methodIL) + public IEnumerable<(RuntimeInterfaceImplementation, TypeDefinition)>? GetReferencedInterfaces (MethodIL methodIL) { var possibleStackTypes = AllPossibleStackTypes (methodIL); if (possibleStackTypes.Count == 0) @@ -73,7 +73,7 @@ public InterfacesOnStackScanner (LinkContext context) if (interfaceTypes.Length == 0) return null; - var interfaceImplementations = new HashSet<(InterfaceImplementation, TypeDefinition)> (); + var interfaceImplementations = new HashSet<(RuntimeInterfaceImplementation, TypeDefinition)> (); // If a type could be on the stack in the body and an interface it implements could be on the stack on the body // then we need to mark that interface implementation. When this occurs it is not safe to remove the interface implementation from the type @@ -139,31 +139,16 @@ HashSet AllPossibleStackTypes (MethodIL methodIL) return types; } - void AddMatchingInterfaces (HashSet<(InterfaceImplementation, TypeDefinition)> results, TypeDefinition type, TypeDefinition[] interfaceTypes) + void AddMatchingInterfaces (HashSet<(RuntimeInterfaceImplementation, TypeDefinition)> results, TypeDefinition type, TypeDefinition[] interfaceTypes) { - if (!type.HasInterfaces) + var runtimeInterfaces = context.Annotations.GetRuntimeInterfaces (type); + if (runtimeInterfaces is null) return; - - foreach (var interfaceType in interfaceTypes) { - if (HasInterface (type, interfaceType, out InterfaceImplementation? implementation)) - results.Add ((implementation, type)); - } - } - - bool HasInterface (TypeDefinition type, TypeDefinition interfaceType, [NotNullWhen (true)] out InterfaceImplementation? implementation) - { - implementation = null; - if (!type.HasInterfaces) - return false; - - foreach (var iface in type.Interfaces) { - if (context.TryResolve (iface.InterfaceType) == interfaceType) { - implementation = iface; - return true; + foreach (var iface in runtimeInterfaces) { + if (interfaceTypes.Contains (iface.InterfaceTypeDefinition)) { + results.Add ((iface, type)); } } - - return false; } void AddFromGenericInstance (HashSet set, IGenericInstance instance) From cf304f206c1905c6d24d9aec9d1dcaf9b9aa9bc0 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:04:49 -0700 Subject: [PATCH 03/27] Add remarks regarding checking for marked ifaceImpls with RuntimeInterfaceImpl --- .../src/linker/Linker/RuntimeInterfaceImplementation.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs index 9328414f580ef..b9286a1bf0afd 100644 --- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs @@ -14,6 +14,11 @@ namespace Mono.Linker /// Represents an implementation of an interface on a type that may be directly on the type or on a base type or implemented interface. /// This type is considered to implement the interface at runtime, even though the interface may not be directly on the type. /// + /// + /// This type should be used for marking, but should NOT be used to check if a runtime interface implementation has been marked. + /// This type represents the most direct way an interface may be implemented, but it may be implemented in a less direct way that is not represented here. + /// You should check all possible implementation 'paths' to determine if an interface is implemented, for example . + /// public class RuntimeInterfaceImplementation { /// From fcbfbb72b4c19b46a233c46971685ddded76065a Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:36:59 -0700 Subject: [PATCH 04/27] Fix ApiCompat failures --- src/tools/illink/src/linker/Linker/OverrideInformation.cs | 1 - .../illink/src/linker/Linker/RuntimeInterfaceImplementation.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/OverrideInformation.cs b/src/tools/illink/src/linker/Linker/OverrideInformation.cs index 30075f7a697ed..6eeba6e9e41aa 100644 --- a/src/tools/illink/src/linker/Linker/OverrideInformation.cs +++ b/src/tools/illink/src/linker/Linker/OverrideInformation.cs @@ -29,7 +29,6 @@ internal OverrideInformation (MethodDefinition @base, MethodDefinition @override Debug.Assert (@base.DeclaringType.IsInterface ? runtimeInterface!.InterfaceTypeDefinition == @base.DeclaringType : true); } - [Obsolete ("Use RuntimeInterfaceImplementation instead to handle recursive interfaces")] public InterfaceImplementation? MatchingInterfaceImplementation => RuntimeInterfaceImplementation?.InterfaceImplementation[0]; diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs index b9286a1bf0afd..87aa7cd1fda87 100644 --- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs @@ -19,7 +19,7 @@ namespace Mono.Linker /// This type represents the most direct way an interface may be implemented, but it may be implemented in a less direct way that is not represented here. /// You should check all possible implementation 'paths' to determine if an interface is implemented, for example . /// - public class RuntimeInterfaceImplementation + internal class RuntimeInterfaceImplementation { /// /// The type that implements . From e5e0679608be6391b19231c8a2b97339869b8c5c Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:35:40 -0700 Subject: [PATCH 05/27] Fix visibility and sealed warnings --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 4 ++-- .../src/linker/Linker/RuntimeInterfaceImplementation.cs | 2 +- src/tools/illink/src/linker/Linker/TypeMapInfo.cs | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index dbb7cc87d7040..95c550137c932 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -32,6 +32,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -2410,8 +2411,7 @@ void MarkInterfaceImplementations (TypeDefinition type) } } - - protected virtual bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation) + internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation) { var type = runtimeInterfaceImplementation.Implementor; var ifaces = runtimeInterfaceImplementation.InterfaceImplementation; diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs index 87aa7cd1fda87..292e0da0bd426 100644 --- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs @@ -19,7 +19,7 @@ namespace Mono.Linker /// This type represents the most direct way an interface may be implemented, but it may be implemented in a less direct way that is not represented here. /// You should check all possible implementation 'paths' to determine if an interface is implemented, for example . /// - internal class RuntimeInterfaceImplementation + internal sealed class RuntimeInterfaceImplementation { /// /// The type that implements . diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index ec7206addba0f..3a4c19ecc54fa 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -38,7 +38,6 @@ namespace Mono.Linker { - public class TypeMapInfo { readonly HashSet assemblies = new HashSet (); @@ -100,17 +99,17 @@ public void EnsureProcessed (AssemblyDefinition assembly) return ret; } - public void AddBaseMethod (MethodDefinition method, MethodDefinition @base, RuntimeInterfaceImplementation? runtimeInterface) + internal void AddBaseMethod (MethodDefinition method, MethodDefinition @base, RuntimeInterfaceImplementation? runtimeInterface) { base_methods.AddToList (method, new OverrideInformation (@base, method, runtimeInterface)); } - public void AddOverride (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null) + internal void AddOverride (MethodDefinition @base, MethodDefinition @override, RuntimeInterfaceImplementation? runtimeInterface = null) { override_methods.AddToList (@base, new OverrideInformation (@base, @override, runtimeInterface)); } - public void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod) + internal void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod) { Debug.Assert (@base.DeclaringType.IsInterface); default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, runtimeInterface)); From 451b677f1f78e12d2935e69e4097447a8b88cb07 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 12 Jun 2024 14:06:42 -0700 Subject: [PATCH 06/27] Add DAM annotations for recursive interfaces --- .../src/System/Reflection/Context/Custom/CustomType.cs | 2 ++ .../src/System/Reflection/Context/Projection/ProjectingType.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs index 95ee34ab4504b..56ab55515ce19 100644 --- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs +++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs @@ -44,6 +44,7 @@ public override bool IsInstanceOfType([NotNullWhen(true)] object? o) return IsAssignableFrom(objectType); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) { // list of properties on this type according to the underlying ReflectionContext @@ -139,6 +140,7 @@ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) return binder.SelectProperty(bindingAttr, matchingProperties.ToArray(), returnType, types, modifiers); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] public override MethodInfo[] GetMethods(BindingFlags bindingAttr) { // list of methods on this type according to the underlying ReflectionContext diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs index 0ac9bb0de89f3..e5fd04e198a23 100644 --- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs +++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs @@ -206,11 +206,13 @@ public override EventInfo[] GetEvents(BindingFlags bindingAttr) return _projector.Project(base.GetEvents(bindingAttr), _projector.ProjectEvent); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] public override FieldInfo? GetField(string name, BindingFlags bindingAttr) { return _projector.ProjectField(base.GetField(name, bindingAttr)); } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] public override FieldInfo[] GetFields(BindingFlags bindingAttr) { return _projector.Project(base.GetFields(bindingAttr), _projector.ProjectField); From 1e1451aaf6f8d9fa6c7eb47d8187493c1252be53 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 13 Jun 2024 11:02:00 -0700 Subject: [PATCH 07/27] Add additional test cases and annotate reflection types with ifdefs --- .../Reflection/Context/Custom/CustomType.cs | 4 + .../Context/Projection/ProjectingType.cs | 40 +++++++++ ...dInterfaceImplementationRemovedOverride.cs | 89 +++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs index 56ab55515ce19..0641a9ff4e22e 100644 --- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs +++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs @@ -44,7 +44,9 @@ public override bool IsInstanceOfType([NotNullWhen(true)] object? o) return IsAssignableFrom(objectType); } +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] +#endif public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) { // list of properties on this type according to the underlying ReflectionContext @@ -140,7 +142,9 @@ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) return binder.SelectProperty(bindingAttr, matchingProperties.ToArray(), returnType, types, modifiers); } +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] +#endif public override MethodInfo[] GetMethods(BindingFlags bindingAttr) { // list of methods on this type according to the underlying ReflectionContext diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs index e5fd04e198a23..4f02989adde57 100644 --- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs +++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs @@ -78,6 +78,9 @@ public override IList GetCustomAttributesData() return _projector.Project(base.GetCustomAttributesData(), _projector.ProjectCustomAttributeData); } +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] +#endif public override EventInfo[] GetEvents() { return _projector.Project(base.GetEvents(), _projector.ProjectEvent); @@ -105,6 +108,15 @@ public override InterfaceMapping GetInterfaceMap(Type interfaceType) return _projector.ProjectInterfaceMapping(base.GetInterfaceMap(interfaceType)); } +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | + DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)] +#endif public override MemberInfo[] GetMember(string name, MemberTypes type, BindingFlags bindingAttr) { StringComparison comparisonType = (bindingAttr & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase @@ -186,6 +198,9 @@ public override bool IsSubclassOf(Type c) return _projector.ProjectConstructor(base.GetConstructorImpl(bindingAttr, binder, callConvention, types, modifiers)); } +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] +#endif public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) { return _projector.Project(base.GetConstructors(bindingAttr), _projector.ProjectConstructor); @@ -196,23 +211,33 @@ public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) return _projector.ProjectType(base.GetElementType()); } +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] +#endif public override EventInfo? GetEvent(string name, BindingFlags bindingAttr) { return _projector.ProjectEvent(base.GetEvent(name, bindingAttr)); } +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] +#endif public override EventInfo[] GetEvents(BindingFlags bindingAttr) { return _projector.Project(base.GetEvents(bindingAttr), _projector.ProjectEvent); } +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] +#endif public override FieldInfo? GetField(string name, BindingFlags bindingAttr) { return _projector.ProjectField(base.GetField(name, bindingAttr)); } +#if NET6_0_OR_GREATER [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] +#endif public override FieldInfo[] GetFields(BindingFlags bindingAttr) { return _projector.Project(base.GetFields(bindingAttr), _projector.ProjectField); @@ -228,6 +253,15 @@ public override Type[] GetInterfaces() return _projector.Project(base.GetInterfaces(), _projector.ProjectType); } +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | + DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | + DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | + DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | + DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | + DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)] +#endif public override MemberInfo[] GetMembers(BindingFlags bindingAttr) { MethodInfo[] methods = GetMethods(bindingAttr); @@ -266,6 +300,9 @@ public override MemberInfo[] GetMembers(BindingFlags bindingAttr) return _projector.ProjectMethod(base.GetMethodImpl(name, bindingAttr, binder, callConvention, types, modifiers)); } +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] +#endif public override MethodInfo[] GetMethods(BindingFlags bindingAttr) { return _projector.Project(base.GetMethods(bindingAttr), _projector.ProjectMethod); @@ -281,6 +318,9 @@ public override Type[] GetNestedTypes(BindingFlags bindingAttr) return _projector.Project(base.GetNestedTypes(bindingAttr), _projector.ProjectType); } +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] +#endif public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) { return _projector.Project(base.GetProperties(bindingAttr), _projector.ProjectProperty); 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 index 01541dd73a17b..499a08d521857 100644 --- 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 @@ -22,6 +22,8 @@ public static void Main () GenericInterfaceGenericType.Test (); PrivateExplicitImplementationReflectedOver.Test (); InheritedInterfaces.Test (); + DimWithOverride.Test (); + Generics.Test (); } [Kept] @@ -274,5 +276,92 @@ class UsedDirectly : IDerived public static void M () { } } } + + [Kept] + public class DimWithOverride + { + [Kept] + public static void Test () + { + CallM (); + } + + [Kept] + static void CallM () where T : IBase + { + T.M (); + } + + [Kept] + interface IBase + { + [Kept] + static abstract void M (); + } + + [Kept] + [KeptInterface (typeof (IBase))] + interface IDerived : IBase + { + [Kept] + static void IBase.M () { } + } + + [Kept] + [KeptInterface (typeof (IDerived))] + [KeptInterface (typeof (IBase))] + class MyClass : IDerived + { + } + + [Kept] + [KeptBaseType (typeof (MyClass))] + [KeptInterface (typeof (IBase))] + class MyDerivedClass : MyClass, IBase + { + } + } + + [Kept] + public class Generics + { + [Kept] + public static void Test () + { + UseIGeneric (); + } + + [Kept] + static void UseIGeneric () where T : IGeneric + { + T.GetT (); + } + + [Kept] + interface IGeneric + { + [Kept] + static abstract T GetT (); + } + + [Kept] + [KeptInterface (typeof (IGeneric))] + [KeptInterface (typeof (IGeneric))] + [KeptInterface (typeof (IGeneric))] + class MyType : IGeneric, IGeneric, IGeneric + { + [Kept] + [KeptOverride (typeof (IGeneric))] + static int IGeneric.GetT () => 0; + + [Kept] + [KeptOverride (typeof (IGeneric))] + static float IGeneric.GetT () => 0; + + [Kept] + [KeptOverride (typeof (IGeneric))] + static string IGeneric.GetT () => ""; + } + } } } From ac5a42a28a9dc8ca0f2c16ad0cabecb22a208218 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:17:07 -0700 Subject: [PATCH 08/27] Use suppressions instead of annotating in System.Reflection.Context --- .../src/ILLink/ILLink.Suppressions.xml | 50 ++++++++++++++++++- .../Reflection/Context/Custom/CustomType.cs | 6 --- .../Context/Projection/ProjectingType.cs | 42 ---------------- 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml index 6e691bf6bc961..8df6b6dd9fa8b 100644 --- a/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml +++ b/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml @@ -523,5 +523,53 @@ member M:System.Reflection.Context.Delegation.DelegatingType.GetInterface(System.String,System.Boolean) + + ILLink + IL2094 + member + M:System.Reflection.Context.Projection.ProjectingType.GetField(System.String,System.Reflection.BindingFlags) + + + ILLink + IL2094 + member + M:System.Reflection.Context.Projection.ProjectingType.GetFields(System.Reflection.BindingFlags) + + + ILLink + IL2094 + member + M:System.Reflection.Context.Projection.ProjectingType.GetMembers(System.Reflection.BindingFlags) + + + ILLink + IL2094 + member + M:System.Reflection.Context.Projection.ProjectingType.GetMethods(System.Reflection.BindingFlags) + + + ILLink + IL2094 + member + M:System.Reflection.Context.Projection.ProjectingType.GetProperties(System.Reflection.BindingFlags) + + + ILLink + IL2094 + member + M:System.Reflection.Context.Projection.ProjectingType.GetDefaultMembers() + + + ILLink + IL2094 + member + M:System.Reflection.Context.Custom.CustomType.GetProperties(System.Reflection.BindingFlags) + + + ILLink + IL2094 + member + M:System.Reflection.Context.Custom.CustomType.GetMethods(System.Reflection.BindingFlags) + - \ No newline at end of file + diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs index 0641a9ff4e22e..95ee34ab4504b 100644 --- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs +++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Custom/CustomType.cs @@ -44,9 +44,6 @@ public override bool IsInstanceOfType([NotNullWhen(true)] object? o) return IsAssignableFrom(objectType); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] -#endif public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) { // list of properties on this type according to the underlying ReflectionContext @@ -142,9 +139,6 @@ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) return binder.SelectProperty(bindingAttr, matchingProperties.ToArray(), returnType, types, modifiers); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] -#endif public override MethodInfo[] GetMethods(BindingFlags bindingAttr) { // list of methods on this type according to the underlying ReflectionContext diff --git a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs index 4f02989adde57..0ac9bb0de89f3 100644 --- a/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs +++ b/src/libraries/System.Reflection.Context/src/System/Reflection/Context/Projection/ProjectingType.cs @@ -78,9 +78,6 @@ public override IList GetCustomAttributesData() return _projector.Project(base.GetCustomAttributesData(), _projector.ProjectCustomAttributeData); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] -#endif public override EventInfo[] GetEvents() { return _projector.Project(base.GetEvents(), _projector.ProjectEvent); @@ -108,15 +105,6 @@ public override InterfaceMapping GetInterfaceMap(Type interfaceType) return _projector.ProjectInterfaceMapping(base.GetInterfaceMap(interfaceType)); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | - DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | - DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | - DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | - DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | - DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)] -#endif public override MemberInfo[] GetMember(string name, MemberTypes type, BindingFlags bindingAttr) { StringComparison comparisonType = (bindingAttr & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase @@ -198,9 +186,6 @@ public override bool IsSubclassOf(Type c) return _projector.ProjectConstructor(base.GetConstructorImpl(bindingAttr, binder, callConvention, types, modifiers)); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] -#endif public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) { return _projector.Project(base.GetConstructors(bindingAttr), _projector.ProjectConstructor); @@ -211,33 +196,21 @@ public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) return _projector.ProjectType(base.GetElementType()); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] -#endif public override EventInfo? GetEvent(string name, BindingFlags bindingAttr) { return _projector.ProjectEvent(base.GetEvent(name, bindingAttr)); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] -#endif public override EventInfo[] GetEvents(BindingFlags bindingAttr) { return _projector.Project(base.GetEvents(bindingAttr), _projector.ProjectEvent); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] -#endif public override FieldInfo? GetField(string name, BindingFlags bindingAttr) { return _projector.ProjectField(base.GetField(name, bindingAttr)); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] -#endif public override FieldInfo[] GetFields(BindingFlags bindingAttr) { return _projector.Project(base.GetFields(bindingAttr), _projector.ProjectField); @@ -253,15 +226,6 @@ public override Type[] GetInterfaces() return _projector.Project(base.GetInterfaces(), _projector.ProjectType); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | - DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | - DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents | - DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | - DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | - DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)] -#endif public override MemberInfo[] GetMembers(BindingFlags bindingAttr) { MethodInfo[] methods = GetMethods(bindingAttr); @@ -300,9 +264,6 @@ public override MemberInfo[] GetMembers(BindingFlags bindingAttr) return _projector.ProjectMethod(base.GetMethodImpl(name, bindingAttr, binder, callConvention, types, modifiers)); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] -#endif public override MethodInfo[] GetMethods(BindingFlags bindingAttr) { return _projector.Project(base.GetMethods(bindingAttr), _projector.ProjectMethod); @@ -318,9 +279,6 @@ public override Type[] GetNestedTypes(BindingFlags bindingAttr) return _projector.Project(base.GetNestedTypes(bindingAttr), _projector.ProjectType); } -#if NET6_0_OR_GREATER - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] -#endif public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) { return _projector.Project(base.GetProperties(bindingAttr), _projector.ProjectProperty); From 4e1f6f9e86b30e4a278069567296b8c6a68826ed Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:39:37 -0700 Subject: [PATCH 09/27] Add test and fix for failure in System.Text.Json When the base type has an interface and an unmarked derived type overrides a method on that interface, we would mark the overriding method without checking if the type is even marked. We will check if the declaring type is marked before marking the override. --- .../src/linker/Linker.Steps/MarkStep.cs | 3 + .../BaseTypeMarkedInterfaceDerivedNotKept.cs | 56 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/BaseTypeMarkedInterfaceDerivedNotKept.cs diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 95c550137c932..9731e51ea5ec2 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -2513,6 +2513,9 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat if (Annotations.IsMarked (method)) return false; + if (!Annotations.IsMarked (method.DeclaringType)) + return false; + // If the interface implementation is not marked, do not mark the implementation method // A type that doesn't implement the interface isn't required to have methods that implement the interface. // We must check all possible ways the interface could be implemented by the type (through all recursive interface implementations, not just the primary one) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/BaseTypeMarkedInterfaceDerivedNotKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/BaseTypeMarkedInterfaceDerivedNotKept.cs new file mode 100644 index 0000000000000..999d0c2855d42 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/BaseTypeMarkedInterfaceDerivedNotKept.cs @@ -0,0 +1,56 @@ +// 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; + +namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces +{ + class BaseTypeMarkedInterfaceDerivedNotKept + { + [Kept] + public static void Main () + { + IFoo foo = new UsedDerived (); + foo.Method (); + } + + [Kept] + [KeptInterface (typeof (IFoo))] + [KeptMember (".ctor()")] + abstract class BaseType : IFoo + { + [Kept] + public abstract void Method (); + } + + [Kept] + [KeptBaseType (typeof (BaseType))] + [KeptMember (".ctor()")] + class UsedDerived : BaseType + { + [Kept] + public override void Method () + { + } + } + + class UnusedDerived : BaseType + { + public override void Method () + { + } + } + + [Kept] + interface IFoo + { + [Kept] + void Method (); + } + } +} From f8dad6ba114d0d94b7f6b918427ab87fe2ceb573 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:56:59 -0700 Subject: [PATCH 10/27] Check for implementor to be marked, not overridingMethod.DeclaringType --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 2 +- ...eritance.Interfaces.RecursiveInterfacesTests.g.cs | 12 ++++++++++++ ...tance.Interfaces.StaticInterfaceMethodsTests.g.cs | 6 ++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 9731e51ea5ec2..9ac54b5f36d81 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -2513,7 +2513,7 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat if (Annotations.IsMarked (method)) return false; - if (!Annotations.IsMarked (method.DeclaringType)) + if (!Annotations.IsMarked (overrideInformation.RuntimeInterfaceImplementation.Implementor)) return false; // If the interface implementation is not marked, do not mark the implementation method diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs index a1f78cc09b63c..1dbdf7bd5c9ac 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs @@ -9,6 +9,12 @@ public sealed partial class RecursiveInterfacesTests : LinkerTestBase protected override string TestSuiteName => "Inheritance.Interfaces.RecursiveInterfaces"; + [Fact] + public Task BaseTypeMarkedInterfaceDerivedNotKept () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task GenericInterfaceImplementedRecursively () { @@ -21,6 +27,12 @@ public Task InterfaceImplementedRecursively () return RunTest (allowMissingWarnings: true); } + [Fact] + public Task OverrideOfRecursiveInterfaceIsRemoved () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task RecursiveGenericInterfaces () { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.StaticInterfaceMethodsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.StaticInterfaceMethodsTests.g.cs index 12c5692019fda..531c0cb0d7d08 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.StaticInterfaceMethodsTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.StaticInterfaceMethodsTests.g.cs @@ -7,6 +7,12 @@ namespace ILLink.RoslynAnalyzer.Tests.Inheritance.Interfaces public sealed partial class StaticInterfaceMethodsTests : LinkerTestBase { + [Fact] + public Task InstanceMethodsWithOverridesSwept () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task OverrideInCopyAssembly () { From 2aca6c4c13af009499dd303446be6863bd46d73c Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 17 Jun 2024 15:28:42 -0700 Subject: [PATCH 11/27] PR feedback: - Make methods local methods with captured variables - Inline local variables - Only store interfaces in runtime interfaces dictionary if the type implements 1 or more interfaces - Rename variables --- .../illink/src/linker/Linker/TypeMapInfo.cs | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index 3a4c19ecc54fa..4cc31ce50c01b 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -109,16 +109,12 @@ internal void AddOverride (MethodDefinition @base, MethodDefinition @override, R override_methods.AddToList (@base, new OverrideInformation (@base, @override, runtimeInterface)); } - internal void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod) - { - Debug.Assert (@base.DeclaringType.IsInterface); - default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, runtimeInterface)); - } - - Dictionary> interfaces = new (); + Dictionary> _runtimeInterfaceImpls = new (); protected virtual void MapType (TypeDefinition type) { - interfaces[type] = GetRecursiveInterfaceImplementations (type); + var runtimeInterfaces = GetRecursiveInterfaceImplementations (type); + if (runtimeInterfaces is not null) + _runtimeInterfaceImpls[type] = runtimeInterfaces.Value; MapVirtualMethods (type); MapInterfaceMethodsInTypeHierarchy (type); @@ -132,59 +128,67 @@ protected virtual void MapType (TypeDefinition type) internal ImmutableArray? GetRecursiveInterfaces (TypeDefinition type) { EnsureProcessed (type.Module.Assembly); - if (interfaces.TryGetValue (type, out var value)) + if (_runtimeInterfaceImpls.TryGetValue (type, out var value)) return value; return null; } - ImmutableArray GetRecursiveInterfaceImplementations (TypeDefinition type) + ImmutableArray? GetRecursiveInterfaceImplementations (TypeDefinition originalType) { // We'll need at least this many interfaces, so let's preallocate - var runtimeIfaces = ImmutableArray.CreateBuilder (type.Interfaces.Count); + ImmutableArray.Builder runtimeIfaces = ImmutableArray.CreateBuilder (originalType.Interfaces.Count); - AddRecursiveInterfaces (type, type, [], runtimeIfaces, context); + List interfaces = new List (16); + AddRecursiveInterfaces (originalType, interfaces); + if (runtimeIfaces.Count == 0) + return null; return runtimeIfaces.ToImmutable (); - static void AddRecursiveInterfaces (TypeDefinition originalType, TypeReference typeRef, IEnumerable pathToType, ImmutableArray.Builder implementors, LinkContext Context) + void AddRecursiveInterfaces (TypeReference typeRef, List pathToType) { - var type = Context.TryResolve (typeRef); + var type = context.TryResolve (typeRef); if (type is null) return; // Get all explicit interfaces of this type foreach (var iface in type.Interfaces) { - var interfaceType = iface.InterfaceType.TryInflateFrom (typeRef, Context); + var interfaceType = iface.InterfaceType.TryInflateFrom (typeRef, context); if (interfaceType is null) { continue; } - if (!implementors.Any (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceType, Context))) { - implementors.Add (new RuntimeInterfaceImplementation (originalType, typeRef, pathToType.Append (iface), interfaceType, Context)); + if (!runtimeIfaces.Any (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceType, context))) { + pathToType.Add (iface); + runtimeIfaces.Add (new RuntimeInterfaceImplementation (originalType, typeRef, pathToType.Append (iface), interfaceType, context)); + pathToType.RemoveAt (pathToType.Count - 1); } } if (type.BaseType is not null) { - var inflatedBaseType = type.BaseType.TryInflateFrom (typeRef, Context)!; - AddRecursiveInterfaces (originalType, inflatedBaseType, [], implementors, Context); + var inflatedBaseType = type.BaseType.TryInflateFrom (typeRef, context)!; + AddRecursiveInterfaces (inflatedBaseType, []); } // Recursive interfaces after all direct interfaces to preserve Inherit/Implement tree order foreach (var iface in type.Interfaces) { // If we can't resolve the interface type we can't find recursive interfaces - var ifaceDirectlyOnType = iface.InterfaceType.TryInflateFrom (typeRef, Context); + var ifaceDirectlyOnType = iface.InterfaceType.TryInflateFrom (typeRef, context); if (ifaceDirectlyOnType is null) { continue; } - AddRecursiveInterfaces (originalType, ifaceDirectlyOnType, pathToType.Append (iface), implementors, Context); + pathToType.Add (iface); + AddRecursiveInterfaces (ifaceDirectlyOnType, pathToType); + pathToType.RemoveAt (pathToType.Count - 1); } } } void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type) { - var inflatedInterfaces = interfaces[type]; + if (!_runtimeInterfaceImpls.TryGetValue (type, out var runtimeInterfaceImpls)) + return; // Foreach interface and for each newslot virtual method on the interface, try // to find the method implementation and record it. - foreach (var interfaceImpl in inflatedInterfaces) { + foreach (var interfaceImpl in runtimeInterfaceImpls) { foreach ((MethodReference interfaceMethod, MethodDefinition resolvedInterfaceMethod) in interfaceImpl.InflatedInterfaceType.GetMethods (context)) { // TODO-NICE: if the interface method is implemented explicitly (with an override), // we shouldn't need to run the below logic. This results in ILLink potentially @@ -257,7 +261,7 @@ void MapOverrides (MethodDefinition method) if (baseMethod == null) continue; if (baseMethod.DeclaringType.IsInterface) { - var found = interfaces.TryGetValue (method.DeclaringType, out var runtimeInterfaces); + var found = _runtimeInterfaceImpls.TryGetValue (method.DeclaringType, out var runtimeInterfaces); Debug.Assert (found); var runtimeInterface = runtimeInterfaces.Single (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, baseMethodRef.DeclaringType, context)); AnnotateMethods (baseMethod, method, runtimeInterface); @@ -332,17 +336,15 @@ void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, Runtim /// /// The InterfaceImplementation on that points to the DeclaringType of . /// - void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference inflatedInterfaceReference, MethodDefinition interfaceMethodToBeImplemented) + void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference inflatedInterfaceMethod, MethodDefinition interfaceMethodDef) { - var typeThatImplementsInterface = originalInterfaceImpl.Implementor; // Go over all interfaces, trying to find a method that is an explicit MethodImpl of the // interface method in question. - var hasInterfaces = interfaces.TryGetValue (typeThatImplementsInterface, out var runtimeIfaces); - if (!hasInterfaces) + if (!_runtimeInterfaceImpls.TryGetValue (originalInterfaceImpl.Implementor, out var runtimeIfaces)) return; - bool findAllPossibleImplementations = inflatedInterfaceReference.DeclaringType is GenericInstanceType; - foreach (var interfaceImpl in runtimeIfaces!) { + bool findAllPossibleImplementations = inflatedInterfaceMethod.DeclaringType is GenericInstanceType; + foreach (var interfaceImpl in runtimeIfaces) { var potentialImplInterface = interfaceImpl.InterfaceTypeDefinition; if (potentialImplInterface is null) continue; @@ -352,9 +354,9 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o // Otherwise, we're good to stop looking higher in the hierarchy after the first. Note we still need to look at other branches of the hierarchy to find any diamond cases. bool foundImpl = false; foreach (var potentialImplMethod in potentialImplInterface.Methods) { - if (potentialImplMethod == interfaceMethodToBeImplemented && + if (potentialImplMethod == interfaceMethodDef && !potentialImplMethod.IsAbstract) { - AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, originalInterfaceImpl, potentialImplMethod); + AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, potentialImplMethod); foundImpl = true; break; } @@ -364,8 +366,8 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o // This method is an override of something. Let's see if it's the method we are looking for. foreach (var baseMethod in potentialImplMethod.Overrides) { - if (context.TryResolve (baseMethod) == interfaceMethodToBeImplemented) { - AddDefaultInterfaceImplementation (interfaceMethodToBeImplemented, originalInterfaceImpl, @potentialImplMethod); + if (context.TryResolve (baseMethod) == interfaceMethodDef) { + AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, @potentialImplMethod); foundImpl = true && !findAllPossibleImplementations; } } @@ -374,12 +376,18 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o } } } + + void AddDefaultInterfaceImplementation (MethodDefinition @base, RuntimeInterfaceImplementation runtimeInterface, MethodDefinition defaultImplementationMethod) + { + Debug.Assert (@base.DeclaringType.IsInterface); + default_interface_implementations.AddToList (@base, new OverrideInformation (@base, defaultImplementationMethod, runtimeInterface)); + } } MethodDefinition? TryMatchMethod (TypeReference type, MethodReference method) { foreach (var (candidate, md) in type.GetMethods (context)) { - if (md.IsVirtual != true) + if (!md.IsVirtual) continue; if (MethodMatch (candidate, method)) return md; From bb308c7e99e1576625bcba9052ad6d51a4f76bb9 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:34:46 -0700 Subject: [PATCH 12/27] Separate out generic DIM matching fix --- src/tools/illink/src/linker/Linker/TypeMapInfo.cs | 5 ++--- .../RecursiveGenericInterfacesStatic.cs | 14 ++------------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index 4cc31ce50c01b..8e5056cb07edb 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -336,14 +336,13 @@ void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, Runtim /// /// The InterfaceImplementation on that points to the DeclaringType of . /// - void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference inflatedInterfaceMethod, MethodDefinition interfaceMethodDef) + void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference _, MethodDefinition interfaceMethodDef) { // Go over all interfaces, trying to find a method that is an explicit MethodImpl of the // interface method in question. if (!_runtimeInterfaceImpls.TryGetValue (originalInterfaceImpl.Implementor, out var runtimeIfaces)) return; - bool findAllPossibleImplementations = inflatedInterfaceMethod.DeclaringType is GenericInstanceType; foreach (var interfaceImpl in runtimeIfaces) { var potentialImplInterface = interfaceImpl.InterfaceTypeDefinition; if (potentialImplInterface is null) @@ -368,7 +367,7 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o foreach (var baseMethod in potentialImplMethod.Overrides) { if (context.TryResolve (baseMethod) == interfaceMethodDef) { AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, @potentialImplMethod); - foundImpl = true && !findAllPossibleImplementations; + foundImpl = true; } } if (foundImpl) { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs index 28fa0a4f3e4c1..b01ee019c6b93 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfacesStatic.cs @@ -15,33 +15,23 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces [TestCaseRequirements (TestRunCharacteristics.SupportsDefaultInterfaceMethods | TestRunCharacteristics.SupportsStaticInterfaceMethods, "Requires support for default and static interface methods")] [Define ("IL_ASSEMBLY_AVAILABLE")] [SetupCompileBefore ("library.dll", new[] { "Dependencies/RecursiveGenericInterfacesStatic.il" })] - [KeptAllTypesAndMembersInAssembly ("library.dll")] [KeptMemberInAssembly ("library.dll", "IBase`3", "GetT()", "GetU()", "GetV()")] [KeptTypeInAssembly ("library.dll", "IMiddle`2")] // Below isn't strictly necessary, but we keep them since the interface is generic and we haven't hardened generic interface handling to only keep the single closest DIM. // We use method definition to match the .override to the required DIM. However, one DIM might be for a different generic instance than we are searching for. // Because of this, we keep all generic interface DIMs that may be the DIM we need. - [KeptMemberInAssembly ("library.dll", "IMiddle`2", "IBase.GetV()", "IBase.GetV()")] + [KeptMemberInAssembly ("library.dll", "IMiddle`2", "IBase.GetV()")] [KeptInterfaceOnTypeInAssembly ("library.dll", "IMiddle`2", "library.dll", "IBase`3")] [KeptInterfaceOnTypeInAssembly ("library.dll", "IMiddle`2", "library.dll", "IBase`3")] [KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase.GetV", "V IBase`3::GetV()")] - [KeptOverrideOnMethodInAssembly ("library.dll", "IMiddle`2", "IBase.GetV", "V IBase`3::GetV()")] [KeptTypeInAssembly ("library.dll", "IDerived`1")] [KeptMemberInAssembly ("library.dll", "IDerived`1", "IBase.GetV()", - "IBase.GetV()", - "IBase.GetU()", - "IBase.GetU()", - "IBase.GetU()", - "IBase.GetU()")] + "IBase.GetU()")] [KeptInterfaceOnTypeInAssembly ("library.dll", "IDerived`1", "library.dll", "IMiddle`2")] [KeptInterfaceOnTypeInAssembly ("library.dll", "IDerived`1", "library.dll", "IMiddle`2")] [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetV", "V IBase`3::GetV()")] - [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetV", "V IBase`3::GetV()")] [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] - [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] - [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] - [KeptOverrideOnMethodInAssembly ("library.dll", "IDerived`1", "IBase.GetU", "U IBase`3::GetU()")] [KeptTypeInAssembly ("library.dll", "MyClass")] [KeptMemberInAssembly ("library.dll", "MyClass", "IBase.GetT()", From 48484488cea6aee913789e5ff0ef34bce375a1e0 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 26 Jun 2024 09:25:10 -0700 Subject: [PATCH 13/27] Remove unused parameter, Add test case --- .../illink/src/linker/Linker/TypeMapInfo.cs | 7 ++-- .../BaseProvidesInterfaceMethod.cs | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index 8e5056cb07edb..a43f37fdd6bd8 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -219,7 +219,7 @@ void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type) } // Look for a default implementation last. - FindAndAddDefaultInterfaceImplementations (interfaceImpl, interfaceMethod, resolvedInterfaceMethod); + FindAndAddDefaultInterfaceImplementations (interfaceImpl, resolvedInterfaceMethod); } } } @@ -336,7 +336,7 @@ void AnnotateMethods (MethodDefinition @base, MethodDefinition @override, Runtim /// /// The InterfaceImplementation on that points to the DeclaringType of . /// - void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodReference _, MethodDefinition interfaceMethodDef) + void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation originalInterfaceImpl, MethodDefinition interfaceMethodDef) { // Go over all interfaces, trying to find a method that is an explicit MethodImpl of the // interface method in question. @@ -348,9 +348,6 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o if (potentialImplInterface is null) continue; - // We go by MethodDefinition here and may miss the correct implementation by exiting early after finding one. - // To correct for this, we'll look for all possible implementations and add them all if the inflatedIntefaceMethodReference is a genericInstance - // Otherwise, we're good to stop looking higher in the hierarchy after the first. Note we still need to look at other branches of the hierarchy to find any diamond cases. bool foundImpl = false; foreach (var potentialImplMethod in potentialImplInterface.Methods) { if (potentialImplMethod == interfaceMethodDef && diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/BaseProvidesInterfaceMethod.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/BaseProvidesInterfaceMethod.cs index 081430458926b..6fe4ac5d8fd55 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/BaseProvidesInterfaceMethod.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/StaticInterfaceMethods/BaseProvidesInterfaceMethod.cs @@ -11,6 +11,7 @@ class BaseProvidesInterfaceMethod public static void Main () { CallMethod (); + CallMN (); } [Kept] @@ -38,5 +39,40 @@ class Base // Compiler generates private explicit implementation that calls Base.Method() class Derived : Base, IFoo { } + + [Kept] + static void CallMN () where T : I + { + T.M (); + T.N (); + } + + [Kept] + interface I + { + [Kept] + static abstract string M (); + [Kept] + static abstract string N (); + } + + [Kept] + [KeptInterface (typeof (I))] + class B : I + { + [Kept] + public static string M () => "B.M"; + [Kept] + static string I.N () => "B's I.N"; + } + + [Kept] + [KeptBaseType (typeof (B))] + [KeptInterface (typeof (I))] + class D : B, I + { + [Kept] + static string I.N () => "D's I.N"; + } } } From dec9745975ecee04155c0cc3d85bd41753558086 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:27:43 -0700 Subject: [PATCH 14/27] Mark all implementation chains for interface impls - All chains of interfaceImpls are tracked in a RuntimeInterfaceImplementation - Reused previously calculated runtime interfaces of base types and explicit interfaces and inflates the relevant types to calculate the types of derived/implementing types. - Updates tests with new behavior. Many cases where interfaces were swept are now marked as a result of marking all paths to an interface. - This implementation will blow up if there are ever cycles in a type hierarchy. --- .../src/linker/Linker.Steps/MarkStep.cs | 28 +++---- .../illink/src/linker/Linker/Annotations.cs | 5 ++ .../Linker/InterfaceImplementationChain.cs | 49 ++++++++++++ .../illink/src/linker/Linker/LinkContext.cs | 2 +- .../src/linker/Linker/OverrideInformation.cs | 2 +- .../Linker/RuntimeInterfaceImplementation.cs | 74 ++++++++++++++----- .../illink/src/linker/Linker/TypeMapInfo.cs | 71 ++++++++---------- .../DataFlow/GenericParameterDataFlow.cs | 2 +- .../UnusedDefaultInterfaceImplementation.cs | 4 + ...icInterfaceWithMethodManyBaseInterfaces.cs | 9 ++- ...ClassImplementingInterfaceMethodsNested.cs | 5 +- .../NestedInterfaces1.cs | 3 +- .../NestedInterfaces2.cs | 6 +- .../NestedInterfaces3.cs | 7 +- ...tructImplementingInterfaceMethodsNested.cs | 5 +- .../RecursiveInterfaceKept.cs | 8 +- 16 files changed, 195 insertions(+), 85 deletions(-) create mode 100644 src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 9ac54b5f36d81..dde982e81ab0d 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -160,6 +160,7 @@ internal DynamicallyAccessedMembersTypeHierarchy DynamicallyAccessedMembersTypeH DependencyKind.ReturnTypeMarshalSpec, DependencyKind.DynamicInterfaceCastableImplementation, DependencyKind.XmlDescriptor, + DependencyKind.InterfaceImplementationOnType, }; static readonly DependencyKind[] _methodReasons = new DependencyKind[] { @@ -712,13 +713,6 @@ void MarkMethodIfNeededByBaseMethod (MethodDefinition method, MessageOrigin orig } } - /// - /// 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 - /// - 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 @@ -2413,13 +2407,12 @@ void MarkInterfaceImplementations (TypeDefinition type) internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation) { - var type = runtimeInterfaceImplementation.Implementor; - var ifaces = runtimeInterfaceImplementation.InterfaceImplementation; + var implementinType = runtimeInterfaceImplementation.Implementor; var ifaceType = runtimeInterfaceImplementation.InterfaceTypeDefinition; - if (ifaces.All (Annotations.IsMarked)) + if (Annotations.IsMarked (runtimeInterfaceImplementation)) return false; - if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, type)) + if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, implementinType)) return true; if (ifaceType is null) @@ -2433,7 +2426,7 @@ internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplemen if (Context.KeepComInterfaces && (ifaceType.IsImport || ifaceType.IsWindowsRuntime)) return true; - return IsFullyPreserved (type); + return IsFullyPreserved (implementinType); } void MarkGenericParameterProvider (IGenericParameterProvider provider, MessageOrigin origin) @@ -2519,7 +2512,7 @@ bool IsInterfaceImplementationMethodNeededByTypeDueToInterface (OverrideInformat // If the interface implementation is not marked, do not mark the implementation method // A type that doesn't implement the interface isn't required to have methods that implement the interface. // We must check all possible ways the interface could be implemented by the type (through all recursive interface implementations, not just the primary one) - if (!(IsInterfaceImplementationMarkedRecursively (overrideInformation.RuntimeInterfaceImplementation.Implementor, @base.DeclaringType))) + if (!overrideInformation.RuntimeInterfaceImplementation.IsAnyImplementationMarked (Annotations, Context)) return false; // If the interface method is not marked and the interface doesn't come from a preserved scope, do not mark the implementation method @@ -2613,7 +2606,7 @@ void MarkICustomMarshalerMethods (TypeDefinition inputType, in DependencyInfo re continue; MarkMethodsIf (iface_type!.Methods, m => !m.IsStatic, reason, origin); - MarkRuntimeInterfaceImplementation (runtimeInterface, new MessageOrigin (Context.Resolve (runtimeInterface.TypeWithInterfaceImplementation))); + MarkRuntimeInterfaceImplementation (runtimeInterface, new MessageOrigin (inputType)); } } @@ -3748,8 +3741,11 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio void MarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterface, MessageOrigin origin, DependencyInfo? reason = null) { - foreach (var iface in runtimeInterface.InterfaceImplementation) { - MarkInterfaceImplementation (iface, origin, reason); + Annotations.Mark (runtimeInterface); + foreach (var iface in runtimeInterface.InterfaceImplementationChains) { + MarkType (iface.TypeWithInterfaceImplementation, reason ?? new DependencyInfo (DependencyKind.InterfaceImplementationOnType, origin.Provider), origin); + foreach (var impl in iface.InterfaceImplementations) + MarkInterfaceImplementation (impl, origin, reason); } } diff --git a/src/tools/illink/src/linker/Linker/Annotations.cs b/src/tools/illink/src/linker/Linker/Annotations.cs index cfcf350bb1804..5d6c4c68ed4ea 100644 --- a/src/tools/illink/src/linker/Linker/Annotations.cs +++ b/src/tools/illink/src/linker/Linker/Annotations.cs @@ -72,6 +72,7 @@ public partial class AnnotationStore protected readonly HashSet indirectly_called = new HashSet (); protected readonly HashSet types_relevant_to_variant_casting = new HashSet (); readonly HashSet reflection_used = new (); + readonly HashSet _marked_runtime_interface_implementations = new (); public AnnotationStore (LinkContext context) { @@ -723,5 +724,9 @@ public void EnqueueVirtualMethod (MethodDefinition method) { return TypeMapInfo.GetRecursiveInterfaces (type); } + + internal bool IsMarked (RuntimeInterfaceImplementation runtimeInterface) => _marked_runtime_interface_implementations.Contains (runtimeInterface); + + internal void Mark (RuntimeInterfaceImplementation runtimeInterface) => _marked_runtime_interface_implementations.Add (runtimeInterface); } } diff --git a/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs b/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs new file mode 100644 index 0000000000000..dda9dbe78591b --- /dev/null +++ b/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs @@ -0,0 +1,49 @@ +// 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.Collections.Immutable; +using System.Diagnostics; +using Mono.Cecil; + +namespace Mono.Linker +{ + internal sealed class InterfaceImplementationChain + { + /// + /// The type that has the InterfaceImplementation - either the or a base type of it. + /// + public TypeReference TypeWithInterfaceImplementation { get; } + + /// + /// The path of .interfaceimpl on or a base type that terminates with . + /// + public ImmutableArray InterfaceImplementations { get; } + + public InterfaceImplementationChain (TypeReference typeWithInterfaceImplementation, ImmutableArray interfaceImplementation) + { + TypeWithInterfaceImplementation = typeWithInterfaceImplementation; + InterfaceImplementations = interfaceImplementation; + } + + /// + /// Returns true if all the .interfaceImpls in the chain and the are marked. + /// + /// + /// + public bool IsMarked (AnnotationStore annotations, ITryResolveMetadata context) + { + var typeDef = context.TryResolve (TypeWithInterfaceImplementation); + // If we have the .interfaceImpls on this type, it must be resolvable + Debug.Assert (typeDef is not null); + if (!annotations.IsMarked (typeDef)) + return false; + + foreach (var impl in InterfaceImplementations) { + if (!annotations.IsMarked (impl)) + return false; + } + + return true; + } + } +} diff --git a/src/tools/illink/src/linker/Linker/LinkContext.cs b/src/tools/illink/src/linker/Linker/LinkContext.cs index 9e6ec519d3054..1c48f91c43305 100644 --- a/src/tools/illink/src/linker/Linker/LinkContext.cs +++ b/src/tools/illink/src/linker/Linker/LinkContext.cs @@ -199,7 +199,7 @@ internal TypeNameResolver TypeNameResolver { public SerializationMarker SerializationMarker { get; } public LinkContext (Pipeline pipeline, ILogger logger, string outputDirectory) - : this(pipeline, logger, outputDirectory, new UnintializedContextFactory ()) + : this (pipeline, logger, outputDirectory, new UnintializedContextFactory ()) { } diff --git a/src/tools/illink/src/linker/Linker/OverrideInformation.cs b/src/tools/illink/src/linker/Linker/OverrideInformation.cs index 6eeba6e9e41aa..f4f667b20b2b2 100644 --- a/src/tools/illink/src/linker/Linker/OverrideInformation.cs +++ b/src/tools/illink/src/linker/Linker/OverrideInformation.cs @@ -30,7 +30,7 @@ internal OverrideInformation (MethodDefinition @base, MethodDefinition @override } public InterfaceImplementation? MatchingInterfaceImplementation - => RuntimeInterfaceImplementation?.InterfaceImplementation[0]; + => RuntimeInterfaceImplementation?.InterfaceImplementationChains[0].InterfaceImplementations[0]; public TypeDefinition? InterfaceType => RuntimeInterfaceImplementation?.InterfaceTypeDefinition; diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs index 292e0da0bd426..e7916e2011c41 100644 --- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs @@ -27,35 +27,75 @@ internal sealed class RuntimeInterfaceImplementation public TypeDefinition Implementor { get; } /// - /// The path of .interfaceimpl on or a base type that terminates with . + /// All the chains of .interfaceImpls that cause to implement /// - public ImmutableArray InterfaceImplementation { get; } + public ImmutableArray InterfaceImplementationChains { get; } /// - /// The type that has the InterfaceImplementation - either the or a base type of it. - /// - public TypeReference TypeWithInterfaceImplementation { get; } - - /// - /// The type of the interface that is implemented by . - /// This type may be different from the corresponding InterfaceImplementation.InterfaceType if it is generic. - /// Generic parameters are replaces with generic arguments from the implementing type. - /// Because of this, do not use this for comparisons or resolving. Use or .Last().InterfaceType instead. + /// The inflated interface type reference that is implemented by . /// public TypeReference InflatedInterfaceType { get; } /// - /// The resolved definition of the interface type implemented by . + /// The of /// public TypeDefinition? InterfaceTypeDefinition { get; } - public RuntimeInterfaceImplementation (TypeDefinition implementor, TypeReference typeWithFirstIfaceImpl, IEnumerable interfaceImplementation, TypeReference inflatedInterfaceType, LinkContext resolver) + public RuntimeInterfaceImplementation (TypeDefinition implementor, TypeReference interfaceType, TypeDefinition? interfaceTypeDefinition, IEnumerable interfaceImplementations) { Implementor = implementor; - TypeWithInterfaceImplementation = typeWithFirstIfaceImpl; - InterfaceImplementation = interfaceImplementation.ToImmutableArray (); - InflatedInterfaceType = inflatedInterfaceType; - InterfaceTypeDefinition = resolver.Resolve (InterfaceImplementation[InterfaceImplementation.Length - 1].InterfaceType); + InterfaceImplementationChains = interfaceImplementations.ToImmutableArray (); + InflatedInterfaceType = interfaceType; + InterfaceTypeDefinition = interfaceTypeDefinition; + } + + public bool IsAnyImplementationMarked (AnnotationStore annotations, ITryResolveMetadata context) + { + if (annotations.IsMarked (this)) + return true; + foreach (var chain in InterfaceImplementationChains) { + if (chain.IsMarked (annotations, context)) { + return true; + } + } + return false; + } + + /// + /// Returns a list of InterfaceImplementationChains for a derived type of . + /// + public IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainsForDerivedType (TypeReference baseTypeRef, ITryResolveMetadata context) + { + // This is only valid for classes + Debug.Assert (Implementor.IsClass); + Debug.Assert (Implementor == context.TryResolve (baseTypeRef)); + + var inflatedInterfaceType = InflatedInterfaceType.TryInflateFrom (baseTypeRef, context); + Debug.Assert (inflatedInterfaceType is not null); + + foreach (var impl in InterfaceImplementationChains) { + var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (baseTypeRef, context); + Debug.Assert (inflatedImplProvider is not null); + + yield return (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations)); + } + } + + /// + /// Returns a list of InterfaceImplementationChains for a type that has an explicit implementation of . + /// + public IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainForImplementingType (TypeDefinition typeThatImplementsImplementor, InterfaceImplementation impl, ITryResolveMetadata context) + { + Debug.Assert (Implementor.IsInterface); + Debug.Assert (typeThatImplementsImplementor.Interfaces.Contains (impl)); + Debug.Assert (context.TryResolve (impl.InterfaceType) == Implementor); + + var inflatedInterfaceType = InflatedInterfaceType.TryInflateFrom (impl.InterfaceType, context); + Debug.Assert (inflatedInterfaceType is not null); + + foreach (var existingImpl in InterfaceImplementationChains) { + yield return (inflatedInterfaceType, new InterfaceImplementationChain (typeThatImplementsImplementor, existingImpl.InterfaceImplementations.Insert (0, impl))); + } } } } diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index a43f37fdd6bd8..61f788806f314 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -112,9 +112,7 @@ internal void AddOverride (MethodDefinition @base, MethodDefinition @override, R Dictionary> _runtimeInterfaceImpls = new (); protected virtual void MapType (TypeDefinition type) { - var runtimeInterfaces = GetRecursiveInterfaceImplementations (type); - if (runtimeInterfaces is not null) - _runtimeInterfaceImpls[type] = runtimeInterfaces.Value; + _runtimeInterfaceImpls[type] = GetRecursiveInterfaceImplementations (type); MapVirtualMethods (type); MapInterfaceMethodsInTypeHierarchy (type); @@ -133,53 +131,48 @@ protected virtual void MapType (TypeDefinition type) return null; } - ImmutableArray? GetRecursiveInterfaceImplementations (TypeDefinition originalType) + + ImmutableArray GetRecursiveInterfaceImplementations (TypeDefinition originalType) { - // We'll need at least this many interfaces, so let's preallocate - ImmutableArray.Builder runtimeIfaces = ImmutableArray.CreateBuilder (originalType.Interfaces.Count); + if (_runtimeInterfaceImpls.TryGetValue (originalType, out var runtimeIfaces)) { + return runtimeIfaces; + } - List interfaces = new List (16); - AddRecursiveInterfaces (originalType, interfaces); + Dictionary> interfaceTypeToImplChainMap = new (new TypeReferenceEqualityComparer (context)); - if (runtimeIfaces.Count == 0) - return null; - return runtimeIfaces.ToImmutable (); + foreach (var explicitIface in originalType.Interfaces) { + // Add runtimeIface for this Iface + var resolvedInterfaceType = context.TryResolve (explicitIface.InterfaceType); + interfaceTypeToImplChainMap.AddToList (explicitIface.InterfaceType, new InterfaceImplementationChain (originalType, [explicitIface])); - void AddRecursiveInterfaces (TypeReference typeRef, List pathToType) - { - var type = context.TryResolve (typeRef); - if (type is null) - return; - // Get all explicit interfaces of this type - foreach (var iface in type.Interfaces) { - var interfaceType = iface.InterfaceType.TryInflateFrom (typeRef, context); - if (interfaceType is null) { - continue; - } - if (!runtimeIfaces.Any (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceType, context))) { - pathToType.Add (iface); - runtimeIfaces.Add (new RuntimeInterfaceImplementation (originalType, typeRef, pathToType.Append (iface), interfaceType, context)); - pathToType.RemoveAt (pathToType.Count - 1); - } + if (resolvedInterfaceType is null) { + continue; } - if (type.BaseType is not null) { - var inflatedBaseType = type.BaseType.TryInflateFrom (typeRef, context)!; - AddRecursiveInterfaces (inflatedBaseType, []); + var recursiveIFaces = GetRecursiveInterfaceImplementations (resolvedInterfaceType); + + foreach (var recursiveIface in recursiveIFaces) { + var impls = recursiveIface.CreateImplementationChainForImplementingType (originalType, explicitIface, context); + foreach (var impl in impls) { + interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain); + } } + } - // Recursive interfaces after all direct interfaces to preserve Inherit/Implement tree order - foreach (var iface in type.Interfaces) { - // If we can't resolve the interface type we can't find recursive interfaces - var ifaceDirectlyOnType = iface.InterfaceType.TryInflateFrom (typeRef, context); - if (ifaceDirectlyOnType is null) { - continue; + if (originalType.BaseType is not null && context.TryResolve (originalType.BaseType) is { } baseTypeDef) { + var baseTypeIfaces = GetRecursiveInterfaceImplementations (baseTypeDef); + foreach (var recursiveIface in baseTypeIfaces) { + var impls = recursiveIface.CreateImplementationChainsForDerivedType (originalType.BaseType, context); + foreach (var impl in impls) { + interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain); } - pathToType.Add (iface); - AddRecursiveInterfaces (ifaceDirectlyOnType, pathToType); - pathToType.RemoveAt (pathToType.Count - 1); } } + + if (interfaceTypeToImplChainMap.Count == 0) + return ImmutableArray.Empty; + + return interfaceTypeToImplChainMap.Select (kvp => new RuntimeInterfaceImplementation (originalType, kvp.Key, context.Resolve (kvp.Key), kvp.Value)).ToImmutableArray (); } void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs index 2cbd62f6d4343..c36de63d7f892 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs @@ -915,7 +915,7 @@ static void TestGenericParameterFlowsToDelegateMethodDeclaringTypeInstance () Action a = instance.InstanceMethod; } - class DelegateMethodTypeRequiresFields<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T> + class DelegateMethodTypeRequiresFields<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] T> { public static void Method () { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/UnusedDefaultInterfaceImplementation.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/UnusedDefaultInterfaceImplementation.cs index 23b029f2a3477..b89a50708476c 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/UnusedDefaultInterfaceImplementation.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/DefaultInterfaceMethods/UnusedDefaultInterfaceImplementation.cs @@ -20,8 +20,11 @@ interface IFoo void InterfaceMethod (); } + [Kept] // Should be removable + [KeptInterface (typeof (IFoo))] interface IDefaultImpl : IFoo { + [Kept] void IFoo.InterfaceMethod () { } @@ -29,6 +32,7 @@ void IFoo.InterfaceMethod () [Kept] [KeptInterface (typeof (IFoo))] + [KeptInterface (typeof (IDefaultImpl))] // Should be removable class Foo : IDefaultImpl { [Kept] diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithMethodManyBaseInterfaces.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithMethodManyBaseInterfaces.cs index f9b338ead868e..c85b9871a2f58 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithMethodManyBaseInterfaces.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/GenericInterfaceWithMethodManyBaseInterfaces.cs @@ -17,10 +17,15 @@ interface IFoo void Method (T arg); } + [Kept] // Should be able to be removed + [KeptInterface (typeof (IFoo<>))] interface IFoo2 : IFoo { } + [Kept] // Should be able to be removed + [KeptInterface (typeof (IFoo<>))] + [KeptInterface (typeof (IFoo2<>))] interface IFoo3 : IFoo2 { } @@ -39,8 +44,10 @@ public void Method (object arg) [KeptMember (".ctor()")] [KeptBaseType (typeof (BaseFoo))] [KeptInterface (typeof (IFoo))] + [KeptInterface (typeof (IFoo2))] // Should be removable + [KeptInterface (typeof (IFoo3))] // Should be removable class FooWithBase : BaseFoo, IFoo3 { } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/ClassImplementingInterfaceMethodsNested.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/ClassImplementingInterfaceMethodsNested.cs index c449911f6e3b4..14b2a7c76ce6b 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/ClassImplementingInterfaceMethodsNested.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/ClassImplementingInterfaceMethodsNested.cs @@ -17,6 +17,8 @@ interface IFoo void Foo (); } + [Kept] + [KeptInterface (typeof (IFoo))] interface IBar : IFoo { void Bar (); @@ -25,6 +27,7 @@ interface IBar : IFoo [Kept] [KeptMember (".ctor()")] [KeptInterface (typeof (IFoo))] + [KeptInterface (typeof (IBar))] class A : IBar { [Kept] @@ -37,4 +40,4 @@ public void Bar () } } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces1.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces1.cs index 434a4174bbea6..9101c793b9617 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces1.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces1.cs @@ -46,6 +46,7 @@ interface IBase3 : IBase2 /// [Kept] [KeptInterface (typeof (IBase3))] + [KeptInterface (typeof (IBase2))] // Should be removable [KeptInterface (typeof (IBase))] class Foo : IBase3 { @@ -55,4 +56,4 @@ public void Method () } } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces2.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces2.cs index 93f50accac117..a23078cd33c29 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces2.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces2.cs @@ -25,6 +25,8 @@ interface IBase { } + [Kept] // Should be removable + [KeptInterface (typeof (IBase))] interface IBase2 : IBase { void Method (); @@ -32,12 +34,14 @@ interface IBase2 : IBase [Kept] [KeptInterface (typeof (IBase))] + [KeptInterface (typeof (IBase2))] // Should be removable interface IBase3 : IBase2 { } [Kept] [KeptInterface (typeof (IBase3))] + [KeptInterface (typeof (IBase2))] // Should be removable [KeptInterface (typeof (IBase))] class Foo : IBase3 { @@ -46,4 +50,4 @@ public void Method () } } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces3.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces3.cs index 5bc09dc62f22b..40f259dd79da9 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces3.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/NoKeptCtorButInterfaceNeeded/NestedInterfaces3.cs @@ -25,6 +25,8 @@ interface IBase { } + [Kept] // Should be removable + [KeptInterface (typeof (IBase))] interface IBase2 : IBase { void Method (); @@ -32,12 +34,14 @@ interface IBase2 : IBase [Kept] [KeptInterface (typeof (IBase))] + [KeptInterface (typeof (IBase2))] // Should be removable interface IBase3 : IBase2 { } [Kept] [KeptInterface (typeof (IBase))] + [KeptInterface (typeof (IBase2))] // Should be removable class Base : IBase2 { public void Method () @@ -48,6 +52,7 @@ public void Method () [Kept] [KeptBaseType (typeof (Base))] [KeptInterface (typeof (IBase3))] + [KeptInterface (typeof (IBase2))] // Should be removable [KeptInterface (typeof (IBase))] class Foo : Base, IBase3, IBase2 { @@ -56,4 +61,4 @@ public void Method () } } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnValueType/StructImplementingInterfaceMethodsNested.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnValueType/StructImplementingInterfaceMethodsNested.cs index 8e438aeda7c8e..8f685def3fa92 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnValueType/StructImplementingInterfaceMethodsNested.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnValueType/StructImplementingInterfaceMethodsNested.cs @@ -17,6 +17,8 @@ interface IFoo void Foo (); } + [Kept] + [KeptInterface (typeof (IFoo))] interface IBar : IFoo { void Bar (); @@ -24,6 +26,7 @@ interface IBar : IFoo [Kept] [KeptInterface (typeof (IFoo))] + [KeptInterface (typeof (IBar))] struct A : IBar { [Kept] @@ -36,4 +39,4 @@ public void Bar () } } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveInterfaceKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveInterfaceKept.cs index 56e08d67be74f..833ace2676c41 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveInterfaceKept.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveInterfaceKept.cs @@ -20,13 +20,13 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (Library.I0100), "library.dll", typeof (Library.I010))] [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (Library.I010), "library.dll", typeof (Library.I01))] [KeptInterfaceOnTypeInAssembly ("library.dll", typeof (Library.I01), "library.dll", typeof (Library.I0))] - [RemovedTypeInAssembly("library.dll", typeof(Library.I00))] - [RemovedTypeInAssembly("library.dll", typeof(Library.I000))] - [RemovedInterfaceOnTypeInAssembly("library.dll", typeof (Library.MyClass), "library.dll", typeof (Library.I000))] + [KeptTypeInAssembly("library.dll", typeof(Library.I00))] + [KeptTypeInAssembly("library.dll", typeof(Library.I000))] + [KeptInterfaceOnTypeInAssembly("library.dll", typeof (Library.MyClass), "library.dll", typeof (Library.I000))] #endif public class RecursiveInterfaceKept { - public static void Main() + public static void Main () { #if IL_ASSEMBLY_AVAILABLE Library.I0 _ = new Library.MyClass(); From b6f101a0f1fb3845db8ff76283a6a5336bf515dd Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 28 Jun 2024 10:34:45 -0700 Subject: [PATCH 15/27] Add runtimeInterface assertions to tests --- .../Linker/MethodReferenceExtensions.cs | 4 +- .../illink/src/linker/Linker/TypeMapInfo.cs | 9 +- .../Assertions/BaseTypeMapInfoAttribute.cs | 7 + .../HasRuntimeInterfaceAttribute.cs | 23 ++ .../RuntimeInterfaceOnTypeInAssembly.cs | 15 + .../OverrideOfRecursiveInterfaceIsRemoved.cs | 3 + .../TestCasesRunner/AssemblyChecker.cs | 74 +++- .../TestCasesRunner/ResultChecker.cs | 372 ++++++++++-------- .../TestCasesRunner/TypeMapInfoValidation.cs | 15 + .../Trimming.Tests.Shared/TestResolver.cs | 8 +- 10 files changed, 347 insertions(+), 183 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseTypeMapInfoAttribute.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/HasRuntimeInterfaceAttribute.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RuntimeInterfaceOnTypeInAssembly.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs diff --git a/src/tools/illink/src/linker/Linker/MethodReferenceExtensions.cs b/src/tools/illink/src/linker/Linker/MethodReferenceExtensions.cs index 4240d3277a002..fe2c58e821ef2 100644 --- a/src/tools/illink/src/linker/Linker/MethodReferenceExtensions.cs +++ b/src/tools/illink/src/linker/Linker/MethodReferenceExtensions.cs @@ -71,7 +71,7 @@ public static string GetDisplayName (this MethodReference method) return sb.ToString (); } - public static TypeReference? GetReturnType (this MethodReference method, LinkContext context) + internal static TypeReference? GetReturnType (this MethodReference method, ITryResolveMetadata context) { if (method.DeclaringType is GenericInstanceType genericInstance) return TypeReferenceExtensions.InflateGenericType (genericInstance, method.ReturnType, context); @@ -84,7 +84,7 @@ public static bool ReturnsVoid (this IMethodSignature method) return method.ReturnType.WithoutModifiers ().MetadataType == MetadataType.Void; } - public static TypeReference? GetInflatedParameterType (this MethodReference method, int parameterIndex, LinkContext context) + internal static TypeReference? GetInflatedParameterType (this MethodReference method, int parameterIndex, ITryResolveMetadata context) { #pragma warning disable RS0030 // MethodReference.Parameters is banned -- it's best to leave this as is for now if (method.DeclaringType is GenericInstanceType genericInstance) diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index 61f788806f314..eba929d5fdeb2 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -41,7 +41,7 @@ namespace Mono.Linker public class TypeMapInfo { readonly HashSet assemblies = new HashSet (); - readonly LinkContext context; + readonly ITryResolveMetadata context; protected readonly Dictionary> base_methods = new Dictionary> (); protected readonly Dictionary> override_methods = new Dictionary> (); protected readonly Dictionary> default_interface_implementations = new Dictionary> (); @@ -51,6 +51,11 @@ public TypeMapInfo (LinkContext context) this.context = context; } + internal TypeMapInfo (ITryResolveMetadata context) + { + this.context = context; + } + public void EnsureProcessed (AssemblyDefinition assembly) { if (!assemblies.Add (assembly)) @@ -172,7 +177,7 @@ ImmutableArray GetRecursiveInterfaceImplementati if (interfaceTypeToImplChainMap.Count == 0) return ImmutableArray.Empty; - return interfaceTypeToImplChainMap.Select (kvp => new RuntimeInterfaceImplementation (originalType, kvp.Key, context.Resolve (kvp.Key), kvp.Value)).ToImmutableArray (); + return interfaceTypeToImplChainMap.Select (kvp => new RuntimeInterfaceImplementation (originalType, kvp.Key, context.TryResolve (kvp.Key), kvp.Value)).ToImmutableArray (); } void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseTypeMapInfoAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseTypeMapInfoAttribute.cs new file mode 100644 index 0000000000000..a9bcbcd5b341e --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/BaseTypeMapInfoAttribute.cs @@ -0,0 +1,7 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + public class BaseTypeMapInfoAttribute : BaseExpectedLinkedBehaviorAttribute { } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/HasRuntimeInterfaceAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/HasRuntimeInterfaceAttribute.cs new file mode 100644 index 0000000000000..8d77a80e146a7 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/HasRuntimeInterfaceAttribute.cs @@ -0,0 +1,23 @@ +// 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; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + [AttributeUsage (AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class HasRuntimeInterfaceAttribute : Attribute + { + public HasRuntimeInterfaceAttribute (Type interfaceType, params Type[] implementationChains) + { + } + + public HasRuntimeInterfaceAttribute (string interfaceType, params string[] implementationChains) + { + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RuntimeInterfaceOnTypeInAssembly.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RuntimeInterfaceOnTypeInAssembly.cs new file mode 100644 index 0000000000000..b0425ffc221d9 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/RuntimeInterfaceOnTypeInAssembly.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.Class, AllowMultiple = true, Inherited = false)] + public class RuntimeInterfaceOnTypeInAssembly : BaseTypeMapInfoAttribute + { + public RuntimeInterfaceOnTypeInAssembly (string dllName, string type, string interfaceType, params string[] implementationChains) + { + } + } +} 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 index 4beafaa1d9d96..6ef9d8848beef 100644 --- 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 @@ -30,6 +30,9 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces [RemovedOverrideOnMethodInAssembly ("library.dll", "Program/A", "O", "System.Void Program/IMiddleUnused::O()")] [RemovedMemberInAssembly ("library.dll", "Program/IBaseUnused", "M()")] [RemovedMemberInAssembly ("library.dll", "Program/IMiddleUnused", "O()")] + [RuntimeInterfaceOnTypeInAssembly ("library.dll", "Program/A", "Program/IDerived", new string[] { "Program/IDerived" })] + [RuntimeInterfaceOnTypeInAssembly ("library.dll", "Program/A", "Program/IBaseUsed", new string[] { "Program/IDerived", "Program/IMiddleUnused", "Program/IBaseUsed" })] + [RuntimeInterfaceOnTypeInAssembly ("library.dll", "Program/A", "Program/IBaseUnused", new string[] { "Program/IDerived", "Program/IMiddleUnused", "Program/IBaseUnused" })] public class OverrideOfRecursiveInterfaceIsRemoved { [Kept] 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 7d984ef2e4863..791d63b5de38e 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -17,6 +17,7 @@ namespace Mono.Linker.Tests.TestCasesRunner partial class AssemblyChecker { readonly AssemblyDefinition originalAssembly, linkedAssembly; + TypeMapInfo _originalTypeMapInfo = new TypeMapInfo (new TestResolver ()); readonly TrimmedTestCaseResult linkedTestCase; HashSet linkedMembers; @@ -43,6 +44,8 @@ public void Verify () IEnumerable GetFailures () { + foreach (var err in VerifyTypeMapInfo (originalAssembly)) + yield return err; foreach (var err in VerifyExportedTypes (originalAssembly, linkedAssembly)) yield return err; foreach (var err in VerifyCustomAttributes (originalAssembly, linkedAssembly)) @@ -99,6 +102,34 @@ IEnumerable GetFailures () } } + private IEnumerable VerifyTypeMapInfo (AssemblyDefinition originalAssembly) + { + foreach (var type in originalAssembly.AllDefinedTypes ()) { + foreach (var att in type.CustomAttributes) { + switch (att.AttributeType.Name) { + case nameof (HasRuntimeInterfaceAttribute): + var expectednterfaceTypeName = att.ConstructorArguments[0].Value as string ?? ((TypeReference) att.ConstructorArguments[0].Value).FullName; + var expectedInterfaceChains = ((CustomAttributeArgument[]) att.ConstructorArguments[1].Value).Select (t => t.Value as string ?? ((TypeReference) t.Value).FullName); + var runtimeIfaces = _originalTypeMapInfo.GetRecursiveInterfaces (type); + var matchingRuntimeIface = runtimeIfaces.FirstOrDefault (i => i.InterfaceType.FullName == expectednterfaceTypeName); + if (matchingRuntimeIface == default) { + yield return $"Type {type.FullName} does not have runtime interface {expectednterfaceTypeName}"; + continue; + } + + if (expectedInterfaceChains.Any ()) { + if (!expectedInterfaceChains.Zip (matchingRuntimeIface.ImplementationChain).All (pair => pair.First == pair.Second.InterfaceType.FullName)) { + yield return $"Type {type.FullName} does not have expected implementation chain for runtime interface {expectednterfaceTypeName}"; + } + } + break; + default: + break; + } + } + } + } + static bool IsBackingField (FieldDefinition field) => field.Name.StartsWith ("<") && field.Name.EndsWith (">k__BackingField"); protected virtual IEnumerable VerifyModule (ModuleDefinition original, ModuleDefinition linked) @@ -715,26 +746,26 @@ static string FormatInstruction (Instruction instr) throw new NotImplementedException (instr.Operand.GetType ().ToString ()); default: { - string operandString = null; - switch (instr.OpCode.OperandType) { - case OperandType.InlineField: - case OperandType.InlineMethod: - case OperandType.InlineType: - case OperandType.InlineTok: - operandString = instr.Operand switch { - FieldReference fieldRef => fieldRef.FullName, - MethodReference methodRef => methodRef.FullName, - TypeReference typeRef => typeRef.FullName, - _ => null - }; - break; - } - - if (operandString != null) - return $"{instr.OpCode.ToString ()} {operandString}"; - else - return instr.OpCode.ToString (); + string operandString = null; + switch (instr.OpCode.OperandType) { + case OperandType.InlineField: + case OperandType.InlineMethod: + case OperandType.InlineType: + case OperandType.InlineTok: + operandString = instr.Operand switch { + FieldReference fieldRef => fieldRef.FullName, + MethodReference methodRef => methodRef.FullName, + TypeReference typeRef => typeRef.FullName, + _ => null + }; + break; } + + if (operandString != null) + return $"{instr.OpCode.ToString ()} {operandString}"; + else + return instr.OpCode.ToString (); + } } } @@ -1186,7 +1217,7 @@ IEnumerable VerifyGenericParameterConstraints (GenericParameter src, Gen yield return $"Mismatch in generic parameter constraints on {src} of {src.Owner}. Input has constraints?: {src.HasConstraints}, Output has constraints?: {linked.HasConstraints}"; yield break; } - + if (!src.HasConstraints) yield break; @@ -1222,7 +1253,8 @@ IEnumerable VerifyGenericParameterConstraints (GenericParameter src, Gen yield return string.Join (Environment.NewLine, $"Custom attributes on `{src}' generic parameter constraints are not matching:", missing, extra); } - static bool IsKeptAttributeOnConstraint (CustomAttribute attr) { + static bool IsKeptAttributeOnConstraint (CustomAttribute attr) + { if (attr.AttributeType.Name != nameof (KeptAttributeOnConstraintAttribute)) return false; 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 f6480e554c352..4b84a0be17ca7 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -72,11 +72,11 @@ protected static void ValidateTypeRefsHaveValidAssemblyRefs (AssemblyDefinition } 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; - } + // 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}'"); } @@ -91,6 +91,9 @@ public virtual void Check (TrimmedTestCaseResult linkResult) var original = ResolveOriginalsAssembly (linkResult.ExpectationsAssemblyPath.FileNameWithoutExtension); VerifyExitCode (linkResult, original); + var errs = VerifyTypeMapOfOtherAssemblies (original); + if (errs.Any ()) + Assert.Fail ($"Errors found in type map validation:\n{string.Join ("\n", errs)}"); if (!HasAttribute (original, nameof (NoLinkedOutputAttribute))) { Assert.IsTrue (linkResult.OutputAssemblyPath.FileExists (), $"The linked output assembly was not found. Expected at {linkResult.OutputAssemblyPath}"); @@ -289,6 +292,45 @@ protected virtual void InitialChecking (TrimmedTestCaseResult linkResult, Assemb ValidateTypeRefsHaveValidAssemblyRefs (linked); } + IEnumerable VerifyTypeMapOfOtherAssemblies (AssemblyDefinition original) + { + var checks = BuildTypeMapInfoCheckTable (original); + List errs = []; + try { + foreach (var assemblyName in checks.Keys) { + var assembly = ResolveOriginalsAssembly (assemblyName); + foreach (var check in checks[assemblyName]) { + switch (check.AttributeType.Name) { + case nameof (RuntimeInterfaceOnTypeInAssembly): + object type = check.ConstructorArguments[1].Value; + TypeDefinition typeDef = GetOriginalTypeFromInAssemblyAttribute (assemblyName, type); + string interfaceName = (check.ConstructorArguments[2].Value as TypeReference)?.FullName ?? (string) check.ConstructorArguments[2].Value; + TypeMapInfo typeMapInfo = new TypeMapInfo (new TestResolver ()); + var runtimeInterfaces = typeMapInfo.GetRecursiveInterfaces (typeDef); + var runtimeInterface = runtimeInterfaces.SingleOrDefault (i => i.InterfaceType.FullName == interfaceName); + if (runtimeInterface == default) + errs.Add ($"Expected type `{typeDef}` to have runtime interface `{interfaceName}`"); + + var implementationChains = (check.ConstructorArguments[3].Value as CustomAttributeArgument[]) + .Select (caa => (caa.Value as TypeReference)?.FullName ?? (string) caa.Value); + if (implementationChains.Any ()) { + if (!implementationChains.SequenceEqual (runtimeInterface.ImplementationChain.Select (i => i.InterfaceType.FullName))) + errs.Add ($"Expected type `{typeDef}` to have interfaceImpl chain `{string.Join (", ", implementationChains)}` for interface `{interfaceName}`"); + } + + break; + default: + break; + } + } + + } + } catch (AssemblyResolutionException e) { + Assert.Fail ($"Failed to resolve linked assembly `{e.AssemblyReference.Name}`. It must not exist in any of the input directories:\n\t{_originalsResolver.GetSearchDirectories ().Aggregate ((buff, s) => $"{buff}\n\t{s}")}\n"); + } + return errs; + } + void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original) { var checks = BuildOtherAssemblyCheckTable (original); @@ -317,7 +359,7 @@ void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original) TypeReference linkedTypeRef = null; try { _linkedTypeNameResolver.TryResolveTypeName (linkedAssembly, expectedTypeName, out linkedTypeRef, out _); - } catch (AssemblyResolutionException) {} + } catch (AssemblyResolutionException) { } TypeDefinition linkedType = linkedTypeRef?.Resolve (); if (linkedType == null && linkedAssembly.MainModule.HasExportedTypes) { @@ -802,48 +844,48 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge switch (attr.AttributeType.Name) { case nameof (LogContainsAttribute): { - var expectedMessage = (string) attr.ConstructorArguments[0].Value; - - List matchedMessages; - if ((bool) attr.ConstructorArguments[1].Value) - matchedMessages = unmatchedMessages.Where (m => Regex.IsMatch (m.ToString (), expectedMessage)).ToList (); - else - matchedMessages = unmatchedMessages.Where (m => m.ToString ().Contains (expectedMessage)).ToList (); ; - if (matchedMessages.Count == 0) - missingMessageWarnings.Add ($"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}"); - - foreach (var matchedMessage in matchedMessages) - unmatchedMessages.Remove (matchedMessage); - } - break; + var expectedMessage = (string) attr.ConstructorArguments[0].Value; + + List matchedMessages; + if ((bool) attr.ConstructorArguments[1].Value) + matchedMessages = unmatchedMessages.Where (m => Regex.IsMatch (m.ToString (), expectedMessage)).ToList (); + else + matchedMessages = unmatchedMessages.Where (m => m.ToString ().Contains (expectedMessage)).ToList (); ; + if (matchedMessages.Count == 0) + missingMessageWarnings.Add ($"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}"); + + foreach (var matchedMessage in matchedMessages) + unmatchedMessages.Remove (matchedMessage); + } + break; case nameof (LogDoesNotContainAttribute): { - var unexpectedMessage = (string) attr.ConstructorArguments[0].Value; - foreach (var loggedMessage in unmatchedMessages) { - bool isRegex = (bool) attr.ConstructorArguments[1].Value; - bool foundMatch = isRegex - ? Regex.IsMatch (loggedMessage.ToString (), unexpectedMessage) - : loggedMessage.ToString ().Contains (unexpectedMessage); - - if (foundMatch) - unexpectedMessageWarnings.Add ($"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage.ToString ()}"); - } + var unexpectedMessage = (string) attr.ConstructorArguments[0].Value; + foreach (var loggedMessage in unmatchedMessages) { + bool isRegex = (bool) attr.ConstructorArguments[1].Value; + bool foundMatch = isRegex + ? Regex.IsMatch (loggedMessage.ToString (), unexpectedMessage) + : loggedMessage.ToString ().Contains (unexpectedMessage); + + if (foundMatch) + unexpectedMessageWarnings.Add ($"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage.ToString ()}"); } - break; + } + break; 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" }, _] + 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" }, _] @@ -851,143 +893,143 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge // 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"); - int? sourceColumn = (int?) attr.GetPropertyValue ("SourceColumn"); - bool? isCompilerGeneratedCode = (bool?) attr.GetPropertyValue ("CompilerGeneratedCode"); + _ => throw new UnreachableException (), + }; + string fileName = (string) attr.GetPropertyValue ("FileName"); + int? sourceLine = (int?) attr.GetPropertyValue ("SourceLine"); + int? sourceColumn = (int?) attr.GetPropertyValue ("SourceColumn"); + bool? isCompilerGeneratedCode = (bool?) attr.GetPropertyValue ("CompilerGeneratedCode"); - int expectedWarningCodeNumber = int.Parse (expectedWarningCode.Substring (2)); - string expectedOrigin = null; - bool expectedWarningFound = false; + int expectedWarningCodeNumber = int.Parse (expectedWarningCode.Substring (2)); + string expectedOrigin = null; + bool expectedWarningFound = false; - foreach (var loggedMessage in unmatchedMessages) { + foreach (var loggedMessage in unmatchedMessages) { - if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber) - continue; + if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber) + continue; - bool messageNotFound = false; - foreach (var expectedMessage in expectedMessageContains) { - if (!loggedMessage.Text.Contains (expectedMessage)) { - messageNotFound = true; - break; - } + bool messageNotFound = false; + foreach (var expectedMessage in expectedMessageContains) { + if (!loggedMessage.Text.Contains (expectedMessage)) { + messageNotFound = true; + break; } - if (messageNotFound) + } + if (messageNotFound) + continue; + + if (fileName != null) { + if (loggedMessage.Origin == null) continue; - if (fileName != null) { - if (loggedMessage.Origin == null) + var actualOrigin = loggedMessage.Origin.Value; + if (actualOrigin.FileName != null) { + // Note: string.Compare(string, StringComparison) doesn't exist in .NET Framework API set + if (actualOrigin.FileName.IndexOf (fileName, StringComparison.OrdinalIgnoreCase) < 0) continue; - var actualOrigin = loggedMessage.Origin.Value; - if (actualOrigin.FileName != null) { - // Note: string.Compare(string, StringComparison) doesn't exist in .NET Framework API set - if (actualOrigin.FileName.IndexOf (fileName, StringComparison.OrdinalIgnoreCase) < 0) - continue; - - if (sourceLine != null && loggedMessage.Origin?.SourceLine != sourceLine.Value) - continue; - - if (sourceColumn != null && loggedMessage.Origin?.SourceColumn != sourceColumn.Value) - continue; - } else { - // The warning was logged with member/ILoffset, so it didn't have line/column info filled - // but it will be computed from PDBs, so instead compare it in a string representation - if (expectedOrigin == null) { - expectedOrigin = fileName; - if (sourceLine.HasValue) { - expectedOrigin += "(" + sourceLine.Value; - if (sourceColumn.HasValue) - expectedOrigin += "," + sourceColumn.Value; - expectedOrigin += ")"; - } - } + if (sourceLine != null && loggedMessage.Origin?.SourceLine != sourceLine.Value) + continue; - string actualOriginString = actualOrigin.ToString () ?? ""; - if (!actualOriginString.EndsWith (expectedOrigin, StringComparison.OrdinalIgnoreCase)) - continue; + if (sourceColumn != null && loggedMessage.Origin?.SourceColumn != sourceColumn.Value) + continue; + } else { + // The warning was logged with member/ILoffset, so it didn't have line/column info filled + // but it will be computed from PDBs, so instead compare it in a string representation + if (expectedOrigin == null) { + expectedOrigin = fileName; + if (sourceLine.HasValue) { + expectedOrigin += "(" + sourceLine.Value; + if (sourceColumn.HasValue) + expectedOrigin += "," + sourceColumn.Value; + expectedOrigin += ")"; + } } - } else if (isCompilerGeneratedCode == true) { - if (loggedMessage.Origin?.Provider is not IMemberDefinition memberDefinition) + + string actualOriginString = actualOrigin.ToString () ?? ""; + if (!actualOriginString.EndsWith (expectedOrigin, StringComparison.OrdinalIgnoreCase)) continue; + } + } else if (isCompilerGeneratedCode == true) { + if (loggedMessage.Origin?.Provider is not IMemberDefinition memberDefinition) + continue; - if (attrProvider is IMemberDefinition expectedMember) { - string actualName = memberDefinition.DeclaringType.FullName + "." + memberDefinition.Name; + if (attrProvider is IMemberDefinition expectedMember) { + string actualName = memberDefinition.DeclaringType.FullName + "." + memberDefinition.Name; - if (actualName.StartsWith (expectedMember.DeclaringType.FullName) && - (actualName.Contains ("<" + expectedMember.Name + ">") || - actualName.EndsWith ("get_" + expectedMember.Name) || - actualName.EndsWith ("set_" + expectedMember.Name))) { + if (actualName.StartsWith (expectedMember.DeclaringType.FullName) && + (actualName.Contains ("<" + expectedMember.Name + ">") || + actualName.EndsWith ("get_" + expectedMember.Name) || + actualName.EndsWith ("set_" + expectedMember.Name))) { + expectedWarningFound = true; + unmatchedMessages.Remove (loggedMessage); + break; + } + if (memberDefinition is not MethodDefinition) + continue; + if (actualName.StartsWith (expectedMember.DeclaringType.FullName)) { + if (memberDefinition.Name == ".cctor" && + (expectedMember is FieldDefinition || expectedMember is PropertyDefinition)) { expectedWarningFound = true; unmatchedMessages.Remove (loggedMessage); break; } - if (memberDefinition is not MethodDefinition) - continue; - if (actualName.StartsWith (expectedMember.DeclaringType.FullName)) { - if (memberDefinition.Name == ".cctor" && - (expectedMember is FieldDefinition || expectedMember is PropertyDefinition)) { - expectedWarningFound = true; - unmatchedMessages.Remove (loggedMessage); - break; - } - if (memberDefinition.Name == ".ctor" && - (expectedMember is FieldDefinition || expectedMember is PropertyDefinition || memberDefinition.DeclaringType.FullName == expectedMember.FullName)) { - expectedWarningFound = true; - unmatchedMessages.Remove (loggedMessage); - break; - } - } - } else if (attrProvider is AssemblyDefinition expectedAssembly) { - // Allow assembly-level attributes to match warnings from compiler-generated Main - if (memberDefinition.Name == "
$" && - memberDefinition.DeclaringType.FullName == "Program" && - memberDefinition.DeclaringType.Module.Assembly.Name.Name == expectedAssembly.Name.Name) { + if (memberDefinition.Name == ".ctor" && + (expectedMember is FieldDefinition || expectedMember is PropertyDefinition || memberDefinition.DeclaringType.FullName == expectedMember.FullName)) { expectedWarningFound = true; unmatchedMessages.Remove (loggedMessage); break; } } - continue; - } else { - if (LogMessageHasSameOriginMember (loggedMessage, attrProvider)) { + } else if (attrProvider is AssemblyDefinition expectedAssembly) { + // Allow assembly-level attributes to match warnings from compiler-generated Main + if (memberDefinition.Name == "
$" && + memberDefinition.DeclaringType.FullName == "Program" && + memberDefinition.DeclaringType.Module.Assembly.Name.Name == expectedAssembly.Name.Name) { expectedWarningFound = true; unmatchedMessages.Remove (loggedMessage); break; } - continue; } - - expectedWarningFound = true; - unmatchedMessages.Remove (loggedMessage); - break; + continue; + } else { + if (LogMessageHasSameOriginMember (loggedMessage, attrProvider)) { + expectedWarningFound = true; + unmatchedMessages.Remove (loggedMessage); + break; + } + continue; } - var expectedOriginString = fileName == null - ? attrProvider switch { - MethodDefinition method => method.GetDisplayName (), - IMemberDefinition member => member.FullName, - AssemblyDefinition asm => asm.Name.Name, - _ => throw new NotImplementedException () - } + ": " - : ""; - - if (!expectedWarningFound) - missingMessageWarnings.Add ($"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" + - $"warning {expectedWarningCode}: {expectedOriginString}" + - $"and message containing {string.Join (" ", expectedMessageContains.Select (m => "'" + m + "'"))}, " + - $"but no such message was found."); + expectedWarningFound = true; + unmatchedMessages.Remove (loggedMessage); + break; } - break; + + var expectedOriginString = fileName == null + ? attrProvider switch { + MethodDefinition method => method.GetDisplayName (), + IMemberDefinition member => member.FullName, + AssemblyDefinition asm => asm.Name.Name, + _ => throw new NotImplementedException () + } + ": " + : ""; + + if (!expectedWarningFound) + missingMessageWarnings.Add ($"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" + + $"warning {expectedWarningCode}: {expectedOriginString}" + + $"and message containing {string.Join (" ", expectedMessageContains.Select (m => "'" + m + "'"))}, " + + $"but no such message was found."); + } + break; case nameof (ExpectedNoWarningsAttribute): { - // Postpone processing of negative checks, to make it possible to mark some warnings as expected (will be removed from the list above) - // and then do the negative check on the rest. - expectedNoWarningsAttributes.Add ((attrProvider, attr)); - break; - } + // Postpone processing of negative checks, to make it possible to mark some warnings as expected (will be removed from the list above) + // and then do the negative check on the rest. + expectedNoWarningsAttributes.Add ((attrProvider, attr)); + break; + } } } } @@ -1211,18 +1253,38 @@ protected TypeDefinition GetOriginalTypeFromInAssemblyAttribute (string assembly return originalType; } + + bool IsTypeInOtherAssemblyAssertion (CustomAttribute attr) + { + if (false == attr.AttributeType.Resolve ()?.DerivesFrom (nameof (BaseInAssemblyAttribute))) + return false; + + Tool? toolTarget = (Tool?) (int?) attr.GetPropertyValue ("Tool"); + if (toolTarget is not null && !toolTarget.Value.HasFlag (Tool.Trimmer)) + return false; + + return true; + } + + bool IsTypeMapInfoInOtherAssemblyAssertion (CustomAttribute attr) + { + return attr.AttributeType.Resolve ()?.DerivesFrom (nameof (BaseTypeMapInfoAttribute)) ?? false; + } + + Dictionary> BuildTypeMapInfoCheckTable (AssemblyDefinition original) + => BuildChecksTable (original, IsTypeMapInfoInOtherAssemblyAssertion); + Dictionary> BuildOtherAssemblyCheckTable (AssemblyDefinition original) + => BuildChecksTable (original, IsTypeInOtherAssemblyAssertion); + + Dictionary> BuildChecksTable (AssemblyDefinition original, Func predicate) { var checks = new Dictionary> (); foreach (var typeWithRemoveInAssembly in original.AllDefinedTypes ()) { - foreach (var attr in typeWithRemoveInAssembly.CustomAttributes.Where (IsTypeInOtherAssemblyAssertion)) { - var assemblyName = (string) attr.ConstructorArguments[0].Value; - - Tool? toolTarget = (Tool?) (int?) attr.GetPropertyValue ("Tool"); - if (toolTarget is not null && !toolTarget.Value.HasFlag (Tool.Trimmer)) - continue; + foreach (var attr in typeWithRemoveInAssembly.CustomAttributes.Where (predicate)) { + var assemblyName = (string) attr.ConstructorArguments[0].Value; if (!checks.TryGetValue (assemblyName, out List checksForAssembly)) checks[assemblyName] = checksForAssembly = new List (); @@ -1238,10 +1300,6 @@ protected virtual void UnhandledOtherAssemblyAssertion (string expectedTypeName, throw new NotImplementedException ($"Type {expectedTypeName}, has an unknown other assembly attribute of type {checkAttrInAssembly.AttributeType}"); } - bool IsTypeInOtherAssemblyAssertion (CustomAttribute attr) - { - return attr.AttributeType.Resolve ()?.DerivesFrom (nameof (BaseInAssemblyAttribute)) ?? false; - } static bool HasAttribute (ICustomAttributeProvider caProvider, string attributeName) { diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs new file mode 100644 index 0000000000000..298e96124598f --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.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; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mono.Linker.Tests.TestCasesRunner +{ + class TypeMapInfoValidation + { + } +} diff --git a/src/tools/illink/test/Trimming.Tests.Shared/TestResolver.cs b/src/tools/illink/test/Trimming.Tests.Shared/TestResolver.cs index af37fcfbe6487..2d76fc7a8c894 100644 --- a/src/tools/illink/test/Trimming.Tests.Shared/TestResolver.cs +++ b/src/tools/illink/test/Trimming.Tests.Shared/TestResolver.cs @@ -5,8 +5,14 @@ namespace Mono.Linker.Tests.TestCasesRunner { - struct TestResolver : ITryResolveMetadata + struct TestResolver : ITryResolveMetadata, IMetadataResolver { + public TypeDefinition Resolve (TypeReference type) => type.Resolve (); + + public FieldDefinition Resolve (FieldReference field) => field.Resolve (); + + public MethodDefinition Resolve (MethodReference method) => method.Resolve (); + public MethodDefinition TryResolve (MethodReference methodReference) => methodReference.Resolve (); public TypeDefinition TryResolve (TypeReference typeReference) => typeReference.Resolve (); From 66a3bcf1cd0057dac0944250f72fad3e90bb5d54 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:12:59 -0700 Subject: [PATCH 16/27] Add tests for TypeMapInfo --- .../Assertions/IsOverrideOfAttribute.cs | 15 +++++++ ...ethodOverridesMethodInAssemblyAttribute.cs | 22 ++++++++++ .../InterfaceVariants.cs | 4 ++ .../SimpleMethod.cs | 3 +- .../RecursiveGenericInterfaces.cs | 3 +- .../TestCasesRunner/AssemblyChecker.cs | 41 ++++++++++++------- .../TestCasesRunner/ResultChecker.cs | 18 +++----- .../TestCasesRunner/TypeMapInfoValidation.cs | 39 +++++++++++++++++- 8 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/IsOverrideOfAttribute.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/MethodOverridesMethodInAssemblyAttribute.cs diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/IsOverrideOfAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/IsOverrideOfAttribute.cs new file mode 100644 index 0000000000000..e468492a000b3 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/IsOverrideOfAttribute.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + [AttributeUsage (AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public class IsOverrideOfAttribute : BaseTypeMapInfoAttribute + { + public IsOverrideOfAttribute (string methodFullName) + { } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/MethodOverridesMethodInAssemblyAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/MethodOverridesMethodInAssemblyAttribute.cs new file mode 100644 index 0000000000000..7760f981833c4 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Assertions/MethodOverridesMethodInAssemblyAttribute.cs @@ -0,0 +1,22 @@ +// 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; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions +{ + class MethodOverridesMethodInAssemblyAttribute : BaseTypeMapInfoAttribute + { + public MethodOverridesMethodInAssemblyAttribute ( + string overridingAssemblyName, + string overridingTypeName, + string overridingMethodName, + string baseAssemblyName, + string baseMethodName) + { } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceVariants.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceVariants.cs index b19208bd563d6..26519f7f37e71 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceVariants.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/InterfaceVariants.cs @@ -136,15 +136,19 @@ public class UninstantiatedPublicClassWithInterface : internal UninstantiatedPublicClassWithInterface () { } [Kept] + [IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.InterfaceVariants/IPublicInterface::PublicInterfaceMethod()")] public void PublicInterfaceMethod () { } [Kept] + [IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.InterfaceVariants/IPublicInterface::ExplicitImplementationPublicInterfaceMethod()")] void IPublicInterface.ExplicitImplementationPublicInterfaceMethod () { } [Kept] + [IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.InterfaceVariants/IPublicStaticInterface::PublicStaticInterfaceMethod()")] public static void PublicStaticInterfaceMethod () { } [Kept] + [IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.InterfaceVariants/IPublicStaticInterface::ExplicitImplementationPublicStaticInterfaceMethod()")] static void IPublicStaticInterface.ExplicitImplementationPublicStaticInterfaceMethod () { } public void InternalInterfaceMethod () { } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleMethod.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleMethod.cs index a395d4197c111..34b817a76e25b 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleMethod.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/BaseProvidesInterfaceMember/SimpleMethod.cs @@ -22,6 +22,7 @@ interface IFoo class BaseFoo { [Kept] + [IsOverrideOf ("System.Void Mono.Linker.Tests.Cases.Inheritance.Interfaces.OnReferenceType.BaseProvidesInterfaceMember.SimpleMethod/IFoo::Method()")] public void Method () { } @@ -35,4 +36,4 @@ class FooWithBase : BaseFoo, IFoo { } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs index 90c1a319c353f..398e0fbdb5ee0 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/RecursiveInterfaces/RecursiveGenericInterfaces.cs @@ -32,7 +32,8 @@ namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.RecursiveInterfaces [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] [KeptOverrideOnMethodInAssembly ("library.dll", "MyClass", "IBase.GetT", "T IBase`3::GetT()")] - + [RuntimeInterfaceOnTypeInAssembly ("library.dll", "MyClass", "IBase`3", ["IDerived`1", "IMiddle`2", "IBase`3"])] + [RuntimeInterfaceOnTypeInAssembly ("library.dll", "MyClass", "IBase`3", ["IDerived`1", "IMiddle`2", "IBase`3"])] public class RecursiveGenericInterfaces { [Kept] 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 791d63b5de38e..f4cd4f8ad45d1 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -26,11 +26,12 @@ partial class AssemblyChecker readonly HashSet verifiedGeneratedTypes = new HashSet (); bool checkNames; - public AssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked, TrimmedTestCaseResult linkedTestCase) + public AssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked, TrimmedTestCaseResult linkedTestCase, TypeMapInfo typeMapInfo) { this.originalAssembly = original; this.linkedAssembly = linked; this.linkedTestCase = linkedTestCase; + this._originalTypeMapInfo = typeMapInfo; checkNames = original.MainModule.GetTypeReferences ().Any (attr => attr.Name == nameof (RemovedNameValueAttribute)); @@ -108,25 +109,35 @@ private IEnumerable VerifyTypeMapInfo (AssemblyDefinition originalAssemb foreach (var att in type.CustomAttributes) { switch (att.AttributeType.Name) { case nameof (HasRuntimeInterfaceAttribute): - var expectednterfaceTypeName = att.ConstructorArguments[0].Value as string ?? ((TypeReference) att.ConstructorArguments[0].Value).FullName; - var expectedInterfaceChains = ((CustomAttributeArgument[]) att.ConstructorArguments[1].Value).Select (t => t.Value as string ?? ((TypeReference) t.Value).FullName); - var runtimeIfaces = _originalTypeMapInfo.GetRecursiveInterfaces (type); - var matchingRuntimeIface = runtimeIfaces.FirstOrDefault (i => i.InterfaceType.FullName == expectednterfaceTypeName); - if (matchingRuntimeIface == default) { - yield return $"Type {type.FullName} does not have runtime interface {expectednterfaceTypeName}"; - continue; - } - - if (expectedInterfaceChains.Any ()) { - if (!expectedInterfaceChains.Zip (matchingRuntimeIface.ImplementationChain).All (pair => pair.First == pair.Second.InterfaceType.FullName)) { - yield return $"Type {type.FullName} does not have expected implementation chain for runtime interface {expectednterfaceTypeName}"; - } - } + var expectedInterfaceTypeName = att.ConstructorArguments[0].Value as string ?? ((TypeReference) att.ConstructorArguments[0].Value).FullName; + var expectedInterfaceChain = ((CustomAttributeArgument[]) att.ConstructorArguments[1].Value).Select (t => t.Value as string ?? ((TypeReference) t.Value).FullName); + var errs = TypeMapInfoValidation.ValidateRuntimeInterfaces (_originalTypeMapInfo, type, expectedInterfaceTypeName, expectedInterfaceChain); + foreach (var err in errs) + yield return err; break; + default: break; } } + foreach (var method in type.Methods) { + foreach (var att in method.CustomAttributes) { + switch (att.AttributeType.Name) { + case nameof (IsOverrideOfAttribute): + string expectedOverriddenMethodName = att.ConstructorArguments.Count switch { + 1 => (string) att.ConstructorArguments[0].Value, + _ => throw new NotImplementedException ($"Unexpected number of arguments in {att.AttributeType.Name} attribute: {att.ConstructorArguments.Count}") + }; + var errs = TypeMapInfoValidation.ValidateMethodIsOverrideOf (_originalTypeMapInfo, method, expectedOverriddenMethodName); + foreach (var err in errs) + yield return err; + + break; + default: + break; + } + } + } } } 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 4b84a0be17ca7..baa39b75989de 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -30,6 +30,7 @@ public class ResultChecker readonly TypeNameResolver _linkedTypeNameResolver; readonly ReaderParameters _originalReaderParameters; readonly ReaderParameters _linkedReaderParameters; + readonly TypeMapInfo _originalTypeMapInfo; public ResultChecker () : this (new TestCaseAssemblyResolver (), new TestCaseAssemblyResolver (), @@ -50,6 +51,7 @@ public ResultChecker (BaseAssemblyResolver originalsResolver, BaseAssemblyResolv _linkedTypeNameResolver = new TypeNameResolver (new TestResolver (), new TestAssemblyNameResolver (_linkedResolver)); _originalReaderParameters = originalReaderParameters; _linkedReaderParameters = linkedReaderParameters; + _originalTypeMapInfo = new TypeMapInfo (new TestResolver ()); } protected static void ValidateTypeRefsHaveValidAssemblyRefs (AssemblyDefinition linked) @@ -147,7 +149,7 @@ void VerifyILOfOtherAssemblies (TrimmedTestCaseResult linkResult) protected virtual AssemblyChecker CreateAssemblyChecker (AssemblyDefinition original, AssemblyDefinition linked, TrimmedTestCaseResult linkedTestCase) { - return new AssemblyChecker (original, linked, linkedTestCase); + return new AssemblyChecker (original, linked, linkedTestCase, _originalTypeMapInfo); } void InitializeResolvers (TrimmedTestCaseResult linkedResult) @@ -305,19 +307,9 @@ IEnumerable VerifyTypeMapOfOtherAssemblies (AssemblyDefinition original) object type = check.ConstructorArguments[1].Value; TypeDefinition typeDef = GetOriginalTypeFromInAssemblyAttribute (assemblyName, type); string interfaceName = (check.ConstructorArguments[2].Value as TypeReference)?.FullName ?? (string) check.ConstructorArguments[2].Value; - TypeMapInfo typeMapInfo = new TypeMapInfo (new TestResolver ()); - var runtimeInterfaces = typeMapInfo.GetRecursiveInterfaces (typeDef); - var runtimeInterface = runtimeInterfaces.SingleOrDefault (i => i.InterfaceType.FullName == interfaceName); - if (runtimeInterface == default) - errs.Add ($"Expected type `{typeDef}` to have runtime interface `{interfaceName}`"); - - var implementationChains = (check.ConstructorArguments[3].Value as CustomAttributeArgument[]) + var expectedImplChain = (check.ConstructorArguments[3].Value as CustomAttributeArgument[]) .Select (caa => (caa.Value as TypeReference)?.FullName ?? (string) caa.Value); - if (implementationChains.Any ()) { - if (!implementationChains.SequenceEqual (runtimeInterface.ImplementationChain.Select (i => i.InterfaceType.FullName))) - errs.Add ($"Expected type `{typeDef}` to have interfaceImpl chain `{string.Join (", ", implementationChains)}` for interface `{interfaceName}`"); - } - + errs.AddRange (TypeMapInfoValidation.ValidateRuntimeInterfaces (_originalTypeMapInfo, typeDef, interfaceName, expectedImplChain)); break; default: break; diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs index 298e96124598f..e21e45d644888 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs @@ -6,10 +6,47 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Mono.Cecil; +using Mono.Linker.Tests.Cases.Inheritance.AbstractClasses.NoKeptCtor.OverrideRemoval; namespace Mono.Linker.Tests.TestCasesRunner { - class TypeMapInfoValidation + internal class TypeMapInfoValidation { + public static IEnumerable ValidateRuntimeInterfaces (TypeMapInfo typeMapInfo, TypeDefinition typeDef, string expectedInterfaceName, IEnumerable expectedImplChain) + { + var runtimeInterfaces = typeMapInfo.GetRecursiveInterfaces (typeDef); + if (!runtimeInterfaces.HasValue) { + yield return ($"Expected type `{typeDef}` to have runtime interface `{expectedInterfaceName}`, but it has none"); + yield break; + } + var runtimeInterface = runtimeInterfaces.Value.SingleOrDefault (i => i.InflatedInterfaceType.FullName == expectedInterfaceName); + if (runtimeInterface == default) { + yield return ($"Expected type `{typeDef}` to have runtime interface `{expectedInterfaceName}`"); + yield break; + } + + if (expectedImplChain.Any ()) { + var matchingImplementationChainCount = runtimeInterface.InterfaceImplementationChains + .Count (chain => ImplChainMatches (chain, expectedImplChain)); + if (matchingImplementationChainCount == 0) { + yield return ($"Type {typeDef.FullName} does not have expected implementation chain for runtime interface `{expectedInterfaceName}`: {string.Join ("->", expectedImplChain.Select (i => $"`{i}`"))}"); + yield break; + } + } + + static bool ImplChainMatches (InterfaceImplementationChain chain, IEnumerable expectedChain) + { + return chain.InterfaceImplementations.Select (i => i.InterfaceType.FullName).SequenceEqual (expectedChain); + } + } + + public static IEnumerable ValidateMethodIsOverrideOf (TypeMapInfo typeMapInfo, MethodDefinition methodDef, string expectedOverriddenMethodName) + { + var overriddenMethods = typeMapInfo.GetBaseMethods (methodDef); + if (overriddenMethods is null || !overriddenMethods.Select (o => o.Base.FullName).Contains (expectedOverriddenMethodName)) + yield return $"Expected method `{methodDef}` to be an override of `{expectedOverriddenMethodName}`"; + } } } From e524b22531a6793e735a0f9c01937bab3cab8c6b Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:24:11 -0700 Subject: [PATCH 17/27] Move RuntimeInterfaces algorithm to its own class --- .../linker/Linker.Dataflow/FlowAnnotations.cs | 4 +- .../src/linker/Linker.Steps/MarkStep.cs | 100 ++++++++-------- .../ValidateVirtualMethodAnnotationsStep.cs | 4 +- .../Linker/RuntimeInterfaceImplementation.cs | 37 ------ .../Linker/RuntimeInterfacesAlgorithm.cs | 110 ++++++++++++++++++ .../illink/src/linker/Linker/TypeMapInfo.cs | 78 +++---------- ...e.Interfaces.RecursiveInterfacesTests.g.cs | 18 +++ 7 files changed, 199 insertions(+), 152 deletions(-) create mode 100644 src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs diff --git a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs index 5f106d831e675..42a0f0b447a9a 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs @@ -544,8 +544,8 @@ void ValidateMethodGenericParametersHaveNoAnnotations (DynamicallyAccessedMember void LogValidationWarning (IMetadataTokenProvider provider, IMetadataTokenProvider baseProvider, OverrideInformation ov) { - IMemberDefinition origin = (ov.IsOverrideOfInterfaceMember && ov.InterfaceImplementor.Implementor != ov.Override.DeclaringType) - ? ov.InterfaceImplementor.Implementor + IMemberDefinition origin = (ov.IsOverrideOfInterfaceMember && ov.RuntimeInterfaceImplementation.Implementor != ov.Override.DeclaringType) + ? ov.RuntimeInterfaceImplementation.Implementor : ov.Override; Debug.Assert (provider.GetType () == baseProvider.GetType ()); Debug.Assert (!(provider is GenericParameter genericParameter) || genericParameter.DeclaringMethod != null); diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index c8d046af3e9b8..e663b50ff284c 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1483,17 +1483,17 @@ bool ProcessLazyAttributes () string attributeFullName = customAttribute.Constructor.DeclaringType.FullName; switch (attributeFullName) { case "System.Diagnostics.DebuggerDisplayAttribute": { - TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider); - if (targetType != null) - MarkTypeWithDebuggerDisplayAttribute (targetType, customAttribute, assemblyOrigin); - break; - } + TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider); + if (targetType != null) + MarkTypeWithDebuggerDisplayAttribute (targetType, customAttribute, assemblyOrigin); + break; + } case "System.Diagnostics.DebuggerTypeProxyAttribute": { - TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider); - if (targetType != null) - MarkTypeWithDebuggerTypeProxyAttribute (targetType, customAttribute, assemblyOrigin); - break; - } + TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider); + if (targetType != null) + MarkTypeWithDebuggerTypeProxyAttribute (targetType, customAttribute, assemblyOrigin); + break; + } } } @@ -2336,12 +2336,12 @@ void MarkInterfaceImplementations (TypeDefinition type) internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplementation runtimeInterfaceImplementation) { - var implementinType = runtimeInterfaceImplementation.Implementor; + var implementingType = runtimeInterfaceImplementation.Implementor; var ifaceType = runtimeInterfaceImplementation.InterfaceTypeDefinition; if (Annotations.IsMarked (runtimeInterfaceImplementation)) return false; - if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, implementinType)) + if (!Context.IsOptimizationEnabled (CodeOptimizations.UnusedInterfaces, implementingType)) return true; if (ifaceType is null) @@ -2355,7 +2355,7 @@ internal bool ShouldMarkRuntimeInterfaceImplementation (RuntimeInterfaceImplemen if (Context.KeepComInterfaces && (ifaceType.IsImport || ifaceType.IsWindowsRuntime)) return true; - return IsFullyPreserved (implementinType); + return IsFullyPreserved (implementingType); } void MarkGenericParameterProvider (IGenericParameterProvider provider, MessageOrigin origin) @@ -3595,47 +3595,47 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio break; case OperandType.InlineMethod: { - (DependencyKind dependencyKind, bool markForReflectionAccess) = instruction.OpCode.Code switch { - Code.Jmp => (DependencyKind.DirectCall, false), - Code.Call => (DependencyKind.DirectCall, false), - Code.Callvirt => (DependencyKind.VirtualCall, false), - Code.Newobj => (DependencyKind.Newobj, false), - Code.Ldvirtftn => (DependencyKind.Ldvirtftn, true), - Code.Ldftn => (DependencyKind.Ldftn, true), - _ => throw new InvalidOperationException ($"unexpected opcode {instruction.OpCode}") - }; - - MethodReference methodReference = (MethodReference) instruction.Operand; - - requiresReflectionMethodBodyScanner |= - ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite (Context, methodReference); - - origin = new MessageOrigin (origin, instruction.Offset); - if (markForReflectionAccess) { - MarkMethodVisibleToReflection (methodReference, new DependencyInfo (dependencyKind, method), origin); - } else { - MarkMethod (methodReference, new DependencyInfo (dependencyKind, method), origin); - } - break; + (DependencyKind dependencyKind, bool markForReflectionAccess) = instruction.OpCode.Code switch { + Code.Jmp => (DependencyKind.DirectCall, false), + Code.Call => (DependencyKind.DirectCall, false), + Code.Callvirt => (DependencyKind.VirtualCall, false), + Code.Newobj => (DependencyKind.Newobj, false), + Code.Ldvirtftn => (DependencyKind.Ldvirtftn, true), + Code.Ldftn => (DependencyKind.Ldftn, true), + _ => throw new InvalidOperationException ($"unexpected opcode {instruction.OpCode}") + }; + + MethodReference methodReference = (MethodReference) instruction.Operand; + + requiresReflectionMethodBodyScanner |= + ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite (Context, methodReference); + + origin = new MessageOrigin (origin, instruction.Offset); + if (markForReflectionAccess) { + MarkMethodVisibleToReflection (methodReference, new DependencyInfo (dependencyKind, method), origin); + } else { + MarkMethod (methodReference, new DependencyInfo (dependencyKind, method), origin); } + break; + } case OperandType.InlineTok: { - object token = instruction.Operand; - Debug.Assert (instruction.OpCode.Code == Code.Ldtoken); - var reason = new DependencyInfo (DependencyKind.Ldtoken, method); - origin = new MessageOrigin (origin, instruction.Offset); - - if (token is TypeReference typeReference) { - // Error will be reported as part of MarkType - if (Context.TryResolve (typeReference) is TypeDefinition type) - MarkTypeVisibleToReflection (typeReference, type, reason, origin); - } else if (token is MethodReference methodReference) { - MarkMethodVisibleToReflection (methodReference, reason, origin); - } else { - MarkFieldVisibleToReflection ((FieldReference) token, reason, origin); - } - break; + object token = instruction.Operand; + Debug.Assert (instruction.OpCode.Code == Code.Ldtoken); + var reason = new DependencyInfo (DependencyKind.Ldtoken, method); + origin = new MessageOrigin (origin, instruction.Offset); + + if (token is TypeReference typeReference) { + // Error will be reported as part of MarkType + if (Context.TryResolve (typeReference) is TypeDefinition type) + MarkTypeVisibleToReflection (typeReference, type, reason, origin); + } else if (token is MethodReference methodReference) { + MarkMethodVisibleToReflection (methodReference, reason, origin); + } else { + MarkFieldVisibleToReflection ((FieldReference) token, reason, origin); } + break; + } case OperandType.InlineType: var operand = (TypeReference) instruction.Operand; diff --git a/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs b/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs index aa9c7ee0d9f09..4d68e20620e26 100644 --- a/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs @@ -51,8 +51,8 @@ void ValidateMethodRequiresUnreferencedCodeAreSame (OverrideInformation ov) nameof (RequiresUnreferencedCodeAttribute), method.GetDisplayName (), baseMethod.GetDisplayName ()); - IMemberDefinition origin = (ov.IsOverrideOfInterfaceMember && ov.InterfaceImplementor.Implementor != method.DeclaringType) - ? ov.InterfaceImplementor.Implementor + IMemberDefinition origin = (ov.IsOverrideOfInterfaceMember && ov.RuntimeInterfaceImplementation.Implementor != method.DeclaringType) + ? ov.RuntimeInterfaceImplementation.Implementor : method; Context.LogWarning (origin, DiagnosticId.RequiresUnreferencedCodeAttributeMismatch, message); } diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs index e7916e2011c41..5e770cb0e275c 100644 --- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs @@ -60,42 +60,5 @@ public bool IsAnyImplementationMarked (AnnotationStore annotations, ITryResolveM } return false; } - - /// - /// Returns a list of InterfaceImplementationChains for a derived type of . - /// - public IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainsForDerivedType (TypeReference baseTypeRef, ITryResolveMetadata context) - { - // This is only valid for classes - Debug.Assert (Implementor.IsClass); - Debug.Assert (Implementor == context.TryResolve (baseTypeRef)); - - var inflatedInterfaceType = InflatedInterfaceType.TryInflateFrom (baseTypeRef, context); - Debug.Assert (inflatedInterfaceType is not null); - - foreach (var impl in InterfaceImplementationChains) { - var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (baseTypeRef, context); - Debug.Assert (inflatedImplProvider is not null); - - yield return (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations)); - } - } - - /// - /// Returns a list of InterfaceImplementationChains for a type that has an explicit implementation of . - /// - public IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainForImplementingType (TypeDefinition typeThatImplementsImplementor, InterfaceImplementation impl, ITryResolveMetadata context) - { - Debug.Assert (Implementor.IsInterface); - Debug.Assert (typeThatImplementsImplementor.Interfaces.Contains (impl)); - Debug.Assert (context.TryResolve (impl.InterfaceType) == Implementor); - - var inflatedInterfaceType = InflatedInterfaceType.TryInflateFrom (impl.InterfaceType, context); - Debug.Assert (inflatedInterfaceType is not null); - - foreach (var existingImpl in InterfaceImplementationChains) { - yield return (inflatedInterfaceType, new InterfaceImplementationChain (typeThatImplementsImplementor, existingImpl.InterfaceImplementations.Insert (0, impl))); - } - } } } diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs new file mode 100644 index 0000000000000..bf604aaf60675 --- /dev/null +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs @@ -0,0 +1,110 @@ +// 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.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Mono.Cecil; + +namespace Mono.Linker.Linker +{ + internal struct RuntimeInterfacesAlgorithm + { + readonly ITryResolveMetadata _context; + readonly Dictionary> _runtimeInterfaceImpls = new (); + + public RuntimeInterfacesAlgorithm (ITryResolveMetadata context) + { + this._context = context; + } + + public ImmutableArray GetRuntimeInterfaceImplementations (TypeDefinition originalType) + { + if (_runtimeInterfaceImpls.TryGetValue (originalType, out var runtimeIfaces)) { + return runtimeIfaces; + } + + Dictionary> interfaceTypeToImplChainMap = new (new TypeReferenceEqualityComparer (_context)); + + foreach (var explicitIface in originalType.Interfaces) { + interfaceTypeToImplChainMap.AddToList (explicitIface.InterfaceType, new InterfaceImplementationChain (originalType, [explicitIface])); + + var resolvedInterfaceType = _context.TryResolve (explicitIface.InterfaceType); + if (resolvedInterfaceType is null) { + continue; + } + + var recursiveIFaces = GetRuntimeInterfaceImplementations (resolvedInterfaceType); + + foreach (var recursiveIface in recursiveIFaces) { + var impls = PrependInterfaceImplToChains (recursiveIface, originalType, explicitIface); + foreach (var impl in impls) { + interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain); + } + } + } + + if (originalType.BaseType is not null && _context.TryResolve (originalType.BaseType) is { } baseTypeDef) { + var baseTypeIfaces = GetRuntimeInterfaceImplementations (baseTypeDef); + foreach (var recursiveIface in baseTypeIfaces) { + var impls = CreateImplementationChainsForDerivedType (recursiveIface, originalType.BaseType); + foreach (var impl in impls) { + interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain); + } + } + } + + if (interfaceTypeToImplChainMap.Count == 0) + return ImmutableArray.Empty; + + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder (interfaceTypeToImplChainMap.Count); + foreach (var kvp in interfaceTypeToImplChainMap) { + builder.Add (new (originalType, kvp.Key, _context.TryResolve (kvp.Key), kvp.Value)); + } + var runtimeInterfaces = builder.MoveToImmutable (); + _runtimeInterfaceImpls[originalType] = runtimeInterfaces; + return runtimeInterfaces; + + } + + /// + /// Returns a list of InterfaceImplementationChains for a derived type of . + /// + IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainsForDerivedType (RuntimeInterfaceImplementation runtimeImpl, TypeReference baseTypeRef) + { + // This is only valid for classes + Debug.Assert (runtimeImpl.Implementor.IsClass); + Debug.Assert (runtimeImpl.Implementor == _context.TryResolve (baseTypeRef)); + + var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (baseTypeRef, _context); + Debug.Assert (inflatedInterfaceType is not null); + + foreach (var impl in runtimeImpl.InterfaceImplementationChains) { + var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (baseTypeRef, _context); + Debug.Assert (inflatedImplProvider is not null); + yield return (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations)); + } + } + + /// + /// Returns a list of InterfaceImplementationChains for a type that has an explicit implementation of . + /// + IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> PrependInterfaceImplToChains (RuntimeInterfaceImplementation runtimeImpl, TypeDefinition typeWithPrependedImpl, InterfaceImplementation implToPrepend) + { + Debug.Assert (runtimeImpl.Implementor.IsInterface); + Debug.Assert (_context.TryResolve (implToPrepend.InterfaceType) == runtimeImpl.Implementor); + Debug.Assert (typeWithPrependedImpl.Interfaces.Contains (implToPrepend)); + + var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (implToPrepend.InterfaceType, _context); + Debug.Assert (inflatedInterfaceType is not null); + + foreach (var existingImpl in runtimeImpl.InterfaceImplementationChains) { + yield return (inflatedInterfaceType, new InterfaceImplementationChain (typeWithPrependedImpl, existingImpl.InterfaceImplementations.Insert (0, implToPrepend))); + } + } + } +} diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index eba929d5fdeb2..ae0942a19c838 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -35,6 +35,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; +using Mono.Linker.Linker; namespace Mono.Linker { @@ -45,15 +46,18 @@ public class TypeMapInfo protected readonly Dictionary> base_methods = new Dictionary> (); protected readonly Dictionary> override_methods = new Dictionary> (); protected readonly Dictionary> default_interface_implementations = new Dictionary> (); + readonly RuntimeInterfacesAlgorithm _recursiveInterfaces; public TypeMapInfo (LinkContext context) { this.context = context; + _recursiveInterfaces = new (context); } internal TypeMapInfo (ITryResolveMetadata context) { this.context = context; + _recursiveInterfaces = new (context); } public void EnsureProcessed (AssemblyDefinition assembly) @@ -114,10 +118,8 @@ internal void AddOverride (MethodDefinition @base, MethodDefinition @override, R override_methods.AddToList (@base, new OverrideInformation (@base, @override, runtimeInterface)); } - Dictionary> _runtimeInterfaceImpls = new (); protected virtual void MapType (TypeDefinition type) { - _runtimeInterfaceImpls[type] = GetRecursiveInterfaceImplementations (type); MapVirtualMethods (type); MapInterfaceMethodsInTypeHierarchy (type); @@ -130,60 +132,14 @@ protected virtual void MapType (TypeDefinition type) internal ImmutableArray? GetRecursiveInterfaces (TypeDefinition type) { - EnsureProcessed (type.Module.Assembly); - if (_runtimeInterfaceImpls.TryGetValue (type, out var value)) - return value; - return null; - } - - - ImmutableArray GetRecursiveInterfaceImplementations (TypeDefinition originalType) - { - if (_runtimeInterfaceImpls.TryGetValue (originalType, out var runtimeIfaces)) { - return runtimeIfaces; - } - - Dictionary> interfaceTypeToImplChainMap = new (new TypeReferenceEqualityComparer (context)); - - foreach (var explicitIface in originalType.Interfaces) { - // Add runtimeIface for this Iface - var resolvedInterfaceType = context.TryResolve (explicitIface.InterfaceType); - interfaceTypeToImplChainMap.AddToList (explicitIface.InterfaceType, new InterfaceImplementationChain (originalType, [explicitIface])); - - if (resolvedInterfaceType is null) { - continue; - } - - var recursiveIFaces = GetRecursiveInterfaceImplementations (resolvedInterfaceType); - - foreach (var recursiveIface in recursiveIFaces) { - var impls = recursiveIface.CreateImplementationChainForImplementingType (originalType, explicitIface, context); - foreach (var impl in impls) { - interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain); - } - } - } - - if (originalType.BaseType is not null && context.TryResolve (originalType.BaseType) is { } baseTypeDef) { - var baseTypeIfaces = GetRecursiveInterfaceImplementations (baseTypeDef); - foreach (var recursiveIface in baseTypeIfaces) { - var impls = recursiveIface.CreateImplementationChainsForDerivedType (originalType.BaseType, context); - foreach (var impl in impls) { - interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain); - } - } - } - - if (interfaceTypeToImplChainMap.Count == 0) - return ImmutableArray.Empty; - - return interfaceTypeToImplChainMap.Select (kvp => new RuntimeInterfaceImplementation (originalType, kvp.Key, context.TryResolve (kvp.Key), kvp.Value)).ToImmutableArray (); + return _recursiveInterfaces.GetRuntimeInterfaceImplementations (type); } void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type) { - if (!_runtimeInterfaceImpls.TryGetValue (type, out var runtimeInterfaceImpls)) - return; + var runtimeInterfaceImpls = _recursiveInterfaces.GetRuntimeInterfaceImplementations (type); + if (runtimeInterfaceImpls.Length == 0) return; + // Foreach interface and for each newslot virtual method on the interface, try // to find the method implementation and record it. foreach (var interfaceImpl in runtimeInterfaceImpls) { @@ -259,8 +215,8 @@ void MapOverrides (MethodDefinition method) if (baseMethod == null) continue; if (baseMethod.DeclaringType.IsInterface) { - var found = _runtimeInterfaceImpls.TryGetValue (method.DeclaringType, out var runtimeInterfaces); - Debug.Assert (found); + var runtimeInterfaces = _recursiveInterfaces.GetRuntimeInterfaceImplementations (method.DeclaringType); + Debug.Assert (runtimeInterfaces.Length > 0); var runtimeInterface = runtimeInterfaces.Single (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, baseMethodRef.DeclaringType, context)); AnnotateMethods (baseMethod, method, runtimeInterface); } else { @@ -338,20 +294,19 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o { // Go over all interfaces, trying to find a method that is an explicit MethodImpl of the // interface method in question. - if (!_runtimeInterfaceImpls.TryGetValue (originalInterfaceImpl.Implementor, out var runtimeIfaces)) + var runtimeIfaces = _recursiveInterfaces.GetRuntimeInterfaceImplementations (originalInterfaceImpl.Implementor); + if (runtimeIfaces.Length == 0) return; - foreach (var interfaceImpl in runtimeIfaces) { - var potentialImplInterface = interfaceImpl.InterfaceTypeDefinition; + foreach (var runtimeIface in runtimeIfaces) { + var potentialImplInterface = runtimeIface.InterfaceTypeDefinition; if (potentialImplInterface is null) continue; - bool foundImpl = false; foreach (var potentialImplMethod in potentialImplInterface.Methods) { - if (potentialImplMethod == interfaceMethodDef && - !potentialImplMethod.IsAbstract) { + if (potentialImplMethod == interfaceMethodDef && !potentialImplMethod.IsAbstract) { + // The interface method definition provides a default implementation. AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, potentialImplMethod); - foundImpl = true; break; } @@ -359,6 +314,7 @@ void FindAndAddDefaultInterfaceImplementations (RuntimeInterfaceImplementation o continue; // This method is an override of something. Let's see if it's the method we are looking for. + bool foundImpl = false; foreach (var baseMethod in potentialImplMethod.Overrides) { if (context.TryResolve (baseMethod) == interfaceMethodDef) { AddDefaultInterfaceImplementation (interfaceMethodDef, originalInterfaceImpl, @potentialImplMethod); diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs index 35cc0b65c0417..1dbdf7bd5c9ac 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs @@ -9,6 +9,12 @@ public sealed partial class RecursiveInterfacesTests : LinkerTestBase protected override string TestSuiteName => "Inheritance.Interfaces.RecursiveInterfaces"; + [Fact] + public Task BaseTypeMarkedInterfaceDerivedNotKept () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task GenericInterfaceImplementedRecursively () { @@ -27,6 +33,18 @@ public Task OverrideOfRecursiveInterfaceIsRemoved () return RunTest (allowMissingWarnings: true); } + [Fact] + public Task RecursiveGenericInterfaces () + { + return RunTest (allowMissingWarnings: true); + } + + [Fact] + public Task RecursiveGenericInterfacesStatic () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task RecursiveInterfaceKept () { From a8dfbd397ad01cafe7fb465bc1de40b525b8979e Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:24:33 -0700 Subject: [PATCH 18/27] Inline the runtime interfaces helpers to avoid confusing naming --- .../Linker/RuntimeInterfacesAlgorithm.cs | 64 ++++++------------- 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs index bf604aaf60675..2fc3ccd94833c 100644 --- a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs @@ -38,22 +38,31 @@ public ImmutableArray GetRuntimeInterfaceImpleme continue; } + // Add the recursive interfaces for each explicit interface, prepending the explicit interface on `originalType` to the chain var recursiveIFaces = GetRuntimeInterfaceImplementations (resolvedInterfaceType); + foreach (var runtimeImpl in recursiveIFaces) { + // Inflate the interface type with the explicit interfaceImpl reference + var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (explicitIface.InterfaceType, _context); + Debug.Assert (inflatedInterfaceType is not null); - foreach (var recursiveIface in recursiveIFaces) { - var impls = PrependInterfaceImplToChains (recursiveIface, originalType, explicitIface); - foreach (var impl in impls) { - interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain); + foreach (var existingImpl in runtimeImpl.InterfaceImplementationChains) { + interfaceTypeToImplChainMap.AddToList (inflatedInterfaceType, new InterfaceImplementationChain (originalType, existingImpl.InterfaceImplementations.Insert (0, explicitIface))); } } } if (originalType.BaseType is not null && _context.TryResolve (originalType.BaseType) is { } baseTypeDef) { var baseTypeIfaces = GetRuntimeInterfaceImplementations (baseTypeDef); - foreach (var recursiveIface in baseTypeIfaces) { - var impls = CreateImplementationChainsForDerivedType (recursiveIface, originalType.BaseType); - foreach (var impl in impls) { - interfaceTypeToImplChainMap.AddToList (impl.InterfaceType, impl.Chain); + foreach (var runtimeImpl in baseTypeIfaces) { + // Inflate the interface type with the base type reference + var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (originalType.BaseType, _context); + Debug.Assert (inflatedInterfaceType is not null); + + foreach (var impl in runtimeImpl.InterfaceImplementationChains) { + // Inflate the provider for the first .impl - this could be a different recursive base type for each chain + var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (originalType.BaseType, _context); + Debug.Assert (inflatedImplProvider is not null); + interfaceTypeToImplChainMap.AddToList (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations)); } } } @@ -61,50 +70,15 @@ public ImmutableArray GetRuntimeInterfaceImpleme if (interfaceTypeToImplChainMap.Count == 0) return ImmutableArray.Empty; + // Build the ImmutableArray and cache it ImmutableArray.Builder builder = ImmutableArray.CreateBuilder (interfaceTypeToImplChainMap.Count); foreach (var kvp in interfaceTypeToImplChainMap) { builder.Add (new (originalType, kvp.Key, _context.TryResolve (kvp.Key), kvp.Value)); } var runtimeInterfaces = builder.MoveToImmutable (); _runtimeInterfaceImpls[originalType] = runtimeInterfaces; - return runtimeInterfaces; - - } - - /// - /// Returns a list of InterfaceImplementationChains for a derived type of . - /// - IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> CreateImplementationChainsForDerivedType (RuntimeInterfaceImplementation runtimeImpl, TypeReference baseTypeRef) - { - // This is only valid for classes - Debug.Assert (runtimeImpl.Implementor.IsClass); - Debug.Assert (runtimeImpl.Implementor == _context.TryResolve (baseTypeRef)); - - var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (baseTypeRef, _context); - Debug.Assert (inflatedInterfaceType is not null); - - foreach (var impl in runtimeImpl.InterfaceImplementationChains) { - var inflatedImplProvider = impl.TypeWithInterfaceImplementation.TryInflateFrom (baseTypeRef, _context); - Debug.Assert (inflatedImplProvider is not null); - yield return (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations)); - } - } - - /// - /// Returns a list of InterfaceImplementationChains for a type that has an explicit implementation of . - /// - IEnumerable<(TypeReference InterfaceType, InterfaceImplementationChain Chain)> PrependInterfaceImplToChains (RuntimeInterfaceImplementation runtimeImpl, TypeDefinition typeWithPrependedImpl, InterfaceImplementation implToPrepend) - { - Debug.Assert (runtimeImpl.Implementor.IsInterface); - Debug.Assert (_context.TryResolve (implToPrepend.InterfaceType) == runtimeImpl.Implementor); - Debug.Assert (typeWithPrependedImpl.Interfaces.Contains (implToPrepend)); - var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.TryInflateFrom (implToPrepend.InterfaceType, _context); - Debug.Assert (inflatedInterfaceType is not null); - - foreach (var existingImpl in runtimeImpl.InterfaceImplementationChains) { - yield return (inflatedInterfaceType, new InterfaceImplementationChain (typeWithPrependedImpl, existingImpl.InterfaceImplementations.Insert (0, implToPrepend))); - } + return runtimeInterfaces; } } } From 01e45182eac5f56a7b053b10e2f67a1065906d1b Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 13 Aug 2024 08:10:17 -0700 Subject: [PATCH 19/27] Undo unrelated formatting --- .../src/linker/Linker.Steps/MarkStep.cs | 94 +++++++++---------- .../illink/src/linker/Linker/TypeMapInfo.cs | 3 +- 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index e663b50ff284c..96e770d116810 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1483,17 +1483,17 @@ bool ProcessLazyAttributes () string attributeFullName = customAttribute.Constructor.DeclaringType.FullName; switch (attributeFullName) { case "System.Diagnostics.DebuggerDisplayAttribute": { - TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider); - if (targetType != null) - MarkTypeWithDebuggerDisplayAttribute (targetType, customAttribute, assemblyOrigin); - break; - } + TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider); + if (targetType != null) + MarkTypeWithDebuggerDisplayAttribute (targetType, customAttribute, assemblyOrigin); + break; + } case "System.Diagnostics.DebuggerTypeProxyAttribute": { - TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider); - if (targetType != null) - MarkTypeWithDebuggerTypeProxyAttribute (targetType, customAttribute, assemblyOrigin); - break; - } + TypeDefinition? targetType = GetDebuggerAttributeTargetType (assemblyLevelAttribute.Attribute, (AssemblyDefinition) assemblyLevelAttribute.Provider); + if (targetType != null) + MarkTypeWithDebuggerTypeProxyAttribute (targetType, customAttribute, assemblyOrigin); + break; + } } } @@ -3595,47 +3595,47 @@ protected virtual void MarkInstruction (Instruction instruction, MethodDefinitio break; case OperandType.InlineMethod: { - (DependencyKind dependencyKind, bool markForReflectionAccess) = instruction.OpCode.Code switch { - Code.Jmp => (DependencyKind.DirectCall, false), - Code.Call => (DependencyKind.DirectCall, false), - Code.Callvirt => (DependencyKind.VirtualCall, false), - Code.Newobj => (DependencyKind.Newobj, false), - Code.Ldvirtftn => (DependencyKind.Ldvirtftn, true), - Code.Ldftn => (DependencyKind.Ldftn, true), - _ => throw new InvalidOperationException ($"unexpected opcode {instruction.OpCode}") - }; - - MethodReference methodReference = (MethodReference) instruction.Operand; - - requiresReflectionMethodBodyScanner |= - ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite (Context, methodReference); - - origin = new MessageOrigin (origin, instruction.Offset); - if (markForReflectionAccess) { - MarkMethodVisibleToReflection (methodReference, new DependencyInfo (dependencyKind, method), origin); - } else { - MarkMethod (methodReference, new DependencyInfo (dependencyKind, method), origin); + (DependencyKind dependencyKind, bool markForReflectionAccess) = instruction.OpCode.Code switch { + Code.Jmp => (DependencyKind.DirectCall, false), + Code.Call => (DependencyKind.DirectCall, false), + Code.Callvirt => (DependencyKind.VirtualCall, false), + Code.Newobj => (DependencyKind.Newobj, false), + Code.Ldvirtftn => (DependencyKind.Ldvirtftn, true), + Code.Ldftn => (DependencyKind.Ldftn, true), + _ => throw new InvalidOperationException ($"unexpected opcode {instruction.OpCode}") + }; + + MethodReference methodReference = (MethodReference) instruction.Operand; + + requiresReflectionMethodBodyScanner |= + ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite (Context, methodReference); + + origin = new MessageOrigin (origin, instruction.Offset); + if (markForReflectionAccess) { + MarkMethodVisibleToReflection (methodReference, new DependencyInfo (dependencyKind, method), origin); + } else { + MarkMethod (methodReference, new DependencyInfo (dependencyKind, method), origin); + } + break; } - break; - } case OperandType.InlineTok: { - object token = instruction.Operand; - Debug.Assert (instruction.OpCode.Code == Code.Ldtoken); - var reason = new DependencyInfo (DependencyKind.Ldtoken, method); - origin = new MessageOrigin (origin, instruction.Offset); - - if (token is TypeReference typeReference) { - // Error will be reported as part of MarkType - if (Context.TryResolve (typeReference) is TypeDefinition type) - MarkTypeVisibleToReflection (typeReference, type, reason, origin); - } else if (token is MethodReference methodReference) { - MarkMethodVisibleToReflection (methodReference, reason, origin); - } else { - MarkFieldVisibleToReflection ((FieldReference) token, reason, origin); + object token = instruction.Operand; + Debug.Assert (instruction.OpCode.Code == Code.Ldtoken); + var reason = new DependencyInfo (DependencyKind.Ldtoken, method); + origin = new MessageOrigin (origin, instruction.Offset); + + if (token is TypeReference typeReference) { + // Error will be reported as part of MarkType + if (Context.TryResolve (typeReference) is TypeDefinition type) + MarkTypeVisibleToReflection (typeReference, type, reason, origin); + } else if (token is MethodReference methodReference) { + MarkMethodVisibleToReflection (methodReference, reason, origin); + } else { + MarkFieldVisibleToReflection ((FieldReference) token, reason, origin); + } + break; } - break; - } case OperandType.InlineType: var operand = (TypeReference) instruction.Operand; diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index bada865b78483..d552e96f6ff06 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -138,7 +138,8 @@ protected virtual void MapType (TypeDefinition type) void MapInterfaceMethodsInTypeHierarchy (TypeDefinition type) { var runtimeInterfaceImpls = _recursiveInterfaces.GetRuntimeInterfaceImplementations (type); - if (runtimeInterfaceImpls.Length == 0) return; + if (runtimeInterfaceImpls.Length == 0) + return; // Foreach interface and for each newslot virtual method on the interface, try // to find the method implementation and record it. From df044848c00e35015040dc6426da5ec7acfb60eb Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 13 Aug 2024 09:49:04 -0700 Subject: [PATCH 20/27] Revert "Implement type name resolution for ILLink analyzer (#106209)" This reverts commit e5b1d021938d41b589feeb5b6e9dcb55035dfda9. --- .../DynamicallyAccessedMembersAnalyzer.cs | 6 +- .../ILLink.RoslynAnalyzer.csproj | 4 - .../TrimAnalysis/GenericArgumentDataFlow.cs | 23 ++- .../TrimAnalysis/HandleCallAction.cs | 5 +- .../TrimAnalysis/ReflectionAccessAnalyzer.cs | 14 +- ...RequireDynamicallyAccessedMembersAction.cs | 31 +--- .../TrimAnalysisAssignmentPattern.cs | 7 +- ...TrimAnalysisGenericInstantiationPattern.cs | 7 +- .../TrimAnalysisMethodCallPattern.cs | 5 +- .../TrimAnalysisReflectionAccessPattern.cs | 3 +- .../TrimAnalysis/TrimAnalysisVisitor.cs | 8 +- .../TrimAnalysis/TypeNameResolver.cs | 149 ------------------ .../DynamicallyAccessedMembersCodeFixTests.cs | 28 ++-- .../TestCaseCompilation.cs | 2 +- .../DataFlow/AssemblyQualifiedNameDataflow.cs | 2 +- .../DataFlow/AttributeConstructorDataflow.cs | 2 +- .../DataFlow/AttributeFieldDataflow.cs | 2 +- .../DataFlow/AttributePropertyDataflow.cs | 8 +- .../GenericParameterWarningLocation.cs | 8 +- .../DataFlow/GetTypeDataFlow.cs | 6 +- .../Reflection/TypeUsedViaReflection.cs | 145 +---------------- .../TestCasesRunner/ResultChecker.cs | 8 +- 22 files changed, 80 insertions(+), 393 deletions(-) delete mode 100644 src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index c9342122aa6de..66b71ab4f9c38 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -52,7 +52,6 @@ public static ImmutableArray GetSupportedDiagnostics () diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedTypeNameInTypeGetType)); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedParameterInMethodCreateInstance)); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ParametersOfAssemblyCreateInstanceCannotBeAnalyzed)); - diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.TypeNameIsNotAssemblyQualified)); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ReturnValueDoesNotMatchFeatureGuards)); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.InvalidFeatureGuard)); @@ -118,12 +117,11 @@ public override void Initialize (AnalysisContext context) var location = GetPrimaryLocation (type.Locations); - var typeNameResolver = new TypeNameResolver (context.Compilation); if (type.BaseType is INamedTypeSymbol baseType) - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, baseType, context.ReportDiagnostic); + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, baseType, context.ReportDiagnostic); foreach (var interfaceType in type.Interfaces) - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (typeNameResolver, location, interfaceType, context.ReportDiagnostic); + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, interfaceType, context.ReportDiagnostic); }, SymbolKind.NamedType); context.RegisterSymbolAction (context => { VerifyMemberOnlyApplyToTypesOrStrings (context, context.Symbol); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj b/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj index c3b0b1d1847b4..bc410523d5d71 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj @@ -15,13 +15,10 @@ optimize the analyzer even in Debug builds. Note: we still use the Debug configuration to get Debug asserts. --> true - true - - @@ -31,7 +28,6 @@ all contentfiles - diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs index 84a6d31d3fa85..8eac76a299339 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/GenericArgumentDataFlow.cs @@ -12,29 +12,28 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis { internal static class GenericArgumentDataFlow { - public static void ProcessGenericArgumentDataFlow (TypeNameResolver typeNameResolver, Location location, INamedTypeSymbol type, Action? reportDiagnostic) + public static void ProcessGenericArgumentDataFlow (Location location, INamedTypeSymbol type, Action reportDiagnostic) { - ProcessGenericArgumentDataFlow (typeNameResolver, location, type.TypeArguments, type.TypeParameters, reportDiagnostic); + ProcessGenericArgumentDataFlow (location, type.TypeArguments, type.TypeParameters, reportDiagnostic); } - public static void ProcessGenericArgumentDataFlow (TypeNameResolver typeNameResolver, Location location, IMethodSymbol method, Action? reportDiagnostic) + public static void ProcessGenericArgumentDataFlow (Location location, IMethodSymbol method, Action reportDiagnostic) { - ProcessGenericArgumentDataFlow (typeNameResolver, location, method.TypeArguments, method.TypeParameters, reportDiagnostic); + ProcessGenericArgumentDataFlow (location, method.TypeArguments, method.TypeParameters, reportDiagnostic); - ProcessGenericArgumentDataFlow (typeNameResolver, location, method.ContainingType, reportDiagnostic); + ProcessGenericArgumentDataFlow (location, method.ContainingType, reportDiagnostic); } - public static void ProcessGenericArgumentDataFlow (TypeNameResolver typeNameResolver, Location location, IFieldSymbol field, Action? reportDiagnostic) + public static void ProcessGenericArgumentDataFlow (Location location, IFieldSymbol field, Action reportDiagnostic) { - ProcessGenericArgumentDataFlow (typeNameResolver, location, field.ContainingType, reportDiagnostic); + ProcessGenericArgumentDataFlow (location, field.ContainingType, reportDiagnostic); } static void ProcessGenericArgumentDataFlow ( - TypeNameResolver typeNameResolver, Location location, ImmutableArray typeArguments, ImmutableArray typeParameters, - Action? reportDiagnostic) + Action reportDiagnostic) { var diagnosticContext = new DiagnosticContext (location, reportDiagnostic); for (int i = 0; i < typeArguments.Length; i++) { @@ -43,14 +42,14 @@ static void ProcessGenericArgumentDataFlow ( var genericParameterValue = new GenericParameterValue (typeParameters[i]); if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) { SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol (typeArgument)!; - var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeNameResolver); - var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer); + var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, reflectionAccessAnalyzer); requireDynamicallyAccessedMembersAction.Invoke (genericArgumentValue, genericParameterValue); } // Recursively process generic argument data flow on the generic argument if it itself is generic if (typeArgument is INamedTypeSymbol namedTypeArgument && namedTypeArgument.IsGenericType) - ProcessGenericArgumentDataFlow (typeNameResolver, location, namedTypeArgument, reportDiagnostic); + ProcessGenericArgumentDataFlow (location, namedTypeArgument, reportDiagnostic); } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs index 53294dc3c32a9..3e8d00d5d050f 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs @@ -28,7 +28,6 @@ internal partial struct HandleCallAction ValueSetLattice _multiValueLattice; public HandleCallAction ( - TypeNameResolver typeNameResolver, Location location, ISymbol owningSymbol, IOperation operation, @@ -40,8 +39,8 @@ public HandleCallAction ( _isNewObj = operation.Kind == OperationKind.ObjectCreation; _diagnosticContext = new DiagnosticContext (location, reportDiagnostic); _annotations = FlowAnnotations.Instance; - _reflectionAccessAnalyzer = new (reportDiagnostic, typeNameResolver); - _requireDynamicallyAccessedMembersAction = new (typeNameResolver, location, reportDiagnostic, _reflectionAccessAnalyzer); + _reflectionAccessAnalyzer = new (reportDiagnostic); + _requireDynamicallyAccessedMembersAction = new (_diagnosticContext, _reflectionAccessAnalyzer); _multiValueLattice = multiValueLattice; } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs index c53e28ff336fd..51d684816418d 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs @@ -15,15 +15,8 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis readonly struct ReflectionAccessAnalyzer { readonly Action? _reportDiagnostic; - readonly TypeNameResolver _typeNameResolver; - public ReflectionAccessAnalyzer ( - Action? reportDiagnostic, - TypeNameResolver typeNameResolver) - { - _reportDiagnostic = reportDiagnostic; - _typeNameResolver = typeNameResolver; - } + public ReflectionAccessAnalyzer (Action? reportDiagnostic) => _reportDiagnostic = reportDiagnostic; #pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods internal void GetReflectionAccessDiagnostics (Location location, ITypeSymbol typeSymbol, DynamicallyAccessedMemberTypes requiredMemberTypes, bool declaredOnly = false) @@ -145,10 +138,5 @@ void GetDiagnosticsForField (Location location, IFieldSymbol fieldSymbol) diagnosticContext.AddDiagnostic (DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection, fieldSymbol.GetDisplayName ()); } } - - internal bool TryResolveTypeNameAndMark (string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, [NotNullWhen (true)] out ITypeSymbol? type) - { - return _typeNameResolver.TryResolveTypeName (typeName, diagnosticContext, out type, needsAssemblyName); - } } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs index 91fdb54ef8394..5226aad4f0320 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs @@ -1,55 +1,40 @@ // 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.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Reflection.Metadata; -using Microsoft.CodeAnalysis; using ILLink.RoslynAnalyzer.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; -using System.Collections.Immutable; namespace ILLink.Shared.TrimAnalysis { internal partial struct RequireDynamicallyAccessedMembersAction { - readonly Location _location; - readonly Action? _reportDiagnostic; readonly ReflectionAccessAnalyzer _reflectionAccessAnalyzer; - readonly TypeNameResolver _typeNameResolver; #pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods #pragma warning disable IDE0060 // Unused parameters - should be removed once methods are actually implemented public RequireDynamicallyAccessedMembersAction ( - TypeNameResolver typeNameResolver, - Location location, - Action? reportDiagnostic, + DiagnosticContext diagnosticContext, ReflectionAccessAnalyzer reflectionAccessAnalyzer) { - _typeNameResolver = typeNameResolver; - _location = location; - _reportDiagnostic = reportDiagnostic; + _diagnosticContext = diagnosticContext; _reflectionAccessAnalyzer = reflectionAccessAnalyzer; - _diagnosticContext = new (location, reportDiagnostic); } public partial bool TryResolveTypeNameAndMark (string typeName, bool needsAssemblyName, out TypeProxy type) { - var diagnosticContext = new DiagnosticContext (_location, _reportDiagnostic); - if (_reflectionAccessAnalyzer.TryResolveTypeNameAndMark (typeName, diagnosticContext, needsAssemblyName, out ITypeSymbol? foundType)) { - if (foundType is INamedTypeSymbol namedType && namedType.IsGenericType) - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (_typeNameResolver, _location, namedType, _reportDiagnostic); + // TODO: Implement type name resolution to type symbol + // https://github.com/dotnet/runtime/issues/95118 - type = new TypeProxy (foundType); - return true; - } + // Important corner cases: + // IL2105 (see it's occurences in the tests) - non-assembly qualified type name which doesn't resolve warns + // - will need to figure out what analyzer should do around this. type = default; return false; } private partial void MarkTypeForDynamicallyAccessedMembers (in TypeProxy type, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes) => - _reflectionAccessAnalyzer.GetReflectionAccessDiagnostics (_location, type.Type, dynamicallyAccessedMemberTypes); + _reflectionAccessAnalyzer.GetReflectionAccessDiagnostics (_diagnosticContext.Location, type.Type, dynamicallyAccessedMemberTypes); } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs index 7f410b7e7c4bc..46793d6b448c6 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisAssignmentPattern.cs @@ -55,7 +55,7 @@ public TrimAnalysisAssignmentPattern Merge ( public void ReportDiagnostics (DataFlowAnalyzerContext context, Action reportDiagnostic) { - var location = Operation.Syntax.GetLocation (); + var diagnosticContext = new DiagnosticContext (Operation.Syntax.GetLocation (), reportDiagnostic); if (context.EnableTrimAnalyzer && !OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) && !FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) { @@ -66,9 +66,8 @@ public void ReportDiagnostics (DataFlowAnalyzerContext context, Action reportDiagnostic) { var location = Operation.Syntax.GetLocation (); - var typeNameResolver = new TypeNameResolver (context.Compilation); - var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeNameResolver); + var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic); if (context.EnableTrimAnalyzer && !OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) && !FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) { diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs index d98d172296aae..58c3c97c55020 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs @@ -43,8 +43,6 @@ internal sealed class TrimAnalysisVisitor : LocalDataFlowVisitor< FeatureChecksVisitor _featureChecksVisitor; - readonly TypeNameResolver _typeNameResolver; - public TrimAnalysisVisitor ( Compilation compilation, LocalStateAndContextLattice, FeatureContextLattice> lattice, @@ -59,7 +57,6 @@ public TrimAnalysisVisitor ( _multiValueLattice = lattice.LocalStateLattice.Lattice.ValueLattice; TrimAnalysisPatterns = trimAnalysisPatterns; _featureChecksVisitor = new FeatureChecksVisitor (dataFlowAnalyzerContext); - _typeNameResolver = new TypeNameResolver (compilation); } public override FeatureChecksValue GetConditionValue (IOperation branchValueOperation, StateValue state) @@ -294,7 +291,7 @@ public override MultiValue HandleMethodCall ( // Especially with DAM on type, this can lead to incorrectly analyzed code (as in unknown type which leads // to noise). ILLink has the same problem currently: https://github.com/dotnet/linker/issues/1952 - HandleCall (_typeNameResolver, operation, OwningSymbol, calledMethod, instance, arguments, Location.None, null, _multiValueLattice, out MultiValue methodReturnValue); + HandleCall (operation, OwningSymbol, calledMethod, instance, arguments, Location.None, null, _multiValueLattice, out MultiValue methodReturnValue); // This will copy the values if necessary TrimAnalysisPatterns.Add (new TrimAnalysisMethodCallPattern ( @@ -318,7 +315,6 @@ public override MultiValue HandleMethodCall ( } internal static void HandleCall( - TypeNameResolver typeNameResolver, IOperation operation, ISymbol owningSymbol, IMethodSymbol calledMethod, @@ -329,7 +325,7 @@ internal static void HandleCall( ValueSetLattice multiValueLattice, out MultiValue methodReturnValue) { - var handleCallAction = new HandleCallAction (typeNameResolver, location, owningSymbol, operation, multiValueLattice, reportDiagnostic); + var handleCallAction = new HandleCallAction (location, owningSymbol, operation, multiValueLattice, reportDiagnostic); MethodProxy method = new (calledMethod); var intrinsicId = Intrinsics.GetIntrinsicIdForMethod (method); if (!handleCallAction.Invoke (method, instance, arguments, intrinsicId, out methodReturnValue)) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs deleted file mode 100644 index a91797e4316eb..0000000000000 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Reflection.Metadata; -using Microsoft.CodeAnalysis; -using ILLink.RoslynAnalyzer.TrimAnalysis; -using ILLink.Shared.TypeSystemProxy; -using System.Collections.Immutable; - -namespace ILLink.Shared.TrimAnalysis -{ - internal struct TypeNameResolver - { - readonly Compilation _compilation; - - static readonly TypeNameParseOptions s_typeNameParseOptions = new () { MaxNodes = int.MaxValue }; - - public TypeNameResolver (Compilation compilation) - { - _compilation = compilation; - } - - public bool TryResolveTypeName ( - string typeNameString, - in DiagnosticContext diagnosticContext, - out ITypeSymbol? type, - bool needsAssemblyName) - { - type = null; - if (!TypeName.TryParse (typeNameString.AsSpan (), out TypeName? typeName, s_typeNameParseOptions)) - return false; - - if (needsAssemblyName && !IsFullyQualified (typeName)) { - diagnosticContext.AddDiagnostic (DiagnosticId.TypeNameIsNotAssemblyQualified, typeNameString); - return false; - } - - type = ResolveTypeName (_compilation.Assembly, typeName); - return type != null; - - static bool IsFullyQualified (TypeName typeName) - { - if (typeName.AssemblyName is null) - return false; - - if (typeName.IsArray || typeName.IsPointer || typeName.IsByRef) - return IsFullyQualified (typeName.GetElementType ()); - - if (typeName.IsConstructedGenericType) { - foreach (var arg in typeName.GetGenericArguments ()) { - if (!IsFullyQualified (arg)) - return false; - } - } - - return true; - } - } - - ITypeSymbol? ResolveTypeName (IAssemblySymbol assembly, TypeName typeName) - { - if (typeName.IsSimple) - return GetSimpleType (assembly, typeName); - - if (typeName.IsConstructedGenericType) - return GetGenericType (assembly, typeName); - - if (typeName.IsArray || typeName.IsPointer || typeName.IsByRef) - { - if (ResolveTypeName (assembly, typeName.GetElementType ()) is not ITypeSymbol type) - return null; - - if (typeName.IsArray) - return typeName.IsSZArray ? _compilation.CreateArrayTypeSymbol (type) : _compilation.CreateArrayTypeSymbol (type, typeName.GetArrayRank ()); - - // Roslyn doesn't have a representation for byref types - // (the byrefness is considered part of the symbol, not its type) - if (typeName.IsByRef) - return null; - - if (typeName.IsPointer) - return _compilation.CreatePointerTypeSymbol (type); - - Debug.Fail ("Unreachable"); - return null; - } - - return null; - } - - private ITypeSymbol? GetSimpleType (IAssemblySymbol assembly, TypeName typeName) - { - IAssemblySymbol module = assembly; - if (typeName.AssemblyName is AssemblyNameInfo assemblyName) { - if (ResolveAssembly (assemblyName) is not IAssemblySymbol resolvedAssembly) - return null; - module = resolvedAssembly; - } - - if (GetSimpleTypeFromModule (typeName, module) is ITypeSymbol type) - return type; - - // The analyzer doesn't see the core library, so can't fall back to lookup up types in corelib. - return null; - } - - private static ITypeSymbol? GetSimpleTypeFromModule (TypeName typeName, IAssemblySymbol module) - { - string fullName = TypeNameHelpers.Unescape (typeName.FullName); - return module.GetTypeByMetadataName (fullName); - } - - private ITypeSymbol? GetGenericType (IAssemblySymbol assembly, TypeName typeName) - { - if (ResolveTypeName (assembly, typeName.GetGenericTypeDefinition ()) is not INamedTypeSymbol typeDefinition) - return null; - - ImmutableArray typeArguments = typeName.GetGenericArguments (); - ITypeSymbol[] instantiation = new ITypeSymbol[typeArguments.Length]; - for (int i = 0; i < typeArguments.Length; i++) - { - if (ResolveTypeName (assembly, typeArguments[i]) is not ITypeSymbol type) - return null; - instantiation[i] = type; - } - return typeDefinition.Construct (instantiation); - } - - IAssemblySymbol? ResolveAssembly (AssemblyNameInfo? assemblyName) - { - if (assemblyName is null) - return null; - - if (_compilation.Assembly.Name == assemblyName.Name) - return _compilation.Assembly; - - foreach (var metadataReference in _compilation.References) { - if (_compilation.GetAssemblyOrModuleSymbol (metadataReference) is not IAssemblySymbol asmSym) - continue; - if (asmSym.Name == assemblyName.Name) - return asmSym; - } - return null; - } - } -} diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs index 9468360d24111..1f7017bff311b 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs @@ -101,7 +101,7 @@ class C [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] static string M(Type t) { M2(t); - return "Foo, test"; + return "Foo"; } static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {} @@ -117,7 +117,7 @@ class C [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] static string M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) { M2(t); - return "Foo, test"; + return "Foo"; } static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {} @@ -187,7 +187,7 @@ class C [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] static string M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] Type t) { M2(t); - return "Foo, test"; + return "Foo"; } static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {} @@ -1067,7 +1067,7 @@ await VerifyDynamicallyAccessedMembersCodeFix ( } [Fact] - public async Task CodeFix_IL2075_ReturnAttributeLeavesOnCodeFix () + public async Task CodeFix_IL2075_ReutrnAttributeLeavesOnCodeFix () { var test = $$$""" namespace System @@ -1078,7 +1078,7 @@ class C : TestSystemTypeBase public static string Main() { GetC().GetMethod("Foo"); - return "Foo, test"; + return "Foo"; } private static Type GetC () @@ -1097,7 +1097,7 @@ class C : TestSystemTypeBase public static string Main() { GetC().GetMethod("Foo"); - return "Foo, test"; + return "Foo"; } [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] @@ -1608,7 +1608,7 @@ public static void Main() private string M1() { M2(this); - return "Foo, test"; + return "Foo"; } private static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) @@ -1633,7 +1633,7 @@ public static void Main() private string M1() { M2(this); - return "Foo, test"; + return "Foo"; } private static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) @@ -1676,7 +1676,7 @@ class C : TestSystemTypeBase { public static void Main() { - new C().M1("Foo, test"); + new C().M1("Foo"); } [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] @@ -1700,7 +1700,7 @@ class C : TestSystemTypeBase { public static void Main() { - new C().M1("Foo, test"); + new C().M1("Foo"); } [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] @@ -1736,7 +1736,7 @@ await VerifyDynamicallyAccessedMembersCodeFix ( // /0/Test0.cs(193,4): warning IL2065: Value passed to implicit 'this' parameter of method 'System.C.M1()' can not be statically determined // and may not meet 'DynamicallyAccessedMembersAttribute' requirements. VerifyCS.Diagnostic(DiagnosticId.ImplicitThisCannotBeStaticallyDetermined) - .WithSpan(193, 4, 193, 27) + .WithSpan(193, 4, 193, 21) .WithArguments("System.C.M1(String)") }); } @@ -1858,7 +1858,7 @@ class C : TestSystemTypeBase { public static void Main() { - new C().M1("Foo, test"); + new C().M1("Foo"); } [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] @@ -1878,7 +1878,7 @@ class C : TestSystemTypeBase { public static void Main() { - new C().M1("Foo, test"); + new C().M1("Foo"); } [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] @@ -1907,7 +1907,7 @@ await VerifyDynamicallyAccessedMembersCodeFix ( // /0/Test0.cs(193,4): warning IL2065: Value passed to implicit 'this' parameter of method 'System.C.M1()' can not be statically determined // and may not meet 'DynamicallyAccessedMembersAttribute' requirements. VerifyCS.Diagnostic(DiagnosticId.ImplicitThisCannotBeStaticallyDetermined) - .WithSpan(193, 4, 193, 27) + .WithSpan(193, 4, 193, 21) .WithArguments("System.C.M1(String)") }); } diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs index 58c57155aaada..1a51ba8408328 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs @@ -44,7 +44,7 @@ public static (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel var sources = new List () { src }; sources.AddRange (additionalSources ?? Array.Empty ()); var comp = CSharpCompilation.Create ( - assemblyName: "test", + assemblyName: Guid.NewGuid ().ToString ("N"), syntaxTrees: sources, references: SourceGenerators.Tests.LiveReferencePack.GetMetadataReferences().Add(mdRef).AddRange(additionalReferences), new CSharpCompilationOptions (consoleApplication ? OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary)); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AssemblyQualifiedNameDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AssemblyQualifiedNameDataflow.cs index 85330b4b7e60f..c32a472e625bc 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AssemblyQualifiedNameDataflow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AssemblyQualifiedNameDataflow.cs @@ -58,7 +58,7 @@ static void TestConstructors () RequireNothing (type); } - [ExpectedWarning ("IL2122", "Type 'System.Invalid.TypeName' is not assembly qualified. Type name strings used for dynamically accessing a type should be assembly qualified.")] + [ExpectedWarning ("IL2122", "Type 'System.Invalid.TypeName' is not assembly qualified. " + "Type name strings used for dynamically accessing a type should be assembly qualified.", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void TestUnqualifiedTypeNameWarns () { RequirePublicConstructors ("System.Invalid.TypeName"); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeConstructorDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeConstructorDataflow.cs index 04746ae30a4dd..b6d5eed8170f1 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeConstructorDataflow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeConstructorDataflow.cs @@ -25,7 +25,7 @@ class AttributeConstructorDataflow [KeepsPublicMethods ("Mono.Linker.Tests.Cases.DataFlow.AttributeConstructorDataflow+ClassWithKeptPublicMethods, test")] [KeepsPublicFields (null, null)] [TypeArray (new Type[] { typeof (AttributeConstructorDataflow) })] - [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--")] + [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/linker/issues/2273")] public static void Main () { typeof (AttributeConstructorDataflow).GetMethod ("Main").GetCustomAttribute (typeof (KeepsPublicConstructorAttribute)); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeFieldDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeFieldDataflow.cs index bb3c1d4bebb1a..e7683d1f33806 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeFieldDataflow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributeFieldDataflow.cs @@ -40,7 +40,7 @@ public static void TestKeepsPublicMethods () [Kept] [KeptAttributeAttribute (typeof (KeepsPublicMethodsAttribute))] - [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--")] + [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] [KeepsPublicMethods (TypeName = "Mono.Linker.Tests.Cases.DataFlow.AttributeFieldDataflow+ClassWithKeptPublicMethods, test")] public static void TestKeepsPublicMethodsString () { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs index ba094199740ea..de7186fc7f54a 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs @@ -55,7 +55,8 @@ public static void TestKeepsPublicMethods () [Kept] [KeptAttributeAttribute (typeof (KeepsPublicMethodsAttribute))] - [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethodsKeptByName--")] + // Trimmer/NativeAot only for now + [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethodsKeptByName--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] [KeepsPublicMethods (TypeName = "Mono.Linker.Tests.Cases.DataFlow.AttributePropertyDataflow+AttributesOnMethod+ClassWithKeptPublicMethodsKeptByName, test")] public static void TestKeepsPublicMethodsByName () { @@ -210,7 +211,8 @@ class AttributeWithConditionalExpression { [Kept] [KeptAttributeAttribute (typeof (KeepsPublicMethodsAttribute))] - [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--")] + // Trimmer/NativeAot only for now + [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] [KeepsPublicMethods (TypeName = 1 + 1 == 2 ? "Mono.Linker.Tests.Cases.DataFlow.AttributePropertyDataflow+AttributeWithConditionalExpression+ClassWithKeptPublicMethodsKeptByName, test" : null)] public static void Test () { @@ -222,7 +224,7 @@ public static void Test () // where the owning symbol is not a method. [Kept] [KeptAttributeAttribute (typeof (KeepsPublicMethodsAttribute))] - [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--")] + [ExpectedWarning ("IL2026", "--ClassWithKeptPublicMethods--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] [KeepsPublicMethods (TypeName = 1 + 1 == 2 ? "Mono.Linker.Tests.Cases.DataFlow.AttributePropertyDataflow+AttributeWithConditionalExpression+ClassWithKeptPublicMethodsKeptByName, test" : null)] public static int field; diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs index 5809d06283532..f28802c174d11 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterWarningLocation.cs @@ -1372,7 +1372,7 @@ class AnnotatedString { static void MethodWithAnnotatedParameter ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] string typeName) { } - [ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod")] + [ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void AnnotatedParameter () { MethodWithAnnotatedParameter ("Mono.Linker.Tests.Cases.DataFlow.GenericParameterWarningLocation+MethodBody+TypeWithPrivateMethods`1[[" @@ -1380,7 +1380,7 @@ static void AnnotatedParameter () + "]], test"); } - [ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod")] + [ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] static string AnnotatedReturnValue () { @@ -1392,7 +1392,7 @@ static string AnnotatedReturnValue () [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] static string _annotatedField; - [ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod")] + [ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void AnnotatedField () { _annotatedField = "Mono.Linker.Tests.Cases.DataFlow.GenericParameterWarningLocation+MethodBody+TypeWithPrivateMethods`1[[" @@ -1410,7 +1410,7 @@ public static void Test () class TypeGetType { - [ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod")] + [ExpectedWarning ("IL2026", "TypeWithRUCMethod.PrivateRUCMethod", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void SpecificType () { Type.GetType ("Mono.Linker.Tests.Cases.DataFlow.GenericParameterWarningLocation+MethodBody+TypeWithPrivateMethods`1[[" diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GetTypeDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GetTypeDataFlow.cs index 5bb64c5e9c2cf..31ef05ef3e55b 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GetTypeDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GetTypeDataFlow.cs @@ -168,8 +168,8 @@ public void Method1 () { } [RequiresUnreferencedCode ("--Method2--")] public void Method2 () { } - [ExpectedWarning ("IL2026", "--Method1--")] - [ExpectedWarning ("IL2026", "--Method2--")] + [ExpectedWarning ("IL2026", "--Method1--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] + [ExpectedWarning ("IL2026", "--Method2--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] public static void Test () { Type.GetType ("Mono.Linker.Tests.Cases.DataFlow." + nameof (GetTypeDataFlow) + "+" + nameof (TypeWithWarnings)).RequiresPublicMethods (); @@ -183,7 +183,7 @@ class OverConstTypeName [RequiresUnreferencedCode ("--Method1--")] public void Method1 () { } - [ExpectedWarning ("IL2026", "--Method1--")] + [ExpectedWarning ("IL2026", "--Method1--", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] public static void Test () { Type.GetType (s_ConstTypeName).RequiresPublicMethods (); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs index 2d0e72e03af83..9dc4df8ff039c 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs @@ -67,7 +67,6 @@ public static void TestNull () { const string reflectionTypeKeptString = null; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] @@ -75,180 +74,119 @@ public static void TestEmptyString () { const string reflectionTypeKeptString = ""; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (Full))] public class Full { } [Kept] - [ExpectedWarning ("IL2026", nameof (Full))] public static void TestFullString () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Full, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (Generic))] public class Generic { } [Kept] - [ExpectedWarning ("IL2026", "Generic")] public static void TestGenericString () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Generic`1, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (GenericInstantiation))] public class GenericInstantiation { } [Kept] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (GenericArgument))] public class GenericArgument { } [Kept] - [ExpectedWarning ("IL2026", "GenericInstantiation")] public static void TestGenericInstantiation () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericInstantiation`1[[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericArgument]]"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (GenericInstantiationFullString))] public class GenericInstantiationFullString { } [Kept] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (GenericArgumentFullString))] public class GenericArgumentFullString { } [Kept] - [ExpectedWarning ("IL2026", "GenericInstantiationFullString")] public static void TestGenericInstantiationFullString () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericInstantiationFullString`1[" + "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericArgumentFullString, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]" + "], test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (GenericInstantiationOverCoreLib))] public class GenericInstantiationOverCoreLib { } [Kept] - [ExpectedWarning ("IL2026", "GenericInstantiationOverCoreLib", Tool.Trimmer | Tool.NativeAot, "Analyzer can't resolve type names from corelib")] public static void TestGenericInstantiationOverCoreLib () { // Note: the argument type should not be assembly-qualified for this test, which is checking that // we can resolve non-assembly-qualified generic argument types from corelib. const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericInstantiationOverCoreLib`1[[System.String]], test"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (FullConst))] public class FullConst { } [Kept] - [ExpectedWarning ("IL2026", nameof (FullConst))] public static void TestFullStringConst () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+FullConst, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (TypeAsmName))] public class TypeAsmName { } [Kept] - [ExpectedWarning ("IL2026", nameof (TypeAsmName))] public static void TestTypeAsmName () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+TypeAsmName, test"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (AType))] public class AType { } [Kept] - [ExpectedWarning ("IL2026", nameof (AType))] public static void TestType () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AType"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] -#if !NATIVEAOT - [KeptMember (".ctor()")] -#endif - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (Pointer))] public class Pointer { } [Kept] - [ExpectedWarning ("IL2026", nameof (Pointer), Tool.Trimmer, "https://github.com/dotnet/runtime/issues/106214")] public static void TestPointer () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Pointer*"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] -#if !NATIVEAOT - [KeptMember (".ctor()")] -#endif - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (Reference))] public class Reference { } [Kept] - [ExpectedWarning ("IL2026", nameof (Reference), Tool.Trimmer, "https://github.com/dotnet/runtime/issues/106214")] public static void TestReference () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Reference&"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (Array))] public class Array { } [Kept] @@ -256,12 +194,9 @@ public static void TestArray () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Array[]"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (ArrayOfArray))] public class ArrayOfArray { } [Kept] @@ -269,13 +204,10 @@ public static void TestArrayOfArray () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+ArrayOfArray[][]"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (MultiDimensionalArray))] public class MultiDimensionalArray { } [Kept] @@ -283,12 +215,9 @@ public static void TestMultiDimensionalArray () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+MultiDimensionalArray[,]"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (MultiDimensionalArrayFullString))] public class MultiDimensionalArrayFullString { } [Kept] @@ -296,12 +225,9 @@ public static void TestMultiDimensionalArrayFullString () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+MultiDimensionalArrayFullString[,], test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (MultiDimensionalArrayAsmName))] public class MultiDimensionalArrayAsmName { } [Kept] @@ -309,7 +235,6 @@ public static void TestMultiDimensionalArrayAsmName () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+MultiDimensionalArrayAsmName[,], test"; var typeKept = Type.GetType (reflectionTypeKeptString, false); - RequireConstructor (typeKept); } [Kept] @@ -319,9 +244,6 @@ class Nested1 class N2 { [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (N3))] class N3 { } @@ -329,39 +251,26 @@ class N3 } [Kept] - [ExpectedWarning ("IL2026", "N3")] static void TestDeeplyNested () { var typeKept = Type.GetType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+Nested1+N2+N3"); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] class TypeOfToKeep { } [Kept] static void TestTypeOf () { var typeKept = typeof (TypeOfToKeep); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (TypeFromBranchA))] class TypeFromBranchA { } - [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (TypeFromBranchB))] class TypeFromBranchB { } [Kept] - [ExpectedWarning ("IL2026", nameof (TypeFromBranchA))] - [ExpectedWarning ("IL2026", nameof (TypeFromBranchB))] static void TestTypeFromBranch (int b) { string name = null; @@ -377,7 +286,6 @@ static void TestTypeFromBranch (int b) } var typeKept = Type.GetType (name); - RequireConstructor (typeKept); } public class CaseInsensitive { } @@ -400,7 +308,6 @@ static void TestTypeUsingCaseUnknownByTheLinker () bool hideCase = GetCase (); const string reflectionTypeKeptString = "mono.linker.tests.cases.reflection.TypeUsedViaReflection+CaseUnknown, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; var typeKept = Type.GetType (reflectionTypeKeptString, false, hideCase); - RequireConstructor (typeKept); } [Kept] @@ -420,38 +327,27 @@ static void TestTypeUsingCaseUnknownByTheLinker2 () { const string reflectionTypeKeptString = "mono.linker.tests.cases.reflection.TypeUsedViaReflection+CaseUnknown2, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; var typeKept = Type.GetType (reflectionTypeKeptString, false, fieldHideCase); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (OverloadWith3Parameters))] public class OverloadWith3Parameters { } [Kept] - [ExpectedWarning ("IL2026", nameof (OverloadWith3Parameters))] static void TestTypeOverloadWith3Parameters () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+OverloadWith3Parameters"; var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (OverloadWith4Parameters))] public class OverloadWith4Parameters { } [Kept] - [ExpectedWarning ("IL2026", nameof (OverloadWith4Parameters))] static void TestTypeOverloadWith4Parameters () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+OverloadWith4Parameters"; var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly, false); - RequireConstructor (typeKept); } public class OverloadWith5ParametersWithIgnoreCase { } @@ -465,22 +361,16 @@ static void TestTypeOverloadWith5ParametersWithIgnoreCase () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+OverloadWith5ParametersWithIgnoreCase"; var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly, false, true); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (OverloadWith5ParametersWithIgnoreCase))] public class OverloadWith5ParametersWithoutIgnoreCase { } [Kept] - [ExpectedWarning ("IL2026", nameof (OverloadWith5ParametersWithIgnoreCase))] static void TestTypeOverloadWith5ParametersWithoutIgnoreCase () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+OverloadWith5ParametersWithoutIgnoreCase"; var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly, false, false); - RequireConstructor (typeKept); } /// @@ -525,22 +415,16 @@ static void TestUnknownIgnoreCase5Params (int num) const string reflectionTypeKeptString = "mono.linker.tests.cases.reflection.TypeUsedViaReflection+CaseUnknown2, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; bool unknownValue = num + 1 == 1; var typeKept = Type.GetType (reflectionTypeKeptString, AssemblyResolver, GetTypeFromAssembly, false, unknownValue); - RequireConstructor (typeKept); } [Kept] - [KeptMember (".ctor()")] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode ("GenericTypeWithAnnotations_OuterType")] public class GenericTypeWithAnnotations_OuterType< [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.NonPublicProperties)] T> + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.NonPublicProperties)] T> { } [Kept] - [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] - [RequiresUnreferencedCode (nameof (GenericTypeWithAnnotations_InnerType))] public class GenericTypeWithAnnotations_InnerType { [Kept] @@ -551,16 +435,12 @@ private static void PrivateMethod () { } } [Kept] - [ExpectedWarning ("IL2026", "GenericTypeWithAnnotations_OuterType")] - [ExpectedWarning ("IL2026", nameof (GenericTypeWithAnnotations_InnerType), "PrivateProperty.get")] - [ExpectedWarning ("IL2026", nameof (GenericTypeWithAnnotations_InnerType), "PrivateProperty.set")] static void TestGenericTypeWithAnnotations () { const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericTypeWithAnnotations_OuterType`1[" + "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericTypeWithAnnotations_InnerType, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]" + "], test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; - var typeKept = Type.GetType (reflectionTypeKeptString); - RequireConstructor (typeKept); + Type.GetType (reflectionTypeKeptString); } [Kept] @@ -613,36 +493,33 @@ static void TestInvalidTypeCombination () static void TestEscapedTypeName () { var typeKept = Type.GetType ("Library.Not\\+Nested, EscapedTypeNames"); - RequireConstructor (typeKept); typeKept = Type.GetType ("Library.Not\\+Nested+Nes\\\\ted, EscapedTypeNames"); - RequireConstructor (typeKept); typeKept = Type.GetType ("Library.Not\\+Nested+Nes/ted, EscapedTypeNames"); - RequireConstructor (typeKept); } [Kept] class AssemblyTypeResolutionBehavior { [Kept] - [ExpectedWarning ("IL2122")] + [ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void TestRequireTypeInSameAssemblyAsGetType () { RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.Dependencies.TypeDefinedInSameAssemblyAsGetType"); } [Kept] - [ExpectedWarning ("IL2122")] + [ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void TestRequireTypeInSameAssemblyAsCallToRequireType () { RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+TypeDefinedInSameAssemblyAsCallToRequireType"); } [Kept] - [ExpectedWarning ("IL2122")] + [ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void TestRequireTypeWithNonAssemblyQualifiedGenericArguments () { RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+Generic`1[[System.Int32]], test"); } [Kept] - [ExpectedWarning ("IL2122")] + [ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void TestRequireTypeWithNonAssemblyQualifiedArrayType () { RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+Generic`1[" + "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+ArrayElementGenericArgumentType]" @@ -650,7 +527,7 @@ static void TestRequireTypeWithNonAssemblyQualifiedArrayType () { } [Kept] - [ExpectedWarning ("IL2122")] + [ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void TestRequireTypeWithNonAssemblyQualifiedPointerType () { RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+Generic`1[" + "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+PointerElementGenericArgumentType]" @@ -658,7 +535,7 @@ static void TestRequireTypeWithNonAssemblyQualifiedPointerType () { } [Kept] - [ExpectedWarning ("IL2122")] + [ExpectedWarning ("IL2122", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/95118")] static void TestRequireTypeWithNonAssemblyQualifiedByRefType () { RequireHelper.RequireType ("Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+Generic`1[" + "[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+AssemblyTypeResolutionBehavior+ByRefElementGenericArgumentType]" @@ -685,11 +562,5 @@ class PointerElementGenericArgumentType {} class ByRefElementGenericArgumentType {} } - - [Kept] - static void RequireConstructor ( - [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type type) { } } } 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 6cb174eccbc44..0bcd402179bce 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -1079,6 +1079,7 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge int? unexpectedWarningCodeNumber = unexpectedWarningCode == null ? null : int.Parse (unexpectedWarningCode.Substring (2)); + MessageContainer? unexpectedWarningMessage = null; foreach (var mc in unmatchedMessages) { if (mc.Category != MessageCategory.Warning) continue; @@ -1090,7 +1091,12 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge if (attrProvider is IMemberDefinition attrMember && (mc.Origin?.Provider is IMemberDefinition member) && member.FullName.Contains (attrMember.FullName) != true) continue; - unexpectedMessageWarnings.Add ($"Unexpected warning found: {mc}"); + unexpectedWarningMessage = mc; + break; + } + + if (unexpectedWarningMessage is not null) { + unexpectedMessageWarnings.Add ($"Unexpected warning found: {unexpectedWarningMessage}"); } } From ce27822ef979b2b59202c0f441df10aef063779f Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 15 Aug 2024 18:03:54 -0700 Subject: [PATCH 21/27] Don't warn on methods implementing iface methods if the iface is not explicitly implemented --- .../src/linker/Linker.Steps/MarkStep.cs | 9 +- .../ValidateVirtualMethodAnnotationsStep.cs | 5 + .../illink/src/linker/Linker/Annotations.cs | 2 +- .../Linker/InterfaceImplementationChain.cs | 6 + .../src/linker/Linker/MethodBodyScanner.cs | 2 - .../src/linker/Linker/OverrideInformation.cs | 4 + .../Linker/RuntimeInterfaceImplementation.cs | 5 + .../illink/src/linker/Linker/TypeMapInfo.cs | 2 +- .../RequiresCapabilityTests.cs | 6 + ...ProvidesInterfaceMethodRequiresMismatch.cs | 166 ++++++++++++++++++ ...actBaseWithNoMethodsImplementsInterface.il | 101 +++++++++++ .../TestCasesRunner/TypeMapInfoValidation.cs | 4 +- 12 files changed, 298 insertions(+), 14 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 96e770d116810..193596246d5a7 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -2324,8 +2324,6 @@ void MarkNamedProperty (TypeDefinition type, string property_name, in Dependency void MarkInterfaceImplementations (TypeDefinition type) { var ifaces = Annotations.GetRuntimeInterfaces (type); - if (ifaces is null) - return; foreach (var runtimeInterface in ifaces) { // Only mark interface implementations of interface types that have been marked. // This enables stripping of interfaces that are never used @@ -2526,10 +2524,7 @@ void MarkCustomMarshalerGetInstance (TypeDefinition type, in DependencyInfo reas void MarkICustomMarshalerMethods (TypeDefinition inputType, in DependencyInfo reason, MessageOrigin origin) { var runtimeInterfaces = Annotations.GetRuntimeInterfaces (inputType); - if (runtimeInterfaces is null) - return; foreach (var runtimeInterface in runtimeInterfaces) { - var iface_type = runtimeInterface.InterfaceTypeDefinition; if (false == iface_type?.IsTypeOf ("System.Runtime.InteropServices", "ICustomMarshaler")) continue; @@ -3233,9 +3228,7 @@ void MarkRuntimeInterfaceImplementation (MethodDefinition method, MethodReferenc return; var interfaceToBeImplemented = ov.DeclaringType; var ifaces = Annotations.GetRuntimeInterfaces (method.DeclaringType); - if (ifaces is null) - return; - Debug.Assert (ifaces.Value.SingleOrDefault (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceToBeImplemented, Context)) is not null); + Debug.Assert (ifaces.SingleOrDefault (i => TypeReferenceEqualityComparer.AreEqual (i.InflatedInterfaceType, interfaceToBeImplemented, Context)) is not null); foreach (var iface in ifaces) { if (TypeReferenceEqualityComparer.AreEqual (iface.InflatedInterfaceType, interfaceToBeImplemented, Context)) { MarkRuntimeInterfaceImplementation (iface, new MessageOrigin (method.DeclaringType)); diff --git a/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs b/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs index 4d68e20620e26..820d5ca1adc32 100644 --- a/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/ValidateVirtualMethodAnnotationsStep.cs @@ -16,6 +16,8 @@ protected override void Process () var baseOverrideInformations = annotations.GetBaseMethods (method); if (baseOverrideInformations != null) { foreach (var baseOv in baseOverrideInformations) { + if (baseOv.IsOverrideOfInterfaceMember && !baseOv.RuntimeInterfaceImplementation.HasExplicitImplementation) + continue; annotations.FlowAnnotations.ValidateMethodAnnotationsAreSame (baseOv); ValidateMethodRequiresUnreferencedCodeAreSame (baseOv); } @@ -30,6 +32,9 @@ protected override void Process () if (annotations.VirtualMethodsWithAnnotationsToValidate.Contains (overrideInformation.Override)) continue; + if (overrideInformation.IsOverrideOfInterfaceMember && !overrideInformation.RuntimeInterfaceImplementation.HasExplicitImplementation) + continue; + annotations.FlowAnnotations.ValidateMethodAnnotationsAreSame (overrideInformation); ValidateMethodRequiresUnreferencedCodeAreSame (overrideInformation); } diff --git a/src/tools/illink/src/linker/Linker/Annotations.cs b/src/tools/illink/src/linker/Linker/Annotations.cs index 569e69ade0d2a..ba8ad7330f12a 100644 --- a/src/tools/illink/src/linker/Linker/Annotations.cs +++ b/src/tools/illink/src/linker/Linker/Annotations.cs @@ -707,7 +707,7 @@ public void EnqueueVirtualMethod (MethodDefinition method) VirtualMethodsWithAnnotationsToValidate.Add (method); } - internal ImmutableArray? GetRuntimeInterfaces (TypeDefinition type) + internal ImmutableArray GetRuntimeInterfaces (TypeDefinition type) { return TypeMapInfo.GetRecursiveInterfaces (type); } diff --git a/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs b/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs index dda9dbe78591b..f0e93e0668725 100644 --- a/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs +++ b/src/tools/illink/src/linker/Linker/InterfaceImplementationChain.cs @@ -19,8 +19,14 @@ internal sealed class InterfaceImplementationChain /// public ImmutableArray InterfaceImplementations { get; } + /// + /// Returns true if the interface implementation is directly on the implementing type. + /// + public bool IsExplicitInterfaceImplementation => InterfaceImplementations.Length == 1; + public InterfaceImplementationChain (TypeReference typeWithInterfaceImplementation, ImmutableArray interfaceImplementation) { + Debug.Assert (interfaceImplementation.Length > 0); TypeWithInterfaceImplementation = typeWithInterfaceImplementation; InterfaceImplementations = interfaceImplementation; } diff --git a/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs b/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs index 41e1f1464506b..bf2fdb5d5f9a9 100644 --- a/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs +++ b/src/tools/illink/src/linker/Linker/MethodBodyScanner.cs @@ -142,8 +142,6 @@ HashSet AllPossibleStackTypes (MethodIL methodIL) void AddMatchingInterfaces (HashSet<(RuntimeInterfaceImplementation, TypeDefinition)> results, TypeDefinition type, TypeDefinition[] interfaceTypes) { var runtimeInterfaces = context.Annotations.GetRuntimeInterfaces (type); - if (runtimeInterfaces is null) - return; foreach (var iface in runtimeInterfaces) { if (interfaceTypes.Contains (iface.InterfaceTypeDefinition)) { results.Add ((iface, type)); diff --git a/src/tools/illink/src/linker/Linker/OverrideInformation.cs b/src/tools/illink/src/linker/Linker/OverrideInformation.cs index f4f667b20b2b2..fd03529a704c8 100644 --- a/src/tools/illink/src/linker/Linker/OverrideInformation.cs +++ b/src/tools/illink/src/linker/Linker/OverrideInformation.cs @@ -38,5 +38,9 @@ public TypeDefinition? InterfaceType [MemberNotNullWhen (true, nameof (RuntimeInterfaceImplementation))] public bool IsOverrideOfInterfaceMember => RuntimeInterfaceImplementation != null; + + [MemberNotNullWhen (true, nameof (RuntimeInterfaceImplementation))] + internal bool IsInterfaceMethodProvidedByBaseType + => IsOverrideOfInterfaceMember && (Override.DeclaringType != RuntimeInterfaceImplementation.Implementor); } } diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs index 5e770cb0e275c..433a39e01be94 100644 --- a/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfaceImplementation.cs @@ -41,6 +41,11 @@ internal sealed class RuntimeInterfaceImplementation /// public TypeDefinition? InterfaceTypeDefinition { get; } + /// + /// Returns true if an interface implementation is directly on the implementing type. + /// + public bool HasExplicitImplementation => InterfaceImplementationChains.Any (c => c.IsExplicitInterfaceImplementation && c.TypeWithInterfaceImplementation == Implementor); + public RuntimeInterfaceImplementation (TypeDefinition implementor, TypeReference interfaceType, TypeDefinition? interfaceTypeDefinition, IEnumerable interfaceImplementations) { Implementor = implementor; diff --git a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs index d552e96f6ff06..98fdd5b56ebea 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapInfo.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapInfo.cs @@ -130,7 +130,7 @@ protected virtual void MapType (TypeDefinition type) MapType (nested); } - internal ImmutableArray? GetRecursiveInterfaces (TypeDefinition type) + internal ImmutableArray GetRecursiveInterfaces (TypeDefinition type) { return _recursiveInterfaces.GetRuntimeInterfaceImplementations (type); } diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs index 3f722ad634e3d..db08bb45f445a 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs @@ -123,5 +123,11 @@ public Task SuppressRequires () { return RunTest (nameof (SuppressRequires)); } + + [Fact] + public Task BaseProvidesInterfaceMethodRequiresMismatch () + { + return RunTest (nameof (BaseProvidesInterfaceMethodRequiresMismatch)); + } } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs new file mode 100644 index 0000000000000..a3432ac34697e --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs @@ -0,0 +1,166 @@ +// 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.RequiresCapability +{ + [ExpectedNoWarnings] + [SkipKeptItemsValidation] + [SetupCompileBefore ("library.dll", new[] { "Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il" })] + [Define ("IL_ASSEMBLY_AVAILABLE")] + class BaseProvidesInterfaceMethodRequiresMismatch + { + [RequiresUnreferencedCode ("Message")] + public static void Main () + { + BaseDoesNotImplementINoRuc.Run (); + BaseDoesNotImplementIRuc.Run (); + BaseImplementsINoRuc.Run (); + ThreeLevelsInheritance.Run (); + DerivedOverridesImplementingMethod.Run (); +#if IL_ASSEMBLY_AVAILABLE + AbstractBaseWithNoMethodsImplementsInterface.Run (); +#endif + } + + interface INoRuc + { + void M (); + } + + interface IRuc + { + [RequiresUnreferencedCode ("Message")] + void MRuc (); + } + + public class BaseDoesNotImplementINoRuc + { + public class C + { + [RequiresUnreferencedCode ("Message")] + public void M () + { + } + } + + [ExpectedWarning ("IL2046", "C.M()", "INoRuc.M()")] + public class D : C, INoRuc + { + } + + public static void Run () + { + ((INoRuc) new D ()).M (); + } + } + + public class BaseDoesNotImplementIRuc + { + public class C + { + public void MRuc () { } + } + + [ExpectedWarning ("IL2046", "C.MRuc()", "IRuc.MRuc()")] + public class D : C, IRuc + { + } + + [RequiresUnreferencedCode ("Message")] + public static void Run () + { + ((IRuc) new D ()).MRuc (); + } + } + + public class BaseImplementsINoRuc + { + class C : INoRuc + { + [RequiresUnreferencedCode ("Message")] + [ExpectedWarning ("IL2046", "C.M()", "INoRuc.M()")] + public void M () + { + } + } + + [ExpectedWarning ("IL2046", "C.M()", "INoRuc.M()")] + class D : C, INoRuc + { + } + + class D2 : C + { + } + + public static void Run () + { + ((INoRuc) new D ()).M (); + ((INoRuc) new D2 ()).M (); + } + } + + public class ThreeLevelsInheritance + { + public class Base + { + [RequiresUnreferencedCode ("Message")] + public void M () { } + } + + [ExpectedWarning ("IL2046", "Base.M()", "INoRuc.M()")] + public class Middle : Base, INoRuc + { + } + + public class Derived : Middle + { + } + + public static void Run () + { + ((INoRuc) new Derived ()).M (); + } + } + + public class DerivedOverridesImplementingMethod + { + class C : INoRuc + { + [RequiresUnreferencedCode ("Message")] + [ExpectedWarning ("IL2046", "C.M()", "INoRuc.M()")] + public virtual void M () + { + } + } + + class D : C, INoRuc + { + [RequiresUnreferencedCode ("Message")] + [ExpectedWarning ("IL2046", "D.M()", "INoRuc.M()")] + public override void M () { } + } + + class D2 : C + { + [RequiresUnreferencedCode ("Message")] + public override void M () { } + } + + public static void Run () + { + ((INoRuc) new D ()).M (); + ((INoRuc) new D2 ()).M (); + } + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il new file mode 100644 index 0000000000000..cb8797d62f91a --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il @@ -0,0 +1,101 @@ +// 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 AbstractBaseWithNoMethodsImplementsInterface + extends [mscorlib]System.Object +{ + // Methods + .method public hidebysig static + void Run () cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute::.ctor(string) = ( + 01 00 03 52 55 43 00 00 + ) + // Method begins at RVA 0x2050 + // Code size 16 (0x10) + .maxstack 8 + + IL_0000: newobj instance void AbstractBaseWithNoMethodsImplementsInterface/Derived::.ctor() + IL_0005: castclass AbstractBaseWithNoMethodsImplementsInterface/I + IL_000a: callvirt instance void AbstractBaseWithNoMethodsImplementsInterface/I::M() + IL_000f: ret + } // end of method Program::Main + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2061 + // 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 + + + // Nested Types + .class nested assembly auto ansi abstract beforefieldinit Base + extends [mscorlib]System.Object + implements AbstractBaseWithNoMethodsImplementsInterface/I + { + // Methods + .method family hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2061 + // 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 Base::.ctor + + } // end of class Base + + .class interface nested assembly auto ansi abstract beforefieldinit I + { + // Methods + .method public hidebysig newslot abstract virtual + instance void M () cil managed + { + .custom instance void [mscorlib]System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute::.ctor(string) = ( + 01 00 03 52 55 43 00 00 // "RUC" + ) + } // end of method I::M + + } // end of class I + + .class nested assembly auto ansi beforefieldinit Derived + extends AbstractBaseWithNoMethodsImplementsInterface/Base + { + // Methods + .method public virtual hidebysig + instance void M () cil managed + { + // Method begins at RVA 0x2069 + .maxstack 8 + + IL_0000: ret + } // end of method Derived::M + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2075 + // Code size 7 (0x7) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void AbstractBaseWithNoMethodsImplementsInterface/Base::.ctor() + IL_0006: ret + } // end of method Derived::.ctor + + } // end of class Derived + +} // end of class Program diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs index e21e45d644888..d5249aa8ec89e 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TypeMapInfoValidation.cs @@ -17,11 +17,11 @@ internal class TypeMapInfoValidation public static IEnumerable ValidateRuntimeInterfaces (TypeMapInfo typeMapInfo, TypeDefinition typeDef, string expectedInterfaceName, IEnumerable expectedImplChain) { var runtimeInterfaces = typeMapInfo.GetRecursiveInterfaces (typeDef); - if (!runtimeInterfaces.HasValue) { + if (runtimeInterfaces is []) { yield return ($"Expected type `{typeDef}` to have runtime interface `{expectedInterfaceName}`, but it has none"); yield break; } - var runtimeInterface = runtimeInterfaces.Value.SingleOrDefault (i => i.InflatedInterfaceType.FullName == expectedInterfaceName); + var runtimeInterface = runtimeInterfaces.SingleOrDefault (i => i.InflatedInterfaceType.FullName == expectedInterfaceName); if (runtimeInterface == default) { yield return ($"Expected type `{typeDef}` to have runtime interface `{expectedInterfaceName}`"); yield break; From db02cfbf3f2ca7381dd42c3314dc672ef9dcdc83 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:50:41 -0700 Subject: [PATCH 22/27] Undo unrelated analyzer changes --- ...nterfaces.DefaultInterfaceMethodsTests.g.cs | 6 ------ ...ce.Interfaces.RecursiveInterfacesTests.g.cs | 18 ------------------ ...eProvidesInterfaceMethodRequiresMismatch.cs | 2 ++ 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs index dc2440a552f56..19189bfcd0170 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs @@ -63,12 +63,6 @@ public Task StaticDefaultInterfaceMethodOnStruct () return RunTest (allowMissingWarnings: true); } - [Fact] - public Task StaticDimOnDerivedInterface () - { - return RunTest (allowMissingWarnings: true); - } - [Fact] public Task StaticDimProvidedByUnreferencedIfaceInHierarchy () { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs index 1dbdf7bd5c9ac..35cc0b65c0417 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs @@ -9,12 +9,6 @@ public sealed partial class RecursiveInterfacesTests : LinkerTestBase protected override string TestSuiteName => "Inheritance.Interfaces.RecursiveInterfaces"; - [Fact] - public Task BaseTypeMarkedInterfaceDerivedNotKept () - { - return RunTest (allowMissingWarnings: true); - } - [Fact] public Task GenericInterfaceImplementedRecursively () { @@ -33,18 +27,6 @@ public Task OverrideOfRecursiveInterfaceIsRemoved () return RunTest (allowMissingWarnings: true); } - [Fact] - public Task RecursiveGenericInterfaces () - { - return RunTest (allowMissingWarnings: true); - } - - [Fact] - public Task RecursiveGenericInterfacesStatic () - { - return RunTest (allowMissingWarnings: true); - } - [Fact] public Task RecursiveInterfaceKept () { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs index a3432ac34697e..0ee0ad00f44e8 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/BaseProvidesInterfaceMethodRequiresMismatch.cs @@ -16,6 +16,8 @@ namespace Mono.Linker.Tests.Cases.RequiresCapability [SkipKeptItemsValidation] [SetupCompileBefore ("library.dll", new[] { "Dependencies/AbstractBaseWithNoMethodsImplementsInterface.il" })] [Define ("IL_ASSEMBLY_AVAILABLE")] + // + [LogContains ("IL2046: AbstractBaseWithNoMethodsImplementsInterface.Derived.M()", ProducedBy = Tool.None)] class BaseProvidesInterfaceMethodRequiresMismatch { [RequiresUnreferencedCode ("Message")] From d88b2195d57fce5ad19ff6ffe2c7618d358b861f Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:55:20 -0700 Subject: [PATCH 23/27] Undo unrelated formatting --- .../TestCasesRunner/ResultChecker.cs | 292 +++++++++--------- 1 file changed, 146 insertions(+), 146 deletions(-) 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 0bcd402179bce..07404a6ec617f 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -75,11 +75,11 @@ protected static void ValidateTypeRefsHaveValidAssemblyRefs (AssemblyDefinition } 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; - } + // 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}'"); } @@ -881,48 +881,48 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge switch (attr.AttributeType.Name) { case nameof (LogContainsAttribute): { - var expectedMessage = (string) attr.ConstructorArguments[0].Value; - - List matchedMessages; - if ((bool) attr.ConstructorArguments[1].Value) - matchedMessages = unmatchedMessages.Where (m => Regex.IsMatch (m.ToString (), expectedMessage)).ToList (); - else - matchedMessages = unmatchedMessages.Where (m => m.ToString ().Contains (expectedMessage)).ToList (); ; - if (matchedMessages.Count == 0) - missingMessageWarnings.Add ($"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}"); - - foreach (var matchedMessage in matchedMessages) - unmatchedMessages.Remove (matchedMessage); - } - break; + var expectedMessage = (string) attr.ConstructorArguments[0].Value; + + List matchedMessages; + if ((bool) attr.ConstructorArguments[1].Value) + matchedMessages = unmatchedMessages.Where (m => Regex.IsMatch (m.ToString (), expectedMessage)).ToList (); + else + matchedMessages = unmatchedMessages.Where (m => m.ToString ().Contains (expectedMessage)).ToList (); ; + if (matchedMessages.Count == 0) + missingMessageWarnings.Add ($"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}"); + + foreach (var matchedMessage in matchedMessages) + unmatchedMessages.Remove (matchedMessage); + } + break; case nameof (LogDoesNotContainAttribute): { - var unexpectedMessage = (string) attr.ConstructorArguments[0].Value; - foreach (var loggedMessage in unmatchedMessages) { - bool isRegex = (bool) attr.ConstructorArguments[1].Value; - bool foundMatch = isRegex - ? Regex.IsMatch (loggedMessage.ToString (), unexpectedMessage) - : loggedMessage.ToString ().Contains (unexpectedMessage); - - if (foundMatch) - unexpectedMessageWarnings.Add ($"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage.ToString ()}"); + var unexpectedMessage = (string) attr.ConstructorArguments[0].Value; + foreach (var loggedMessage in unmatchedMessages) { + bool isRegex = (bool) attr.ConstructorArguments[1].Value; + bool foundMatch = isRegex + ? Regex.IsMatch (loggedMessage.ToString (), unexpectedMessage) + : loggedMessage.ToString ().Contains (unexpectedMessage); + + if (foundMatch) + unexpectedMessageWarnings.Add ($"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage.ToString ()}"); + } } - } - break; + break; 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" }, _] + 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" }, _] @@ -930,143 +930,143 @@ void VerifyLoggedMessages (AssemblyDefinition original, TrimmingTestLogger logge // 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"); - int? sourceColumn = (int?) attr.GetPropertyValue ("SourceColumn"); - bool? isCompilerGeneratedCode = (bool?) attr.GetPropertyValue ("CompilerGeneratedCode"); + _ => throw new UnreachableException (), + }; + string fileName = (string) attr.GetPropertyValue ("FileName"); + int? sourceLine = (int?) attr.GetPropertyValue ("SourceLine"); + int? sourceColumn = (int?) attr.GetPropertyValue ("SourceColumn"); + bool? isCompilerGeneratedCode = (bool?) attr.GetPropertyValue ("CompilerGeneratedCode"); - int expectedWarningCodeNumber = int.Parse (expectedWarningCode.Substring (2)); - string expectedOrigin = null; - bool expectedWarningFound = false; + int expectedWarningCodeNumber = int.Parse (expectedWarningCode.Substring (2)); + string expectedOrigin = null; + bool expectedWarningFound = false; - foreach (var loggedMessage in unmatchedMessages) { + foreach (var loggedMessage in unmatchedMessages) { - if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber) - continue; + if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber) + continue; - bool messageNotFound = false; - foreach (var expectedMessage in expectedMessageContains) { - if (!loggedMessage.Text.Contains (expectedMessage)) { - messageNotFound = true; - break; + bool messageNotFound = false; + foreach (var expectedMessage in expectedMessageContains) { + if (!loggedMessage.Text.Contains (expectedMessage)) { + messageNotFound = true; + break; + } } - } - if (messageNotFound) - continue; - - if (fileName != null) { - if (loggedMessage.Origin == null) + if (messageNotFound) continue; - var actualOrigin = loggedMessage.Origin.Value; - if (actualOrigin.FileName != null) { - // Note: string.Compare(string, StringComparison) doesn't exist in .NET Framework API set - if (actualOrigin.FileName.IndexOf (fileName, StringComparison.OrdinalIgnoreCase) < 0) - continue; - - if (sourceLine != null && loggedMessage.Origin?.SourceLine != sourceLine.Value) + if (fileName != null) { + if (loggedMessage.Origin == null) continue; - if (sourceColumn != null && loggedMessage.Origin?.SourceColumn != sourceColumn.Value) - continue; - } else { - // The warning was logged with member/ILoffset, so it didn't have line/column info filled - // but it will be computed from PDBs, so instead compare it in a string representation - if (expectedOrigin == null) { - expectedOrigin = fileName; - if (sourceLine.HasValue) { - expectedOrigin += "(" + sourceLine.Value; - if (sourceColumn.HasValue) - expectedOrigin += "," + sourceColumn.Value; - expectedOrigin += ")"; + var actualOrigin = loggedMessage.Origin.Value; + if (actualOrigin.FileName != null) { + // Note: string.Compare(string, StringComparison) doesn't exist in .NET Framework API set + if (actualOrigin.FileName.IndexOf (fileName, StringComparison.OrdinalIgnoreCase) < 0) + continue; + + if (sourceLine != null && loggedMessage.Origin?.SourceLine != sourceLine.Value) + continue; + + if (sourceColumn != null && loggedMessage.Origin?.SourceColumn != sourceColumn.Value) + continue; + } else { + // The warning was logged with member/ILoffset, so it didn't have line/column info filled + // but it will be computed from PDBs, so instead compare it in a string representation + if (expectedOrigin == null) { + expectedOrigin = fileName; + if (sourceLine.HasValue) { + expectedOrigin += "(" + sourceLine.Value; + if (sourceColumn.HasValue) + expectedOrigin += "," + sourceColumn.Value; + expectedOrigin += ")"; + } } - } - string actualOriginString = actualOrigin.ToString () ?? ""; - if (!actualOriginString.EndsWith (expectedOrigin, StringComparison.OrdinalIgnoreCase)) + string actualOriginString = actualOrigin.ToString () ?? ""; + if (!actualOriginString.EndsWith (expectedOrigin, StringComparison.OrdinalIgnoreCase)) + continue; + } + } else if (isCompilerGeneratedCode == true) { + if (loggedMessage.Origin?.Provider is not IMemberDefinition memberDefinition) continue; - } - } else if (isCompilerGeneratedCode == true) { - if (loggedMessage.Origin?.Provider is not IMemberDefinition memberDefinition) - continue; - if (attrProvider is IMemberDefinition expectedMember) { - string actualName = memberDefinition.DeclaringType.FullName + "." + memberDefinition.Name; + if (attrProvider is IMemberDefinition expectedMember) { + string actualName = memberDefinition.DeclaringType.FullName + "." + memberDefinition.Name; - if (actualName.StartsWith (expectedMember.DeclaringType.FullName) && - (actualName.Contains ("<" + expectedMember.Name + ">") || - actualName.EndsWith ("get_" + expectedMember.Name) || - actualName.EndsWith ("set_" + expectedMember.Name))) { - expectedWarningFound = true; - unmatchedMessages.Remove (loggedMessage); - break; - } - if (memberDefinition is not MethodDefinition) - continue; - if (actualName.StartsWith (expectedMember.DeclaringType.FullName)) { - if (memberDefinition.Name == ".cctor" && - (expectedMember is FieldDefinition || expectedMember is PropertyDefinition)) { + if (actualName.StartsWith (expectedMember.DeclaringType.FullName) && + (actualName.Contains ("<" + expectedMember.Name + ">") || + actualName.EndsWith ("get_" + expectedMember.Name) || + actualName.EndsWith ("set_" + expectedMember.Name))) { expectedWarningFound = true; unmatchedMessages.Remove (loggedMessage); break; } - if (memberDefinition.Name == ".ctor" && - (expectedMember is FieldDefinition || expectedMember is PropertyDefinition || memberDefinition.DeclaringType.FullName == expectedMember.FullName)) { + if (memberDefinition is not MethodDefinition) + continue; + if (actualName.StartsWith (expectedMember.DeclaringType.FullName)) { + if (memberDefinition.Name == ".cctor" && + (expectedMember is FieldDefinition || expectedMember is PropertyDefinition)) { + expectedWarningFound = true; + unmatchedMessages.Remove (loggedMessage); + break; + } + if (memberDefinition.Name == ".ctor" && + (expectedMember is FieldDefinition || expectedMember is PropertyDefinition || memberDefinition.DeclaringType.FullName == expectedMember.FullName)) { + expectedWarningFound = true; + unmatchedMessages.Remove (loggedMessage); + break; + } + } + } else if (attrProvider is AssemblyDefinition expectedAssembly) { + // Allow assembly-level attributes to match warnings from compiler-generated Main + if (memberDefinition.Name == "
$" && + memberDefinition.DeclaringType.FullName == "Program" && + memberDefinition.DeclaringType.Module.Assembly.Name.Name == expectedAssembly.Name.Name) { expectedWarningFound = true; unmatchedMessages.Remove (loggedMessage); break; } } - } else if (attrProvider is AssemblyDefinition expectedAssembly) { - // Allow assembly-level attributes to match warnings from compiler-generated Main - if (memberDefinition.Name == "
$" && - memberDefinition.DeclaringType.FullName == "Program" && - memberDefinition.DeclaringType.Module.Assembly.Name.Name == expectedAssembly.Name.Name) { + continue; + } else { + if (LogMessageHasSameOriginMember (loggedMessage, attrProvider)) { expectedWarningFound = true; unmatchedMessages.Remove (loggedMessage); break; } + continue; } - continue; - } else { - if (LogMessageHasSameOriginMember (loggedMessage, attrProvider)) { - expectedWarningFound = true; - unmatchedMessages.Remove (loggedMessage); - break; - } - continue; + + expectedWarningFound = true; + unmatchedMessages.Remove (loggedMessage); + break; } - expectedWarningFound = true; - unmatchedMessages.Remove (loggedMessage); - break; + var expectedOriginString = fileName == null + ? attrProvider switch { + MethodDefinition method => method.GetDisplayName (), + IMemberDefinition member => member.FullName, + AssemblyDefinition asm => asm.Name.Name, + _ => throw new NotImplementedException () + } + ": " + : ""; + + if (!expectedWarningFound) + missingMessageWarnings.Add ($"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" + + $"warning {expectedWarningCode}: {expectedOriginString}" + + $"and message containing {string.Join (" ", expectedMessageContains.Select (m => "'" + m + "'"))}, " + + $"but no such message was found."); } - - var expectedOriginString = fileName == null - ? attrProvider switch { - MethodDefinition method => method.GetDisplayName (), - IMemberDefinition member => member.FullName, - AssemblyDefinition asm => asm.Name.Name, - _ => throw new NotImplementedException () - } + ": " - : ""; - - if (!expectedWarningFound) - missingMessageWarnings.Add ($"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" + - $"warning {expectedWarningCode}: {expectedOriginString}" + - $"and message containing {string.Join (" ", expectedMessageContains.Select (m => "'" + m + "'"))}, " + - $"but no such message was found."); - } - break; + break; case nameof (ExpectedNoWarningsAttribute): { - // Postpone processing of negative checks, to make it possible to mark some warnings as expected (will be removed from the list above) - // and then do the negative check on the rest. - expectedNoWarningsAttributes.Add ((attrProvider, attr)); - break; - } + // Postpone processing of negative checks, to make it possible to mark some warnings as expected (will be removed from the list above) + // and then do the negative check on the rest. + expectedNoWarningsAttributes.Add ((attrProvider, attr)); + break; + } } } } From 259d734246d8bb7eebf8ec63b20814c9376acf98 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:58:32 -0700 Subject: [PATCH 24/27] Revert suppression changes --- .../src/ILLink/ILLink.Suppressions.xml | 50 +------------------ 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml index 8df6b6dd9fa8b..6e691bf6bc961 100644 --- a/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml +++ b/src/libraries/System.Reflection.Context/src/ILLink/ILLink.Suppressions.xml @@ -523,53 +523,5 @@ member M:System.Reflection.Context.Delegation.DelegatingType.GetInterface(System.String,System.Boolean) - - ILLink - IL2094 - member - M:System.Reflection.Context.Projection.ProjectingType.GetField(System.String,System.Reflection.BindingFlags) - - - ILLink - IL2094 - member - M:System.Reflection.Context.Projection.ProjectingType.GetFields(System.Reflection.BindingFlags) - - - ILLink - IL2094 - member - M:System.Reflection.Context.Projection.ProjectingType.GetMembers(System.Reflection.BindingFlags) - - - ILLink - IL2094 - member - M:System.Reflection.Context.Projection.ProjectingType.GetMethods(System.Reflection.BindingFlags) - - - ILLink - IL2094 - member - M:System.Reflection.Context.Projection.ProjectingType.GetProperties(System.Reflection.BindingFlags) - - - ILLink - IL2094 - member - M:System.Reflection.Context.Projection.ProjectingType.GetDefaultMembers() - - - ILLink - IL2094 - member - M:System.Reflection.Context.Custom.CustomType.GetProperties(System.Reflection.BindingFlags) - - - ILLink - IL2094 - member - M:System.Reflection.Context.Custom.CustomType.GetMethods(System.Reflection.BindingFlags) - - + \ No newline at end of file From 1d769b2932207bd6ba0eae552fd4022cda1b71ae Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:06:00 -0700 Subject: [PATCH 25/27] Undo unrelated formatting --- .../DataFlow/GenericParameterDataFlow.cs | 2 +- .../TestCasesRunner/AssemblyChecker.cs | 38 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs index c36de63d7f892..2cbd62f6d4343 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs @@ -915,7 +915,7 @@ static void TestGenericParameterFlowsToDelegateMethodDeclaringTypeInstance () Action a = instance.InstanceMethod; } - class DelegateMethodTypeRequiresFields<[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] T> + class DelegateMethodTypeRequiresFields<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T> { public static void Method () { 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 f4cd4f8ad45d1..893aca48a5eb4 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -757,26 +757,26 @@ static string FormatInstruction (Instruction instr) throw new NotImplementedException (instr.Operand.GetType ().ToString ()); default: { - string operandString = null; - switch (instr.OpCode.OperandType) { - case OperandType.InlineField: - case OperandType.InlineMethod: - case OperandType.InlineType: - case OperandType.InlineTok: - operandString = instr.Operand switch { - FieldReference fieldRef => fieldRef.FullName, - MethodReference methodRef => methodRef.FullName, - TypeReference typeRef => typeRef.FullName, - _ => null - }; - break; - } + string operandString = null; + switch (instr.OpCode.OperandType) { + case OperandType.InlineField: + case OperandType.InlineMethod: + case OperandType.InlineType: + case OperandType.InlineTok: + operandString = instr.Operand switch { + FieldReference fieldRef => fieldRef.FullName, + MethodReference methodRef => methodRef.FullName, + TypeReference typeRef => typeRef.FullName, + _ => null + }; + break; + } - if (operandString != null) - return $"{instr.OpCode.ToString ()} {operandString}"; - else - return instr.OpCode.ToString (); - } + if (operandString != null) + return $"{instr.OpCode.ToString ()} {operandString}"; + else + return instr.OpCode.ToString (); + } } } From 756d90ea235fcc214d326ec2dbfd31a4bb890599 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 20 Aug 2024 10:43:08 -0700 Subject: [PATCH 26/27] Add 'as' cast to RuntimeInterfacesAlgorithm --- .../illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs index c3d17a0107fb6..f83432d36cf33 100644 --- a/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs +++ b/src/tools/illink/src/linker/Linker/RuntimeInterfacesAlgorithm.cs @@ -42,7 +42,7 @@ public ImmutableArray GetRuntimeInterfaceImpleme var recursiveIFaces = GetRuntimeInterfaceImplementations (resolvedInterfaceType); foreach (var runtimeImpl in recursiveIFaces) { // Inflate the interface type with the explicit interfaceImpl reference - var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.InflateFrom (explicitIface.InterfaceType); + var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.InflateFrom (explicitIface.InterfaceType as IGenericInstance); foreach (var existingImpl in runtimeImpl.InterfaceImplementationChains) { interfaceTypeToImplChainMap.AddToList (inflatedInterfaceType, new InterfaceImplementationChain (originalType, existingImpl.InterfaceImplementations.Insert (0, explicitIface))); } @@ -53,10 +53,10 @@ public ImmutableArray GetRuntimeInterfaceImpleme var baseTypeIfaces = GetRuntimeInterfaceImplementations (baseTypeDef); foreach (var runtimeImpl in baseTypeIfaces) { // Inflate the interface type with the base type reference - var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.InflateFrom (originalType.BaseType); + var inflatedInterfaceType = runtimeImpl.InflatedInterfaceType.InflateFrom (originalType.BaseType as IGenericInstance); foreach (var impl in runtimeImpl.InterfaceImplementationChains) { // Inflate the provider for the first .impl - this could be a different recursive base type for each chain - var inflatedImplProvider = impl.TypeWithInterfaceImplementation.InflateFrom (originalType.BaseType); + var inflatedImplProvider = impl.TypeWithInterfaceImplementation.InflateFrom (originalType.BaseType as IGenericInstance); interfaceTypeToImplChainMap.AddToList (inflatedInterfaceType, new InterfaceImplementationChain (inflatedImplProvider, impl.InterfaceImplementations)); } } From 626934c2dff0cc15d0e0ac6a6392bb173e552cbe Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 20 Aug 2024 10:47:34 -0700 Subject: [PATCH 27/27] Add generated analyzer tests --- ...nterfaces.DefaultInterfaceMethodsTests.g.cs | 6 ++++++ ...ce.Interfaces.RecursiveInterfacesTests.g.cs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs index 19189bfcd0170..dc2440a552f56 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.DefaultInterfaceMethodsTests.g.cs @@ -63,6 +63,12 @@ public Task StaticDefaultInterfaceMethodOnStruct () return RunTest (allowMissingWarnings: true); } + [Fact] + public Task StaticDimOnDerivedInterface () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task StaticDimProvidedByUnreferencedIfaceInHierarchy () { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs index 35cc0b65c0417..1dbdf7bd5c9ac 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/Inheritance.Interfaces.RecursiveInterfacesTests.g.cs @@ -9,6 +9,12 @@ public sealed partial class RecursiveInterfacesTests : LinkerTestBase protected override string TestSuiteName => "Inheritance.Interfaces.RecursiveInterfaces"; + [Fact] + public Task BaseTypeMarkedInterfaceDerivedNotKept () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task GenericInterfaceImplementedRecursively () { @@ -27,6 +33,18 @@ public Task OverrideOfRecursiveInterfaceIsRemoved () return RunTest (allowMissingWarnings: true); } + [Fact] + public Task RecursiveGenericInterfaces () + { + return RunTest (allowMissingWarnings: true); + } + + [Fact] + public Task RecursiveGenericInterfacesStatic () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task RecursiveInterfaceKept () {