< Summary

Information
Class: DotnetFuzzing.Fuzzers.Utf8JsonWriterFuzzer
Assembly: DotnetFuzzing
File(s): D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\Utf8JsonWriterFuzzer.cs
Line coverage
99%
Covered lines: 152
Uncovered lines: 1
Coverable lines: 153
Total lines: 261
Line coverage: 99.3%
Branch coverage
100%
Covered branches: 42
Total branches: 42
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
FuzzTarget(...)100%3232100%
WriteStringValueSegments(...)100%66100%
WriteStringValueSegments(...)100%22100%
EncodeToUtf8(...)100%22100%
EncodeToUtf8(...)100%11100%
EncodeTranscode(...)100%11100%

File(s)

D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\Utf8JsonWriterFuzzer.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.Diagnostics;
 10using System.IO;
 11using System.Runtime.CompilerServices;
 12using System.Runtime.InteropServices;
 13using System.Text;
 14using System.Text.Encodings.Web;
 15using System.Text.Json;
 16using System.Text.Unicode;
 17using SharpFuzz;
 18
 19namespace DotnetFuzzing.Fuzzers;
 20
 21internal sealed class Utf8JsonWriterFuzzer : IFuzzer
 22{
 123    public string[] TargetAssemblies { get; } = ["System.Text.Json"];
 24
 025    public string[] TargetCoreLibPrefixes => [];
 26
 27    // One of the bytes in the input is used to set various test options.
 28    // Each bit in that byte represents a different option as indicated here.
 29
 30    // Options for JsonWriterOptions
 31    private const byte IndentFlag = 1;
 32    private const byte EncoderFlag = 1 << 1;
 33    private const byte MaxDepthFlag = 1 << 2;
 34    private const byte NewLineFlag = 1 << 3;
 35    private const byte SkipValidationFlag = 1 << 4;
 36
 37    // Options for choosing between base64, UTF-8 and UTF-16 encoding
 38    private const byte EncodingMask = 0b11 << 5;
 39    private const byte Utf8EncodingFlag = 0b00 << 5;
 40    private const byte Utf16EncodingFlag = 0b01 << 5;
 41    private const byte Base64EncodingFlag = 0b10 << 5;
 42
 43    public void FuzzTarget(ReadOnlySpan<byte> bytes)
 28244    {
 45        const int minLength = 10; // 2 ints, 1 byte, and 1 padding to align chars
 28246        if (bytes.Length < minLength)
 147        {
 148            return;
 49        }
 50
 51        // First 2 ints are used as indices to slice the input and the following byte is used for options
 28152        ReadOnlySpan<int> ints = MemoryMarshal.Cast<byte, int>(bytes);
 28153        int slice1 = ints[0];
 28154        int slice2 = ints[1];
 28155        byte optionsByte = bytes[8];
 28156        bytes = bytes.Slice(minLength);
 28157        ReadOnlySpan<char> chars = MemoryMarshal.Cast<byte, char>(bytes);
 58
 59        // Validate that the indices are within bounds of the input
 28160        int encoding = optionsByte & EncodingMask;
 28161        if (encoding is not Utf8EncodingFlag and not Utf16EncodingFlag and not Base64EncodingFlag)
 262        {
 263            return;
 64        }
 65
 27966        if (!(0 <= slice1 && slice1 <= slice2 && slice2 <= (encoding is Utf16EncodingFlag ? chars.Length : bytes.Length)
 867        {
 868            return;
 69        }
 70
 71        // Set up options based on the first byte
 27172        bool indented = (optionsByte & IndentFlag) == 0;
 27173        JsonWriterOptions options = new()
 27174        {
 27175            Encoder = (optionsByte & EncoderFlag) == 0 ? JavaScriptEncoder.Default : JavaScriptEncoder.UnsafeRelaxedJson
 27176            Indented = indented,
 27177            MaxDepth = (optionsByte & MaxDepthFlag) == 0 ? 1 : 0,
 27178            NewLine = (optionsByte & NewLineFlag) == 0 ? "\n" : "\r\n",
 27179            SkipValidation = (optionsByte & SkipValidationFlag) == 0,
 27180        };
 81
 82        // Compute the expected result by using the encoder directly and the input
 27183        int maxExpandedSizeBytes = 6 * bytes.Length + 2;
 27184        byte[] expectedBuffer = ArrayPool<byte>.Shared.Rent(maxExpandedSizeBytes);
 27185        Span<byte> expected =
 27186            expectedBuffer.AsSpan(0, encoding == Utf16EncodingFlag
 27187                ? EncodeToUtf8(chars, expectedBuffer, options.Encoder)
 27188                : EncodeToUtf8(bytes, expectedBuffer, options.Encoder, encoding == Base64EncodingFlag));
 89
 90        // Compute the actual result by using Utf8JsonWriter. Each iteration is a different slice of the input, but the 
 27191        byte[] actualBuffer = new byte[expected.Length];
 243992        foreach (ReadOnlySpan<Range> ranges in new[]
 27193        {
 27194            new[] { 0.. },
 27195            new[] { 0..slice1, slice1.. },
 27196            new[] { 0..slice1, slice1..slice2, slice2.. },
 27197        })
 81398        {
 81399            using MemoryStream stream = new(actualBuffer);
 813100            using Utf8JsonWriter writer = new(stream, options);
 101
 813102            if (encoding == Utf16EncodingFlag)
 396103            {
 396104                WriteStringValueSegments(writer, chars, ranges);
 396105            }
 106            else
 417107            {
 417108                WriteStringValueSegments(writer, bytes, ranges, encoding == Base64EncodingFlag);
 417109            }
 110
 813111            writer.Flush();
 112
 113            // Compare the expected and actual results
 813114            Assert.SequenceEqual(expected, actualBuffer);
 813115            Assert.Equal(expected.Length, writer.BytesCommitted);
 813116            Assert.Equal(0, writer.BytesPending);
 117
 813118            Array.Clear(actualBuffer);
 813119        }
 120
 121        // Additional test for mixing UTF-8 and UTF-16 encoding. The alignment math is easier in UTF-16 mode so just run
 271122        if (encoding == Utf16EncodingFlag && !options.SkipValidation)
 91123        {
 91124            Array.Clear(expectedBuffer);
 125
 91126            {
 91127                ReadOnlySpan<char> firstSegment = chars[slice1..];
 91128                ReadOnlySpan<byte> secondSegment = bytes[0..(2 * slice1)];
 129
 91130                expected = expectedBuffer.AsSpan(0, EncodeToUtf8(firstSegment, expectedBuffer, options.Encoder));
 131
 91132                actualBuffer = new byte[expected.Length];
 91133                using MemoryStream stream = new(actualBuffer);
 91134                using Utf8JsonWriter writer = new(stream, options);
 135
 136                // UTF-16 + UTF-8
 91137                writer.WriteStringValueSegment(firstSegment, false);
 182138                Assert.Throws<InvalidOperationException, ReadOnlySpan<byte>>(state => writer.WriteStringValueSegment(sta
 139
 91140                stream.Position = 0;
 91141                writer.Reset();
 142
 143                // UTF-16 + Base64
 91144                writer.WriteStringValueSegment(firstSegment, false);
 182145                Assert.Throws<InvalidOperationException, ReadOnlySpan<byte>>(state => writer.WriteBase64StringSegment(st
 91146            }
 147
 91148            Array.Clear(expectedBuffer);
 149
 91150            {
 91151                ReadOnlySpan<byte> firstSegment = bytes[0..(2 * slice1)];
 91152                ReadOnlySpan<char> secondSegment = chars[slice1..];
 153
 91154                expected = expectedBuffer.AsSpan(0, EncodeToUtf8(firstSegment, expectedBuffer, options.Encoder, base64En
 155
 91156                actualBuffer = new byte[expected.Length];
 91157                using MemoryStream stream = new(actualBuffer);
 91158                using Utf8JsonWriter writer = new(stream, options);
 159
 160                // UTF-8 + UTF-16
 91161                writer.WriteStringValueSegment(firstSegment, false);
 182162                Assert.Throws<InvalidOperationException, ReadOnlySpan<char>>(state => writer.WriteStringValueSegment(sta
 163
 91164                stream.Position = 0;
 91165                writer.Reset();
 166
 167                // UTF-8 + Base64
 91168                writer.WriteStringValueSegment(firstSegment, false);
 182169                Assert.Throws<InvalidOperationException, ReadOnlySpan<byte>>(state => writer.WriteBase64StringSegment(st
 91170            }
 171
 91172            Array.Clear(expectedBuffer);
 173
 91174            {
 91175                ReadOnlySpan<byte> firstSegment = bytes[0..(2 * slice1)];
 91176                ReadOnlySpan<char> secondSegment = chars[slice1..];
 177
 91178                expected = expectedBuffer.AsSpan(0, EncodeToUtf8(firstSegment, expectedBuffer, options.Encoder, base64En
 179
 91180                actualBuffer = new byte[expected.Length];
 91181                using MemoryStream stream = new(actualBuffer);
 91182                using Utf8JsonWriter writer = new(stream, options);
 183
 184                // Base64 + UTF-16
 91185                writer.WriteBase64StringSegment(firstSegment, false);
 182186                Assert.Throws<InvalidOperationException, ReadOnlySpan<char>>(state => writer.WriteStringValueSegment(sta
 187
 91188                stream.Position = 0;
 91189                writer.Reset();
 190
 191                // Base64 + UTF-8
 91192                writer.WriteBase64StringSegment(firstSegment, false);
 182193                Assert.Throws<InvalidOperationException, ReadOnlySpan<byte>>(state => writer.WriteStringValueSegment(sta
 91194            }
 91195        }
 196
 271197        ArrayPool<byte>.Shared.Return(expectedBuffer);
 282198    }
 199
 200    private static void WriteStringValueSegments(Utf8JsonWriter writer, ReadOnlySpan<byte> bytes, ReadOnlySpan<Range> ra
 417201    {
 417202        if (base64Encode)
 156203        {
 936204            for (int i = 0; i < ranges.Length; i++)
 312205            {
 312206                writer.WriteBase64StringSegment(bytes[ranges[i]], i == ranges.Length - 1);
 312207            }
 156208        }
 209        else
 261210        {
 1566211            for (int i = 0; i < ranges.Length; i++)
 522212            {
 522213                writer.WriteStringValueSegment(bytes[ranges[i]], i == ranges.Length - 1);
 522214            }
 261215        }
 417216    }
 217
 218    private static void WriteStringValueSegments(Utf8JsonWriter writer, ReadOnlySpan<char> chars, ReadOnlySpan<Range> ra
 396219    {
 2376220        for (int i = 0; i < ranges.Length; i++)
 792221        {
 792222            writer.WriteStringValueSegment(chars[ranges[i]], i == ranges.Length - 1);
 792223        }
 396224    }
 225
 226    private static int EncodeToUtf8(ReadOnlySpan<byte> bytes, Span<byte> destBuffer, JavaScriptEncoder encoder, bool bas
 321227    {
 321228        destBuffer[0] = (byte)'"';
 229
 230        int written;
 321231        if (base64Encode)
 143232        {
 143233            Base64.EncodeToUtf8(bytes, destBuffer[1..], out _, out written, isFinalBlock: true);
 143234        }
 235        else
 178236        {
 178237            encoder.EncodeUtf8(bytes, destBuffer[1..], out _, out written, isFinalBlock: true);
 178238        }
 239
 321240        destBuffer[++written] = (byte)'"';
 321241        return written + 1;
 321242    }
 243
 244    private static int EncodeToUtf8(ReadOnlySpan<char> chars, Span<byte> destBuffer, JavaScriptEncoder encoder)
 223245    {
 223246        int written = 1;
 223247        destBuffer[0] = (byte)'"';
 223248        destBuffer[written += EncodeTranscode(chars, destBuffer[1..], encoder)] = (byte)'"';
 223249        return written + 1;
 223250    }
 251
 252    private static int EncodeTranscode(ReadOnlySpan<char> chars, Span<byte> destBuffer, JavaScriptEncoder encoder, bool 
 223253    {
 223254        var utf16buffer = ArrayPool<char>.Shared.Rent(6 * chars.Length);
 223255        encoder.Encode(chars, utf16buffer, out _, out int written, isFinalBlock: true);
 256
 223257        Utf8.FromUtf16(utf16buffer.AsSpan(0, written), destBuffer, out _, out written, isFinalBlock);
 223258        ArrayPool<char>.Shared.Return(utf16buffer);
 223259        return written;
 223260    }
 261}