Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

[release/3.0] Avoid MemoryMarshal.Cast when transcoding from UTF-16 to UTF-8 while escaping in Utf8JsonWriter. #40997

Merged
merged 5 commits into from
Sep 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,18 @@ public static int NeedsEscaping(ReadOnlySpan<byte> value, JavaScriptEncoder enco
return idx;
}

public static int NeedsEscaping(ReadOnlySpan<char> value, JavaScriptEncoder encoder)
public static unsafe int NeedsEscaping(ReadOnlySpan<char> value, JavaScriptEncoder encoder)
{
int idx;

if (encoder != null)
// Some implementations of JavascriptEncoder.FindFirstCharacterToEncode may not accept
// null pointers and gaurd against that. Hence, check up-front and fall down to return -1.
if (encoder != null && !value.IsEmpty)
{
idx = encoder.FindFirstCharacterToEncodeUtf8(MemoryMarshal.Cast<char, byte>(value));
fixed (char* ptr = value)
{
idx = encoder.FindFirstCharacterToEncode(ptr, value.Length);
}
goto Return;
}

Expand Down
12 changes: 12 additions & 0 deletions src/System.Text.Json/tests/Serialization/Value.WriteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ public static void WriteStringWithRelaxedEscaper()
Assert.NotEqual(expected, JsonSerializer.Serialize(inputString));
}

// https://github.com/dotnet/corefx/issues/40979
[Fact]
public static void EscapingShouldntStackOverflow_40979()
{
var test = new { Name = "\u6D4B\u8A6611" };

var options = new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
string result = JsonSerializer.Serialize(test, options);

Assert.Equal("{\"name\":\"\u6D4B\u8A6611\"}", result);
}

[Fact]
public static void WritePrimitives()
{
Expand Down
122 changes: 122 additions & 0 deletions src/System.Text.Json/tests/Utf8JsonWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,128 @@ public void CantWriteToNonWritableStream(bool formatted, bool skipValidation)
Assert.Throws<ArgumentException>(() => new Utf8JsonWriter(stream, options));
}

[Fact]
public static void WritingNullStringsWithCustomEscaping()
{
var writerOptions = new JsonWriterOptions();
WriteNullStringsHelper(writerOptions);

writerOptions = new JsonWriterOptions { Encoder = JavaScriptEncoder.Default };
WriteNullStringsHelper(writerOptions);

writerOptions = new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
WriteNullStringsHelper(writerOptions);
}

[Fact]
public static void WritingNullStringsWithBuggyJavascriptEncoder()
{
var writerOptions = new JsonWriterOptions { Encoder = new BuggyJavaScriptEncoder() };
WriteNullStringsHelper(writerOptions);
}

private static void WriteNullStringsHelper(JsonWriterOptions writerOptions)
{
var output = new ArrayBufferWriter<byte>();
string str = null;

using (var writer = new Utf8JsonWriter(output, writerOptions))
{
writer.WriteStringValue(str);
}
JsonTestHelper.AssertContents("null", output);

output.Clear();
using (var writer = new Utf8JsonWriter(output, writerOptions))
{
writer.WriteStringValue(str.AsSpan());
}
JsonTestHelper.AssertContents("\"\"", output);

byte[] utf8Str = null;
output.Clear();
using (var writer = new Utf8JsonWriter(output, writerOptions))
{
writer.WriteStringValue(utf8Str.AsSpan());
}
JsonTestHelper.AssertContents("\"\"", output);

JsonEncodedText jsonText = JsonEncodedText.Encode(utf8Str.AsSpan());
output.Clear();
using (var writer = new Utf8JsonWriter(output, writerOptions))
{
writer.WriteStringValue(jsonText);
}
JsonTestHelper.AssertContents("\"\"", output);
}

public class BuggyJavaScriptEncoder : JavaScriptEncoder
{
public override int MaxOutputCharactersPerInputCharacter => throw new NotImplementedException();

public override unsafe int FindFirstCharacterToEncode(char* text, int textLength)
{
// Access the text pointer even though it might be null and text length is 0.
return *text;
}

public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten)
{
numberOfCharactersWritten = 0;
return false;
}

public override bool WillEncode(int unicodeScalar) => false;
}

[Fact]
public static void WritingStringsWithCustomEscaping()
{
var output = new ArrayBufferWriter<byte>();
var writerOptions = new JsonWriterOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };

using (var writer = new Utf8JsonWriter(output))
{
writer.WriteStringValue("\u6D4B\u8A6611");
}
JsonTestHelper.AssertContents("\"\\u6D4B\\u8A6611\"", output);

output.Clear();
using (var writer = new Utf8JsonWriter(output, writerOptions))
{
writer.WriteStringValue("\u6D4B\u8A6611");
}
JsonTestHelper.AssertContents("\"\u6D4B\u8A6611\"", output);

output.Clear();
using (var writer = new Utf8JsonWriter(output))
{
writer.WriteStringValue("\u00E9\"");
}
JsonTestHelper.AssertContents("\"\\u00E9\\u0022\"", output);

output.Clear();
using (var writer = new Utf8JsonWriter(output, writerOptions))
{
writer.WriteStringValue("\u00E9\"");
}
JsonTestHelper.AssertContents("\"\u00E9\\\"\"", output);

output.Clear();
using (var writer = new Utf8JsonWriter(output))
{
writer.WriteStringValue("\u2020\"");
}
JsonTestHelper.AssertContents("\"\\u2020\\u0022\"", output);

output.Clear();
using (var writer = new Utf8JsonWriter(output, writerOptions))
{
writer.WriteStringValue("\u2020\"");
}
JsonTestHelper.AssertContents("\"\u2020\\\"\"", output);
}

[Fact]
public void WriteJsonWritesToIBWOnDemand_Dispose()
{
Expand Down