diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 9ae7f7b9cea5c9..8c4bcd08ff4e63 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -254,14 +254,7 @@ protected virtual void Initialize() { InitializeCorelibAttributeXml(); Context.Pipeline.InitializeMarkHandlers(Context, MarkContext); - - if (Annotations.GetEntryPointAssembly() is AssemblyDefinition entryPoint) - { - _typeMapHandler = new TypeMapHandler(entryPoint); - } - - _typeMapHandler.Initialize(Context, this); - + _typeMapHandler.Initialize(Context, this, Annotations.GetEntryPointAssembly()); ProcessMarkedPending(); } @@ -1458,7 +1451,7 @@ protected bool CheckProcessed(IMetadataTokenProvider provider) return !Annotations.SetProcessed(provider); } - protected virtual void MarkAssembly(AssemblyDefinition assembly, DependencyInfo reason, MessageOrigin origin) + public void MarkAssembly(AssemblyDefinition assembly, DependencyInfo reason, MessageOrigin origin) { Annotations.Mark(assembly, reason, origin); if (CheckProcessed(assembly)) diff --git a/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs b/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs index 5ad9a2d337d9db..3a98dadfd2e1ce 100644 --- a/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs @@ -261,7 +261,7 @@ protected virtual void SweepAssembly(AssemblyDefinition assembly) bool IsMarkedAssembly(AssemblyDefinition assembly) { - return Annotations.IsMarked(assembly.MainModule); + return Annotations.IsMarked(assembly) || Annotations.IsMarked(assembly.MainModule); } bool CanSweepNamesForMember(IMemberDefinition member, MetadataTrimming metadataTrimming) diff --git a/src/tools/illink/src/linker/Linker/DependencyInfo.cs b/src/tools/illink/src/linker/Linker/DependencyInfo.cs index 0f3e02ee145819..7a72ee84b7cc70 100644 --- a/src/tools/illink/src/linker/Linker/DependencyInfo.cs +++ b/src/tools/illink/src/linker/Linker/DependencyInfo.cs @@ -147,6 +147,7 @@ public enum DependencyKind UnsafeAccessorTarget = 89, // the member is referenced via UnsafeAccessor attribute TypeMapEntry = 90, // external or proxy type map entry + TypeMapAssemblyTarget = 91, // TypeMapTargetAssembly for a used typeMapGroup } public readonly struct DependencyInfo : IEquatable diff --git a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs index 8cbc8fb301f37f..5cadd46b65851b 100644 --- a/src/tools/illink/src/linker/Linker/TypeMapHandler.cs +++ b/src/tools/illink/src/linker/Linker/TypeMapHandler.cs @@ -4,10 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Text; -using ILLink.Shared.TrimAnalysis; +using System.Linq; using Mono.Cecil; -using Mono.CompilerServices.SymbolWriter; using Mono.Linker.Steps; using CustomAttributeWithOrigin = (Mono.Cecil.CustomAttribute Attribute, Mono.Cecil.AssemblyDefinition Origin); @@ -16,7 +14,9 @@ namespace Mono.Linker { sealed class TypeMapHandler { - readonly TypeMapResolver _lazyTypeMapResolver; + TypeMapResolver _lazyTypeMapResolver = null!; + LinkContext _context = null!; + MarkStep _markStep = null!; // [trim target: [type map group: custom attributes with assembly origin]] readonly Dictionary>> _unmarkedExternalTypeMapEntries = []; @@ -26,94 +26,94 @@ sealed class TypeMapHandler // CustomAttributes that we want to mark when the type mapping APIs are used. // [type map group: custom attributes] - Dictionary> _pendingExternalTypeMapEntries = []; - Dictionary> _pendingProxyTypeMapEntries = []; - HashSet _referencedExternalTypeMaps = []; - HashSet _referencedProxyTypeMaps = []; + Dictionary> _pendingExternalTypeMapEntries = null!; + Dictionary> _pendingProxyTypeMapEntries = null!; + Dictionary> _pendingAssemblyTargets = null!; - LinkContext _context = null!; - MarkStep _markStep = null!; + HashSet _referencedExternalTypeMaps = null!; + HashSet _referencedProxyTypeMaps = null!; public TypeMapHandler() { - _lazyTypeMapResolver = new TypeMapResolver(new HashSet()); } - public TypeMapHandler(AssemblyDefinition entryPointAssembly) + [Conditional("DEBUG")] + private void EnsureInitialized() { - HashSet assemblies = [AssemblyNameReference.Parse(entryPointAssembly.FullName)]; - foreach (var attr in entryPointAssembly.CustomAttributes) - { - if (attr.AttributeType is not GenericInstanceType - { - Namespace: "System.Runtime.InteropServices", - GenericArguments: [_] - }) - { - continue; // Only interested in System.Runtime.InteropServices attributes - } - - if (attr.AttributeType.Name != "TypeMapAssemblyTarget`1" - || attr.ConstructorArguments[0].Value is not string str) - { - // Invalid attribute, skip it. - // Let the runtime handle the failure. - continue; - } - - assemblies.Add(AssemblyNameReference.Parse(str)); - } - - _lazyTypeMapResolver = new TypeMapResolver(assemblies); + if (_lazyTypeMapResolver is null) + throw new InvalidOperationException("TypeMapHandler not initialized"); } - public void Initialize(LinkContext context, MarkStep markStep) + public void Initialize(LinkContext context, MarkStep markStep, AssemblyDefinition? entryPointAssembly) { _context = context; _markStep = markStep; - _lazyTypeMapResolver.Resolve(context, this); + var typeReferenceEqualityComparer = new TypeReferenceEqualityComparer(context); + _pendingExternalTypeMapEntries = new(typeReferenceEqualityComparer); + _pendingProxyTypeMapEntries = new(typeReferenceEqualityComparer); + _pendingAssemblyTargets = new(typeReferenceEqualityComparer); + _referencedExternalTypeMaps = new(typeReferenceEqualityComparer); + _referencedProxyTypeMaps = new(typeReferenceEqualityComparer); + var typeMapResolver = new TypeMapResolver(entryPointAssembly); + typeMapResolver.Resolve(context, this); + _lazyTypeMapResolver = typeMapResolver; } public void ProcessExternalTypeMapGroupSeen(MethodDefinition callingMethod, TypeReference typeMapGroup) { + EnsureInitialized(); _referencedExternalTypeMaps.Add(typeMapGroup); - if (!_pendingExternalTypeMapEntries.Remove(typeMapGroup, out List? pendingEntries)) + if (_pendingExternalTypeMapEntries.Remove(typeMapGroup, out List? pendingEntries)) { - return; + foreach (var entry in pendingEntries) + { + MarkTypeMapAttribute(entry, new DependencyInfo(DependencyKind.TypeMapEntry, callingMethod)); + } } - - foreach (var entry in pendingEntries) + if (_pendingAssemblyTargets.Remove(typeMapGroup, out List? assemblyTargets)) { - MarkTypeMapAttribute(entry, new DependencyInfo(DependencyKind.TypeMapEntry, callingMethod)); + foreach (var entry in assemblyTargets) + { + var info = new DependencyInfo(DependencyKind.TypeMapAssemblyTarget, callingMethod); + MarkTypeMapAttribute(entry, info); + } } } public void ProcessProxyTypeMapGroupSeen(MethodDefinition callingMethod, TypeReference typeMapGroup) { + EnsureInitialized(); _referencedProxyTypeMaps.Add(typeMapGroup); - if (!_pendingProxyTypeMapEntries.Remove(typeMapGroup, out List? pendingEntries)) + if (_pendingProxyTypeMapEntries.Remove(typeMapGroup, out List? pendingEntries)) { - return; + foreach (var entry in pendingEntries) + { + MarkTypeMapAttribute(entry, new DependencyInfo(DependencyKind.TypeMapEntry, callingMethod)); + } } - - foreach (var entry in pendingEntries) + if (_pendingAssemblyTargets.Remove(typeMapGroup, out List? assemblyTargets)) { - MarkTypeMapAttribute(entry, new DependencyInfo(DependencyKind.TypeMapEntry, callingMethod)); + foreach (var entry in assemblyTargets) + { + var info = new DependencyInfo(DependencyKind.TypeMapAssemblyTarget, callingMethod); + MarkTypeMapAttribute(entry, info); + } } } void MarkTypeMapAttribute(CustomAttributeWithOrigin entry, DependencyInfo info) { _markStep.MarkCustomAttribute(entry.Attribute, info, new MessageOrigin(entry.Origin)); + _markStep.MarkAssembly(entry.Origin, info, new MessageOrigin(entry.Origin)); // Mark the target type as instantiated - TypeReference targetType = (TypeReference)entry.Attribute.ConstructorArguments[1].Value; - if (targetType is not null && _context.Resolve(targetType) is TypeDefinition targetTypeDef) + if (entry.TargetType is { } targetType && _context.Resolve(targetType) is TypeDefinition targetTypeDef) _context.Annotations.MarkInstantiated(targetTypeDef); } public void ProcessType(TypeDefinition definition) { + EnsureInitialized(); RecordTargetTypeSeen(definition, _unmarkedExternalTypeMapEntries, _referencedExternalTypeMaps, _pendingExternalTypeMapEntries); } @@ -123,32 +123,32 @@ void RecordTargetTypeSeen( HashSet referenceTypeMapGroups, Dictionary> typeMapAttributesPendingUniverseMarking) { - if (unmarkedTypeMapAttributes.Remove(targetType, out Dictionary>? entries)) + if (!unmarkedTypeMapAttributes.Remove(targetType, out Dictionary>? entries)) + return; + + foreach (var (typeMapGroup, attributes) in entries) { - foreach (var (typeMapGroup, attributes) in entries) + if (referenceTypeMapGroups.Contains(typeMapGroup)) { - - if (referenceTypeMapGroups.Contains(typeMapGroup)) - { - foreach (var attr in attributes) - { - MarkTypeMapAttribute(attr, new DependencyInfo(DependencyKind.TypeMapEntry, targetType)); - } - } - else if (!typeMapAttributesPendingUniverseMarking.TryGetValue(typeMapGroup, out List? value)) + foreach (var attr in attributes) { - typeMapAttributesPendingUniverseMarking[typeMapGroup] = [.. attributes]; - } - else - { - value.AddRange(attributes); + MarkTypeMapAttribute(attr, new DependencyInfo(DependencyKind.TypeMapEntry, targetType)); } } + else if (!typeMapAttributesPendingUniverseMarking.TryGetValue(typeMapGroup, out List? value)) + { + typeMapAttributesPendingUniverseMarking[typeMapGroup] = [.. attributes]; + } + else + { + value.AddRange(attributes); + } } } public void ProcessInstantiated(TypeDefinition definition) { + EnsureInitialized(); RecordTargetTypeSeen(definition, _unmarkedProxyTypeMapEntries, _referencedProxyTypeMaps, _pendingProxyTypeMapEntries); } @@ -156,14 +156,12 @@ void AddExternalTypeMapEntry(TypeReference group, CustomAttributeWithOrigin attr { if (attr.Attribute.ConstructorArguments is [_, _, { Value: TypeReference trimTarget }]) { - RecordTypeMapEntry(attr, group, trimTarget, _unmarkedExternalTypeMapEntries); - return; + RecordTypeMapEntry(attr, group, trimTarget, _unmarkedExternalTypeMapEntries, _referencedExternalTypeMaps, _pendingExternalTypeMapEntries); } - if (attr.Attribute.ConstructorArguments is [_, { Value: TypeReference }]) + else if (attr.Attribute.ConstructorArguments is [_, { Value: TypeReference }]) { // There's no trim target, so include the attribute unconditionally. - RecordTypeMapEntry(attr, group, null, _unmarkedExternalTypeMapEntries); - return; + RecordTypeMapEntry(attr, group, null, _unmarkedExternalTypeMapEntries, _referencedExternalTypeMaps, _pendingExternalTypeMapEntries); } // Invalid attribute, skip it. // Let the runtime handle the failure. @@ -174,50 +172,77 @@ void AddProxyTypeMapEntry(TypeReference group, CustomAttributeWithOrigin attr) if (attr.Attribute.ConstructorArguments is [{ Value: TypeReference sourceType }, _]) { // This is a TypeMapAssociationAttribute, which has a single type argument. - RecordTypeMapEntry(attr, group, sourceType, _unmarkedProxyTypeMapEntries); + RecordTypeMapEntry(attr, group, sourceType, _unmarkedProxyTypeMapEntries, _referencedProxyTypeMaps, _pendingProxyTypeMapEntries); return; } // Invalid attribute, skip it. // Let the runtime handle the failure. } - void RecordTypeMapEntry(CustomAttributeWithOrigin attr, TypeReference group, TypeReference? trimTarget, Dictionary>> unmarkedEntryList) + private void AddAssemblyTarget(TypeReference typeMapGroup, CustomAttributeWithOrigin attr) { - if (trimTarget is null) + // Validate attribute + if (attr.Attribute.ConstructorArguments is not ([{ Value: string }])) + return; + + // If the type map group has been seen, mark the attribute immediately + if (_referencedExternalTypeMaps.Contains(typeMapGroup) || _referencedProxyTypeMaps.Contains(typeMapGroup)) { - // If there's no trim target, we can just mark the attribute. - MarkTypeMapAttribute(attr, new DependencyInfo(DependencyKind.TypeMapEntry, null)); + _markStep.MarkCustomAttribute(attr.Attribute, new DependencyInfo(DependencyKind.TypeMapEntry, null), new MessageOrigin(attr.Origin)); return; } - TypeDefinition? typeDef = _context.Resolve(trimTarget); - if (typeDef is null) + // Otherwise, it's pending until the type map group is seen + _pendingAssemblyTargets.AddToList(typeMapGroup, attr); + } + + + void RecordTypeMapEntry(CustomAttributeWithOrigin attr, TypeReference group, TypeReference? dependencySource, Dictionary>> pendingDependencySourceMarking, HashSet seenTypeGroups, Dictionary> pendingTypeMapGroupMarking) + { + if (dependencySource is null) + { + // Mark or directly add to pending group + if (seenTypeGroups.Contains(group)) + { + // If there's no trim target, we can just mark the attribute. + MarkTypeMapAttribute(attr, new DependencyInfo(DependencyKind.TypeMapEntry, null)); + return; + } + else + { + pendingTypeMapGroupMarking.AddToList(group, attr); + return; + } + } + + TypeDefinition? dependencyTypeDef = _context.Resolve(dependencySource); + if (dependencyTypeDef is null) { return; // Couldn't find the type we were asked about. } - if (_context.Annotations.IsMarked(typeDef)) + if (attr.DependencySourceRequiresTarget(_context, dependencyTypeDef)) { - MarkTypeMapAttribute(attr, new DependencyInfo(DependencyKind.TypeMapEntry, trimTarget)); + if (seenTypeGroups.Contains(group)) + { + MarkTypeMapAttribute(attr, new DependencyInfo(DependencyKind.TypeMapEntry, dependencySource)); + } + else + { + pendingTypeMapGroupMarking.AddToList(group, attr); + } } else { - if (!unmarkedEntryList.TryGetValue(typeDef, out Dictionary>? entries)) + if (!pendingDependencySourceMarking.TryGetValue(dependencyTypeDef, out Dictionary>? entries)) { - entries = new() { + entries = new(new TypeReferenceEqualityComparer(_context)) { { group, [] } }; - unmarkedEntryList[typeDef] = entries; + pendingDependencySourceMarking[dependencyTypeDef] = entries; } - if (!entries.TryGetValue(group, out List? attrs)) - { - entries[group] = [attr]; - } - else - { - attrs.Add(attr); - } + entries.AddToList(group, attr); } } @@ -226,18 +251,18 @@ public static bool IsTypeMapAttributeType(TypeDefinition type) return type is { Namespace: "System.Runtime.InteropServices", Name: "TypeMapAttribute`1" or "TypeMapAssociationAttribute`1" or "TypeMapAssemblyTargetAttribute`1" }; } - class TypeMapResolver(IReadOnlySet assemblies) + class TypeMapResolver(AssemblyDefinition? _assembly) { public void Resolve(LinkContext context, TypeMapHandler manager) { - foreach (AssemblyNameReference assemblyName in assemblies) + if (_assembly is null) + return; + HashSet seen = new(); + Queue toVisit = new(); + toVisit.Enqueue(_assembly); + while (toVisit.Count > 0) { - if (context.TryResolve(assemblyName) is not AssemblyDefinition assembly) - { - // If we cannot find the assembly, skip it. - // We'll fail at runtime as expected. - continue; - } + var assembly = toVisit.Dequeue(); foreach (CustomAttribute attr in assembly.CustomAttributes) { if (attr.AttributeType is not GenericInstanceType @@ -257,9 +282,51 @@ public void Resolve(LinkContext context, TypeMapHandler manager) { manager.AddProxyTypeMapEntry(typeMapGroup, (attr, assembly)); } + else if (attr.AttributeType.Name is "TypeMapAssemblyTargetAttribute`1") + { + manager.AddAssemblyTarget(typeMapGroup, (attr, assembly)); + if (attr.ConstructorArguments[0].Value is string str) + { + var nextAssemblyName = AssemblyNameReference.Parse(str); + if (context.TryResolve(nextAssemblyName) is AssemblyDefinition nextAssembly) + { + if (seen.Add(nextAssembly)) + { + toVisit.Enqueue(nextAssembly); + } + } + } + } } } } } } -} + + file static class CustomAttributeWithOriginExtensions + { + extension(CustomAttributeWithOrigin self) + { + public bool DependencySourceRequiresTarget(LinkContext context, TypeDefinition sourceType) + { + return self.Attribute.AttributeType.Name switch + { + "TypeMapAttribute`1" => + sourceType is null || context.Annotations.IsMarked(sourceType), + "TypeMapAssociationAttribute`1" => + context.Annotations.IsMarked(sourceType), + _ => false, + }; + } + + public TypeReference? TargetType => + self.Attribute.AttributeType.Name switch + { + "TypeMapAttribute`1" or "TypeMapAssociationAttribute`1" => + (TypeReference)self.Attribute.ConstructorArguments[1].Value!, + _ => null + }; + + } + } + } diff --git a/src/tools/illink/src/linker/Linker/TypeReferenceEqualityComparer.cs b/src/tools/illink/src/linker/Linker/TypeReferenceEqualityComparer.cs index 6ed69ada047b7c..f7cacb7e047827 100644 --- a/src/tools/illink/src/linker/Linker/TypeReferenceEqualityComparer.cs +++ b/src/tools/illink/src/linker/Linker/TypeReferenceEqualityComparer.cs @@ -11,6 +11,9 @@ namespace Mono.Linker // Copied from https://github.com/jbevain/cecil/blob/master/Mono.Cecil/TypeReferenceComparer.cs internal sealed class TypeReferenceEqualityComparer : EqualityComparer { + [Obsolete("Default will point to default object equality comparer. Use the constructor to create an instance with a resolver.")] + public static new object Default => throw new InvalidOperationException(); + public readonly ITryResolveMetadata _resolver; public TypeReferenceEqualityComparer(ITryResolveMetadata resolver) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/Dependencies/TypeMapReferencedAssembly.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/Dependencies/TypeMapReferencedAssembly.cs new file mode 100644 index 00000000000000..82ea66d3933a6b --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/Dependencies/TypeMapReferencedAssembly.cs @@ -0,0 +1,51 @@ +// 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.Runtime.InteropServices; +using Mono.Linker.Tests.Cases.Reflection.Dependencies; +using Mono.Linker.Tests.Cases.Reflection.Dependencies.Library; + +[assembly: TypeMap("UnimportantString", typeof(TargetTypeUnconditional1))] +[assembly: TypeMap("UnimportantString", typeof(TargetTypeConditional1), typeof(TrimTarget1))] +[assembly: TypeMapAssociation(typeof(ProxySource1), typeof(ProxyTarget1))] + +[assembly: TypeMap("UnimportantString", typeof(TargetTypeUnconditional2))] +[assembly: TypeMap("UnimportantString", typeof(TargetTypeConditional2), typeof(TrimTarget2))] +[assembly: TypeMapAssociation(typeof(ProxySource2), typeof(ProxyTarget2))] +[assembly: TypeMapAssemblyTarget("library2")] + +namespace Mono.Linker.Tests.Cases.Reflection.Dependencies +{ + public class TypeMapReferencedAssembly + { + public static void Main() + { + // Mark expected trim targets + _ = new TrimTarget1(); + _ = new ProxySource1(); + _ = new TrimTarget2(); + _ = new ProxySource2(); + + // Mark expected type map universe + _ = TypeMapping.GetOrCreateExternalTypeMapping(); + _ = TypeMapping.GetOrCreateProxyTypeMapping(); + } + } + + public class UsedTypeMapUniverse; + public class UnusedTypeMapUniverse; +} + +namespace Mono.Linker.Tests.Cases.Reflection.Dependencies.Library +{ + public class TargetTypeUnconditional1; + public class TargetTypeConditional1; + public class ProxySource1; + public class ProxyTarget1; + public class TargetTypeUnconditional2; + public class TargetTypeConditional2; + public class ProxySource2; + public class ProxyTarget2; + public class TrimTarget1; + public class TrimTarget2; +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/Dependencies/TypeMapSecondOrderReference.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/Dependencies/TypeMapSecondOrderReference.cs new file mode 100644 index 00000000000000..860d8f2fcc98f4 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/Dependencies/TypeMapSecondOrderReference.cs @@ -0,0 +1,43 @@ + +// 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.Runtime.InteropServices; +using Mono.Linker.Tests.Cases.Reflection.Dependencies; +using Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2; + +// Nothing in this assembly should be referenced or rooted directly +// This should validate the if only the type map attributes are kept, the assembly is still preserved +[assembly: TypeMap("UnimportantString", typeof(long))] +[assembly: TypeMap("UnimportantString", typeof(uint), typeof(string))] +[assembly: TypeMapAssociation(typeof(int), typeof(uint))] + +[assembly: TypeMap("UnimportantString", typeof(TargetTypeUnconditional2))] +[assembly: TypeMap("UnimportantString", typeof(TargetTypeConditional2), typeof(TrimTarget2))] +[assembly: TypeMapAssociation(typeof(ProxySource2), typeof(ProxyTarget2))] + +[assembly: TypeMapAssemblyTarget("library")] // Circular reference + +namespace Mono.Linker.Tests.Cases.Reflection.Dependencies +{ + public class TypeMapReferencedAssembly2 + { + } + + public class UsedTypeMapUniverse2; + public class UnusedTypeMapUniverse2; +} + +namespace Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2 +{ + public class TargetTypeUnconditional1; + public class TargetTypeConditional1; + public class ProxySource1; + public class ProxyTarget1; + public class TargetTypeUnconditional2; + public class TargetTypeConditional2; + public class ProxySource2; + public class ProxyTarget2; + public class TrimTarget1; + public class TrimTarget2; +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs index 76ce4b0468abad..9e6dbe1745c824 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeMap.cs @@ -11,9 +11,13 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; using Mono.Linker.Tests.Cases.Reflection; +using Mono.Linker.Tests.Cases.Reflection.Dependencies; +using Mono.Linker.Tests.Cases.Reflection.Dependencies.Library; -[assembly: KeptAttributeAttribute(typeof(TypeMapAttribute), By = Tool.Trimmer)] -[assembly: KeptAttributeAttribute(typeof(TypeMapAssociationAttribute), By = Tool.Trimmer)] +[assembly: KeptAttributeAttribute(typeof(TypeMapAttribute))] +[assembly: KeptAttributeAttribute(typeof(TypeMapAssociationAttribute))] +[assembly: KeptAttributeAttribute(typeof(TypeMapAssociationAttribute))] +[assembly: KeptAttributeAttribute(typeof(TypeMapAttribute))] [assembly: TypeMap("TrimTargetIsTarget", typeof(TargetAndTrimTarget), typeof(TargetAndTrimTarget))] [assembly: TypeMap("TrimTargetIsUnrelated", typeof(TargetType), typeof(TrimTarget))] [assembly: TypeMap(nameof(AllocatedNoTypeCheckClassTarget), typeof(AllocatedNoTypeCheckClassTarget), typeof(AllocatedNoTypeCheckClass))] @@ -28,6 +32,15 @@ [assembly: TypeMap("Ldobj", typeof(LdobjTarget), typeof(LdobjType))] [assembly: TypeMap("ArrayElement", typeof(ArrayElementTarget), typeof(ArrayElement))] [assembly: TypeMap("TrimTargetIsAllocatedNoTypeCheckNoBoxStruct", typeof(ConstructedNoTypeCheckOrBoxTarget), typeof(ConstructedNoTypeCheckNoBoxStruct))] + +// The TypeMap Universes are kept separate such that Proxy attributes shouldn't be kept if only the External type map is needed. +[assembly: TypeMap("UsedOnlyForExternalTypeMap", typeof(UsedExternalTarget), typeof(UsedTrimTarget))] // Kept +[assembly: TypeMapAssociation(typeof(UsedProxySource), typeof(UsedProxyTarget))] // Removed + +// The TypeMap Universes are kept separate such that External attributes shouldn't be kept if only the Proxy type map is needed. +[assembly: TypeMap("UsedOnlyForExternalTypeMap", typeof(UsedExternalTarget2), typeof(UsedTrimTarget2))] // Removed +[assembly: TypeMapAssociation(typeof(UsedProxySource2), typeof(UsedProxyTarget2))] // Kept + [assembly: TypeMapAssociation(typeof(SourceClass), typeof(ProxyType))] [assembly: TypeMapAssociation(typeof(TypeCheckOnlyClass), typeof(TypeCheckOnlyProxy))] [assembly: TypeMapAssociation(typeof(AllocatedNoBoxStructType), typeof(AllocatedNoBoxProxy))] @@ -40,10 +53,61 @@ [assembly: TypeMap("ClassWithStaticMethod", typeof(TargetType4), typeof(ClassWithStaticMethod))] [assembly: TypeMap("ClassWithStaticMethodAndField", typeof(TargetType5), typeof(ClassWithStaticMethodAndField))] +[assembly: KeptAttributeAttribute(typeof(TypeMapAssemblyTargetAttribute))] +[assembly: TypeMapAssemblyTarget("library")] +// TypeMapAssemblyTarget is kept regardless of which type map the program needs (External or Proxy) +[assembly: KeptAttributeAttribute(typeof(TypeMapAssemblyTargetAttribute))] +[assembly: KeptAttributeAttribute(typeof(TypeMapAssemblyTargetAttribute))] +[assembly: TypeMapAssemblyTarget("library")] +[assembly: TypeMapAssemblyTarget("library")] +[assembly: TypeMapAssemblyTarget("library")] // Should be removed + namespace Mono.Linker.Tests.Cases.Reflection { - [Kept] + [SetupLinkerAction("link", "System.Private.CoreLib")] // Needed to get the RemoveAttributeInstances in embedded xml + [SetupLinkerArgument("--ignore-link-attributes", "false")] [SetupCompileArgument("/unsafe")] + [SetupCompileBefore("library.dll", new[] { "Dependencies/TypeMapReferencedAssembly.cs" })] + [SetupCompileBefore("library2.dll", new[] { "Dependencies/TypeMapSecondOrderReference.cs" })] + [Kept] + [KeptAssembly("library.dll")] + [KeptAssembly("library2.dll")] + [KeptTypeInAssembly("library.dll", typeof(TypeMapReferencedAssembly))] + [KeptMemberInAssembly("library.dll", typeof(TypeMapReferencedAssembly), "Main()")] + [KeptTypeInAssembly("library.dll", typeof(TargetTypeUnconditional1), Tool = Tool.Trimmer)] + [KeptTypeInAssembly("library.dll", typeof(TrimTarget1))] + [KeptMemberInAssembly("library.dll", typeof(TrimTarget1), ".ctor()")] + [KeptTypeInAssembly("library.dll", typeof(TrimTarget2))] + [KeptMemberInAssembly("library.dll", typeof(TrimTarget2), ".ctor()")] + [KeptTypeInAssembly("library.dll", typeof(TargetTypeConditional1), Tool = Tool.Trimmer)] + [KeptTypeInAssembly("library.dll", typeof(ProxySource1))] + [KeptMemberInAssembly("library.dll", typeof(ProxySource1), ".ctor()")] + [KeptTypeInAssembly("library.dll", typeof(ProxyTarget1))] + + [KeptAttributeInAssembly("library.dll", typeof(TypeMapAttribute))] + [KeptAttributeInAssembly("library.dll", typeof(TypeMapAssociationAttribute))] + [KeptAttributeInAssembly("library.dll", typeof(TypeMapAssemblyTargetAttribute))] + [RemovedAttributeInAssembly("library.dll", typeof(TypeMapAttribute))] + [RemovedAttributeInAssembly("library.dll", typeof(TypeMapAssociationAttribute))] + + [KeptAttributeInAssembly("library2.dll", typeof(TypeMapAttribute))] + [KeptAttributeInAssembly("library2.dll", typeof(TypeMapAssociationAttribute))] + [KeptAttributeInAssembly("library2.dll", typeof(TypeMapAssemblyTargetAttribute))] + [RemovedAttributeInAssembly("library2.dll", typeof(TypeMapAttribute))] + [RemovedAttributeInAssembly("library2.dll", typeof(TypeMapAssociationAttribute))] + // No types kept in library2, just TypeMap attributes + [RemovedTypeInAssembly("library2.dll", typeof(TypeMapReferencedAssembly2))] + [RemovedTypeInAssembly("library2.dll", typeof(UsedTypeMapUniverse2))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.ProxySource1))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.ProxySource2))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.ProxyTarget1))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.ProxyTarget2))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.TargetTypeConditional1))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.TargetTypeConditional2))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.TargetTypeUnconditional1))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.TargetTypeUnconditional2))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.TrimTarget1))] + [RemovedTypeInAssembly("library2.dll", typeof(Mono.Linker.Tests.Cases.Reflection.Dependencies.Library2.TrimTarget2))] class TypeMap { [Kept] @@ -114,6 +178,38 @@ static void ConstrainedStaticCall(T t) where T : IStaticInterface Console.WriteLine(new ArrayElement[1]); Console.WriteLine(new ConstructedNoTypeCheckNoBoxStruct(42).Value); + + TypeMapReferencedAssembly.Main(); + + // TypeMapUniverses are independent between External and Proxy type maps. + // That is, if the External type map is used for a given universe, that doesn't keep the Proxy type map, and vice versa. + // Since we only use UsedExternalTypeMap for its External type map, the Proxy type map and its attributes should be removed. + // And vice versa for UsedProxyTypeMap. + _ = TypeMapping.GetOrCreateExternalTypeMapping(); + _ = TypeMapping.GetOrCreateProxyTypeMapping(); + _ = new UsedProxySource(); + _ = new UsedProxySource2(); + + UseTrimTargets(); + + // For the second order reference, instantiate int and string as the dependency source types to root the TypeMap attributes + // Do not directly root anything in the assembly to validate the assembly is kept even in only the typemap attributes are marked. + _ = new string('h', 2); + _ = new int(); + _ = TypeMapping.GetOrCreateExternalTypeMapping(); + _ = TypeMapping.GetOrCreateProxyTypeMapping(); + } + + [ExpectBodyModified] + [Kept] + static void UseTrimTargets() + { + object obj = null!; + // Rewritten as if these types are removed + if (obj is UsedTrimTarget) + obj = 1; + if (obj is UsedTrimTarget2) + obj = 2; } [Kept] @@ -180,7 +276,7 @@ private static void CheckTrimTarget(object o) [Kept] private static UnboxedOnly Unbox(object o) { - return (UnboxedOnly) o; + return (UnboxedOnly)o; } [Kept] @@ -216,6 +312,7 @@ class ProxyType; [Kept] class UnusedTypeMap; + class UnusedTypeMap2; class UnusedTargetType; class UnusedSourceClass; class UnusedProxyType; @@ -400,57 +497,29 @@ public AllocatedNoBoxStructType(int value) [Kept] class AllocatedNoBoxProxy; -} -// Polyfill for the type map types until we use an LKG runtime that has them with an updated LinkAttributes XML. -namespace System.Runtime.InteropServices -{ - [Kept(By = Tool.Trimmer)] - [KeptBaseType(typeof(Attribute), By = Tool.Trimmer)] - [KeptAttributeAttribute(typeof(AttributeUsageAttribute), By = Tool.Trimmer)] - [KeptAttributeAttribute(typeof(RemoveAttributeInstancesAttribute), By = Tool.Trimmer)] - [RemoveAttributeInstances] - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class TypeMapAttribute : Attribute - { - [Kept(By = Tool.Trimmer)] - public TypeMapAttribute(string value, Type target) { } + [Kept] + class UsedExternalTypeMap; + [Kept] + class UsedProxyTypeMap; - [Kept(By = Tool.Trimmer)] - [KeptAttributeAttribute(typeof(RequiresUnreferencedCodeAttribute), By = Tool.Trimmer)] - [RequiresUnreferencedCode("Interop types may be removed by trimming")] - public TypeMapAttribute(string value, Type target, Type trimTarget) { } - } + [Kept] + [KeptMember(".ctor()")] + class UsedProxySource; + [Kept] + [KeptMember(".ctor()")] + class UsedProxySource2; + [Kept] + class UsedTrimTarget; + [Kept(By=Tool.NativeAot)] // Associated attribute not kept + class UsedTrimTarget2; - [Kept(By = Tool.Trimmer)] - [KeptBaseType(typeof(Attribute), By = Tool.Trimmer)] - [KeptAttributeAttribute(typeof(AttributeUsageAttribute), By = Tool.Trimmer)] - [KeptAttributeAttribute(typeof(RemoveAttributeInstancesAttribute), By = Tool.Trimmer)] - [RemoveAttributeInstances] - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public sealed class TypeMapAssociationAttribute : Attribute - { - [Kept(By = Tool.Trimmer)] - public TypeMapAssociationAttribute(Type source, Type proxy) { } - } + [Kept] + class UsedExternalTarget; + class UsedExternalTarget2; - [Kept(By = Tool.Trimmer)] - public static class TypeMapping - { - [Kept(By = Tool.Trimmer)] - [KeptAttributeAttribute(typeof(RequiresUnreferencedCodeAttribute), By = Tool.Trimmer)] - [RequiresUnreferencedCode("Interop types may be removed by trimming")] - public static IReadOnlyDictionary GetOrCreateExternalTypeMapping() - { - throw new NotImplementedException($"External type map for {typeof(TTypeMapGroup).Name}"); - } - [Kept(By = Tool.Trimmer)] - [KeptAttributeAttribute(typeof(RequiresUnreferencedCodeAttribute), By = Tool.Trimmer)] - [RequiresUnreferencedCode("Interop types may be removed by trimming")] - public static IReadOnlyDictionary GetOrCreateProxyTypeMapping() - { - throw new NotImplementedException($"Proxy type map for {typeof(TTypeMapGroup).Name}"); - } - } + class UsedProxyTarget; + [Kept] + class UsedProxyTarget2; }