Skip to content

Commit

Permalink
Add a JsonWriterOptions.MaxDepth property (#61608)
Browse files Browse the repository at this point in the history
* Add a JsonWriterOptions.MaxDepth property

* remove depth checks from the converter layer

* Revert "remove depth checks from the converter layer"

This reverts commit 0e43092.
  • Loading branch information
eiriktsarpalis authored Nov 22, 2021
1 parent ee7f142 commit 4892e4f
Show file tree
Hide file tree
Showing 36 changed files with 262 additions and 91 deletions.
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;

/// <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

0 comments on commit 4892e4f

Please sign in to comment.