| File: System\Text\Json\Serialization\ReadStack.cs | Web Access |
| Project: src\runtime\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json { [StructLayout(LayoutKind.Auto)] [DebuggerDisplay("{DebuggerDisplay,nq}")] internal struct ReadStack { /// <summary> /// Exposes the stack frame that is currently active. /// </summary> public ReadStackFrame Current; /// <summary> /// Gets the parent stack frame, if it exists. /// </summary> public readonly ref ReadStackFrame Parent { get { Debug.Assert(_count > 1); Debug.Assert(_stack is not null); return ref _stack[_count - 2]; } } public readonly JsonPropertyInfo? ParentProperty => Current.HasParentObject ? Parent.JsonPropertyInfo : null; /// <summary> /// Buffer containing all frames in the stack. For performance it is only populated for serialization depths > 1. /// </summary> private ReadStackFrame[] _stack; /// <summary> /// Tracks the current depth of the stack. /// </summary> private int _count; /// <summary> /// If not zero, indicates that the stack is part of a re-entrant continuation of given depth. /// </summary> private int _continuationCount; /// <summary> /// Indicates that the state still contains suspended frames waiting re-entry. /// </summary> public readonly bool IsContinuation => _continuationCount != 0; // The bag of preservable references. public ReferenceResolver ReferenceResolver; /// <summary> /// Whether we need to read ahead in the inner read loop. /// </summary> public bool SupportContinuation; /// <summary> /// Holds the value of $id or $ref of the currently read object /// </summary> public string? ReferenceId; /// <summary> /// Holds the value of $type of the currently read object /// </summary> public object? PolymorphicTypeDiscriminator; /// <summary> /// Holds the resolved Type when a custom type classifier is used /// instead of discriminator-based resolution. /// </summary> public Type? PolymorphicResolvedType; /// <summary> /// Global flag indicating whether we can read preserved references. /// </summary> public bool PreserveReferences; /// <summary> /// Ensures that the stack buffer has sufficient capacity to hold an additional frame. /// </summary> private void EnsurePushCapacity() { if (_stack is null) { _stack = new ReadStackFrame[4]; } else if (_count - 1 == _stack.Length) { Array.Resize(ref _stack, 2 * _stack.Length); } } internal void Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation = false) { JsonSerializerOptions options = jsonTypeInfo.Options; if (options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.Preserve) { ReferenceResolver = options.ReferenceHandler!.CreateResolver(writing: false); PreserveReferences = true; } Current.JsonTypeInfo = jsonTypeInfo; Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo; Current.NumberHandling = Current.JsonPropertyInfo.EffectiveNumberHandling; Current.CanContainMetadata = PreserveReferences || jsonTypeInfo.PolymorphicTypeResolver?.UsesTypeDiscriminators == true || (jsonTypeInfo.PolymorphicTypeResolver is not null && jsonTypeInfo.TypeClassifier is not null); SupportContinuation = supportContinuation; } public void Push() { if (_continuationCount == 0) { if (_count == 0) { // Performance optimization: reuse the first stack frame on the first push operation. // NB need to be careful when making writes to Current _before_ the first `Push` // operation is performed. _count = 1; } else { JsonTypeInfo jsonTypeInfo = Current.JsonPropertyInfo?.JsonTypeInfo ?? Current.CtorArgumentState!.JsonParameterInfo!.JsonTypeInfo; JsonNumberHandling? numberHandling = Current.NumberHandling; EnsurePushCapacity(); _stack[_count - 1] = Current; Current = default; _count++; Current.JsonTypeInfo = jsonTypeInfo; Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo; // Allow number handling on property to win over handling on type. Current.NumberHandling = numberHandling ?? Current.JsonPropertyInfo.EffectiveNumberHandling; Current.CanContainMetadata = PreserveReferences || jsonTypeInfo.PolymorphicTypeResolver?.UsesTypeDiscriminators == true || (jsonTypeInfo.PolymorphicTypeResolver is not null && jsonTypeInfo.TypeClassifier is not null); } } else { // We are re-entering a continuation, adjust indices accordingly. if (_count++ > 0) { _stack[_count - 2] = Current; Current = _stack[_count - 1]; } // check if we are done if (_continuationCount == _count) { _continuationCount = 0; } } SetConstructorArgumentState(); #if DEBUG // Ensure the method is always exercised in debug builds. _ = JsonPath(); #endif } public void Pop(bool success) { Debug.Assert(_count > 0); Debug.Assert(JsonPath() is not null); if (!success) { // Check if we need to initialize the continuation. if (_continuationCount == 0) { if (_count == 1) { // No need to copy any frames here. _continuationCount = 1; _count = 0; return; } // Need to push the Current frame to the stack, // ensure that we have sufficient capacity. EnsurePushCapacity(); _continuationCount = _count--; } else if (--_count == 0) { // reached the root, no need to copy frames. return; } _stack[_count] = Current; Current = _stack[_count - 1]; } else { Debug.Assert(_continuationCount == 0); if (--_count > 0) { Current = _stack[_count - 1]; } } } /// <summary> /// Configures the current stack frame for a polymorphic converter. /// </summary> public JsonConverter InitializePolymorphicReEntry(JsonTypeInfo derivedJsonTypeInfo) { Debug.Assert(!IsContinuation); Debug.Assert(Current.PolymorphicJsonTypeInfo is null); Debug.Assert(Current.PolymorphicSerializationState == PolymorphicSerializationState.None); Current.PolymorphicJsonTypeInfo = Current.JsonTypeInfo; Current.JsonTypeInfo = derivedJsonTypeInfo; Current.JsonPropertyInfo = derivedJsonTypeInfo.PropertyInfoForTypeInfo; Current.NumberHandling ??= Current.JsonPropertyInfo.NumberHandling; Current.PolymorphicSerializationState = PolymorphicSerializationState.PolymorphicReEntryStarted; SetConstructorArgumentState(); return derivedJsonTypeInfo.Converter; } /// <summary> /// Configures the current frame for a continuation of a polymorphic converter. /// </summary> public JsonConverter ResumePolymorphicReEntry() { Debug.Assert(Current.PolymorphicJsonTypeInfo is not null); Debug.Assert(Current.PolymorphicSerializationState == PolymorphicSerializationState.PolymorphicReEntrySuspended); // Swap out the two values as we resume the polymorphic converter (Current.JsonTypeInfo, Current.PolymorphicJsonTypeInfo) = (Current.PolymorphicJsonTypeInfo, Current.JsonTypeInfo); Current.PolymorphicSerializationState = PolymorphicSerializationState.PolymorphicReEntryStarted; return Current.JsonTypeInfo.Converter; } /// <summary> /// Updates frame state after a polymorphic converter has returned. /// </summary> public void ExitPolymorphicConverter(bool success) { Debug.Assert(Current.PolymorphicJsonTypeInfo is not null); Debug.Assert(Current.PolymorphicSerializationState == PolymorphicSerializationState.PolymorphicReEntryStarted); // Swap out the two values as we exit the polymorphic converter (Current.JsonTypeInfo, Current.PolymorphicJsonTypeInfo) = (Current.PolymorphicJsonTypeInfo, Current.JsonTypeInfo); Current.PolymorphicSerializationState = success ? PolymorphicSerializationState.None : PolymorphicSerializationState.PolymorphicReEntrySuspended; } // Return a JSONPath using simple dot-notation when possible. When special characters are present, bracket-notation is used: // $.x.y[0].z // $['PropertyName.With.Special.Chars'] public string JsonPath() { StringBuilder sb = new StringBuilder("$"); (int frameCount, bool includeCurrentFrame) = _continuationCount switch { 0 => (_count - 1, true), // Not a continuation, report previous frames and Current. 1 => (0, true), // Continuation of depth 1, just report Current frame. int c => (c, false) // Continuation of depth > 1, report the entire stack. }; for (int i = 0; i < frameCount; i++) { AppendStackFrame(sb, ref _stack[i]); } if (includeCurrentFrame) { AppendStackFrame(sb, ref Current); } return sb.ToString(); static void AppendStackFrame(StringBuilder sb, ref ReadStackFrame frame) { // Append the property name. string? propertyName = GetPropertyName(ref frame); AppendPropertyName(sb, propertyName); if (frame.JsonTypeInfo is not null && frame.IsProcessingEnumerable()) { if (frame.ReturnValue is not IEnumerable enumerable) { return; } // For continuation scenarios only, before or after all elements are read, the exception is not within the array. if (frame.ObjectState == StackFrameObjectState.None || frame.ObjectState == StackFrameObjectState.CreatedObject || frame.ObjectState == StackFrameObjectState.ReadElements) { sb.Append('['); sb.Append(GetCount(enumerable)); sb.Append(']'); } } } static int GetCount(IEnumerable enumerable) { if (enumerable is ICollection collection) { return collection.Count; } int count = 0; IEnumerator enumerator = enumerable.GetEnumerator(); while (enumerator.MoveNext()) { count++; } return count; } static void AppendPropertyName(StringBuilder sb, string? propertyName) { if (propertyName is not null) { if (propertyName.AsSpan().ContainsSpecialCharacters()) { sb.Append(@"['"); sb.AppendEscapedPropertyName(propertyName); sb.Append(@"']"); } else { sb.Append('.'); sb.Append(propertyName); } } } static string? GetPropertyName(ref ReadStackFrame frame) { string? propertyName = null; // Attempt to get the JSON property name from the frame. byte[]? utf8PropertyName = frame.JsonPropertyName; if (utf8PropertyName is null) { if (frame.JsonPropertyNameAsString is not null) { // Attempt to get the JSON property name set manually for dictionary // keys and KeyValuePair property names. propertyName = frame.JsonPropertyNameAsString; } else { // Attempt to get the JSON property name from the JsonPropertyInfo or JsonParameterInfo. utf8PropertyName = frame.JsonPropertyInfo?.NameAsUtf8Bytes ?? frame.CtorArgumentState?.JsonParameterInfo?.JsonNameAsUtf8Bytes; } } if (utf8PropertyName is not null) { propertyName = Encoding.UTF8.GetString(utf8PropertyName); } return propertyName; } } // Traverses the stack for the outermost object being deserialized using constructor parameters // Only called when calculating exception information. public JsonTypeInfo GetTopJsonTypeInfoWithParameterizedConstructor() { Debug.Assert(!IsContinuation); for (int i = 0; i < _count - 1; i++) { if (_stack[i].JsonTypeInfo.UsesParameterizedConstructor) { return _stack[i].JsonTypeInfo; } } Debug.Assert(Current.JsonTypeInfo.UsesParameterizedConstructor); return Current.JsonTypeInfo; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetConstructorArgumentState() { if (Current.JsonTypeInfo.UsesParameterizedConstructor) { Current.CtorArgumentState ??= new(); } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string DebuggerDisplay => $"Path = {JsonPath()}, Current = ConverterStrategy.{Current.JsonTypeInfo?.Converter.ConverterStrategy}, {Current.JsonTypeInfo?.Type.Name}"; } }