< 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
52%
Covered lines: 155
Uncovered lines: 141
Coverable lines: 296
Total lines: 503
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)

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[]
 676821            {
 676822                _name = name;
 676823                _version = version;
 676824                _cultureName = cultureName;
 676825                _flags = flags;
 676826                _publicKeyOrToken = publicKeyOrToken;
 676827            }
 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)
 1117261        {
 62#if SYSTEM_PRIVATE_CORELIB
 63            if (input.Length == 0)
 64                throw new ArgumentException(SR.Format_StringZeroLength);
 65#else
 1117266            Debug.Assert(input.Length > 0);
 67#endif
 68
 1117269            _input = input;
 1117270            _index = 0;
 1117271        }
 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)
 1117289        {
 1117290            AssemblyNameParser parser = new(name);
 1117291            return parser.TryParse(ref parts);
 1117292        }
 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)
 11172105        {
 106            // Name must come first.
 11172107            if (!TryGetNextToken(out string name, out Token token) || token != Token.String || string.IsNullOrEmpty(name
 1636108                return false;
 109
 9536110            Version? version = null;
 9536111            string? cultureName = null;
 9536112            byte[]? pkt = null;
 9536113            AssemblyNameFlags flags = 0;
 114
 9536115            AttributeKind alreadySeen = default;
 9536116            if (!TryGetNextToken(out _, out token))
 16117                return false;
 118
 16252119            while (token != Token.End)
 9484120            {
 9484121                if (token != Token.Comma)
 364122                    return false;
 123
 9120124                if (!TryGetNextToken(out string attributeName, out token) || token != Token.String)
 1108125                    return false;
 126
 8012127                if (!TryGetNextToken(out _, out token) || token != Token.Equals)
 820128                    return false;
 129
 7192130                if (!TryGetNextToken(out string attributeValue, out token) || token != Token.String)
 136131                    return false;
 132
 7056133                if (attributeName == string.Empty)
 168134                    return false;
 135
 6888136                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                }
 6888147                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                }
 6888158                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                }
 6888169                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                }
 6888181                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                }
 6888193                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                }
 6888213                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
 6888230                {
 231                    // Desktop compat: If we got here, the attribute name is unknown to us. Ignore it.
 6888232                }
 233
 6888234                if (!TryGetNextToken(out _, out token))
 156235                {
 156236                    return false;
 237                }
 6732238            }
 239
 6768240            result = new AssemblyNameParts(name, version, cultureName, flags, pkt);
 6768241            return true;
 11172242        }
 243
 244        private static bool IsAttribute(string candidate, string attributeKind)
 48216245            => 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)
 60688342            => ch is '\n' or '\r' or ' ' or '\t';
 343
 344        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 345        private bool TryGetNextChar(out char ch)
 345948346        {
 345948347            if (_index < _input.Length)
 330680348            {
 330680349                ch = _input[_index++];
 330680350                if (ch == '\0')
 316351                {
 316352                    return false;
 353                }
 330364354            }
 355            else
 15268356            {
 15268357                ch = '\0';
 15268358            }
 359
 345632360            return true;
 345948361        }
 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)
 51920368        {
 51920369            tokenString = string.Empty;
 370            char c;
 371
 55740372            while (true)
 55740373            {
 55740374                if (!TryGetNextChar(out c))
 88375                {
 88376                    token = default;
 88377                    return false;
 378                }
 379
 55652380                switch (c)
 381                {
 382                    case ',':
 9360383                    {
 9360384                        token = Token.Comma;
 9360385                        return true;
 386                    }
 387                    case '=':
 7552388                    {
 7552389                        token = Token.Equals;
 7552390                        return true;
 391                    }
 392                    case '\0':
 7596393                    {
 7596394                        token = Token.End;
 7596395                        return true;
 396                    }
 397                }
 398
 31144399                if (!IsWhiteSpace(c))
 27324400                {
 27324401                    break;
 402                }
 3820403            }
 404
 27324405            using ValueStringBuilder sb = new ValueStringBuilder(stackalloc char[64]);
 406
 27324407            char quoteChar = '\0';
 27324408            if (c is '\'' or '\"')
 856409            {
 856410                quoteChar = c;
 856411                if (!TryGetNextChar(out c))
 4412                {
 4413                    token = default;
 4414                    return false;
 415                }
 852416            }
 417
 418            for (; ; )
 268256419            {
 268256420                if (c == 0)
 7420421                {
 7420422                    if (quoteChar != 0)
 136423                    {
 424                        // EOS and unclosed quotes is an error
 136425                        token = default;
 136426                        return false;
 427                    }
 428                    // Reached end of input and therefore of string
 7284429                    break;
 430                }
 431
 260836432                if (quoteChar != 0 && c == quoteChar)
 716433                    break;  // Terminate: Found closing quote of quoted string.
 434
 260120435                if (quoteChar == 0 && (c is ',' or '='))
 16744436                {
 16744437                    _index--;
 16744438                    break;  // Terminate: Found start of a new ',' or '=' token.
 439                }
 440
 243376441                if (quoteChar == 0 && (c is '\'' or '\"'))
 204442                {
 204443                    token = default;
 204444                    return false;
 445                }
 446
 243172447                if (c is '\\')
 48220448                {
 48220449                    if (!TryGetNextChar(out c))
 28450                    {
 28451                        token = default;
 28452                        return false;
 453                    }
 454
 48192455                    switch (c)
 456                    {
 457                        case '\\':
 458                        case ',':
 459                        case '=':
 460                        case '\'':
 461                        case '"':
 37528462                            sb.Append(c);
 37528463                            break;
 464                        case 't':
 1320465                            sb.Append('\t');
 1320466                            break;
 467                        case 'r':
 4756468                            sb.Append('\r');
 4756469                            break;
 470                        case 'n':
 2576471                            sb.Append('\n');
 2576472                            break;
 473                        default:
 2012474                            token = default;
 2012475                            return false;
 476                    }
 46180477                }
 478                else
 194952479                {
 194952480                    sb.Append(c);
 194952481                }
 482
 241132483                if (!TryGetNextChar(out c))
 196484                {
 196485                    token = default;
 196486                    return false;
 487                }
 240936488            }
 489
 490
 24744491            int length = sb.Length;
 24744492            if (quoteChar == 0)
 24028493            {
 29632494                while (length > 0 && IsWhiteSpace(sb[length - 1]))
 5604495                    length--;
 24028496            }
 497
 24744498            tokenString = sb.AsSpan(0, length).ToString();
 24744499            token = Token.String;
 24744500            return true;
 51920501        }
 502    }
 503}