diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs index 977afdad20e829..de1afd6419f45c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs @@ -539,31 +539,38 @@ private unsafe bool CheckLiteralMultiSegment(ReadOnlySpan span, ReadOnlySp { Debug.Assert(span.Length > 0 && span[0] == literal[0] && literal.Length <= JsonConstants.MaximumLiteralLength); + // The exception message quotes the literal's bytes up to and including the first mismatch, + // or all of them if the data ends first, so at most literal.Length bytes are ever written. Span readSoFar = stackalloc byte[JsonConstants.MaximumLiteralLength]; - int written = 0; + int written; long prevTotalConsumed = _totalConsumed; + int prevConsumed = _consumed; SequencePosition copy = _currentPosition; if (span.Length >= literal.Length || IsLastSpan) { - _bytePositionInLine += FindMismatch(span, literal); + int indexOfFirstMismatch = FindMismatch(span, literal); + _bytePositionInLine += indexOfFirstMismatch; - int amountToWrite = AmountToWrite(span, _bytePositionInLine, readSoFar, written); - span.Slice(0, amountToWrite).CopyTo(readSoFar); - written += amountToWrite; + written = Math.Min(span.Length, indexOfFirstMismatch + 1); + span.Slice(0, written).CopyTo(readSoFar); goto Throw; } else { if (!literal.StartsWith(span)) { - _bytePositionInLine += FindMismatch(span, literal); - int amountToWrite = AmountToWrite(span, _bytePositionInLine, readSoFar, written); - span.Slice(0, amountToWrite).CopyTo(readSoFar); - written += amountToWrite; + int indexOfFirstMismatch = FindMismatch(span, literal); + _bytePositionInLine += indexOfFirstMismatch; + + written = indexOfFirstMismatch + 1; + span.Slice(0, written).CopyTo(readSoFar); goto Throw; } + span.CopyTo(readSoFar); + written = span.Length; + ReadOnlySpan leftToMatch = literal.Slice(span.Length); SequencePosition startPosition = _currentPosition; @@ -585,10 +592,6 @@ private unsafe bool CheckLiteralMultiSegment(ReadOnlySpan span, ReadOnlySp return false; } - int amountToWrite = Math.Min(span.Length, readSoFar.Length - written); - span.Slice(0, amountToWrite).CopyTo(readSoFar.Slice(written)); - written += amountToWrite; - span = _buffer; if (span.StartsWith(leftToMatch)) @@ -603,28 +606,25 @@ private unsafe bool CheckLiteralMultiSegment(ReadOnlySpan span, ReadOnlySp if (!leftToMatch.StartsWith(span)) { - _bytePositionInLine += FindMismatch(span, leftToMatch); - - amountToWrite = AmountToWrite(span, _bytePositionInLine, readSoFar, written); - span.Slice(0, amountToWrite).CopyTo(readSoFar.Slice(written)); - written += amountToWrite; + int indexOfFirstMismatch = FindMismatch(span, leftToMatch); + _bytePositionInLine += indexOfFirstMismatch; + span.Slice(0, indexOfFirstMismatch + 1).CopyTo(readSoFar.Slice(written)); + written += indexOfFirstMismatch + 1; goto Throw; } + span.CopyTo(readSoFar.Slice(written)); + written += span.Length; + leftToMatch = leftToMatch.Slice(span.Length); alreadyMatched = span.Length; } } - static int AmountToWrite(ReadOnlySpan span, long bytePositionInLine, ReadOnlySpan readSoFar, int written) - { - return Math.Min( - readSoFar.Length - written, - Math.Min(span.Length, (int)bytePositionInLine + 1)); - } Throw: _totalConsumed = prevTotalConsumed; + _consumed = prevConsumed; consumed = default; _currentPosition = copy; throw GetInvalidLiteralMultiSegment(readSoFar.Slice(0, written).ToArray()); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs index d1ba71625b124b..6da539fd4e30cb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs @@ -1195,7 +1195,7 @@ private bool CheckLiteral(ReadOnlySpan span, ReadOnlySpan literal) if (span[i] != literal[i]) { _bytePositionInLine += i; - ThrowInvalidLiteral(span); + ThrowInvalidLiteral(span.Slice(0, i + 1)); } } else diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonReaderTests.MultiSegment.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonReaderTests.MultiSegment.cs index f6a3ed4d4416a4..d9aebc83cf5cdf 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonReaderTests.MultiSegment.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonReaderTests.MultiSegment.cs @@ -1914,6 +1914,73 @@ public static void MultiSegmentInvalidLiteralInObject() }); } + [Theory] + [InlineData("t", 1, 0, "t")] + [InlineData("nul", 3, 0, "nul")] + [InlineData("fals", 4, 0, "fals")] + [InlineData("[tru", 4, 1, "tru")] + [InlineData("tb:", 1, 0, "tb")] + [InlineData("f-2.2e-2,-", 1, 0, "f-")] + [InlineData("[nulL]", 4, 1, "nulL")] + [InlineData("[falsX]", 5, 1, "falsX")] + [InlineData("[[n{\"a\":", 3, 2, "n{")] + [InlineData("{\"\":tr", 6, 4, "tr")] + [InlineData("[ trux]", 10, 7, "trux")] + public static void InvalidLiteralVariousSegmentSizes(string input, int expectedBytePositionInLine, int expectedConsumed, string expectedLiteral) + { + byte[] utf8 = Encoding.UTF8.GetBytes(input); + + var jsonReader = new Utf8JsonReader(utf8); + InvalidReadLiteralHelper(ref jsonReader, sequence: null, expectedBytePositionInLine, expectedConsumed, expectedLiteral); + + var sequence = new ReadOnlySequence(utf8); + jsonReader = new Utf8JsonReader(sequence); + InvalidReadLiteralHelper(ref jsonReader, sequence, expectedBytePositionInLine, expectedConsumed, expectedLiteral); + + sequence = JsonTestHelper.GetSequence(utf8, 1); + jsonReader = new Utf8JsonReader(sequence); + InvalidReadLiteralHelper(ref jsonReader, sequence, expectedBytePositionInLine, expectedConsumed, expectedLiteral); + + for (int splitLocation = 0; splitLocation <= utf8.Length; splitLocation++) + { + sequence = JsonTestHelper.CreateSegments(utf8, splitLocation); + jsonReader = new Utf8JsonReader(sequence); + InvalidReadLiteralHelper(ref jsonReader, sequence, expectedBytePositionInLine, expectedConsumed, expectedLiteral); + } + + for (int firstSplit = 0; firstSplit <= utf8.Length; firstSplit++) + { + for (int secondSplit = firstSplit; secondSplit <= utf8.Length; secondSplit++) + { + sequence = JsonTestHelper.CreateSegments(utf8, firstSplit, secondSplit); + jsonReader = new Utf8JsonReader(sequence); + InvalidReadLiteralHelper(ref jsonReader, sequence, expectedBytePositionInLine, expectedConsumed, expectedLiteral); + } + } + } + + private static void InvalidReadLiteralHelper(ref Utf8JsonReader jsonReader, ReadOnlySequence? sequence, int expectedBytePositionInLine, int expectedConsumed, string expectedLiteral) + { + JsonException ex = JsonTestHelper.AssertThrows(ref jsonReader, (ref jsonReader) => + { + while (jsonReader.Read()) + ; + }); + + Assert.Equal(0, ex.LineNumber); + Assert.Equal(expectedBytePositionInLine, ex.BytePositionInLine); + Assert.Equal(expectedConsumed, jsonReader.BytesConsumed); + if (sequence is ReadOnlySequence input) + { + Assert.Equal(expectedConsumed, input.Slice(0, jsonReader.Position).Length); + } + else + { + Assert.Equal(default, jsonReader.Position); + } + Assert.Contains($"'{expectedLiteral}' ", ex.Message); + } + [Theory] [MemberData(nameof(JsonTokenWithExtraValueAndComments))] public static void ReadJsonTokenWithExtraValueAndCommentsMultiSegment(string jsonString)