Skip to content

Commit 16fe4d4

Browse files
authored
[TypeName] Nested types should respect MaxNode count (#106334)
1 parent fa0b3f0 commit 16fe4d4

File tree

7 files changed

+233
-102
lines changed

7 files changed

+233
-102
lines changed

src/libraries/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@
44
using System.Diagnostics;
55
using System.Diagnostics.CodeAnalysis;
66
using System.Reflection.Metadata;
7-
using System.Text;
87

98
namespace System.Reflection.Metadata
109
{
1110
internal struct TypeNameParseOptions
1211
{
1312
public TypeNameParseOptions() { }
1413
#pragma warning disable CA1822 // Mark members as static
15-
// CoreLib does not enforce any limits
16-
public bool IsMaxDepthExceeded(int _) => false;
1714
public int MaxNodes
1815
{
1916
get

src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeName.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -301,24 +301,28 @@ public string Name
301301
/// </remarks>
302302
public int GetNodeCount()
303303
{
304+
// This method uses checked arithmetic to avoid silent overflows.
305+
// It's impossible to parse a TypeName with NodeCount > int.MaxValue
306+
// (TypeNameParseOptions.MaxNodes is an int), but it's possible
307+
// to create such names with the Make* APIs.
304308
int result = 1;
305309

306-
if (IsNested)
310+
if (IsArray || IsPointer || IsByRef)
307311
{
308-
result += DeclaringType.GetNodeCount();
312+
result = checked(result + GetElementType().GetNodeCount());
309313
}
310314
else if (IsConstructedGenericType)
311315
{
312-
result++;
313-
}
314-
else if (IsArray || IsPointer || IsByRef)
315-
{
316-
result += GetElementType().GetNodeCount();
317-
}
316+
result = checked(result + GetGenericTypeDefinition().GetNodeCount());
318317

319-
foreach (TypeName genericArgument in GetGenericArguments())
318+
foreach (TypeName genericArgument in GetGenericArguments())
319+
{
320+
result = checked(result + genericArgument.GetNodeCount());
321+
}
322+
}
323+
else if (IsNested)
320324
{
321-
result += genericArgument.GetNodeCount();
325+
result = checked(result + DeclaringType.GetNodeCount());
322326
}
323327

324328
return result;

src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeNameParser.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ private TypeNameParser(ReadOnlySpan<char> name, bool throwOnError, TypeNameParse
4949
{
5050
if (throwOnError)
5151
{
52-
if (parser._parseOptions.IsMaxDepthExceeded(recursiveDepth))
52+
if (IsMaxDepthExceeded(parser._parseOptions, recursiveDepth))
5353
{
5454
ThrowInvalidOperation_MaxNodesExceeded(parser._parseOptions.MaxNodes);
5555
}
@@ -61,19 +61,21 @@ private TypeNameParser(ReadOnlySpan<char> name, bool throwOnError, TypeNameParse
6161
return null;
6262
}
6363

64+
Debug.Assert(parsedName.GetNodeCount() == recursiveDepth, $"Node count mismatch for '{typeName.ToString()}'");
65+
6466
return parsedName;
6567
}
6668

6769
// this method should return null instead of throwing, so the caller can get errorIndex and include it in error msg
6870
private TypeName? ParseNextTypeName(bool allowFullyQualifiedName, ref int recursiveDepth)
6971
{
70-
if (!TryDive(ref recursiveDepth))
72+
if (!TryDive(_parseOptions, ref recursiveDepth))
7173
{
7274
return null;
7375
}
7476

7577
List<int>? nestedNameLengths = null;
76-
if (!TryGetTypeNameInfo(ref _inputString, ref nestedNameLengths, out int fullTypeNameLength))
78+
if (!TryGetTypeNameInfo(_parseOptions, ref _inputString, ref nestedNameLengths, ref recursiveDepth, out int fullTypeNameLength))
7779
{
7880
return null;
7981
}
@@ -147,14 +149,24 @@ private TypeNameParser(ReadOnlySpan<char> name, bool throwOnError, TypeNameParse
147149
{
148150
_inputString = capturedBeforeProcessing;
149151
}
152+
else
153+
{
154+
// Every constructed generic type needs the generic type definition.
155+
if (!TryDive(_parseOptions, ref recursiveDepth))
156+
{
157+
return null;
158+
}
159+
// If that generic type is a nested type, we don't increase the recursiveDepth any further,
160+
// as generic type definition uses exactly the same declaring type as the constructed generic type.
161+
}
150162

151163
int previousDecorator = default;
152164
// capture the current state so we can reprocess it again once we know the AssemblyName
153165
capturedBeforeProcessing = _inputString;
154166
// iterate over the decorators to ensure there are no illegal combinations
155167
while (TryParseNextDecorator(ref _inputString, out int parsedDecorator))
156168
{
157-
if (!TryDive(ref recursiveDepth))
169+
if (!TryDive(_parseOptions, ref recursiveDepth))
158170
{
159171
return null;
160172
}
@@ -245,15 +257,5 @@ private bool TryParseAssemblyName(ref AssemblyNameInfo? assemblyName)
245257

246258
return declaringType;
247259
}
248-
249-
private bool TryDive(ref int depth)
250-
{
251-
if (_parseOptions.IsMaxDepthExceeded(depth))
252-
{
253-
return false;
254-
}
255-
depth++;
256-
return true;
257-
}
258260
}
259261
}

src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeNameParserHelpers.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ internal static bool IsBeginningOfGenericArgs(ref ReadOnlySpan<char> span, out b
220220
return false;
221221
}
222222

223-
internal static bool TryGetTypeNameInfo(ref ReadOnlySpan<char> input, ref List<int>? nestedNameLengths, out int totalLength)
223+
internal static bool TryGetTypeNameInfo(TypeNameParseOptions options, ref ReadOnlySpan<char> input,
224+
ref List<int>? nestedNameLengths, ref int recursiveDepth, out int totalLength)
224225
{
225226
bool isNestedType;
226227
totalLength = 0;
@@ -248,6 +249,11 @@ internal static bool TryGetTypeNameInfo(ref ReadOnlySpan<char> input, ref List<i
248249
#endif
249250
if (isNestedType)
250251
{
252+
if (!TryDive(options, ref recursiveDepth))
253+
{
254+
return false;
255+
}
256+
251257
(nestedNameLengths ??= new()).Add(length);
252258
totalLength += 1; // skip the '+' sign in next search
253259
}
@@ -389,6 +395,19 @@ internal static void ThrowInvalidOperation_HasToBeArrayClass()
389395
#endif
390396
}
391397

398+
internal static bool IsMaxDepthExceeded(TypeNameParseOptions options, int depth)
399+
#if SYSTEM_PRIVATE_CORELIB
400+
=> false; // CoreLib does not enforce any limits
401+
#else
402+
=> depth > options.MaxNodes;
403+
#endif
404+
405+
internal static bool TryDive(TypeNameParseOptions options, ref int depth)
406+
{
407+
depth++;
408+
return !IsMaxDepthExceeded(options, depth);
409+
}
410+
392411
#if SYSTEM_REFLECTION_METADATA
393412
[DoesNotReturn]
394413
internal static void ThrowInvalidOperation_NotSimpleName(string fullName)

src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeNameParserOptions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,5 @@ public int MaxNodes
2727
_maxNodes = value;
2828
}
2929
}
30-
31-
internal bool IsMaxDepthExceeded(int depth) => depth >= _maxNodes;
3230
}
3331
}

src/libraries/System.Reflection.Metadata/tests/Metadata/TypeNameParserHelpersTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,17 @@ public void TryGetTypeNameInfoGetsAllTheInfo(string input, bool expectedResult,
122122
{
123123
List<int>? nestedNameLengths = null;
124124
ReadOnlySpan<char> span = input.AsSpan();
125-
bool result = TypeNameParserHelpers.TryGetTypeNameInfo(ref span, ref nestedNameLengths, out int totalLength);
125+
TypeNameParseOptions options = new();
126+
int recursiveDepth = 0;
127+
bool result = TypeNameParserHelpers.TryGetTypeNameInfo(options, ref span, ref nestedNameLengths, ref recursiveDepth, out int totalLength);
126128

127129
Assert.Equal(expectedResult, result);
128130

129131
if (expectedResult)
130132
{
131133
Assert.Equal(expectedNestedNameLengths, nestedNameLengths?.ToArray());
132134
Assert.Equal(expectedTotalLength, totalLength);
135+
Assert.Equal(expectedNestedNameLengths?.Length ?? 0, recursiveDepth);
133136
}
134137
}
135138

0 commit comments

Comments
 (0)