< Summary

Information
Line coverage
52%
Covered lines: 155
Uncovered lines: 141
Coverable lines: 296
Total lines: 504
Line coverage: 52.3%
Branch coverage
62%
Covered branches: 117
Total branches: 186
Branch coverage: 62.9%
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(...)0%220%
TryParse(...)48.61%727236.19%
IsAttribute(...)100%11100%
TryParseVersion(...)0%16160%
TryParseCulture(...)0%220%
TryParsePKT(...)0%12120%
TryParseProcessorArchitecture(...)100%110%
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[]
 715621            {
 715622                _name = name;
 715623                _version = version;
 715624                _cultureName = cultureName;
 715625                _flags = flags;
 715626                _publicKeyOrToken = publicKeyOrToken;
 715627            }
 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)
 1114061        {
 62#if SYSTEM_PRIVATE_CORELIB
 63            if (input.Length == 0)
 64                throw new ArgumentException(SR.Format_StringZeroLength);
 65#else
 1114066            Debug.Assert(input.Length > 0);
 67#endif
 68
 1114069            _input = input;
 1114070            _index = 0;
 1114071        }
 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)
 1114089        {
 1114090            AssemblyNameParser parser = new(name);
 1114091            return parser.TryParse(ref parts);
 1114092        }
 93
 94        private static bool TryRecordNewSeen(scoped ref AttributeKind seenAttributes, AttributeKind newAttribute)
 095        {
 096            if ((seenAttributes & newAttribute) != 0)
 097            {
 098                return false;
 99            }
 0100            seenAttributes |= newAttribute;
 0101            return true;
 0102        }
 103
 104        private bool TryParse(ref AssemblyNameParts result)
 11140105        {
 106            // Name must come first.
 11140107            if (!TryGetNextToken(out string name, out Token token) || token != Token.String || string.IsNullOrEmpty(name
 1144108                return false;
 109
 9996110            Version? version = null;
 9996111            string? cultureName = null;
 9996112            byte[]? pkt = null;
 9996113            AssemblyNameFlags flags = 0;
 114
 9996115            AttributeKind alreadySeen = default;
 9996116            if (!TryGetNextToken(out _, out token))
 52117                return false;
 118
 24920119            while (token != Token.End)
 17764120            {
 17764121                if (token != Token.Comma)
 104122                    return false;
 123
 17660124                if (!TryGetNextToken(out string attributeName, out token) || token != Token.String)
 560125                    return false;
 126
 17100127                if (!TryGetNextToken(out _, out token) || token != Token.Equals)
 1936128                    return false;
 129
 15164130                if (!TryGetNextToken(out string attributeValue, out token) || token != Token.String)
 148131                    return false;
 132
 15016133                if (attributeName == string.Empty)
 4134                    return false;
 135
 15012136                if (IsAttribute(attributeName, "Version"))
 0137                {
 0138                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Version))
 0139                    {
 0140                        return false;
 141                    }
 0142                    if (!TryParseVersion(attributeValue, ref version))
 0143                    {
 0144                        return false;
 145                    }
 0146                }
 15012147                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                }
 15012158                else if (IsAttribute(attributeName, "PublicKeyToken"))
 0159                {
 0160                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.PublicKeyOrToken))
 0161                    {
 0162                        return false;
 163                    }
 0164                    if (!TryParsePKT(attributeValue, isToken: true, out pkt))
 0165                    {
 0166                        return false;
 167                    }
 0168                }
 15012169                else if (IsAttribute(attributeName, "PublicKey"))
 0170                {
 0171                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.PublicKeyOrToken))
 0172                    {
 0173                        return false;
 174                    }
 0175                    if (!TryParsePKT(attributeValue, isToken: false, out pkt))
 0176                    {
 0177                        return false;
 178                    }
 0179                    flags |= AssemblyNameFlags.PublicKey;
 0180                }
 15012181                else if (IsAttribute(attributeName, "ProcessorArchitecture"))
 0182                {
 0183                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.ProcessorArchitecture))
 0184                    {
 0185                        return false;
 186                    }
 0187                    if (!TryParseProcessorArchitecture(attributeValue, out ProcessorArchitecture arch))
 0188                    {
 0189                        return false;
 190                    }
 0191                    flags |= (AssemblyNameFlags)(((int)arch) << 4);
 0192                }
 15012193                else if (IsAttribute(attributeName, "Retargetable"))
 0194                {
 0195                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.Retargetable))
 0196                    {
 0197                        return false;
 198                    }
 199
 0200                    if (attributeValue.Equals("Yes", StringComparison.OrdinalIgnoreCase))
 0201                    {
 0202                        flags |= AssemblyNameFlags.Retargetable;
 0203                    }
 0204                    else if (attributeValue.Equals("No", StringComparison.OrdinalIgnoreCase))
 0205                    {
 206                        // nothing to do
 0207                    }
 208                    else
 0209                    {
 0210                        return false;
 211                    }
 0212                }
 15012213                else if (IsAttribute(attributeName, "ContentType"))
 0214                {
 0215                    if (!TryRecordNewSeen(ref alreadySeen, AttributeKind.ContentType))
 0216                    {
 0217                        return false;
 218                    }
 219
 0220                    if (attributeValue.Equals("WindowsRuntime", StringComparison.OrdinalIgnoreCase))
 0221                    {
 0222                        flags |= (AssemblyNameFlags)(((int)AssemblyContentType.WindowsRuntime) << 9);
 0223                    }
 224                    else
 0225                    {
 0226                        return false;
 227                    }
 0228                }
 229                else
 15012230                {
 231                    // Desktop compat: If we got here, the attribute name is unknown to us. Ignore it.
 15012232                }
 233
 15012234                if (!TryGetNextToken(out _, out token))
 36235                {
 36236                    return false;
 237                }
 14976238            }
 239
 7156240            result = new AssemblyNameParts(name, version, cultureName, flags, pkt);
 7156241            return true;
 11140242        }
 243
 244        private static bool IsAttribute(string candidate, string attributeKind)
 105084245            => candidate.Equals(attributeKind, StringComparison.OrdinalIgnoreCase);
 246
 247        private static unsafe bool TryParseVersion(string attributeValue, ref Version? version)
 0248        {
 249#if NET
 0250            ReadOnlySpan<char> attributeValueSpan = attributeValue;
 0251            Span<Range> parts = stackalloc Range[5];
 0252            parts = parts.Slice(0, attributeValueSpan.Split(parts, '.'));
 253#else
 254            string[] parts = attributeValue.Split('.');
 255#endif
 0256            if (parts.Length is < 2 or > 4)
 0257            {
 0258                return false;
 259            }
 260
 0261            Span<ushort> versionNumbers = [ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue];
 0262            for (int i = 0; i < parts.Length; i++)
 0263            {
 0264                if (!ushort.TryParse(
 0265#if NET
 0266                    attributeValueSpan[parts[i]],
 0267#else
 0268                    parts[i],
 0269#endif
 0270                    NumberStyles.None, NumberFormatInfo.InvariantInfo, out versionNumbers[i]))
 0271                {
 0272                    return false;
 273                }
 0274            }
 275
 0276            if (versionNumbers[0] == ushort.MaxValue ||
 0277                versionNumbers[1] == ushort.MaxValue)
 0278            {
 0279                return false;
 280            }
 281
 0282            version =
 0283                versionNumbers[2] == ushort.MaxValue ? new Version(versionNumbers[0], versionNumbers[1]) :
 0284                versionNumbers[3] == ushort.MaxValue ? new Version(versionNumbers[0], versionNumbers[1], versionNumbers[
 0285                new Version(versionNumbers[0], versionNumbers[1], versionNumbers[2], versionNumbers[3]);
 286
 0287            return true;
 0288        }
 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)
 0303        {
 0304            if (attributeValue.Equals("null", StringComparison.OrdinalIgnoreCase) || attributeValue == string.Empty)
 0305            {
 0306                result = [];
 0307                return true;
 308            }
 309
 0310            if (attributeValue.Length % 2 != 0 || (isToken && attributeValue.Length != 8 * 2))
 0311            {
 0312                result = null;
 0313                return false;
 314            }
 315
 0316            byte[] pkt = new byte[attributeValue.Length / 2];
 0317            if (!HexConverter.TryDecodeFromUtf16(attributeValue.AsSpan(), pkt, out int _))
 0318            {
 0319                result = null;
 0320                return false;
 321            }
 322
 0323            result = pkt;
 0324            return true;
 0325        }
 326
 327        private static bool TryParseProcessorArchitecture(string attributeValue, out ProcessorArchitecture result)
 0328        {
 0329            result = attributeValue switch
 0330            {
 0331                _ when attributeValue.Equals("msil", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.MSIL,
 0332                _ when attributeValue.Equals("x86", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.X86,
 0333                _ when attributeValue.Equals("ia64", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.IA64,
 0334                _ when attributeValue.Equals("amd64", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.Amd64
 0335                _ when attributeValue.Equals("arm", StringComparison.OrdinalIgnoreCase) => ProcessorArchitecture.Arm,
 0336                _ => ProcessorArchitecture.None
 0337            };
 0338            return result != ProcessorArchitecture.None;
 0339        }
 340
 341        private static bool IsWhiteSpace(char ch)
 97260342            => ch is '\n' or '\r' or ' ' or '\t';
 343
 344        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 345        private bool TryGetNextChar(out char ch)
 873844346        {
 873844347            if (_index < _input.Length)
 858528348            {
 858528349                ch = _input[_index++];
 858528350                if (ch == '\0')
 568351                {
 568352                    return false;
 353                }
 857960354            }
 355            else
 15316356            {
 15316357                ch = '\0';
 15316358            }
 359
 873276360            return true;
 873844361        }
 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)
 86072368        {
 86072369            tokenString = string.Empty;
 370            char c;
 371
 90580372            while (true)
 90580373            {
 90580374                if (!TryGetNextChar(out c))
 244375                {
 244376                    token = default;
 244377                    return false;
 378                }
 379
 90336380                switch (c)
 381                {
 382                    case ',':
 18980383                    {
 18980384                        token = Token.Comma;
 18980385                        return true;
 386                    }
 387                    case '=':
 15264388                    {
 15264389                        token = Token.Equals;
 15264390                        return true;
 391                    }
 392                    case '\0':
 8280393                    {
 8280394                        token = Token.End;
 8280395                        return true;
 396                    }
 397                }
 398
 47812399                if (!IsWhiteSpace(c))
 43304400                {
 43304401                    break;
 402                }
 4508403            }
 404
 43304405            using ValueStringBuilder sb = new ValueStringBuilder(stackalloc char[64]);
 406
 43304407            char quoteChar = '\0';
 43304408            if (c is '\'' or '\"')
 1852409            {
 1852410                quoteChar = c;
 1852411                if (!TryGetNextChar(out c))
 16412                {
 16413                    token = default;
 16414                    return false;
 415                }
 1836416            }
 417
 418            for (; ; )
 798228419            {
 798228420                if (c == 0)
 7004421                {
 7004422                    if (quoteChar != 0)
 240423                    {
 424                        // EOS and unclosed quotes is an error
 240425                        token = default;
 240426                        return false;
 427                    }
 428                    // Reached end of input and therefore of string
 6764429                    break;
 430                }
 431
 791224432                if (quoteChar != 0 && c == quoteChar)
 1584433                    break;  // Terminate: Found closing quote of quoted string.
 434
 789640435                if (quoteChar == 0 && (c is ',' or '='))
 33876436                {
 33876437                    _index--;
 33876438                    break;  // Terminate: Found start of a new ',' or '=' token.
 439                }
 440
 755764441                if (quoteChar == 0 && (c is '\'' or '\"'))
 16442                {
 16443                    token = default;
 16444                    return false;
 445                }
 446
 755748447                if (c is '\\')
 26184448                {
 26184449                    if (!TryGetNextChar(out c))
 20450                    {
 20451                        token = default;
 20452                        return false;
 453                    }
 454
 26164455                    switch (c)
 456                    {
 457                        case '\\':
 458                        case ',':
 459                        case '=':
 460                        case '\'':
 461                        case '"':
 21264462                            sb.Append(c);
 21264463                            break;
 464                        case 't':
 1300465                            sb.Append('\t');
 1300466                            break;
 467                        case 'r':
 1252468                            sb.Append('\r');
 1252469                            break;
 470                        case 'n':
 1848471                            sb.Append('\n');
 1848472                            break;
 473                        default:
 500474                            token = default;
 500475                            return false;
 476                    }
 25664477                }
 478                else
 729564479                {
 729564480                    sb.Append(c);
 729564481                }
 482
 755228483                if (!TryGetNextChar(out c))
 288484                {
 288485                    token = default;
 288486                    return false;
 487                }
 754940488            }
 489
 490
 42224491            int length = sb.Length;
 42224492            if (quoteChar == 0)
 40640493            {
 49528494                while (length > 0 && IsWhiteSpace(sb[length - 1]))
 8888495                    length--;
 40640496            }
 497
 42224498            tokenString = sb.AsSpan(0, length).ToString();
 42224499            token = Token.String;
 42224500            return true;
 86072501        }
 502    }
 503}
 504