< Summary

Line coverage
100%
Covered lines: 135
Uncovered lines: 0
Coverable lines: 135
Total lines: 285
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)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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
 1544024        private TypeNameParser(ReadOnlySpan<char> name, bool throwOnError, TypeNameParseOptions? options) : this()
 1544025        {
 1544026            _inputString = name;
 1544027            _throwOnError = throwOnError;
 1544028            _parseOptions = options ?? s_defaults;
 1544029        }
 30
 31        internal static TypeName? Parse(ReadOnlySpan<char> typeName, bool throwOnError, TypeNameParseOptions? options = 
 1544432        {
 1544433            ReadOnlySpan<char> trimmedName = typeName.TrimStart(); // whitespaces at beginning are always OK
 1544434            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
 1544044            int recursiveDepth = 0;
 1544045            TypeNameParser parser = new(trimmedName, throwOnError, options);
 1544046            TypeName? parsedName = parser.ParseNextTypeName(allowFullyQualifiedName: true, ref recursiveDepth);
 47
 1544048            if (parsedName is null || !parser._inputString.IsEmpty) // unconsumed input == error
 809649            {
 809650                if (throwOnError)
 404851                {
 404852                    if (IsMaxDepthExceeded(parser._parseOptions, recursiveDepth))
 25453                    {
 25454                        ThrowInvalidOperation_MaxNodesExceeded(parser._parseOptions.MaxNodes);
 55                    }
 56
 379457                    int errorIndex = typeName.Length - parser._inputString.Length;
 379458                    ThrowArgumentException_InvalidTypeName(errorIndex);
 59                }
 60
 404861                return null;
 62            }
 63
 734464            Debug.Assert(parsedName.GetNodeCount() == recursiveDepth, $"Node count mismatch for '{typeName.ToString()}'"
 65
 734466            return parsedName;
 1139467        }
 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)
 5060071        {
 5060072            if (!TryDive(_parseOptions, ref recursiveDepth))
 23673            {
 23674                return null;
 75            }
 76
 5036477            List<int>? nestedNameLengths = null;
 5036478            if (!TryGetTypeNameInfo(_parseOptions, ref _inputString, ref nestedNameLengths, ref recursiveDepth, out int 
 66079            {
 66080                return null;
 81            }
 82
 83            // At this point, we have performed O(fullTypeNameLength) total work.
 84
 4970485            ReadOnlySpan<char> fullTypeName = _inputString.Slice(0, fullTypeNameLength);
 4970486            _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
 4970492            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.
 4970499            ReadOnlySpan<char> capturedBeforeProcessing = _inputString;
 49704100            if (IsBeginningOfGenericArgs(ref _inputString, out bool doubleBrackets))
 16708101            {
 35160102            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
 35160108                TypeName? genericArg = ParseNextTypeName(allowFullyQualifiedName: doubleBrackets, ref recursiveDepth);
 35160109                if (genericArg is null) // parsing failed
 5492110                {
 5492111                    return null;
 112                }
 113
 114                // For [[, there had better be a ']' after the type name.
 29668115                if (doubleBrackets && !TryStripFirstCharAndTrailingSpaces(ref _inputString, ']'))
 344116                {
 344117                    return null;
 118                }
 119
 29324120                if (genericArgs is null)
 14680121                {
 122#if SYSTEM_PRIVATE_CORELIB
 123                    genericArgs = new List<TypeName>(2);
 124#else
 14680125                    genericArgs = ImmutableArray.CreateBuilder<TypeName>(2);
 126#endif
 14680127                }
 29324128                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?
 29324132                if (TryStripFirstCharAndTrailingSpaces(ref _inputString, ','))
 18452133                {
 18452134                    doubleBrackets = TryStripFirstCharAndTrailingSpaces(ref _inputString, '[');
 135
 18452136                    goto ParseAnotherGenericArg;
 137                }
 138
 139                // The only other allowable character is ']', indicating the end of
 140                // the generic type arg list.
 10872141                if (!TryStripFirstCharAndTrailingSpaces(ref _inputString, ']'))
 1860142                {
 1860143                    return null;
 144                }
 9012145            }
 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.
 42008156            if (genericArgs is null)
 32996157            {
 32996158                _inputString = capturedBeforeProcessing;
 32996159            }
 160            else
 9012161            {
 162                // Every constructed generic type needs the generic type definition.
 9012163                if (!TryDive(_parseOptions, ref recursiveDepth))
 32164                {
 32165                    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.
 8980169            }
 170
 41976171            int previousDecorator = default;
 172            // capture the current state so we can reprocess it again once we know the AssemblyName
 41976173            capturedBeforeProcessing = _inputString;
 174            // iterate over the decorators to ensure there are no illegal combinations
 58224175            while (TryParseNextDecorator(ref _inputString, out int parsedDecorator))
 16392176            {
 16392177                if (!TryDive(_parseOptions, ref recursiveDepth))
 144178                {
 144179                    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.
 16248184                previousDecorator = parsedDecorator;
 16248185            }
 186
 41832187            AssemblyNameInfo? assemblyName = null;
 41832188            if (allowFullyQualifiedName && !TryParseAssemblyName(ref assemblyName))
 4096189            {
 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
 4096208                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 .
 37736214            string fullName = fullTypeName.ToString();
 215
 37736216            TypeName? declaringType = GetDeclaringType(fullName, nestedNameLengths, assemblyName);
 37736217            TypeName result = new(fullName, assemblyName, declaringType: declaringType);
 37736218            if (genericArgs is not null)
 8936219            {
 8936220                result = new(fullName: null, assemblyName, elementOrGenericType: result, declaringType, genericArgs);
 8936221            }
 222
 223            // The loop below is protected by the dive check during the first decorator pass prior
 224            // to assembly name parsing above.
 225
 37736226            if (previousDecorator != default) // some decorators were recognized
 7360227            {
 21356228                while (TryParseNextDecorator(ref capturedBeforeProcessing, out int parsedModifier))
 13996229                {
 13996230                    result = new(fullName: null, assemblyName, elementOrGenericType: result, rankOrModifier: parsedModif
 13996231                }
 7360232            }
 233
 37736234            return result;
 50600235        }
 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)
 16452239        {
 16452240            ReadOnlySpan<char> capturedBeforeProcessing = _inputString;
 16452241            if (TryStripFirstCharAndTrailingSpaces(ref _inputString, ','))
 11252242            {
 11252243                if (_inputString.IsEmpty)
 88244                {
 88245                    _inputString = capturedBeforeProcessing; // restore the state
 88246                    return false;
 247                }
 248
 11164249                ReadOnlySpan<char> candidate = GetAssemblyNameCandidate(_inputString);
 11164250                if (!AssemblyNameInfo.TryParse(candidate, out assemblyName))
 4008251                {
 4008252                    return false;
 253                }
 254
 7156255                _inputString = _inputString.Slice(candidate.Length);
 7156256                return true;
 257            }
 258
 5200259            return true;
 16452260        }
 261
 262        private static TypeName? GetDeclaringType(string fullTypeName, List<int>? nestedNameLengths, AssemblyNameInfo? a
 37736263        {
 37736264            if (nestedNameLengths is null)
 35436265            {
 35436266                return null;
 267            }
 268
 269            // The loop below is protected by the dive check in GetFullTypeNameLength.
 270
 2300271            TypeName? declaringType = null;
 2300272            int nameOffset = 0;
 13828273            foreach (int nestedNameLength in nestedNameLengths)
 3464274            {
 3464275                Debug.Assert(nestedNameLength > 0, "TryGetTypeNameInfo should return error on zero lengths");
 3464276                int fullNameLength = nameOffset + nestedNameLength;
 3464277                declaringType = new(fullTypeName, assemblyName, declaringType: declaringType, nestedNameLength: fullName
 3464278                nameOffset += nestedNameLength + 1; // include the '+' that was skipped in name
 3464279            }
 280
 2300281            return declaringType;
 37736282        }
 283    }
 284}
 285