Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a JsonWriterOptions.MaxDepth property #61608

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ public partial struct JsonWriterOptions
private int _dummyPrimitive;
public System.Text.Encodings.Web.JavaScriptEncoder? Encoder { readonly get { throw null; } set { } }
public bool Indented { get { throw null; } set { } }
public int MaxDepth { readonly get { throw null; } set { } }
public bool SkipValidation { get { throw null; } set { } }
}
public ref partial struct Utf8JsonReader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ internal static partial class JsonConstants
public static ReadOnlySpan<byte> EscapableChars => new byte[] { Quote, (byte)'n', (byte)'r', (byte)'t', Slash, (byte)'u', (byte)'b', (byte)'f' };

public const int SpacesPerIndent = 2;
public const int MaxWriterDepth = 1_000;
public const int RemoveFlagsBitMask = 0x7FFFFFFF;

public const int StackallocByteThreshold = 256;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public sealed partial class JsonSerializerOptions
{
internal const int BufferSizeDefault = 16 * 1024;

// For backward compatibility the default max depth for JsonSerializer is 64,
// the minimum of JsonReaderOptions.DefaultMaxDepth and JsonWriterOptions.DefaultMaxDepth.
internal const int DefaultMaxDepth = JsonReaderOptions.DefaultMaxDepth;
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets a read-only, singleton instance of <see cref="JsonSerializerOptions" /> that uses the default configuration.
/// </summary>
Expand Down Expand Up @@ -433,12 +437,11 @@ public int MaxDepth
}

_maxDepth = value;
EffectiveMaxDepth = (value == 0 ? JsonReaderOptions.DefaultMaxDepth : value);
EffectiveMaxDepth = (value == 0 ? DefaultMaxDepth : value);
}
}

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

/// <summary>
/// Specifies the policy used to convert a property's name on an object to another format, such as camel-casing.
Expand Down Expand Up @@ -699,7 +702,7 @@ internal JsonReaderOptions GetReaderOptions()
{
AllowTrailingCommas = AllowTrailingCommas,
CommentHandling = ReadCommentHandling,
MaxDepth = MaxDepth
MaxDepth = EffectiveMaxDepth
};
}

Expand All @@ -709,6 +712,7 @@ internal JsonWriterOptions GetWriterOptions()
{
Encoder = Encoder,
Indented = WriteIndented,
MaxDepth = EffectiveMaxDepth,
#if !DEBUG
SkipValidation = true
#endif
Expand Down
30 changes: 15 additions & 15 deletions src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,12 @@ public static void ThrowArgumentException(ReadOnlySpan<char> propertyName, ReadO
}

[DoesNotReturn]
public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<byte> propertyName, int currentDepth)
public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<byte> propertyName, int currentDepth, int maxDepth)
{
currentDepth &= JsonConstants.RemoveFlagsBitMask;
if (currentDepth >= JsonConstants.MaxWriterDepth)
if (currentDepth >= maxDepth)
{
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, JsonConstants.MaxWriterDepth));
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, maxDepth));
}
else
{
Expand All @@ -141,11 +141,11 @@ public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<byte> p
}

[DoesNotReturn]
public static void ThrowInvalidOperationException(int currentDepth)
public static void ThrowInvalidOperationException(int currentDepth, int maxDepth)
{
currentDepth &= JsonConstants.RemoveFlagsBitMask;
Debug.Assert(currentDepth >= JsonConstants.MaxWriterDepth);
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, JsonConstants.MaxWriterDepth));
Debug.Assert(currentDepth >= maxDepth);
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, maxDepth));
}

[DoesNotReturn]
Expand Down Expand Up @@ -183,12 +183,12 @@ private static InvalidOperationException GetInvalidOperationException(int curren
}

[DoesNotReturn]
public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<char> propertyName, int currentDepth)
public static void ThrowInvalidOperationOrArgumentException(ReadOnlySpan<char> propertyName, int currentDepth, int maxDepth)
{
currentDepth &= JsonConstants.RemoveFlagsBitMask;
if (currentDepth >= JsonConstants.MaxWriterDepth)
if (currentDepth >= maxDepth)
{
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, JsonConstants.MaxWriterDepth));
ThrowInvalidOperationException(SR.Format(SR.DepthTooLarge, currentDepth, maxDepth));
}
else
{
Expand Down Expand Up @@ -433,9 +433,9 @@ private static string GetResourceString(ref Utf8JsonReader json, ExceptionResour
}

[DoesNotReturn]
public static void ThrowInvalidOperationException(ExceptionResource resource, int currentDepth, byte token, JsonTokenType tokenType)
public static void ThrowInvalidOperationException(ExceptionResource resource, int currentDepth, int maxDepth, byte token, JsonTokenType tokenType)
{
throw GetInvalidOperationException(resource, currentDepth, token, tokenType);
throw GetInvalidOperationException(resource, currentDepth, maxDepth, token, tokenType);
}

[DoesNotReturn]
Expand Down Expand Up @@ -508,9 +508,9 @@ public static InvalidOperationException GetInvalidOperationException(string mess
}

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

// This function will convert an ExceptionResource enum value to the resource string.
[MethodImpl(MethodImplOptions.NoInlining)]
private static string GetResourceString(ExceptionResource resource, int currentDepth, byte token, JsonTokenType tokenType)
private static string GetResourceString(ExceptionResource resource, int currentDepth, int maxDepth, byte token, JsonTokenType tokenType)
{
string message = "";
switch (resource)
Expand All @@ -536,7 +536,7 @@ private static string GetResourceString(ExceptionResource resource, int currentD
SR.Format(SR.MismatchedObjectArray, (char)token);
break;
case ExceptionResource.DepthTooLarge:
message = SR.Format(SR.DepthTooLarge, currentDepth & JsonConstants.RemoveFlagsBitMask, JsonConstants.MaxWriterDepth);
message = SR.Format(SR.DepthTooLarge, currentDepth & JsonConstants.RemoveFlagsBitMask, maxDepth);
break;
case ExceptionResource.CannotStartObjectArrayWithoutProperty:
message = SR.Format(SR.CannotStartObjectArrayWithoutProperty, tokenType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ namespace System.Text.Json
/// </summary>
public struct JsonWriterOptions
{
internal const int DefaultMaxDepth = 1000;

private int _maxDepth;
private int _optionsMask;

/// <summary>
Expand Down Expand Up @@ -40,6 +43,27 @@ public bool Indented
}
}

/// <summary>
/// Gets or sets the maximum depth allowed when writing JSON, with the default (i.e. 0) indicating a max depth of 1000.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the max depth is set to a negative value.
/// </exception>
/// <remarks>
/// Reading past this depth will throw a <exception cref="JsonException"/>.
/// </remarks>
public int MaxDepth
{
readonly get => _maxDepth;
set
{
if (value < 0)
throw ThrowHelper.GetArgumentOutOfRangeException_MaxDepthMustBePositive(nameof(value));

_maxDepth = value;
}
}

/// <summary>
/// Defines whether the <see cref="Utf8JsonWriter"/> should skip structural validation and allow
/// the user to write invalid JSON, when set to true. If set to false, any attempts to write invalid JSON will result in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ private void WriteBase64Minimized(ReadOnlySpan<byte> escapedPropertyName, ReadOn
private void WriteBase64Indented(ReadOnlySpan<char> escapedPropertyName, ReadOnlySpan<byte> bytes)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
Debug.Assert(indent <= 2 * _options.MaxDepth);

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

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTi
private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTime value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
Debug.Assert(indent <= 2 * _options.MaxDepth);

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

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, DateTi
private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, DateTimeOffset value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
Debug.Assert(indent <= 2 * _options.MaxDepth);

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

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, decima
private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, decimal value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
Debug.Assert(indent <= 2 * _options.MaxDepth);

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

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, double
private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, double value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
Debug.Assert(indent <= 2 * _options.MaxDepth);

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

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ private void WriteNumberMinimized(ReadOnlySpan<byte> escapedPropertyName, float
private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, float value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
Debug.Assert(indent <= 2 * _options.MaxDepth);

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

Expand Down Expand Up @@ -321,7 +321,7 @@ private void WriteNumberIndented(ReadOnlySpan<char> escapedPropertyName, float v
private void WriteNumberIndented(ReadOnlySpan<byte> escapedPropertyName, float value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
Debug.Assert(indent <= 2 * _options.MaxDepth);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ private void WriteStringMinimized(ReadOnlySpan<byte> escapedPropertyName, Guid v
private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, Guid value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
Debug.Assert(indent <= 2 * _options.MaxDepth);

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

Expand Down Expand Up @@ -329,7 +329,7 @@ private void WriteStringIndented(ReadOnlySpan<char> escapedPropertyName, Guid va
private void WriteStringIndented(ReadOnlySpan<byte> escapedPropertyName, Guid value)
{
int indent = Indentation;
Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth);
Debug.Assert(indent <= 2 * _options.MaxDepth);

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

Expand Down
Loading