Skip to content

Commit f2a55e2

Browse files
authored
System.Text.Json: stackalloc constants + misc PR feedback (#55350)
* Follow-up feedback from #54186. * Code review. * Code review. * Dedicated constant for the max number of chars permitted to be allocated on the stack.
1 parent c5edf0e commit f2a55e2

33 files changed

+169
-146
lines changed

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.TryGetProperty.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ internal bool TryGetNamedPropertyValue(int index, ReadOnlySpan<char> propertyNam
2727
int startIndex = index + DbRow.Size;
2828
int endIndex = checked(row.NumberOfRows * DbRow.Size + index);
2929

30-
if (maxBytes < JsonConstants.StackallocThreshold)
30+
if (maxBytes < JsonConstants.StackallocByteThreshold)
3131
{
32-
Span<byte> utf8Name = stackalloc byte[JsonConstants.StackallocThreshold];
32+
Span<byte> utf8Name = stackalloc byte[JsonConstants.StackallocByteThreshold];
3333
int len = JsonReaderHelper.GetUtf8FromText(propertyName, utf8Name);
3434
utf8Name = utf8Name.Slice(0, len);
3535

@@ -139,7 +139,7 @@ private bool TryGetNamedPropertyValue(
139139
out JsonElement value)
140140
{
141141
ReadOnlySpan<byte> documentSpan = _utf8Json.Span;
142-
Span<byte> utf8UnescapedStack = stackalloc byte[JsonConstants.StackallocThreshold];
142+
Span<byte> utf8UnescapedStack = stackalloc byte[JsonConstants.StackallocByteThreshold];
143143

144144
// Move to the row before the EndObject
145145
int index = endIndex - DbRow.Size;

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,8 @@ internal bool TextEquals(int index, ReadOnlySpan<char> otherText, bool isPropert
298298
byte[]? otherUtf8TextArray = null;
299299

300300
int length = checked(otherText.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);
301-
Span<byte> otherUtf8Text = length <= JsonConstants.StackallocThreshold ?
302-
stackalloc byte[JsonConstants.StackallocThreshold] :
301+
Span<byte> otherUtf8Text = length <= JsonConstants.StackallocByteThreshold ?
302+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
303303
(otherUtf8TextArray = ArrayPool<byte>.Shared.Rent(length));
304304

305305
ReadOnlySpan<byte> utf16Text = MemoryMarshal.AsBytes(otherText);

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ internal static class JsonConstants
5151
public const int MaxWriterDepth = 1_000;
5252
public const int RemoveFlagsBitMask = 0x7FFFFFFF;
5353

54-
public const int StackallocThreshold = 256;
54+
public const int StackallocByteThreshold = 256;
55+
public const int StackallocCharThreshold = StackallocByteThreshold / 2;
5556

5657
// In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped.
5758
// For example: '+' becomes '\u0043'
@@ -60,7 +61,7 @@ internal static class JsonConstants
6061
public const int MaxExpansionFactorWhileEscaping = 6;
6162

6263
// In the worst case, a single UTF-16 character could be expanded to 3 UTF-8 bytes.
63-
// Only surrogate pairs expand to 4 UTF-8 bytes but that is a transformation of 2 UTF-16 characters goign to 4 UTF-8 bytes (factor of 2).
64+
// Only surrogate pairs expand to 4 UTF-8 bytes but that is a transformation of 2 UTF-16 characters going to 4 UTF-8 bytes (factor of 2).
6465
// All other UTF-16 characters can be represented by either 1 or 2 UTF-8 bytes.
6566
public const int MaxExpansionFactorWhileTranscoding = 3;
6667

@@ -95,6 +96,8 @@ internal static class JsonConstants
9596
public const int MinimumDateTimeParseLength = 10; // YYYY-MM-DD
9697
public const int MaximumEscapedDateTimeOffsetParseLength = MaxExpansionFactorWhileEscaping * MaximumDateTimeOffsetParseLength;
9798

99+
public const int MaximumLiteralLength = 5; // Must be able to fit null, true, & false.
100+
98101
// Encoding Helpers
99102
public const char HighSurrogateStart = '\ud800';
100103
public const char HighSurrogateEnd = '\udbff';

src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Date.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ public static bool TryParseAsISO(ReadOnlySpan<char> source, out DateTime value)
5151

5252
int maxLength = checked(source.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);
5353

54-
Span<byte> bytes = maxLength <= JsonConstants.StackallocThreshold
55-
? stackalloc byte[JsonConstants.StackallocThreshold]
54+
Span<byte> bytes = maxLength <= JsonConstants.StackallocByteThreshold
55+
? stackalloc byte[JsonConstants.StackallocByteThreshold]
5656
: new byte[maxLength];
5757

5858
int length = JsonReaderHelper.GetUtf8FromText(source, bytes);
@@ -86,8 +86,8 @@ public static bool TryParseAsISO(ReadOnlySpan<char> source, out DateTimeOffset v
8686

8787
int maxLength = checked(source.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);
8888

89-
Span<byte> bytes = maxLength <= JsonConstants.StackallocThreshold
90-
? stackalloc byte[JsonConstants.StackallocThreshold]
89+
Span<byte> bytes = maxLength <= JsonConstants.StackallocByteThreshold
90+
? stackalloc byte[JsonConstants.StackallocByteThreshold]
9191
: new byte[maxLength];
9292

9393
int length = JsonReaderHelper.GetUtf8FromText(source, bytes);

src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Escaping.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public static byte[] EscapeValue(
3737

3838
int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
3939

40-
Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
41-
stackalloc byte[length] :
40+
Span<byte> escapedValue = length <= JsonConstants.StackallocByteThreshold ?
41+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
4242
(valueArray = ArrayPool<byte>.Shared.Rent(length));
4343

4444
JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
@@ -65,8 +65,8 @@ private static byte[] GetEscapedPropertyNameSection(
6565

6666
int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
6767

68-
Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
69-
stackalloc byte[length] :
68+
Span<byte> escapedValue = length <= JsonConstants.StackallocByteThreshold ?
69+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
7070
(valueArray = ArrayPool<byte>.Shared.Rent(length));
7171

7272
JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);

src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.Unescaping.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ public static bool TryGetUnescapedBase64Bytes(ReadOnlySpan<byte> utf8Source, int
1414
{
1515
byte[]? unescapedArray = null;
1616

17-
Span<byte> utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocThreshold ?
18-
stackalloc byte[utf8Source.Length] :
17+
Span<byte> utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocByteThreshold ?
18+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
1919
(unescapedArray = ArrayPool<byte>.Shared.Rent(utf8Source.Length));
2020

2121
Unescape(utf8Source, utf8Unescaped, idx, out int written);
@@ -44,8 +44,8 @@ public static string GetUnescapedString(ReadOnlySpan<byte> utf8Source, int idx)
4444
int length = utf8Source.Length;
4545
byte[]? pooledName = null;
4646

47-
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocThreshold ?
48-
stackalloc byte[length] :
47+
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocByteThreshold ?
48+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
4949
(pooledName = ArrayPool<byte>.Shared.Rent(length));
5050

5151
Unescape(utf8Source, utf8Unescaped, idx, out int written);
@@ -71,8 +71,8 @@ public static ReadOnlySpan<byte> GetUnescapedSpan(ReadOnlySpan<byte> utf8Source,
7171
int length = utf8Source.Length;
7272
byte[]? pooledName = null;
7373

74-
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocThreshold ?
75-
stackalloc byte[length] :
74+
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocByteThreshold ?
75+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
7676
(pooledName = ArrayPool<byte>.Shared.Rent(length));
7777

7878
Unescape(utf8Source, utf8Unescaped, idx, out int written);
@@ -96,8 +96,8 @@ public static bool UnescapeAndCompare(ReadOnlySpan<byte> utf8Source, ReadOnlySpa
9696

9797
byte[]? unescapedArray = null;
9898

99-
Span<byte> utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocThreshold ?
100-
stackalloc byte[utf8Source.Length] :
99+
Span<byte> utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocByteThreshold ?
100+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
101101
(unescapedArray = ArrayPool<byte>.Shared.Rent(utf8Source.Length));
102102

103103
Unescape(utf8Source, utf8Unescaped, 0, out int written);
@@ -127,12 +127,12 @@ public static bool UnescapeAndCompare(ReadOnlySequence<byte> utf8Source, ReadOnl
127127

128128
int length = checked((int)utf8Source.Length);
129129

130-
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocThreshold ?
131-
stackalloc byte[length] :
130+
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocByteThreshold ?
131+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
132132
(unescapedArray = ArrayPool<byte>.Shared.Rent(length));
133133

134-
Span<byte> utf8Escaped = length <= JsonConstants.StackallocThreshold ?
135-
stackalloc byte[length] :
134+
Span<byte> utf8Escaped = length <= JsonConstants.StackallocByteThreshold ?
135+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
136136
(escapedArray = ArrayPool<byte>.Shared.Rent(length));
137137

138138
utf8Source.CopyTo(utf8Escaped);
@@ -174,8 +174,8 @@ public static bool TryDecodeBase64(ReadOnlySpan<byte> utf8Unescaped, [NotNullWhe
174174
{
175175
byte[]? pooledArray = null;
176176

177-
Span<byte> byteSpan = utf8Unescaped.Length <= JsonConstants.StackallocThreshold ?
178-
stackalloc byte[utf8Unescaped.Length] :
177+
Span<byte> byteSpan = utf8Unescaped.Length <= JsonConstants.StackallocByteThreshold ?
178+
stackalloc byte[JsonConstants.StackallocByteThreshold] :
179179
(pooledArray = ArrayPool<byte>.Shared.Rent(utf8Unescaped.Length));
180180

181181
OperationStatus status = Base64.DecodeFromUtf8(utf8Unescaped, byteSpan, out int bytesConsumed, out int bytesWritten);

src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ public static bool TryGetEscapedDateTime(ReadOnlySpan<byte> source, out DateTime
252252
Debug.Assert(backslash != -1);
253253

254254
Debug.Assert(source.Length <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength);
255-
Span<byte> sourceUnescaped = stackalloc byte[source.Length];
255+
Span<byte> sourceUnescaped = stackalloc byte[JsonConstants.MaximumEscapedDateTimeOffsetParseLength];
256256

257257
Unescape(source, sourceUnescaped, backslash, out int written);
258258
Debug.Assert(written > 0);
@@ -277,7 +277,7 @@ public static bool TryGetEscapedDateTimeOffset(ReadOnlySpan<byte> source, out Da
277277
Debug.Assert(backslash != -1);
278278

279279
Debug.Assert(source.Length <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength);
280-
Span<byte> sourceUnescaped = stackalloc byte[source.Length];
280+
Span<byte> sourceUnescaped = stackalloc byte[JsonConstants.MaximumEscapedDateTimeOffsetParseLength];
281281

282282
Unescape(source, sourceUnescaped, backslash, out int written);
283283
Debug.Assert(written > 0);
@@ -303,7 +303,7 @@ public static bool TryGetEscapedGuid(ReadOnlySpan<byte> source, out Guid value)
303303
int idx = source.IndexOf(JsonConstants.BackSlash);
304304
Debug.Assert(idx != -1);
305305

306-
Span<byte> utf8Unescaped = stackalloc byte[source.Length];
306+
Span<byte> utf8Unescaped = stackalloc byte[JsonConstants.MaximumEscapedGuidLength];
307307

308308
Unescape(source, utf8Unescaped, idx, out int written);
309309
Debug.Assert(written > 0);

src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,9 +540,9 @@ private bool ConsumeLiteralMultiSegment(ReadOnlySpan<byte> literal, JsonTokenTyp
540540

541541
private bool CheckLiteralMultiSegment(ReadOnlySpan<byte> span, ReadOnlySpan<byte> literal, out int consumed)
542542
{
543-
Debug.Assert(span.Length > 0 && span[0] == literal[0]);
543+
Debug.Assert(span.Length > 0 && span[0] == literal[0] && literal.Length <= JsonConstants.MaximumLiteralLength);
544544

545-
Span<byte> readSoFar = stackalloc byte[literal.Length];
545+
Span<byte> readSoFar = stackalloc byte[JsonConstants.MaximumLiteralLength];
546546
int written = 0;
547547

548548
long prevTotalConsumed = _totalConsumed;

src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ public bool ValueTextEquals(ReadOnlySpan<char> text)
513513

514514
int length = checked(text.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);
515515

516-
if (length > JsonConstants.StackallocThreshold)
516+
if (length > JsonConstants.StackallocByteThreshold)
517517
{
518518
otherUtf8TextArray = ArrayPool<byte>.Shared.Rent(length);
519519
otherUtf8Text = otherUtf8TextArray;
@@ -523,8 +523,8 @@ public bool ValueTextEquals(ReadOnlySpan<char> text)
523523
// Cannot create a span directly since it gets passed to instance methods on a ref struct.
524524
unsafe
525525
{
526-
byte* ptr = stackalloc byte[JsonConstants.StackallocThreshold];
527-
otherUtf8Text = new Span<byte>(ptr, JsonConstants.StackallocThreshold);
526+
byte* ptr = stackalloc byte[JsonConstants.StackallocByteThreshold];
527+
otherUtf8Text = new Span<byte>(ptr, JsonConstants.StackallocByteThreshold);
528528
}
529529
}
530530

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso
5454
int backslash = source.IndexOf(JsonConstants.BackSlash);
5555
Debug.Assert(backslash != -1);
5656

57-
Span<byte> sourceUnescaped = stackalloc byte[source.Length];
57+
Span<byte> sourceUnescaped = stackalloc byte[MaximumEscapedTimeSpanFormatLength];
5858

5959
JsonReaderHelper.Unescape(source, sourceUnescaped, backslash, out int written);
6060
Debug.Assert(written > 0);

0 commit comments

Comments
 (0)