< 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
 1452024        private TypeNameParser(ReadOnlySpan<char> name, bool throwOnError, TypeNameParseOptions? options) : this()
 1452025        {
 1452026            _inputString = name;
 1452027            _throwOnError = throwOnError;
 1452028            _parseOptions = options ?? s_defaults;
 1452029        }
 30
 31        internal static TypeName? Parse(ReadOnlySpan<char> typeName, bool throwOnError, TypeNameParseOptions? options = 
 1452432        {
 1452433            ReadOnlySpan<char> trimmedName = typeName.TrimStart(); // whitespaces at beginning are always OK
 1452434            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
 1452044            int recursiveDepth = 0;
 1452045            TypeNameParser parser = new(trimmedName, throwOnError, options);
 1452046            TypeName? parsedName = parser.ParseNextTypeName(allowFullyQualifiedName: true, ref recursiveDepth);
 47
 1452048            if (parsedName is null || !parser._inputString.IsEmpty) // unconsumed input == error
 749249            {
 749250                if (throwOnError)
 374651                {
 374652                    if (IsMaxDepthExceeded(parser._parseOptions, recursiveDepth))
 16253                    {
 16254                        ThrowInvalidOperation_MaxNodesExceeded(parser._parseOptions.MaxNodes);
 55                    }
 56
 358457                    int errorIndex = typeName.Length - parser._inputString.Length;
 358458                    ThrowArgumentException_InvalidTypeName(errorIndex);
 59                }
 60
 374661                return null;
 62            }
 63
 702864            Debug.Assert(parsedName.GetNodeCount() == recursiveDepth, $"Node count mismatch for '{typeName.ToString()}'"
 65
 702866            return parsedName;
 1077667        }
 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)
 4360871        {
 4360872            if (!TryDive(_parseOptions, ref recursiveDepth))
 9273            {
 9274                return null;
 75            }
 76
 4351677            List<int>? nestedNameLengths = null;
 4351678            if (!TryGetTypeNameInfo(_parseOptions, ref _inputString, ref nestedNameLengths, ref recursiveDepth, out int 
 53679            {
 53680                return null;
 81            }
 82
 83            // At this point, we have performed O(fullTypeNameLength) total work.
 84
 4298085            ReadOnlySpan<char> fullTypeName = _inputString.Slice(0, fullTypeNameLength);
 4298086            _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
 4298092            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.
 4298099            ReadOnlySpan<char> capturedBeforeProcessing = _inputString;
 42980100            if (IsBeginningOfGenericArgs(ref _inputString, out bool doubleBrackets))
 14868101            {
 29088102            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
 29088108                TypeName? genericArg = ParseNextTypeName(allowFullyQualifiedName: doubleBrackets, ref recursiveDepth);
 29088109                if (genericArg is null) // parsing failed
 3632110                {
 3632111                    return null;
 112                }
 113
 114                // For [[, there had better be a ']' after the type name.
 25456115                if (doubleBrackets && !TryStripFirstCharAndTrailingSpaces(ref _inputString, ']'))
 156116                {
 156117                    return null;
 118                }
 119
 25300120                if (genericArgs is null)
 12248121                {
 122#if SYSTEM_PRIVATE_CORELIB
 123                    genericArgs = new List<TypeName>(2);
 124#else
 12248125                    genericArgs = ImmutableArray.CreateBuilder<TypeName>(2);
 126#endif
 12248127                }
 25300128                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?
 25300132                if (TryStripFirstCharAndTrailingSpaces(ref _inputString, ','))
 14220133                {
 14220134                    doubleBrackets = TryStripFirstCharAndTrailingSpaces(ref _inputString, '[');
 135
 14220136                    goto ParseAnotherGenericArg;
 137                }
 138
 139                // The only other allowable character is ']', indicating the end of
 140                // the generic type arg list.
 11080141                if (!TryStripFirstCharAndTrailingSpaces(ref _inputString, ']'))
 1292142                {
 1292143                    return null;
 144                }
 9788145            }
 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.
 37900156            if (genericArgs is null)
 28112157            {
 28112158                _inputString = capturedBeforeProcessing;
 28112159            }
 160            else
 9788161            {
 162                // Every constructed generic type needs the generic type definition.
 9788163                if (!TryDive(_parseOptions, ref recursiveDepth))
 152164                {
 152165                    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.
 9636169            }
 170
 37748171            int previousDecorator = default;
 172            // capture the current state so we can reprocess it again once we know the AssemblyName
 37748173            capturedBeforeProcessing = _inputString;
 174            // iterate over the decorators to ensure there are no illegal combinations
 51356175            while (TryParseNextDecorator(ref _inputString, out int parsedDecorator))
 13636176            {
 13636177                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.
 13608184                previousDecorator = parsedDecorator;
 13608185            }
 186
 37720187            AssemblyNameInfo? assemblyName = null;
 37720188            if (allowFullyQualifiedName && !TryParseAssemblyName(ref assemblyName))
 4504189            {
 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
 4504208                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 .
 33216214            string fullName = fullTypeName.ToString();
 215
 33216216            TypeName? declaringType = GetDeclaringType(fullName, nestedNameLengths, assemblyName);
 33216217            TypeName result = new(fullName, assemblyName, declaringType: declaringType);
 33216218            if (genericArgs is not null)
 9572219            {
 9572220                result = new(fullName: null, assemblyName, elementOrGenericType: result, declaringType, genericArgs);
 9572221            }
 222
 223            // The loop below is protected by the dive check during the first decorator pass prior
 224            // to assembly name parsing above.
 225
 33216226            if (previousDecorator != default) // some decorators were recognized
 5300227            {
 17692228                while (TryParseNextDecorator(ref capturedBeforeProcessing, out int parsedModifier))
 12392229                {
 12392230                    result = new(fullName: null, assemblyName, elementOrGenericType: result, rankOrModifier: parsedModif
 12392231                }
 5300232            }
 233
 33216234            return result;
 43608235        }
 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)
 18232239        {
 18232240            ReadOnlySpan<char> capturedBeforeProcessing = _inputString;
 18232241            if (TryStripFirstCharAndTrailingSpaces(ref _inputString, ','))
 11272242            {
 11272243                if (_inputString.IsEmpty)
 92244                {
 92245                    _inputString = capturedBeforeProcessing; // restore the state
 92246                    return false;
 247                }
 248
 11180249                ReadOnlySpan<char> candidate = GetAssemblyNameCandidate(_inputString);
 11180250                if (!AssemblyNameInfo.TryParse(candidate, out assemblyName))
 4412251                {
 4412252                    return false;
 253                }
 254
 6768255                _inputString = _inputString.Slice(candidate.Length);
 6768256                return true;
 257            }
 258
 6960259            return true;
 18232260        }
 261
 262        private static TypeName? GetDeclaringType(string fullTypeName, List<int>? nestedNameLengths, AssemblyNameInfo? a
 33216263        {
 33216264            if (nestedNameLengths is null)
 31884265            {
 31884266                return null;
 267            }
 268
 269            // The loop below is protected by the dive check in GetFullTypeNameLength.
 270
 1332271            TypeName? declaringType = null;
 1332272            int nameOffset = 0;
 9764273            foreach (int nestedNameLength in nestedNameLengths)
 2884274            {
 2884275                Debug.Assert(nestedNameLength > 0, "TryGetTypeNameInfo should return error on zero lengths");
 2884276                int fullNameLength = nameOffset + nestedNameLength;
 2884277                declaringType = new(fullTypeName, assemblyName, declaringType: declaringType, nestedNameLength: fullName
 2884278                nameOffset += nestedNameLength + 1; // include the '+' that was skipped in name
 2884279            }
 280
 1332281            return declaringType;
 33216282        }
 283    }
 284}