< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 216
Coverable lines: 216
Total lines: 442
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 101
Branch coverage: 0%
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/WriteStack.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.IO.Pipelines;
 7using System.Runtime.ExceptionServices;
 8using System.Runtime.InteropServices;
 9using System.Text.Json.Serialization;
 10using System.Text.Json.Serialization.Metadata;
 11using System.Threading;
 12using System.Threading.Tasks;
 13
 14namespace System.Text.Json
 15{
 16    [StructLayout(LayoutKind.Auto)]
 17    [DebuggerDisplay("{DebuggerDisplay,nq}")]
 18    internal struct WriteStack
 19    {
 020        public readonly int CurrentDepth => _count;
 21
 22        /// <summary>
 23        /// Exposes the stack frame that is currently active.
 24        /// </summary>
 25        public WriteStackFrame Current;
 26
 27        /// <summary>
 28        /// Gets the parent stack frame, if it exists.
 29        /// </summary>
 30        public readonly ref WriteStackFrame Parent
 31        {
 32            get
 33            {
 34                Debug.Assert(_count - _indexOffset > 0);
 35                Debug.Assert(_stack is not null);
 36                return ref _stack[_count - _indexOffset - 1];
 37            }
 38        }
 39
 40        /// <summary>
 41        /// Buffer containing all frames in the stack. For performance it is only populated for serialization depths > 1
 42        /// </summary>
 43        private WriteStackFrame[] _stack;
 44
 45        /// <summary>
 46        /// Tracks the current depth of the stack.
 47        /// </summary>
 48        private int _count;
 49
 50        /// <summary>
 51        /// If not zero, indicates that the stack is part of a re-entrant continuation of given depth.
 52        /// </summary>
 53        private int _continuationCount;
 54
 55        /// <summary>
 56        /// Offset used to derive the index of the current frame in the stack buffer from the current value of <see cref
 57        /// following the formula currentIndex := _count - _indexOffset.
 58        /// Value can vary between 0 or 1 depending on whether we need to allocate a new frame on the first Push() opera
 59        /// which can happen if the root converter is polymorphic.
 60        /// </summary>
 61        private byte _indexOffset;
 62
 63        /// <summary>
 64        /// Cancellation token used by converters performing async serialization (e.g. IAsyncEnumerable)
 65        /// </summary>
 66        public CancellationToken CancellationToken;
 67
 68        /// <summary>
 69        /// In the case of async serialization, used by resumable converters to signal that
 70        /// the current buffer contents should not be flushed to the underlying stream.
 71        /// </summary>
 72        public bool SuppressFlush;
 73
 74        /// <summary>
 75        /// Stores a pending task that a resumable converter depends on to continue work.
 76        /// It must be awaited by the root context before serialization is resumed.
 77        /// </summary>
 78        public Task? PendingTask;
 79
 80        /// <summary>
 81        /// The amount of bytes to write before the underlying Stream should be flushed and the
 82        /// current buffer adjusted to remove the processed bytes.
 83        /// </summary>
 84        public int FlushThreshold;
 85
 86        public PipeWriter? PipeWriter;
 87
 88        /// <summary>
 89        /// Indicates that the state still contains suspended frames waiting re-entry.
 90        /// </summary>
 091        public readonly bool IsContinuation => _continuationCount != 0;
 92
 93        // The bag of preservable references.
 94        public ReferenceResolver ReferenceResolver;
 95
 96        /// <summary>
 97        /// Internal flag to let us know that we need to read ahead in the inner read loop.
 98        /// </summary>
 99        public bool SupportContinuation;
 100
 101        /// <summary>
 102        /// Internal flag indicating that async serialization is supported. Implies `SupportContinuation`.
 103        /// </summary>
 104        public bool SupportAsync;
 105
 106        /// <summary>
 107        /// Stores a reference id that has been calculated for a newly serialized object.
 108        /// </summary>
 109        public string? NewReferenceId;
 110
 111        /// <summary>
 112        /// Indicates that the next converter is polymorphic and must serialize a type discriminator.
 113        /// </summary>
 114        public object? PolymorphicTypeDiscriminator;
 115
 116        /// <summary>
 117        /// The polymorphic type resolver used by the next converter.
 118        /// </summary>
 119        public PolymorphicTypeResolver? PolymorphicTypeResolver;
 120
 121        /// <summary>
 122        /// Whether the current frame needs to write out any metadata.
 123        /// </summary>
 0124        public readonly bool CurrentContainsMetadata => NewReferenceId is not null || PolymorphicTypeDiscriminator is no
 125
 126        private void EnsurePushCapacity()
 0127        {
 0128            if (_stack is null)
 0129            {
 0130                _stack = new WriteStackFrame[4];
 0131            }
 0132            else if (_count - _indexOffset == _stack.Length)
 0133            {
 0134                Array.Resize(ref _stack, 2 * _stack.Length);
 0135            }
 0136        }
 137
 138        internal void Initialize(
 139            JsonTypeInfo jsonTypeInfo,
 140            object? rootValueBoxed = null,
 141            bool supportContinuation = false,
 142            bool supportAsync = false)
 0143        {
 0144            Debug.Assert(!supportAsync || supportContinuation, "supportAsync must imply supportContinuation");
 0145            Debug.Assert(!IsContinuation);
 0146            Debug.Assert(CurrentDepth == 0);
 147
 0148            Current.JsonTypeInfo = jsonTypeInfo;
 0149            Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 0150            Current.NumberHandling = Current.JsonPropertyInfo.EffectiveNumberHandling;
 0151            SupportContinuation = supportContinuation;
 0152            SupportAsync = supportAsync;
 153
 0154            JsonSerializerOptions options = jsonTypeInfo.Options;
 0155            if (options.ReferenceHandlingStrategy != JsonKnownReferenceHandler.Unspecified)
 0156            {
 0157                Debug.Assert(options.ReferenceHandler is not null);
 0158                ReferenceResolver = options.ReferenceHandler.CreateResolver(writing: true);
 159
 0160                if (options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.IgnoreCycles &&
 0161                    rootValueBoxed is not null && jsonTypeInfo.Type.IsValueType)
 0162                {
 163                    // Root object is a boxed value type, we need to push it to the reference stack before starting the 
 0164                    ReferenceResolver.PushReferenceForCycleDetection(rootValueBoxed);
 0165                }
 0166            }
 0167        }
 168
 169        /// <summary>
 170        /// Gets the nested JsonTypeInfo before resolving any polymorphic converters
 171        /// </summary>
 172        public readonly JsonTypeInfo PeekNestedJsonTypeInfo()
 0173        {
 0174            Debug.Assert(Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStarte
 0175            return _count == 0 ? Current.JsonTypeInfo : Current.JsonPropertyInfo!.JsonTypeInfo;
 0176        }
 177
 178        public void Push()
 0179        {
 0180            Debug.Assert(_continuationCount == 0 || _count < _continuationCount);
 181
 0182            if (_continuationCount == 0)
 0183            {
 0184                Debug.Assert(Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntrySu
 185
 0186                if (_count == 0 && Current.PolymorphicSerializationState == PolymorphicSerializationState.None)
 0187                {
 188                    // Perf enhancement: do not create a new stackframe on the first push operation
 189                    // unless the converter has primed the current frame for polymorphic dispatch.
 0190                    _count = 1;
 0191                    _indexOffset = 1; // currentIndex := _count - 1;
 0192                }
 193                else
 0194                {
 0195                    JsonTypeInfo jsonTypeInfo = Current.GetNestedJsonTypeInfo();
 0196                    JsonNumberHandling? numberHandling = Current.NumberHandling;
 197
 0198                    EnsurePushCapacity();
 0199                    _stack[_count - _indexOffset] = Current;
 0200                    Current = default;
 0201                    _count++;
 202
 0203                    Current.JsonTypeInfo = jsonTypeInfo;
 0204                    Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 205                    // Allow number handling on property to win over handling on type.
 0206                    Current.NumberHandling = numberHandling ?? Current.JsonPropertyInfo.EffectiveNumberHandling;
 0207                }
 0208            }
 209            else
 0210            {
 211                // We are re-entering a continuation, adjust indices accordingly
 0212                if (_count++ > 0 || _indexOffset == 0)
 0213                {
 0214                    Current = _stack[_count - _indexOffset];
 0215                }
 216
 217                // check if we are done
 0218                if (_continuationCount == _count)
 0219                {
 0220                    _continuationCount = 0;
 0221                }
 0222            }
 223
 224#if DEBUG
 225            // Ensure the method is always exercised in debug builds.
 0226            _ = PropertyPath();
 227#endif
 0228        }
 229
 230        public void Pop(bool success)
 0231        {
 0232            Debug.Assert(_count > 0);
 0233            Debug.Assert(_continuationCount == 0 || _count < _continuationCount);
 234
 0235            if (!success)
 0236            {
 237                // Check if we need to initialize the continuation.
 0238                if (_continuationCount == 0)
 0239                {
 0240                    if (_count == 1 && _indexOffset > 0)
 0241                    {
 242                        // No need to copy any frames here.
 0243                        _continuationCount = 1;
 0244                        _count = 0;
 0245                        return;
 246                    }
 247
 248                    // Need to push the Current frame to the stack,
 249                    // ensure that we have sufficient capacity.
 0250                    EnsurePushCapacity();
 0251                    _continuationCount = _count--;
 0252                }
 0253                else if (--_count == 0 && _indexOffset > 0)
 0254                {
 255                    // reached the root, no need to copy frames.
 0256                    return;
 257                }
 258
 0259                int currentIndex = _count - _indexOffset;
 0260                _stack[currentIndex + 1] = Current;
 0261                Current = _stack[currentIndex];
 0262            }
 263            else
 0264            {
 0265                Debug.Assert(_continuationCount == 0);
 266
 0267                if (--_count > 0 || _indexOffset == 0)
 0268                {
 0269                    Current = _stack[_count - _indexOffset];
 0270                }
 0271            }
 0272        }
 273
 274        /// <summary>
 275        /// Walks the stack cleaning up any leftover IDisposables
 276        /// in the event of an exception on serialization
 277        /// </summary>
 278        public readonly void DisposePendingDisposablesOnException()
 0279        {
 0280            Exception? exception = null;
 281
 0282            Debug.Assert(Current.AsyncEnumerator is null);
 0283            DisposeFrame(Current.CollectionEnumerator, ref exception);
 284
 0285            if (_stack is not null)
 0286            {
 0287                int currentIndex = _count - _indexOffset;
 0288                int stackSize = Math.Max(currentIndex, _continuationCount);
 0289                for (int i = 0; i < stackSize; i++)
 0290                {
 0291                    Debug.Assert(_stack[i].AsyncEnumerator is null);
 292
 0293                    if (i == currentIndex)
 0294                    {
 295                        // Matches the entry in Current, skip to avoid double disposal.
 0296                        Debug.Assert(_stack[i].CollectionEnumerator is null || ReferenceEquals(Current.CollectionEnumera
 0297                        continue;
 298                    }
 299
 0300                    DisposeFrame(_stack[i].CollectionEnumerator, ref exception);
 0301                }
 0302            }
 303
 0304            if (exception is not null)
 0305            {
 0306                ExceptionDispatchInfo.Capture(exception).Throw();
 307            }
 308
 309            static void DisposeFrame(IEnumerator? collectionEnumerator, ref Exception? exception)
 0310            {
 311                try
 0312                {
 0313                    if (collectionEnumerator is IDisposable disposable)
 0314                    {
 0315                        disposable.Dispose();
 0316                    }
 0317                }
 0318                catch (Exception e)
 0319                {
 0320                    exception = e;
 0321                }
 0322            }
 0323        }
 324
 325        /// <summary>
 326        /// Walks the stack cleaning up any leftover I(Async)Disposables
 327        /// in the event of an exception on async serialization
 328        /// </summary>
 329        public readonly async ValueTask DisposePendingDisposablesOnExceptionAsync()
 0330        {
 0331            Exception? exception = null;
 332
 0333            exception = await DisposeFrame(Current.CollectionEnumerator, Current.AsyncEnumerator, exception).ConfigureAw
 334
 0335            if (_stack is not null)
 0336            {
 0337                Debug.Assert(_continuationCount == 0 || _count < _continuationCount);
 0338                int currentIndex = _count - _indexOffset;
 0339                int stackSize = Math.Max(currentIndex, _continuationCount);
 0340                for (int i = 0; i < stackSize; i++)
 0341                {
 0342                    if (i == currentIndex)
 0343                    {
 344                        // Matches the entry in Current, skip to avoid double disposal.
 0345                        Debug.Assert(_stack[i].CollectionEnumerator is null || ReferenceEquals(Current.CollectionEnumera
 0346                        Debug.Assert(_stack[i].AsyncEnumerator is null || ReferenceEquals(Current.AsyncEnumerator, _stac
 0347                        continue;
 348                    }
 349
 0350                    exception = await DisposeFrame(_stack[i].CollectionEnumerator, _stack[i].AsyncEnumerator, exception)
 0351                }
 0352            }
 353
 0354            if (exception is not null)
 0355            {
 0356                ExceptionDispatchInfo.Capture(exception).Throw();
 357            }
 358
 359            static async ValueTask<Exception?> DisposeFrame(IEnumerator? collectionEnumerator, object? asyncEnumerator, 
 0360            {
 0361                Debug.Assert(!(collectionEnumerator is not null && asyncEnumerator is not null));
 362
 363                try
 0364                {
 0365                    if (collectionEnumerator is IDisposable disposable)
 0366                    {
 0367                        disposable.Dispose();
 0368                    }
 0369                    else if (asyncEnumerator is IAsyncDisposable asyncDisposable)
 0370                    {
 0371                        await asyncDisposable.DisposeAsync().ConfigureAwait(false);
 0372                    }
 0373                }
 0374                catch (Exception e)
 0375                {
 0376                    exception = e;
 0377                }
 378
 0379                return exception;
 0380            }
 0381        }
 382
 383        // Return a property path as a simple JSONPath using dot-notation when possible. When special characters are pre
 384        // $.x.y.z
 385        // $['PropertyName.With.Special.Chars']
 386        public string PropertyPath()
 0387        {
 0388            StringBuilder sb = new StringBuilder("$");
 389
 0390            (int frameCount, bool includeCurrentFrame) = _continuationCount switch
 0391            {
 0392                0 => (_count - 1, true), // Not a continuation, report previous frames and Current.
 0393                1 => (0, true), // Continuation of depth 1, just report Current frame.
 0394                int c => (c, false) // Continuation of depth > 1, report the entire stack.
 0395            };
 396
 0397            for (int i = 1; i <= frameCount; i++)
 0398            {
 0399                AppendStackFrame(sb, ref _stack[i - _indexOffset]);
 0400            }
 401
 0402            if (includeCurrentFrame)
 0403            {
 0404                AppendStackFrame(sb, ref Current);
 0405            }
 406
 0407            return sb.ToString();
 408
 409            static void AppendStackFrame(StringBuilder sb, ref WriteStackFrame frame)
 0410            {
 411                // Append the property name. Or attempt to get the JSON property name from the property name specified i
 0412                string? propertyName =
 0413                    frame.JsonPropertyInfo?.MemberName ??
 0414                    frame.JsonPropertyNameAsString;
 415
 0416                AppendPropertyName(sb, propertyName);
 0417            }
 418
 419            static void AppendPropertyName(StringBuilder sb, string? propertyName)
 0420            {
 0421                if (propertyName is not null)
 0422                {
 0423                    if (propertyName.AsSpan().ContainsSpecialCharacters())
 0424                    {
 0425                        sb.Append(@"['");
 0426                        sb.AppendEscapedPropertyName(propertyName);
 0427                        sb.Append(@"']");
 0428                    }
 429                    else
 0430                    {
 0431                        sb.Append('.');
 0432                        sb.Append(propertyName);
 0433                    }
 0434                }
 0435            }
 0436        }
 437
 438        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 0439        private string DebuggerDisplay => $"Path = {PropertyPath()} Current = ConverterStrategy.{Current.JsonPropertyInf
 440    }
 441}
 442