< Summary

Line coverage
45%
Covered lines: 41
Uncovered lines: 49
Coverable lines: 90
Total lines: 317
Line coverage: 45.5%
Branch coverage
31%
Covered branches: 19
Total branches: 60
Branch coverage: 31.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
Reset()100%22100%
InternalReset()100%110%
InternalInitialize(...)100%11100%
CreateAndInitialize(...)50%22100%
InternalFallback(...)43.75%161655%
InternalFallback(...)0%141411.11%
InternalFallbackGetCharCount(...)50%22100%
TryInternalFallbackGetChars(...)0%220%
GetNextRune()50%4475%
DrainRemainingDataForGetCharCount()75%4471.42%
TryDrainRemainingDataForGetChars(...)75%4470%
ThrowLastBytesRecursive(...)0%10100%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Text/DecoderFallback.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.Diagnostics;
 5using System.Diagnostics.CodeAnalysis;
 6using System.Globalization;
 7
 8namespace System.Text
 9{
 10    public abstract class DecoderFallback
 11    {
 12        // Default fallback, uses no best fit & "?"
 13        public static DecoderFallback ReplacementFallback => DecoderReplacementFallback.s_default;
 14
 15        public static DecoderFallback ExceptionFallback => DecoderExceptionFallback.s_default;
 16
 17        // Fallback
 18        //
 19        // Return the appropriate unicode string alternative to the character that need to fall back.
 20        // Most implementations will be:
 21        //      return new MyCustomDecoderFallbackBuffer(this);
 22
 23        public abstract DecoderFallbackBuffer CreateFallbackBuffer();
 24
 25        // Maximum number of characters that this instance of this fallback could return
 26
 27        public abstract int MaxCharCount { get; }
 28    }
 29
 30
 31    public abstract class DecoderFallbackBuffer
 32    {
 33        // Most implementations will probably need an implementation-specific constructor
 34
 35        // internal methods that cannot be overridden that let us do our fallback thing
 36        // These wrap the internal methods so that we can check for people doing stuff that's incorrect
 37
 38        public abstract bool Fallback(byte[] bytesUnknown, int index);
 39
 40        // Get next character
 41
 42        public abstract char GetNextChar();
 43
 44        // Back up a character
 45
 46        public abstract bool MovePrevious();
 47
 48        // How many chars left in this fallback?
 49
 50        public abstract int Remaining { get; }
 51
 52        // Clear the buffer
 53
 54        public virtual void Reset()
 55        {
 7656            while (GetNextChar() != (char)0) ;
 7657        }
 58
 59        // Internal items to help us figure out what we're doing as far as error messages, etc.
 60        // These help us with our performance and messages internally
 61        internal unsafe byte* byteStart;
 62        internal unsafe char* charEnd;
 63
 64        internal Encoding? _encoding;
 65        internal DecoderNLS? _decoder;
 66        private int _originalByteCount;
 67
 68        // Internal Reset
 69        internal unsafe void InternalReset()
 70        {
 071            byteStart = null;
 072            Reset();
 073        }
 74
 75        // Set the above values
 76        // This can't be part of the constructor because DecoderFallbacks would have to know how to implement these.
 77        internal unsafe void InternalInitialize(byte* byteStart, char* charEnd)
 78        {
 80251379            this.byteStart = byteStart;
 80251380            this.charEnd = charEnd;
 80251381        }
 82
 83        internal static DecoderFallbackBuffer CreateAndInitialize(Encoding encoding, DecoderNLS? decoder, int originalBy
 84        {
 85            // The original byte count is only used for keeping track of what 'index' value needs
 86            // to be passed to the abstract Fallback method. The index value is calculated by subtracting
 87            // 'bytes.Length' (where bytes is expected to be the entire remaining input buffer)
 88            // from the 'originalByteCount' value specified here.
 89
 20224790            DecoderFallbackBuffer fallbackBuffer = (decoder is null) ? encoding.DecoderFallback.CreateFallbackBuffer() :
 91
 20224792            fallbackBuffer._encoding = encoding;
 20224793            fallbackBuffer._decoder = decoder;
 20224794            fallbackBuffer._originalByteCount = originalByteCount;
 95
 20224796            return fallbackBuffer;
 97        }
 98
 99        // Fallback the current byte by sticking it into the remaining char buffer.
 100        // This can only be called by our encodings (other have to use the public fallback methods), so
 101        // we can use our DecoderNLS here too (except we don't).
 102        // Returns true if we are successful, false if we can't fallback the character (no buffer space)
 103        // So caller needs to throw buffer space if return false.
 104        // Right now this has both bytes and bytes[], since we might have extra bytes, hence the
 105        // array, and we might need the index, hence the byte*
 106        // Don't touch ref chars unless we succeed
 107        internal unsafe bool InternalFallback(byte[] bytes, byte* pBytes, ref char* chars)
 108        {
 1364032109            Debug.Assert(byteStart != null, "[DecoderFallback.InternalFallback]Used InternalFallback without calling Int
 110
 111            // See if there's a fallback character and we have an output buffer then copy our string.
 1364032112            if (this.Fallback(bytes, (int)(pBytes - byteStart - bytes.Length)))
 113            {
 114                // Copy the chars to our output
 115                char ch;
 1363498116                char* charTemp = chars;
 1363498117                bool bHighSurrogate = false;
 2726996118                while ((ch = GetNextChar()) != 0)
 119                {
 120                    // Make sure no mixed up surrogates
 1363498121                    if (char.IsSurrogate(ch))
 122                    {
 0123                        if (char.IsHighSurrogate(ch))
 124                        {
 125                            // High Surrogate
 0126                            if (bHighSurrogate)
 0127                                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 0128                            bHighSurrogate = true;
 129                        }
 130                        else
 131                        {
 132                            // Low surrogate
 0133                            if (!bHighSurrogate)
 0134                                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 0135                            bHighSurrogate = false;
 136                        }
 137                    }
 138
 1363498139                    if (charTemp >= charEnd)
 140                    {
 141                        // No buffer space
 0142                        return false;
 143                    }
 144
 1363498145                    *(charTemp++) = ch;
 146                }
 147
 148                // Need to make sure that bHighSurrogate isn't true
 1363498149                if (bHighSurrogate)
 0150                    throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 151
 152                // Now we aren't going to be false, so its OK to update chars
 1363498153                chars = charTemp;
 154            }
 155
 1364032156            return true;
 157        }
 158
 159        // This version just counts the fallback and doesn't actually copy anything.
 160        internal virtual unsafe int InternalFallback(byte[] bytes, byte* pBytes)
 161        // Right now this has both bytes and bytes[], since we might have extra bytes, hence the
 162        // array, and we might need the index, hence the byte*
 163        {
 5654164            Debug.Assert(byteStart != null, "[DecoderFallback.InternalFallback]Used InternalFallback without calling Int
 165
 166            // See if there's a fallback character and we have an output buffer then copy our string.
 5654167            if (this.Fallback(bytes, (int)(pBytes - byteStart - bytes.Length)))
 168            {
 0169                int count = 0;
 170
 171                char ch;
 0172                bool bHighSurrogate = false;
 0173                while ((ch = GetNextChar()) != 0)
 174                {
 175                    // Make sure no mixed up surrogates
 0176                    if (char.IsSurrogate(ch))
 177                    {
 0178                        if (char.IsHighSurrogate(ch))
 179                        {
 180                            // High Surrogate
 0181                            if (bHighSurrogate)
 0182                                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 0183                            bHighSurrogate = true;
 184                        }
 185                        else
 186                        {
 187                            // Low surrogate
 0188                            if (!bHighSurrogate)
 0189                                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 0190                            bHighSurrogate = false;
 191                        }
 192                    }
 193
 0194                    count++;
 195                }
 196
 197                // Need to make sure that bHighSurrogate isn't true
 0198                if (bHighSurrogate)
 0199                    throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 200
 0201                return count;
 202            }
 203
 204            // If no fallback return 0
 0205            return 0;
 206        }
 207
 208        internal int InternalFallbackGetCharCount(ReadOnlySpan<byte> remainingBytes, int fallbackLength)
 209        {
 814925210            return (Fallback(remainingBytes.Slice(0, fallbackLength).ToArray(), index: _originalByteCount - remainingByt
 814925211                ? DrainRemainingDataForGetCharCount()
 814925212                : 0;
 213        }
 214
 215        internal bool TryInternalFallbackGetChars(ReadOnlySpan<byte> remainingBytes, int fallbackLength, Span<char> char
 216        {
 0217            if (Fallback(remainingBytes.Slice(0, fallbackLength).ToArray(), index: _originalByteCount - remainingBytes.L
 218            {
 0219                return TryDrainRemainingDataForGetChars(chars, out charsWritten);
 220            }
 221            else
 222            {
 223                // Return true because we weren't asked to write anything, so this is a "success" in the sense that
 224                // the output buffer was large enough to hold the desired 0 chars of output.
 225
 0226                charsWritten = 0;
 0227                return true;
 228            }
 229        }
 230
 231        private Rune GetNextRune()
 232        {
 233            // Call GetNextChar() and try treating it as a non-surrogate character.
 234            // If that fails, call GetNextChar() again and attempt to treat the two chars
 235            // as a surrogate pair. If that still fails, throw an exception since the fallback
 236            // mechanism is giving us a bad replacement character.
 237
 1865796238            char ch = GetNextChar();
 1865796239            if (!Rune.TryCreate(ch, out Rune rune) && !Rune.TryCreate(ch, GetNextChar(), out rune))
 240            {
 0241                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 242            }
 243
 1865796244            return rune;
 245        }
 246
 247        internal int DrainRemainingDataForGetCharCount()
 248        {
 811866249            int totalCharCount = 0;
 250
 251            Rune thisRune;
 1623732252            while ((thisRune = GetNextRune()).Value != 0)
 253            {
 254                // We need to check for overflow while tallying the fallback char count.
 255
 811866256                totalCharCount += thisRune.Utf16SequenceLength;
 811866257                if (totalCharCount < 0)
 258                {
 0259                    InternalReset();
 0260                    Encoding.ThrowConversionOverflow();
 261                }
 262            }
 263
 811866264            return totalCharCount;
 265        }
 266
 267        internal bool TryDrainRemainingDataForGetChars(Span<char> chars, out int charsWritten)
 268        {
 121032269            int originalCharCount = chars.Length;
 270
 271            Rune thisRune;
 242064272            while ((thisRune = GetNextRune()).Value != 0)
 273            {
 121032274                if (thisRune.TryEncodeToUtf16(chars, out int charsWrittenJustNow))
 275                {
 121032276                    chars = chars.Slice(charsWrittenJustNow);
 121032277                    continue;
 278                }
 279                else
 280                {
 0281                    InternalReset();
 0282                    charsWritten = default;
 0283                    return false;
 284                }
 285            }
 286
 121032287            charsWritten = originalCharCount - chars.Length;
 121032288            return true;
 289        }
 290
 291        // private helper methods
 292        [DoesNotReturn]
 293        internal static void ThrowLastBytesRecursive(byte[] bytesUnknown)
 294        {
 0295            bytesUnknown ??= [];
 296
 297            // Create a string representation of our bytes.
 0298            StringBuilder strBytes = new StringBuilder(bytesUnknown.Length * 3);
 299            int i;
 0300            for (i = 0; i < bytesUnknown.Length && i < 20; i++)
 301            {
 0302                if (strBytes.Length > 0)
 0303                    strBytes.Append(' ');
 0304                strBytes.Append($"\\x{bytesUnknown[i]:X2}");
 305            }
 306            // In case the string's really long
 0307            if (i == 20)
 0308                strBytes.Append(" ...");
 309
 310            // Throw it, using our complete bytes
 0311            throw new ArgumentException(
 0312                SR.Format(SR.Argument_RecursiveFallbackBytes,
 0313                    strBytes.ToString()), nameof(bytesUnknown));
 314        }
 315    }
 316}
 317