From a4dbef6725b865b3b38a4d92331b640954a4869b Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 12 Aug 2017 04:15:51 -0300 Subject: [PATCH] Added support for escapable strings --- .../System/Text/Json/JsonReader.cs | 2 +- .../System/Text/Json/JsonWriter.cs | 112 ++++++++++++++++++--- 2 files changed, 101 insertions(+), 13 deletions(-) diff --git a/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonReader.cs b/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonReader.cs index 7d2981fab..a141f7e48 100644 --- a/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonReader.cs +++ b/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonReader.cs @@ -720,7 +720,7 @@ namespace System.Text.Json int doubleSize = _working.Free.Count * 2; int minNewSize = _working.Capacity + segmentLength; int newSize = minNewSize > doubleSize ? minNewSize : doubleSize; - var newArray = ArrayPool.Shared.Rent(minNewSize + _working.Count); + var newArray = ArrayPool.Shared.Rent(newSize); var oldArray = _working.Resize(newArray); ArrayPool.Shared.Return(oldArray); } diff --git a/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs b/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs index f031eb374..900dea56a 100644 --- a/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs +++ b/src/Discord.Net.Serialization/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Buffers; +using System.Collections.Sequences; using System.Runtime.CompilerServices; using System.Text.Formatting; @@ -14,6 +16,7 @@ namespace System.Text.Json int _indent; bool _firstItem; + ResizableArray _working; // These next 2 properties are used to check for whether we can take the fast path // for invariant UTF-8 or UTF-16 processing. Otherwise, we need to go through the @@ -33,6 +36,7 @@ namespace System.Text.Json _indent = -1; _firstItem = true; + _working = default; var symbolTable = output.SymbolTable; if (symbolTable == SymbolTable.InvariantUtf8) @@ -356,7 +360,6 @@ namespace System.Text.Json private void WriteQuotedString(string value) { WriteControl(JsonConstants.Quote); - // TODO: We need to handle escaping. Write(value.AsSpan()); WriteControl(JsonConstants.Quote); } @@ -423,25 +426,110 @@ namespace System.Text.Json if (UseFastUtf8) { - Span destination = _output.Buffer; + bool hasBackslashes = false; + for (int i = 0; i < value.Length; i++) + { + char c = value[i]; + if (c == '\\' || c == '"') + { + hasBackslashes = true; + break; + } + } - while (true) + if (!hasBackslashes) //Fast path { - var status = Encoders.Utf16.ToUtf8(source, destination, out int consumed, out int written); - if (status == Buffers.TransformationStatus.Done) + Span destination = _output.Buffer; + + while (true) { - _output.Advance(written); - return; + var status = Encoders.Utf16.ToUtf8(source, destination, out int consumed, out int written); + if (status == Buffers.TransformationStatus.Done) + { + _output.Advance(written); + return; + } + if (status == Buffers.TransformationStatus.DestinationTooSmall) + { + destination = EnsureBuffer(); + continue; + } + + // This is a failure due to bad input. This shouldn't happen under normal circumstances. + throw new FormatException(); } + } + else //Slow path + { + if (_working.Items == null) + _working = new ResizableArray(ArrayPool.Shared.Rent(128)); + else + _working.Clear(); - if (status == Buffers.TransformationStatus.DestinationTooSmall) + while (true) { - destination = EnsureBuffer(); - continue; + int outputWritten = 0; + var workingBuffer = _working.Free.AsSpan(); + var status = Encoders.Utf16.ToUtf8(source, _working.Free, out int consumed, out int workingWritten); + _working.Count += workingWritten; + if (status == Buffers.TransformationStatus.Done) + { + int start = 0; + int workingLength = _output.Buffer.Length; + for (int i = 0; i <= workingWritten; i++) + { + byte c = i != workingWritten ? _working[i] : (byte)0; + + //Search for next backslash/quote or end of string + bool isEscapable = c == JsonConstants.Backslash || c == JsonConstants.Quote; + if (isEscapable || i == workingWritten) + { + int segmentLength = i - start; + + //Ensure buffer length + var destination = _output.Buffer; + if (destination.Length <= segmentLength) + { + workingLength += segmentLength; + _output.Enlarge(workingLength); + destination = _output.Buffer; + } + + //Write data before escapable char + if (segmentLength != 0) + { + workingBuffer.Slice(start, segmentLength).CopyTo(destination); //TODO: Optimize w/ ref ptrs + start = i; + outputWritten += segmentLength; + _output.Advance(segmentLength); + } + + //Write backslash + if (isEscapable) + { + destination[segmentLength] = JsonConstants.Backslash; + outputWritten++; + _output.Advance(1); + } + } + } + return; + } + if (status == Buffers.TransformationStatus.DestinationTooSmall) + { + int newSize = _working.Free.Count * 2; + var newArray = ArrayPool.Shared.Rent(newSize); + var oldArray = _working.Resize(newArray); + ArrayPool.Shared.Return(oldArray); + + workingBuffer = _working.Free.AsSpan(); + continue; + } + + // This is a failure due to bad input. This shouldn't happen under normal circumstances. + throw new FormatException(); } - // This is a failure due to bad input. This shouldn't happen under normal circumstances. - throw new FormatException(); } } else if (UseFastUtf16)