< Summary

Line coverage
20%
Covered lines: 127
Uncovered lines: 485
Coverable lines: 612
Total lines: 1419
Line coverage: 20.7%
Branch coverage
11%
Covered branches: 34
Total branches: 285
Branch coverage: 11.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
File 1: RequiresSpecialNumberHandlingOnWrite(...)0%220%
File 1: StableSortByKey(...)0%440%
File 1: TraverseGraphWithTopologicalSort(...)0%24240%
File 2: GetUnescapedSpan(...)0%880%
File 2: TryAdvanceWithOptionalReadAhead(...)75%44100%
File 2: TryAdvanceToNextRootLevelValueWithOptionalReadAhead(...)0%660%
File 2: TryAdvanceWithReadAhead(...)0%10100%
File 2: IsInRangeInclusive(...)100%110%
File 2: IsInRangeInclusive(...)100%11100%
File 2: IsInRangeInclusive(...)100%11100%
File 2: IsDigit(...)100%11100%
File 2: ReadWithVerify(...)100%110%
File 2: SkipWithVerify(...)100%110%
File 2: TrySkipPartial(...)100%110%
File 2: TryLookupUtf8Key(...)0%440%
File 2: IsFinite(...)100%11100%
File 2: IsFinite(...)100%11100%
File 2: ValidateInt32MaxArrayLength(...)0%220%
File 2: AreEqualJsonNumbers(...)50%161664%
File 2: ParseNumber(System.ReadOnlySpan`1<System.Byte>,System.Boolean&,System.ReadOnlySpan`1<System.Byte>&,System.ReadOnlySpan`1<System.Byte>&,System.Int32&)35.71%282842.02%
File 2: IndexOfLastLeadingZero(System.ReadOnlySpan`1<System.Byte>)0%220%
File 2: IndexOfFirstTrailingZero(System.ReadOnlySpan`1<System.Byte>)100%22100%
File 3: IsValidDateTimeOffsetParseLength(...)100%11100%
File 3: IsValidUnescapedDateTimeOffsetParseLength(...)100%110%
File 3: TryParseAsISO(...)10%101033.33%
File 3: TryParseAsISO(...)12.5%8854.54%
File 3: TryParseAsIso(...)50%6670%
File 3: TryParseDateTimeOffset(...)6.84%737312.37%
File 3: ParseOffset(System.Text.Json.JsonHelpers/DateTimeParseData&,System.ReadOnlySpan`1<System.Byte>)0%12120%
File 3: TryGetNextTwoDigits(...)0%440%
File 3: TryCreateDateTimeOffset(...)0%10100%
File 3: TryCreateDateTimeOffset(...)0%440%
File 3: TryCreateDateTimeOffsetInterpretingDataAsLocalTime(...)0%220%
File 3: TryCreateDateTime(...)0%30300%
File 4: GetEscapedPropertyNameSection(...)50%2275%
File 4: EscapeValue(...)0%660%
File 4: GetEscapedPropertyNameSection(...)0%660%
File 4: GetPropertyNameSection(...)100%11100%

File(s)

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\Common\JsonHelpers.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.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Runtime.InteropServices;
 8using System.Text.Json.Serialization;
 9
 10namespace System.Text.Json
 11{
 12    internal static partial class JsonHelpers
 13    {
 14#if !NET
 15        /// <summary>
 16        /// netstandard/netfx polyfill for Dictionary.TryAdd
 17        /// </summary>
 18        public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value) where 
 19        {
 20            if (!dictionary.ContainsKey(key))
 21            {
 22                dictionary[key] = value;
 23                return true;
 24            }
 25
 26            return false;
 27        }
 28
 29        /// <summary>
 30        /// netstandard/netfx polyfill for IDictionary.TryAdd
 31        /// </summary>
 32        public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value) where
 33        {
 34            if (!dictionary.ContainsKey(key))
 35            {
 36                dictionary[key] = value;
 37                return true;
 38            }
 39
 40            return false;
 41        }
 42
 43        /// <summary>
 44        /// netstandard/netfx polyfill for Queue.TryDequeue
 45        /// </summary>
 46        public static bool TryDequeue<T>(this Queue<T> queue, [NotNullWhen(true)] out T? result)
 47        {
 48            if (queue.Count > 0)
 49            {
 50                result = queue.Dequeue();
 51                return true;
 52            }
 53
 54            result = default;
 55            return false;
 56        }
 57#endif
 58
 59        internal static bool RequiresSpecialNumberHandlingOnWrite(JsonNumberHandling? handling)
 060        {
 061            return handling != null
 062                ? (handling.Value & (JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLitera
 063                : false;
 064        }
 65
 66        /// <summary>
 67        /// Provides an in-place, stable sorting implementation for List.
 68        /// </summary>
 69        internal static void StableSortByKey<T, TKey>(this List<T> items, Func<T, TKey> keySelector)
 70            where TKey : unmanaged, IComparable<TKey>
 071        {
 72#if NET
 073            Span<T> span = CollectionsMarshal.AsSpan(items);
 74
 75            // Tuples implement lexical ordering OOTB which can be used to encode stable sorting
 76            // using the actual key as the first element and index as the second element.
 77            const int StackallocThreshold = 32;
 078            Span<(TKey, int)> keys = span.Length <= StackallocThreshold
 079                ? (stackalloc (TKey, int)[StackallocThreshold]).Slice(0, span.Length)
 080                : new (TKey, int)[span.Length];
 81
 082            for (int i = 0; i < keys.Length; i++)
 083            {
 084                keys[i] = (keySelector(span[i]), i);
 085            }
 86
 087            MemoryExtensions.Sort(keys, span);
 88#else
 89            T[] arrayCopy = items.ToArray();
 90            (TKey, int)[] keys = new (TKey, int)[arrayCopy.Length];
 91            for (int i = 0; i < keys.Length; i++)
 92            {
 93                keys[i] = (keySelector(arrayCopy[i]), i);
 94            }
 95
 96            Array.Sort(keys, arrayCopy);
 97            items.Clear();
 98            items.AddRange(arrayCopy);
 99#endif
 0100        }
 101
 102        /// <summary>
 103        /// Traverses a DAG and returns its nodes applying topological sorting to the result.
 104        /// </summary>
 105        public static T[] TraverseGraphWithTopologicalSort<T>(T entryNode, Func<T, ICollection<T>> getChildren, IEqualit
 106            where T : notnull
 0107        {
 0108            comparer ??= EqualityComparer<T>.Default;
 109
 110            // Implements Kahn's algorithm.
 111            // Step 1. Traverse and build the graph, labeling each node with an integer.
 112
 0113            var nodes = new List<T> { entryNode }; // the integer-to-node mapping
 0114            var nodeIndex = new Dictionary<T, int>(comparer) { [entryNode] = 0 }; // the node-to-integer mapping
 0115            var adjacency = new List<bool[]?>(); // the growable adjacency matrix
 0116            var childlessQueue = new Queue<int>(); // the queue of nodes without children or whose children have been vi
 117
 0118            for (int i = 0; i < nodes.Count; i++)
 0119            {
 0120                T next = nodes[i];
 0121                ICollection<T> children = getChildren(next);
 0122                int count = children.Count;
 123
 0124                if (count == 0)
 0125                {
 0126                    adjacency.Add(null); // can use null in this row of the adjacency matrix.
 0127                    childlessQueue.Enqueue(i);
 0128                    continue;
 129                }
 130
 0131                var adjacencyRow = new bool[Math.Max(nodes.Count, count)];
 0132                foreach (T childNode in children)
 0133                {
 0134                    if (!nodeIndex.TryGetValue(childNode, out int index))
 0135                    {
 136                        // this is the first time we're encountering this node.
 137                        // Assign it an index and append it to the maps.
 138
 0139                        index = nodes.Count;
 0140                        nodeIndex.Add(childNode, index);
 0141                        nodes.Add(childNode);
 0142                    }
 143
 144                    // Grow the adjacency row as appropriate.
 0145                    if (index >= adjacencyRow.Length)
 0146                    {
 0147                        Array.Resize(ref adjacencyRow, index + 1);
 0148                    }
 149
 150                    // Set the relevant bit in the adjacency row.
 0151                    adjacencyRow[index] = true;
 0152                }
 153
 154                // Append the row to the adjacency matrix.
 0155                adjacency.Add(adjacencyRow);
 0156            }
 157
 0158            Debug.Assert(childlessQueue.Count > 0, "The graph contains cycles.");
 159
 160            // Step 2. Build the sorted array, walking from the nodes without children upward.
 0161            var sortedNodes = new T[nodes.Count];
 0162            int idx = sortedNodes.Length;
 163
 164            do
 0165            {
 0166                int nextIndex = childlessQueue.Dequeue();
 0167                sortedNodes[--idx] = nodes[nextIndex];
 168
 169                // Iterate over the adjacency matrix, removing any occurrence of nextIndex.
 0170                for (int i = 0; i < adjacency.Count; i++)
 0171                {
 0172                    if (adjacency[i] is { } childMap && nextIndex < childMap.Length && childMap[nextIndex])
 0173                    {
 0174                        childMap[nextIndex] = false;
 175
 0176                        if (childMap.AsSpan().IndexOf(true) == -1)
 0177                        {
 178                            // nextIndex was the last child removed from i, add to queue.
 0179                            childlessQueue.Enqueue(i);
 0180                        }
 0181                    }
 0182                }
 183
 0184            } while (childlessQueue.Count > 0);
 185
 0186            Debug.Assert(idx == 0, "should have populated the entire sortedNodes array.");
 0187            return sortedNodes;
 0188        }
 189    }
 190}

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\JsonHelpers.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.Buffers;
 5using System.Buffers.Text;
 6using System.Collections;
 7using System.Collections.Generic;
 8using System.Diagnostics;
 9using System.Diagnostics.CodeAnalysis;
 10using System.Runtime.CompilerServices;
 11using System.Text.Json.Nodes;
 12using System.Text.RegularExpressions;
 13
 14namespace System.Text.Json
 15{
 16    internal static partial class JsonHelpers
 17    {
 18        /// <summary>
 19        /// Returns the unescaped span for the given reader.
 20        /// </summary>
 21        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 22        public static ReadOnlySpan<byte> GetUnescapedSpan(this scoped ref Utf8JsonReader reader)
 023        {
 024            Debug.Assert(reader.TokenType is JsonTokenType.String or JsonTokenType.PropertyName);
 025            ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
 026            return reader.ValueIsEscaped ? JsonReaderHelper.GetUnescaped(span) : span;
 027        }
 28
 29        /// <summary>
 30        /// Attempts to perform a Read() operation and optionally checks that the full JSON value has been buffered.
 31        /// The reader will be reset if the operation fails.
 32        /// </summary>
 33        /// <param name="reader">The reader to advance.</param>
 34        /// <param name="requiresReadAhead">If reading a partial payload, read ahead to ensure that the full JSON value 
 35        /// <returns>True if the reader has been buffered with all required data.</returns>
 36        // AggressiveInlining used since this method is on a hot path and short. The AdvanceWithReadAhead method should 
 37        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 38        public static bool TryAdvanceWithOptionalReadAhead(this scoped ref Utf8JsonReader reader, bool requiresReadAhead
 5614639        {
 40            // No read-ahead necessary if we're at the final block of JSON data.
 5614641            bool readAhead = requiresReadAhead && !reader.IsFinalBlock;
 5614642            return readAhead ? TryAdvanceWithReadAhead(ref reader) : reader.Read();
 2608843        }
 44
 45        /// <summary>
 46        /// Attempts to read ahead to the next root-level JSON value, if it exists.
 47        /// </summary>
 48        public static bool TryAdvanceToNextRootLevelValueWithOptionalReadAhead(this scoped ref Utf8JsonReader reader, bo
 049        {
 050            Debug.Assert(reader.AllowMultipleValues, "only supported by readers that support multiple values.");
 051            Debug.Assert(reader.CurrentDepth == 0, "should only invoked for top-level values.");
 52
 053            Utf8JsonReader checkpoint = reader;
 054            if (!reader.Read())
 055            {
 56                // If the reader didn't return any tokens and it's the final block,
 57                // then there are no other JSON values to be read.
 058                isAtEndOfStream = reader.IsFinalBlock;
 059                reader = checkpoint;
 060                return false;
 61            }
 62
 63            // We found another JSON value, read ahead accordingly.
 064            isAtEndOfStream = false;
 065            if (requiresReadAhead && !reader.IsFinalBlock)
 066            {
 67                // Perform full read-ahead to ensure the full JSON value has been buffered.
 068                reader = checkpoint;
 069                return TryAdvanceWithReadAhead(ref reader);
 70            }
 71
 072            return true;
 073        }
 74
 75        private static bool TryAdvanceWithReadAhead(scoped ref Utf8JsonReader reader)
 076        {
 77            // When we're reading ahead we always have to save the state
 78            // as we don't know if the next token is a start object or array.
 079            Utf8JsonReader restore = reader;
 80
 081            if (!reader.Read())
 082            {
 083                return false;
 84            }
 85
 86            // Perform the actual read-ahead.
 087            JsonTokenType tokenType = reader.TokenType;
 088            if (tokenType is JsonTokenType.StartObject or JsonTokenType.StartArray)
 089            {
 90                // Attempt to skip to make sure we have all the data we need.
 091                bool complete = reader.TrySkipPartial();
 92
 93                // We need to restore the state in all cases as we need to be positioned back before
 94                // the current token to either attempt to skip again or to actually read the value.
 095                reader = restore;
 96
 097                if (!complete)
 098                {
 99                    // Couldn't read to the end of the object, exit out to get more data in the buffer.
 0100                    return false;
 101                }
 102
 103                // Success, requeue the reader to the start token.
 0104                reader.ReadWithVerify();
 0105                Debug.Assert(tokenType == reader.TokenType);
 0106            }
 107
 0108            return true;
 0109        }
 110
 111#if !NET
 112        /// <summary>
 113        /// Returns <see langword="true"/> if <paramref name="value"/> is a valid Unicode scalar
 114        /// value, i.e., is in [ U+0000..U+D7FF ], inclusive; or [ U+E000..U+10FFFF ], inclusive.
 115        /// </summary>
 116        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 117        public static bool IsValidUnicodeScalar(uint value)
 118        {
 119            // By XORing the incoming value with 0xD800, surrogate code points
 120            // are moved to the range [ U+0000..U+07FF ], and all valid scalar
 121            // values are clustered into the single range [ U+0800..U+10FFFF ],
 122            // which allows performing a single fast range check.
 123
 124            return IsInRangeInclusive(value ^ 0xD800U, 0x800U, 0x10FFFFU);
 125        }
 126#endif
 127
 128        /// <summary>
 129        /// Returns <see langword="true"/> if <paramref name="value"/> is between
 130        /// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
 131        /// </summary>
 132        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 133        public static bool IsInRangeInclusive(uint value, uint lowerBound, uint upperBound)
 0134            => (value - lowerBound) <= (upperBound - lowerBound);
 135
 136        /// <summary>
 137        /// Returns <see langword="true"/> if <paramref name="value"/> is between
 138        /// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
 139        /// </summary>
 140        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 141        public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound)
 3172142            => (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound);
 143
 144        /// <summary>
 145        /// Returns <see langword="true"/> if <paramref name="value"/> is between
 146        /// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
 147        /// </summary>
 148        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 149        public static bool IsInRangeInclusive(long value, long lowerBound, long upperBound)
 291150            => (ulong)(value - lowerBound) <= (ulong)(upperBound - lowerBound);
 151
 152        /// <summary>
 153        /// Returns <see langword="true"/> if <paramref name="value"/> is between
 154        /// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
 155        /// </summary>
 156        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 157        public static bool IsInRangeInclusive(JsonTokenType value, JsonTokenType lowerBound, JsonTokenType upperBound)
 158            => (value - lowerBound) <= (upperBound - lowerBound);
 159
 160        /// <summary>
 161        /// Returns <see langword="true"/> if <paramref name="value"/> is in the range [0..9].
 162        /// Otherwise, returns <see langword="false"/>.
 163        /// </summary>
 139255164        public static bool IsDigit(byte value) => (uint)(value - '0') <= '9' - '0';
 165
 166        /// <summary>
 167        /// Perform a Read() with a Debug.Assert verifying the reader did not return false.
 168        /// This should be called when the Read() return value is not used, such as non-Stream cases where there is only
 169        /// </summary>
 170        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 171        public static void ReadWithVerify(this ref Utf8JsonReader reader)
 0172        {
 0173            bool result = reader.Read();
 0174            Debug.Assert(result);
 0175        }
 176
 177        /// <summary>
 178        /// Performs a TrySkip() with a Debug.Assert verifying the reader did not return false.
 179        /// </summary>
 180        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 181        public static void SkipWithVerify(this ref Utf8JsonReader reader)
 0182        {
 0183            bool success = reader.TrySkipPartial(reader.CurrentDepth);
 0184            Debug.Assert(success, "The skipped value should have already been buffered.");
 0185        }
 186
 187        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 188        public static bool TrySkipPartial(this ref Utf8JsonReader reader)
 0189        {
 0190            return reader.TrySkipPartial(reader.CurrentDepth);
 0191        }
 192
 193        public static bool TryLookupUtf8Key<TValue>(
 194            this Dictionary<string, TValue> dictionary,
 195            ReadOnlySpan<byte> utf8Key,
 196            [MaybeNullWhen(false)] out TValue result)
 0197        {
 198#if NET
 0199            Debug.Assert(dictionary.Comparer is IAlternateEqualityComparer<ReadOnlySpan<char>, string>);
 200
 0201            Dictionary<string, TValue>.AlternateLookup<ReadOnlySpan<char>> spanLookup =
 0202                dictionary.GetAlternateLookup<ReadOnlySpan<char>>();
 203
 0204            char[]? rentedBuffer = null;
 205
 0206            Span<char> charBuffer = utf8Key.Length <= JsonConstants.StackallocCharThreshold ?
 0207                stackalloc char[JsonConstants.StackallocCharThreshold] :
 0208                (rentedBuffer = ArrayPool<char>.Shared.Rent(utf8Key.Length));
 209
 0210            int charsWritten = Encoding.UTF8.GetChars(utf8Key, charBuffer);
 0211            Span<char> decodedKey = charBuffer[0..charsWritten];
 212
 0213            bool success = spanLookup.TryGetValue(decodedKey, out result);
 214
 0215            if (rentedBuffer != null)
 0216            {
 0217                decodedKey.Clear();
 0218                ArrayPool<char>.Shared.Return(rentedBuffer);
 0219            }
 220
 0221            return success;
 222#else
 223            string key = Encoding.UTF8.GetString(utf8Key);
 224            return dictionary.TryGetValue(key, out result);
 225#endif
 0226        }
 227
 228        /// <summary>
 229        /// Emulates Dictionary(IEnumerable{KeyValuePair}) on netstandard.
 230        /// </summary>
 231        public static Dictionary<TKey, TValue> CreateDictionaryFromCollection<TKey, TValue>(
 232            IEnumerable<KeyValuePair<TKey, TValue>> collection,
 233            IEqualityComparer<TKey> comparer)
 234            where TKey : notnull
 235        {
 236#if !NET
 237            var dictionary = new Dictionary<TKey, TValue>(comparer);
 238
 239            foreach (KeyValuePair<TKey, TValue> item in collection)
 240            {
 241                dictionary.Add(item.Key, item.Value);
 242            }
 243
 244            return dictionary;
 245#else
 246            return new Dictionary<TKey, TValue>(collection: collection, comparer);
 247#endif
 248        }
 249
 250        public static bool IsFinite(double value)
 12251        {
 252#if NET
 12253            return double.IsFinite(value);
 254#else
 255            return !(double.IsNaN(value) || double.IsInfinity(value));
 256#endif
 12257        }
 258
 259        public static bool IsFinite(float value)
 12260        {
 261#if NET
 12262            return float.IsFinite(value);
 263#else
 264            return !(float.IsNaN(value) || float.IsInfinity(value));
 265#endif
 12266        }
 267
 268        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 269        public static void ValidateInt32MaxArrayLength(uint length)
 0270        {
 0271            if (length > 0X7FEFFFFF) // prior to .NET 6, max array length for sizeof(T) != 1 (size == 1 is larger)
 0272            {
 0273                ThrowHelper.ThrowOutOfMemoryException(length);
 274            }
 0275        }
 276
 277#if !NET
 278        public static bool HasAllSet(this BitArray bitArray)
 279        {
 280            for (int i = 0; i < bitArray.Count; i++)
 281            {
 282                if (!bitArray[i])
 283                {
 284                    return false;
 285                }
 286            }
 287
 288            return true;
 289        }
 290#endif
 291
 292        /// <summary>
 293        /// Gets a Regex instance for recognizing integer representations of enums.
 294        /// </summary>
 295        private const string IntegerRegexPattern = @"^\s*(?:\+|\-)?[0-9]+\s*$";
 296        private const int IntegerRegexTimeoutMs = 200;
 297
 298#if NET
 299        [GeneratedRegex(IntegerRegexPattern, RegexOptions.None, matchTimeoutMilliseconds: IntegerRegexTimeoutMs)]
 300        public static partial Regex IntegerRegex { get; }
 301#else
 302        public static Regex IntegerRegex { get; } = new(IntegerRegexPattern, RegexOptions.Compiled, TimeSpan.FromMillise
 303#endif
 304
 305        /// <summary>
 306        /// Compares two valid UTF-8 encoded JSON numbers for decimal equality.
 307        /// </summary>
 308        public static bool AreEqualJsonNumbers(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
 35309        {
 35310            Debug.Assert(left.Length > 0 && right.Length > 0);
 311
 35312            ParseNumber(left,
 35313                out bool leftIsNegative,
 35314                out ReadOnlySpan<byte> leftIntegral,
 35315                out ReadOnlySpan<byte> leftFractional,
 35316                out int leftExponent);
 317
 35318            ParseNumber(right,
 35319                out bool rightIsNegative,
 35320                out ReadOnlySpan<byte> rightIntegral,
 35321                out ReadOnlySpan<byte> rightFractional,
 35322                out int rightExponent);
 323
 324            int nDigits;
 35325            if (leftIsNegative != rightIsNegative ||
 35326                leftExponent != rightExponent ||
 35327                (nDigits = (leftIntegral.Length + leftFractional.Length)) !=
 35328                            rightIntegral.Length + rightFractional.Length)
 0329            {
 0330                return false;
 331            }
 332
 333            // Need to check that the concatenated integral and fractional parts are equal;
 334            // break each representation into three parts such that their lengths exactly match.
 335            ReadOnlySpan<byte> leftFirst;
 336            ReadOnlySpan<byte> leftMiddle;
 337            ReadOnlySpan<byte> leftLast;
 338
 339            ReadOnlySpan<byte> rightFirst;
 340            ReadOnlySpan<byte> rightMiddle;
 341            ReadOnlySpan<byte> rightLast;
 342
 35343            int diff = leftIntegral.Length - rightIntegral.Length;
 35344            switch (diff)
 345            {
 346                case < 0:
 0347                    leftFirst = leftIntegral;
 0348                    leftMiddle = leftFractional.Slice(0, -diff);
 0349                    leftLast = leftFractional.Slice(-diff);
 0350                    int rightOffset = rightIntegral.Length + diff;
 0351                    rightFirst = rightIntegral.Slice(0, rightOffset);
 0352                    rightMiddle = rightIntegral.Slice(rightOffset);
 0353                    rightLast = rightFractional;
 0354                    break;
 355
 356                case 0:
 35357                    leftFirst = leftIntegral;
 35358                    leftMiddle = default;
 35359                    leftLast = leftFractional;
 35360                    rightFirst = rightIntegral;
 35361                    rightMiddle = default;
 35362                    rightLast = rightFractional;
 35363                    break;
 364
 365                case > 0:
 0366                    int leftOffset = leftIntegral.Length - diff;
 0367                    leftFirst = leftIntegral.Slice(0, leftOffset);
 0368                    leftMiddle = leftIntegral.Slice(leftOffset);
 0369                    leftLast = leftFractional;
 0370                    rightFirst = rightIntegral;
 0371                    rightMiddle = rightFractional.Slice(0, diff);
 0372                    rightLast = rightFractional.Slice(diff);
 0373                    break;
 374            }
 375
 35376            Debug.Assert(leftFirst.Length == rightFirst.Length);
 35377            Debug.Assert(leftMiddle.Length == rightMiddle.Length);
 35378            Debug.Assert(leftLast.Length == rightLast.Length);
 35379            return leftFirst.SequenceEqual(rightFirst) &&
 35380                leftMiddle.SequenceEqual(rightMiddle) &&
 35381                leftLast.SequenceEqual(rightLast);
 382
 383            static void ParseNumber(
 384                ReadOnlySpan<byte> span,
 385                out bool isNegative,
 386                out ReadOnlySpan<byte> integral,
 387                out ReadOnlySpan<byte> fractional,
 388                out int exponent)
 70389            {
 390                // Parses a JSON number into its integral, fractional, and exponent parts.
 391                // The returned components use a normal-form decimal representation:
 392                //
 393                //   Number := sign * <integral + fractional> * 10^exponent
 394                //
 395                // where integral and fractional are sequences of digits whose concatenation
 396                // represents the significand of the number without leading or trailing zeros.
 397                // Two such normal-form numbers are treated as equal if and only if they have
 398                // equal signs, significands, and exponents.
 399
 400                bool neg;
 401                ReadOnlySpan<byte> intg;
 402                ReadOnlySpan<byte> frac;
 403                int exp;
 404
 70405                Debug.Assert(span.Length > 0);
 406
 70407                if (span[0] == '-')
 0408                {
 0409                    neg = true;
 0410                    span = span.Slice(1);
 0411                }
 412                else
 70413                {
 70414                    Debug.Assert(char.IsDigit((char)span[0]), "leading plus not allowed in valid JSON numbers.");
 70415                    neg = false;
 70416                }
 417
 70418                int i = span.IndexOfAny((byte)'.', (byte)'e', (byte)'E');
 70419                if (i < 0)
 70420                {
 70421                    intg = span;
 70422                    frac = default;
 70423                    exp = 0;
 70424                    goto Normalize;
 425                }
 426
 0427                intg = span.Slice(0, i);
 428
 0429                if (span[i] == '.')
 0430                {
 0431                    span = span.Slice(i + 1);
 0432                    i = span.IndexOfAny((byte)'e', (byte)'E');
 0433                    if (i < 0)
 0434                    {
 0435                        frac = span;
 0436                        exp = 0;
 0437                        goto Normalize;
 438                    }
 439
 0440                    frac = span.Slice(0, i);
 0441                }
 442                else
 0443                {
 0444                    frac = default;
 0445                }
 446
 0447                Debug.Assert(span[i] is (byte)'e' or (byte)'E');
 0448                if (!Utf8Parser.TryParse(span.Slice(i + 1), out exp, out _))
 0449                {
 0450                    Debug.Assert(span.Length >= 10);
 0451                    ThrowHelper.ThrowArgumentOutOfRangeException_JsonNumberExponentTooLarge(nameof(exponent));
 452                }
 453
 70454            Normalize: // Calculates the normal form of the number.
 455
 70456                if (IndexOfFirstTrailingZero(frac) is >= 0 and int iz)
 0457                {
 458                    // Trim trailing zeros from the fractional part.
 459                    // e.g. 3.1400 -> 3.14
 0460                    frac = frac.Slice(0, iz);
 0461                }
 462
 70463                if (intg[0] == '0')
 0464                {
 0465                    Debug.Assert(intg.Length == 1, "Leading zeros not permitted in JSON numbers.");
 466
 0467                    if (IndexOfLastLeadingZero(frac) is >= 0 and int lz)
 0468                    {
 469                        // Trim leading zeros from the fractional part
 470                        // and update the exponent accordingly.
 471                        // e.g. 0.000123 -> 0.123e-3
 0472                        frac = frac.Slice(lz + 1);
 0473                        exp -= lz + 1;
 0474                    }
 475
 476                    // Normalize "0" to the empty span.
 0477                    intg = default;
 0478                }
 479
 70480                if (frac.IsEmpty && IndexOfFirstTrailingZero(intg) is >= 0 and int fz)
 40481                {
 482                    // There is no fractional part, trim trailing zeros from
 483                    // the integral part and increase the exponent accordingly.
 484                    // e.g. 1000 -> 1e3
 40485                    exp += intg.Length - fz;
 40486                    intg = intg.Slice(0, fz);
 40487                }
 488
 489                // Normalize the exponent by subtracting the length of the fractional part.
 490                // e.g. 3.14 -> 314e-2
 70491                exp -= frac.Length;
 492
 70493                if (intg.IsEmpty && frac.IsEmpty)
 0494                {
 495                    // Normalize zero representations.
 0496                    neg = false;
 0497                    exp = 0;
 0498                }
 499
 500                // Copy to out parameters.
 70501                isNegative = neg;
 70502                integral = intg;
 70503                fractional = frac;
 70504                exponent = exp;
 505
 506                static int IndexOfLastLeadingZero(ReadOnlySpan<byte> span)
 0507                {
 508#if NET
 0509                    int firstNonZero = span.IndexOfAnyExcept((byte)'0');
 0510                    return firstNonZero < 0 ? span.Length - 1 : firstNonZero - 1;
 511#else
 512                    for (int i = 0; i < span.Length; i++)
 513                    {
 514                        if (span[i] != '0')
 515                        {
 516                            return i - 1;
 517                        }
 518                    }
 519
 520                    return span.Length - 1;
 521#endif
 0522                }
 523
 524                static int IndexOfFirstTrailingZero(ReadOnlySpan<byte> span)
 140525                {
 526#if NET
 140527                    int lastNonZero = span.LastIndexOfAnyExcept((byte)'0');
 140528                    return lastNonZero == span.Length - 1 ? -1 : lastNonZero + 1;
 529#else
 530                    if (span.IsEmpty)
 531                    {
 532                        return -1;
 533                    }
 534
 535                    for (int i = span.Length - 1; i >= 0; i--)
 536                    {
 537                        if (span[i] != '0')
 538                        {
 539                            return i == span.Length - 1 ? -1 : i + 1;
 540                        }
 541                    }
 542
 543                    return 0;
 544#endif
 140545                }
 70546            }
 35547        }
 548    }
 549}

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\JsonHelpers.Date.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;
 6using System.Runtime.InteropServices;
 7
 8namespace System.Text.Json
 9{
 10    internal static partial class JsonHelpers
 11    {
 12        [StructLayout(LayoutKind.Auto)]
 13        private struct DateTimeParseData
 14        {
 15            public int Year;
 16            public int Month;
 17            public int Day;
 18            public bool IsCalendarDateOnly;
 19            public int Hour;
 20            public int Minute;
 21            public int Second;
 22            public int Fraction; // This value should never be greater than 9_999_999.
 23            public int OffsetHours;
 24            public int OffsetMinutes;
 025            public bool OffsetNegative => OffsetToken == JsonConstants.Hyphen;
 26            public byte OffsetToken;
 27        }
 28
 29        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 30        public static bool IsValidDateTimeOffsetParseLength(int length)
 61031        {
 61032            return IsInRangeInclusive(length, JsonConstants.MinimumDateTimeParseLength, JsonConstants.MaximumEscapedDate
 61033        }
 34
 35        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 36        public static bool IsValidUnescapedDateTimeOffsetParseLength(int length)
 037        {
 038            return IsInRangeInclusive(length, JsonConstants.MinimumDateTimeParseLength, JsonConstants.MaximumDateTimeOff
 039        }
 40
 41        /// <summary>
 42        /// Parse the given UTF-8 <paramref name="source"/> as extended ISO 8601 format.
 43        /// </summary>
 44        /// <param name="source">UTF-8 source to parse.</param>
 45        /// <param name="value">The parsed <see cref="DateTime"/> if successful.</param>
 46        /// <returns>"true" if successfully parsed.</returns>
 47        public static bool TryParseAsISO(ReadOnlySpan<byte> source, out DateTime value)
 4048        {
 4049            if (!TryParseDateTimeOffset(source, out DateTimeParseData parseData))
 4050            {
 4051                value = default;
 4052                return false;
 53            }
 54
 055            if (parseData.OffsetToken == JsonConstants.UtcOffsetToken)
 056            {
 057                return TryCreateDateTime(parseData, DateTimeKind.Utc, out value);
 58            }
 059            else if (parseData.OffsetToken == JsonConstants.Plus || parseData.OffsetToken == JsonConstants.Hyphen)
 060            {
 061                if (!TryCreateDateTimeOffset(ref parseData, out DateTimeOffset dateTimeOffset))
 062                {
 063                    value = default;
 064                    return false;
 65                }
 66
 067                value = dateTimeOffset.LocalDateTime;
 068                return true;
 69            }
 70
 071            return TryCreateDateTime(parseData, DateTimeKind.Unspecified, out value);
 4072        }
 73
 74        /// <summary>
 75        /// Parse the given UTF-8 <paramref name="source"/> as extended ISO 8601 format.
 76        /// </summary>
 77        /// <param name="source">UTF-8 source to parse.</param>
 78        /// <param name="value">The parsed <see cref="DateTimeOffset"/> if successful.</param>
 79        /// <returns>"true" if successfully parsed.</returns>
 80        public static bool TryParseAsISO(ReadOnlySpan<byte> source, out DateTimeOffset value)
 1881        {
 1882            if (!TryParseDateTimeOffset(source, out DateTimeParseData parseData))
 1883            {
 1884                value = default;
 1885                return false;
 86            }
 87
 088            if (parseData.OffsetToken == JsonConstants.UtcOffsetToken || // Same as specifying an offset of "+00:00", ex
 089                parseData.OffsetToken == JsonConstants.Plus || parseData.OffsetToken == JsonConstants.Hyphen)
 090            {
 091                return TryCreateDateTimeOffset(ref parseData, out value);
 92            }
 93
 94            // No offset, attempt to read as local time.
 095            return TryCreateDateTimeOffsetInterpretingDataAsLocalTime(parseData, out value);
 1896        }
 97
 98#if NET
 99        public static bool TryParseAsIso(ReadOnlySpan<byte> source, out DateOnly value)
 12100        {
 12101            if (TryParseDateTimeOffset(source, out DateTimeParseData parseData) &&
 12102                parseData.IsCalendarDateOnly &&
 12103                TryCreateDateTime(parseData, DateTimeKind.Unspecified, out DateTime dateTime))
 0104            {
 0105                value = DateOnly.FromDateTime(dateTime);
 0106                return true;
 107            }
 108
 12109            value = default;
 12110            return false;
 12111        }
 112#endif
 113
 114        /// <summary>
 115        /// ISO 8601 date time parser (ISO 8601-1:2019).
 116        /// </summary>
 117        /// <param name="source">The date/time to parse in UTF-8 format.</param>
 118        /// <param name="parseData">The parsed <see cref="DateTimeParseData"/> for the given <paramref name="source"/>.<
 119        /// <remarks>
 120        /// Supports extended calendar date (5.2.2.1) and complete (5.4.2.1) calendar date/time of day
 121        /// representations with optional specification of seconds and fractional seconds.
 122        ///
 123        /// Times can be explicitly specified as UTC ("Z" - 5.3.3) or offsets from UTC ("+/-hh:mm" 5.3.4.2).
 124        /// If unspecified they are considered to be local per spec.
 125        ///
 126        /// Examples: (TZD is either "Z" or hh:mm offset from UTC)
 127        ///
 128        ///  YYYY-MM-DD               (eg 1997-07-16)
 129        ///  YYYY-MM-DDThh:mm         (eg 1997-07-16T19:20)
 130        ///  YYYY-MM-DDThh:mm:ss      (eg 1997-07-16T19:20:30)
 131        ///  YYYY-MM-DDThh:mm:ss.s    (eg 1997-07-16T19:20:30.45)
 132        ///  YYYY-MM-DDThh:mmTZD      (eg 1997-07-16T19:20+01:00)
 133        ///  YYYY-MM-DDThh:mm:ssTZD   (eg 1997-07-16T19:20:3001:00)
 134        ///  YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45Z)
 135        ///
 136        /// Generally speaking we always require the "extended" option when one exists (3.1.3.5).
 137        /// The extended variants have separator characters between components ('-', ':', '.', etc.).
 138        /// Spaces are not permitted.
 139        /// </remarks>
 140        /// <returns>"true" if successfully parsed.</returns>
 141        private static bool TryParseDateTimeOffset(ReadOnlySpan<byte> source, out DateTimeParseData parseData)
 70142        {
 70143            parseData = default;
 144
 145            // too short datetime
 70146            Debug.Assert(source.Length >= 10);
 147
 148            // Parse the calendar date
 149            // -----------------------
 150            // ISO 8601-1:2019 5.2.2.1b "Calendar date complete extended format"
 151            //  [dateX] = [year]["-"][month]["-"][day]
 152            //  [year]  = [YYYY] [0000 - 9999] (4.3.2)
 153            //  [month] = [MM] [01 - 12] (4.3.3)
 154            //  [day]   = [DD] [01 - 28, 29, 30, 31] (4.3.4)
 155            //
 156            // Note: 5.2.2.2 "Representations with reduced precision" allows for
 157            // just [year]["-"][month] (a) and just [year] (b), but we currently
 158            // don't permit it.
 159
 70160            {
 70161                uint digit1 = source[0] - (uint)'0';
 70162                uint digit2 = source[1] - (uint)'0';
 70163                uint digit3 = source[2] - (uint)'0';
 70164                uint digit4 = source[3] - (uint)'0';
 165
 70166                if (digit1 > 9 || digit2 > 9 || digit3 > 9 || digit4 > 9)
 70167                {
 70168                    return false;
 169                }
 170
 0171                parseData.Year = (int)(digit1 * 1000 + digit2 * 100 + digit3 * 10 + digit4);
 0172            }
 173
 0174            if (source[4] != JsonConstants.Hyphen
 0175                || !TryGetNextTwoDigits(source.Slice(start: 5, length: 2), ref parseData.Month)
 0176                || source[7] != JsonConstants.Hyphen
 0177                || !TryGetNextTwoDigits(source.Slice(start: 8, length: 2), ref parseData.Day))
 0178            {
 0179                return false;
 180            }
 181
 182            // We now have YYYY-MM-DD [dateX]
 0183            if (source.Length == 10)
 0184            {
 0185                parseData.IsCalendarDateOnly = true;
 0186                return true;
 187            }
 188
 189            // Parse the time of day
 190            // ---------------------
 191            //
 192            // ISO 8601-1:2019 5.3.1.2b "Local time of day complete extended format"
 193            //  [timeX]   = ["T"][hour][":"][min][":"][sec]
 194            //  [hour]    = [hh] [00 - 23] (4.3.8a)
 195            //  [minute]  = [mm] [00 - 59] (4.3.9a)
 196            //  [sec]     = [ss] [00 - 59, 60 with a leap second] (4.3.10a)
 197            //
 198            // ISO 8601-1:2019 5.3.3 "UTC of day"
 199            //  [timeX]["Z"]
 200            //
 201            // ISO 8601-1:2019 5.3.4.2 "Local time of day with the time shift between
 202            // local time scale and UTC" (Extended format)
 203            //
 204            //  [shiftX] = ["+"|"-"][hour][":"][min]
 205            //
 206            // Notes:
 207            //
 208            // "T" is optional per spec, but _only_ when times are used alone. In our
 209            // case, we're reading out a complete date & time and as such require "T".
 210            // (5.4.2.1b).
 211            //
 212            // For [timeX] We allow seconds to be omitted per 5.3.1.3a "Representations
 213            // with reduced precision". 5.3.1.3b allows just specifying the hour, but
 214            // we currently don't permit this.
 215            //
 216            // Decimal fractions are allowed for hours, minutes and seconds (5.3.14).
 217            // We only allow fractions for seconds currently. Lower order components
 218            // can't follow, i.e. you can have T23.3, but not T23.3:04. There must be
 219            // one digit, but the max number of digits is implementation defined. We
 220            // currently allow up to 16 digits of fractional seconds only. While we
 221            // support 16 fractional digits we only parse the first seven, anything
 222            // past that is considered a zero. This is to stay compatible with the
 223            // DateTime implementation which is limited to this resolution.
 224
 0225            if (source.Length < 16)
 0226            {
 227                // Source does not have enough characters for YYYY-MM-DDThh:mm
 0228                return false;
 229            }
 230
 231            // Parse THH:MM (e.g. "T10:32")
 0232            if (source[10] != JsonConstants.TimePrefix || source[13] != JsonConstants.Colon
 0233                || !TryGetNextTwoDigits(source.Slice(start: 11, length: 2), ref parseData.Hour)
 0234                || !TryGetNextTwoDigits(source.Slice(start: 14, length: 2), ref parseData.Minute))
 0235            {
 0236                return false;
 237            }
 238
 239            // We now have YYYY-MM-DDThh:mm
 0240            Debug.Assert(source.Length >= 16);
 0241            if (source.Length == 16)
 0242            {
 0243                return true;
 244            }
 245
 0246            byte curByte = source[16];
 0247            int sourceIndex = 17;
 248
 249            // Either a TZD ['Z'|'+'|'-'] or a seconds separator [':'] is valid at this point
 0250            switch (curByte)
 251            {
 252                case JsonConstants.UtcOffsetToken:
 0253                    parseData.OffsetToken = JsonConstants.UtcOffsetToken;
 0254                    return sourceIndex == source.Length;
 255                case JsonConstants.Plus:
 256                case JsonConstants.Hyphen:
 0257                    parseData.OffsetToken = curByte;
 0258                    return ParseOffset(ref parseData, source.Slice(sourceIndex));
 259                case JsonConstants.Colon:
 0260                    break;
 261                default:
 0262                    return false;
 263            }
 264
 265            // Try reading the seconds
 0266            if (source.Length < 19
 0267                || !TryGetNextTwoDigits(source.Slice(start: 17, length: 2), ref parseData.Second))
 0268            {
 0269                return false;
 270            }
 271
 272            // We now have YYYY-MM-DDThh:mm:ss
 0273            Debug.Assert(source.Length >= 19);
 0274            if (source.Length == 19)
 0275            {
 0276                return true;
 277            }
 278
 0279            curByte = source[19];
 0280            sourceIndex = 20;
 281
 282            // Either a TZD ['Z'|'+'|'-'] or a seconds decimal fraction separator ['.'] is valid at this point
 0283            switch (curByte)
 284            {
 285                case JsonConstants.UtcOffsetToken:
 0286                    parseData.OffsetToken = JsonConstants.UtcOffsetToken;
 0287                    return sourceIndex == source.Length;
 288                case JsonConstants.Plus:
 289                case JsonConstants.Hyphen:
 0290                    parseData.OffsetToken = curByte;
 0291                    return ParseOffset(ref parseData, source.Slice(sourceIndex));
 292                case JsonConstants.Period:
 0293                    break;
 294                default:
 0295                    return false;
 296            }
 297
 298            // Source does not have enough characters for second fractions (i.e. ".s")
 299            // YYYY-MM-DDThh:mm:ss.s
 0300            if (source.Length < 21)
 0301            {
 0302                return false;
 303            }
 304
 305            // Parse fraction. This value should never be greater than 9_999_999
 0306            {
 0307                int numDigitsRead = 0;
 0308                int fractionEnd = Math.Min(sourceIndex + JsonConstants.DateTimeParseNumFractionDigits, source.Length);
 309
 0310                while (sourceIndex < fractionEnd && IsDigit(curByte = source[sourceIndex]))
 0311                {
 0312                    if (numDigitsRead < JsonConstants.DateTimeNumFractionDigits)
 0313                    {
 0314                        parseData.Fraction = (parseData.Fraction * 10) + (int)(curByte - (uint)'0');
 0315                        numDigitsRead++;
 0316                    }
 317
 0318                    sourceIndex++;
 0319                }
 320
 0321                if (parseData.Fraction != 0)
 0322                {
 0323                    while (numDigitsRead < JsonConstants.DateTimeNumFractionDigits)
 0324                    {
 0325                        parseData.Fraction *= 10;
 0326                        numDigitsRead++;
 0327                    }
 0328                }
 0329            }
 330
 331            // We now have YYYY-MM-DDThh:mm:ss.s
 0332            Debug.Assert(sourceIndex <= source.Length);
 0333            if (sourceIndex == source.Length)
 0334            {
 0335                return true;
 336            }
 337
 0338            curByte = source[sourceIndex++];
 339
 340            // TZD ['Z'|'+'|'-'] is valid at this point
 0341            switch (curByte)
 342            {
 343                case JsonConstants.UtcOffsetToken:
 0344                    parseData.OffsetToken = JsonConstants.UtcOffsetToken;
 0345                    return sourceIndex == source.Length;
 346                case JsonConstants.Plus:
 347                case JsonConstants.Hyphen:
 0348                    parseData.OffsetToken = curByte;
 0349                    return ParseOffset(ref parseData, source.Slice(sourceIndex));
 350                default:
 0351                    return false;
 352            }
 353
 354            static bool ParseOffset(ref DateTimeParseData parseData, ReadOnlySpan<byte> offsetData)
 0355            {
 356                // Parse the hours for the offset
 0357                if (offsetData.Length < 2
 0358                    || !TryGetNextTwoDigits(offsetData.Slice(0, 2), ref parseData.OffsetHours))
 0359                {
 0360                    return false;
 361                }
 362
 363                // We now have YYYY-MM-DDThh:mm:ss.s+|-hh
 364
 0365                if (offsetData.Length == 2)
 0366                {
 367                    // Just hours offset specified
 0368                    return true;
 369                }
 370
 371                // Ensure we have enough for ":mm"
 0372                if (offsetData.Length != 5
 0373                    || offsetData[2] != JsonConstants.Colon
 0374                    || !TryGetNextTwoDigits(offsetData.Slice(3), ref parseData.OffsetMinutes))
 0375                {
 0376                    return false;
 377                }
 378
 0379                return true;
 0380            }
 70381        }
 382
 383        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 384        private static bool TryGetNextTwoDigits(ReadOnlySpan<byte> source, ref int value)
 0385        {
 0386            Debug.Assert(source.Length == 2);
 387
 0388            uint digit1 = source[0] - (uint)'0';
 0389            uint digit2 = source[1] - (uint)'0';
 390
 0391            if (digit1 > 9 || digit2 > 9)
 0392            {
 0393                value = default;
 0394                return false;
 395            }
 396
 0397            value = (int)(digit1 * 10 + digit2);
 0398            return true;
 0399        }
 400
 401        // The following methods are borrowed verbatim from src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Pa
 402
 403        /// <summary>
 404        /// Overflow-safe DateTimeOffset factory.
 405        /// </summary>
 406        private static bool TryCreateDateTimeOffset(DateTime dateTime, ref DateTimeParseData parseData, out DateTimeOffs
 0407        {
 0408            if (((uint)parseData.OffsetHours) > JsonConstants.MaxDateTimeUtcOffsetHours)
 0409            {
 0410                value = default;
 0411                return false;
 412            }
 413
 0414            if (((uint)parseData.OffsetMinutes) > 59)
 0415            {
 0416                value = default;
 0417                return false;
 418            }
 419
 0420            if (parseData.OffsetHours == JsonConstants.MaxDateTimeUtcOffsetHours && parseData.OffsetMinutes != 0)
 0421            {
 0422                value = default;
 0423                return false;
 424            }
 425
 0426            long offsetTicks = (((long)parseData.OffsetHours) * 3600 + ((long)parseData.OffsetMinutes) * 60) * TimeSpan.
 0427            if (parseData.OffsetNegative)
 0428            {
 0429                offsetTicks = -offsetTicks;
 0430            }
 431
 432            try
 0433            {
 0434                value = new DateTimeOffset(ticks: dateTime.Ticks, offset: new TimeSpan(ticks: offsetTicks));
 0435            }
 0436            catch (ArgumentOutOfRangeException)
 0437            {
 438                // If we got here, the combination of the DateTime + UTC offset strayed outside the 1..9999 year range. 
 439                // that it's better to catch the exception rather than replicate DateTime's range checking (which it's g
 0440                value = default;
 0441                return false;
 442            }
 443
 0444            return true;
 0445        }
 446
 447        /// <summary>
 448        /// Overflow-safe DateTimeOffset factory.
 449        /// </summary>
 450        private static bool TryCreateDateTimeOffset(ref DateTimeParseData parseData, out DateTimeOffset value)
 0451        {
 0452            if (!TryCreateDateTime(parseData, kind: DateTimeKind.Unspecified, out DateTime dateTime))
 0453            {
 0454                value = default;
 0455                return false;
 456            }
 457
 0458            if (!TryCreateDateTimeOffset(dateTime: dateTime, ref parseData, out value))
 0459            {
 0460                value = default;
 0461                return false;
 462            }
 463
 0464            return true;
 0465        }
 466
 467        /// <summary>
 468        /// Overflow-safe DateTimeOffset/Local time conversion factory.
 469        /// </summary>
 470        private static bool TryCreateDateTimeOffsetInterpretingDataAsLocalTime(DateTimeParseData parseData, out DateTime
 0471        {
 0472            if (!TryCreateDateTime(parseData, DateTimeKind.Local, out DateTime dateTime))
 0473            {
 0474                value = default;
 0475                return false;
 476            }
 477
 478            try
 0479            {
 0480                value = new DateTimeOffset(dateTime);
 0481            }
 0482            catch (ArgumentOutOfRangeException)
 0483            {
 484                // If we got here, the combination of the DateTime + UTC offset strayed outside the 1..9999 year range. 
 485                // that it's better to catch the exception rather than replicate DateTime's range checking (which it's g
 0486                value = default;
 0487                return false;
 488            }
 489
 0490            return true;
 0491        }
 492
 493        /// <summary>
 494        /// Overflow-safe DateTime factory.
 495        /// </summary>
 496        private static bool TryCreateDateTime(DateTimeParseData parseData, DateTimeKind kind, out DateTime value)
 0497        {
 0498            if (parseData.Year == 0)
 0499            {
 0500                value = default;
 0501                return false;
 502            }
 503
 0504            Debug.Assert(parseData.Year <= 9999); // All of our callers to date parse the year from fixed 4-digit fields
 505
 0506            if ((((uint)parseData.Month) - 1) >= 12)
 0507            {
 0508                value = default;
 0509                return false;
 510            }
 511
 0512            uint dayMinusOne = ((uint)parseData.Day) - 1;
 0513            if (dayMinusOne >= 28 && dayMinusOne >= DateTime.DaysInMonth(parseData.Year, parseData.Month))
 0514            {
 0515                value = default;
 0516                return false;
 517            }
 518
 519            // Per ISO 8601-1:2019, 24:00:00 represents end of a calendar day
 520            // (same instant as next day's 00:00:00), but only when minute, second, and fraction are all zero.
 521            // We treat it as hour=0 and add one day at the end.
 0522            bool isEndOfDay = false;
 0523            if (parseData.Hour == 24)
 0524            {
 0525                if (parseData.Minute != 0 || parseData.Second != 0 || parseData.Fraction != 0)
 0526                {
 0527                    value = default;
 0528                    return false;
 529                }
 530
 0531                parseData.Hour = 0;
 0532                isEndOfDay = true;
 0533            }
 534
 0535            if (((uint)parseData.Hour) > 23)
 0536            {
 0537                value = default;
 0538                return false;
 539            }
 540
 0541            if (((uint)parseData.Minute) > 59)
 0542            {
 0543                value = default;
 0544                return false;
 545            }
 546
 547            // This needs to allow leap seconds when appropriate.
 548            // See https://github.com/dotnet/runtime/issues/30135.
 0549            if (((uint)parseData.Second) > 59)
 0550            {
 0551                value = default;
 0552                return false;
 553            }
 554
 0555            Debug.Assert(parseData.Fraction >= 0 && parseData.Fraction <= JsonConstants.MaxDateTimeFraction); // All of 
 556
 0557            ReadOnlySpan<int> days = DateTime.IsLeapYear(parseData.Year) ? DaysToMonth366 : DaysToMonth365;
 0558            int yearMinusOne = parseData.Year - 1;
 0559            int totalDays = (yearMinusOne * 365) + (yearMinusOne / 4) - (yearMinusOne / 100) + (yearMinusOne / 400) + da
 0560            long ticks = totalDays * TimeSpan.TicksPerDay;
 0561            int totalSeconds = (parseData.Hour * 3600) + (parseData.Minute * 60) + parseData.Second;
 0562            ticks += totalSeconds * TimeSpan.TicksPerSecond;
 0563            ticks += parseData.Fraction;
 564
 565            // If hour was originally 24 (end of day per ISO 8601), add one day to advance to next day's 00:00:00
 0566            if (isEndOfDay)
 0567            {
 0568                ticks += TimeSpan.TicksPerDay;
 0569                if ((ulong)ticks > (ulong)DateTime.MaxValue.Ticks)
 0570                {
 0571                    value = default;
 0572                    return false;
 573                }
 0574            }
 575
 0576            value = new DateTime(ticks: ticks, kind: kind);
 0577            return true;
 0578        }
 579
 0580        private static ReadOnlySpan<int> DaysToMonth365 => [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
 0581        private static ReadOnlySpan<int> DaysToMonth366 => [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
 582    }
 583}

C:\h\w\B31A098C\w\BB5A0A33\e\runtime-utils\Runner\runtime\src\libraries\System.Text.Json\src\System\Text\Json\JsonHelpers.Escaping.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.Buffers;
 5using System.Diagnostics;
 6using System.Runtime.CompilerServices;
 7using System.Text.Encodings.Web;
 8
 9namespace System.Text.Json
 10{
 11    internal static partial class JsonHelpers
 12    {
 13        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 14        public static byte[] GetEscapedPropertyNameSection(ReadOnlySpan<byte> utf8Value, JavaScriptEncoder? encoder)
 779415        {
 779416            int idx = JsonWriterHelper.NeedsEscaping(utf8Value, encoder);
 17
 779418            if (idx != -1)
 019            {
 020                return GetEscapedPropertyNameSection(utf8Value, idx, encoder);
 21            }
 22            else
 779423            {
 779424                return GetPropertyNameSection(utf8Value);
 25            }
 779426        }
 27
 28        public static byte[] EscapeValue(
 29            ReadOnlySpan<byte> utf8Value,
 30            int firstEscapeIndexVal,
 31            JavaScriptEncoder? encoder)
 032        {
 033            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
 034            Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
 35
 036            byte[]? valueArray = null;
 37
 038            int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
 39
 040            Span<byte> escapedValue = length <= JsonConstants.StackallocByteThreshold ?
 041                stackalloc byte[JsonConstants.StackallocByteThreshold] :
 042                (valueArray = ArrayPool<byte>.Shared.Rent(length));
 43
 044            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
 45
 046            byte[] escapedString = escapedValue.Slice(0, written).ToArray();
 47
 048            if (valueArray != null)
 049            {
 050                ArrayPool<byte>.Shared.Return(valueArray);
 051            }
 52
 053            return escapedString;
 054        }
 55
 56        private static byte[] GetEscapedPropertyNameSection(
 57            ReadOnlySpan<byte> utf8Value,
 58            int firstEscapeIndexVal,
 59            JavaScriptEncoder? encoder)
 060        {
 061            Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
 062            Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
 63
 064            byte[]? valueArray = null;
 65
 066            int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
 67
 068            Span<byte> escapedValue = length <= JsonConstants.StackallocByteThreshold ?
 069                stackalloc byte[JsonConstants.StackallocByteThreshold] :
 070                (valueArray = ArrayPool<byte>.Shared.Rent(length));
 71
 072            JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
 73
 074            byte[] propertySection = GetPropertyNameSection(escapedValue.Slice(0, written));
 75
 076            if (valueArray != null)
 077            {
 078                ArrayPool<byte>.Shared.Return(valueArray);
 079            }
 80
 081            return propertySection;
 082        }
 83
 84        private static byte[] GetPropertyNameSection(ReadOnlySpan<byte> utf8Value)
 779485        {
 779486            int length = utf8Value.Length;
 779487            byte[] propertySection = new byte[length + 3];
 88
 779489            propertySection[0] = JsonConstants.Quote;
 779490            utf8Value.CopyTo(propertySection.AsSpan(1, length));
 779491            propertySection[++length] = JsonConstants.Quote;
 779492            propertySection[++length] = JsonConstants.KeyValueSeparator;
 93
 779494            return propertySection;
 779495        }
 96    }
 97}

Methods/Properties

RequiresSpecialNumberHandlingOnWrite(System.Nullable`1<System.Text.Json.Serialization.JsonNumberHandling>)
StableSortByKey(System.Collections.Generic.List`1<T>,System.Func`2<T,TKey>)
TraverseGraphWithTopologicalSort(T,System.Func`2<T,System.Collections.Generic.ICollection`1<T>>,System.Collections.Generic.IEqualityComparer`1<T>)
GetUnescapedSpan(System.Text.Json.Utf8JsonReader&)
TryAdvanceWithOptionalReadAhead(System.Text.Json.Utf8JsonReader&,System.Boolean)
TryAdvanceToNextRootLevelValueWithOptionalReadAhead(System.Text.Json.Utf8JsonReader&,System.Boolean,System.Boolean&)
TryAdvanceWithReadAhead(System.Text.Json.Utf8JsonReader&)
IsInRangeInclusive(System.UInt32,System.UInt32,System.UInt32)
IsInRangeInclusive(System.Int32,System.Int32,System.Int32)
IsInRangeInclusive(System.Int64,System.Int64,System.Int64)
IsDigit(System.Byte)
ReadWithVerify(System.Text.Json.Utf8JsonReader&)
SkipWithVerify(System.Text.Json.Utf8JsonReader&)
TrySkipPartial(System.Text.Json.Utf8JsonReader&)
TryLookupUtf8Key(System.Collections.Generic.Dictionary`2<System.String,TValue>,System.ReadOnlySpan`1<System.Byte>,TValue&)
IsFinite(System.Double)
IsFinite(System.Single)
ValidateInt32MaxArrayLength(System.UInt32)
AreEqualJsonNumbers(System.ReadOnlySpan`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>)
ParseNumber(System.ReadOnlySpan`1<System.Byte>,System.Boolean&,System.ReadOnlySpan`1<System.Byte>&,System.ReadOnlySpan`1<System.Byte>&,System.Int32&)
IndexOfLastLeadingZero(System.ReadOnlySpan`1<System.Byte>)
IndexOfFirstTrailingZero(System.ReadOnlySpan`1<System.Byte>)
OffsetNegative()
IsValidDateTimeOffsetParseLength(System.Int32)
IsValidUnescapedDateTimeOffsetParseLength(System.Int32)
TryParseAsISO(System.ReadOnlySpan`1<System.Byte>,System.DateTime&)
TryParseAsISO(System.ReadOnlySpan`1<System.Byte>,System.DateTimeOffset&)
TryParseAsIso(System.ReadOnlySpan`1<System.Byte>,System.DateOnly&)
TryParseDateTimeOffset(System.ReadOnlySpan`1<System.Byte>,System.Text.Json.JsonHelpers/DateTimeParseData&)
ParseOffset(System.Text.Json.JsonHelpers/DateTimeParseData&,System.ReadOnlySpan`1<System.Byte>)
TryGetNextTwoDigits(System.ReadOnlySpan`1<System.Byte>,System.Int32&)
TryCreateDateTimeOffset(System.DateTime,System.Text.Json.JsonHelpers/DateTimeParseData&,System.DateTimeOffset&)
TryCreateDateTimeOffset(System.Text.Json.JsonHelpers/DateTimeParseData&,System.DateTimeOffset&)
TryCreateDateTimeOffsetInterpretingDataAsLocalTime(System.Text.Json.JsonHelpers/DateTimeParseData,System.DateTimeOffset&)
TryCreateDateTime(System.Text.Json.JsonHelpers/DateTimeParseData,System.DateTimeKind,System.DateTime&)
DaysToMonth365()
DaysToMonth366()
GetEscapedPropertyNameSection(System.ReadOnlySpan`1<System.Byte>,System.Text.Encodings.Web.JavaScriptEncoder)
EscapeValue(System.ReadOnlySpan`1<System.Byte>,System.Int32,System.Text.Encodings.Web.JavaScriptEncoder)
GetEscapedPropertyNameSection(System.ReadOnlySpan`1<System.Byte>,System.Int32,System.Text.Encodings.Web.JavaScriptEncoder)
GetPropertyNameSection(System.ReadOnlySpan`1<System.Byte>)