< Summary

Information
Class: System.Reflection.Metadata.TypeNameParser
Assembly: System.Reflection.Metadata
File(s): D:\runner\runtime\src\libraries\System.Reflection.Metadata\src\System\Reflection\Metadata\TypeNameParser.cs
Line coverage
100%
Covered lines: 135
Uncovered lines: 0
Coverable lines: 135
Total lines: 284
Line coverage: 100%
Branch coverage
96%
Covered branches: 60
Total branches: 62
Branch coverage: 96.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%11100%
.ctor(...)50%22100%
Parse(...)92.85%1414100%
ParseNextTypeName(...)100%3636100%
TryParseAssemblyName(...)100%66100%
GetDeclaringType(...)100%44100%

File(s)

D:\runner\runtime\src\libraries\System.Reflection.Metadata\src\System\Reflection\Metadata\TypeNameParser.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.Collections.Generic;
 5using System.Diagnostics;
 6
 7#if !SYSTEM_PRIVATE_CORELIB
 8using System.Collections.Immutable;
 9#endif
 10
 11using static System.Reflection.Metadata.TypeNameParserHelpers;
 12
 13namespace System.Reflection.Metadata
 14{
 15    [DebuggerDisplay("{_inputString}")]
 16    internal ref struct TypeNameParser
 17    {
 118        private static readonly TypeNameParseOptions s_defaults = new();
 19
 20        private readonly bool _throwOnError;
 21        private readonly TypeNameParseOptions _parseOptions;
 22        private ReadOnlySpan<char> _inputString;
 23
 1420024        private TypeNameParser(ReadOnlySpan<char> name, bool throwOnError, TypeNameParseOptions? options) : this()
 1420025        {
 1420026            _inputString = name;
 1420027            _throwOnError = throwOnError;
 1420028            _parseOptions = options ?? s_defaults;
 1420029        }
 30
 31        internal static TypeName? Parse(ReadOnlySpan<char> typeName, bool throwOnError, TypeNameParseOptions? options = 
 1420432        {
 1420433            ReadOnlySpan<char> trimmedName = typeName.TrimStart(); // whitespaces at beginning are always OK
 1420434            if (trimmedName.IsEmpty)
 435            {
 436                if (throwOnError)
 237                {
 238                    ThrowArgumentException_InvalidTypeName(errorIndex: 0); // whitespace input needs to report the error
 39                }
 40
 241                return null;
 42            }
 43
 1420044            int recursiveDepth = 0;
 1420045            TypeNameParser parser = new(trimmedName, throwOnError, options);
 1420046            TypeName? parsedName = parser.ParseNextTypeName(allowFullyQualifiedName: true, ref recursiveDepth);
 47
 1420048            if (parsedName is null || !parser._inputString.IsEmpty) // unconsumed input == error
 730449            {
 730450                if (throwOnError)
 365251                {
 365252                    if (IsMaxDepthExceeded(parser._parseOptions, recursiveDepth))
 15653                    {
 15654                        ThrowInvalidOperation_MaxNodesExceeded(parser._parseOptions.MaxNodes);
 55                    }
 56
 349657                    int errorIndex = typeName.Length - parser._inputString.Length;
 349658                    ThrowArgumentException_InvalidTypeName(errorIndex);
 59                }
 60
 365261                return null;
 62            }
 63
 689664            Debug.Assert(parsedName.GetNodeCount() == recursiveDepth, $"Node count mismatch for '{typeName.ToString()}'"
 65
 689666            return parsedName;
 1055067        }
 68
 69        // this method should return null instead of throwing, so the caller can get errorIndex and include it in error 
 70        private TypeName? ParseNextTypeName(bool allowFullyQualifiedName, ref int recursiveDepth)
 4222471        {
 4222472            if (!TryDive(_parseOptions, ref recursiveDepth))
 9273            {
 9274                return null;
 75            }
 76
 4213277            List<int>? nestedNameLengths = null;
 4213278            if (!TryGetTypeNameInfo(_parseOptions, ref _inputString, ref nestedNameLengths, ref recursiveDepth, out int 
 51279            {
 51280                return null;
 81            }
 82
 83            // At this point, we have performed O(fullTypeNameLength) total work.
 84
 4162085            ReadOnlySpan<char> fullTypeName = _inputString.Slice(0, fullTypeNameLength);
 4162086            _inputString = _inputString.Slice(fullTypeNameLength);
 87
 88            // Don't allocate now, as it may be an open generic type like "Name`1"
 89#if SYSTEM_PRIVATE_CORELIB
 90            List<TypeName>? genericArgs = null;
 91#else
 4162092            ImmutableArray<TypeName>.Builder? genericArgs = null;
 93#endif
 94
 95            // Are there any captured generic args? We'll look for "[[" and "[".
 96            // There are no spaces allowed before the first '[', but spaces are allowed
 97            // after that. The check slices _inputString, so we'll capture it into
 98            // a local so we can restore it later if needed.
 4162099            ReadOnlySpan<char> capturedBeforeProcessing = _inputString;
 41620100            if (IsBeginningOfGenericArgs(ref _inputString, out bool doubleBrackets))
 14484101            {
 28024102            ParseAnotherGenericArg:
 103
 104                // Namespace.Type`2[[GenericArgument1, AssemblyName1],[GenericArgument2, AssemblyName2]] - double square
 105                // Namespace.Type`2[GenericArgument1,GenericArgument2] - single square bracket syntax is legal only for 
 106                // Namespace.Type`2[[GenericArgument1, AssemblyName1], GenericArgument2] - mixed mode
 107                // Namespace.Type`2[GenericArgument1, [GenericArgument2, AssemblyName2]] - mixed mode
 28024108                TypeName? genericArg = ParseNextTypeName(allowFullyQualifiedName: doubleBrackets, ref recursiveDepth);
 28024109                if (genericArg is null) // parsing failed
 3508110                {
 3508111                    return null;
 112                }
 113
 114                // For [[, there had better be a ']' after the type name.
 24516115                if (doubleBrackets && !TryStripFirstCharAndTrailingSpaces(ref _inputString, ']'))
 148116                {
 148117                    return null;
 118                }
 119
 24368120                if (genericArgs is null)
 11936121                {
 122#if SYSTEM_PRIVATE_CORELIB
 123                    genericArgs = new List<TypeName>(2);
 124#else
 11936125                    genericArgs = ImmutableArray.CreateBuilder<TypeName>(2);
 126#endif
 11936127                }
 24368128                genericArgs.Add(genericArg);
 129
 130                // Is there a ',[' indicating fully qualified generic type arg?
 131                // Is there a ',' indicating non-fully qualified generic type arg?
 24368132                if (TryStripFirstCharAndTrailingSpaces(ref _inputString, ','))
 13540133                {
 13540134                    doubleBrackets = TryStripFirstCharAndTrailingSpaces(ref _inputString, '[');
 135
 13540136                    goto ParseAnotherGenericArg;
 137                }
 138
 139                // The only other allowable character is ']', indicating the end of
 140                // the generic type arg list.
 10828141                if (!TryStripFirstCharAndTrailingSpaces(ref _inputString, ']'))
 1252142                {
 1252143                    return null;
 144                }
 9576145            }
 146
 147            // At this point, we may have performed O(fullTypeNameLength + _inputString.Length) total work.
 148            // This will be the case if there was whitespace after the full type name in the original input
 149            // string. We could end up looking at these same whitespace chars again later in this method,
 150            // such as when parsing decorators. We rely on the TryDive routine to limit the total number
 151            // of times we might inspect the same character.
 152
 153            // If there was an error stripping the generic args, back up to
 154            // before we started processing them, and let the decorator
 155            // parser try handling it.
 36712156            if (genericArgs is null)
 27136157            {
 27136158                _inputString = capturedBeforeProcessing;
 27136159            }
 160            else
 9576161            {
 162                // Every constructed generic type needs the generic type definition.
 9576163                if (!TryDive(_parseOptions, ref recursiveDepth))
 144164                {
 144165                    return null;
 166                }
 167                // If that generic type is a nested type, we don't increase the recursiveDepth any further,
 168                // as generic type definition uses exactly the same declaring type as the constructed generic type.
 9432169            }
 170
 36568171            int previousDecorator = default;
 172            // capture the current state so we can reprocess it again once we know the AssemblyName
 36568173            capturedBeforeProcessing = _inputString;
 174            // iterate over the decorators to ensure there are no illegal combinations
 50004175            while (TryParseNextDecorator(ref _inputString, out int parsedDecorator))
 13464176            {
 13464177                if (!TryDive(_parseOptions, ref recursiveDepth))
 28178                {
 28179                    return null;
 180                }
 181
 182                // Currently it's illegal for managed reference to be followed by any other decorator,
 183                // but this is a runtime-specific behavior and the parser is not enforcing that rule.
 13436184                previousDecorator = parsedDecorator;
 13436185            }
 186
 36540187            AssemblyNameInfo? assemblyName = null;
 36540188            if (allowFullyQualifiedName && !TryParseAssemblyName(ref assemblyName))
 4404189            {
 190#if SYSTEM_PRIVATE_CORELIB
 191                // Backward compatibility: throw for non-empty invalid assembly names.
 192                if (!_inputString.TrimStart().StartsWith(","))
 193                {
 194                    // Reject attempt to provide top-level assembly name to Assembly.GetType
 195                    if (_parseOptions.IsAssemblyGetType)
 196                    {
 197                        if (_throwOnError)
 198                        {
 199                            throw new ArgumentException(SR.Argument_AssemblyGetTypeCannotSpecifyAssembly);
 200                        }
 201                        return null;
 202                    }
 203
 204                    // Otherwise, no matter what throwOnError is set to, we throw FileLoadException for invalid assembly
 205                    throw new IO.FileLoadException(SR.InvalidAssemblyName, _inputString.ToString());
 206                }
 207#endif
 4404208                return null;
 209            }
 210
 211            // No matter what was parsed, the full name string is allocated only once.
 212            // In case of generic, nested, array, pointer and byref types the full name is allocated
 213            // when needed for the first time .
 32136214            string fullName = fullTypeName.ToString();
 215
 32136216            TypeName? declaringType = GetDeclaringType(fullName, nestedNameLengths, assemblyName);
 32136217            TypeName result = new(fullName, assemblyName, declaringType: declaringType);
 32136218            if (genericArgs is not null)
 9368219            {
 9368220                result = new(fullName: null, assemblyName, elementOrGenericType: result, declaringType, genericArgs);
 9368221            }
 222
 223            // The loop below is protected by the dive check during the first decorator pass prior
 224            // to assembly name parsing above.
 225
 32136226            if (previousDecorator != default) // some decorators were recognized
 5160227            {
 17412228                while (TryParseNextDecorator(ref capturedBeforeProcessing, out int parsedModifier))
 12252229                {
 12252230                    result = new(fullName: null, assemblyName, elementOrGenericType: result, rankOrModifier: parsedModif
 12252231                }
 5160232            }
 233
 32136234            return result;
 42224235        }
 236
 237        /// <returns>false means the input was invalid and parsing has failed. Empty input is valid and returns true.</r
 238        private bool TryParseAssemblyName(ref AssemblyNameInfo? assemblyName)
 17920239        {
 17920240            ReadOnlySpan<char> capturedBeforeProcessing = _inputString;
 17920241            if (TryStripFirstCharAndTrailingSpaces(ref _inputString, ','))
 11064242            {
 11064243                if (_inputString.IsEmpty)
 92244                {
 92245                    _inputString = capturedBeforeProcessing; // restore the state
 92246                    return false;
 247                }
 248
 10972249                ReadOnlySpan<char> candidate = GetAssemblyNameCandidate(_inputString);
 10972250                if (!AssemblyNameInfo.TryParse(candidate, out assemblyName))
 4312251                {
 4312252                    return false;
 253                }
 254
 6660255                _inputString = _inputString.Slice(candidate.Length);
 6660256                return true;
 257            }
 258
 6856259            return true;
 17920260        }
 261
 262        private static TypeName? GetDeclaringType(string fullTypeName, List<int>? nestedNameLengths, AssemblyNameInfo? a
 32136263        {
 32136264            if (nestedNameLengths is null)
 30892265            {
 30892266                return null;
 267            }
 268
 269            // The loop below is protected by the dive check in GetFullTypeNameLength.
 270
 1244271            TypeName? declaringType = null;
 1244272            int nameOffset = 0;
 9252273            foreach (int nestedNameLength in nestedNameLengths)
 2760274            {
 2760275                Debug.Assert(nestedNameLength > 0, "TryGetTypeNameInfo should return error on zero lengths");
 2760276                int fullNameLength = nameOffset + nestedNameLength;
 2760277                declaringType = new(fullTypeName, assemblyName, declaringType: declaringType, nestedNameLength: fullName
 2760278                nameOffset += nestedNameLength + 1; // include the '+' that was skipped in name
 2760279            }
 280
 1244281            return declaringType;
 32136282        }
 283    }
 284}