Fix Utf8JsonReader invalid-literal errors: unbounded message, dropped bytes, IndexOutOfRangeException - #134448
Open
caraioniurie47 wants to merge 1 commit into
Open
caraioniurie47 wants to merge 1 commit into
caraioniurie47 wants to merge 1 commit into
Conversation
When a true, false or null literal is invalid, the single-segment reader quoted the rest of the buffer in the exception message. The multi-segment reader dropped the last segment's bytes from the message when the data ended inside the literal, threw IndexOutOfRangeException instead of JsonException when nothing had been copied yet, and left BytesConsumed too low when the literal straddled a segment boundary. Quote the literal's bytes up to and including the first mismatch, or all of them when the data ends, in both readers; copy each matching span as soon as it matches; and restore _consumed before throwing. Contributes to dotnet#30706 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @dotnet/area-system-text-json |
This was referenced Sep 22, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When
Utf8JsonReaderfinds an invalidtrue,falseornull, the exception depends on how the input is segmented, and in some layouts it is not aJsonExceptionat all. Measured on .NET 10.0.12:byte[], or a one-segmentReadOnlySequence): the message quotes everything to the end of the buffer.CheckLiteralpassesThrowInvalidLiteralthe spanConsumeLiteralgave it,_buffer.Slice(_consumed), sotb:gives'tb:' is an invalid JSON literal, and a 1,000,007-byte payload starting[falsXgave a 1,000,105-character message.IndexOutOfRangeException, e.g. segments"t",""or"[","tr","".CheckLiteralMultiSegmentcopied a span into its message buffer only afterGetNextSpan()returned the next one, so nothing was copied, andGetInvalidLiteralMultiSegmentreadspan[0]of an empty span.falsread one byte per segment gives'fal'.BytesConsumedafter the exception can be too low when the literal straddles a segment boundary:[truas two segments split after[tor[trreports 0, while thebyte[]and the other splits that throw aJsonExceptionreport 1. The throw path restored_totalConsumedbut not_consumed, whichGetNextSpan()had reset.Change. Both paths now quote the literal's bytes up to and including the first mismatching byte, or every byte present when the data ends, so the quote is at most the literal's length and the same for every segmentation.
CheckLiteralMultiSegmentcopies each span's matching bytes as soon as they match, which removes theAmountToWritehelper, and restores_consumedbefore throwing. HowBytePositionInLineandLineNumberare computed is unchanged.This addresses the literal half of #30706. The comment-handling differences listed there (
{/,/+and others, withJsonCommentHandling.SkipandAllow) are not changed here and remain. Nothing is thrown that was not thrown before, apart fromJsonExceptionreplacingIndexOutOfRangeException; the observable differences are shorter messages, and a message,BytesConsumedandPositionthat are the same across segmentations.Tests
InvalidLiteralVariousSegmentSizesadded toUtf8JsonReaderTests.MultiSegment.cs, in the shape ofInvalidJsonNumberVariousSegmentSizes: 11 inputs, each read as abyte[], a one-segmentReadOnlySequence, one byte per segment (the helper adds an empty last segment), and every one- and two-splitReadOnlySequence, including splits at the end so an empty last segment is covered. Each read assertsLineNumber,BytePositionInLine,BytesConsumed,Positionand the quoted literal.IndexOutOfRangeExceptionfort,'nu','fal'and'tr'for the dropped bytes,'tb:'and'nulL]'for the whole-buffer quote. With it all 11 pass.System.Text.Json.Tests,System.Text.Json.SourceGeneration.Roslyn4.4.TestsandSystem.Text.Json.SourceGeneration.Roslyn3.11.Testspass in both target frameworks.Performance
BenchmarkDotNet 0.15.2, Windows 11 x64: a Release build of
mainand the same build with this change toSystem.Text.Json, each in its own Release testhost, compared with--coreRun. The benchmark reads an array of 3000 elements to the end:Literalsistrue,false,nullrepeated,Objectsis{"id":n,"active":true|false,"parent":null,"name":"item n"}. Segment size 0 is abyte[]; with 3 every literal straddles segments, the path whose copying changed. Three runs, one launch per build and then three, mean ± SD in µs:main(runs 1 / 2 / 3)By mean, the lower build changes between runs in every row except
Objects/3, where this branch is lower in all three runs by 0.6% to 3%.Objects/64, higher on this branch in runs 1 and 2, was rerun alone with twenty launches per build, once in each host order: this branch 485.7 ± 18.3 againstmain481.0 ± 19.4 µs, and 490.9 ± 16.5 against 491.0 ± 15.3 µs. Its spread is about 3.5% of the mean and neither build is consistently ahead. Thebyte[]rows, whose only change is in the throw path, move by up to 4% between builds within a run.Contributes to #30706
Note
AI-generated, written at my direction and reviewed by me before posting. Everything ran on Windows 11 x64 (build 26200). The measurements in the first list ran on the installed .NET 10.0.12 release with a scratch console app; the two crash inputs and the 1,000,105-character message were re-run under the Release
corerunofmainbuilt locally, with the same result, and under this branch's, where they giveJsonExceptionand a 104-character message.Positionafter the exception was measured the same way, on all three builds, for every two-segment split of[tru,[nulL],{"":trand[ trux]. The tests ran on Debug libraries with a Checked runtime; the benchmark as described under Performance.