< Summary

Line coverage
37%
Covered lines: 82
Uncovered lines: 139
Coverable lines: 221
Total lines: 410
Line coverage: 37.1%
Branch coverage
31%
Covered branches: 33
Total branches: 106
Branch coverage: 31.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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
 1436            => 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>
 2369856        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()
 091        {
 092            if (_stack is null)
 093            {
 094                _stack = new ReadStackFrame[4];
 095            }
 096            else if (_count - 1 == _stack.Length)
 097            {
 098                Array.Resize(ref _stack, 2 * _stack.Length);
 099            }
 0100        }
 101
 102        internal void Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation = false)
 23206103        {
 23206104            JsonSerializerOptions options = jsonTypeInfo.Options;
 23206105            if (options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.Preserve)
 0106            {
 0107                ReferenceResolver = options.ReferenceHandler!.CreateResolver(writing: false);
 0108                PreserveReferences = true;
 0109            }
 110
 23206111            Current.JsonTypeInfo = jsonTypeInfo;
 23206112            Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 23206113            Current.NumberHandling = Current.JsonPropertyInfo.EffectiveNumberHandling;
 23206114            Current.CanContainMetadata = PreserveReferences || jsonTypeInfo.PolymorphicTypeResolver?.UsesTypeDiscriminat
 23206115                || (jsonTypeInfo.PolymorphicTypeResolver is not null && jsonTypeInfo.TypeClassifier is not null);
 23206116            SupportContinuation = supportContinuation;
 23206117        }
 118
 119        public void Push()
 38120        {
 38121            if (_continuationCount == 0)
 38122            {
 38123                if (_count == 0)
 38124                {
 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.
 38128                    _count = 1;
 38129                }
 130                else
 0131                {
 0132                    JsonTypeInfo jsonTypeInfo = Current.JsonPropertyInfo?.JsonTypeInfo ?? Current.CtorArgumentState!.Jso
 0133                    JsonNumberHandling? numberHandling = Current.NumberHandling;
 134
 0135                    EnsurePushCapacity();
 0136                    _stack[_count - 1] = Current;
 0137                    Current = default;
 0138                    _count++;
 139
 0140                    Current.JsonTypeInfo = jsonTypeInfo;
 0141                    Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 142                    // Allow number handling on property to win over handling on type.
 0143                    Current.NumberHandling = numberHandling ?? Current.JsonPropertyInfo.EffectiveNumberHandling;
 0144                    Current.CanContainMetadata = PreserveReferences || jsonTypeInfo.PolymorphicTypeResolver?.UsesTypeDis
 0145                        || (jsonTypeInfo.PolymorphicTypeResolver is not null && jsonTypeInfo.TypeClassifier is not null)
 0146                }
 38147            }
 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
 38165            SetConstructorArgumentState();
 166#if DEBUG
 167            // Ensure the method is always exercised in debug builds.
 38168            _ = JsonPath();
 169#endif
 38170        }
 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()
 23244266        {
 23244267            StringBuilder sb = new StringBuilder("$");
 268
 23244269            (int frameCount, bool includeCurrentFrame) = _continuationCount switch
 23244270            {
 23244271                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.
 23244274            };
 275
 46488276            for (int i = 0; i < frameCount; i++)
 0277            {
 0278                AppendStackFrame(sb, ref _stack[i]);
 0279            }
 280
 23244281            if (includeCurrentFrame)
 23244282            {
 23244283                AppendStackFrame(sb, ref Current);
 23244284            }
 285
 23244286            return sb.ToString();
 287
 288            static void AppendStackFrame(StringBuilder sb, ref ReadStackFrame frame)
 23244289            {
 290                // Append the property name.
 23244291                string? propertyName = GetPropertyName(ref frame);
 23244292                AppendPropertyName(sb, propertyName);
 293
 23244294                if (frame.JsonTypeInfo is not null && frame.IsProcessingEnumerable())
 694295                {
 694296                    if (frame.ReturnValue is not IEnumerable enumerable)
 680297                    {
 680298                        return;
 299                    }
 300
 301                    // For continuation scenarios only, before or after all elements are read, the exception is not with
 14302                    if (frame.ObjectState == StackFrameObjectState.None ||
 14303                        frame.ObjectState == StackFrameObjectState.CreatedObject ||
 14304                        frame.ObjectState == StackFrameObjectState.ReadElements)
 14305                    {
 14306                        sb.Append('[');
 14307                        sb.Append(GetCount(enumerable));
 14308                        sb.Append(']');
 14309                    }
 14310                }
 23244311            }
 312
 313            static int GetCount(IEnumerable enumerable)
 14314            {
 14315                if (enumerable is ICollection collection)
 14316                {
 14317                    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;
 14328            }
 329
 330            static void AppendPropertyName(StringBuilder sb, string? propertyName)
 23244331            {
 23244332                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                }
 23244346            }
 347
 348            static string? GetPropertyName(ref ReadStackFrame frame)
 23244349            {
 23244350                string? propertyName = null;
 351
 352                // Attempt to get the JSON property name from the frame.
 23244353                byte[]? utf8PropertyName = frame.JsonPropertyName;
 23244354                if (utf8PropertyName is null)
 23244355                {
 23244356                    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
 23244363                    {
 364                        // Attempt to get the JSON property name from the JsonPropertyInfo or JsonParameterInfo.
 23244365                        utf8PropertyName = frame.JsonPropertyInfo?.NameAsUtf8Bytes ??
 23244366                            frame.CtorArgumentState?.JsonParameterInfo?.JsonNameAsUtf8Bytes;
 23244367                    }
 23244368                }
 369
 23244370                if (utf8PropertyName is not null)
 0371                {
 0372                    propertyName = Encoding.UTF8.GetString(utf8PropertyName);
 0373                }
 374
 23244375                return propertyName;
 23244376            }
 23244377        }
 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()
 38399        {
 38400            if (Current.JsonTypeInfo.UsesParameterizedConstructor)
 6401            {
 6402                Current.CtorArgumentState ??= new();
 6403            }
 38404        }
 405
 406        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 0407        private string DebuggerDisplay => $"Path = {JsonPath()}, Current = ConverterStrategy.{Current.JsonTypeInfo?.Conv
 408    }
 409}
 410