< Summary

Line coverage
50%
Covered lines: 111
Uncovered lines: 108
Coverable lines: 219
Total lines: 401
Line coverage: 50.6%
Branch coverage
50%
Covered branches: 49
Total branches: 98
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\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
 30236            => 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>
 8201256        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        /// Global flag indicating whether we can read preserved references.
 78        /// </summary>
 79        public bool PreserveReferences;
 80
 81        /// <summary>
 82        /// Ensures that the stack buffer has sufficient capacity to hold an additional frame.
 83        /// </summary>
 84        private void EnsurePushCapacity()
 485        {
 486            if (_stack is null)
 487            {
 488                _stack = new ReadStackFrame[4];
 489            }
 090            else if (_count - 1 == _stack.Length)
 091            {
 092                Array.Resize(ref _stack, 2 * _stack.Length);
 093            }
 494        }
 95
 96        internal void Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation = false)
 5592497        {
 5592498            JsonSerializerOptions options = jsonTypeInfo.Options;
 5592499            if (options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.Preserve)
 0100            {
 0101                ReferenceResolver = options.ReferenceHandler!.CreateResolver(writing: false);
 0102                PreserveReferences = true;
 0103            }
 104
 55924105            Current.JsonTypeInfo = jsonTypeInfo;
 55924106            Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 55924107            Current.NumberHandling = Current.JsonPropertyInfo.EffectiveNumberHandling;
 55924108            Current.CanContainMetadata = PreserveReferences || jsonTypeInfo.PolymorphicTypeResolver?.UsesTypeDiscriminat
 55924109            SupportContinuation = supportContinuation;
 55924110        }
 111
 112        public void Push()
 1736113        {
 1736114            if (_continuationCount == 0)
 1736115            {
 1736116                if (_count == 0)
 1732117                {
 118                    // Performance optimization: reuse the first stack frame on the first push operation.
 119                    // NB need to be careful when making writes to Current _before_ the first `Push`
 120                    // operation is performed.
 1732121                    _count = 1;
 1732122                }
 123                else
 4124                {
 4125                    JsonTypeInfo jsonTypeInfo = Current.JsonPropertyInfo?.JsonTypeInfo ?? Current.CtorArgumentState!.Jso
 4126                    JsonNumberHandling? numberHandling = Current.NumberHandling;
 127
 4128                    EnsurePushCapacity();
 4129                    _stack[_count - 1] = Current;
 4130                    Current = default;
 4131                    _count++;
 132
 4133                    Current.JsonTypeInfo = jsonTypeInfo;
 4134                    Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 135                    // Allow number handling on property to win over handling on type.
 4136                    Current.NumberHandling = numberHandling ?? Current.JsonPropertyInfo.EffectiveNumberHandling;
 4137                    Current.CanContainMetadata = PreserveReferences || jsonTypeInfo.PolymorphicTypeResolver?.UsesTypeDis
 4138                }
 1736139            }
 140            else
 0141            {
 142                // We are re-entering a continuation, adjust indices accordingly.
 143
 0144                if (_count++ > 0)
 0145                {
 0146                    _stack[_count - 2] = Current;
 0147                    Current = _stack[_count - 1];
 0148                }
 149
 150                // check if we are done
 0151                if (_continuationCount == _count)
 0152                {
 0153                    _continuationCount = 0;
 0154                }
 0155            }
 156
 1736157            SetConstructorArgumentState();
 158#if DEBUG
 159            // Ensure the method is always exercised in debug builds.
 1736160            _ = JsonPath();
 161#endif
 1736162        }
 163
 164        public void Pop(bool success)
 34165        {
 34166            Debug.Assert(_count > 0);
 34167            Debug.Assert(JsonPath() is not null);
 168
 34169            if (!success)
 0170            {
 171                // Check if we need to initialize the continuation.
 0172                if (_continuationCount == 0)
 0173                {
 0174                    if (_count == 1)
 0175                    {
 176                        // No need to copy any frames here.
 0177                        _continuationCount = 1;
 0178                        _count = 0;
 0179                        return;
 180                    }
 181
 182                    // Need to push the Current frame to the stack,
 183                    // ensure that we have sufficient capacity.
 0184                    EnsurePushCapacity();
 0185                    _continuationCount = _count--;
 0186                }
 0187                else if (--_count == 0)
 0188                {
 189                    // reached the root, no need to copy frames.
 0190                    return;
 191                }
 192
 0193                _stack[_count] = Current;
 0194                Current = _stack[_count - 1];
 0195            }
 196            else
 34197            {
 34198                Debug.Assert(_continuationCount == 0);
 199
 34200                if (--_count > 0)
 0201                {
 0202                    Current = _stack[_count - 1];
 0203                }
 34204            }
 34205        }
 206
 207        /// <summary>
 208        /// Configures the current stack frame for a polymorphic converter.
 209        /// </summary>
 210        public JsonConverter InitializePolymorphicReEntry(JsonTypeInfo derivedJsonTypeInfo)
 0211        {
 0212            Debug.Assert(!IsContinuation);
 0213            Debug.Assert(Current.PolymorphicJsonTypeInfo == null);
 0214            Debug.Assert(Current.PolymorphicSerializationState == PolymorphicSerializationState.None);
 215
 0216            Current.PolymorphicJsonTypeInfo = Current.JsonTypeInfo;
 0217            Current.JsonTypeInfo = derivedJsonTypeInfo;
 0218            Current.JsonPropertyInfo = derivedJsonTypeInfo.PropertyInfoForTypeInfo;
 0219            Current.NumberHandling ??= Current.JsonPropertyInfo.NumberHandling;
 0220            Current.PolymorphicSerializationState = PolymorphicSerializationState.PolymorphicReEntryStarted;
 0221            SetConstructorArgumentState();
 222
 0223            return derivedJsonTypeInfo.Converter;
 0224        }
 225
 226
 227        /// <summary>
 228        /// Configures the current frame for a continuation of a polymorphic converter.
 229        /// </summary>
 230        public JsonConverter ResumePolymorphicReEntry()
 0231        {
 0232            Debug.Assert(Current.PolymorphicJsonTypeInfo != null);
 0233            Debug.Assert(Current.PolymorphicSerializationState == PolymorphicSerializationState.PolymorphicReEntrySuspen
 234
 235            // Swap out the two values as we resume the polymorphic converter
 0236            (Current.JsonTypeInfo, Current.PolymorphicJsonTypeInfo) = (Current.PolymorphicJsonTypeInfo, Current.JsonType
 0237            Current.PolymorphicSerializationState = PolymorphicSerializationState.PolymorphicReEntryStarted;
 0238            return Current.JsonTypeInfo.Converter;
 0239        }
 240
 241        /// <summary>
 242        /// Updates frame state after a polymorphic converter has returned.
 243        /// </summary>
 244        public void ExitPolymorphicConverter(bool success)
 0245        {
 0246            Debug.Assert(Current.PolymorphicJsonTypeInfo != null);
 0247            Debug.Assert(Current.PolymorphicSerializationState == PolymorphicSerializationState.PolymorphicReEntryStarte
 248
 249            // Swap out the two values as we exit the polymorphic converter
 0250            (Current.JsonTypeInfo, Current.PolymorphicJsonTypeInfo) = (Current.PolymorphicJsonTypeInfo, Current.JsonType
 0251            Current.PolymorphicSerializationState = success ? PolymorphicSerializationState.None : PolymorphicSerializat
 0252        }
 253
 254        // Return a JSONPath using simple dot-notation when possible. When special characters are present, bracket-notat
 255        // $.x.y[0].z
 256        // $['PropertyName.With.Special.Chars']
 257        public string JsonPath()
 57498258        {
 57498259            StringBuilder sb = new StringBuilder("$");
 260
 57498261            (int frameCount, bool includeCurrentFrame) = _continuationCount switch
 57498262            {
 57498263                0 => (_count - 1, true), // Not a continuation, report previous frames and Current.
 0264                1 => (0, true), // Continuation of depth 1, just report Current frame.
 0265                int c => (c, false) // Continuation of depth > 1, report the entire stack.
 57498266            };
 267
 115012268            for (int i = 0; i < frameCount; i++)
 8269            {
 8270                AppendStackFrame(sb, ref _stack[i]);
 8271            }
 272
 57498273            if (includeCurrentFrame)
 57498274            {
 57498275                AppendStackFrame(sb, ref Current);
 57498276            }
 277
 57498278            return sb.ToString();
 279
 280            static void AppendStackFrame(StringBuilder sb, ref ReadStackFrame frame)
 57506281            {
 282                // Append the property name.
 57506283                string? propertyName = GetPropertyName(ref frame);
 57506284                AppendPropertyName(sb, propertyName);
 285
 57506286                if (frame.JsonTypeInfo != null && frame.IsProcessingEnumerable())
 2276287                {
 2276288                    if (frame.ReturnValue is not IEnumerable enumerable)
 2026289                    {
 2026290                        return;
 291                    }
 292
 293                    // For continuation scenarios only, before or after all elements are read, the exception is not with
 250294                    if (frame.ObjectState == StackFrameObjectState.None ||
 250295                        frame.ObjectState == StackFrameObjectState.CreatedObject ||
 250296                        frame.ObjectState == StackFrameObjectState.ReadElements)
 182297                    {
 182298                        sb.Append('[');
 182299                        sb.Append(GetCount(enumerable));
 182300                        sb.Append(']');
 182301                    }
 250302                }
 57506303            }
 304
 305            static int GetCount(IEnumerable enumerable)
 182306            {
 182307                if (enumerable is ICollection collection)
 182308                {
 182309                    return collection.Count;
 310                }
 311
 0312                int count = 0;
 0313                IEnumerator enumerator = enumerable.GetEnumerator();
 0314                while (enumerator.MoveNext())
 0315                {
 0316                    count++;
 0317                }
 318
 0319                return count;
 182320            }
 321
 322            static void AppendPropertyName(StringBuilder sb, string? propertyName)
 57506323            {
 57506324                if (propertyName != null)
 0325                {
 0326                    if (propertyName.AsSpan().ContainsSpecialCharacters())
 0327                    {
 0328                        sb.Append(@"['");
 0329                        sb.AppendEscapedPropertyName(propertyName);
 0330                        sb.Append(@"']");
 0331                    }
 332                    else
 0333                    {
 0334                        sb.Append('.');
 0335                        sb.Append(propertyName);
 0336                    }
 0337                }
 57506338            }
 339
 340            static string? GetPropertyName(ref ReadStackFrame frame)
 57506341            {
 57506342                string? propertyName = null;
 343
 344                // Attempt to get the JSON property name from the frame.
 57506345                byte[]? utf8PropertyName = frame.JsonPropertyName;
 57506346                if (utf8PropertyName == null)
 57506347                {
 57506348                    if (frame.JsonPropertyNameAsString != null)
 0349                    {
 350                        // Attempt to get the JSON property name set manually for dictionary
 351                        // keys and KeyValuePair property names.
 0352                        propertyName = frame.JsonPropertyNameAsString;
 0353                    }
 354                    else
 57506355                    {
 356                        // Attempt to get the JSON property name from the JsonPropertyInfo or JsonParameterInfo.
 57506357                        utf8PropertyName = frame.JsonPropertyInfo?.NameAsUtf8Bytes ??
 57506358                            frame.CtorArgumentState?.JsonParameterInfo?.JsonNameAsUtf8Bytes;
 57506359                    }
 57506360                }
 361
 57506362                if (utf8PropertyName != null)
 0363                {
 0364                    propertyName = Encoding.UTF8.GetString(utf8PropertyName);
 0365                }
 366
 57506367                return propertyName;
 57506368            }
 57498369        }
 370
 371        // Traverses the stack for the outermost object being deserialized using constructor parameters
 372        // Only called when calculating exception information.
 373        public JsonTypeInfo GetTopJsonTypeInfoWithParameterizedConstructor()
 0374        {
 0375            Debug.Assert(!IsContinuation);
 376
 0377            for (int i = 0; i < _count - 1; i++)
 0378            {
 0379                if (_stack[i].JsonTypeInfo.UsesParameterizedConstructor)
 0380                {
 0381                    return _stack[i].JsonTypeInfo;
 382                }
 0383            }
 384
 0385            Debug.Assert(Current.JsonTypeInfo.UsesParameterizedConstructor);
 0386            return Current.JsonTypeInfo;
 0387        }
 388
 389        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 390        private void SetConstructorArgumentState()
 1736391        {
 1736392            if (Current.JsonTypeInfo.UsesParameterizedConstructor)
 234393            {
 234394                Current.CtorArgumentState ??= new();
 234395            }
 1736396        }
 397
 398        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 0399        private string DebuggerDisplay => $"Path = {JsonPath()}, Current = ConverterStrategy.{Current.JsonTypeInfo?.Conv
 400    }
 401}