< Summary

Information
Line coverage
37%
Covered lines: 34
Uncovered lines: 57
Coverable lines: 91
Total lines: 202
Line coverage: 37.3%
Branch coverage
8%
Covered branches: 3
Total branches: 34
Branch coverage: 8.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
PushTrue()50%2270%
PushFalse()50%2270%
PushToArray(...)0%16160%
Pop()25%4456.25%
PeekInArray()0%440%
Peek()0%220%
DoubleArray(...)0%440%
SetFirstBit()100%11100%
ResetFirstBit()100%11100%
Div32Rem(...)100%110%

File(s)

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\BitStack.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.Diagnostics;
 5using System.Runtime.CompilerServices;
 6
 7namespace System.Text.Json
 8{
 9    internal struct BitStack
 10    {
 11        // We are using a ulong to represent our nested state, so we can only
 12        // go 64 levels deep without having to allocate.
 13        private const int AllocationFreeMaxDepth = sizeof(ulong) * 8;
 14
 15        private const int DefaultInitialArraySize = 2;
 16
 17        // The backing array for the stack used when the depth exceeds AllocationFreeMaxDepth.
 18        private int[]? _array;
 19
 20        // This ulong container represents a tiny stack to track the state during nested transitions.
 21        // The first bit represents the state of the current depth (1 == object, 0 == array).
 22        // Each subsequent bit is the parent / containing type (object or array). Since this
 23        // reader does a linear scan, we only need to keep a single path as we go through the data.
 24        // This is primarily used as an optimization to avoid having to allocate an object for
 25        // depths up to 64 (which is the default max depth).
 26        private ulong _allocationFreeContainer;
 27
 28        private int _currentDepth;
 29
 30        /// <summary>
 31        /// Gets the number of elements in the stack.
 32        /// </summary>
 3395233        public readonly int CurrentDepth => _currentDepth;
 34
 35        /// <summary>
 36        /// Pushes <see langword="true"/> onto the stack.
 37        /// </summary>
 38        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 39        public void PushTrue()
 8240        {
 8241            if (_currentDepth < AllocationFreeMaxDepth)
 8242            {
 8243                _allocationFreeContainer = (_allocationFreeContainer << 1) | 1;
 8244            }
 45            else
 046            {
 047                PushToArray(true);
 048            }
 8249            _currentDepth++;
 8250        }
 51
 52        /// <summary>
 53        /// Pushes <see langword="false"/> onto the stack.
 54        /// </summary>
 55        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 56        public void PushFalse()
 135457        {
 135458            if (_currentDepth < AllocationFreeMaxDepth)
 135459            {
 135460                _allocationFreeContainer <<= 1;
 135461            }
 62            else
 063            {
 064                PushToArray(false);
 065            }
 135466            _currentDepth++;
 135467        }
 68
 69        /// <summary>
 70        /// Pushes a bit onto the stack. Allocate the bit array lazily only when it is absolutely necessary.
 71        /// </summary>
 72        /// <param name="value">The bit to push onto the stack.</param>
 73        [MethodImpl(MethodImplOptions.NoInlining)]
 74        private void PushToArray(bool value)
 075        {
 076            _array ??= new int[DefaultInitialArraySize];
 77
 078            int index = _currentDepth - AllocationFreeMaxDepth;
 79
 080            Debug.Assert(index >= 0, $"Set - Negative - index: {index}, arrayLength: {_array.Length}");
 81
 82            // Maximum possible array length if bitLength was int.MaxValue (i.e. 67_108_864)
 083            Debug.Assert(_array.Length <= int.MaxValue / 32 + 1, $"index: {index}, arrayLength: {_array.Length}");
 84
 085            int elementIndex = Div32Rem(index, out int extraBits);
 86
 87            // Grow the array when setting a bit if it isn't big enough
 88            // This way the caller doesn't have to check.
 089            if (elementIndex >= _array.Length)
 090            {
 91                // This multiplication can overflow, so cast to uint first.
 092                Debug.Assert(index >= 0 && index > (int)((uint)_array.Length * 32 - 1), $"Only grow when necessary - ind
 093                DoubleArray(elementIndex);
 094            }
 95
 096            Debug.Assert(elementIndex < _array.Length, $"Set - index: {index}, elementIndex: {elementIndex}, arrayLength
 97
 098            int newValue = _array[elementIndex];
 099            if (value)
 0100            {
 0101                newValue |= 1 << extraBits;
 0102            }
 103            else
 0104            {
 0105                newValue &= ~(1 << extraBits);
 0106            }
 0107            _array[elementIndex] = newValue;
 0108        }
 109
 110        /// <summary>
 111        /// Pops the bit at the top of the stack and returns its value.
 112        /// </summary>
 113        /// <returns>The bit that was popped.</returns>
 114        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 115        public bool Pop()
 332116        {
 332117            _currentDepth--;
 118            bool inObject;
 332119            if (_currentDepth < AllocationFreeMaxDepth)
 332120            {
 332121                _allocationFreeContainer >>= 1;
 332122                inObject = (_allocationFreeContainer & 1) != 0;
 332123            }
 0124            else if (_currentDepth == AllocationFreeMaxDepth)
 0125            {
 0126                inObject = (_allocationFreeContainer & 1) != 0;
 0127            }
 128            else
 0129            {
 130                // Decrementing depth above effectively pops the last element in the array-backed case.
 0131                inObject = PeekInArray();
 0132            }
 332133            return inObject;
 332134        }
 135
 136        /// <summary>
 137        /// If the stack has a backing array allocated, this method will find the topmost bit in the array and return it
 138        /// This should only be called if the depth is greater than AllocationFreeMaxDepth and an array has been allocat
 139        /// </summary>
 140        [MethodImpl(MethodImplOptions.NoInlining)]
 141        private readonly bool PeekInArray()
 0142        {
 0143            int index = _currentDepth - AllocationFreeMaxDepth - 1;
 0144            Debug.Assert(_array != null);
 0145            Debug.Assert(index >= 0, $"Get - Negative - index: {index}, arrayLength: {_array.Length}");
 146
 0147            int elementIndex = Div32Rem(index, out int extraBits);
 148
 0149            Debug.Assert(elementIndex < _array.Length, $"Get - index: {index}, elementIndex: {elementIndex}, arrayLength
 150
 0151            return (_array[elementIndex] & (1 << extraBits)) != 0;
 0152        }
 153
 154        /// <summary>
 155        /// Peeks at the bit at the top of the stack.
 156        /// </summary>
 157        /// <returns>The bit at the top of the stack.</returns>
 158        public readonly bool Peek()
 159            // If the stack is small enough, we can use the allocation-free container, otherwise check the allocated arr
 0160            => _currentDepth <= AllocationFreeMaxDepth ? (_allocationFreeContainer & 1) != 0 : PeekInArray();
 161
 162        private void DoubleArray(int minSize)
 0163        {
 0164            Debug.Assert(_array != null);
 0165            Debug.Assert(_array.Length < int.MaxValue / 2, $"Array too large - arrayLength: {_array.Length}");
 0166            Debug.Assert(minSize >= 0 && minSize >= _array.Length);
 167
 0168            int nextDouble = Math.Max(minSize + 1, _array.Length * 2);
 0169            Debug.Assert(nextDouble > minSize);
 170
 0171            Array.Resize(ref _array, nextDouble);
 0172        }
 173
 174        /// <summary>
 175        /// Optimization to push <see langword="true"/> as the first bit when the stack is empty.
 176        /// </summary>
 177        public void SetFirstBit()
 1932178        {
 1932179            Debug.Assert(_currentDepth == 0, "Only call SetFirstBit when depth is 0");
 1932180            _currentDepth++;
 1932181            _allocationFreeContainer = 1;
 1932182        }
 183
 184        /// <summary>
 185        /// Optimization to push <see langword="false"/> as the first bit when the stack is empty.
 186        /// </summary>
 187        public void ResetFirstBit()
 7698188        {
 7698189            Debug.Assert(_currentDepth == 0, "Only call ResetFirstBit when depth is 0");
 7698190            _currentDepth++;
 7698191            _allocationFreeContainer = 0;
 7698192        }
 193
 194        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 195        private static int Div32Rem(int number, out int remainder)
 0196        {
 0197            uint quotient = (uint)number / 32;
 0198            remainder = number & (32 - 1);   // equivalent to number % 32, since 32 is a power of 2
 0199            return (int)quotient;
 0200        }
 201    }
 202}