diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs index 2d23d46991f93f..b440abf85d581e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ExternalTypeMapNode.cs @@ -13,9 +13,9 @@ namespace ILCompiler.DependencyAnalysis { internal sealed class ExternalTypeMapNode : DependencyNodeCore, IExternalTypeMapNode { - private readonly IEnumerable> _mapEntries; + private readonly IEnumerable trimmingTargetType)>> _mapEntries; - public ExternalTypeMapNode(TypeDesc typeMapGroup, IEnumerable> mapEntries) + public ExternalTypeMapNode(TypeDesc typeMapGroup, IEnumerable trimmingTargetTypes)>> mapEntries) { _mapEntries = mapEntries; TypeMapGroup = typeMapGroup; @@ -35,13 +35,27 @@ public override IEnumerable GetConditionalStaticDep { foreach (var entry in _mapEntries) { - var (targetType, trimmingTargetType) = entry.Value; - if (trimmingTargetType is not null) + bool unconditional = false; + foreach (var trimTarget in entry.Value.trimmingTargetType) { - yield return new CombinedDependencyListEntry( - context.MetadataTypeSymbol(targetType), - context.NecessaryTypeSymbol(trimmingTargetType), - "Type in external type map is cast target"); + if (trimTarget is null) + { + unconditional = true; + break; + } + } + if (unconditional) + continue; + foreach (var trimmingTargetType in entry.Value.trimmingTargetType) + { + var targetType = entry.Value.targetType; + if (trimmingTargetType is not null) + { + yield return new CombinedDependencyListEntry( + context.MetadataTypeSymbol(targetType), + context.NecessaryTypeSymbol(trimmingTargetType), + "Type in external type map is cast target"); + } } } } @@ -50,12 +64,16 @@ public override IEnumerable GetStaticDependencies(NodeFacto { foreach (var entry in _mapEntries) { - var (targetType, trimmingTargetType) = entry.Value; - if (trimmingTargetType is null) + var (targetType, trimmingTargetTypes) = entry.Value; + foreach (var trimTarget in trimmingTargetTypes) { - yield return new DependencyListEntry( - context.MetadataTypeSymbol(targetType), - "External type map entry target type"); + if (trimTarget is null) + { + yield return new DependencyListEntry( + context.MetadataTypeSymbol(targetType), + "External type map entry target type"); + break; + } } } } @@ -75,14 +93,18 @@ public int CompareToImpl(ISortableNode other, CompilerComparer comparer) { foreach (var entry in _mapEntries) { - var (targetType, trimmingTargetType) = entry.Value; + var (targetType, trimmingTargetTypes) = entry.Value; - if (trimmingTargetType is null - || factory.NecessaryTypeSymbol(trimmingTargetType).Marked) + foreach (var trimmingTargetType in trimmingTargetTypes) { - IEETypeNode targetNode = factory.MetadataTypeSymbol(targetType); - Debug.Assert(targetNode.Marked); - yield return (entry.Key, targetNode); + if (trimmingTargetType is null + || factory.NecessaryTypeSymbol(trimmingTargetType).Marked) + { + IEETypeNode targetNode = factory.MetadataTypeSymbol(targetType); + Debug.Assert(targetNode.Marked); + yield return (entry.Key, targetNode); + break; + } } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapMetadata.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapMetadata.cs index 5eec852b659c90..7b64bd4c1f6cef 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapMetadata.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypeMapMetadata.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection.Metadata; using ILCompiler.DependencyAnalysis; using Internal.IL; @@ -58,7 +59,7 @@ protected override int CompareToImpl(MethodDesc other, TypeSystemComparer compar } private readonly Dictionary _associatedTypeMap = []; - private readonly Dictionary _externalTypeMap = []; + private readonly Dictionary trimmingTargets)> _externalTypeMap = []; private ThrowingMethodStub _externalTypeMapExceptionStub; private ThrowingMethodStub _associatedTypeMapExceptionStub; @@ -78,9 +79,21 @@ public void AddAssociatedTypeMapEntry(TypeDesc type, TypeDesc associatedType) } public void AddExternalTypeMapEntry(string typeName, TypeDesc type, TypeDesc trimmingTarget) { - if (!_externalTypeMap.TryAdd(typeName, (type, trimmingTarget))) + if (_externalTypeMap.TryGetValue(typeName, out var currentValue)) { - ThrowHelper.ThrowBadImageFormatException(); + if (currentValue.type != type) + { + // Conflicting type map entry + ThrowHelper.ThrowBadImageFormatException(); + } + else + { + currentValue.trimmingTargets.Add(trimmingTarget); + } + } + else + { + _externalTypeMap[typeName] = (type, [trimmingTarget]); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapLazyDictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapLazyDictionary.cs index a1fc75f100bdba..557d7328b386b0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapLazyDictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapLazyDictionary.cs @@ -265,6 +265,13 @@ private unsafe struct TypeNameUtf8 { public required void* Utf8TypeName { get; init; } public required int Utf8TypeNameLen { get; init; } + + public bool Equals(TypeNameUtf8 other) + { + ReadOnlySpan thisSpan = new ReadOnlySpan(Utf8TypeName, Utf8TypeNameLen); + ReadOnlySpan otherSpan = new ReadOnlySpan(other.Utf8TypeName, other.Utf8TypeNameLen); + return thisSpan.SequenceEqual(otherSpan); + } } [RequiresUnreferencedCode("Lazy TypeMap isn't supported for Trimmer scenarios")] @@ -282,6 +289,8 @@ public DelayedType(TypeNameUtf8 typeNameUtf8, RuntimeAssembly fallbackAssembly) _type = null; } + public TypeNameUtf8 TypeName => _typeNameUtf8; + public unsafe Type GetOrLoadType() { if (_type is null) @@ -328,12 +337,16 @@ protected override bool TryGetOrLoadType(string key, [NotNullWhen(true)] out Typ public void Add(string key, TypeNameUtf8 targetType, RuntimeAssembly fallbackAssembly) { int hash = ComputeHashCode(key); - if (_lazyData.ContainsKey(hash)) + // Allow duplicates that have the same string -> mapping. They may have different trimTargets. + // Warn if the mapping conflicts with an existing mapping. + if (!_lazyData.TryGetValue(hash, out DelayedType? existing)) + { + _lazyData.Add(hash, new DelayedType(targetType, fallbackAssembly)); + } + else if (!existing.TypeName.Equals(targetType)) { ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); } - - _lazyData.Add(hash, new DelayedType(targetType, fallbackAssembly)); } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/TypeMap.cs b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/TypeMap.cs index 5dcf5e35009ecd..2f5385db9d75bd 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/TypeMap.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TrimmingTests/TypeMap.cs @@ -8,6 +8,8 @@ [assembly: TypeMap("TrimTargetIsTarget", typeof(TargetAndTrimTarget), typeof(TargetAndTrimTarget))] [assembly: TypeMap("TrimTargetIsUnrelated", typeof(TargetType), typeof(TrimTarget))] +[assembly: TypeMap("DuplicateMappingWithDifferentTrimTargets", typeof(TargetType2), typeof(TrimTarget2))] +[assembly: TypeMap("DuplicateMappingWithDifferentTrimTargets", typeof(TargetType2), typeof(TrimTarget3))] [assembly: TypeMap("TrimTargetIsUnreferenced", typeof(UnreferencedTargetType), typeof(UnreferencedTrimTarget))] [assembly: TypeMapAssociation(typeof(SourceClass), typeof(ProxyType))] @@ -27,6 +29,14 @@ { Console.WriteLine("Type deriving from TrimTarget instantiated."); } + else if (t is TrimTarget2) + { + Console.WriteLine("Type deriving from TrimTarget2 instantiated."); + } + else if (t is TrimTarget3) + { + Console.WriteLine("Type deriving from TrimTarget3 instantiated."); + } Console.WriteLine("Hash code of SourceClass instance: " + new SourceClass().GetHashCode()); return -1; @@ -95,6 +105,12 @@ return 10; } +if (!usedTypeMap.TryGetValue("DuplicateMappingWithDifferentTrimTargets", out Type duplicatedTarget)) +{ + Console.WriteLine("Could not find duplicated target type"); + return 11; +} + return 100; [MethodImpl(MethodImplOptions.NoInlining)] @@ -106,7 +122,10 @@ static Type GetTypeWithoutTrimAnalysis(string typeName) class UsedTypeMap; class TargetAndTrimTarget; class TargetType; +class TargetType2; class TrimTarget; +class TrimTarget2; +class TrimTarget3; class UnreferencedTargetType; class UnreferencedTrimTarget; class SourceClass; diff --git a/src/tests/Interop/TypeMap/GroupTypes.cs b/src/tests/Interop/TypeMap/GroupTypes.cs index 67fa870a233120..e5c0dcf359e090 100644 --- a/src/tests/Interop/TypeMap/GroupTypes.cs +++ b/src/tests/Interop/TypeMap/GroupTypes.cs @@ -17,7 +17,9 @@ public class I2 {} public class TypicalUseCase { } -public class DuplicateTypeNameKey { } +public class ValidDuplicateTypeNameKey { } + +public class InvalidDuplicateTypeNameKey { } public class InvalidTypeNameKey { } diff --git a/src/tests/Interop/TypeMap/TypeMapApp.cs b/src/tests/Interop/TypeMap/TypeMapApp.cs index 5a355855d89df5..99f6a57ef0cd12 100644 --- a/src/tests/Interop/TypeMap/TypeMapApp.cs +++ b/src/tests/Interop/TypeMap/TypeMapApp.cs @@ -61,11 +61,15 @@ [assembly: TypeMap(null!, typeof(object))] [assembly: TypeMapAssociation(null!, typeof(object))] -[assembly: TypeMap("1", typeof(object))] -[assembly: TypeMap("1", typeof(object))] +[assembly: TypeMap("1", typeof(object))] +[assembly: TypeMap("1", typeof(object), typeof(object))] +[assembly: TypeMap("1", typeof(object), typeof(string))] -[assembly: TypeMapAssociation(typeof(DupType_MapObject), typeof(object))] -[assembly: TypeMapAssociation(typeof(DupType_MapString), typeof(string))] +[assembly: TypeMap("1", typeof(object))] +[assembly: TypeMap("1", typeof(string))] + +[assembly: TypeMapAssociation(typeof(DupType_MapObject), typeof(object))] +[assembly: TypeMapAssociation(typeof(DupType_MapString), typeof(string))] // Redefine the same type as in the TypeMapLib2 assembly // This is testing the duplicate type name key for the @@ -178,19 +182,28 @@ public static void Validate_ProxyTypeMapping() } [Fact] - public static void Validate_ExternalTypeMapping_DuplicateTypeKey() + public static void Validate_ExternalTypeMapping_InvalidDuplicateTypeKey() { - Console.WriteLine(nameof(Validate_ExternalTypeMapping_DuplicateTypeKey)); + Console.WriteLine(nameof(Validate_ExternalTypeMapping_InvalidDuplicateTypeKey)); - AssertExtensions.ThrowsAny(() => TypeMapping.GetOrCreateExternalTypeMapping()); + AssertExtensions.ThrowsAny(() => TypeMapping.GetOrCreateExternalTypeMapping()); } + [Fact] + public static void Validate_ExternalTypeMapping_ValidDuplicateTypeKey() + { + Console.WriteLine(nameof(Validate_ExternalTypeMapping_ValidDuplicateTypeKey)); + + var mapping = TypeMapping.GetOrCreateExternalTypeMapping(); + Assert.Equal(typeof(object), mapping["1"]); + } [Fact] public static void Validate_ProxyTypeMapping_DuplicateTypeKey() { Console.WriteLine(nameof(Validate_ProxyTypeMapping_DuplicateTypeKey)); - IReadOnlyDictionary map = TypeMapping.GetOrCreateProxyTypeMapping(); + // Invalid external mapping shouldn't impact proxy mapping + IReadOnlyDictionary map = TypeMapping.GetOrCreateProxyTypeMapping(); Assert.Equal(typeof(object), map[typeof(DupType_MapObject)]); Assert.Equal(typeof(string), map[typeof(DupType_MapString)]);