< Summary

Information
Class: DotnetFuzzing.Fuzzers.TextEncodingFuzzer
Assembly: DotnetFuzzing
File(s): D:\runner\runtime\src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\TextEncodingFuzzer.cs
Line coverage
0%
Covered lines: 1
Uncovered lines: 106
Coverable lines: 107
Total lines: 188
Line coverage: 0.9%
Branch coverage
0%
Covered branches: 0
Total branches: 8
Branch coverage: 0%
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%110%
TestLatin1(...)100%110%
TestASCII(...)100%110%
TestUnicode(...)100%110%
TestUtf32(...)100%110%
TestUtf7(...)100%110%
TestUtf8(...)100%110%
TestWithSubstitution(...)100%110%
TestWithExceptions(...)100%110%
TestWithConvert(...)0%440%
TestWithConvert(...)0%440%

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)
 026    {
 027        using PooledBoundedMemory<byte> poisonAfter = PooledBoundedMemory<byte>.Rent(bytes, PoisonPagePlacement.After);
 28
 029        TestLatin1(poisonAfter.Span);
 030        TestASCII(poisonAfter.Span);
 031        TestUnicode(poisonAfter.Span);
 032        TestUtf32(poisonAfter.Span);
 033        TestUtf7(poisonAfter.Span);
 034        TestUtf8(poisonAfter.Span);
 035    }
 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)
 041    {
 042        TestWithSubstitution(input, Encoding.GetEncoding("ISO-8859-1"));
 043        TestWithConvert(input, Encoding.GetEncoding("ISO-8859-1"));
 044    }
 45
 46    private static void TestASCII(ReadOnlySpan<byte> input)
 047    {
 048        TestWithSubstitution(input, new ASCIIEncoding());
 049        TestWithConvert(input, new ASCIIEncoding());
 050    }
 51
 52    private static void TestUnicode(ReadOnlySpan<byte> input)
 053    {
 054        TestWithSubstitution(input, new UnicodeEncoding());
 055        TestWithExceptions(input, new UnicodeEncoding(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: true)
 056        TestWithConvert(input, new UnicodeEncoding());
 057    }
 58
 59    private static void TestUtf32(ReadOnlySpan<byte> input)
 060    {
 061        TestWithSubstitution(input, new UTF32Encoding());
 062        TestWithExceptions(input, new UTF32Encoding(bigEndian: false, byteOrderMark: false, throwOnInvalidCharacters: tr
 063        TestWithConvert(input, new UTF32Encoding());
 064    }
 65
 66    private static void TestUtf7(ReadOnlySpan<byte> input)
 067    {
 68#pragma warning disable SYSLIB0001 // Type or member is obsolete
 069        TestWithSubstitution(input, new UTF7Encoding());
 70#pragma warning restore SYSLIB0001
 071    }
 72
 73    private static void TestUtf8(ReadOnlySpan<byte> input)
 074    {
 075        TestWithSubstitution(input, new UTF8Encoding());
 076        TestWithExceptions(input, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true));
 077        TestWithConvert(input, new UTF8Encoding());
 078    }
 79
 80    private static void TestWithSubstitution(ReadOnlySpan<byte> input, Encoding encoding)
 081    {
 082        Decoder decoder = encoding.GetDecoder();
 083        int charCount = decoder.GetCharCount(input, flush: true);
 84
 085        using PooledBoundedMemory<char> chars = PooledBoundedMemory<char>.Rent(charCount, PoisonPagePlacement.After);
 086        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.
 090        using PooledBoundedMemory<byte> bytes = PooledBoundedMemory<byte>.Rent(charCount * 4 + 2, PoisonPagePlacement.Af
 91
 092        decoder.Reset();
 093        int written = decoder.GetChars(input, chars.Span, flush: true);
 094        Assert.Equal(charCount, written);
 95
 096        Encoder encoder = encoding.GetEncoder();
 97        // We use flush:true here for UTF7Encoding which may do Base64 padding at the end.
 098        int bytesWritten = encoder.GetBytes(chars.Span, bytes.Span, flush: true);
 99
 100        // Decode the encoded values. Any substitutions will be comparable now.
 0101        decoder.Reset();
 0102        written = decoder.GetChars(bytes.Span.Slice(0, bytesWritten), chars2.Span, flush: true);
 0103        Assert.Equal(charCount, written);
 104
 105        // Verify that we round-tripped the values.
 0106        Assert.SequenceEqual<char>(chars.Span, chars2.Span);
 0107    }
 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)
 0112    {
 0113        Assert.Equal(typeof(DecoderExceptionFallback), encoding.DecoderFallback.GetType());
 0114        Assert.Equal(typeof(EncoderExceptionFallback), encoding.EncoderFallback.GetType());
 115
 0116        Decoder decoder = encoding.GetDecoder();
 117
 118        int charCount;
 119        try
 0120        {
 0121            charCount = decoder.GetCharCount(input, flush: true);
 0122        }
 0123        catch (DecoderFallbackException)
 0124        {
 125            // The input is not valid without fallbacks.
 0126            return;
 127        }
 128
 0129        TestWithSubstitution(input, encoding);
 0130    }
 131
 132    private static void TestWithConvert(ReadOnlySpan<byte> input, Encoding encoding)
 0133    {
 134        // Use a few boundary cases.
 0135        TestWithConvert(input, encoding, 1);
 0136        TestWithConvert(input, encoding, 2);
 0137        TestWithConvert(input, encoding, 3);
 0138        TestWithConvert(input, encoding, 4);
 0139        TestWithConvert(input, encoding, input.Length);
 140
 0141        if (input.Length >= 6)
 0142        {
 0143            TestWithConvert(input, encoding, input.Length - 1);
 144
 0145            if (input.Length >= 12)
 0146            {
 0147                TestWithConvert(input, encoding, input.Length / 2);
 0148            }
 0149        }
 0150    }
 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)
 0154    {
 0155        Decoder decoder = encoding.GetDecoder();
 156
 0157        int charCount = decoder.GetCharCount(input, flush: true);
 158
 0159        using PooledBoundedMemory<char> chars = PooledBoundedMemory<char>.Rent(charCount, PoisonPagePlacement.After);
 0160        using PooledBoundedMemory<char> chars2 = PooledBoundedMemory<char>.Rent(charCount, PoisonPagePlacement.After);
 161
 0162        decoder.Reset();
 0163        int charsUsedTotal = 0;
 0164        int i = 0;
 165
 0166        while (i < input.Length)
 0167        {
 0168            bool lastIteration = i + blockSize >= input.Length;
 0169            int bytesToRead = lastIteration ? input.Length - i : blockSize;
 170
 0171            decoder.Convert(
 0172                input.Slice(i, bytesToRead),
 0173                chars.Span.Slice(charsUsedTotal, charCount - charsUsedTotal),
 0174                flush: lastIteration,
 0175                out int bytesUsed,
 0176                out int charsUsed,
 0177                out bool _);
 178
 0179            i += bytesUsed;
 0180            charsUsedTotal += charsUsed;
 0181        }
 182
 0183        Assert.Equal(charsUsedTotal, charCount);
 0184        decoder.Reset();
 0185        decoder.GetChars(input, chars2.Span, flush: true);
 0186        Assert.SequenceEqual<char>(chars.Span, chars2.Span);
 0187    }
 188}