Description
The System.Text.Json JsonInheritanceConverter<TBase> still throws an uncatchable StackOverflowException in a case that is not covered by the #1728 fix.
#1728 fixed GetObjectSubtype to return null for unknown discriminators, so a completely unknown discriminator now correctly throws InvalidOperationException. However, GetDiscriminatorType still has two branches that can return a type which re-triggers the same converter on the same document, causing infinite recursion:
if (objectType.Name == discriminatorValue)
return objectType; // base type -> re-enters this converter
var typeName = objectType.Namespace + "." + discriminatorValue;
var subtype = ...Assembly.GetType(typeName);
if (subtype != null)
return subtype; // may be a type carrying the same converter
Since the converter is registered on the base type via [JsonConverter], JsonSerializer.Deserialize(bytes, subtype, options) re-invokes Read with the same payload whenever subtype is the base type itself (or another type carrying the same converter) → infinite recursion → StackOverflowException (which cannot be caught by a try/catch, so the whole process crashes).
Repro
[JsonInheritanceConverter(typeof(Note), "@type")]
[JsonInheritanceAttribute("NoteDeployment", typeof(NoteDeployment))]
public partial class Note { }
public partial class NoteDeployment : Note { }
Payload where @type equals the base type's own name (e.g. a malformed producer):
Deserializing to Note:
Expected
A catchable exception (e.g. JsonException / InvalidOperationException), consistent with the unknown-discriminator case.
Suggested fix
In GetDiscriminatorType, never return a type equal to objectType (the type currently being converted): fall through to the final throw instead. As an additional safety net, a per-thread re-entrancy / recursion-depth guard in Read would turn any remaining infinite recursion into a catchable JsonException.
Versions
- NJsonSchema 11.6.1 (via NSwag 14.7.1)
- System.Text.Json, .NET 8
- The
JsonInheritanceConverter.liquid template on master still exhibits this behavior.
Description
The System.Text.Json
JsonInheritanceConverter<TBase>still throws an uncatchableStackOverflowExceptionin a case that is not covered by the #1728 fix.#1728 fixed
GetObjectSubtypeto returnnullfor unknown discriminators, so a completely unknown discriminator now correctly throwsInvalidOperationException. However,GetDiscriminatorTypestill has two branches that can return a type which re-triggers the same converter on the same document, causing infinite recursion:Since the converter is registered on the base type via
[JsonConverter],JsonSerializer.Deserialize(bytes, subtype, options)re-invokesReadwith the same payload wheneversubtypeis the base type itself (or another type carrying the same converter) → infinite recursion →StackOverflowException(which cannot be caught by a try/catch, so the whole process crashes).Repro
Payload where
@typeequals the base type's own name (e.g. a malformed producer):{ "@type": "Note" }Deserializing to
Note:GetObjectSubtype(typeof(Note), "Note")returnsnull(fix from Unknown subtype causes Stackoverflow exception at deserialization #1728),objectType.Name == "Note"istrue→ returnstypeof(Note),Deserialize(bytes, typeof(Note), options)re-enters the same converter with the same document → StackOverflow.Expected
A catchable exception (e.g.
JsonException/InvalidOperationException), consistent with the unknown-discriminator case.Suggested fix
In
GetDiscriminatorType, never return a type equal toobjectType(the type currently being converted): fall through to the finalthrowinstead. As an additional safety net, a per-thread re-entrancy / recursion-depth guard inReadwould turn any remaining infinite recursion into a catchableJsonException.Versions
JsonInheritanceConverter.liquidtemplate onmasterstill exhibits this behavior.