Skip to content

Commit

Permalink
Avoid MemoryMarshal.Cast when transcoding from UTF-16 to UTF-8 while …
Browse files Browse the repository at this point in the history
…escaping in Utf8JsonWriter. (dotnet/corefx#40996)

* Avoid MemoryMarshal.Cast when transcoding from UTF-16 to UTF-8 while
escaping in Utf8JsonWriter.

* Fix white space typo in the test expected string.

* Guard against empty spans where an implementation of JavascriptEncoder
might not handle null ptrs correctly.

* Cleanup tests to avoid some duplication.

* Some more test clean up.


Commit migrated from dotnet/corefx@ee9995f
  • Loading branch information
ahsonkhan authored Sep 11, 2019
1 parent 0306cd7 commit ab16296
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 3 deletions.
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
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/libraries/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

0 comments on commit ab16296

Please sign in to comment.