Skip to content

Commit

Permalink
Fix polymorphic recursion condition when calling DeserializeAsyncEnum…
Browse files Browse the repository at this point in the history
…erable in NativeAOT.
  • Loading branch information
eiriktsarpalis committed Apr 21, 2023
1 parent f107b4b commit f10bc02
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 151 deletions.
4 changes: 0 additions & 4 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,6 @@ public static partial class JsonSerializer
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
public static System.Threading.Tasks.ValueTask<object?> DeserializeAsync(System.IO.Stream utf8Json, System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.ValueTask<object?> DeserializeAsync(System.IO.Stream utf8Json, System.Type returnType, System.Text.Json.Serialization.JsonSerializerContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Collections.Generic.IAsyncEnumerable<object?> DeserializeAsyncEnumerable(System.IO.Stream utf8Json, System.Text.Json.Serialization.Metadata.JsonTypeInfo jsonTypeInfo, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
public static System.Collections.Generic.IAsyncEnumerable<object?> DeserializeAsyncEnumerable(System.IO.Stream utf8Json, System.Type returnType, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")]
public static System.Collections.Generic.IAsyncEnumerable<TValue?> DeserializeAsyncEnumerable<TValue>(System.IO.Stream utf8Json, System.Text.Json.JsonSerializerOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Converters;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -428,7 +430,7 @@ public static partial class JsonSerializer
}

JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
return jsonTypeInfo.DeserializeAsyncEnumerable(utf8Json, cancellationToken);
return DeserializeAsyncEnumerableCore(utf8Json, jsonTypeInfo, cancellationToken);
}

/// <summary>
Expand Down Expand Up @@ -459,65 +461,28 @@ public static partial class JsonSerializer
}

jsonTypeInfo.EnsureConfigured();
return jsonTypeInfo.DeserializeAsyncEnumerable(utf8Json, cancellationToken);
return DeserializeAsyncEnumerableCore(utf8Json, jsonTypeInfo, cancellationToken);
}

/// <summary>
/// Wraps the UTF-8 encoded text into an <see cref="IAsyncEnumerable{Object}" />
/// that can be used to deserialize root-level JSON arrays in a streaming manner.
/// </summary>
/// <returns>An <see cref="IAsyncEnumerable{Object}" /> representation of the provided JSON array.</returns>
/// <param name="utf8Json">JSON data to parse.</param>
/// <param name="returnType">The type of the object to convert to and return.</param>
/// <param name="options">Options to control the behavior during reading.</param>
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> that can be used to cancel the read operation.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="utf8Json"/> or <paramref name="returnType"/> is <see langword="null"/>.
/// </exception>
[RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static IAsyncEnumerable<object?> DeserializeAsyncEnumerable(
Stream utf8Json,
Type returnType,
JsonSerializerOptions? options = null,
CancellationToken cancellationToken = default)
private static IAsyncEnumerable<T> DeserializeAsyncEnumerableCore<T>(Stream utf8Json, JsonTypeInfo<T> jsonTypeInfo, CancellationToken cancellationToken)
{
if (utf8Json is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
return jsonTypeInfo.DeserializeAsyncEnumerableAsObject(utf8Json, cancellationToken);
}
Debug.Assert(jsonTypeInfo.IsConfigured);
jsonTypeInfo.AsyncEnumerableQueueTypeInfo ??= CreateQueueTypeInfo(jsonTypeInfo);
return jsonTypeInfo.DeserializeAsyncEnumerable(utf8Json, cancellationToken);

/// <summary>
/// Wraps the UTF-8 encoded text into an <see cref="IAsyncEnumerable{Object}" />
/// that can be used to deserialize root-level JSON arrays in a streaming manner.
/// </summary>
/// <returns>An <see cref="IAsyncEnumerable{Object}" /> representation of the provided JSON array.</returns>
/// <param name="utf8Json">JSON data to parse.</param>
/// <param name="jsonTypeInfo">Metadata about the element type to convert.</param>
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> that can be used to cancel the read operation.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="utf8Json"/> or <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
/// </exception>
public static IAsyncEnumerable<object?> DeserializeAsyncEnumerable(
Stream utf8Json,
JsonTypeInfo jsonTypeInfo,
CancellationToken cancellationToken = default)
{
if (utf8Json is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
}
if (jsonTypeInfo is null)
static JsonTypeInfo<Queue<T>> CreateQueueTypeInfo(JsonTypeInfo<T> jsonTypeInfo)
{
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
var queueConverter = new QueueOfTConverter<Queue<T>, T>();
var queueTypeInfo = new JsonTypeInfo<Queue<T>>(queueConverter, jsonTypeInfo.Options)
{
CreateObject = static () => new Queue<T>(),
ElementTypeInfo = jsonTypeInfo,
NumberHandling = jsonTypeInfo.Options.NumberHandling,
};

jsonTypeInfo.EnsureConfigured();
return jsonTypeInfo.DeserializeAsyncEnumerableAsObject(utf8Json, cancellationToken);
queueTypeInfo.EnsureConfigured();
return queueTypeInfo;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,6 @@ public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name)
internal abstract object? DeserializeAsObject(ref Utf8JsonReader reader, ref ReadStack state);
internal abstract ValueTask<object?> DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken);
internal abstract object? DeserializeAsObject(Stream utf8Json);
internal abstract IAsyncEnumerable<object?> DeserializeAsyncEnumerableAsObject(Stream utf8Json, CancellationToken cancellationToken);

/// <summary>
/// Used by the built-in resolvers to add property metadata applying conflict resolution.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,65 +78,46 @@ public partial class JsonTypeInfo<T>
}
}

private JsonTypeInfo<Queue<T>>? _asuncEnumerableQueueTypeInfo;
internal IAsyncEnumerable<T> DeserializeAsyncEnumerable(Stream utf8Json, CancellationToken cancellationToken)
{
Debug.Assert(IsConfigured);

JsonTypeInfo<Queue<T>>? queueTypeInfo = _asuncEnumerableQueueTypeInfo;
if (queueTypeInfo is null)
{
var queueConverter = new QueueOfTConverter<Queue<T>, T>();
queueTypeInfo = new JsonTypeInfo<Queue<T>>(queueConverter, Options)
{
CreateObject = static () => new Queue<T>(),
ElementTypeInfo = this,
NumberHandling = Options.NumberHandling,
};
// Creating a queue JsonTypeInfo from within the DeserializeAsyncEnumerable method
// triggers polymorphic recursion warnings from the AOT compiler so we instead
// have the callers do it for us externally (cf. https://github.com/dotnet/runtime/issues/84922)
internal JsonTypeInfo<Queue<T>>? AsyncEnumerableQueueTypeInfo;

queueTypeInfo.EnsureConfigured();
_asuncEnumerableQueueTypeInfo = queueTypeInfo;
}
internal async IAsyncEnumerable<T> DeserializeAsyncEnumerable(Stream utf8Json, [EnumeratorCancellation] CancellationToken cancellationToken)
{
Debug.Assert(AsyncEnumerableQueueTypeInfo?.IsConfigured == true, "must be populated before calling the method.");
JsonTypeInfo<Queue<T>> queueTypeInfo = AsyncEnumerableQueueTypeInfo;
JsonSerializerOptions options = queueTypeInfo.Options;
var bufferState = new ReadBufferState(options.DefaultBufferSize);
ReadStack readStack = default;
readStack.Initialize(queueTypeInfo, supportContinuation: true);

return CreateAsyncEnumerableDeserializer(utf8Json, queueTypeInfo, cancellationToken);
var jsonReaderState = new JsonReaderState(options.GetReaderOptions());

static async IAsyncEnumerable<T> CreateAsyncEnumerableDeserializer(
Stream utf8Json,
JsonTypeInfo<Queue<T>> queueTypeInfo,
[EnumeratorCancellation] CancellationToken cancellationToken)
try
{
Debug.Assert(queueTypeInfo.IsConfigured);
JsonSerializerOptions options = queueTypeInfo.Options;
var bufferState = new ReadBufferState(options.DefaultBufferSize);
ReadStack readStack = default;
readStack.Initialize(queueTypeInfo, supportContinuation: true);

var jsonReaderState = new JsonReaderState(options.GetReaderOptions());

try
do
{
do
{
bufferState = await bufferState.ReadFromStreamAsync(utf8Json, cancellationToken, fillBuffer: false).ConfigureAwait(false);
queueTypeInfo.ContinueDeserialize(
ref bufferState,
ref jsonReaderState,
ref readStack);
bufferState = await bufferState.ReadFromStreamAsync(utf8Json, cancellationToken, fillBuffer: false).ConfigureAwait(false);
queueTypeInfo.ContinueDeserialize(
ref bufferState,
ref jsonReaderState,
ref readStack);

if (readStack.Current.ReturnValue is Queue<T> queue)
if (readStack.Current.ReturnValue is { } returnValue)
{
var queue = (Queue<T>)returnValue!;
while (queue.Count > 0)
{
while (queue.Count > 0)
{
yield return queue.Dequeue();
}
yield return queue.Dequeue();
}
}
while (!bufferState.IsFinalBlock);
}
finally
{
bufferState.Dispose();
}
while (!bufferState.IsFinalBlock);
}
finally
{
bufferState.Dispose();
}
}

Expand All @@ -152,22 +133,6 @@ static async IAsyncEnumerable<T> CreateAsyncEnumerableDeserializer(
internal sealed override object? DeserializeAsObject(Stream utf8Json)
=> Deserialize(utf8Json);

internal sealed override IAsyncEnumerable<object?> DeserializeAsyncEnumerableAsObject(Stream utf8Json, CancellationToken cancellationToken)
{
IAsyncEnumerable<T> typedSource = DeserializeAsyncEnumerable(utf8Json, cancellationToken);
return AsObjectEnumerable(typedSource, cancellationToken);

static async IAsyncEnumerable<object?> AsObjectEnumerable(
IAsyncEnumerable<T> source,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (T elem in source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
yield return elem;
}
}
}

private T? ContinueDeserialize(
ref ReadBufferState bufferState,
ref JsonReaderState jsonReaderState,
Expand Down
Loading

0 comments on commit f10bc02

Please sign in to comment.