< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 135
Coverable lines: 135
Total lines: 285
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 62
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
.ctor(...)0%220%
Parse(...)0%14140%
ParseNextTypeName(...)0%36360%
TryParseAssemblyName(...)0%660%
GetDeclaringType(...)0%440%

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    {
 018        private static readonly TypeNameParseOptions s_defaults = new();
 19
 20        private readonly bool _throwOnError;
 21        private readonly TypeNameParseOptions _parseOptions;
 22        private ReadOnlySpan<char> _inputString;
 23
 024        private TypeNameParser(ReadOnlySpan<char> name, bool throwOnError, TypeNameParseOptions? options) : this()
 025        {
 026            _inputString = name;
 027            _throwOnError = throwOnError;
 028            _parseOptions = options ?? s_defaults;
 029        }
 30
 31        internal static TypeName? Parse(ReadOnlySpan<char> typeName, bool throwOnError, TypeNameParseOptions? options = 
 032        {
 033            ReadOnlySpan<char> trimmedName = typeName.TrimStart(); // whitespaces at beginning are always OK
 034            if (trimmedName.IsEmpty)
 035            {
 036                if (throwOnError)
 037                {
 038                    ThrowArgumentException_InvalidTypeName(errorIndex: 0); // whitespace input needs to report the error
 39                }
 40
 041                return null;
 42            }
 43
 044            int recursiveDepth = 0;
 045            TypeNameParser parser = new(trimmedName, throwOnError, options);
 046            TypeName? parsedName = parser.ParseNextTypeName(allowFullyQualifiedName: true, ref recursiveDepth);
 47
 048            if (parsedName is null || !parser._inputString.IsEmpty) // unconsumed input == error
 049            {
 050                if (throwOnError)
 051                {
 052                    if (IsMaxDepthExceeded(parser._parseOptions, recursiveDepth))
 053                    {
 054                        ThrowInvalidOperation_MaxNodesExceeded(parser._parseOptions.MaxNodes);
 55                    }
 56
 057                    int errorIndex = typeName.Length - parser._inputString.Length;
 058                    ThrowArgumentException_InvalidTypeName(errorIndex);
 59                }
 60
 061                return null;
 62            }
 63
 064            Debug.Assert(parsedName.GetNodeCount() == recursiveDepth, $"Node count mismatch for '{typeName.ToString()}'"
 65
 066            return parsedName;
 067        }
 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)
 071        {
 072            if (!TryDive(_parseOptions, ref recursiveDepth))
 073            {
 074                return null;
 75            }
 76
 077            List<int>? nestedNameLengths = null;
 078            if (!TryGetTypeNameInfo(_parseOptions, ref _inputString, ref nestedNameLengths, ref recursiveDepth, out int 
 079            {
 080                return null;
 81            }
 82
 83            // At this point, we have performed O(fullTypeNameLength) total work.
 84
 085            ReadOnlySpan<char> fullTypeName = _inputString.Slice(0, fullTypeNameLength);
 086            _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
 092            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.
 099            ReadOnlySpan<char> capturedBeforeProcessing = _inputString;
 0100            if (IsBeginningOfGenericArgs(ref _inputString, out bool doubleBrackets))
 0101            {
 0102            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
 0108                TypeName? genericArg = ParseNextTypeName(allowFullyQualifiedName: doubleBrackets, ref recursiveDepth);
 0109                if (genericArg is null) // parsing failed
 0110                {
 0111                    return null;
 112                }
 113
 114                // For [[, there had better be a ']' after the type name.
 0115                if (doubleBrackets && !TryStripFirstCharAndTrailingSpaces(ref _inputString, ']'))
 0116                {
 0117                    return null;
 118                }
 119
 0120                if (genericArgs is null)
 0121                {
 122#if SYSTEM_PRIVATE_CORELIB
 123                    genericArgs = new List<TypeName>(2);
 124#else
 0125                    genericArgs = ImmutableArray.CreateBuilder<TypeName>(2);
 126#endif
 0127                }
 0128                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?
 0132                if (TryStripFirstCharAndTrailingSpaces(ref _inputString, ','))
 0133                {
 0134                    doubleBrackets = TryStripFirstCharAndTrailingSpaces(ref _inputString, '[');
 135
 0136                    goto ParseAnotherGenericArg;
 137                }
 138
 139                // The only other allowable character is ']', indicating the end of
 140                // the generic type arg list.
 0141                if (!TryStripFirstCharAndTrailingSpaces(ref _inputString, ']'))
 0142                {
 0143                    return null;
 144                }
 0145            }
 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.
 0156            if (genericArgs is null)
 0157            {
 0158                _inputString = capturedBeforeProcessing;
 0159            }
 160            else
 0161            {
 162                // Every constructed generic type needs the generic type definition.
 0163                if (!TryDive(_parseOptions, ref recursiveDepth))
 0164                {
 0165                    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.
 0169            }
 170
 0171            int previousDecorator = default;
 172            // capture the current state so we can reprocess it again once we know the AssemblyName
 0173            capturedBeforeProcessing = _inputString;
 174            // iterate over the decorators to ensure there are no illegal combinations
 0175            while (TryParseNextDecorator(ref _inputString, out int parsedDecorator))
 0176            {
 0177                if (!TryDive(_parseOptions, ref recursiveDepth))
 0178                {
 0179                    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.
 0184                previousDecorator = parsedDecorator;
 0185            }
 186
 0187            AssemblyNameInfo? assemblyName = null;
 0188            if (allowFullyQualifiedName && !TryParseAssemblyName(ref assemblyName))
 0189            {
 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
 0208                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 .
 0214            string fullName = fullTypeName.ToString();
 215
 0216            TypeName? declaringType = GetDeclaringType(fullName, nestedNameLengths, assemblyName);
 0217            TypeName result = new(fullName, assemblyName, declaringType: declaringType);
 0218            if (genericArgs is not null)
 0219            {
 0220                result = new(fullName: null, assemblyName, elementOrGenericType: result, declaringType, genericArgs);
 0221            }
 222
 223            // The loop below is protected by the dive check during the first decorator pass prior
 224            // to assembly name parsing above.
 225
 0226            if (previousDecorator != default) // some decorators were recognized
 0227            {
 0228                while (TryParseNextDecorator(ref capturedBeforeProcessing, out int parsedModifier))
 0229                {
 0230                    result = new(fullName: null, assemblyName, elementOrGenericType: result, rankOrModifier: parsedModif
 0231                }
 0232            }
 233
 0234            return result;
 0235        }
 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)
 0239        {
 0240            ReadOnlySpan<char> capturedBeforeProcessing = _inputString;
 0241            if (TryStripFirstCharAndTrailingSpaces(ref _inputString, ','))
 0242            {
 0243                if (_inputString.IsEmpty)
 0244                {
 0245                    _inputString = capturedBeforeProcessing; // restore the state
 0246                    return false;
 247                }
 248
 0249                ReadOnlySpan<char> candidate = GetAssemblyNameCandidate(_inputString);
 0250                if (!AssemblyNameInfo.TryParse(candidate, out assemblyName))
 0251                {
 0252                    return false;
 253                }
 254
 0255                _inputString = _inputString.Slice(candidate.Length);
 0256                return true;
 257            }
 258
 0259            return true;
 0260        }
 261
 262        private static TypeName? GetDeclaringType(string fullTypeName, List<int>? nestedNameLengths, AssemblyNameInfo? a
 0263        {
 0264            if (nestedNameLengths is null)
 0265            {
 0266                return null;
 267            }
 268
 269            // The loop below is protected by the dive check in GetFullTypeNameLength.
 270
 0271            TypeName? declaringType = null;
 0272            int nameOffset = 0;
 0273            foreach (int nestedNameLength in nestedNameLengths)
 0274            {
 0275                Debug.Assert(nestedNameLength > 0, "TryGetTypeNameInfo should return error on zero lengths");
 0276                int fullNameLength = nameOffset + nestedNameLength;
 0277                declaringType = new(fullTypeName, assemblyName, declaringType: declaringType, nestedNameLength: fullName
 0278                nameOffset += nestedNameLength + 1; // include the '+' that was skipped in name
 0279            }
 280
 0281            return declaringType;
 0282        }
 283    }
 284}
 285