diff --git a/Src/Newtonsoft.Json.Tests/JsonTextWriterAsyncTests.cs b/Src/Newtonsoft.Json.Tests/JsonTextWriterAsyncTests.cs index 21e92f28..88017a75 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextWriterAsyncTests.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextWriterAsyncTests.cs @@ -1770,6 +1770,68 @@ null//comment Assert.IsTrue(writer.WriteValueAsync(default(ushort?), token).IsCanceled); Assert.IsTrue(writer.WriteWhitespaceAsync(" ", token).IsCanceled); } + + [Test] + public async Task FailureOnStartWriteProperty() + { + JsonTextWriter writer = new JsonTextWriter(new ThrowingWriter(' ')); + writer.Formatting = Formatting.Indented; + await writer.WriteStartObjectAsync(); + await ExceptionAssert.ThrowsAsync(async () => await writer.WritePropertyNameAsync("aa")); + } + + + [Test] + public async Task FailureOnStartWriteObject() + { + JsonTextWriter writer = new JsonTextWriter(new ThrowingWriter('{')); + await ExceptionAssert.ThrowsAsync(async () => await writer.WriteStartObjectAsync()); + } + + public class ThrowingWriter : TextWriter + { + // allergic to certain characters, this null-stream writer throws on any attempt to write them. + + private char[] _singleCharBuffer = new char[1]; + + public ThrowingWriter(params char[] throwChars) + { + ThrowChars = throwChars; + } + + public char[] ThrowChars { get; set; } + + public override Encoding Encoding => Encoding.UTF8; + + public override Task WriteAsync(char value) + { + _singleCharBuffer[0] = value; + return WriteAsync(_singleCharBuffer, 0, 1); + } + + public override Task WriteAsync(char[] buffer, int index, int count) + { + if (buffer.Skip(index).Take(count).Any(c => ThrowChars.Contains(c))) + { + // Pre-4.6 equivalent to .FromException() + TaskCompletionSource tcs = new TaskCompletionSource(); + tcs.SetException(new InvalidOperationException()); + return tcs.Task; + } + + return Task.Delay(0); + } + + public override Task WriteAsync(string value) + { + return WriteAsync(value.ToCharArray(), 0, value.Length); + } + + public override void Write(char value) + { + throw new NotImplementedException(); + } + } } public class CustomAsyncJsonTextWriter : CustomJsonTextWriter diff --git a/Src/Newtonsoft.Json/JsonTextWriter.Async.cs b/Src/Newtonsoft.Json/JsonTextWriter.Async.cs index c1588233..49bac08b 100644 --- a/Src/Newtonsoft.Json/JsonTextWriter.Async.cs +++ b/Src/Newtonsoft.Json/JsonTextWriter.Async.cs @@ -186,9 +186,13 @@ namespace Newtonsoft.Json private Task WriteValueInternalAsync(JsonToken token, string value, CancellationToken cancellationToken) { Task task = InternalWriteValueAsync(token, cancellationToken); - if (task.IsCompleted) + switch (task.Status) { - return _writer.WriteAsync(value, cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return _writer.WriteAsync(value, cancellationToken); } return WriteValueInternalAsync(task, value, cancellationToken); @@ -266,9 +270,13 @@ namespace Newtonsoft.Json private Task WriteIntegerValueAsync(ulong uvalue, bool negative, CancellationToken cancellationToken) { Task task = InternalWriteValueAsync(JsonToken.Integer, cancellationToken); - if (task.IsCompleted) + switch (task.Status) { - return WriteDigitsAsync(uvalue, negative, cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return WriteDigitsAsync(uvalue, negative, cancellationToken); } return WriteIntegerValueAsync(task, uvalue, negative, cancellationToken); @@ -317,18 +325,30 @@ namespace Newtonsoft.Json internal Task DoWritePropertyNameAsync(string name, CancellationToken cancellationToken) { Task task = InternalWritePropertyNameAsync(name, cancellationToken); - if (!task.IsCompleted) + switch (task.Status) { - return DoWritePropertyNameAsync(task, name, cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + // Completed synchronously (or very fast!), continue within this rather + // that the default branch which uses async + break; + default: + return DoWritePropertyNameAsync(task, name, cancellationToken); } task = WriteEscapedStringAsync(name, _quoteName, cancellationToken); - if (!task.IsCompleted) + switch (task.Status) { - return JavaScriptUtils.WriteCharAsync(task, _writer, ':', cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return _writer.WriteAsync(':', cancellationToken); } - return _writer.WriteAsync(':', cancellationToken); + return JavaScriptUtils.WriteCharAsync(task, _writer, ':', cancellationToken); } private async Task DoWritePropertyNameAsync(Task task, string name, CancellationToken cancellationToken) @@ -395,9 +415,13 @@ namespace Newtonsoft.Json internal Task DoWriteStartArrayAsync(CancellationToken cancellationToken) { Task task = InternalWriteStartAsync(JsonToken.StartArray, JsonContainerType.Array, cancellationToken); - if (task.IsCompleted) + switch (task.Status) { - return _writer.WriteAsync('[', cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return _writer.WriteAsync('[', cancellationToken); } return DoWriteStartArrayAsync(task, cancellationToken); @@ -425,9 +449,13 @@ namespace Newtonsoft.Json internal Task DoWriteStartObjectAsync(CancellationToken cancellationToken) { Task task = InternalWriteStartAsync(JsonToken.StartObject, JsonContainerType.Object, cancellationToken); - if (task.IsCompleted) + switch (task.Status) { - return _writer.WriteAsync('{', cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return _writer.WriteAsync('{', cancellationToken); } return DoWriteStartObjectAsync(task, cancellationToken); @@ -477,9 +505,13 @@ namespace Newtonsoft.Json internal Task DoWriteUndefinedAsync(CancellationToken cancellationToken) { Task task = InternalWriteValueAsync(JsonToken.Undefined, cancellationToken); - if (task.IsCompleted) + switch (task.Status) { - return _writer.WriteAsync(JsonConvert.Undefined, cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return _writer.WriteAsync(JsonConvert.Undefined, cancellationToken); } return DoWriteUndefinedAsync(task, cancellationToken); @@ -1054,9 +1086,13 @@ namespace Newtonsoft.Json internal Task DoWriteValueAsync(string value, CancellationToken cancellationToken) { Task task = InternalWriteValueAsync(JsonToken.String, cancellationToken); - if (task.IsCompleted) + switch (task.Status) { - return value == null ? _writer.WriteAsync(JsonConvert.Null, cancellationToken) : WriteEscapedStringAsync(value, true, cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return value == null ? _writer.WriteAsync(JsonConvert.Null, cancellationToken) : WriteEscapedStringAsync(value, true, cancellationToken); } return DoWriteValueAsync(task, value, cancellationToken); @@ -1189,9 +1225,13 @@ namespace Newtonsoft.Json internal Task WriteValueNotNullAsync(Uri value, CancellationToken cancellationToken) { Task task = InternalWriteValueAsync(JsonToken.String, cancellationToken); - if (task.IsCompleted) + switch (task.Status) { - return WriteEscapedStringAsync(value.OriginalString, true, cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return WriteEscapedStringAsync(value.OriginalString, true, cancellationToken); } return WriteValueNotNullAsync(task, value, cancellationToken); @@ -1310,9 +1350,13 @@ namespace Newtonsoft.Json { UpdateScopeWithFinishedValue(); Task task = AutoCompleteAsync(JsonToken.Undefined, cancellationToken); - if (task.IsCompleted) + switch (task.Status) { - return WriteRawAsync(json, cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return WriteRawAsync(json, cancellationToken); } return DoWriteRawValueAsync(task, json, cancellationToken); diff --git a/Src/Newtonsoft.Json/Linq/JProperty.Async.cs b/Src/Newtonsoft.Json/Linq/JProperty.Async.cs index 7e90bda4..35c76517 100644 --- a/Src/Newtonsoft.Json/Linq/JProperty.Async.cs +++ b/Src/Newtonsoft.Json/Linq/JProperty.Async.cs @@ -44,12 +44,16 @@ namespace Newtonsoft.Json.Linq public override Task WriteToAsync(JsonWriter writer, CancellationToken cancellationToken, params JsonConverter[] converters) { Task task = writer.WritePropertyNameAsync(_name, cancellationToken); - if (!task.IsCompleted) + switch (task.Status) { - return WriteToAsync(task, writer, cancellationToken, converters); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return WriteValueAsync(writer, cancellationToken, converters); } - return WriteValueAsync(writer, cancellationToken, converters); + return WriteToAsync(task, writer, cancellationToken, converters); } private async Task WriteToAsync(Task task, JsonWriter writer, CancellationToken cancellationToken, params JsonConverter[] converters) diff --git a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs index b017ad4a..841ff69d 100644 --- a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs @@ -381,21 +381,34 @@ namespace Newtonsoft.Json.Utilities bool[] charEscapeFlags, StringEscapeHandling stringEscapeHandling, JsonTextWriter client, char[] writeBuffer, CancellationToken cancellationToken) { Task task = writer.WriteAsync(delimiter, cancellationToken); - if (!task.IsCompleted) + switch (task.Status) { - return WriteEscapedJavaScriptStringWithDelimitersAsync(task, writer, s, delimiter, charEscapeFlags, stringEscapeHandling, client, writeBuffer, cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + // Completed synchronously (or very fast!), continue within this rather + // that the default branch which uses async + break; + default: + return WriteEscapedJavaScriptStringWithDelimitersAsync(task, writer, s, delimiter, charEscapeFlags, stringEscapeHandling, client, writeBuffer, cancellationToken); } if (!string.IsNullOrEmpty(s)) { task = WriteEscapedJavaScriptStringWithoutDelimitersAsync(writer, s, charEscapeFlags, stringEscapeHandling, client, writeBuffer, cancellationToken); - if (!task.IsCompleted) + switch (task.Status) { - return WriteCharAsync(task, writer, delimiter, cancellationToken); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + return task; + case TaskStatus.RanToCompletion: + return writer.WriteAsync(delimiter, cancellationToken); } } - return writer.WriteAsync(delimiter, cancellationToken); + return WriteCharAsync(task, writer, delimiter, cancellationToken); + } private static async Task WriteEscapedJavaScriptStringWithDelimitersAsync(Task task, TextWriter writer, string s, char delimiter,