diff --git a/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs b/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs index 59c82c03f110e3..1cae9d6f046431 100644 --- a/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs +++ b/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs @@ -129,6 +129,7 @@ private static Utf8String SanitizeNameWithHash(Utf8String literal, byte[] hash = /// Dictionary given a mangled name for a given /// private Dictionary _mangledTypeNames = new Dictionary(); + private Dictionary _mangledAssemblyNames = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Given a set of names check if @@ -195,6 +196,84 @@ protected Utf8String NestMangledName(Utf8String name) return Utf8String.Concat(EnterNameScopeSequence, name, ExitNameScopeSequence); } + private Utf8String GetMangledAssemblyName(EcmaAssembly assembly) + { + string assemblyName = assembly.GetName().Name; + lock (this) + { + if (_mangledAssemblyNames.TryGetValue(assemblyName, out Utf8String mangledName)) + return mangledName; + + return ComputeMangledAssemblyName(assemblyName, (CompilerTypeSystemContext)assembly.Context); + } + } + + private Utf8String ComputeMangledAssemblyName(string assemblyName, CompilerTypeSystemContext context) + { + lock (this) + { + if (!_mangledAssemblyNames.TryGetValue(assemblyName, out Utf8String name)) + { + var assemblies = new List(context.InputFilePaths.Count + context.ReferenceFilePaths.Count + 1); + var assemblySet = new HashSet(StringComparer.OrdinalIgnoreCase); + if (assemblySet.Add(assemblyName)) + assemblies.Add(assemblyName); + foreach (string candidateAssemblyName in context.InputFilePaths.Keys) + { + if (assemblySet.Add(candidateAssemblyName)) + assemblies.Add(candidateAssemblyName); + } + foreach (string candidateAssemblyName in context.ReferenceFilePaths.Keys) + { + if (assemblySet.Add(candidateAssemblyName)) + assemblies.Add(candidateAssemblyName); + } + assemblies.Sort(CompareAssembliesForMangling); + + var deduplicator = new HashSet(); + foreach (string candidateAssemblyName in assemblies) + { + if (_mangledAssemblyNames.TryGetValue(candidateAssemblyName, out Utf8String existingMangledName)) + { + deduplicator.Add(existingMangledName); + continue; + } + + bool isSystemPrivate = IsSystemPrivateAssemblyName(candidateAssemblyName); + string prefixAssemblyName = isSystemPrivate + ? string.Concat("S.P.", candidateAssemblyName.AsSpan(15)) + : candidateAssemblyName; + + name = SanitizeName(new Utf8String(prefixAssemblyName)); + + if (!isSystemPrivate) + name = DisambiguateName(name, deduplicator); + + deduplicator.Add(name); + _mangledAssemblyNames.Add(candidateAssemblyName, name); + } + + name = _mangledAssemblyNames[assemblyName]; + } + return name; + } + + static int CompareAssembliesForMangling(string leftName, string rightName) + { + bool leftSystemPrivate = IsSystemPrivateAssemblyName(leftName); + bool rightSystemPrivate = IsSystemPrivateAssemblyName(rightName); + if (leftSystemPrivate != rightSystemPrivate) + return leftSystemPrivate ? -1 : 1; + + return string.CompareOrdinal(leftName, rightName); + } + + static bool IsSystemPrivateAssemblyName(string assemblyName) + { + return assemblyName.StartsWith("System.Private.", StringComparison.Ordinal); + } + } + /// /// If given is an precompute its mangled type name /// along with all the other types from the same module as . @@ -215,15 +294,7 @@ private Utf8String ComputeMangledTypeName(TypeDesc type) { bool isSystemModule = ecmaType.Module == ecmaType.Context.SystemModule; - string assemblyName = ((EcmaAssembly)ecmaType.Module).GetName().Name; - bool isSystemPrivate = assemblyName.StartsWith("System.Private."); - - // Abbreviate System.Private to S.P. This might conflict with user defined assembly names, - // but we already have a problem due to running SanitizeName without disambiguating the result - // This problem needs a better fix. - if (isSystemPrivate) - assemblyName = string.Concat("S.P.", assemblyName.AsSpan(15)); - Utf8String prependAssemblyName = SanitizeName(new Utf8String(assemblyName)); + Utf8String prependAssemblyName = GetMangledAssemblyName((EcmaAssembly)ecmaType.Module); var deduplicator = new HashSet(); diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.cs b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.cs new file mode 100644 index 00000000000000..36a86955b5f6a8 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +extern alias AssemblyWithDot; +extern alias AssemblyWithUnderscore; + +class Program +{ + public static int Main() + { + if (new AssemblyWithDot::TestNamespace.TestType().Value != "A.B") + return 101; + + if (new AssemblyWithUnderscore::TestNamespace.TestType().Value != "A_B") + return 102; + + return 100; + } +} diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.csproj b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.csproj new file mode 100644 index 00000000000000..a3e0126a377a18 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.csproj @@ -0,0 +1,10 @@ + + + Exe + 0 + + + + + + diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/AssemblyWithDot.csproj b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/AssemblyWithDot.csproj new file mode 100644 index 00000000000000..b333d242ead865 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/AssemblyWithDot.csproj @@ -0,0 +1,5 @@ + + + A.B + + diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/TestType.cs b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/TestType.cs new file mode 100644 index 00000000000000..7f8a99eec02e5e --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/TestType.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace TestNamespace; + +public sealed class TestType +{ + public string Value => "A.B"; +} diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/AssemblyWithUnderscore.csproj b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/AssemblyWithUnderscore.csproj new file mode 100644 index 00000000000000..1ea149d380a71a --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/AssemblyWithUnderscore.csproj @@ -0,0 +1,5 @@ + + + A_B + + diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/TestType.cs b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/TestType.cs new file mode 100644 index 00000000000000..f7f27d1f6d766a --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/TestType.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace TestNamespace; + +public sealed class TestType +{ + public string Value => "A_B"; +}