< Summary

Information
Class: DotnetFuzzing.Fuzzers.UTF8Fuzzer
Assembly: DotnetFuzzing
File(s): D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\UTF8Fuzzer.cs
Line coverage
98%
Covered lines: 93
Uncovered lines: 1
Coverable lines: 94
Total lines: 154
Line coverage: 98.9%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%11100%
FuzzTarget(...)100%11100%
Test(...)100%88100%
GetCharCountFromRunes(...)100%22100%
CompareUtf8AndUtf16RuneEnumeration(...)100%66100%

File(s)

D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\UTF8Fuzzer.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.Text.Unicode;
 6using System.Text;
 7
 8namespace DotnetFuzzing.Fuzzers;
 9
 10// Adapted from https://github.com/GrabYourPitchforks/utf8fuzz
 11internal sealed class UTF8Fuzzer : IFuzzer
 12{
 013    public string[] TargetAssemblies => [];
 114    public string[] TargetCoreLibPrefixes { get; } = ["System.Text"];
 15
 116    private static readonly Encoding s_encodingWithCustomReplacement =
 117        Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, new DecoderReplacementFallback("{BAD}"));
 18
 119    private static readonly ArrayBufferWriter<char> s_replacementBufferWriter = new(4096);
 20
 21    public void FuzzTarget(ReadOnlySpan<byte> bytes)
 496222    {
 496223        using var poisonAfter = PooledBoundedMemory<byte>.Rent(bytes, PoisonPagePlacement.After);
 24
 496225        Test(poisonAfter.Span);
 992426    }
 27
 28    private static void Test(ReadOnlySpan<byte> input)
 496229    {
 496230        int charCount = Encoding.UTF8.GetCharCount(input);
 31
 496232        Assert.Equal(charCount, GetCharCountFromRunes(input));
 33
 496234        using var chars = PooledBoundedMemory<char>.Rent(charCount, PoisonPagePlacement.After);
 496235        using var chars2 = PooledBoundedMemory<char>.Rent(charCount + 1024, PoisonPagePlacement.After);
 36
 496237        Assert.Equal(charCount, Encoding.UTF8.GetChars(input, chars.Span));
 38
 496239        CompareUtf8AndUtf16RuneEnumeration(input, chars.Span);
 40
 41        // ToUtf16 with replace=true and exact size buffer.
 496242        {
 496243            OperationStatus opStatus = Utf8.ToUtf16(input, chars2.Span.Slice(0, charCount), out int bytesReadJustNow, ou
 44
 496245            Assert.Equal(OperationStatus.Done, opStatus);
 496246            Assert.Equal(input.Length, bytesReadJustNow);
 496247            Assert.Equal(charCount, charsWrittenJustNow);
 496248            Assert.SequenceEqual<char>(chars.Span, chars2.Span.Slice(0, charCount));
 496249        }
 50
 51        // ToUtf16 with replace=true and extra large buffer.
 496252        {
 496253            OperationStatus opStatus = Utf8.ToUtf16(input, chars2.Span, out int bytesReadJustNow, out int charsWrittenJu
 54
 496255            Assert.Equal(OperationStatus.Done, opStatus);
 496256            Assert.Equal(input.Length, bytesReadJustNow);
 496257            Assert.Equal(charCount, charsWrittenJustNow);
 496258            Assert.SequenceEqual<char>(chars.Span, chars2.Span.Slice(0, charCount));
 496259        }
 60
 61        // Running ToUtf16 with replace=false and extra large buffer.
 496262        {
 496263            ReadOnlySpan<byte> utf8 = input;
 496264            Span<char> output = chars2.Span;
 496265            output.Clear();
 66
 33526667            while (!utf8.IsEmpty)
 33030468            {
 33030469                OperationStatus opStatus = Utf8.ToUtf16(utf8, output, out int bytesReadJustNow, out int charsWrittenJust
 70
 33030471                CompareUtf8AndUtf16RuneEnumeration(utf8.Slice(0, bytesReadJustNow), output.Slice(0, charsWrittenJustNow)
 72
 33030473                utf8 = utf8.Slice(bytesReadJustNow);
 74
 33030475                if (opStatus != OperationStatus.Done)
 32778876                {
 32778877                    Assert.Equal(OperationStatus.InvalidData, opStatus);
 78
 79                    // Skip over invalid data
 32778880                    Rune.DecodeFromUtf8(utf8, out _, out int bytesToSkip);
 32778881                    utf8 = utf8.Slice(bytesToSkip);
 32778882                }
 33030483            }
 496284        }
 85
 86        // Trying a custom decoder replacement.
 496287        {
 496288            ReadOnlySpan<byte> utf8 = input;
 89
 496290            string decoded = s_encodingWithCustomReplacement.GetString(utf8);
 91
 496292            using var decodedChars = PooledBoundedMemory<char>.Rent(decoded.Length, PoisonPagePlacement.After);
 93
 496294            s_replacementBufferWriter.ResetWrittenCount();
 95
 33526696            while (!utf8.IsEmpty)
 33030497            {
 33030498                OperationStatus opStatus = Utf8.ToUtf16(utf8, decodedChars.Span, out int bytesReadJustNow, out int chars
 33030499                s_replacementBufferWriter.Write(decodedChars.Span.Slice(0, charsWrittenJustNow));
 100
 330304101                utf8 = utf8.Slice(bytesReadJustNow);
 102
 330304103                if (opStatus != OperationStatus.Done)
 327788104                {
 327788105                    Assert.Equal(OperationStatus.InvalidData, opStatus);
 106
 107                    // Skip over invalid data
 327788108                    Rune.DecodeFromUtf8(utf8, out _, out int bytesToSkip);
 327788109                    utf8 = utf8.Slice(bytesToSkip);
 110
 327788111                    s_replacementBufferWriter.Write("{BAD}");
 327788112                }
 330304113            }
 114
 4962115            Assert.SequenceEqual(decoded, s_replacementBufferWriter.WrittenSpan);
 4962116        }
 9924117    }
 118
 119    private static int GetCharCountFromRunes(ReadOnlySpan<byte> utf8)
 4962120    {
 4962121        int charCount = 0;
 122
 1036978123        while (!utf8.IsEmpty)
 1032016124        {
 1032016125            Rune.DecodeFromUtf8(utf8, out Rune thisRune, out int bytesConsumed);
 1032016126            charCount += thisRune.Utf16SequenceLength; // ok if U+FFFD replacement
 1032016127            utf8 = utf8.Slice(bytesConsumed);
 1032016128        }
 129
 4962130        return charCount;
 4962131    }
 132
 133    private static void CompareUtf8AndUtf16RuneEnumeration(ReadOnlySpan<byte> utf8, ReadOnlySpan<char> utf16, bool expec
 335266134    {
 2071510135        while (!utf8.IsEmpty && !utf16.IsEmpty)
 1736244136        {
 1736244137            OperationStatus utf8Status = Rune.DecodeFromUtf8(utf8, out Rune inputUtf8Rune, out int bytesConsumed);
 1736244138            OperationStatus utf16Status = Rune.DecodeFromUtf16(utf16, out Rune inputUtf16Rune, out int charsConsumed);
 139
 1736244140            if (expectIsValid)
 704228141            {
 704228142                Assert.Equal(OperationStatus.Done, utf8Status);
 704228143                Assert.Equal(OperationStatus.Done, utf16Status);
 704228144            }
 145
 1736244146            Assert.Equal(inputUtf8Rune, inputUtf16Rune);
 147
 1736244148            utf8 = utf8.Slice(bytesConsumed);
 1736244149            utf16 = utf16.Slice(charsConsumed);
 1736244150        }
 151
 335266152        Assert.Equal(utf8.Length, utf16.Length);
 335266153    }
 154}