< Summary

Information
Class: DotnetFuzzing.Fuzzers.Utf8JsonReaderFuzzer
Assembly: DotnetFuzzing
File(s): D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\Utf8JsonReaderFuzzer.cs
Line coverage
40%
Covered lines: 197
Uncovered lines: 287
Coverable lines: 484
Total lines: 672
Line coverage: 40.7%
Branch coverage
11%
Covered branches: 33
Total branches: 296
Branch coverage: 11.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%11100%
NextByte()100%11100%
FuzzTarget(...)6.06%666634.8%
Test(...)5.55%181850%
TestDeserializeAsync(...)100%11100%
GenerateRandomType(...)87.5%8895%
TestRandomType(...)100%11100%
CreateVariableSegmentSequence(...)75%202090.32%
.ctor(...)100%11100%
SetNext(...)100%11100%
.ctor(...)100%110%
Equals(...)0%14140%
GetHashCode()0%12120%
TestWrapper(...)100%11100%
CompareExceptions(...)3.79%15815810.67%

File(s)

D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\Utf8JsonReaderFuzzer.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;
 5using System.Buffers;
 6using System.Buffers.Text;
 7using System.Collections;
 8using System.Collections.Generic;
 9using System.IO;
 10using System.IO.Pipelines;
 11using System.Runtime.CompilerServices;
 12using System.Runtime.InteropServices;
 13using System.Text;
 14using System.Text.Encodings.Web;
 15using System.Text.Json;
 16using System.Text.Json.Nodes;
 17using System.Text.Json.Serialization;
 18using System.Text.Unicode;
 19using SharpFuzz;
 20
 21namespace DotnetFuzzing.Fuzzers;
 22
 23internal sealed class Utf8JsonReaderFuzzer : IFuzzer
 24{
 125    public string[] TargetAssemblies { get; } = ["System.Text.Json"];
 26
 027    public string[] TargetCoreLibPrefixes => [];
 28
 29    delegate T RefFunc<T>(ref Utf8JsonReader reader);
 30
 131    static byte[] s_controlBytes = [];
 32    static int s_controlIndex;
 33
 34    static byte NextByte()
 249335    {
 249336        byte value = s_controlBytes[s_controlIndex];
 249337        s_controlIndex = (s_controlIndex + 1) % s_controlBytes.Length;
 249338        return value;
 249339    }
 40
 41    public void FuzzTarget(ReadOnlySpan<byte> bytes)
 10642    {
 43        const int controlByteCount = 32;
 44        const int minLength = controlByteCount + 10;
 10645        if (bytes.Length < minLength)
 046        {
 047            return;
 48        }
 49
 50        // Use the first bytes directly as control data instead of seeding a PRNG,
 51        // so the fuzzing engine can see the relationship between input and code coverage.
 10652        s_controlBytes = bytes.Slice(0, controlByteCount).ToArray();
 10653        s_controlIndex = 0;
 10654        bytes = bytes.Slice(controlByteCount);
 55
 10656        JsonSerializerOptions options = new JsonSerializerOptions
 10657        {
 10658            AllowTrailingCommas = NextByte() % 2 == 0,
 10659            Encoder = NextByte() % 2 == 0 ? JavaScriptEncoder.Default : JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
 10660            // TODO: JsonExceptions differ between Span and Sequence paths
 10661            ReadCommentHandling = (JsonCommentHandling)(NextByte() % 2),
 10662            NumberHandling = (JsonNumberHandling)(NextByte() % 4),
 10663        };
 64
 65        // Fuzz using ReadOnlySpan<byte>
 10666        var readerSpan = new Utf8JsonReader(bytes, isFinalBlock: true, state: default);
 67
 68        // Fuzz using ReadOnlySequence<byte>
 10669        var sequence = CreateVariableSegmentSequence(bytes);
 10670        var readerSequence = new Utf8JsonReader(sequence, isFinalBlock: true, state: default);
 71
 10672        var byteArr = bytes.ToArray();
 10673        TestDeserializeAsync<string>(byteArr, sequence, options);
 10674        TestDeserializeAsync<decimal>(byteArr, sequence, options);
 10675        TestDeserializeAsync<byte>(byteArr, sequence, options);
 10676        TestDeserializeAsync<Guid>(byteArr, sequence, options);
 10677        TestDeserializeAsync<DateTime>(byteArr, sequence, options);
 10678        TestDeserializeAsync<DateTimeOffset>(byteArr, sequence, options);
 10679        TestDeserializeAsync<IntEnum>(byteArr, sequence, options);
 10680        TestDeserializeAsync<StringEnum>(byteArr, sequence, options);
 10681        TestDeserializeAsync<bool>(byteArr, sequence, options);
 10682        TestDeserializeAsync<byte[]>(byteArr, sequence, options, (l, r) => l.AsSpan().SequenceEqual(r.AsSpan()));
 10683        TestDeserializeAsync<char>(byteArr, sequence, options);
 10684        TestDeserializeAsync<double>(byteArr, sequence, options);
 10685        TestDeserializeAsync<Int16>(byteArr, sequence, options);
 10686        TestDeserializeAsync<Int32>(byteArr, sequence, options);
 10687        TestDeserializeAsync<Int64>(byteArr, sequence, options);
 10688        TestDeserializeAsync<sbyte>(byteArr, sequence, options);
 10689        TestDeserializeAsync<Single>(byteArr, sequence, options);
 10690        TestDeserializeAsync<TimeSpan>(byteArr, sequence, options);
 10691        TestDeserializeAsync<UInt16>(byteArr, sequence, options);
 10692        TestDeserializeAsync<UInt32>(byteArr, sequence, options);
 10693        TestDeserializeAsync<UInt64>(byteArr, sequence, options);
 10694        TestDeserializeAsync<Uri>(byteArr, sequence, options);
 10695        TestDeserializeAsync<Version>(byteArr, sequence, options);
 10696        TestDeserializeAsync<object>(byteArr, sequence, options, (l, r) => JsonElement.DeepEquals((JsonElement)l!, (Json
 10697        TestDeserializeAsync<JsonElement>(byteArr, sequence, options, (l, r) => JsonElement.DeepEquals(l, r));
 10698        TestDeserializeAsync<JsonDocument>(byteArr, sequence, options, (l, r) => JsonElement.DeepEquals(l!.RootElement, 
 10699        TestDeserializeAsync<JsonArray>(byteArr, sequence, options, (l, r) => JsonNode.DeepEquals(l, r));
 106100        TestDeserializeAsync<JsonValue>(byteArr, sequence, options, (l, r) => JsonNode.DeepEquals(l, r));
 106101        TestDeserializeAsync<JsonNode>(byteArr, sequence, options, (l, r) => JsonNode.DeepEquals(l, r));
 106102        TestDeserializeAsync<JsonObject>(byteArr, sequence, options, (l, r) => JsonNode.DeepEquals(l, r));
 106103        TestDeserializeAsync<Memory<byte>>(byteArr, sequence, options, (l, r) => l.Span.SequenceEqual(r.Span));
 106104        TestDeserializeAsync<ReadOnlyMemory<byte>>(byteArr, sequence, options, (l, r) => l.Span.SequenceEqual(r.Span));
 106105        TestDeserializeAsync<DateOnly>(byteArr, sequence, options);
 106106        TestDeserializeAsync<TimeOnly>(byteArr, sequence, options);
 106107        TestDeserializeAsync<Half>(byteArr, sequence, options);
 106108        TestDeserializeAsync<Int128>(byteArr, sequence, options);
 106109        TestDeserializeAsync<UInt128>(byteArr, sequence, options);
 106110        TestDeserializeAsync<List<int>>(byteArr, sequence, options, (l, r) =>
 0111        {
 0112           if (l!.Count != r!.Count)
 0113               return false;
 106114
 0115           for (int i = 0; i < l.Count; i++)
 0116           {
 0117               if (l[i] != r[i])
 0118                   return false;
 0119           }
 0120           return true;
 106121        });
 106122        TestDeserializeAsync<Dictionary<string, int>>(byteArr, sequence, options, (l, r) =>
 0123        {
 0124           if (l!.Count != r!.Count)
 0125               return false;
 106126
 0127           foreach (var kvp in l)
 0128           {
 0129               if (!r.TryGetValue(kvp.Key, out int value) || value != kvp.Value)
 0130                   return false;
 0131           }
 0132           return true;
 106133        });
 134
 106135        TestRandomType(bytes, sequence, options, maxDepth: NextByte() % 3);
 106136        TestRandomType(bytes, sequence, options, maxDepth: NextByte() % 3);
 137
 106138        while (true)
 106139        {
 106140            if (!Test(ref readerSpan, ref readerSequence,
 212141                static (ref readerSpan) => { return readerSpan.Read(); },
 212142                static (ref readerSequence) => { return readerSequence.Read(); }
 106143            ))
 106144            {
 106145                break;
 146            }
 147
 0148            if (readerSpan.TokenType != readerSequence.TokenType ||
 0149                !(readerSequence.HasValueSequence ? readerSpan.ValueSpan.SequenceEqual(readerSequence.ValueSequence.ToAr
 0150                readerSpan.ValueSpan.SequenceEqual(readerSequence.ValueSpan)))
 0151            {
 0152                throw new InvalidOperationException("Span and Sequence readers diverged in token or value. " +
 0153                                                    $"Span TokenType: {readerSpan.TokenType}, Value: {Convert.ToHexStrin
 0154                                                    $"Seq TokenType: {readerSequence.TokenType}, Value: {Convert.ToHexSt
 155            }
 156
 157            // Example: Test TryGetInt32 after each Read
 0158            if (readerSpan.TokenType == JsonTokenType.Number)
 0159            {
 0160                Test(ref readerSpan, ref readerSequence,
 0161                    static (ref readerSpan) => { int value; return readerSpan.TryGetInt32(out value) ? value : (int?)nul
 0162                    static (ref readerSequence) => { int value; return readerSequence.TryGetInt32(out value) ? value : (
 0163                );
 164
 0165                Test(ref readerSpan, ref readerSequence,
 0166                    static (ref readerSpan) => { byte value; return readerSpan.TryGetByte(out value) ? value : (byte?)nu
 0167                    static (ref readerSequence) => { byte value; return readerSequence.TryGetByte(out value) ? value : (
 0168                );
 169
 0170                Test(ref readerSpan, ref readerSequence,
 0171                    static (ref readerSpan) => { short value; return readerSpan.TryGetInt16(out value) ? value : (short?
 0172                    static (ref readerSequence) => { short value; return readerSequence.TryGetInt16(out value) ? value :
 0173                );
 174
 0175                Test(ref readerSpan, ref readerSequence,
 0176                    static (ref readerSpan) => { long value; return readerSpan.TryGetInt64(out value) ? value : (long?)n
 0177                    static (ref readerSequence) => { long value; return readerSequence.TryGetInt64(out value) ? value : 
 0178                );
 179
 0180                Test(ref readerSpan, ref readerSequence,
 0181                    static (ref readerSpan) => { float value; return readerSpan.TryGetSingle(out value) ? value : (float
 0182                    static (ref readerSequence) => { float value; return readerSequence.TryGetSingle(out value) ? value 
 0183                );
 184
 0185                Test(ref readerSpan, ref readerSequence,
 0186                    static (ref readerSpan) => { double value; return readerSpan.TryGetDouble(out value) ? value : (doub
 0187                    static (ref readerSequence) => { double value; return readerSequence.TryGetDouble(out value) ? value
 0188                );
 189
 0190                Test(ref readerSpan, ref readerSequence,
 0191                    static (ref readerSpan) => { decimal value; return readerSpan.TryGetDecimal(out value) ? value : (de
 0192                    static (ref readerSequence) => { decimal value; return readerSequence.TryGetDecimal(out value) ? val
 0193                );
 194
 0195                Test(ref readerSpan, ref readerSequence,
 0196                    static (ref readerSpan) => { sbyte value; return readerSpan.TryGetSByte(out value) ? value : (sbyte?
 0197                    static (ref readerSequence) => { sbyte value; return readerSequence.TryGetSByte(out value) ? value :
 0198                );
 0199            }
 200
 0201            if (readerSpan.TokenType == JsonTokenType.String)
 0202            {
 0203                Test(ref readerSpan, ref readerSequence,
 0204                    static (ref readerSpan) =>
 0205                    {
 0206                        DateTime value;
 0207                        try
 0208                        {
 0209                            readerSpan.TryGetDateTime(out value);
 0210                        }
 0211                        catch (InvalidOperationException ex) when (ex.Message.Contains("surrogate"))
 0212                        {
 0213                            // If the string is not a valid DateTime, we expect an exception
 0214                            return (DateTime?)null;
 0215                        }
 0216                        return value;
 0217                    },
 0218                    static (ref readerSequence) =>
 0219                    {
 0220                        DateTime value;
 0221                        try
 0222                        {
 0223                            readerSequence.TryGetDateTime(out value);
 0224                        }
 0225                        catch (InvalidOperationException ex) when (ex.Message.Contains("surrogate"))
 0226                        {
 0227                            // If the string is not a valid DateTime, we expect an exception
 0228                            return (DateTime?)null;
 0229                        }
 0230                        return value;
 0231                    }
 0232                );
 233
 0234                Test(ref readerSpan, ref readerSequence,
 0235                    static (ref readerSpan) =>
 0236                    {
 0237                        string? value;
 0238                        try
 0239                        {
 0240                            value = readerSpan.GetString();
 0241                        }
 0242                        catch (InvalidOperationException ex) when (ex.Message.Contains("surrogate"))
 0243                        {
 0244                            // If the string contains invalid surrogate characters, we expect an exception
 0245                            return null;
 0246                        }
 0247                        return value;
 0248                    },
 0249                    static (ref readerSequence) =>
 0250                    {
 0251                        string? value;
 0252                        try
 0253                        {
 0254                            value = readerSequence.GetString();
 0255                        }
 0256                        catch (InvalidOperationException ex) when (ex.Message.Contains("surrogate"))
 0257                        {
 0258                            // If the string contains invalid surrogate characters, we expect an exception
 0259                            return null;
 0260                        }
 0261                        return value;
 0262                    }
 0263                );
 0264            }
 265
 0266            if (readerSpan.TokenType == JsonTokenType.True || readerSpan.TokenType == JsonTokenType.False)
 0267            {
 0268                Test(ref readerSpan, ref readerSequence,
 0269                    static (ref readerSpan) => { return readerSpan.GetBoolean(); },
 0270                    static (ref readerSequence) => { return readerSequence.GetBoolean(); }
 0271                );
 0272            }
 0273        }
 106274    }
 275
 276    private static T Test<T>(
 277        ref Utf8JsonReader spanReader,
 278        ref Utf8JsonReader seqReader,
 279        RefFunc<T> span,
 280        RefFunc<T> seq,
 281        Func<T, T, bool>? comparer = null)
 4452282    {
 4452283        T spanToken = default!;
 4452284        T seqToken = default!;
 8904285        Exception? spanEx = null, seqEx = null;
 286
 287        try
 4452288        {
 4452289            spanToken = span(ref spanReader);
 0290        }
 4452291        catch (Exception ex)
 4452292        {
 4452293            spanEx = ex;
 4452294        }
 295
 296        try
 4452297        {
 4452298            seqToken = seq(ref seqReader);
 0299        }
 4452300        catch (Exception ex)
 4452301        {
 4452302            seqEx = ex;
 4452303        }
 304
 4452305        if (CompareExceptions(spanEx, seqEx))
 4452306        {
 4452307            return default!;
 308        }
 309
 0310        if (spanToken is null && seqToken is null)
 0311        {
 312            // Both returned null, consider it a match
 0313            return spanToken;
 314        }
 0315        else if (spanToken is null || seqToken is null)
 0316        {
 0317            throw new InvalidOperationException("Span and Sequence readers diverged in token availability. " +
 0318                                                $"Span has token: {spanToken}, Sequence has token: {seqToken}");
 319        }
 320
 0321        if (!comparer?.Invoke(spanToken, seqToken) ?? !spanToken.Equals(seqToken))
 0322        {
 0323            throw new InvalidOperationException("Span and Sequence readers diverged in token availability. " +
 0324                                                $"Span has token: {spanToken}, Sequence has token: {seqToken}");
 325        }
 326
 0327        if (spanReader.BytesConsumed != seqReader.BytesConsumed ||
 0328            spanReader.TokenStartIndex != seqReader.TokenStartIndex)
 0329        {
 0330            throw new InvalidOperationException("Span and Sequence readers diverged in BytesConsumed or TokenStartIndex.
 0331                                                $"Span BytesConsumed: {spanReader.BytesConsumed}, TokenStartIndex: {span
 0332                                                $"Seq BytesConsumed: {seqReader.BytesConsumed}, TokenStartIndex: {seqRea
 333        }
 334
 0335        return spanToken;
 4452336    }
 337
 338    internal static void TestDeserializeAsync<T>(byte[] bytes, ReadOnlySequence<byte> sequence, JsonSerializerOptions op
 4346339    {
 4346340        Utf8JsonReader readerSpan = new();
 4346341        Test(ref readerSpan, ref readerSpan,
 4346342            (ref _) => (T?)JsonSerializer.DeserializeAsync(new MemoryStream(bytes), typeof(T), options).GetAwaiter().Get
 4346343            (ref _) => (T?)JsonSerializer.DeserializeAsync(PipeReader.Create(sequence), typeof(T), options).GetAwaiter()
 4346344            comparer);
 4346345    }
 346
 1347    static readonly Type[] groundTypes = [
 1348        typeof(int),
 1349        typeof(string),
 1350        typeof(decimal),
 1351        typeof(DateTimeOffset),
 1352        typeof(JsonDocument),
 1353        typeof(byte),
 1354        typeof(Guid),
 1355        typeof(DateTime),
 1356        typeof(IntEnum),
 1357        typeof(StringEnum),
 1358        typeof(bool),
 1359        typeof(double),
 1360        typeof(long),
 1361        typeof(Int128),
 1362        typeof(char),
 1363    ];
 364
 365    static Type GenerateRandomType(int maxDepth)
 1382366    {
 1382367        if (maxDepth <= 0)
 1008368            return groundTypes[NextByte() % groundTypes.Length];
 369
 374370        int kind = NextByte() % 5;
 374371        return kind switch
 374372        {
 39373            0 => groundTypes[NextByte() % groundTypes.Length],
 18374            1 => typeof(List<>).MakeGenericType(GenerateRandomType(maxDepth - 1)),
 150375            2 => typeof(Dictionary<,>).MakeGenericType(typeof(string), GenerateRandomType(maxDepth - 1)),
 162376            3 => typeof(PocoTemplate<,,,,,>).MakeGenericType(
 162377                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1),
 162378                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1),
 162379                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1)),
 5380            4 => typeof(PocoWithSetters<,,,,,>).MakeGenericType(
 5381                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1),
 5382                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1),
 5383                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1)),
 0384            _ => groundTypes[0],
 374385        };
 1382386    }
 387
 388    static void TestRandomType(ReadOnlySpan<byte> bytes, ReadOnlySequence<byte> sequence, JsonSerializerOptions options,
 212389    {
 212390        Type type = GenerateRandomType(maxDepth);
 212391        Type testerType = typeof(Tester<>).MakeGenericType(type);
 212392        var testerInstance = (ITester)Activator.CreateInstance(testerType)!;
 212393        testerInstance.TestWrapper(bytes, sequence, options);
 212394    }
 395
 396    private static ReadOnlySequence<byte> CreateVariableSegmentSequence(ReadOnlySpan<byte> bytes)
 106397    {
 398        // Split into 2-5 segments, but never less than 1 byte per segment
 106399        int maxSegments = Math.Min(6, bytes.Length + 1);
 106400        int segmentCount = maxSegments <= 2 ? 2 : 2 + NextByte() % (maxSegments - 2);
 401
 106402        if (bytes.Length == 0 || bytes.Length < segmentCount)
 0403        {
 0404            return ReadOnlySequence<byte>.Empty;
 405        }
 406
 106407        int[] segmentSizes = new int[segmentCount];
 106408        int remaining = bytes.Length;
 409
 410        // Assign at least 1 byte per segment
 1084411        for (int i = 0; i < segmentCount; i++)
 436412        {
 436413            int max = remaining - (segmentCount - i - 1);
 436414            segmentSizes[i] = (i == segmentCount - 1) ? max : (max <= 1 ? 1 : 1 + NextByte() % (max - 1));
 436415            remaining -= segmentSizes[i];
 436416        }
 417
 418        // Build segments
 106419        var buffers = new List<ReadOnlyMemory<byte>>(segmentCount);
 106420        int offset = 0;
 1084421        for (int i = 0; i < segmentCount; i++)
 436422        {
 436423            buffers.Add(bytes.Slice(offset, segmentSizes[i]).ToArray());
 436424            offset += segmentSizes[i];
 436425        }
 426
 427        // Link segments
 106428        ReadOnlySequence<byte> sequence = new ReadOnlySequence<byte>(buffers[0]);
 106429        if (buffers.Count == 1)
 0430            return sequence;
 431
 106432        var segments = new List<BufferSegment>();
 1190433        foreach (var buffer in buffers)
 436434            segments.Add(new BufferSegment(buffer));
 435
 872436        for (int i = 0; i < segments.Count - 1; i++)
 330437            segments[i].SetNext(segments[i + 1]);
 438
 106439        return new ReadOnlySequence<byte>(segments[0], 0, segments[^1], segments[^1].Memory.Length);
 106440    }
 441
 442    // Helper class for multi-segment ReadOnlySequence
 443    private sealed class BufferSegment : ReadOnlySequenceSegment<byte>
 444    {
 436445        public BufferSegment(ReadOnlyMemory<byte> memory)
 436446        {
 436447            Memory = memory;
 436448        }
 449        public void SetNext(BufferSegment next)
 330450        {
 330451            Next = next;
 330452            next.RunningIndex = RunningIndex + Memory.Length;
 330453        }
 454    }
 455
 0456    public record PocoTemplate<T1, T2, T3, T4, T5, T6>(
 0457        T1 Property1,
 0458        T2 Property2,
 0459        T3 Property3,
 0460        T4 Property4,
 0461        T5 Property5,
 0462        T6 Property6
 0463    );
 464
 465    public class PocoWithSetters<T1, T2, T3, T4, T5, T6>
 466    {
 0467        public T1 Property1 { get; set; } = default!;
 0468        public T2 Property2 { get; set; } = default!;
 0469        public T3 Property3 { get; set; } = default!;
 0470        public T4 Property4 { get; set; } = default!;
 0471        public T5 Property5 { get; set; } = default!;
 0472        public T6 Property6 { get; set; } = default!;
 473
 474        public override bool Equals(object? obj)
 0475        {
 0476            if (ReferenceEquals(this, obj))
 0477                return true;
 0478            if (obj is not PocoWithSetters<T1, T2, T3, T4, T5, T6> other)
 0479                return false;
 480
 0481            return
 0482                EqualityComparer<T1>.Default.Equals(Property1, other.Property1) &&
 0483                EqualityComparer<T2>.Default.Equals(Property2, other.Property2) &&
 0484                EqualityComparer<T3>.Default.Equals(Property3, other.Property3) &&
 0485                EqualityComparer<T4>.Default.Equals(Property4, other.Property4) &&
 0486                EqualityComparer<T5>.Default.Equals(Property5, other.Property5) &&
 0487                EqualityComparer<T6>.Default.Equals(Property6, other.Property6);
 0488        }
 489
 490        public override int GetHashCode()
 0491        {
 0492            return HashCode.Combine(
 0493                Property1 is null ? 0 : EqualityComparer<T1>.Default.GetHashCode(Property1),
 0494                Property2 is null ? 0 : EqualityComparer<T2>.Default.GetHashCode(Property2),
 0495                Property3 is null ? 0 : EqualityComparer<T3>.Default.GetHashCode(Property3),
 0496                Property4 is null ? 0 : EqualityComparer<T4>.Default.GetHashCode(Property4),
 0497                Property5 is null ? 0 : EqualityComparer<T5>.Default.GetHashCode(Property5),
 0498                Property6 is null ? 0 : EqualityComparer<T6>.Default.GetHashCode(Property6)
 0499            );
 0500        }
 501    }
 502
 503    public interface ITester
 504    {
 505        void TestWrapper(ReadOnlySpan<byte> bytes, ReadOnlySequence<byte> sequence, JsonSerializerOptions options);
 506    }
 507
 508    public class Tester<T> : ITester
 509    {
 510        public void TestWrapper(ReadOnlySpan<byte> bytes, ReadOnlySequence<byte> sequence, JsonSerializerOptions options
 212511        {
 212512            TestDeserializeAsync<T>(bytes.ToArray(), sequence, options, static (_, _) => true);
 212513        }
 514    }
 515
 516    // true if had exception and processed it, false if no exception, throw if exceptions don't match or are unexpected
 517    public static bool CompareExceptions(Exception? spanEx, Exception? seqEx)
 4452518    {
 4452519        if (spanEx is null || seqEx is null)
 0520        {
 0521            if (spanEx is not null)
 0522            {
 0523                throw new InvalidOperationException(
 0524                    $"Span and Sequence readers diverged in exception:{Environment.NewLine}" +
 0525                    $"Span: {spanEx.GetType().Name} '{spanEx.Message}'{Environment.NewLine}" +
 0526                    $"Seq: No Exception");
 527            }
 0528            else if (seqEx is not null)
 0529            {
 0530                throw new InvalidOperationException(
 0531                    $"Span and Sequence readers diverged in exception:{Environment.NewLine}" +
 0532                    $"Span: No Exception{Environment.NewLine}" +
 0533                    $"Seq: {seqEx.GetType().Name} '{seqEx.Message}");
 534            }
 535            else
 0536            {
 537                // both are null exceptions
 0538                return false;
 539            }
 540        }
 541
 4452542        if (spanEx.GetType() != seqEx.GetType())
 0543        {
 0544            throw new InvalidOperationException(
 0545                    $"Span and Sequence readers diverged in exception type:{Environment.NewLine}" +
 0546                    $"Span: {spanEx.GetType().Name} '{spanEx.Message}'{Environment.NewLine}" +
 0547                    $"Seq: {seqEx.GetType().Name} '{seqEx.Message}'");
 548        }
 549
 4452550        if (spanEx is JsonException spanJsonEx && seqEx is JsonException seqJsonEx)
 4452551        {
 4452552            if (//spanJsonEx.LineNumber != seqJsonEx.LineNumber ||
 4452553                //spanJsonEx.BytePositionInLine != seqJsonEx.BytePositionInLine ||
 4452554                spanJsonEx.Path != seqJsonEx.Path/* ||
 4452555                spanJsonEx.Message != seqJsonEx.Message*/)
 0556            {
 0557                throw new InvalidOperationException(
 0558                    $"Span and Sequence readers diverged in exception:{Environment.NewLine}" +
 0559                    $"Span: {spanEx.GetType().Name} '{spanEx.Message}'{Environment.NewLine}" +
 0560                    $"Seq: {seqEx.GetType().Name} '{seqEx.Message}'");
 561            }
 562            // If both threw the same exception, consider the test passed for this input
 4452563            return true;
 564        }
 565
 0566        if (spanEx is DecoderFallbackException spanDecoderEx && seqEx is DecoderFallbackException seqDecoderEx)
 0567        {
 0568            if (spanDecoderEx.BytesUnknown.SequenceEqual(seqDecoderEx.BytesUnknown) != true)
 0569            {
 0570                throw new InvalidOperationException(
 0571                    $"Span and Sequence readers diverged in decoder exception:{Environment.NewLine}" +
 0572                    $"Span: {spanDecoderEx.GetType().Name ?? "none"} '{Convert.ToHexString(spanDecoderEx?.BytesUnknown ?
 0573                    $"Seq: {seqDecoderEx.GetType().Name ?? "none"} '{Convert.ToHexString(seqDecoderEx?.BytesUnknown ?? [
 574            }
 575            // If both threw the same decoder exception, consider the test passed for this input
 0576            return true;
 577        }
 578
 0579        if (spanEx is InvalidOperationException spanInvalidEx && seqEx is InvalidOperationException seqInvalidEx)
 0580        {
 0581            if (spanInvalidEx.Message.Contains("surrogate") || seqInvalidEx.Message.Contains("surrogate"))
 0582            {
 0583                if (spanInvalidEx?.Message.Equals(seqInvalidEx?.Message) is not true)
 0584                {
 0585                    throw new InvalidOperationException(
 0586                        $"Span and Sequence readers diverged in surrogate exception:{Environment.NewLine}" +
 0587                        $"Span: '{spanInvalidEx?.Message ?? "null"}'{Environment.NewLine}" +
 0588                        $"Seq: '{seqInvalidEx?.Message ?? "null"}'");
 589                }
 590
 591                // If both threw the same exception, consider the test passed for this input
 0592                return true;
 593            }
 0594            else if (spanInvalidEx.Message.Contains("invalid UTF-8") || seqInvalidEx.Message.Contains("invalid UTF-8"))
 0595            {
 0596                if (spanInvalidEx?.Message.Equals(seqInvalidEx?.Message) is not true)
 0597                {
 0598                    throw new InvalidOperationException(
 0599                        $"Span and Sequence readers diverged in invalid UTF-8 exception:{Environment.NewLine}" +
 0600                        $"Span: '{spanInvalidEx?.Message ?? "null"}'{Environment.NewLine}" +
 0601                        $"Seq: '{seqInvalidEx?.Message ?? "null"}'");
 602                }
 603
 604                // If both threw the same exception, consider the test passed for this input
 0605                return true;
 606            }
 0607            else if (spanInvalidEx.Message.Contains("element cannot be an object or array") ||
 0608                    seqInvalidEx.Message.Contains("element cannot be an object or array"))
 0609            {
 0610                if (spanInvalidEx?.Message.Equals(seqInvalidEx?.Message) is not true)
 0611                {
 0612                    throw new InvalidOperationException(
 0613                        $"Span and Sequence readers diverged in element object/array exception:{Environment.NewLine}" +
 0614                        $"Span: '{spanInvalidEx?.Message ?? "null"}'{Environment.NewLine}" +
 0615                        $"Seq: '{seqInvalidEx?.Message ?? "null"}'");
 616                }
 617
 618                // If both threw the same exception, consider the test passed for this input
 0619                return true;
 620            }
 0621        }
 622
 0623        if (spanEx is ArgumentOutOfRangeException spanRangeEx && seqEx is ArgumentOutOfRangeException seqRangeEx)
 0624        {
 625            // e.g. 42.1e240071992547409914
 0626            if (spanRangeEx.Message.Contains("exponent") || seqRangeEx.Message.Contains("exponent"))
 0627            {
 0628                if (spanRangeEx?.Message.Equals(seqRangeEx?.Message) is not true)
 0629                {
 0630                    throw new InvalidOperationException(
 0631                        $"Span and Sequence readers diverged in exponent exception:{Environment.NewLine}" +
 0632                        $"Span: '{spanRangeEx?.Message ?? "null"}'{Environment.NewLine}" +
 0633                        $"Seq: '{seqRangeEx?.Message ?? "null"}'");
 634                }
 635
 636                // If both threw the same exception, consider the test passed for this input
 0637                return true;
 638            }
 0639        }
 640
 0641        if (spanEx is ArgumentException spanArgumentEx && seqEx is ArgumentException seqArgumentEx)
 0642        {
 643            // e.g. {"key":"value", "key":"value"}
 0644            if (spanArgumentEx.Message.Contains("same key") || seqArgumentEx.Message.Contains("same key"))
 0645            {
 0646                if (spanArgumentEx?.Message.Equals(seqArgumentEx?.Message) is not true)
 0647                {
 0648                    throw new InvalidOperationException(
 0649                        $"Span and Sequence readers diverged in same key exception:{Environment.NewLine}" +
 0650                        $"Span: '{spanArgumentEx?.Message ?? "null"}'{Environment.NewLine}" +
 0651                        $"Seq: '{seqArgumentEx?.Message ?? "null"}'");
 652                }
 653
 654                // If both threw the same exception, consider the test passed for this input
 0655                return true;
 656            }
 0657        }
 658
 0659        throw new InvalidOperationException($"Unexpected error:{Environment.NewLine}" +
 0660            $"Span: '{spanEx?.GetType()}' '{spanEx?.Message ?? "null"}'{Environment.NewLine}" +
 0661            $"Seq: '{seqEx?.GetType()}' '{seqEx?.Message ?? "null"}'");
 4452662    }
 663}
 664
 665public enum IntEnum
 666{
 667}
 668
 669[JsonConverter(typeof(JsonStringEnumConverter))]
 670public enum StringEnum
 671{
 672}