diff --git a/linker/Linker.Steps/MarkStep.cs b/linker/Linker.Steps/MarkStep.cs index 766902effec8..fc358960c666 100644 --- a/linker/Linker.Steps/MarkStep.cs +++ b/linker/Linker.Steps/MarkStep.cs @@ -982,6 +982,8 @@ protected virtual TypeDefinition MarkType (TypeReference reference) // and there are no other usages of that interface type, then we need to make sure the interface type is still marked because // this type is going to retain the interface implementation MarkRequirementsForInstantiatedTypes (type); + } else if (AlwaysMarkTypeAsInstantiated (type)) { + MarkRequirementsForInstantiatedTypes (type); } if (type.HasInterfaces) @@ -1401,6 +1403,20 @@ static bool IsMulticastDelegate (TypeDefinition td) return td.BaseType != null && td.BaseType.FullName == "System.MulticastDelegate"; } + protected virtual bool AlwaysMarkTypeAsInstantiated (TypeDefinition td) + { + switch (td.Name) { + // These types are created from native code which means we are unable to track when they are instantiated + // Since these are such foundational types, let's take the easy route and just always assume an instance of one of these + // could exist + case "Delegate": + case "MulticastDelegate": + return td.Namespace == "System"; + } + + return false; + } + void MarkEventSourceProviders (TypeDefinition td) { foreach (var nestedType in td.NestedTypes) { diff --git a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptBaseOnTypeInAssemblyAttribute.cs b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptBaseOnTypeInAssemblyAttribute.cs new file mode 100644 index 000000000000..2d3e749566c7 --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Assertions/KeptBaseOnTypeInAssemblyAttribute.cs @@ -0,0 +1,32 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Assertions { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class KeptBaseOnTypeInAssemblyAttribute : BaseInAssemblyAttribute { + public KeptBaseOnTypeInAssemblyAttribute (string assemblyFileName, Type type, string baseAssemblyFileName, Type baseType) + { + if (type == null) + throw new ArgumentNullException (nameof (type)); + if (string.IsNullOrEmpty (assemblyFileName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (assemblyFileName)); + + if (string.IsNullOrEmpty (baseAssemblyFileName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (baseAssemblyFileName)); + if (baseType == null) + throw new ArgumentException ("Value cannot be null or empty.", nameof (baseType)); + } + + public KeptBaseOnTypeInAssemblyAttribute (string assemblyFileName, string typeName, string baseAssemblyFileName, string baseTypeName) + { + if (string.IsNullOrEmpty (assemblyFileName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (assemblyFileName)); + if (string.IsNullOrEmpty (typeName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (typeName)); + + if (string.IsNullOrEmpty (baseAssemblyFileName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (baseAssemblyFileName)); + if (string.IsNullOrEmpty (baseTypeName)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (baseTypeName)); + } + } +} \ No newline at end of file diff --git a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj index 9d3289f5f5a2..f5dbe5b0955a 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj +++ b/linker/Tests/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj @@ -52,6 +52,7 @@ + diff --git a/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/DelegateAndMulticastDelegateKeepInstantiatedReqs.cs b/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/DelegateAndMulticastDelegateKeepInstantiatedReqs.cs new file mode 100644 index 000000000000..e84506ca118f --- /dev/null +++ b/linker/Tests/Mono.Linker.Tests.Cases/CoreLink/DelegateAndMulticastDelegateKeepInstantiatedReqs.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.Serialization; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.CoreLink { + /// + /// Delegate and is created from + /// + [SetupLinkerCoreAction ("link")] + [KeptBaseOnTypeInAssembly ("mscorlib.dll", typeof (MulticastDelegate), "mscorlib.dll", typeof (Delegate))] + + // Check a couple override methods to verify they were not removed + [KeptMemberInAssembly ("mscorlib.dll", typeof (MulticastDelegate), "GetHashCode()")] + [KeptMemberInAssembly ("mscorlib.dll", typeof (MulticastDelegate), "GetObjectData(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)")] + + [KeptMemberInAssembly ("mscorlib.dll", typeof (Delegate), "GetHashCode()")] + [KeptMemberInAssembly ("mscorlib.dll", typeof (Delegate), "Equals(System.Object)")] + [KeptInterfaceOnTypeInAssembly("mscorlib.dll", typeof (Delegate), "mscorlib.dll", typeof (ICloneable))] + [KeptInterfaceOnTypeInAssembly("mscorlib.dll", typeof (Delegate), "mscorlib.dll", typeof (ISerializable))] + + // Fails due to Runtime critical type System.Reflection.CustomAttributeData not found. + [SkipPeVerify(SkipPeVerifyForToolchian.Pedump)] + public class DelegateAndMulticastDelegateKeepInstantiatedReqs { + public static void Main () + { + typeof (MulticastDelegate).ToString (); + } + } +} \ No newline at end of file diff --git a/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj b/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj index ba7870f9e439..1769fbad95fb 100644 --- a/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj +++ b/linker/Tests/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj @@ -172,6 +172,7 @@ + diff --git a/linker/Tests/TestCasesRunner/ResultChecker.cs b/linker/Tests/TestCasesRunner/ResultChecker.cs index 9958002c1468..545e381cd444 100644 --- a/linker/Tests/TestCasesRunner/ResultChecker.cs +++ b/linker/Tests/TestCasesRunner/ResultChecker.cs @@ -227,6 +227,11 @@ void VerifyLinkingOfOtherAssemblies (AssemblyDefinition original) VerifyRemovedMemberInAssembly (checkAttrInAssembly, linkedType); break; + case nameof (KeptBaseOnTypeInAssemblyAttribute): + if (linkedType == null) + Assert.Fail ($"Type `{expectedTypeName}' should have been kept"); + VerifyKeptBaseOnTypeInAssembly (checkAttrInAssembly, linkedType); + break; case nameof (KeptMemberInAssemblyAttribute): if (linkedType == null) Assert.Fail ($"Type `{expectedTypeName}' should have been kept"); @@ -393,6 +398,21 @@ void VerifyKeptInterfaceOnTypeInAssembly (CustomAttribute inAssemblyAttribute, T Assert.Fail ($"Expected `{linkedType}` to have interface of type {originalInterface.FullName}"); } + void VerifyKeptBaseOnTypeInAssembly (CustomAttribute inAssemblyAttribute, TypeDefinition linkedType) + { + var originalType = GetOriginalTypeFromInAssemblyAttribute (inAssemblyAttribute); + + var baseAssemblyName = inAssemblyAttribute.ConstructorArguments [2].Value.ToString (); + var baseType = inAssemblyAttribute.ConstructorArguments [3].Value; + + var originalBase = GetOriginalTypeFromInAssemblyAttribute (baseAssemblyName, baseType); + if (originalType.BaseType.Resolve () != originalBase) + Assert.Fail ("Invalid assertion. Original type's base does not match the expected base"); + + Assert.That (originalBase.FullName, Is.EqualTo (linkedType.BaseType.FullName), + $"Incorrect base on `{linkedType.FullName}`. Expected `{originalBase.FullName}` but was `{linkedType.BaseType.FullName}`"); + } + protected static InterfaceImplementation GetMatchingInterfaceImplementationOnType (TypeDefinition type, string expectedInterfaceTypeName) { return type.Interfaces.FirstOrDefault (impl =>