< Summary

Information
Class: DotnetFuzzing.Fuzzers.UTF8Fuzzer
Assembly: DotnetFuzzing
File(s): D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\UTF8Fuzzer.cs
Line coverage
1%
Covered lines: 1
Uncovered lines: 93
Coverable lines: 94
Total lines: 154
Line coverage: 1%
Branch coverage
0%
Covered branches: 0
Total branches: 16
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%
FuzzTarget(...)100%110%
Test(...)0%880%
GetCharCountFromRunes(...)0%220%
CompareUtf8AndUtf16RuneEnumeration(...)0%660%

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
 016    private static readonly Encoding s_encodingWithCustomReplacement =
 017        Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, new DecoderReplacementFallback("{BAD}"));
 18
 019    private static readonly ArrayBufferWriter<char> s_replacementBufferWriter = new(4096);
 20
 21    public void FuzzTarget(ReadOnlySpan<byte> bytes)
 022    {
 023        using var poisonAfter = PooledBoundedMemory<byte>.Rent(bytes, PoisonPagePlacement.After);
 24
 025        Test(poisonAfter.Span);
 026    }
 27
 28    private static void Test(ReadOnlySpan<byte> input)
 029    {
 030        int charCount = Encoding.UTF8.GetCharCount(input);
 31
 032        Assert.Equal(charCount, GetCharCountFromRunes(input));
 33
 034        using var chars = PooledBoundedMemory<char>.Rent(charCount, PoisonPagePlacement.After);
 035        using var chars2 = PooledBoundedMemory<char>.Rent(charCount + 1024, PoisonPagePlacement.After);
 36
 037        Assert.Equal(charCount, Encoding.UTF8.GetChars(input, chars.Span));
 38
 039        CompareUtf8AndUtf16RuneEnumeration(input, chars.Span);
 40
 41        // ToUtf16 with replace=true and exact size buffer.
 042        {
 043            OperationStatus opStatus = Utf8.ToUtf16(input, chars2.Span.Slice(0, charCount), out int bytesReadJustNow, ou
 44
 045            Assert.Equal(OperationStatus.Done, opStatus);
 046            Assert.Equal(input.Length, bytesReadJustNow);
 047            Assert.Equal(charCount, charsWrittenJustNow);
 048            Assert.SequenceEqual<char>(chars.Span, chars2.Span.Slice(0, charCount));
 049        }
 50
 51        // ToUtf16 with replace=true and extra large buffer.
 052        {
 053            OperationStatus opStatus = Utf8.ToUtf16(input, chars2.Span, out int bytesReadJustNow, out int charsWrittenJu
 54
 055            Assert.Equal(OperationStatus.Done, opStatus);
 056            Assert.Equal(input.Length, bytesReadJustNow);
 057            Assert.Equal(charCount, charsWrittenJustNow);
 058            Assert.SequenceEqual<char>(chars.Span, chars2.Span.Slice(0, charCount));
 059        }
 60
 61        // Running ToUtf16 with replace=false and extra large buffer.
 062        {
 063            ReadOnlySpan<byte> utf8 = input;
 064            Span<char> output = chars2.Span;
 065            output.Clear();
 66
 067            while (!utf8.IsEmpty)
 068            {
 069                OperationStatus opStatus = Utf8.ToUtf16(utf8, output, out int bytesReadJustNow, out int charsWrittenJust
 70
 071                CompareUtf8AndUtf16RuneEnumeration(utf8.Slice(0, bytesReadJustNow), output.Slice(0, charsWrittenJustNow)
 72
 073                utf8 = utf8.Slice(bytesReadJustNow);
 74
 075                if (opStatus != OperationStatus.Done)
 076                {
 077                    Assert.Equal(OperationStatus.InvalidData, opStatus);
 78
 79                    // Skip over invalid data
 080                    Rune.DecodeFromUtf8(utf8, out _, out int bytesToSkip);
 081                    utf8 = utf8.Slice(bytesToSkip);
 082                }
 083            }
 084        }
 85
 86        // Trying a custom decoder replacement.
 087        {
 088            ReadOnlySpan<byte> utf8 = input;
 89
 090            string decoded = s_encodingWithCustomReplacement.GetString(utf8);
 91
 092            using var decodedChars = PooledBoundedMemory<char>.Rent(decoded.Length, PoisonPagePlacement.After);
 93
 094            s_replacementBufferWriter.ResetWrittenCount();
 95
 096            while (!utf8.IsEmpty)
 097            {
 098                OperationStatus opStatus = Utf8.ToUtf16(utf8, decodedChars.Span, out int bytesReadJustNow, out int chars
 099                s_replacementBufferWriter.Write(decodedChars.Span.Slice(0, charsWrittenJustNow));
 100
 0101                utf8 = utf8.Slice(bytesReadJustNow);
 102
 0103                if (opStatus != OperationStatus.Done)
 0104                {
 0105                    Assert.Equal(OperationStatus.InvalidData, opStatus);
 106
 107                    // Skip over invalid data
 0108                    Rune.DecodeFromUtf8(utf8, out _, out int bytesToSkip);
 0109                    utf8 = utf8.Slice(bytesToSkip);
 110
 0111                    s_replacementBufferWriter.Write("{BAD}");
 0112                }
 0113            }
 114
 0115            Assert.SequenceEqual(decoded, s_replacementBufferWriter.WrittenSpan);
 0116        }
 0117    }
 118
 119    private static int GetCharCountFromRunes(ReadOnlySpan<byte> utf8)
 0120    {
 0121        int charCount = 0;
 122
 0123        while (!utf8.IsEmpty)
 0124        {
 0125            Rune.DecodeFromUtf8(utf8, out Rune thisRune, out int bytesConsumed);
 0126            charCount += thisRune.Utf16SequenceLength; // ok if U+FFFD replacement
 0127            utf8 = utf8.Slice(bytesConsumed);
 0128        }
 129
 0130        return charCount;
 0131    }
 132
 133    private static void CompareUtf8AndUtf16RuneEnumeration(ReadOnlySpan<byte> utf8, ReadOnlySpan<char> utf16, bool expec
 0134    {
 0135        while (!utf8.IsEmpty && !utf16.IsEmpty)
 0136        {
 0137            OperationStatus utf8Status = Rune.DecodeFromUtf8(utf8, out Rune inputUtf8Rune, out int bytesConsumed);
 0138            OperationStatus utf16Status = Rune.DecodeFromUtf16(utf16, out Rune inputUtf16Rune, out int charsConsumed);
 139
 0140            if (expectIsValid)
 0141            {
 0142                Assert.Equal(OperationStatus.Done, utf8Status);
 0143                Assert.Equal(OperationStatus.Done, utf16Status);
 0144            }
 145
 0146            Assert.Equal(inputUtf8Rune, inputUtf16Rune);
 147
 0148            utf8 = utf8.Slice(bytesConsumed);
 0149            utf16 = utf16.Slice(charsConsumed);
 0150        }
 151
 0152        Assert.Equal(utf8.Length, utf16.Length);
 0153    }
 154}