From 5ef03ba67a356a36799c270e8f1305d89e06d36a Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 12 Apr 2024 15:48:06 +0200 Subject: [PATCH] AssemblyNameInfo implementation --- .../Reflection/TypeNameParser.CoreCLR.cs | 2 +- .../ILCompiler.TypeSystem.csproj | 9 + .../Reflection/AssemblyNameFormatter.cs | 12 +- .../AssemblyNameHelpers.StrongName.cs | 9 +- .../Reflection/Metadata/AssemblyNameInfo.cs | 276 ++++++++++++++++++ .../System/Reflection/Metadata/TypeName.cs | 40 +-- .../Reflection/Metadata/TypeNameParser.cs | 29 +- .../Reflection/TypeNameParser.Helpers.cs | 4 +- .../src/Resources/Strings.resx | 5 +- .../System.Private.CoreLib.Shared.projitems | 11 +- .../ref/System.Reflection.Metadata.cs | 18 ++ .../src/System.Reflection.Metadata.csproj | 5 + .../tests/Metadata/AssemblyNameInfoTests.cs | 55 ++++ .../tests/Metadata/TypeNameParserSamples.cs | 2 +- .../tests/Metadata/TypeNameTests.cs | 15 +- .../System.Reflection.Metadata.Tests.csproj | 1 + 16 files changed, 419 insertions(+), 74 deletions(-) rename src/libraries/{System.Private.CoreLib => Common}/src/System/Reflection/AssemblyNameFormatter.cs (92%) rename src/libraries/{System.Private.CoreLib => Common}/src/System/Reflection/AssemblyNameHelpers.StrongName.cs (95%) create mode 100644 src/libraries/Common/src/System/Reflection/Metadata/AssemblyNameInfo.cs create mode 100644 src/libraries/System.Reflection.Metadata/tests/Metadata/AssemblyNameInfoTests.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameParser.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameParser.CoreCLR.cs index 55544a6f8ecc8..1a49aecab6d7f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameParser.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameParser.CoreCLR.cs @@ -85,7 +85,7 @@ internal partial struct TypeNameParser { return null; } - else if (topLevelAssembly is not null && parsed.GetAssemblyName() is not null) + else if (topLevelAssembly is not null && parsed.AssemblyName is not null) { return throwOnError ? throw new ArgumentException(SR.Argument_AssemblyGetTypeCannotSpecifyAssembly) : null; } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj index 6091f03b4acbc..0a69514553758 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj @@ -201,9 +201,18 @@ Utilities\HexConverter.cs + + Utilities\AssemblyNameHelpers.StrongName.cs + + + Utilities\AssemblyNameFormatter.cs + Utilities\AssemblyNameParser.cs + + Utilities\AssemblyNameInfo.cs + Utilities\TypeName.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameFormatter.cs b/src/libraries/Common/src/System/Reflection/AssemblyNameFormatter.cs similarity index 92% rename from src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameFormatter.cs rename to src/libraries/Common/src/System/Reflection/AssemblyNameFormatter.cs index c1a810db50755..9d5eeebc85a6d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameFormatter.cs +++ b/src/libraries/Common/src/System/Reflection/AssemblyNameFormatter.cs @@ -6,6 +6,8 @@ using System.Globalization; using System.Text; +#nullable enable + namespace System.Reflection { internal static class AssemblyNameFormatter @@ -91,7 +93,8 @@ private static void AppendQuoted(this ref ValueStringBuilder vsb, string s) // App-compat: You can use double or single quotes to quote a name, and Fusion (or rather the IdentityAuthority) picks one // by some algorithm. Rather than guess at it, we use double quotes consistently. - if (s != s.Trim() || s.Contains('\"') || s.Contains('\'')) + ReadOnlySpan span = s.AsSpan(); + if (s.Length != span.Trim().Length || span.IndexOfAny('\"', '\'') >= 0) needsQuoting = true; if (needsQuoting) @@ -125,5 +128,12 @@ private static void AppendQuoted(this ref ValueStringBuilder vsb, string s) if (needsQuoting) vsb.Append(quoteChar); } + +#if !NETCOREAPP + private static void AppendSpanFormattable(this ref ValueStringBuilder vsb, ushort value) + { + vsb.Append(value.ToString()); + } +#endif } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameHelpers.StrongName.cs b/src/libraries/Common/src/System/Reflection/AssemblyNameHelpers.StrongName.cs similarity index 95% rename from src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameHelpers.StrongName.cs rename to src/libraries/Common/src/System/Reflection/AssemblyNameHelpers.StrongName.cs index 7fb7a66815956..34413f7a43f23 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameHelpers.StrongName.cs +++ b/src/libraries/Common/src/System/Reflection/AssemblyNameHelpers.StrongName.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers.Binary; +using System.Diagnostics; using System.Security; +#nullable enable + namespace System.Reflection { internal static partial class AssemblyNameHelpers @@ -16,8 +19,12 @@ internal static partial class AssemblyNameHelpers if (publicKey.Length == 0) return Array.Empty(); +#if SYSTEM_PRIVATE_CORELIB if (!IsValidPublicKey(publicKey)) throw new SecurityException(SR.Security_InvalidAssemblyPublicKey); +#else + Debug.Assert(IsValidPublicKey(publicKey)); +#endif Span hash = stackalloc byte[20]; @@ -35,7 +42,7 @@ internal static partial class AssemblyNameHelpers // // This validation logic is a port of StrongNameIsValidPublicKey() from src\coreclr\md\runtime\strongnameinternal.cpp // - private static bool IsValidPublicKey(byte[] publicKey) + internal static bool IsValidPublicKey(byte[] publicKey) { uint publicKeyLength = (uint)(publicKey.Length); diff --git a/src/libraries/Common/src/System/Reflection/Metadata/AssemblyNameInfo.cs b/src/libraries/Common/src/System/Reflection/Metadata/AssemblyNameInfo.cs new file mode 100644 index 0000000000000..fa1a2eeeb6336 --- /dev/null +++ b/src/libraries/Common/src/System/Reflection/Metadata/AssemblyNameInfo.cs @@ -0,0 +1,276 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace System.Reflection.Metadata +{ + [DebuggerDisplay("{FullName}")] +#if SYSTEM_PRIVATE_CORELIB + internal +#else + public +#endif + sealed class AssemblyNameInfo : IEquatable + { + private string? _fullName; + +#if !SYSTEM_PRIVATE_CORELIB + public AssemblyNameInfo(string name, Version? version = null, string? cultureName = null, AssemblyNameFlags flags = AssemblyNameFlags.None, + Collections.Immutable.ImmutableArray publicKey = default, Collections.Immutable.ImmutableArray publicKeyToken = default) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Version = version; + CultureName = cultureName; + + if (!publicKey.IsDefaultOrEmpty +#if NET8_0_OR_GREATER + && ValidatePublicKey(Runtime.InteropServices.ImmutableCollectionsMarshal.AsArray(publicKey))) +#else + && ValidatePublicKey(System.Linq.ImmutableArrayExtensions.ToArray(publicKey))) +#endif + { + throw new ArgumentException("SR.Security_InvalidAssemblyPublicKey", nameof(publicKey)); // TODO adsitnik: use actual resource + } + + PublicKey = publicKey; + PublicKeyToken = publicKeyToken; + + if (!publicKey.IsDefaultOrEmpty) + { + flags |= AssemblyNameFlags.PublicKey; + } + + Flags = flags; + } +#endif + + internal AssemblyNameInfo(AssemblyNameParser.AssemblyNameParts parts) + { + Name = parts._name; + Version = parts._version; + CultureName = parts._cultureName; + Flags = parts._flags; + + bool publicKey = (parts._flags & AssemblyNameFlags.PublicKey) != 0; + +#if SYSTEM_PRIVATE_CORELIB + PublicKey = publicKey ? parts._publicKeyOrToken : null; + PublicKeyToken = publicKey ? null : parts._publicKeyOrToken; +#else + PublicKey = ToImmutable(publicKey ? parts._publicKeyOrToken : null); + PublicKeyToken = ToImmutable(publicKey ? null : parts._publicKeyOrToken); + + static Collections.Immutable.ImmutableArray ToImmutable(byte[]? bytes) + => bytes is null ? default : bytes.Length == 0 ? Collections.Immutable.ImmutableArray.Empty : + #if NET8_0_OR_GREATER + Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(bytes); + #else + Collections.Immutable.ImmutableArray.Create(bytes); + #endif +#endif + } + + public string Name { get; } + public Version? Version { get; } + public string? CultureName { get; } + public AssemblyNameFlags Flags { get; } + +#if SYSTEM_PRIVATE_CORELIB + public byte[]? PublicKey { get; } + public byte[]? PublicKeyToken { get; } +#else + public Collections.Immutable.ImmutableArray PublicKey { get; } + public Collections.Immutable.ImmutableArray PublicKeyToken { get; } +#endif + + public string FullName + { + get + { + if (_fullName is null) + { +#if SYSTEM_PRIVATE_CORELIB + byte[]? pkt = PublicKeyToken ?? AssemblyNameHelpers.ComputePublicKeyToken(PublicKey); +#elif NET8_0_OR_GREATER + byte[]? pkt = !PublicKeyToken.IsDefault + ? Runtime.InteropServices.ImmutableCollectionsMarshal.AsArray(PublicKeyToken) + : !PublicKey.IsDefault + ? AssemblyNameHelpers.ComputePublicKeyToken(Runtime.InteropServices.ImmutableCollectionsMarshal.AsArray(PublicKey)) + : null; +#else + byte[]? pkt = !PublicKeyToken.IsDefault + ? System.Linq.ImmutableArrayExtensions.ToArray(PublicKeyToken) + : !PublicKey.IsDefault + ? AssemblyNameHelpers.ComputePublicKeyToken(System.Linq.ImmutableArrayExtensions.ToArray(PublicKey)) + : null; +#endif + _fullName = AssemblyNameFormatter.ComputeDisplayName(Name, Version, CultureName, pkt/*, ExtractAssemblyNameFlags(Flags), ExtractAssemblyContentType(Flags)*/); ; + } + + return _fullName; + } + } + + public bool Equals(AssemblyNameInfo? other) + { + if (other is null || Flags != other.Flags || !Name.Equals(other.Name) || !string.Equals(CultureName, other.CultureName)) + { + return false; + } + + if (Version is null) + { + if (other.Version is not null) + { + return false; + } + } + else + { + if (!Version.Equals(other.Version)) + { + return false; + } + } + + if (!SequenceEqual(PublicKey, other.PublicKey) || !SequenceEqual(PublicKeyToken, other.PublicKeyToken)) + { + return false; + } + + return true; + +#if SYSTEM_PRIVATE_CORELIB + static bool SequenceEqual(byte[]? left, byte[]? right) + { + if (left is null) + { + if (right is not null) + { + return false; + } + } + else if (right is null) + { + return false; + } + else if (left.Length != right.Length) + { + return false; + } + else + { + for (int i = 0; i < left.Length; i++) + { + if (left[i] != right[i]) + { + return false; + } + } + } + + return true; + } +#else + static bool SequenceEqual(Collections.Immutable.ImmutableArray left, Collections.Immutable.ImmutableArray right) + { + int leftLength = left.IsDefaultOrEmpty ? 0 : left.Length; + int rightLength = right.IsDefaultOrEmpty ? 0 : right.Length; + + if (leftLength != rightLength) + { + return false; + } + else if (leftLength > 0) + { + for (int i = 0; i < leftLength; i++) + { + if (left[i] != right[i]) + { + return false; + } + } + } + + return true; + } +#endif + } + + public override bool Equals(object? obj) => Equals(obj as AssemblyNameInfo); + + public override int GetHashCode() => FullName.GetHashCode(); + + public AssemblyName ToAssemblyName() + { + AssemblyName assemblyName = new(); + assemblyName.Name = Name; + assemblyName.CultureName = CultureName; + assemblyName.Version = Version; + +#if SYSTEM_PRIVATE_CORELIB + assemblyName._flags = Flags; + assemblyName.SetPublicKey(PublicKey); + assemblyName.SetPublicKeyToken(PublicKeyToken); +#else + assemblyName.Flags = Flags; + + if (!PublicKey.IsDefault) + { + assemblyName.SetPublicKey(System.Linq.ImmutableArrayExtensions.ToArray(PublicKey)); + } + if (!PublicKeyToken.IsDefault) + { + assemblyName.SetPublicKeyToken(System.Linq.ImmutableArrayExtensions.ToArray(PublicKeyToken)); + } +#endif + + return assemblyName; + } + + /// + /// Parses a span of characters into a assembly name. + /// + /// A span containing the characters representing the assembly name to parse. + /// Parsed type name. + /// Provided assembly name was invalid. + public static AssemblyNameInfo Parse(ReadOnlySpan assemblyName) + => TryParse(assemblyName, out AssemblyNameInfo? result) + ? result + : throw new ArgumentException("TODO_adsitnik_add_or_reuse_resource"); + + /// + /// Tries to parse a span of characters into an assembly name. + /// + /// A span containing the characters representing the assembly name to parse. + /// Contains the result when parsing succeeds. + /// true if assembly name was converted successfully, otherwise, false. + public static bool TryParse(ReadOnlySpan assemblyName, +#if SYSTEM_REFLECTION_METADATA || SYSTEM_PRIVATE_CORELIB // required by some tools that include this file but don't include the attribute + [NotNullWhen(true)] +#endif + out AssemblyNameInfo? result) + { + AssemblyNameParser.AssemblyNameParts parts = default; + if (AssemblyNameParser.TryParse(assemblyName, ref parts) + && ((parts._flags & AssemblyNameFlags.PublicKey) == 0 || ValidatePublicKey(parts._publicKeyOrToken))) + { + result = new(parts); + return true; + } + + result = null; + return false; + } + + private static bool ValidatePublicKey(byte[]? publicKey) + => publicKey is null + || publicKey.Length == 0 + || AssemblyNameHelpers.IsValidPublicKey(publicKey); + } +} diff --git a/src/libraries/Common/src/System/Reflection/Metadata/TypeName.cs b/src/libraries/Common/src/System/Reflection/Metadata/TypeName.cs index 53d46071523fe..3be55a528b37e 100644 --- a/src/libraries/Common/src/System/Reflection/Metadata/TypeName.cs +++ b/src/libraries/Common/src/System/Reflection/Metadata/TypeName.cs @@ -29,13 +29,12 @@ sealed class TypeName : IEquatable /// private readonly int _nestedNameLength; private readonly TypeName[]? _genericArguments; - private readonly AssemblyName? _assemblyName; private readonly TypeName? _elementOrGenericType; private readonly TypeName? _declaringType; private string? _name, _fullName, _assemblyQualifiedName; internal TypeName(string? fullName, - AssemblyName? assemblyName, + AssemblyNameInfo? assemblyName, TypeName? elementOrGenericType = default, TypeName? declaringType = default, TypeName[]? genericTypeArguments = default, @@ -43,7 +42,7 @@ internal TypeName(string? fullName, int nestedNameLength = -1) { _fullName = fullName; - _assemblyName = assemblyName; + AssemblyName = assemblyName; _rankOrModifier = rankOrModifier; _elementOrGenericType = elementOrGenericType; _declaringType = declaringType; @@ -58,18 +57,16 @@ internal TypeName(string? fullName, /// The assembly-qualified name of the type; e.g., "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089". /// /// - /// If returns null, simply returns . + /// If returns null, simply returns . /// public string AssemblyQualifiedName - => _assemblyQualifiedName ??= _assemblyName is null ? FullName : $"{FullName}, {_assemblyName.FullName}"; + => _assemblyQualifiedName ??= AssemblyName is null ? FullName : $"{FullName}, {AssemblyName.FullName}"; /// - /// Returns the name of the assembly (not the full name). + /// Returns assembly name which contains this type, or null if this was not + /// created from a fully-qualified name. /// - /// - /// If returns null, simply returns null. - /// - public string? AssemblySimpleName => _assemblyName?.Name; + public AssemblyNameInfo? AssemblyName { get; } /// /// If this type is a nested type (see ), gets @@ -224,8 +221,8 @@ public bool Equals(TypeName? other) => other is not null && other._rankOrModifier == _rankOrModifier // try to prevent from allocations if possible (AssemblyQualifiedName can allocate) - && ((other._assemblyName is null && _assemblyName is null) - || (other._assemblyName is not null && _assemblyName is not null)) + && ((other.AssemblyName is null && AssemblyName is null) + || (other.AssemblyName is not null && AssemblyName is not null)) && other.AssemblyQualifiedName == AssemblyQualifiedName; public override bool Equals(object? obj) => Equals(obj as TypeName); @@ -353,25 +350,6 @@ public int GetArrayRank() _ => throw TypeNameParserHelpers.InvalidOperation_HasToBeArrayClass() }; - /// - /// Returns assembly name which contains this type, or null if this was not - /// created from a fully-qualified name. - /// - /// Since is mutable, this method returns a copy of it. - public AssemblyName? GetAssemblyName() - { - if (_assemblyName is null) - { - return null; - } - -#if SYSTEM_PRIVATE_CORELIB - return _assemblyName; // no need for a copy in CoreLib (it's internal) -#else - return (AssemblyName)_assemblyName.Clone(); -#endif - } - /// /// If this represents a constructed generic type, returns an array /// of all the generic arguments. Otherwise it returns an empty array. diff --git a/src/libraries/Common/src/System/Reflection/Metadata/TypeNameParser.cs b/src/libraries/Common/src/System/Reflection/Metadata/TypeNameParser.cs index 602a78255ffd3..84a6147e154ce 100644 --- a/src/libraries/Common/src/System/Reflection/Metadata/TypeNameParser.cs +++ b/src/libraries/Common/src/System/Reflection/Metadata/TypeNameParser.cs @@ -193,7 +193,7 @@ private TypeNameParser(ReadOnlySpan name, bool throwOnError, TypeNameParse previousDecorator = parsedDecorator; } - AssemblyName? assemblyName = null; + AssemblyNameInfo? assemblyName = null; if (allowFullyQualifiedName && !TryParseAssemblyName(ref assemblyName)) { #if SYSTEM_PRIVATE_CORELIB @@ -232,7 +232,7 @@ private TypeNameParser(ReadOnlySpan name, bool throwOnError, TypeNameParse } /// false means the input was invalid and parsing has failed. Empty input is valid and returns true. - private bool TryParseAssemblyName(ref AssemblyName? assemblyName) + private bool TryParseAssemblyName(ref AssemblyNameInfo? assemblyName) { ReadOnlySpan capturedBeforeProcessing = _inputString; if (TryStripFirstCharAndTrailingSpaces(ref _inputString, ',')) @@ -247,33 +247,12 @@ private bool TryParseAssemblyName(ref AssemblyName? assemblyName) // Otherwise EOL serves as the terminator. int assemblyNameLength = (int)Math.Min((uint)_inputString.IndexOf(']'), (uint)_inputString.Length); ReadOnlySpan candidate = _inputString.Slice(0, assemblyNameLength); - AssemblyNameParser.AssemblyNameParts parts = default; - if (!AssemblyNameParser.TryParse(candidate, ref parts)) + if (!AssemblyNameInfo.TryParse(candidate, out assemblyName)) { return false; } - assemblyName = new AssemblyName(); -#if SYSTEM_PRIVATE_CORELIB - assemblyName.Init(parts); -#else - assemblyName.Name = parts._name; - assemblyName.CultureName = parts._cultureName; - assemblyName.Version = parts._version; - - if (parts._publicKeyOrToken is not null) - { - if ((parts._flags & AssemblyNameFlags.PublicKey) != 0) - { - assemblyName.SetPublicKey(parts._publicKeyOrToken); - } - else - { - assemblyName.SetPublicKeyToken(parts._publicKeyOrToken); - } - } -#endif _inputString = _inputString.Slice(assemblyNameLength); return true; } @@ -281,7 +260,7 @@ private bool TryParseAssemblyName(ref AssemblyName? assemblyName) return true; } - private static TypeName? GetDeclaringType(string fullTypeName, List? nestedNameLengths, AssemblyName? assemblyName) + private static TypeName? GetDeclaringType(string fullTypeName, List? nestedNameLengths, AssemblyNameInfo? assemblyName) { if (nestedNameLengths is null) { diff --git a/src/libraries/Common/src/System/Reflection/TypeNameParser.Helpers.cs b/src/libraries/Common/src/System/Reflection/TypeNameParser.Helpers.cs index be5a784a40f75..6fae810b6bd0e 100644 --- a/src/libraries/Common/src/System/Reflection/TypeNameParser.Helpers.cs +++ b/src/libraries/Common/src/System/Reflection/TypeNameParser.Helpers.cs @@ -128,7 +128,7 @@ private static (string typeNamespace, string name) SplitFullTypeName(string type } string nonNestedParentName = current!.FullName; - Type? type = GetType(nonNestedParentName, nestedTypeNames, typeName.GetAssemblyName(), typeName.FullName); + Type? type = GetType(nonNestedParentName, nestedTypeNames, typeName.AssemblyName?.ToAssemblyName(), typeName.FullName); return Make(type, typeName); } else if (typeName.IsConstructedGenericType) @@ -141,7 +141,7 @@ private static (string typeNamespace, string name) SplitFullTypeName(string type } else { - Type? type = GetType(typeName.FullName, nestedTypeNames: ReadOnlySpan.Empty, typeName.GetAssemblyName(), typeName.FullName); + Type? type = GetType(typeName.FullName, nestedTypeNames: ReadOnlySpan.Empty, typeName.AssemblyName?.ToAssemblyName(), typeName.FullName); return Make(type, typeName); } diff --git a/src/libraries/System.Collections.Immutable/src/Resources/Strings.resx b/src/libraries/System.Collections.Immutable/src/Resources/Strings.resx index 5abd00a70bd3b..e76cc9b3aa577 100644 --- a/src/libraries/System.Collections.Immutable/src/Resources/Strings.resx +++ b/src/libraries/System.Collections.Immutable/src/Resources/Strings.resx @@ -105,4 +105,7 @@ Non-negative number required. - + + Invalid assembly public key. + + \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index c534b2f299207..a2af87f01dd26 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -647,9 +647,7 @@ - - @@ -1477,6 +1475,15 @@ Common\System\Reflection\AssemblyNameParser.cs + + Common\System\Reflection\AssemblyNameFormatter.cs + + + Common\System\Reflection\AssemblyNameHelpers.StrongName.cs + + + Common\System\Reflection\Metadata\AssemblyNameInfo.cs + Common\System\Reflection\Metadata\TypeName.cs diff --git a/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs b/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs index 2cc2644e2c8ae..7908892726241 100644 --- a/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs +++ b/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs @@ -2408,6 +2408,24 @@ public readonly partial struct TypeLayout public int PackingSize { get { throw null; } } public int Size { get { throw null; } } } + public sealed partial class AssemblyNameInfo : System.IEquatable + { + public AssemblyNameInfo(string name, System.Version? version = null, string? cultureName = null, System.Reflection.AssemblyNameFlags flags = AssemblyNameFlags.None, + Collections.Immutable.ImmutableArray publicKey = default, Collections.Immutable.ImmutableArray publicKeyToken = default) { } + public string Name { get { throw null; } } + public string? CultureName { get { throw null; } } + public string FullName { get { throw null; } } + public System.Version? Version { get { throw null; } } + public System.Reflection.AssemblyNameFlags Flags { get { throw null; } } + public System.Collections.Immutable.ImmutableArray PublicKey { get { throw null; } } + public System.Collections.Immutable.ImmutableArray PublicKeyToken { get { throw null; } } + public static System.Reflection.Metadata.AssemblyNameInfo Parse(System.ReadOnlySpan assemblyName) { throw null; } + public static bool TryParse(System.ReadOnlySpan assemblyName, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Reflection.Metadata.AssemblyNameInfo? result) { throw null; } + public override bool Equals(object? obj) { throw null; } + public bool Equals(System.Reflection.Metadata.AssemblyNameInfo? other) { throw null; } + public override int GetHashCode() { throw null; } + public System.Reflection.AssemblyName ToAssemblyName() { throw null; } + } public sealed partial class TypeName : System.IEquatable { internal TypeName() { } diff --git a/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj b/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj index 7c683a07db249..44998d8c76983 100644 --- a/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj +++ b/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj @@ -253,12 +253,17 @@ The System.Reflection.Metadata library is built-in as part of the shared framewo + + + + + diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/AssemblyNameInfoTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/AssemblyNameInfoTests.cs new file mode 100644 index 0000000000000..0bf63a12a2ac5 --- /dev/null +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/AssemblyNameInfoTests.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Reflection.Metadata.Tests.Metadata +{ + public class AssemblyNameInfoTests + { + [Theory] + [InlineData("MyAssemblyName, Version=1.0.0.0, PublicKeyToken=b77a5c561934e089", "MyAssemblyName, Version=1.0.0.0, PublicKeyToken=b77a5c561934e089")] + [InlineData("MyAssemblyName, Version=1.0.0.0, PublicKey=00000000000000000400000000000000", "MyAssemblyName, Version=1.0.0.0, PublicKeyToken=b77a5c561934e089")] + [InlineData("TerraFX.Interop.Windows, PublicKey=" + + "002400000c800000940000000602000000240000525341310004000001000100897039f5ff762b25b9ba982c3f5836c34e299279c33df505bf806a07bccdf0e1216e661943f557b954cb18422ed522a5" + + "b3174b85385052677f39c4ce19f30a1ddbaa507054bc5943461651f396afc612cd80419c5ee2b5277571ff65f51d14ba99e4e4196de0f393e89850a465f019dbdc365ed5e81bbafe1370f54efd254ba8", + "TerraFX.Interop.Windows, PublicKeyToken=35b01b53313a6f7e")] + public void WithPublicKeyOrToken(string name, string expectedName) + { + AssemblyName assemblyName = new AssemblyName(name); + + AssemblyNameInfo assemblyNameInfo = AssemblyNameInfo.Parse(name.AsSpan()); + + Assert.Equal(expectedName, assemblyName.FullName); + Assert.Equal(expectedName, assemblyNameInfo.FullName); + + Roundtrip(assemblyName); + } + + [Fact] + public void NoPublicKeyOrToken() + { + AssemblyName source = new AssemblyName(); + source.Name = "test"; + source.Version = new Version(1, 2, 3, 4); + source.CultureName = "en-US"; + + Roundtrip(source); + } + + static void Roundtrip(AssemblyName source) + { + AssemblyNameInfo parsed = AssemblyNameInfo.Parse(source.FullName.AsSpan()); + Assert.Equal(source.Name, parsed.Name); + Assert.Equal(source.Version, parsed.Version); + Assert.Equal(source.CultureName, parsed.CultureName); + Assert.Equal(source.FullName, parsed.FullName); + + AssemblyName fromParsed = parsed.ToAssemblyName(); + Assert.Equal(source.Name, fromParsed.Name); + Assert.Equal(source.Version, fromParsed.Version); + Assert.Equal(source.CultureName, fromParsed.CultureName); + Assert.Equal(source.FullName, fromParsed.FullName); + } + } +} diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameParserSamples.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameParserSamples.cs index 3176d6e8196f0..0c48cdc2641d9 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameParserSamples.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameParserSamples.cs @@ -70,7 +70,7 @@ public SampleSerializationBinder(Type[]? allowedTypes = null) throw new InvalidOperationException($"Invalid type name: '{typeName}'"); } - if (parsed.GetAssemblyName() is not null) + if (parsed.AssemblyName is not null) { // The attackers may create such a payload, // where "typeName" passed to BindToType contains the assembly name diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameTests.cs index bbd8344367e01..14d0037cfed04 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameTests.cs @@ -113,19 +113,16 @@ static void Verify(Type type, AssemblyName expectedAssemblyName, TypeName parsed Assert.Equal(type.FullName, parsed.FullName); Assert.Equal(type.Name, parsed.Name); - AssemblyName parsedAssemblyName = parsed.GetAssemblyName(); + AssemblyNameInfo parsedAssemblyName = parsed.AssemblyName; Assert.NotNull(parsedAssemblyName); Assert.Equal(expectedAssemblyName.Name, parsedAssemblyName.Name); - Assert.Equal(expectedAssemblyName.Name, parsed.AssemblySimpleName); Assert.Equal(expectedAssemblyName.Version, parsedAssemblyName.Version); Assert.Equal(expectedAssemblyName.CultureName, parsedAssemblyName.CultureName); - Assert.Equal(expectedAssemblyName.GetPublicKeyToken(), parsedAssemblyName.GetPublicKeyToken()); + Assert.Equal(expectedAssemblyName.GetPublicKeyToken(), parsedAssemblyName.PublicKeyToken.ToArray()); Assert.Equal(expectedAssemblyName.FullName, parsedAssemblyName.FullName); - Assert.Equal(default, parsedAssemblyName.ContentType); Assert.Equal(default, parsedAssemblyName.Flags); - Assert.Equal(default, parsedAssemblyName.ProcessorArchitecture); } } @@ -375,12 +372,12 @@ public void GenericArgumentsAreSupported(string input, string name, string fullN { if (assemblyNames[i] is null) { - Assert.Null(genericArg.GetAssemblyName()); + Assert.Null(genericArg.AssemblyName); } else { - Assert.Equal(assemblyNames[i].FullName, genericArg.GetAssemblyName().FullName); - Assert.Equal(assemblyNames[i].Name, genericArg.AssemblySimpleName); + Assert.Equal(assemblyNames[i].FullName, genericArg.AssemblyName.FullName); + Assert.Equal(assemblyNames[i].Name, genericArg.AssemblyName.Name); } } } @@ -691,7 +688,7 @@ static void Verify(Type type, TypeName typeName, bool ignoreCase) { Assert.True(typeName.IsSimple); - AssemblyName? assemblyName = typeName.GetAssemblyName(); + AssemblyName? assemblyName = typeName.AssemblyName.ToAssemblyName(); Type? type = assemblyName is null ? Type.GetType(typeName.FullName, throwOnError, ignoreCase) : Assembly.Load(assemblyName).GetType(typeName.FullName, throwOnError, ignoreCase); diff --git a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj index 134c82d238b6a..f739ac0789ff8 100644 --- a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj +++ b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj @@ -27,6 +27,7 @@ Link="Common\System\IO\TempFile.cs" /> +