< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 235
Coverable lines: 235
Total lines: 476
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 109
Branch coverage: 0%
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\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.Collections.Generic;
 6using System.Diagnostics;
 7using System.IO.Pipelines;
 8using System.Runtime.ExceptionServices;
 9using System.Runtime.InteropServices;
 10using System.Text.Json.Serialization;
 11using System.Text.Json.Serialization.Metadata;
 12using System.Threading;
 13using System.Threading.Tasks;
 14
 15namespace System.Text.Json
 16{
 17    [StructLayout(LayoutKind.Auto)]
 18    [DebuggerDisplay("{DebuggerDisplay,nq}")]
 19    internal struct WriteStack
 20    {
 021        public readonly int CurrentDepth => _count;
 22
 23        /// <summary>
 24        /// Exposes the stack frame that is currently active.
 25        /// </summary>
 26        public WriteStackFrame Current;
 27
 28        /// <summary>
 29        /// Gets the parent stack frame, if it exists.
 30        /// </summary>
 31        public readonly ref WriteStackFrame Parent
 32        {
 33            get
 34            {
 35                Debug.Assert(_count - _indexOffset > 0);
 36                Debug.Assert(_stack is not null);
 37                return ref _stack[_count - _indexOffset - 1];
 38            }
 39        }
 40
 41        /// <summary>
 42        /// Buffer containing all frames in the stack. For performance it is only populated for serialization depths > 1
 43        /// </summary>
 44        private WriteStackFrame[] _stack;
 45
 46        /// <summary>
 47        /// Tracks the current depth of the stack.
 48        /// </summary>
 49        private int _count;
 50
 51        /// <summary>
 52        /// If not zero, indicates that the stack is part of a re-entrant continuation of given depth.
 53        /// </summary>
 54        private int _continuationCount;
 55
 56        /// <summary>
 57        /// Offset used to derive the index of the current frame in the stack buffer from the current value of <see cref
 58        /// following the formula currentIndex := _count - _indexOffset.
 59        /// Value can vary between 0 or 1 depending on whether we need to allocate a new frame on the first Push() opera
 60        /// which can happen if the root converter is polymorphic.
 61        /// </summary>
 62        private byte _indexOffset;
 63
 64        /// <summary>
 65        /// Cancellation token used by converters performing async serialization (e.g. IAsyncEnumerable)
 66        /// </summary>
 67        public CancellationToken CancellationToken;
 68
 69        /// <summary>
 70        /// In the case of async serialization, used by resumable converters to signal that
 71        /// the current buffer contents should not be flushed to the underlying stream.
 72        /// </summary>
 73        public bool SuppressFlush;
 74
 75        /// <summary>
 76        /// Stores a pending task that a resumable converter depends on to continue work.
 77        /// It must be awaited by the root context before serialization is resumed.
 78        /// </summary>
 79        public Task? PendingTask;
 80
 81        /// <summary>
 82        /// List of completed IAsyncDisposables that have been scheduled for disposal by converters.
 83        /// </summary>
 84        public List<IAsyncDisposable>? CompletedAsyncDisposables;
 85
 86        /// <summary>
 87        /// The amount of bytes to write before the underlying Stream should be flushed and the
 88        /// current buffer adjusted to remove the processed bytes.
 89        /// </summary>
 90        public int FlushThreshold;
 91
 92        public PipeWriter? PipeWriter;
 93
 94        /// <summary>
 95        /// Indicates that the state still contains suspended frames waiting re-entry.
 96        /// </summary>
 097        public readonly bool IsContinuation => _continuationCount != 0;
 98
 99        // The bag of preservable references.
 100        public ReferenceResolver ReferenceResolver;
 101
 102        /// <summary>
 103        /// Internal flag to let us know that we need to read ahead in the inner read loop.
 104        /// </summary>
 105        public bool SupportContinuation;
 106
 107        /// <summary>
 108        /// Internal flag indicating that async serialization is supported. Implies `SupportContinuation`.
 109        /// </summary>
 110        public bool SupportAsync;
 111
 112        /// <summary>
 113        /// Stores a reference id that has been calculated for a newly serialized object.
 114        /// </summary>
 115        public string? NewReferenceId;
 116
 117        /// <summary>
 118        /// Indicates that the next converter is polymorphic and must serialize a type discriminator.
 119        /// </summary>
 120        public object? PolymorphicTypeDiscriminator;
 121
 122        /// <summary>
 123        /// The polymorphic type resolver used by the next converter.
 124        /// </summary>
 125        public PolymorphicTypeResolver? PolymorphicTypeResolver;
 126
 127        /// <summary>
 128        /// Whether the current frame needs to write out any metadata.
 129        /// </summary>
 0130        public readonly bool CurrentContainsMetadata => NewReferenceId != null || PolymorphicTypeDiscriminator != null;
 131
 132        private void EnsurePushCapacity()
 0133        {
 0134            if (_stack is null)
 0135            {
 0136                _stack = new WriteStackFrame[4];
 0137            }
 0138            else if (_count - _indexOffset == _stack.Length)
 0139            {
 0140                Array.Resize(ref _stack, 2 * _stack.Length);
 0141            }
 0142        }
 143
 144        internal void Initialize(
 145            JsonTypeInfo jsonTypeInfo,
 146            object? rootValueBoxed = null,
 147            bool supportContinuation = false,
 148            bool supportAsync = false)
 0149        {
 0150            Debug.Assert(!supportAsync || supportContinuation, "supportAsync must imply supportContinuation");
 0151            Debug.Assert(!IsContinuation);
 0152            Debug.Assert(CurrentDepth == 0);
 153
 0154            Current.JsonTypeInfo = jsonTypeInfo;
 0155            Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 0156            Current.NumberHandling = Current.JsonPropertyInfo.EffectiveNumberHandling;
 0157            SupportContinuation = supportContinuation;
 0158            SupportAsync = supportAsync;
 159
 0160            JsonSerializerOptions options = jsonTypeInfo.Options;
 0161            if (options.ReferenceHandlingStrategy != JsonKnownReferenceHandler.Unspecified)
 0162            {
 0163                Debug.Assert(options.ReferenceHandler != null);
 0164                ReferenceResolver = options.ReferenceHandler.CreateResolver(writing: true);
 165
 0166                if (options.ReferenceHandlingStrategy == JsonKnownReferenceHandler.IgnoreCycles &&
 0167                    rootValueBoxed is not null && jsonTypeInfo.Type.IsValueType)
 0168                {
 169                    // Root object is a boxed value type, we need to push it to the reference stack before starting the 
 0170                    ReferenceResolver.PushReferenceForCycleDetection(rootValueBoxed);
 0171                }
 0172            }
 0173        }
 174
 175        /// <summary>
 176        /// Gets the nested JsonTypeInfo before resolving any polymorphic converters
 177        /// </summary>
 178        public readonly JsonTypeInfo PeekNestedJsonTypeInfo()
 0179        {
 0180            Debug.Assert(Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStarte
 0181            return _count == 0 ? Current.JsonTypeInfo : Current.JsonPropertyInfo!.JsonTypeInfo;
 0182        }
 183
 184        public void Push()
 0185        {
 0186            Debug.Assert(_continuationCount == 0 || _count < _continuationCount);
 187
 0188            if (_continuationCount == 0)
 0189            {
 0190                Debug.Assert(Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntrySu
 191
 0192                if (_count == 0 && Current.PolymorphicSerializationState == PolymorphicSerializationState.None)
 0193                {
 194                    // Perf enhancement: do not create a new stackframe on the first push operation
 195                    // unless the converter has primed the current frame for polymorphic dispatch.
 0196                    _count = 1;
 0197                    _indexOffset = 1; // currentIndex := _count - 1;
 0198                }
 199                else
 0200                {
 0201                    JsonTypeInfo jsonTypeInfo = Current.GetNestedJsonTypeInfo();
 0202                    JsonNumberHandling? numberHandling = Current.NumberHandling;
 203
 0204                    EnsurePushCapacity();
 0205                    _stack[_count - _indexOffset] = Current;
 0206                    Current = default;
 0207                    _count++;
 208
 0209                    Current.JsonTypeInfo = jsonTypeInfo;
 0210                    Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
 211                    // Allow number handling on property to win over handling on type.
 0212                    Current.NumberHandling = numberHandling ?? Current.JsonPropertyInfo.EffectiveNumberHandling;
 0213                }
 0214            }
 215            else
 0216            {
 217                // We are re-entering a continuation, adjust indices accordingly
 0218                if (_count++ > 0 || _indexOffset == 0)
 0219                {
 0220                    Current = _stack[_count - _indexOffset];
 0221                }
 222
 223                // check if we are done
 0224                if (_continuationCount == _count)
 0225                {
 0226                    _continuationCount = 0;
 0227                }
 0228            }
 229
 230#if DEBUG
 231            // Ensure the method is always exercised in debug builds.
 0232            _ = PropertyPath();
 233#endif
 0234        }
 235
 236        public void Pop(bool success)
 0237        {
 0238            Debug.Assert(_count > 0);
 0239            Debug.Assert(_continuationCount == 0 || _count < _continuationCount);
 240
 0241            if (!success)
 0242            {
 243                // Check if we need to initialize the continuation.
 0244                if (_continuationCount == 0)
 0245                {
 0246                    if (_count == 1 && _indexOffset > 0)
 0247                    {
 248                        // No need to copy any frames here.
 0249                        _continuationCount = 1;
 0250                        _count = 0;
 0251                        return;
 252                    }
 253
 254                    // Need to push the Current frame to the stack,
 255                    // ensure that we have sufficient capacity.
 0256                    EnsurePushCapacity();
 0257                    _continuationCount = _count--;
 0258                }
 0259                else if (--_count == 0 && _indexOffset > 0)
 0260                {
 261                    // reached the root, no need to copy frames.
 0262                    return;
 263                }
 264
 0265                int currentIndex = _count - _indexOffset;
 0266                _stack[currentIndex + 1] = Current;
 0267                Current = _stack[currentIndex];
 0268            }
 269            else
 0270            {
 0271                Debug.Assert(_continuationCount == 0);
 272
 0273                if (--_count > 0 || _indexOffset == 0)
 0274                {
 0275                    Current = _stack[_count - _indexOffset];
 0276                }
 0277            }
 0278        }
 279
 280        public void AddCompletedAsyncDisposable(IAsyncDisposable asyncDisposable)
 0281            => (CompletedAsyncDisposables ??= new List<IAsyncDisposable>()).Add(asyncDisposable);
 282
 283        // Asynchronously dispose of any AsyncDisposables that have been scheduled for disposal
 284        public readonly async ValueTask DisposeCompletedAsyncDisposables()
 0285        {
 0286            Debug.Assert(CompletedAsyncDisposables?.Count > 0);
 0287            Exception? exception = null;
 288
 0289            foreach (IAsyncDisposable asyncDisposable in CompletedAsyncDisposables)
 0290            {
 291                try
 0292                {
 0293                    await asyncDisposable.DisposeAsync().ConfigureAwait(false);
 0294                }
 0295                catch (Exception e)
 0296                {
 0297                    exception = e;
 0298                }
 0299            }
 300
 0301            if (exception is not null)
 0302            {
 0303                ExceptionDispatchInfo.Capture(exception).Throw();
 304            }
 305
 0306            CompletedAsyncDisposables.Clear();
 0307        }
 308
 309        /// <summary>
 310        /// Walks the stack cleaning up any leftover IDisposables
 311        /// in the event of an exception on serialization
 312        /// </summary>
 313        public readonly void DisposePendingDisposablesOnException()
 0314        {
 0315            Exception? exception = null;
 316
 0317            Debug.Assert(Current.AsyncDisposable is null);
 0318            DisposeFrame(Current.CollectionEnumerator, ref exception);
 319
 0320            if (_stack is not null)
 0321            {
 0322                int currentIndex = _count - _indexOffset;
 0323                int stackSize = Math.Max(currentIndex, _continuationCount);
 0324                for (int i = 0; i < stackSize; i++)
 0325                {
 0326                    Debug.Assert(_stack[i].AsyncDisposable is null);
 327
 0328                    if (i == currentIndex)
 0329                    {
 330                        // Matches the entry in Current, skip to avoid double disposal.
 0331                        Debug.Assert(_stack[i].CollectionEnumerator is null || ReferenceEquals(Current.CollectionEnumera
 0332                        continue;
 333                    }
 334
 0335                    DisposeFrame(_stack[i].CollectionEnumerator, ref exception);
 0336                }
 0337            }
 338
 0339            if (exception is not null)
 0340            {
 0341                ExceptionDispatchInfo.Capture(exception).Throw();
 342            }
 343
 344            static void DisposeFrame(IEnumerator? collectionEnumerator, ref Exception? exception)
 0345            {
 346                try
 0347                {
 0348                    if (collectionEnumerator is IDisposable disposable)
 0349                    {
 0350                        disposable.Dispose();
 0351                    }
 0352                }
 0353                catch (Exception e)
 0354                {
 0355                    exception = e;
 0356                }
 0357            }
 0358        }
 359
 360        /// <summary>
 361        /// Walks the stack cleaning up any leftover I(Async)Disposables
 362        /// in the event of an exception on async serialization
 363        /// </summary>
 364        public readonly async ValueTask DisposePendingDisposablesOnExceptionAsync()
 0365        {
 0366            Exception? exception = null;
 367
 0368            exception = await DisposeFrame(Current.CollectionEnumerator, Current.AsyncDisposable, exception).ConfigureAw
 369
 0370            if (_stack is not null)
 0371            {
 0372                Debug.Assert(_continuationCount == 0 || _count < _continuationCount);
 0373                int currentIndex = _count - _indexOffset;
 0374                int stackSize = Math.Max(currentIndex, _continuationCount);
 0375                for (int i = 0; i < stackSize; i++)
 0376                {
 0377                    if (i == currentIndex)
 0378                    {
 379                        // Matches the entry in Current, skip to avoid double disposal.
 0380                        Debug.Assert(_stack[i].CollectionEnumerator is null || ReferenceEquals(Current.CollectionEnumera
 0381                        Debug.Assert(_stack[i].AsyncDisposable is null || ReferenceEquals(Current.AsyncDisposable, _stac
 0382                        continue;
 383                    }
 384
 0385                    exception = await DisposeFrame(_stack[i].CollectionEnumerator, _stack[i].AsyncDisposable, exception)
 0386                }
 0387            }
 388
 0389            if (exception is not null)
 0390            {
 0391                ExceptionDispatchInfo.Capture(exception).Throw();
 392            }
 393
 394            static async ValueTask<Exception?> DisposeFrame(IEnumerator? collectionEnumerator, IAsyncDisposable? asyncDi
 0395            {
 0396                Debug.Assert(!(collectionEnumerator is not null && asyncDisposable is not null));
 397
 398                try
 0399                {
 0400                    if (collectionEnumerator is IDisposable disposable)
 0401                    {
 0402                        disposable.Dispose();
 0403                    }
 0404                    else if (asyncDisposable is not null)
 0405                    {
 0406                        await asyncDisposable.DisposeAsync().ConfigureAwait(false);
 0407                    }
 0408                }
 0409                catch (Exception e)
 0410                {
 0411                    exception = e;
 0412                }
 413
 0414                return exception;
 0415            }
 0416        }
 417
 418        // Return a property path as a simple JSONPath using dot-notation when possible. When special characters are pre
 419        // $.x.y.z
 420        // $['PropertyName.With.Special.Chars']
 421        public string PropertyPath()
 0422        {
 0423            StringBuilder sb = new StringBuilder("$");
 424
 0425            (int frameCount, bool includeCurrentFrame) = _continuationCount switch
 0426            {
 0427                0 => (_count - 1, true), // Not a continuation, report previous frames and Current.
 0428                1 => (0, true), // Continuation of depth 1, just report Current frame.
 0429                int c => (c, false) // Continuation of depth > 1, report the entire stack.
 0430            };
 431
 0432            for (int i = 1; i <= frameCount; i++)
 0433            {
 0434                AppendStackFrame(sb, ref _stack[i - _indexOffset]);
 0435            }
 436
 0437            if (includeCurrentFrame)
 0438            {
 0439                AppendStackFrame(sb, ref Current);
 0440            }
 441
 0442            return sb.ToString();
 443
 444            static void AppendStackFrame(StringBuilder sb, ref WriteStackFrame frame)
 0445            {
 446                // Append the property name. Or attempt to get the JSON property name from the property name specified i
 0447                string? propertyName =
 0448                    frame.JsonPropertyInfo?.MemberName ??
 0449                    frame.JsonPropertyNameAsString;
 450
 0451                AppendPropertyName(sb, propertyName);
 0452            }
 453
 454            static void AppendPropertyName(StringBuilder sb, string? propertyName)
 0455            {
 0456                if (propertyName != null)
 0457                {
 0458                    if (propertyName.AsSpan().ContainsSpecialCharacters())
 0459                    {
 0460                        sb.Append(@"['");
 0461                        sb.AppendEscapedPropertyName(propertyName);
 0462                        sb.Append(@"']");
 0463                    }
 464                    else
 0465                    {
 0466                        sb.Append('.');
 0467                        sb.Append(propertyName);
 0468                    }
 0469                }
 0470            }
 0471        }
 472
 473        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
 0474        private string DebuggerDisplay => $"Path = {PropertyPath()} Current = ConverterStrategy.{Current.JsonPropertyInf
 475    }
 476}