< Summary

Information
Class: DotnetFuzzing.Fuzzers.TextEncodingFuzzer
Assembly: DotnetFuzzing
File(s): D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\TextEncodingFuzzer.cs
Line coverage
99%
Covered lines: 106
Uncovered lines: 1
Coverable lines: 107
Total lines: 188
Line coverage: 99%
Branch coverage
100%
Covered branches: 8
Total branches: 8
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
DotnetFuzzing.IFuzzer.FuzzTarget(...)100%11100%
TestLatin1(...)100%11100%
TestASCII(...)100%11100%
TestUnicode(...)100%11100%
TestUtf32(...)100%11100%
TestUtf7(...)100%11100%
TestUtf8(...)100%11100%
TestWithSubstitution(...)100%11100%
TestWithExceptions(...)100%11100%
TestWithConvert(...)100%44100%
TestWithConvert(...)100%44100%

File(s)

D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\TextEncodingFuzzer.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.Diagnostics;
 6using System.Text;
 7
 8namespace DotnetFuzzing.Fuzzers;
 9
 10// The fuzzing infrastructure currently does not support fuzzing .NET Framework.
 11// However, this test class, while running under .NET Core, was used to foward the fuzzing
 12// input to a .NET Framework console app. That app had the same test semantics as the tests
 13// here, although used slightly different supporting APIs since not all supporting library
 14// and language features are present in .NET Framework.
 15// This fowarding approach and .NET Framework test code is presevered in the original Pull
 16// Request for this file. The approach used Base64 encoding to convert the incoming
 17// ReadOnlySpan<byte> to a string which was then passed to the Main() method of the .NET
 18// Framework app which was then converted back to bytes before being passed to the .NET
 19// Framework fuzzing tests.
 20internal sealed class TextEncodingFuzzer : IFuzzer
 21{
 022    string[] IFuzzer.TargetAssemblies => [];
 123    string[] IFuzzer.TargetCoreLibPrefixes { get; } = ["System.Text"];
 24
 25    void IFuzzer.FuzzTarget(ReadOnlySpan<byte> bytes)
 318126    {
 318127        using PooledBoundedMemory<byte> poisonAfter = PooledBoundedMemory<byte>.Rent(bytes, PoisonPagePlacement.After);
 28
 318129        TestLatin1(poisonAfter.Span);
 318130        TestASCII(poisonAfter.Span);
 318131        TestUnicode(poisonAfter.Span);
 318132        TestUtf32(poisonAfter.Span);
 318133        TestUtf7(poisonAfter.Span);
 318134        TestUtf8(poisonAfter.Span);
 636235    }
 36
 37    // We use individual methods for each encoding, so if there's an exception then
 38    // it's clear which encoding failed based on the call stack.
 39
 40    private static void TestLatin1(ReadOnlySpan<byte> input)
 318141    {
 318142        TestWithSubstitution(input, Encoding.GetEncoding("ISO-8859-1"));
 318143        TestWithConvert(input, Encoding.GetEncoding("ISO-8859-1"));
 318144    }
 45
 46    private static void TestASCII(ReadOnlySpan<byte> input)
 318147    {
 318148        TestWithSubstitution(input, new ASCIIEncoding());
 318149        TestWithConvert(input, new ASCIIEncoding());
 318150    }
 51
 52    private static void TestUnicode(ReadOnlySpan<byte> input)
 318153    {
 318154        TestWithSubstitution(input, new UnicodeEncoding());
 318155        TestWithExceptions(input, new UnicodeEncoding(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: true)
 318156        TestWithConvert(input, new UnicodeEncoding());
 318157    }
 58
 59    private static void TestUtf32(ReadOnlySpan<byte> input)
 318160    {
 318161        TestWithSubstitution(input, new UTF32Encoding());
 318162        TestWithExceptions(input, new UTF32Encoding(bigEndian: false, byteOrderMark: false, throwOnInvalidCharacters: tr
 318163        TestWithConvert(input, new UTF32Encoding());
 318164    }
 65
 66    private static void TestUtf7(ReadOnlySpan<byte> input)
 318167    {
 68#pragma warning disable SYSLIB0001 // Type or member is obsolete
 318169        TestWithSubstitution(input, new UTF7Encoding());
 70#pragma warning restore SYSLIB0001
 318171    }
 72
 73    private static void TestUtf8(ReadOnlySpan<byte> input)
 318174    {
 318175        TestWithSubstitution(input, new UTF8Encoding());
 318176        TestWithExceptions(input, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true));
 318177        TestWithConvert(input, new UTF8Encoding());
 318178    }
 79
 80    private static void TestWithSubstitution(ReadOnlySpan<byte> input, Encoding encoding)
 2069281    {
 2069282        Decoder decoder = encoding.GetDecoder();
 2069283        int charCount = decoder.GetCharCount(input, flush: true);
 84
 2069285        using PooledBoundedMemory<char> chars = PooledBoundedMemory<char>.Rent(charCount, PoisonPagePlacement.After);
 2069286        using PooledBoundedMemory<char> chars2 = PooledBoundedMemory<char>.Rent(charCount, PoisonPagePlacement.After);
 87
 88        // *4 for worst case scenario (*2 for char->byte + *2 for encoding)
 89        // +2 is for possible Base64 padding with UTF7Encoding.
 2069290        using PooledBoundedMemory<byte> bytes = PooledBoundedMemory<byte>.Rent(charCount * 4 + 2, PoisonPagePlacement.Af
 91
 2069292        decoder.Reset();
 2069293        int written = decoder.GetChars(input, chars.Span, flush: true);
 2069294        Assert.Equal(charCount, written);
 95
 2069296        Encoder encoder = encoding.GetEncoder();
 97        // We use flush:true here for UTF7Encoding which may do Base64 padding at the end.
 2069298        int bytesWritten = encoder.GetBytes(chars.Span, bytes.Span, flush: true);
 99
 100        // Decode the encoded values. Any substitutions will be comparable now.
 20692101        decoder.Reset();
 20692102        written = decoder.GetChars(bytes.Span.Slice(0, bytesWritten), chars2.Span, flush: true);
 20692103        Assert.Equal(charCount, written);
 104
 105        // Verify that we round-tripped the values.
 20692106        Assert.SequenceEqual<char>(chars.Span, chars2.Span);
 41384107    }
 108
 109    // If there are substitutions, these cases will fail with DecoderFallbackException early on,
 110    // otherwise there should be no DecoderFallbackExceptions.
 111    private static void TestWithExceptions(ReadOnlySpan<byte> input, Encoding encoding)
 9543112    {
 9543113        Assert.Equal(typeof(DecoderExceptionFallback), encoding.DecoderFallback.GetType());
 9543114        Assert.Equal(typeof(EncoderExceptionFallback), encoding.EncoderFallback.GetType());
 115
 9543116        Decoder decoder = encoding.GetDecoder();
 117
 118        int charCount;
 119        try
 9543120        {
 9543121            charCount = decoder.GetCharCount(input, flush: true);
 1606122        }
 7937123        catch (DecoderFallbackException)
 7937124        {
 125            // The input is not valid without fallbacks.
 7937126            return;
 127        }
 128
 1606129        TestWithSubstitution(input, encoding);
 9543130    }
 131
 132    private static void TestWithConvert(ReadOnlySpan<byte> input, Encoding encoding)
 15905133    {
 134        // Use a few boundary cases.
 15905135        TestWithConvert(input, encoding, 1);
 15905136        TestWithConvert(input, encoding, 2);
 15905137        TestWithConvert(input, encoding, 3);
 15905138        TestWithConvert(input, encoding, 4);
 15905139        TestWithConvert(input, encoding, input.Length);
 140
 15905141        if (input.Length >= 6)
 13610142        {
 13610143            TestWithConvert(input, encoding, input.Length - 1);
 144
 13610145            if (input.Length >= 12)
 11170146            {
 11170147                TestWithConvert(input, encoding, input.Length / 2);
 11170148            }
 13610149        }
 15905150    }
 151
 152    // Verify that obtaining data using several Convert() calls matches the result from a single GetChars() call.
 153    private static void TestWithConvert(ReadOnlySpan<byte> input, Encoding encoding, int blockSize)
 104305154    {
 104305155        Decoder decoder = encoding.GetDecoder();
 156
 104305157        int charCount = decoder.GetCharCount(input, flush: true);
 158
 104305159        using PooledBoundedMemory<char> chars = PooledBoundedMemory<char>.Rent(charCount, PoisonPagePlacement.After);
 104305160        using PooledBoundedMemory<char> chars2 = PooledBoundedMemory<char>.Rent(charCount, PoisonPagePlacement.After);
 161
 104305162        decoder.Reset();
 104305163        int charsUsedTotal = 0;
 104305164        int i = 0;
 165
 2685540166        while (i < input.Length)
 2581235167        {
 2581235168            bool lastIteration = i + blockSize >= input.Length;
 2581235169            int bytesToRead = lastIteration ? input.Length - i : blockSize;
 170
 2581235171            decoder.Convert(
 2581235172                input.Slice(i, bytesToRead),
 2581235173                chars.Span.Slice(charsUsedTotal, charCount - charsUsedTotal),
 2581235174                flush: lastIteration,
 2581235175                out int bytesUsed,
 2581235176                out int charsUsed,
 2581235177                out bool _);
 178
 2581235179            i += bytesUsed;
 2581235180            charsUsedTotal += charsUsed;
 2581235181        }
 182
 104305183        Assert.Equal(charsUsedTotal, charCount);
 104305184        decoder.Reset();
 104305185        decoder.GetChars(input, chars2.Span, flush: true);
 104305186        Assert.SequenceEqual<char>(chars.Span, chars2.Span);
 208610187    }
 188}