Apologies for the lack of concrete repro or not being able to provide the _exact_ object with values that was being serialized, but we're running a canary of ASP.NET Core 3.0 preview 8 in our production environment and just observed the following exception from an HTTP PUT operation being made:
System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
at Span<T> System.Span<T>.Slice(int start, int length)
at void System.Text.Json.JsonWriterHelper.EscapeString(ReadOnlySpan<char> value, Span<char> destination, JavaScriptEncoder encoder, ref int written)
at void System.Text.Json.JsonWriterHelper.EscapeString(ReadOnlySpan<char> value, Span<char> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int written)
at void System.Text.Json.Utf8JsonWriter.WriteStringEscapeValue(ReadOnlySpan<char> value, int firstEscapeIndexVal)
at void System.Text.Json.Utf8JsonWriter.WriteStringEscape(ReadOnlySpan<char> value)
at void System.Text.Json.Utf8JsonWriter.WriteStringValue(ReadOnlySpan<char> value)
at void System.Text.Json.Serialization.Converters.JsonConverterString.Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
at void System.Text.Json.JsonPropertyInfoNotNullable<TClass, TDeclaredProperty, TRuntimeProperty, TConverter>.OnWrite(ref WriteStackFrame current, Utf8JsonWriter writer)
at void System.Text.Json.JsonPropertyInfo.Write(ref WriteStack state, Utf8JsonWriter writer)
at bool System.Text.Json.JsonSerializer.HandleObject(JsonPropertyInfo jsonPropertyInfo, JsonSerializerOptions options, Utf8JsonWriter writer, ref WriteStack state)
at bool System.Text.Json.JsonSerializer.WriteObject(JsonSerializerOptions options, Utf8JsonWriter writer, ref WriteStack state)
at bool System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, int originalWriterDepth, int flushThreshold, JsonSerializerOptions options, ref WriteStack state)
at void System.Text.Json.JsonSerializer.WriteCore(Utf8JsonWriter writer, PooledByteBufferWriter output, object value, Type type, JsonSerializerOptions options)
at void System.Text.Json.JsonSerializer.WriteCore(PooledByteBufferWriter output, object value, Type type, JsonSerializerOptions options)
at string System.Text.Json.JsonSerializer.WriteCoreString(object value, Type type, JsonSerializerOptions options)
at Task<HttpContent> JustEat.HttpClientFactory.Extensions.SystemTextJsonContentSerializer.SerializeAsync<T>(T item)
The call stack appears to have been inlined/optimized to some degree, so here is the source code of the IContentSerializer implementation we're using with Refit that calls into System.Text.Json's serializer:
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Refit;
namespace JustEat.HttpClientFactory.Extensions
{
public sealed class SystemTextJsonContentSerializer : IContentSerializer
{
public SystemTextJsonContentSerializer(JsonSerializerOptions serializerOptions)
{
SerializerOptions = serializerOptions ?? throw new ArgumentNullException(nameof(serializerOptions));
}
private JsonSerializerOptions SerializerOptions { get; }
public async Task<T> DeserializeAsync<T>(HttpContent content)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
using var utf8Json = await content.ReadAsStreamAsync().ConfigureAwait(false);
return await JsonSerializer.DeserializeAsync<T>(utf8Json, SerializerOptions);
}
public Task<HttpContent> SerializeAsync<T>(T item)
{
string json = JsonSerializer.Serialize(item, SerializerOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
return Task.FromResult<HttpContent>(content);
}
}
}
Below is the definition of the POCO being serialized, where both sets of serialization attributes are in use to support using either serializer for a transition period:
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace JustEat.MyApp.Providers.Rating
{
public sealed class SubmitReviewRequest
{
[JsonProperty("RatingCode")]
[JsonPropertyName("RatingCode")]
public string RatingCode { get; set; }
[JsonProperty("ConsumerId")]
[JsonPropertyName("ConsumerId")]
public int? ConsumerId { get; set; }
[JsonProperty("OrderId")]
[JsonPropertyName("OrderId")]
public string OrderId { get; set; }
[JsonProperty("CustomerComments")]
[JsonPropertyName("CustomerComments")]
public string CustomerComments { get; set; }
[JsonProperty("Delivery")]
[JsonPropertyName("Delivery")]
public int Delivery { get; set; }
[JsonProperty("Quality")]
[JsonPropertyName("Quality")]
public int Quality { get; set; }
[JsonProperty("Service")]
[JsonPropertyName("Service")]
public int Service { get; set; }
}
}
Let me know if there's any additional context I could provide that would help root-cause this.
/cc @steveharter @layomia
I've got a repro locally, I'm seeing if I can minimise down the real payload into something I can add here.
Here's a fairly minimal repro. It can probably be made more minimal but the issue appears to be a combination of the length and content of the string. I've replaced all [Aa-Zz] characters with 'x'.
Three interesting things about the interesting value:
’ (text was entered on an iPhone).Reducing the length of the string appears to resolve the issue, as does removing the ’ character and increasing the string's length by adding 43 'x' characters to the end of the string.
using System.Text.Json;
using Xunit;
namespace JsonSerializeArgumentOutOfRangeRepro
{
public static class Repro
{
[Theory]
[InlineData("xxxx xxx xx xxxxxx xx xxxx xxx xxx xxxxxxxxxx xx xxxx xxx xxxx. xx xxx xxxx xx xxxx xxxx xxx xxxxxxx xx xxxx. xxxxxxx xxxx xxx xxx xxxxxxxx xx xxxx xxx xxxxxxx xxxx. xxxxxxxx xxxx xxx. xx xxxxxx xxxxx xxxx xxxx xxx xxxxx. xxxxxxxx xxx xxxx xxx xxxx xxxx x xxxxx xxxxxx xxxx xx xx xxxxx xxx xxxxx xx xxxx xxx xxxxx xx xxxx xxxx xxxx xxx xxxxxx xxxxxx, xxxxxxxxxx xx xxxxx xxxxxx xxx xxxx xxxxxxxxx xx xxxxx xxxx xx xxxx xxx. xxx xxxxx xxxxxx xxxx xxxxxx xxxxxxx xx xxx’xx xxxxxx x xxxxxxx xxx xxxxx xxxxxxxx. ")] // Fails
[InlineData("xxxx xxx xx xxxxxx xx xxxx xxx xxx xxxxxxxxxx xx xxxx xxx xxxx. xx xxx xxxx xx xxxx xxxx xxx xxxxxxx xx xxxx. xxxxxxx xxxx xxx xxx xxxxxxxx xx xxxx xxx xxxxxxx xxxx. xxxxxxxx xxxx xxx. xx xxxxxx xxxxx xxxx xxxx xxx xxxxx. xxxxxxxx xxx xxxx xxx xxxx xxxx x xxxxx xxxxxx xxxx xx xx xxxxx xxx xxxxx xx xxxx xxx xxxxx xx xxxx xxxx xxxx xxx xxxxxx xxxxxx, xxxxxxxxxx xx xxxxx xxxxxx xxx xxxx xxxxxxxxx xx xxxxx xxxx xx xxxx xxx. xxx xxxxx xxxxxx xxxx xxxxxx xxxxxxx xx xxx’xx xxxxxx x xxxxxxx xxx xxxxx xxxxxxxx. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")] // 42 extra x's and still fails
[InlineData("xxxx xxx xx xxxxxx xx xxxx xxx xxx xxxxxxxxxx xx xxxx xxx xxxx. xx xxx xxxx xx xxxx xxxx xxx xxxxxxx xx xxxx. xxxxxxx xxxx xxx xxx xxxxxxxx xx xxxx xxx xxxxxxx xxxx. xxxxxxxx xxxx xxx. xx xxxxxx xxxxx xxxx xxxx xxx xxxxx. xxxxxxxx xxx xxxx xxx xxxx xxxx x xxxxx xxxxxx xxxx xx xx xxxxx xxx xxxxx xx xxxx xxx xxxxx xx xxxx xxxx xxxx xxx xxxxxx xxxxxx, xxxxxxxxxx xx xxxxx xxxxxx xxx xxxx xxxxxxxxx xx xxxxx xxxx xx xxxx xxx. xxx xxxxx xxxxxx xxxx xxxxxx xxxxxxx xx xxx’xx xxxxxx x xxxxxxx xxx xxxxx xxxxxxxx. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")] // 43 extra x's and passes
[InlineData("xxxx xxx xx xxxxxx xx xxxx xxx xxx xxxxxxxxxx xx xxxx xxx xxxx. xx xxx xxxx xx xxxx xxxx xxx xxxxxxx xx xxxx. xxxxxxx xxxx xxx xxx xxxxxxxx xx xxxx xxx xxxxxxx xxxx. xxxxxxxx xxxx xxx. xx xxxxxx xxxxx xxxx xxxx xxx xxxxx. xxxxxxxx xxx xxxx xxx xxxx xxxx x xxxxx xxxxxx xxxx xx xx xxxxx xxx xxxxx xx xxxx xxx xxxxx xx xxxx xxxx xxxx xxx xxxxxx xxxxxx, xxxxxxxxxx xx xxxxx xxxxxx xxx xxxx xxxxxxxxx xx xxxxx xxxx xx xxxx xxx. xxx xxxxx xxxxxx xxxx xxxxxx xxxxxxx xx xxx’xx xxxxxx x xxxxxxx xxx xxxxx xxxxxxxx.")] // No trailing space and passes
[InlineData("xxxx xxx xx xxxxxx xx xxxx xxx xxx xxxxxxxxxx xx xxxx xxx xxxx. xx xxx xxxx xx xxxx xxxx xxx xxxxxxx xx xxxx. xxxxxxx xxxx xxx xxx xxxxxxxx xx xxxx xxx xxxxxxx xxxx. xxxxxxxx xxxx xxx. xx xxxxxx xxxxx xxxx xxxx xxx xxxxx. xxxxxxxx xxx xxxx xxx xxxx xxxx x xxxxx xxxxxx xxxx xx xx xxxxx xxx xxxxx xx xxxx xxx xxxxx xx xxxx xxxx xxxx xxx xxxxxx xxxxxx, xxxxxxxxxx xx xxxxx xxxxxx xxx xxxx xxxxxxxxx xx xxxxx xxxx xx xxxx xxx. xxx xxxxx xxxxxx xxxx xxxxxx xxxxxxx xx xxx'xx xxxxxx x xxxxxxx xxx xxxxx xxxxxxxx. ")] // No smart quote and passes
public static void Serialize_Serializes_Object(string value)
{
// Arrange
var item = new Model()
{
Value = value,
};
// Act
string json = JsonSerializer.Serialize(item);
// Assert
Assert.NotNull(json);
}
public sealed class Model
{
public string Value { get; set; }
}
}
}
Looks like the exception is coming from here when trying to generate an exception message:
This is a duplicate of https://github.com/dotnet/corefx/issues/40412 / https://github.com/dotnet/corefx/issues/40409
@martincostello, please let me know if you can repro it in master or preview9.
https://github.com/dotnet/corefx/issues/40409#issuecomment-522514553
Can you try with preview 9 bits (or latest nightly)? It is no longer reproducible on master.
Please try the nightly SDK/runtime with the fix to verify it works (https://github.com/dotnet/core-sdk#installers-and-binaries), or wait till preview 9 ships. Alternatively, you could reference the latest S.T.Json NuGet package. For example:
<ItemGroup> <PackageReference Include="System.Text.Json" Version="4.6.0-preview9.19413.13" /> <PackageReference Include="System.Text.Encodings.Web" Version="4.6.0-preview9.19413.13" /> </ItemGroup>This bug was introduced in preview 8 but has since been fixed (preview 9 and in master). See #39524 (comment)
Here's a fairly minimal repro. It can probably be made more minimal but the issue appears to be a combination of the length and content of the string. I've replaced all
[Aa-Zz]characters with'x'.
Yep, it is difficult to hit that code path easily. As you found out, the bug is related to the Utf8JsonWriter doing the writing (that the serializer uses). It has to do with where the unicode character shows up and how much ascii text is before/after. We were slicing the array-pool backed buffer incorrectly before clearing. This code path no longer exists.
Apologies for the lack of concrete repro or not being able to provide the exact object with values that was being serialized, but we're running a canary of ASP.NET Core 3.0 preview 8 in our production environment and just observed the following exception from an HTTP PUT operation being made
No worries, @martincostello. Thanks for the detailed repro and initial analysis :)
I’ll validate the repro tomorrow with the original content from the latest 3.0 branch build, but won’t be able to try out in production again until preview 9 lands (unless the master builds are covered by go-live support, though I assume not).
@ahsonkhan Can confirm this no longer repros with 3.0.100-preview9-013959 of the SDK.
@martincostello thanks for the repro. Closing.