< Summary

Line coverage
12%
Covered lines: 12
Uncovered lines: 85
Coverable lines: 97
Total lines: 360
Line coverage: 12.3%
Branch coverage
1%
Covered branches: 1
Total branches: 55
Branch coverage: 1.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Text/EncoderFallback.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.Diagnostics.CodeAnalysis;
 7
 8namespace System.Text
 9{
 10    public abstract class EncoderFallback
 11    {
 12        // Default fallback, uses no best fit & "?"
 13        public static EncoderFallback ReplacementFallback => EncoderReplacementFallback.s_default;
 14
 15        public static EncoderFallback ExceptionFallback => EncoderExceptionFallback.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 MyCustomEncoderFallbackBuffer(this);
 22
 23        public abstract EncoderFallbackBuffer 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 EncoderFallbackBuffer
 32    {
 33        // Most implementations will probably need an implementation-specific constructor
 34
 35        // Public 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 is incorrect
 37
 38        public abstract bool Fallback(char charUnknown, int index);
 39
 40        public abstract bool Fallback(char charUnknownHigh, char charUnknownLow, int index);
 41
 42        // Get next character
 43
 44        public abstract char GetNextChar();
 45
 46        // Back up a character
 47
 48        public abstract bool MovePrevious();
 49
 50        // How many chars left in this fallback?
 51
 52        public abstract int Remaining { get; }
 53
 54        // Not sure if this should be public or not.
 55        // Clear the buffer
 56
 57        public virtual void Reset()
 58        {
 059            while (GetNextChar() != (char)0) ;
 060        }
 61
 62        // Internal items to help us figure out what we're doing as far as error messages, etc.
 63        // These help us with our performance and messages internally
 64        internal unsafe char* charStart;
 65        internal unsafe char* charEnd;
 66        internal EncoderNLS? encoder; // TODO: MAKE ME PRIVATE
 67        internal bool setEncoder;
 68        internal bool bUsedEncoder;
 69        internal bool bFallingBack;
 70        internal int iRecursionCount;
 71        private const int iMaxRecursion = 250;
 72        private Encoding? encoding;
 73        private int originalCharCount;
 74
 75        // Internal Reset
 76        // For example, what if someone fails a conversion and wants to reset one of our fallback buffers?
 77        internal unsafe void InternalReset()
 78        {
 079            charStart = null;
 080            bFallingBack = false;
 081            iRecursionCount = 0;
 082            Reset();
 083        }
 84
 85        // Set the above values
 86        // This can't be part of the constructor because EncoderFallbacks would have to know how to implement these.
 87        internal unsafe void InternalInitialize(char* charStart, char* charEnd, EncoderNLS? encoder, bool setEncoder)
 88        {
 684489            this.charStart = charStart;
 684490            this.charEnd = charEnd;
 684491            this.encoder = encoder;
 684492            this.setEncoder = setEncoder;
 684493            this.bUsedEncoder = false;
 684494            this.bFallingBack = false;
 684495            this.iRecursionCount = 0;
 684496        }
 97
 98        internal static EncoderFallbackBuffer CreateAndInitialize(Encoding encoding, EncoderNLS? encoder, int originalCh
 99        {
 100            // The original char count is only used for keeping track of what 'index' value needs
 101            // to be passed to the abstract Fallback method. The index value is calculated by subtracting
 102            // 'chars.Length' (where chars is expected to be the entire remaining input buffer)
 103            // from the 'originalCharCount' value specified here.
 104
 0105            EncoderFallbackBuffer fallbackBuffer = (encoder is null) ? encoding.EncoderFallback.CreateFallbackBuffer() :
 106
 0107            fallbackBuffer.encoding = encoding;
 0108            fallbackBuffer.encoder = encoder;
 0109            fallbackBuffer.originalCharCount = originalCharCount;
 110
 0111            return fallbackBuffer;
 112        }
 113
 114        internal char InternalGetNextChar()
 115        {
 364088116            char ch = GetNextChar();
 364088117            bFallingBack = (ch != 0);
 728176118            if (ch == 0) iRecursionCount = 0;
 364088119            return ch;
 120        }
 121
 122        private bool InternalFallback(ReadOnlySpan<char> chars, out int charsConsumed)
 123        {
 0124            Debug.Assert(!chars.IsEmpty, "Caller shouldn't invoke this if there's no data to fall back.");
 125
 126            // First, try falling back a single BMP character or a standalone low surrogate.
 127            // If the first char is a high surrogate, we'll try to combine it with the next
 128            // char in the input sequence.
 129
 0130            char firstChar = chars[0];
 0131            char secondChar = default;
 132
 0133            if (!chars.IsEmpty)
 134            {
 0135                firstChar = chars[0];
 136
 0137                if (chars.Length > 1)
 138                {
 0139                    secondChar = chars[1];
 140                }
 141            }
 142
 143            // Ask the subclassed type to initiate fallback logic.
 144
 0145            int index = originalCharCount - chars.Length;
 146
 0147            if (!char.IsSurrogatePair(firstChar, secondChar))
 148            {
 149                // This code path is also used when 'firstChar' is a standalone surrogate or
 150                // if it's a high surrogate at the end of the input buffer.
 151
 0152                charsConsumed = 1;
 0153                return Fallback(firstChar, index);
 154            }
 155            else
 156            {
 0157                charsConsumed = 2;
 0158                return Fallback(firstChar, secondChar, index);
 159            }
 160        }
 161
 162        internal int InternalFallbackGetByteCount(ReadOnlySpan<char> chars, out int charsConsumed)
 163        {
 0164            int bytesWritten = 0;
 165
 0166            if (InternalFallback(chars, out charsConsumed))
 167            {
 168                // There's data in the fallback buffer - pull it out now.
 169
 0170                bytesWritten = DrainRemainingDataForGetByteCount();
 171            }
 172
 0173            return bytesWritten;
 174        }
 175
 176        internal bool TryInternalFallbackGetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, out int charsConsumed, out
 177        {
 0178            if (InternalFallback(chars, out charsConsumed))
 179            {
 180                // There's data in the fallback buffer - pull it out now.
 181
 0182                return TryDrainRemainingDataForGetBytes(bytes, out bytesWritten);
 183            }
 184            else
 185            {
 186                // There's no data in the fallback buffer.
 187
 0188                bytesWritten = 0;
 0189                return true; // true = didn't run out of space in destination buffer
 190            }
 191        }
 192
 193        internal bool TryDrainRemainingDataForGetBytes(Span<byte> bytes, out int bytesWritten)
 194        {
 0195            int originalBytesLength = bytes.Length;
 196
 0197            Debug.Assert(encoding != null);
 198            Rune thisRune;
 0199            while ((thisRune = GetNextRune()).Value != 0)
 200            {
 0201                switch (encoding.EncodeRune(thisRune, bytes, out int bytesWrittenJustNow))
 202                {
 203                    case OperationStatus.Done:
 204
 0205                        bytes = bytes.Slice(bytesWrittenJustNow);
 0206                        continue;
 207
 208                    case OperationStatus.DestinationTooSmall:
 209
 210                        // Since we're not consuming the Rune we just read, back up as many chars as necessary
 211                        // to undo the read we just performed, then report to our caller that we ran out of space.
 212
 0213                        for (int i = 0; i < thisRune.Utf16SequenceLength; i++)
 214                        {
 0215                            MovePrevious();
 216                        }
 217
 0218                        bytesWritten = originalBytesLength - bytes.Length;
 0219                        return false; // ran out of destination buffer
 220
 221                    case OperationStatus.InvalidData:
 222
 223                        // We can't fallback the fallback. We can't make forward progress, so report to our caller
 224                        // that something went terribly wrong. The error message contains the fallback char that
 225                        // couldn't be converted. (Ideally we'd provide the first char that originally triggered
 226                        // the fallback, but it's complicated to keep this state around, and a fallback producing
 227                        // invalid data should be a very rare occurrence.)
 228
 0229                        ThrowLastCharRecursive(thisRune.Value);
 230                        break; // will never be hit; call above throws
 231
 232                    default:
 233
 0234                        Debug.Fail("Unexpected return value.");
 235                        break;
 236                }
 237            }
 238
 0239            bytesWritten = originalBytesLength - bytes.Length;
 0240            return true; // finished successfully
 241        }
 242
 243        internal int DrainRemainingDataForGetByteCount()
 244        {
 0245            int totalByteCount = 0;
 246
 0247            Debug.Assert(encoding != null);
 248            Rune thisRune;
 0249            while ((thisRune = GetNextRune()).Value != 0)
 250            {
 0251                if (!encoding.TryGetByteCount(thisRune, out int byteCountThisIteration))
 252                {
 253                    // We can't fallback the fallback. We can't make forward progress, so report to our caller
 254                    // that something went terribly wrong. The error message contains the fallback char that
 255                    // couldn't be converted. (Ideally we'd provide the first char that originally triggered
 256                    // the fallback, but it's complicated to keep this state around, and a fallback producing
 257                    // invalid data should be a very rare occurrence.)
 258
 0259                    ThrowLastCharRecursive(thisRune.Value);
 260                }
 261
 0262                Debug.Assert(byteCountThisIteration >= 0, "Encoding shouldn't have returned a negative byte count.");
 263
 264                // We need to check for overflow while tallying the fallback byte count.
 265
 0266                totalByteCount += byteCountThisIteration;
 0267                if (totalByteCount < 0)
 268                {
 0269                    InternalReset();
 0270                    Encoding.ThrowConversionOverflow();
 271                }
 272            }
 273
 0274            return totalByteCount;
 275        }
 276
 277        private Rune GetNextRune()
 278        {
 0279            char firstChar = GetNextChar();
 0280            if (Rune.TryCreate(firstChar, out Rune value) || Rune.TryCreate(firstChar, GetNextChar(), out value))
 281            {
 0282                return value;
 283            }
 284
 0285            throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 286        }
 287
 288        // Fallback the current character using the remaining buffer and encoder if necessary
 289        // This can only be called by our encodings (other have to use the public fallback methods), so
 290        // we can use our EncoderNLS here too.
 291        // setEncoder is true if we're calling from a GetBytes method, false if we're calling from a GetByteCount
 292        //
 293        // Note that this could also change the contents of this.encoder, which is the same
 294        // object that the caller is using, so the caller could mess up the encoder for us
 295        // if they aren't careful.
 296        internal unsafe bool InternalFallback(char ch, ref char* chars)
 297        {
 298            // Shouldn't have null charStart
 0299            Debug.Assert(charStart != null,
 0300                "[EncoderFallback.InternalFallbackBuffer]Fallback buffer is not initialized");
 301
 302            // Get our index, remember chars was preincremented to point at next char, so have to -1
 0303            int index = (int)(chars - charStart) - 1;
 304
 305            // See if it was a high surrogate
 0306            if (char.IsHighSurrogate(ch))
 307            {
 308                // See if there's a low surrogate to go with it
 0309                if (chars >= this.charEnd)
 310                {
 311                    // Nothing left in input buffer
 312                    // No input, return 0 if mustflush is false
 0313                    if (this.encoder != null && !this.encoder.MustFlush)
 314                    {
 315                        // Done, nothing to fallback
 0316                        if (this.setEncoder)
 317                        {
 0318                            bUsedEncoder = true;
 0319                            this.encoder._charLeftOver = ch;
 320                        }
 0321                        bFallingBack = false;
 0322                        return false;
 323                    }
 324                }
 325                else
 326                {
 327                    // Might have a low surrogate
 0328                    char cNext = *chars;
 0329                    if (char.IsLowSurrogate(cNext))
 330                    {
 331                        // If already falling back then fail
 0332                        if (bFallingBack && iRecursionCount++ > iMaxRecursion)
 0333                            ThrowLastCharRecursive(char.ConvertToUtf32(ch, cNext));
 334
 335                        // Next is a surrogate, add it as surrogate pair, and increment chars
 0336                        chars++;
 0337                        bFallingBack = Fallback(ch, cNext, index);
 0338                        return bFallingBack;
 339                    }
 340                    // Next isn't a low surrogate, just fallback the high surrogate
 341                }
 342            }
 343
 344            // If already falling back then fail
 0345            if (bFallingBack && iRecursionCount++ > iMaxRecursion)
 0346                ThrowLastCharRecursive((int)ch);
 347
 348            // Fall back our char
 0349            bFallingBack = Fallback(ch, index);
 350
 0351            return bFallingBack;
 352        }
 353
 354        [DoesNotReturn]
 355        internal static void ThrowLastCharRecursive(int charRecursive) =>
 356            // Throw it, using our complete character
 0357            throw new ArgumentException(SR.Format(SR.Argument_RecursiveFallback, charRecursive), "chars");
 358    }
 359}
 360