< Summary

Information
Class: DotnetFuzzing.Fuzzers.Utf8JsonReaderFuzzer
Assembly: DotnetFuzzing
File(s): D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\Utf8JsonReaderFuzzer.cs
Line coverage
0%
Covered lines: 1
Uncovered lines: 483
Coverable lines: 484
Total lines: 672
Line coverage: 0.2%
Branch coverage
0%
Covered branches: 0
Total branches: 296
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
NextByte()100%110%
FuzzTarget(...)0%66660%
Test(...)0%18180%
TestDeserializeAsync(...)100%110%
GenerateRandomType(...)0%880%
TestRandomType(...)100%110%
CreateVariableSegmentSequence(...)0%20200%
.ctor(...)100%110%
SetNext(...)100%110%
.ctor(...)100%110%
Equals(...)0%14140%
GetHashCode()0%12120%
TestWrapper(...)100%110%
CompareExceptions(...)0%1581580%

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
 031    static byte[] s_controlBytes = [];
 32    static int s_controlIndex;
 33
 34    static byte NextByte()
 035    {
 036        byte value = s_controlBytes[s_controlIndex];
 037        s_controlIndex = (s_controlIndex + 1) % s_controlBytes.Length;
 038        return value;
 039    }
 40
 41    public void FuzzTarget(ReadOnlySpan<byte> bytes)
 042    {
 43        const int controlByteCount = 32;
 44        const int minLength = controlByteCount + 10;
 045        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.
 052        s_controlBytes = bytes.Slice(0, controlByteCount).ToArray();
 053        s_controlIndex = 0;
 054        bytes = bytes.Slice(controlByteCount);
 55
 056        JsonSerializerOptions options = new JsonSerializerOptions
 057        {
 058            AllowTrailingCommas = NextByte() % 2 == 0,
 059            Encoder = NextByte() % 2 == 0 ? JavaScriptEncoder.Default : JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
 060            // TODO: JsonExceptions differ between Span and Sequence paths
 061            ReadCommentHandling = (JsonCommentHandling)(NextByte() % 2),
 062            NumberHandling = (JsonNumberHandling)(NextByte() % 4),
 063        };
 64
 65        // Fuzz using ReadOnlySpan<byte>
 066        var readerSpan = new Utf8JsonReader(bytes, isFinalBlock: true, state: default);
 67
 68        // Fuzz using ReadOnlySequence<byte>
 069        var sequence = CreateVariableSegmentSequence(bytes);
 070        var readerSequence = new Utf8JsonReader(sequence, isFinalBlock: true, state: default);
 71
 072        var byteArr = bytes.ToArray();
 073        TestDeserializeAsync<string>(byteArr, sequence, options);
 074        TestDeserializeAsync<decimal>(byteArr, sequence, options);
 075        TestDeserializeAsync<byte>(byteArr, sequence, options);
 076        TestDeserializeAsync<Guid>(byteArr, sequence, options);
 077        TestDeserializeAsync<DateTime>(byteArr, sequence, options);
 078        TestDeserializeAsync<DateTimeOffset>(byteArr, sequence, options);
 079        TestDeserializeAsync<IntEnum>(byteArr, sequence, options);
 080        TestDeserializeAsync<StringEnum>(byteArr, sequence, options);
 081        TestDeserializeAsync<bool>(byteArr, sequence, options);
 082        TestDeserializeAsync<byte[]>(byteArr, sequence, options, (l, r) => l.AsSpan().SequenceEqual(r.AsSpan()));
 083        TestDeserializeAsync<char>(byteArr, sequence, options);
 084        TestDeserializeAsync<double>(byteArr, sequence, options);
 085        TestDeserializeAsync<Int16>(byteArr, sequence, options);
 086        TestDeserializeAsync<Int32>(byteArr, sequence, options);
 087        TestDeserializeAsync<Int64>(byteArr, sequence, options);
 088        TestDeserializeAsync<sbyte>(byteArr, sequence, options);
 089        TestDeserializeAsync<Single>(byteArr, sequence, options);
 090        TestDeserializeAsync<TimeSpan>(byteArr, sequence, options);
 091        TestDeserializeAsync<UInt16>(byteArr, sequence, options);
 092        TestDeserializeAsync<UInt32>(byteArr, sequence, options);
 093        TestDeserializeAsync<UInt64>(byteArr, sequence, options);
 094        TestDeserializeAsync<Uri>(byteArr, sequence, options);
 095        TestDeserializeAsync<Version>(byteArr, sequence, options);
 096        TestDeserializeAsync<object>(byteArr, sequence, options, (l, r) => JsonElement.DeepEquals((JsonElement)l!, (Json
 097        TestDeserializeAsync<JsonElement>(byteArr, sequence, options, (l, r) => JsonElement.DeepEquals(l, r));
 098        TestDeserializeAsync<JsonDocument>(byteArr, sequence, options, (l, r) => JsonElement.DeepEquals(l!.RootElement, 
 099        TestDeserializeAsync<JsonArray>(byteArr, sequence, options, (l, r) => JsonNode.DeepEquals(l, r));
 0100        TestDeserializeAsync<JsonValue>(byteArr, sequence, options, (l, r) => JsonNode.DeepEquals(l, r));
 0101        TestDeserializeAsync<JsonNode>(byteArr, sequence, options, (l, r) => JsonNode.DeepEquals(l, r));
 0102        TestDeserializeAsync<JsonObject>(byteArr, sequence, options, (l, r) => JsonNode.DeepEquals(l, r));
 0103        TestDeserializeAsync<Memory<byte>>(byteArr, sequence, options, (l, r) => l.Span.SequenceEqual(r.Span));
 0104        TestDeserializeAsync<ReadOnlyMemory<byte>>(byteArr, sequence, options, (l, r) => l.Span.SequenceEqual(r.Span));
 0105        TestDeserializeAsync<DateOnly>(byteArr, sequence, options);
 0106        TestDeserializeAsync<TimeOnly>(byteArr, sequence, options);
 0107        TestDeserializeAsync<Half>(byteArr, sequence, options);
 0108        TestDeserializeAsync<Int128>(byteArr, sequence, options);
 0109        TestDeserializeAsync<UInt128>(byteArr, sequence, options);
 0110        TestDeserializeAsync<List<int>>(byteArr, sequence, options, (l, r) =>
 0111        {
 0112           if (l!.Count != r!.Count)
 0113               return false;
 0114
 0115           for (int i = 0; i < l.Count; i++)
 0116           {
 0117               if (l[i] != r[i])
 0118                   return false;
 0119           }
 0120           return true;
 0121        });
 0122        TestDeserializeAsync<Dictionary<string, int>>(byteArr, sequence, options, (l, r) =>
 0123        {
 0124           if (l!.Count != r!.Count)
 0125               return false;
 0126
 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;
 0133        });
 134
 0135        TestRandomType(bytes, sequence, options, maxDepth: NextByte() % 3);
 0136        TestRandomType(bytes, sequence, options, maxDepth: NextByte() % 3);
 137
 0138        while (true)
 0139        {
 0140            if (!Test(ref readerSpan, ref readerSequence,
 0141                static (ref readerSpan) => { return readerSpan.Read(); },
 0142                static (ref readerSequence) => { return readerSequence.Read(); }
 0143            ))
 0144            {
 0145                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        }
 0274    }
 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)
 0282    {
 0283        T spanToken = default!;
 0284        T seqToken = default!;
 0285        Exception? spanEx = null, seqEx = null;
 286
 287        try
 0288        {
 0289            spanToken = span(ref spanReader);
 0290        }
 0291        catch (Exception ex)
 0292        {
 0293            spanEx = ex;
 0294        }
 295
 296        try
 0297        {
 0298            seqToken = seq(ref seqReader);
 0299        }
 0300        catch (Exception ex)
 0301        {
 0302            seqEx = ex;
 0303        }
 304
 0305        if (CompareExceptions(spanEx, seqEx))
 0306        {
 0307            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;
 0336    }
 337
 338    internal static void TestDeserializeAsync<T>(byte[] bytes, ReadOnlySequence<byte> sequence, JsonSerializerOptions op
 0339    {
 0340        Utf8JsonReader readerSpan = new();
 0341        Test(ref readerSpan, ref readerSpan,
 0342            (ref _) => (T?)JsonSerializer.DeserializeAsync(new MemoryStream(bytes), typeof(T), options).GetAwaiter().Get
 0343            (ref _) => (T?)JsonSerializer.DeserializeAsync(PipeReader.Create(sequence), typeof(T), options).GetAwaiter()
 0344            comparer);
 0345    }
 346
 0347    static readonly Type[] groundTypes = [
 0348        typeof(int),
 0349        typeof(string),
 0350        typeof(decimal),
 0351        typeof(DateTimeOffset),
 0352        typeof(JsonDocument),
 0353        typeof(byte),
 0354        typeof(Guid),
 0355        typeof(DateTime),
 0356        typeof(IntEnum),
 0357        typeof(StringEnum),
 0358        typeof(bool),
 0359        typeof(double),
 0360        typeof(long),
 0361        typeof(Int128),
 0362        typeof(char),
 0363    ];
 364
 365    static Type GenerateRandomType(int maxDepth)
 0366    {
 0367        if (maxDepth <= 0)
 0368            return groundTypes[NextByte() % groundTypes.Length];
 369
 0370        int kind = NextByte() % 5;
 0371        return kind switch
 0372        {
 0373            0 => groundTypes[NextByte() % groundTypes.Length],
 0374            1 => typeof(List<>).MakeGenericType(GenerateRandomType(maxDepth - 1)),
 0375            2 => typeof(Dictionary<,>).MakeGenericType(typeof(string), GenerateRandomType(maxDepth - 1)),
 0376            3 => typeof(PocoTemplate<,,,,,>).MakeGenericType(
 0377                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1),
 0378                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1),
 0379                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1)),
 0380            4 => typeof(PocoWithSetters<,,,,,>).MakeGenericType(
 0381                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1),
 0382                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1),
 0383                GenerateRandomType(maxDepth - 1), GenerateRandomType(maxDepth - 1)),
 0384            _ => groundTypes[0],
 0385        };
 0386    }
 387
 388    static void TestRandomType(ReadOnlySpan<byte> bytes, ReadOnlySequence<byte> sequence, JsonSerializerOptions options,
 0389    {
 0390        Type type = GenerateRandomType(maxDepth);
 0391        Type testerType = typeof(Tester<>).MakeGenericType(type);
 0392        var testerInstance = (ITester)Activator.CreateInstance(testerType)!;
 0393        testerInstance.TestWrapper(bytes, sequence, options);
 0394    }
 395
 396    private static ReadOnlySequence<byte> CreateVariableSegmentSequence(ReadOnlySpan<byte> bytes)
 0397    {
 398        // Split into 2-5 segments, but never less than 1 byte per segment
 0399        int maxSegments = Math.Min(6, bytes.Length + 1);
 0400        int segmentCount = maxSegments <= 2 ? 2 : 2 + NextByte() % (maxSegments - 2);
 401
 0402        if (bytes.Length == 0 || bytes.Length < segmentCount)
 0403        {
 0404            return ReadOnlySequence<byte>.Empty;
 405        }
 406
 0407        int[] segmentSizes = new int[segmentCount];
 0408        int remaining = bytes.Length;
 409
 410        // Assign at least 1 byte per segment
 0411        for (int i = 0; i < segmentCount; i++)
 0412        {
 0413            int max = remaining - (segmentCount - i - 1);
 0414            segmentSizes[i] = (i == segmentCount - 1) ? max : (max <= 1 ? 1 : 1 + NextByte() % (max - 1));
 0415            remaining -= segmentSizes[i];
 0416        }
 417
 418        // Build segments
 0419        var buffers = new List<ReadOnlyMemory<byte>>(segmentCount);
 0420        int offset = 0;
 0421        for (int i = 0; i < segmentCount; i++)
 0422        {
 0423            buffers.Add(bytes.Slice(offset, segmentSizes[i]).ToArray());
 0424            offset += segmentSizes[i];
 0425        }
 426
 427        // Link segments
 0428        ReadOnlySequence<byte> sequence = new ReadOnlySequence<byte>(buffers[0]);
 0429        if (buffers.Count == 1)
 0430            return sequence;
 431
 0432        var segments = new List<BufferSegment>();
 0433        foreach (var buffer in buffers)
 0434            segments.Add(new BufferSegment(buffer));
 435
 0436        for (int i = 0; i < segments.Count - 1; i++)
 0437            segments[i].SetNext(segments[i + 1]);
 438
 0439        return new ReadOnlySequence<byte>(segments[0], 0, segments[^1], segments[^1].Memory.Length);
 0440    }
 441
 442    // Helper class for multi-segment ReadOnlySequence
 443    private sealed class BufferSegment : ReadOnlySequenceSegment<byte>
 444    {
 0445        public BufferSegment(ReadOnlyMemory<byte> memory)
 0446        {
 0447            Memory = memory;
 0448        }
 449        public void SetNext(BufferSegment next)
 0450        {
 0451            Next = next;
 0452            next.RunningIndex = RunningIndex + Memory.Length;
 0453        }
 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
 0511        {
 0512            TestDeserializeAsync<T>(bytes.ToArray(), sequence, options, static (_, _) => true);
 0513        }
 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)
 0518    {
 0519        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
 0542        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
 0550        if (spanEx is JsonException spanJsonEx && seqEx is JsonException seqJsonEx)
 0551        {
 0552            if (//spanJsonEx.LineNumber != seqJsonEx.LineNumber ||
 0553                //spanJsonEx.BytePositionInLine != seqJsonEx.BytePositionInLine ||
 0554                spanJsonEx.Path != seqJsonEx.Path/* ||
 0555                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
 0563            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"}'");
 0662    }
 663}
 664
 665public enum IntEnum
 666{
 667}
 668
 669[JsonConverter(typeof(JsonStringEnumConverter))]
 670public enum StringEnum
 671{
 672}