Skip to content

Commit 4892e4f

Browse files
Add a JsonWriterOptions.MaxDepth property (#61608)
* Add a JsonWriterOptions.MaxDepth property * remove depth checks from the converter layer * Revert "remove depth checks from the converter layer" This reverts commit 0e43092.
1 parent ee7f142 commit 4892e4f

36 files changed

+262
-91
lines changed

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ public partial struct JsonWriterOptions
358358
private int _dummyPrimitive;
359359
public System.Text.Encodings.Web.JavaScriptEncoder? Encoder { readonly get { throw null; } set { } }
360360
public bool Indented { get { throw null; } set { } }
361+
public int MaxDepth { readonly get { throw null; } set { } }
361362
public bool SkipValidation { get { throw null; } set { } }
362363
}
363364
public ref partial struct Utf8JsonReader

src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ internal static partial class JsonConstants
4848
public static ReadOnlySpan<byte> EscapableChars => new byte[] { Quote, (byte)'n', (byte)'r', (byte)'t', Slash, (byte)'u', (byte)'b', (byte)'f' };
4949

5050
public const int SpacesPerIndent = 2;
51-
public const int MaxWriterDepth = 1_000;
5251
public const int RemoveFlagsBitMask = 0x7FFFFFFF;
5352

5453
public const int StackallocByteThreshold = 256;

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ public sealed partial class JsonSerializerOptions
2020
{
2121
internal const int BufferSizeDefault = 16 * 1024;
2222

23+
// For backward compatibility the default max depth for JsonSerializer is 64,
24+
// the minimum of JsonReaderOptions.DefaultMaxDepth and JsonWriterOptions.DefaultMaxDepth.
25+
internal const int DefaultMaxDepth = JsonReaderOptions.DefaultMaxDepth;
26+
2327
/// <summary>
2428
/// Gets a read-only, singleton instance of <see cref="JsonSerializerOptions" /> that uses the default configuration.
2529
/// </summary>
@@ -433,12 +437,11 @@ public int MaxDepth
433437
}
434438

435439
_maxDepth = value;
436-
EffectiveMaxDepth = (value == 0 ? JsonReaderOptions.DefaultMaxDepth : value);
440+
EffectiveMaxDepth = (value == 0 ? DefaultMaxDepth : value);
437441
}
438442
}
439443

440-
// The default is 64 because that is what the reader uses, so re-use the same JsonReaderOptions.DefaultMaxDepth constant.
441-
internal int EffectiveMaxDepth { get; private set; } = JsonReaderOptions.DefaultMaxDepth;
444+
internal int EffectiveMaxDepth { get; private set; } = DefaultMaxDepth;
442445

443446
/// <summary>
444447
/// Specifies the policy used to convert a property's name on an object to another format, such as camel-casing.
@@ -699,7 +702,7 @@ internal JsonReaderOptions GetReaderOptions()
699702
{
700703
AllowTrailingCommas = AllowTrailingCommas,
701704
CommentHandling = ReadCommentHandling,
702-
MaxDepth = MaxDepth
705+
MaxDepth = EffectiveMaxDepth
703706
};
704707
}
705708

@@ -709,6 +712,7 @@ internal JsonWriterOptions GetWriterOptions()
709712
{
710713
Encoder = Encoder,
711714
Indented = WriteIndented,
715+
MaxDepth = EffectiveMaxDepth,
712716
#if !DEBUG
713717
SkipValidation = true
714718
#endif

src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,12 @@ public static void ThrowArgumentException(ReadOnlySpan<char> propertyName, ReadO
126126
}
127127

128128
[DoesNotReturn]
129-
public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<byte> propertyName, int currentDepth)
129+
public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<byte> propertyName, int currentDepth, int maxDepth)
130130
{
131131
currentDepth &= JsonConstants.RemoveFlagsBitMask;
132-
if (currentDepth >= JsonConstants.MaxWriterDepth)
132+
if (currentDepth >= maxDepth)
133133
{
134-
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, JsonConstants.MaxWriterDepth));
134+
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, maxDepth));
135135
}
136136
else
137137
{
@@ -141,11 +141,11 @@ public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<byte> p
141141
}
142142

143143
[DoesNotReturn]
144-
public static void ThrowInvalidOperationException(int currentDepth)
144+
public static void ThrowInvalidOperationException(int currentDepth, int maxDepth)
145145
{
146146
currentDepth &= JsonConstants.RemoveFlagsBitMask;
147-
Debug.Assert(currentDepth >= JsonConstants.MaxWriterDepth);
148-
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, JsonConstants.MaxWriterDepth));
147+
Debug.Assert(currentDepth >= maxDepth);
148+
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, maxDepth));
149149
}
150150

151151
[DoesNotReturn]
@@ -183,12 +183,12 @@ private static InvalidOperationException GetInvalidOperationException(int curren
183183
}
184184

185185
[DoesNotReturn]
186-
public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<char> propertyName, int currentDepth)
186+
public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<char> propertyName, int currentDepth, int maxDepth)
187187
{
188188
currentDepth &= JsonConstants.RemoveFlagsBitMask;
189-
if (currentDepth >= JsonConstants.MaxWriterDepth)
189+
if (currentDepth >= maxDepth)
190190
{
191-
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, JsonConstants.MaxWriterDepth));
191+
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, maxDepth));
192192
}
193193
else
194194
{
@@ -433,9 +433,9 @@ private static string GetResourceString(ref Utf8JsonReader json, ExceptionResour
433433
}
434434

435435
[DoesNotReturn]
436-
public static void ThrowInvalidOperationException(ExceptionResource resource, int currentDepth, byte token, JsonTokenType tokenType)
436+
public static void ThrowInvalidOperationException(ExceptionResource resource, int currentDepth, int maxDepth, byte token, JsonTokenType tokenType)
437437
{
438-
throw GetInvalidOperationException(resource, currentDepth, token, tokenType);
438+
throw GetInvalidOperationException(resource, currentDepth, maxDepth, token, tokenType);
439439
}
440440

441441
[DoesNotReturn]
@@ -508,9 +508,9 @@ public static InvalidOperationException GetInvalidOperationException(string mess
508508
}
509509

510510
[MethodImpl(MethodImplOptions.NoInlining)]
511-
public static InvalidOperationException GetInvalidOperationException(ExceptionResource resource, int currentDepth, byte token, JsonTokenType tokenType)
511+
public static InvalidOperationException GetInvalidOperationException(ExceptionResource resource, int currentDepth, int maxDepth, byte token, JsonTokenType tokenType)
512512
{
513-
string message = GetResourceString(resource, currentDepth, token, tokenType);
513+
string message = GetResourceString(resource, currentDepth, maxDepth, token, tokenType);
514514
InvalidOperationException ex = GetInvalidOperationException(message);
515515
ex.Source = ExceptionSourceValueToRethrowAsJsonException;
516516
return ex;
@@ -524,7 +524,7 @@ public static void ThrowOutOfMemoryException(uint capacity)
524524

525525
// This function will convert an ExceptionResource enum value to the resource string.
526526
[MethodImpl(MethodImplOptions.NoInlining)]
527-
private static string GetResourceString(ExceptionResource resource, int currentDepth, byte token, JsonTokenType tokenType)
527+
private static string GetResourceString(ExceptionResource resource, int currentDepth, int maxDepth, byte token, JsonTokenType tokenType)
528528
{
529529
string message = "";
530530
switch (resource)
@@ -536,7 +536,7 @@ private static string GetResourceString(ExceptionResource resource, int currentD
536536
SR.Format(SR.MismatchedObjectArray, (char)token);
537537
break;
538538
case ExceptionResource.DepthTooLarge:
539-
message = SR.Format(SR.DepthTooLarge, currentDepth & JsonConstants.RemoveFlagsBitMask, JsonConstants.MaxWriterDepth);
539+
message = SR.Format(SR.DepthTooLarge, currentDepth & JsonConstants.RemoveFlagsBitMask, maxDepth);
540540
break;
541541
case ExceptionResource.CannotStartObjectArrayWithoutProperty:
542542
message = SR.Format(SR.CannotStartObjectArrayWithoutProperty, tokenType);

src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterOptions.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ namespace System.Text.Json
1313
/// </summary>
1414
public struct JsonWriterOptions
1515
{
16+
internal const int DefaultMaxDepth = 1000;
17+
18+
private int _maxDepth;
1619
private int _optionsMask;
1720

1821
/// <summary>
@@ -40,6 +43,27 @@ public bool Indented
4043
}
4144
}
4245

46+
/// <summary>
47+
/// Gets or sets the maximum depth allowed when writing JSON, with the default (i.e. 0) indicating a max depth of 1000.
48+
/// </summary>
49+
/// <exception cref="ArgumentOutOfRangeException">
50+
/// Thrown when the max depth is set to a negative value.
51+
/// </exception>
52+
/// <remarks>
53+
/// Reading past this depth will throw a <exception cref="JsonException"/>.
54+
/// </remarks>
55+
public int MaxDepth
56+
{
57+
readonly get => _maxDepth;
58+
set
59+
{
60+
if (value < 0)
61+
throw ThrowHelper.GetArgumentOutOfRangeException_MaxDepthMustBePositive(nameof(value));
62+
63+
_maxDepth = value;
64+
}
65+
}
66+
4367
/// <summary>
4468
/// Defines whether the <see cref="Utf8JsonWriter"/> should skip structural validation and allow
4569
/// the user to write invalid JSON, when set to true. If set to false, any attempts to write invalid JSON will result in

src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Bytes.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ private void WriteBase64Minimized(ReadOnlySpan<byte> escapedPropertyName, ReadOn
276276
private void WriteBase64Indented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> bytes)
277277
{
278278
int indent = Indentation;
279-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
279+
Debug.Assert(indent <= 2 * _options.MaxDepth);
280280

281281
int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);
282282

@@ -326,7 +326,7 @@ private void WriteBase64Indented(ReadOnlySpan<char> escapedPropertyName, ReadOnl
326326
private void WriteBase64Indented(ReadOnlySpan<byte> escapedPropertyName, ReadOnlySpan<byte> bytes)
327327
{
328328
int indent = Indentation;
329-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
329+
Debug.Assert(indent <= 2 * _options.MaxDepth);
330330

331331
int encodedLength = Base64.GetMaxEncodedToUtf8Length(bytes.Length);
332332

src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTi
278278
private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTime value)
279279
{
280280
int indent = Indentation;
281-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
281+
Debug.Assert(indent <= 2 * _options.MaxDepth);
282282

283283
Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);
284284

@@ -327,7 +327,7 @@ private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTim
327327
private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTime value)
328328
{
329329
int indent = Indentation;
330-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
330+
Debug.Assert(indent <= 2 * _options.MaxDepth);
331331

332332
Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);
333333

src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTi
277277
private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTimeOffset value)
278278
{
279279
int indent = Indentation;
280-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
280+
Debug.Assert(indent <= 2 * _options.MaxDepth);
281281

282282
Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);
283283

@@ -326,7 +326,7 @@ private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTim
326326
private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, DateTimeOffset value)
327327
{
328328
int indent = Indentation;
329-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
329+
Debug.Assert(indent <= 2 * _options.MaxDepth);
330330

331331
Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength);
332332

src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, decima
271271
private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, decimal value)
272272
{
273273
int indent = Indentation;
274-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
274+
Debug.Assert(indent <= 2 * _options.MaxDepth);
275275

276276
Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength);
277277

@@ -317,7 +317,7 @@ private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, decimal
317317
private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, decimal value)
318318
{
319319
int indent = Indentation;
320-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
320+
Debug.Assert(indent <= 2 * _options.MaxDepth);
321321

322322
Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength);
323323

src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, double
275275
private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, double value)
276276
{
277277
int indent = Indentation;
278-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
278+
Debug.Assert(indent <= 2 * _options.MaxDepth);
279279

280280
Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength);
281281

@@ -321,7 +321,7 @@ private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, double
321321
private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, double value)
322322
{
323323
int indent = Indentation;
324-
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
324+
Debug.Assert(indent <= 2 * _options.MaxDepth);
325325

326326
Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength);
327327

0 commit comments

Comments
 (0)