< Summary

Information
Class: System.Text.Json.ReadStack
Assembly: System.Text.Json
File(s): D:\runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\Serialization\ReadStack.cs
Line coverage
47%
Covered lines: 104
Uncovered lines: 117
Coverable lines: 221
Total lines: 409
Line coverage: 47%
Branch coverage
41%
Covered branches: 44
Total branches: 106
Branch coverage: 41.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

D:\runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\Serialization\ReadStack.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections;
 5using System.Diagnostics;
 6using System.Runtime.CompilerServices;
 7using System.Runtime.InteropServices;
 8using System.Text.Json.Serialization;
 9using System.Text.Json.Serialization.Metadata;
 10
 11namespace System.Text.Json
 12{
 13    [StructLayout(LayoutKind.Auto)]
 14    [DebuggerDisplay("{DebuggerDisplay,nq}")]
 15    internal struct ReadStack
 16    {
 17        /// <summary>
 18        /// Exposes the stack frame that is currently active.
 19        /// </summary>
 20        public ReadStackFrame Current;
 21
 22        /// <summary>
 23        /// Gets the parent stack frame, if it exists.
 24        /// </summary>
 25        public readonly ref ReadStackFrame Parent
 26        {
 27            get
 028            {
 029                Debug.Assert(_count > 1);
 030                Debug.Assert(_stack is not null);
 031                return ref _stack[_count - 2];
 032            }
 33        }
 34
 35        public readonly JsonPropertyInfo? ParentProperty
 27636            => Current.HasParentObject ? Parent.JsonPropertyInfo : null;
 37
 38        /// <summary>
 39        /// Buffer containing all frames in the stack. For performance it is only populated for serialization depths > 1
 40        /// </summary>
 41        private ReadStackFrame[] _stack;
 42
 43        /// <summary>
 44        /// Tracks the current depth of the stack.
 45        /// </summary>
 46        private int _count;
 47
 48        /// <summary>
 49        /// If not zero, indicates that the stack is part of a re-entrant continuation of given depth.
 50        /// </summary>
 51        private int _continuationCount;
 52
 53        /// <summary>
 54        /// Indicates that the state still contains suspended frames waiting re-entry.
 55        /// </summary>
 5028856        public readonly bool IsContinuation => _continuationCount != 0;
 57
 58        // The bag of preservable references.
 59        public ReferenceResolver ReferenceResolver;
 60
 61        /// <summary>
 62        /// Whether we need to read ahead in the inner read loop.
 63        /// </summary>
 64        public bool SupportContinuation;
 65
 66        /// <summary>
 67        /// Holds the value of $id or $ref of the currently read object
 68        /// </summary>
 69        public string? ReferenceId;
 70
 71        /// <summary>
 72        /// Holds the value of $type of the currently read object
 73        /// </summary>
 74        public object? PolymorphicTypeDiscriminator;
 75
 76        /// <summary>
 77        /// Holds the resolved Type when a custom type classifier is used
 78        /// instead of discriminator-based resolution.
 79        /// </summary>
 80        public Type? PolymorphicResolvedType;
 81
 82        /// <summary>
 83        /// Global flag indicating whether we can read preserved references.
 84        /// </summary>
 85        public bool PreserveReferences;
 86
 87        /// <summary>
 88        /// Ensures that the stack buffer has sufficient capacity to hold an additional frame.
 89        /// </summary>
 90        private void EnsurePushCapacity()
 1491        {
 1492            if (_stack is null)
 1493            {
 1494                _stack = new ReadStackFrame[4];
 1495            }
 096            else if (_count - 1 == _stack.Length)
 097            {
 098                Array.Resize(ref _stack, 2 * _stack.Length);
 099            }
 14100        }
 101
 102        internal void Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation = false)
 34768103        {
 34768104            JsonSerializerOptions options = jsonTypeInfo.Options;
 34768105            if (options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.Preserve)
 0106            {
 0107                ReferenceResolver = options.ReferenceHandler!.CreateResolver(writing: false);
 0108                PreserveReferences = true;
 0109            }
 110
 34768111            Current.JsonTypeInfo = jsonTypeInfo;
 34768112            Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 34768113            Current.NumberHandling = Current.JsonPropertyInfo.EffectiveNumberHandling;
 34768114            Current.CanContainMetadata = PreserveReferences || jsonTypeInfo.PolymorphicTypeResolver?.UsesTypeDiscriminat
 34768115                || (jsonTypeInfo.PolymorphicTypeResolver is not null && jsonTypeInfo.TypeClassifier is not null);
 34768116            SupportContinuation = supportContinuation;
 34768117        }
 118
 119        public void Push()
 1170120        {
 1170121            if (_continuationCount == 0)
 1170122            {
 1170123                if (_count == 0)
 1156124                {
 125                    // Performance optimization: reuse the first stack frame on the first push operation.
 126                    // NB need to be careful when making writes to Current _before_ the first `Push`
 127                    // operation is performed.
 1156128                    _count = 1;
 1156129                }
 130                else
 14131                {
 14132                    JsonTypeInfo jsonTypeInfo = Current.JsonPropertyInfo?.JsonTypeInfo ?? Current.CtorArgumentState!.Jso
 14133                    JsonNumberHandling? numberHandling = Current.NumberHandling;
 134
 14135                    EnsurePushCapacity();
 14136                    _stack[_count - 1] = Current;
 14137                    Current = default;
 14138                    _count++;
 139
 14140                    Current.JsonTypeInfo = jsonTypeInfo;
 14141                    Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 142                    // Allow number handling on property to win over handling on type.
 14143                    Current.NumberHandling = numberHandling ?? Current.JsonPropertyInfo.EffectiveNumberHandling;
 14144                    Current.CanContainMetadata = PreserveReferences || jsonTypeInfo.PolymorphicTypeResolver?.UsesTypeDis
 14145                        || (jsonTypeInfo.PolymorphicTypeResolver is not null && jsonTypeInfo.TypeClassifier is not null)
 14146                }
 1170147            }
 148            else
 0149            {
 150                // We are re-entering a continuation, adjust indices accordingly.
 151
 0152                if (_count++ > 0)
 0153                {
 0154                    _stack[_count - 2] = Current;
 0155                    Current = _stack[_count - 1];
 0156                }
 157
 158                // check if we are done
 0159                if (_continuationCount == _count)
 0160                {
 0161                    _continuationCount = 0;
 0162                }
 0163            }
 164
 1170165            SetConstructorArgumentState();
 166#if DEBUG
 167            // Ensure the method is always exercised in debug builds.
 1170168            _ = JsonPath();
 169#endif
 1170170        }
 171
 172        public void Pop(bool success)
 0173        {
 0174            Debug.Assert(_count > 0);
 0175            Debug.Assert(JsonPath() is not null);
 176
 0177            if (!success)
 0178            {
 179                // Check if we need to initialize the continuation.
 0180                if (_continuationCount == 0)
 0181                {
 0182                    if (_count == 1)
 0183                    {
 184                        // No need to copy any frames here.
 0185                        _continuationCount = 1;
 0186                        _count = 0;
 0187                        return;
 188                    }
 189
 190                    // Need to push the Current frame to the stack,
 191                    // ensure that we have sufficient capacity.
 0192                    EnsurePushCapacity();
 0193                    _continuationCount = _count--;
 0194                }
 0195                else if (--_count == 0)
 0196                {
 197                    // reached the root, no need to copy frames.
 0198                    return;
 199                }
 200
 0201                _stack[_count] = Current;
 0202                Current = _stack[_count - 1];
 0203            }
 204            else
 0205            {
 0206                Debug.Assert(_continuationCount == 0);
 207
 0208                if (--_count > 0)
 0209                {
 0210                    Current = _stack[_count - 1];
 0211                }
 0212            }
 0213        }
 214
 215        /// <summary>
 216        /// Configures the current stack frame for a polymorphic converter.
 217        /// </summary>
 218        public JsonConverter InitializePolymorphicReEntry(JsonTypeInfo derivedJsonTypeInfo)
 0219        {
 0220            Debug.Assert(!IsContinuation);
 0221            Debug.Assert(Current.PolymorphicJsonTypeInfo is null);
 0222            Debug.Assert(Current.PolymorphicSerializationState == PolymorphicSerializationState.None);
 223
 0224            Current.PolymorphicJsonTypeInfo = Current.JsonTypeInfo;
 0225            Current.JsonTypeInfo = derivedJsonTypeInfo;
 0226            Current.JsonPropertyInfo = derivedJsonTypeInfo.PropertyInfoForTypeInfo;
 0227            Current.NumberHandling ??= Current.JsonPropertyInfo.NumberHandling;
 0228            Current.PolymorphicSerializationState = PolymorphicSerializationState.PolymorphicReEntryStarted;
 0229            SetConstructorArgumentState();
 230
 0231            return derivedJsonTypeInfo.Converter;
 0232        }
 233
 234
 235        /// <summary>
 236        /// Configures the current frame for a continuation of a polymorphic converter.
 237        /// </summary>
 238        public JsonConverter ResumePolymorphicReEntry()
 0239        {
 0240            Debug.Assert(Current.PolymorphicJsonTypeInfo is not null);
 0241            Debug.Assert(Current.PolymorphicSerializationState == PolymorphicSerializationState.PolymorphicReEntrySuspen
 242
 243            // Swap out the two values as we resume the polymorphic converter
 0244            (Current.JsonTypeInfo, Current.PolymorphicJsonTypeInfo) = (Current.PolymorphicJsonTypeInfo, Current.JsonType
 0245            Current.PolymorphicSerializationState = PolymorphicSerializationState.PolymorphicReEntryStarted;
 0246            return Current.JsonTypeInfo.Converter;
 0247        }
 248
 249        /// <summary>
 250        /// Updates frame state after a polymorphic converter has returned.
 251        /// </summary>
 252        public void ExitPolymorphicConverter(bool success)
 0253        {
 0254            Debug.Assert(Current.PolymorphicJsonTypeInfo is not null);
 0255            Debug.Assert(Current.PolymorphicSerializationState == PolymorphicSerializationState.PolymorphicReEntryStarte
 256
 257            // Swap out the two values as we exit the polymorphic converter
 0258            (Current.JsonTypeInfo, Current.PolymorphicJsonTypeInfo) = (Current.PolymorphicJsonTypeInfo, Current.JsonType
 0259            Current.PolymorphicSerializationState = success ? PolymorphicSerializationState.None : PolymorphicSerializat
 0260        }
 261
 262        // Return a JSONPath using simple dot-notation when possible. When special characters are present, bracket-notat
 263        // $.x.y[0].z
 264        // $['PropertyName.With.Special.Chars']
 265        public string JsonPath()
 35304266        {
 35304267            StringBuilder sb = new StringBuilder("$");
 268
 35304269            (int frameCount, bool includeCurrentFrame) = _continuationCount switch
 35304270            {
 35304271                0 => (_count - 1, true), // Not a continuation, report previous frames and Current.
 0272                1 => (0, true), // Continuation of depth 1, just report Current frame.
 0273                int c => (c, false) // Continuation of depth > 1, report the entire stack.
 35304274            };
 275
 70664276            for (int i = 0; i < frameCount; i++)
 28277            {
 28278                AppendStackFrame(sb, ref _stack[i]);
 28279            }
 280
 35304281            if (includeCurrentFrame)
 35304282            {
 35304283                AppendStackFrame(sb, ref Current);
 35304284            }
 285
 35304286            return sb.ToString();
 287
 288            static void AppendStackFrame(StringBuilder sb, ref ReadStackFrame frame)
 35332289            {
 290                // Append the property name.
 35332291                string? propertyName = GetPropertyName(ref frame);
 35332292                AppendPropertyName(sb, propertyName);
 293
 35332294                if (frame.JsonTypeInfo is not null && frame.IsProcessingEnumerable())
 1458295                {
 1458296                    if (frame.ReturnValue is not IEnumerable enumerable)
 1200297                    {
 1200298                        return;
 299                    }
 300
 301                    // For continuation scenarios only, before or after all elements are read, the exception is not with
 258302                    if (frame.ObjectState == StackFrameObjectState.None ||
 258303                        frame.ObjectState == StackFrameObjectState.CreatedObject ||
 258304                        frame.ObjectState == StackFrameObjectState.ReadElements)
 258305                    {
 258306                        sb.Append('[');
 258307                        sb.Append(GetCount(enumerable));
 258308                        sb.Append(']');
 258309                    }
 258310                }
 35332311            }
 312
 313            static int GetCount(IEnumerable enumerable)
 258314            {
 258315                if (enumerable is ICollection collection)
 258316                {
 258317                    return collection.Count;
 318                }
 319
 0320                int count = 0;
 0321                IEnumerator enumerator = enumerable.GetEnumerator();
 0322                while (enumerator.MoveNext())
 0323                {
 0324                    count++;
 0325                }
 326
 0327                return count;
 258328            }
 329
 330            static void AppendPropertyName(StringBuilder sb, string? propertyName)
 35332331            {
 35332332                if (propertyName is not null)
 0333                {
 0334                    if (propertyName.AsSpan().ContainsSpecialCharacters())
 0335                    {
 0336                        sb.Append(@"['");
 0337                        sb.AppendEscapedPropertyName(propertyName);
 0338                        sb.Append(@"']");
 0339                    }
 340                    else
 0341                    {
 0342                        sb.Append('.');
 0343                        sb.Append(propertyName);
 0344                    }
 0345                }
 35332346            }
 347
 348            static string? GetPropertyName(ref ReadStackFrame frame)
 35332349            {
 35332350                string? propertyName = null;
 351
 352                // Attempt to get the JSON property name from the frame.
 35332353                byte[]? utf8PropertyName = frame.JsonPropertyName;
 35332354                if (utf8PropertyName is null)
 35332355                {
 35332356                    if (frame.JsonPropertyNameAsString is not null)
 0357                    {
 358                        // Attempt to get the JSON property name set manually for dictionary
 359                        // keys and KeyValuePair property names.
 0360                        propertyName = frame.JsonPropertyNameAsString;
 0361                    }
 362                    else
 35332363                    {
 364                        // Attempt to get the JSON property name from the JsonPropertyInfo or JsonParameterInfo.
 35332365                        utf8PropertyName = frame.JsonPropertyInfo?.NameAsUtf8Bytes ??
 35332366                            frame.CtorArgumentState?.JsonParameterInfo?.JsonNameAsUtf8Bytes;
 35332367                    }
 35332368                }
 369
 35332370                if (utf8PropertyName is not null)
 0371                {
 0372                    propertyName = Encoding.UTF8.GetString(utf8PropertyName);
 0373                }
 374
 35332375                return propertyName;
 35332376            }
 35304377        }
 378
 379        // Traverses the stack for the outermost object being deserialized using constructor parameters
 380        // Only called when calculating exception information.
 381        public JsonTypeInfo GetTopJsonTypeInfoWithParameterizedConstructor()
 0382        {
 0383            Debug.Assert(!IsContinuation);
 384
 0385            for (int i = 0; i < _count - 1; i++)
 0386            {
 0387                if (_stack[i].JsonTypeInfo.UsesParameterizedConstructor)
 0388                {
 0389                    return _stack[i].JsonTypeInfo;
 390                }
 0391            }
 392
 0393            Debug.Assert(Current.JsonTypeInfo.UsesParameterizedConstructor);
 0394            return Current.JsonTypeInfo;
 0395        }
 396
 397        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 398        private void SetConstructorArgumentState()
 1170399        {
 1170400            if (Current.JsonTypeInfo.UsesParameterizedConstructor)
 120401            {
 120402                Current.CtorArgumentState ??= new();
 120403            }
 1170404        }
 405
 406        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 0407        private string DebuggerDisplay => $"Path = {JsonPath()}, Current = ConverterStrategy.{Current.JsonTypeInfo?.Conv
 408    }
 409}