< Summary

Information
Class: System.Reflection.AssemblyNameParser
Assembly: System.Reflection.Metadata
File(s): D:\runner\runtime\src\libraries\Common\src\System\Reflection\AssemblyNameParser.cs
Line coverage
93%
Covered lines: 276
Uncovered lines: 20
Coverable lines: 296
Total lines: 503
Line coverage: 93.2%
Branch coverage
94%
Covered branches: 176
Total branches: 186
Branch coverage: 94.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%
.ctor(...)100%11100%
TryParse(...)100%11100%
TryRecordNewSeen(...)100%22100%
TryParse(...)91.66%727290.47%
IsAttribute(...)100%11100%
TryParseVersion(...)87.5%161693.33%
TryParseCulture(...)0%220%
TryParsePKT(...)100%1212100%
TryParseProcessorArchitecture(...)100%11100%
IsWhiteSpace(...)100%88100%
TryGetNextChar(...)100%44100%
TryGetNextToken(...)100%7070100%

File(s)

D:\runner\runtime\src\libraries\Common\src\System\Reflection\AssemblyNameParser.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Diagnostics.CodeAnalysis;
 6using System.Globalization;
 7using System.IO;
 8using System.Runtime.CompilerServices;
 9using System.Text;
 10
 11namespace System.Reflection
 12{
 13    /// <summary>
 14    /// Parses an assembly name.
 15    /// </summary>
 16    internal ref partial struct AssemblyNameParser
 17    {
 18        public readonly struct AssemblyNameParts
 19        {
 20            public AssemblyNameParts(string name, Version? version, string? cultureName, AssemblyNameFlags flags, byte[]
 470421            {
 470422                _name = name;
 470423                _version = version;
 470424                _cultureName = cultureName;
 470425                _flags = flags;
 470426                _publicKeyOrToken = publicKeyOrToken;
 470427            }
 28
 29            public readonly string _name;
 30            public readonly Version? _version;
 31            public readonly string? _cultureName;
 32            public readonly AssemblyNameFlags _flags;
 33            public readonly byte[]? _publicKeyOrToken;
 34        }
 35
 36        /// <summary>
 37        /// Token categories for the lexer.
 38        /// </summary>
 39        private enum Token
 40        {
 41            Equals = 1,
 42            Comma = 2,
 43            String = 3,
 44            End = 4,
 45        }
 46
 47        private enum AttributeKind
 48        {
 49            Version = 1,
 50            Culture = 2,
 51            PublicKeyOrToken = 4,
 52            ProcessorArchitecture = 8,
 53            Retargetable = 16,
 54            ContentType = 32
 55        }
 56
 57        private readonly ReadOnlySpan<char> _input;
 58        private int _index;
 59
 60        private AssemblyNameParser(ReadOnlySpan<char> input)
 1014061        {
 62#if SYSTEM_PRIVATE_CORELIB
 63            if (input.Length == 0)
 64                throw new ArgumentException(SR.Format_StringZeroLength);
 65#else
 1014066            Debug.Assert(input.Length > 0);
 67#endif
 68
 1014069            _input = input;
 1014070            _index = 0;
 1014071        }
 72
 73#if SYSTEM_PRIVATE_CORELIB
 74        public static AssemblyNameParts Parse(string name) => Parse(name.AsSpan());
 75
 76        public static AssemblyNameParts Parse(ReadOnlySpan<char> name)
 77        {
 78            AssemblyNameParser parser = new(name);
 79            AssemblyNameParts result = default;
 80            if (parser.TryParse(ref result))
 81            {
 82                return result;
 83            }
 84            throw new FileLoadException(SR.InvalidAssemblyName, name.ToString());
 85        }
 86#endif
 87
 88        internal static bool TryParse(ReadOnlySpan<char> name, ref AssemblyNameParts parts)
 1014089        {
 1014090            AssemblyNameParser parser = new(name);
 1014091            return parser.TryParse(ref parts);
 1014092        }
 93
 94        private static bool TryRecordNewSeen(scoped ref AttributeKind seenAttributes, AttributeKind newAttribute)
 480495        {
 480496            if ((seenAttributes & newAttribute) != 0)
 63297            {
 63298                return false;
 99            }
 4172100            seenAttributes |= newAttribute;
 4172101            return true;
 4804102        }
 103
 104        private bool TryParse(ref AssemblyNameParts result)
 10140105        {
 106            // Name must come first.
 10140107            if (!TryGetNextToken(out string name, out Token token) || token != Token.String || string.IsNullOrEmpty(name
 984108                return false;
 109
 9156110            Version? version = null;
 9156111            string? cultureName = null;
 9156112            byte[]? pkt = null;
 9156113            AssemblyNameFlags flags = 0;
 114
 9156115            AttributeKind alreadySeen = default;
 9156116            if (!TryGetNextToken(out _, out token))
 72117                return false;
 118
 42556119            while (token != Token.End)
 37852120            {
 37852121                if (token != Token.Comma)
 444122                    return false;
 123
 37408124                if (!TryGetNextToken(out string attributeName, out token) || token != Token.String)
 320125                    return false;
 126
 37088127                if (!TryGetNextToken(out _, out token) || token != Token.Equals)
 1012128                    return false;
 129
 36076130                if (!TryGetNextToken(out string attributeValue, out token) || token != Token.String)
 292131                    return false;
 132
 35784133                if (attributeName == string.Empty)
 76134                    return false;
 135
 35708136                if (IsAttribute(attributeName, "Version"))
 1728137                {
 1728138                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Version))
 228139                    {
 228140                        return false;
 141                    }
 1500142                    if (!TryParseVersion(attributeValue, ref version))
 432143                    {
 432144                        return false;
 145                    }
 1068146                }
 33980147                else if (IsAttribute(attributeName, "Culture"))
 0148                {
 0149                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Culture))
 0150                    {
 0151                        return false;
 152                    }
 0153                    if (!TryParseCulture(attributeValue, out cultureName))
 0154                    {
 0155                        return false;
 156                    }
 0157                }
 33980158                else if (IsAttribute(attributeName, "PublicKeyToken"))
 952159                {
 952160                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.PublicKeyOrToken))
 196161                    {
 196162                        return false;
 163                    }
 756164                    if (!TryParsePKT(attributeValue, isToken: true, out pkt))
 528165                    {
 528166                        return false;
 167                    }
 228168                }
 33028169                else if (IsAttribute(attributeName, "PublicKey"))
 1016170                {
 1016171                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.PublicKeyOrToken))
 88172                    {
 88173                        return false;
 174                    }
 928175                    if (!TryParsePKT(attributeValue, isToken: false, out pkt))
 304176                    {
 304177                        return false;
 178                    }
 624179                    flags |= AssemblyNameFlags.PublicKey;
 624180                }
 32012181                else if (IsAttribute(attributeName, "ProcessorArchitecture"))
 468182                {
 468183                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.ProcessorArchitecture))
 24184                    {
 24185                        return false;
 186                    }
 444187                    if (!TryParseProcessorArchitecture(attributeValue, out ProcessorArchitecture arch))
 88188                    {
 88189                        return false;
 190                    }
 356191                    flags |= (AssemblyNameFlags)(((int)arch) << 4);
 356192                }
 31544193                else if (IsAttribute(attributeName, "Retargetable"))
 372194                {
 372195                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Retargetable))
 28196                    {
 28197                        return false;
 198                    }
 199
 344200                    if (attributeValue.Equals("Yes", StringComparison.OrdinalIgnoreCase))
 272201                    {
 272202                        flags |= AssemblyNameFlags.Retargetable;
 272203                    }
 72204                    else if (attributeValue.Equals("No", StringComparison.OrdinalIgnoreCase))
 0205                    {
 206                        // nothing to do
 0207                    }
 208                    else
 72209                    {
 72210                        return false;
 211                    }
 272212                }
 31172213                else if (IsAttribute(attributeName, "ContentType"))
 268214                {
 268215                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.ContentType))
 68216                    {
 68217                        return false;
 218                    }
 219
 200220                    if (attributeValue.Equals("WindowsRuntime", StringComparison.OrdinalIgnoreCase))
 96221                    {
 96222                        flags |= (AssemblyNameFlags)(((int)AssemblyContentType.WindowsRuntime) << 9);
 96223                    }
 224                    else
 104225                    {
 104226                        return false;
 227                    }
 96228                }
 229                else
 30904230                {
 231                    // Desktop compat: If we got here, the attribute name is unknown to us. Ignore it.
 30904232                }
 233
 33548234                if (!TryGetNextToken(out _, out token))
 76235                {
 76236                    return false;
 237                }
 33472238            }
 239
 4704240            result = new AssemblyNameParts(name, version, cultureName, flags, pkt);
 4704241            return true;
 10140242        }
 243
 244        private static bool IsAttribute(string candidate, string attributeKind)
 231424245            => candidate.Equals(attributeKind, StringComparison.OrdinalIgnoreCase);
 246
 247        private static unsafe bool TryParseVersion(string attributeValue, ref Version? version)
 1500248        {
 249#if NET
 1500250            ReadOnlySpan<char> attributeValueSpan = attributeValue;
 1500251            Span<Range> parts = stackalloc Range[5];
 1500252            parts = parts.Slice(0, attributeValueSpan.Split(parts, '.'));
 253#else
 254            string[] parts = attributeValue.Split('.');
 255#endif
 1500256            if (parts.Length is < 2 or > 4)
 172257            {
 172258                return false;
 259            }
 260
 1328261            Span<ushort> versionNumbers = [ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue];
 10928262            for (int i = 0; i < parts.Length; i++)
 4396263            {
 4396264                if (!ushort.TryParse(
 4396265#if NET
 4396266                    attributeValueSpan[parts[i]],
 4396267#else
 4396268                    parts[i],
 4396269#endif
 4396270                    NumberStyles.None, NumberFormatInfo.InvariantInfo, out versionNumbers[i]))
 260271                {
 260272                    return false;
 273                }
 4136274            }
 275
 1068276            if (versionNumbers[0] == ushort.MaxValue ||
 1068277                versionNumbers[1] == ushort.MaxValue)
 0278            {
 0279                return false;
 280            }
 281
 1068282            version =
 1068283                versionNumbers[2] == ushort.MaxValue ? new Version(versionNumbers[0], versionNumbers[1]) :
 1068284                versionNumbers[3] == ushort.MaxValue ? new Version(versionNumbers[0], versionNumbers[1], versionNumbers[
 1068285                new Version(versionNumbers[0], versionNumbers[1], versionNumbers[2], versionNumbers[3]);
 286
 1068287            return true;
 1500288        }
 289
 290        private static bool TryParseCulture(string attributeValue, out string? result)
 0291        {
 0292            if (attributeValue.Equals("Neutral", StringComparison.OrdinalIgnoreCase))
 0293            {
 0294                result = "";
 0295                return true;
 296            }
 297
 0298            result = attributeValue;
 0299            return true;
 0300        }
 301
 302        private static bool TryParsePKT(string attributeValue, bool isToken, out byte[]? result)
 1684303        {
 1684304            if (attributeValue.Equals("null", StringComparison.OrdinalIgnoreCase) || attributeValue == string.Empty)
 240305            {
 240306                result = [];
 240307                return true;
 308            }
 309
 1444310            if (attributeValue.Length % 2 != 0 || (isToken && attributeValue.Length != 8 * 2))
 412311            {
 412312                result = null;
 412313                return false;
 314            }
 315
 1032316            byte[] pkt = new byte[attributeValue.Length / 2];
 1032317            if (!HexConverter.TryDecodeFromUtf16(attributeValue.AsSpan(), pkt, out int _))
 420318            {
 420319                result = null;
 420320                return false;
 321            }
 322
 612323            result = pkt;
 612324            return true;
 1684325        }
 326
 327        private static bool TryParseProcessorArchitecture(string attributeValue, out ProcessorArchitecture result)
 444328        {
 444329            result = attributeValue switch
 444330            {
 712331                _ when attributeValue.Equals("msil", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.MSIL,
 216332                _ when attributeValue.Equals("x86", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.X86,
 136333                _ when attributeValue.Equals("ia64", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.IA64,
 160334                _ when attributeValue.Equals("amd64", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.Amd64
 136335                _ when attributeValue.Equals("arm", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.Arm,
 88336                _ => ProcessorArchitecture.None
 444337            };
 444338            return result != ProcessorArchitecture.None;
 444339        }
 340
 341        private static bool IsWhiteSpace(char ch)
 186228342            => ch is '\n' or '\r' or ' ' or '\t';
 343
 344        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 345        private bool TryGetNextChar(out char ch)
 1445748346        {
 1445748347            if (_index < _input.Length)
 1433000348            {
 1433000349                ch = _input[_index++];
 1433000350                if (ch == '\0')
 368351                {
 368352                    return false;
 353                }
 1432632354            }
 355            else
 12748356            {
 12748357                ch = '\0';
 12748358            }
 359
 1445380360            return true;
 1445748361        }
 362
 363        //
 364        // Return the next token in assembly name. If the result is Token.String,
 365        // sets "tokenString" to the tokenized string.
 366        //
 367        private unsafe bool TryGetNextToken(out string tokenString, out Token token)
 163416368        {
 163416369            tokenString = string.Empty;
 370            char c;
 371
 179060372            while (true)
 179060373            {
 179060374                if (!TryGetNextChar(out c))
 20375                {
 20376                    token = default;
 20377                    return false;
 378                }
 379
 179040380                switch (c)
 381                {
 382                    case ',':
 37928383                    {
 37928384                        token = Token.Comma;
 37928385                        return true;
 386                    }
 387                    case '=':
 36360388                    {
 36360389                        token = Token.Equals;
 36360390                        return true;
 391                    }
 392                    case '\0':
 5444393                    {
 5444394                        token = Token.End;
 5444395                        return true;
 396                    }
 397                }
 398
 99308399                if (!IsWhiteSpace(c))
 83664400                {
 83664401                    break;
 402                }
 15644403            }
 404
 83664405            using ValueStringBuilder sb = new ValueStringBuilder(stackalloc char[64]);
 406
 83664407            char quoteChar = '\0';
 83664408            if (c is '\'' or '\"')
 1688409            {
 1688410                quoteChar = c;
 1688411                if (!TryGetNextChar(out c))
 8412                {
 8413                    token = default;
 8414                    return false;
 415                }
 1680416            }
 417
 418            for (; ; )
 1319848419            {
 1319848420                if (c == 0)
 7060421                {
 7060422                    if (quoteChar != 0)
 552423                    {
 424                        // EOS and unclosed quotes is an error
 552425                        token = default;
 552426                        return false;
 427                    }
 428                    // Reached end of input and therefore of string
 6508429                    break;
 430                }
 431
 1312788432                if (quoteChar != 0 && c == quoteChar)
 1000433                    break;  // Terminate: Found closing quote of quoted string.
 434
 1311788435                if (quoteChar == 0 && (c is ',' or '='))
 74752436                {
 74752437                    _index--;
 74752438                    break;  // Terminate: Found start of a new ',' or '=' token.
 439                }
 440
 1237036441                if (quoteChar == 0 && (c is '\'' or '\"'))
 164442                {
 164443                    token = default;
 164444                    return false;
 445                }
 446
 1236872447                if (c is '\\')
 28472448                {
 28472449                    if (!TryGetNextChar(out c))
 4450                    {
 4451                        token = default;
 4452                        return false;
 453                    }
 454
 28468455                    switch (c)
 456                    {
 457                        case '\\':
 458                        case ',':
 459                        case '=':
 460                        case '\'':
 461                        case '"':
 23688462                            sb.Append(c);
 23688463                            break;
 464                        case 't':
 2388465                            sb.Append('\t');
 2388466                            break;
 467                        case 'r':
 996468                            sb.Append('\r');
 996469                            break;
 470                        case 'n':
 1056471                            sb.Append('\n');
 1056472                            break;
 473                        default:
 340474                            token = default;
 340475                            return false;
 476                    }
 28128477                }
 478                else
 1208400479                {
 1208400480                    sb.Append(c);
 1208400481                }
 482
 1236528483                if (!TryGetNextChar(out c))
 336484                {
 336485                    token = default;
 336486                    return false;
 487                }
 1236192488            }
 489
 490
 82260491            int length = sb.Length;
 82260492            if (quoteChar == 0)
 81260493            {
 86976494                while (length > 0 && IsWhiteSpace(sb[length - 1]))
 5716495                    length--;
 81260496            }
 497
 82260498            tokenString = sb.AsSpan(0, length).ToString();
 82260499            token = Token.String;
 82260500            return true;
 163416501        }
 502    }
 503}