Skip to content

Commit f10bc02

Browse files
Fix polymorphic recursion condition when calling DeserializeAsyncEnumerable in NativeAOT.
1 parent f107b4b commit f10bc02

File tree

5 files changed

+50
-151
lines changed

5 files changed

+50
-151
lines changed

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,6 @@ public static partial class JsonSerializer
244244
[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.")]
245245
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; }
246246
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; }
247-
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; }
248-
[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.")]
249-
[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.")]
250-
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; }
251247
[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.")]
252248
[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.")]
253249
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; }

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs

Lines changed: 19 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Generic;
5+
using System.Diagnostics;
56
using System.Diagnostics.CodeAnalysis;
67
using System.IO;
78
using System.Text.Json.Serialization;
9+
using System.Text.Json.Serialization.Converters;
810
using System.Text.Json.Serialization.Metadata;
911
using System.Threading;
1012
using System.Threading.Tasks;
@@ -428,7 +430,7 @@ public static partial class JsonSerializer
428430
}
429431

430432
JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
431-
return jsonTypeInfo.DeserializeAsyncEnumerable(utf8Json, cancellationToken);
433+
return DeserializeAsyncEnumerableCore(utf8Json, jsonTypeInfo, cancellationToken);
432434
}
433435

434436
/// <summary>
@@ -459,65 +461,28 @@ public static partial class JsonSerializer
459461
}
460462

461463
jsonTypeInfo.EnsureConfigured();
462-
return jsonTypeInfo.DeserializeAsyncEnumerable(utf8Json, cancellationToken);
464+
return DeserializeAsyncEnumerableCore(utf8Json, jsonTypeInfo, cancellationToken);
463465
}
464466

465-
/// <summary>
466-
/// Wraps the UTF-8 encoded text into an <see cref="IAsyncEnumerable{Object}" />
467-
/// that can be used to deserialize root-level JSON arrays in a streaming manner.
468-
/// </summary>
469-
/// <returns>An <see cref="IAsyncEnumerable{Object}" /> representation of the provided JSON array.</returns>
470-
/// <param name="utf8Json">JSON data to parse.</param>
471-
/// <param name="returnType">The type of the object to convert to and return.</param>
472-
/// <param name="options">Options to control the behavior during reading.</param>
473-
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> that can be used to cancel the read operation.</param>
474-
/// <exception cref="System.ArgumentNullException">
475-
/// <paramref name="utf8Json"/> or <paramref name="returnType"/> is <see langword="null"/>.
476-
/// </exception>
477-
[RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
478-
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
479-
public static IAsyncEnumerable<object?> DeserializeAsyncEnumerable(
480-
Stream utf8Json,
481-
Type returnType,
482-
JsonSerializerOptions? options = null,
483-
CancellationToken cancellationToken = default)
467+
private static IAsyncEnumerable<T> DeserializeAsyncEnumerableCore<T>(Stream utf8Json, JsonTypeInfo<T> jsonTypeInfo, CancellationToken cancellationToken)
484468
{
485-
if (utf8Json is null)
486-
{
487-
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
488-
}
489-
490-
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
491-
return jsonTypeInfo.DeserializeAsyncEnumerableAsObject(utf8Json, cancellationToken);
492-
}
469+
Debug.Assert(jsonTypeInfo.IsConfigured);
470+
jsonTypeInfo.AsyncEnumerableQueueTypeInfo ??= CreateQueueTypeInfo(jsonTypeInfo);
471+
return jsonTypeInfo.DeserializeAsyncEnumerable(utf8Json, cancellationToken);
493472

494-
/// <summary>
495-
/// Wraps the UTF-8 encoded text into an <see cref="IAsyncEnumerable{Object}" />
496-
/// that can be used to deserialize root-level JSON arrays in a streaming manner.
497-
/// </summary>
498-
/// <returns>An <see cref="IAsyncEnumerable{Object}" /> representation of the provided JSON array.</returns>
499-
/// <param name="utf8Json">JSON data to parse.</param>
500-
/// <param name="jsonTypeInfo">Metadata about the element type to convert.</param>
501-
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> that can be used to cancel the read operation.</param>
502-
/// <exception cref="System.ArgumentNullException">
503-
/// <paramref name="utf8Json"/> or <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
504-
/// </exception>
505-
public static IAsyncEnumerable<object?> DeserializeAsyncEnumerable(
506-
Stream utf8Json,
507-
JsonTypeInfo jsonTypeInfo,
508-
CancellationToken cancellationToken = default)
509-
{
510-
if (utf8Json is null)
511-
{
512-
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
513-
}
514-
if (jsonTypeInfo is null)
473+
static JsonTypeInfo<Queue<T>> CreateQueueTypeInfo(JsonTypeInfo<T> jsonTypeInfo)
515474
{
516-
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
517-
}
475+
var queueConverter = new QueueOfTConverter<Queue<T>, T>();
476+
var queueTypeInfo = new JsonTypeInfo<Queue<T>>(queueConverter, jsonTypeInfo.Options)
477+
{
478+
CreateObject = static () => new Queue<T>(),
479+
ElementTypeInfo = jsonTypeInfo,
480+
NumberHandling = jsonTypeInfo.Options.NumberHandling,
481+
};
518482

519-
jsonTypeInfo.EnsureConfigured();
520-
return jsonTypeInfo.DeserializeAsyncEnumerableAsObject(utf8Json, cancellationToken);
483+
queueTypeInfo.EnsureConfigured();
484+
return queueTypeInfo;
485+
}
521486
}
522487
}
523488
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,6 @@ public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name)
910910
internal abstract object? DeserializeAsObject(ref Utf8JsonReader reader, ref ReadStack state);
911911
internal abstract ValueTask<object?> DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken);
912912
internal abstract object? DeserializeAsObject(Stream utf8Json);
913-
internal abstract IAsyncEnumerable<object?> DeserializeAsyncEnumerableAsObject(Stream utf8Json, CancellationToken cancellationToken);
914913

915914
/// <summary>
916915
/// Used by the built-in resolvers to add property metadata applying conflict resolution.

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.ReadHelper.cs

Lines changed: 30 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -78,65 +78,46 @@ public partial class JsonTypeInfo<T>
7878
}
7979
}
8080

81-
private JsonTypeInfo<Queue<T>>? _asuncEnumerableQueueTypeInfo;
82-
internal IAsyncEnumerable<T> DeserializeAsyncEnumerable(Stream utf8Json, CancellationToken cancellationToken)
83-
{
84-
Debug.Assert(IsConfigured);
85-
86-
JsonTypeInfo<Queue<T>>? queueTypeInfo = _asuncEnumerableQueueTypeInfo;
87-
if (queueTypeInfo is null)
88-
{
89-
var queueConverter = new QueueOfTConverter<Queue<T>, T>();
90-
queueTypeInfo = new JsonTypeInfo<Queue<T>>(queueConverter, Options)
91-
{
92-
CreateObject = static () => new Queue<T>(),
93-
ElementTypeInfo = this,
94-
NumberHandling = Options.NumberHandling,
95-
};
81+
// Creating a queue JsonTypeInfo from within the DeserializeAsyncEnumerable method
82+
// triggers polymorphic recursion warnings from the AOT compiler so we instead
83+
// have the callers do it for us externally (cf. https://github.com/dotnet/runtime/issues/84922)
84+
internal JsonTypeInfo<Queue<T>>? AsyncEnumerableQueueTypeInfo;
9685

97-
queueTypeInfo.EnsureConfigured();
98-
_asuncEnumerableQueueTypeInfo = queueTypeInfo;
99-
}
86+
internal async IAsyncEnumerable<T> DeserializeAsyncEnumerable(Stream utf8Json, [EnumeratorCancellation] CancellationToken cancellationToken)
87+
{
88+
Debug.Assert(AsyncEnumerableQueueTypeInfo?.IsConfigured == true, "must be populated before calling the method.");
89+
JsonTypeInfo<Queue<T>> queueTypeInfo = AsyncEnumerableQueueTypeInfo;
90+
JsonSerializerOptions options = queueTypeInfo.Options;
91+
var bufferState = new ReadBufferState(options.DefaultBufferSize);
92+
ReadStack readStack = default;
93+
readStack.Initialize(queueTypeInfo, supportContinuation: true);
10094

101-
return CreateAsyncEnumerableDeserializer(utf8Json, queueTypeInfo, cancellationToken);
95+
var jsonReaderState = new JsonReaderState(options.GetReaderOptions());
10296

103-
static async IAsyncEnumerable<T> CreateAsyncEnumerableDeserializer(
104-
Stream utf8Json,
105-
JsonTypeInfo<Queue<T>> queueTypeInfo,
106-
[EnumeratorCancellation] CancellationToken cancellationToken)
97+
try
10798
{
108-
Debug.Assert(queueTypeInfo.IsConfigured);
109-
JsonSerializerOptions options = queueTypeInfo.Options;
110-
var bufferState = new ReadBufferState(options.DefaultBufferSize);
111-
ReadStack readStack = default;
112-
readStack.Initialize(queueTypeInfo, supportContinuation: true);
113-
114-
var jsonReaderState = new JsonReaderState(options.GetReaderOptions());
115-
116-
try
99+
do
117100
{
118-
do
119-
{
120-
bufferState = await bufferState.ReadFromStreamAsync(utf8Json, cancellationToken, fillBuffer: false).ConfigureAwait(false);
121-
queueTypeInfo.ContinueDeserialize(
122-
ref bufferState,
123-
ref jsonReaderState,
124-
ref readStack);
101+
bufferState = await bufferState.ReadFromStreamAsync(utf8Json, cancellationToken, fillBuffer: false).ConfigureAwait(false);
102+
queueTypeInfo.ContinueDeserialize(
103+
ref bufferState,
104+
ref jsonReaderState,
105+
ref readStack);
125106

126-
if (readStack.Current.ReturnValue is Queue<T> queue)
107+
if (readStack.Current.ReturnValue is { } returnValue)
108+
{
109+
var queue = (Queue<T>)returnValue!;
110+
while (queue.Count > 0)
127111
{
128-
while (queue.Count > 0)
129-
{
130-
yield return queue.Dequeue();
131-
}
112+
yield return queue.Dequeue();
132113
}
133114
}
134-
while (!bufferState.IsFinalBlock);
135-
}
136-
finally
137-
{
138-
bufferState.Dispose();
139115
}
116+
while (!bufferState.IsFinalBlock);
117+
}
118+
finally
119+
{
120+
bufferState.Dispose();
140121
}
141122
}
142123

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

155-
internal sealed override IAsyncEnumerable<object?> DeserializeAsyncEnumerableAsObject(Stream utf8Json, CancellationToken cancellationToken)
156-
{
157-
IAsyncEnumerable<T> typedSource = DeserializeAsyncEnumerable(utf8Json, cancellationToken);
158-
return AsObjectEnumerable(typedSource, cancellationToken);
159-
160-
static async IAsyncEnumerable<object?> AsObjectEnumerable(
161-
IAsyncEnumerable<T> source,
162-
[EnumeratorCancellation] CancellationToken cancellationToken)
163-
{
164-
await foreach (T elem in source.WithCancellation(cancellationToken).ConfigureAwait(false))
165-
{
166-
yield return elem;
167-
}
168-
}
169-
}
170-
171136
private T? ContinueDeserialize(
172137
ref ReadBufferState bufferState,
173138
ref JsonReaderState jsonReaderState,

0 commit comments

Comments
 (0)