< Summary

Information
Line coverage
93%
Covered lines: 276
Uncovered lines: 20
Coverable lines: 296
Total lines: 504
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)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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[]
 494021            {
 494022                _name = name;
 494023                _version = version;
 494024                _cultureName = cultureName;
 494025                _flags = flags;
 494026                _publicKeyOrToken = publicKeyOrToken;
 494027            }
 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)
 840861        {
 62#if SYSTEM_PRIVATE_CORELIB
 63            if (input.Length == 0)
 64                throw new ArgumentException(SR.Format_StringZeroLength);
 65#else
 840866            Debug.Assert(input.Length > 0);
 67#endif
 68
 840869            _input = input;
 840870            _index = 0;
 840871        }
 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)
 840889        {
 840890            AssemblyNameParser parser = new(name);
 840891            return parser.TryParse(ref parts);
 840892        }
 93
 94        private static bool TryRecordNewSeen(scoped ref AttributeKind seenAttributes, AttributeKind newAttribute)
 324095        {
 324096            if ((seenAttributes & newAttribute) != 0)
 46897            {
 46898                return false;
 99            }
 2772100            seenAttributes |= newAttribute;
 2772101            return true;
 3240102        }
 103
 104        private bool TryParse(ref AssemblyNameParts result)
 8408105        {
 106            // Name must come first.
 8408107            if (!TryGetNextToken(out string name, out Token token) || token != Token.String || string.IsNullOrEmpty(name
 580108                return false;
 109
 7828110            Version? version = null;
 7828111            string? cultureName = null;
 7828112            byte[]? pkt = null;
 7828113            AssemblyNameFlags flags = 0;
 114
 7828115            AttributeKind alreadySeen = default;
 7828116            if (!TryGetNextToken(out _, out token))
 52117                return false;
 118
 28248119            while (token != Token.End)
 23308120            {
 23308121                if (token != Token.Comma)
 288122                    return false;
 123
 23020124                if (!TryGetNextToken(out string attributeName, out token) || token != Token.String)
 236125                    return false;
 126
 22784127                if (!TryGetNextToken(out _, out token) || token != Token.Equals)
 728128                    return false;
 129
 22056130                if (!TryGetNextToken(out string attributeValue, out token) || token != Token.String)
 36131                    return false;
 132
 22020133                if (attributeName == string.Empty)
 16134                    return false;
 135
 22004136                if (IsAttribute(attributeName, "Version"))
 1040137                {
 1040138                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Version))
 120139                    {
 120140                        return false;
 141                    }
 920142                    if (!TryParseVersion(attributeValue, ref version))
 284143                    {
 284144                        return false;
 145                    }
 636146                }
 20964147                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                }
 20964158                else if (IsAttribute(attributeName, "PublicKeyToken"))
 436159                {
 436160                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.PublicKeyOrToken))
 80161                    {
 80162                        return false;
 163                    }
 356164                    if (!TryParsePKT(attributeValue, isToken: true, out pkt))
 204165                    {
 204166                        return false;
 167                    }
 152168                }
 20528169                else if (IsAttribute(attributeName, "PublicKey"))
 1284170                {
 1284171                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.PublicKeyOrToken))
 132172                    {
 132173                        return false;
 174                    }
 1152175                    if (!TryParsePKT(attributeValue, isToken: false, out pkt))
 348176                    {
 348177                        return false;
 178                    }
 804179                    flags |= AssemblyNameFlags.PublicKey;
 804180                }
 19244181                else if (IsAttribute(attributeName, "ProcessorArchitecture"))
 184182                {
 184183                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.ProcessorArchitecture))
 40184                    {
 40185                        return false;
 186                    }
 144187                    if (!TryParseProcessorArchitecture(attributeValue, out ProcessorArchitecture arch))
 20188                    {
 20189                        return false;
 190                    }
 124191                    flags |= (AssemblyNameFlags)(((int)arch) << 4);
 124192                }
 19060193                else if (IsAttribute(attributeName, "Retargetable"))
 112194                {
 112195                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Retargetable))
 36196                    {
 36197                        return false;
 198                    }
 199
 76200                    if (attributeValue.Equals("Yes", StringComparison.OrdinalIgnoreCase))
 52201                    {
 52202                        flags |= AssemblyNameFlags.Retargetable;
 52203                    }
 24204                    else if (attributeValue.Equals("No", StringComparison.OrdinalIgnoreCase))
 0205                    {
 206                        // nothing to do
 0207                    }
 208                    else
 24209                    {
 24210                        return false;
 211                    }
 52212                }
 18948213                else if (IsAttribute(attributeName, "ContentType"))
 184214                {
 184215                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.ContentType))
 60216                    {
 60217                        return false;
 218                    }
 219
 124220                    if (attributeValue.Equals("WindowsRuntime", StringComparison.OrdinalIgnoreCase))
 72221                    {
 72222                        flags |= (AssemblyNameFlags)(((int)AssemblyContentType.WindowsRuntime) << 9);
 72223                    }
 224                    else
 52225                    {
 52226                        return false;
 227                    }
 72228                }
 229                else
 18764230                {
 231                    // Desktop compat: If we got here, the attribute name is unknown to us. Ignore it.
 18764232                }
 233
 20604234                if (!TryGetNextToken(out _, out token))
 132235                {
 132236                    return false;
 237                }
 20472238            }
 239
 4940240            result = new AssemblyNameParts(name, version, cultureName, flags, pkt);
 4940241            return true;
 8408242        }
 243
 244        private static bool IsAttribute(string candidate, string attributeKind)
 141712245            => candidate.Equals(attributeKind, StringComparison.OrdinalIgnoreCase);
 246
 247        private static unsafe bool TryParseVersion(string attributeValue, ref Version? version)
 920248        {
 249#if NET
 920250            ReadOnlySpan<char> attributeValueSpan = attributeValue;
 920251            Span<Range> parts = stackalloc Range[5];
 920252            parts = parts.Slice(0, attributeValueSpan.Split(parts, '.'));
 253#else
 254            string[] parts = attributeValue.Split('.');
 255#endif
 920256            if (parts.Length is < 2 or > 4)
 92257            {
 92258                return false;
 259            }
 260
 828261            Span<ushort> versionNumbers = [ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue];
 5776262            for (int i = 0; i < parts.Length; i++)
 2252263            {
 2252264                if (!ushort.TryParse(
 2252265#if NET
 2252266                    attributeValueSpan[parts[i]],
 2252267#else
 2252268                    parts[i],
 2252269#endif
 2252270                    NumberStyles.None, NumberFormatInfo.InvariantInfo, out versionNumbers[i]))
 192271                {
 192272                    return false;
 273                }
 2060274            }
 275
 636276            if (versionNumbers[0] == ushort.MaxValue ||
 636277                versionNumbers[1] == ushort.MaxValue)
 0278            {
 0279                return false;
 280            }
 281
 636282            version =
 636283                versionNumbers[2] == ushort.MaxValue ? new Version(versionNumbers[0], versionNumbers[1]) :
 636284                versionNumbers[3] == ushort.MaxValue ? new Version(versionNumbers[0], versionNumbers[1], versionNumbers[
 636285                new Version(versionNumbers[0], versionNumbers[1], versionNumbers[2], versionNumbers[3]);
 286
 636287            return true;
 920288        }
 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)
 1508303        {
 1508304            if (attributeValue.Equals("null", StringComparison.OrdinalIgnoreCase) || attributeValue == string.Empty)
 52305            {
 52306                result = [];
 52307                return true;
 308            }
 309
 1456310            if (attributeValue.Length % 2 != 0 || (isToken && attributeValue.Length != 8 * 2))
 92311            {
 92312                result = null;
 92313                return false;
 314            }
 315
 1364316            byte[] pkt = new byte[attributeValue.Length / 2];
 1364317            if (!HexConverter.TryDecodeFromUtf16(attributeValue.AsSpan(), pkt, out int _))
 460318            {
 460319                result = null;
 460320                return false;
 321            }
 322
 904323            result = pkt;
 904324            return true;
 1508325        }
 326
 327        private static bool TryParseProcessorArchitecture(string attributeValue, out ProcessorArchitecture result)
 144328        {
 144329            result = attributeValue switch
 144330            {
 164331                _ when attributeValue.Equals("msil", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.MSIL,
 148332                _ when attributeValue.Equals("x86", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.X86,
 100333                _ when attributeValue.Equals("ia64", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.IA64,
 156334                _ when attributeValue.Equals("amd64", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.Amd64
 68335                _ when attributeValue.Equals("arm", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.Arm,
 20336                _ => ProcessorArchitecture.None
 144337            };
 144338            return result != ProcessorArchitecture.None;
 144339        }
 340
 341        private static bool IsWhiteSpace(char ch)
 120460342            => ch is '\n' or '\r' or ' ' or '\t';
 343
 344        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 345        private bool TryGetNextChar(out char ch)
 1232760346        {
 1232760347            if (_index < _input.Length)
 1220556348            {
 1220556349                ch = _input[_index++];
 1220556350                if (ch == '\0')
 140351                {
 140352                    return false;
 353                }
 1220416354            }
 355            else
 12204356            {
 12204357                ch = '\0';
 12204358            }
 359
 1232620360            return true;
 1232760361        }
 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)
 104700368        {
 104700369            tokenString = string.Empty;
 370            char c;
 371
 115056372            while (true)
 115056373            {
 115056374                if (!TryGetNextChar(out c))
 32375                {
 32376                    token = default;
 32377                    return false;
 378                }
 379
 115024380                switch (c)
 381                {
 382                    case ',':
 23488383                    {
 23488384                        token = Token.Comma;
 23488385                        return true;
 386                    }
 387                    case '=':
 22320388                    {
 22320389                        token = Token.Equals;
 22320390                        return true;
 391                    }
 392                    case '\0':
 5444393                    {
 5444394                        token = Token.End;
 5444395                        return true;
 396                    }
 397                }
 398
 63772399                if (!IsWhiteSpace(c))
 53416400                {
 53416401                    break;
 402                }
 10356403            }
 404
 53416405            using ValueStringBuilder sb = new ValueStringBuilder(stackalloc char[64]);
 406
 53416407            char quoteChar = '\0';
 53416408            if (c is '\'' or '\"')
 696409            {
 696410                quoteChar = c;
 696411                if (!TryGetNextChar(out c))
 8412                {
 8413                    token = default;
 8414                    return false;
 415                }
 688416            }
 417
 418            for (; ; )
 1141844419            {
 1141844420                if (c == 0)
 6740421                {
 6740422                    if (quoteChar != 0)
 276423                    {
 424                        // EOS and unclosed quotes is an error
 276425                        token = default;
 276426                        return false;
 427                    }
 428                    // Reached end of input and therefore of string
 6464429                    break;
 430                }
 431
 1135104432                if (quoteChar != 0 && c == quoteChar)
 412433                    break;  // Terminate: Found closing quote of quoted string.
 434
 1134692435                if (quoteChar == 0 && (c is ',' or '='))
 45828436                {
 45828437                    _index--;
 45828438                    break;  // Terminate: Found start of a new ',' or '=' token.
 439                }
 440
 1088864441                if (quoteChar == 0 && (c is '\'' or '\"'))
 40442                {
 40443                    token = default;
 40444                    return false;
 445                }
 446
 1088824447                if (c is '\\')
 28500448                {
 28500449                    if (!TryGetNextChar(out c))
 28450                    {
 28451                        token = default;
 28452                        return false;
 453                    }
 454
 28472455                    switch (c)
 456                    {
 457                        case '\\':
 458                        case ',':
 459                        case '=':
 460                        case '\'':
 461                        case '"':
 11208462                            sb.Append(c);
 11208463                            break;
 464                        case 't':
 6808465                            sb.Append('\t');
 6808466                            break;
 467                        case 'r':
 7884468                            sb.Append('\r');
 7884469                            break;
 470                        case 'n':
 2284471                            sb.Append('\n');
 2284472                            break;
 473                        default:
 288474                            token = default;
 288475                            return false;
 476                    }
 28184477                }
 478                else
 1060324479                {
 1060324480                    sb.Append(c);
 1060324481                }
 482
 1088508483                if (!TryGetNextChar(out c))
 72484                {
 72485                    token = default;
 72486                    return false;
 487                }
 1088436488            }
 489
 490
 52704491            int length = sb.Length;
 52704492            if (quoteChar == 0)
 52292493            {
 56736494                while (length > 0 && IsWhiteSpace(sb[length - 1]))
 4444495                    length--;
 52292496            }
 497
 52704498            tokenString = sb.AsSpan(0, length).ToString();
 52704499            token = Token.String;
 52704500            return true;
 104700501        }
 502    }
 503}
 504