< Summary

Line coverage
100%
Covered lines: 2
Uncovered lines: 0
Coverable lines: 2
Total lines: 317
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

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 & "?"
 680813        public static DecoderFallback ReplacementFallback => DecoderReplacementFallback.s_default;
 14
 1020915        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        {
 56            while (GetNextChar() != (char)0) ;
 57        }
 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        {
 71            byteStart = null;
 72            Reset();
 73        }
 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        {
 79            this.byteStart = byteStart;
 80            this.charEnd = charEnd;
 81        }
 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
 90            DecoderFallbackBuffer fallbackBuffer = (decoder is null) ? encoding.DecoderFallback.CreateFallbackBuffer() :
 91
 92            fallbackBuffer._encoding = encoding;
 93            fallbackBuffer._decoder = decoder;
 94            fallbackBuffer._originalByteCount = originalByteCount;
 95
 96            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        {
 109            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.
 112            if (this.Fallback(bytes, (int)(pBytes - byteStart - bytes.Length)))
 113            {
 114                // Copy the chars to our output
 115                char ch;
 116                char* charTemp = chars;
 117                bool bHighSurrogate = false;
 118                while ((ch = GetNextChar()) != 0)
 119                {
 120                    // Make sure no mixed up surrogates
 121                    if (char.IsSurrogate(ch))
 122                    {
 123                        if (char.IsHighSurrogate(ch))
 124                        {
 125                            // High Surrogate
 126                            if (bHighSurrogate)
 127                                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 128                            bHighSurrogate = true;
 129                        }
 130                        else
 131                        {
 132                            // Low surrogate
 133                            if (!bHighSurrogate)
 134                                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 135                            bHighSurrogate = false;
 136                        }
 137                    }
 138
 139                    if (charTemp >= charEnd)
 140                    {
 141                        // No buffer space
 142                        return false;
 143                    }
 144
 145                    *(charTemp++) = ch;
 146                }
 147
 148                // Need to make sure that bHighSurrogate isn't true
 149                if (bHighSurrogate)
 150                    throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 151
 152                // Now we aren't going to be false, so its OK to update chars
 153                chars = charTemp;
 154            }
 155
 156            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        {
 164            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.
 167            if (this.Fallback(bytes, (int)(pBytes - byteStart - bytes.Length)))
 168            {
 169                int count = 0;
 170
 171                char ch;
 172                bool bHighSurrogate = false;
 173                while ((ch = GetNextChar()) != 0)
 174                {
 175                    // Make sure no mixed up surrogates
 176                    if (char.IsSurrogate(ch))
 177                    {
 178                        if (char.IsHighSurrogate(ch))
 179                        {
 180                            // High Surrogate
 181                            if (bHighSurrogate)
 182                                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 183                            bHighSurrogate = true;
 184                        }
 185                        else
 186                        {
 187                            // Low surrogate
 188                            if (!bHighSurrogate)
 189                                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 190                            bHighSurrogate = false;
 191                        }
 192                    }
 193
 194                    count++;
 195                }
 196
 197                // Need to make sure that bHighSurrogate isn't true
 198                if (bHighSurrogate)
 199                    throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 200
 201                return count;
 202            }
 203
 204            // If no fallback return 0
 205            return 0;
 206        }
 207
 208        internal int InternalFallbackGetCharCount(ReadOnlySpan<byte> remainingBytes, int fallbackLength)
 209        {
 210            return (Fallback(remainingBytes.Slice(0, fallbackLength).ToArray(), index: _originalByteCount - remainingByt
 211                ? DrainRemainingDataForGetCharCount()
 212                : 0;
 213        }
 214
 215        internal bool TryInternalFallbackGetChars(ReadOnlySpan<byte> remainingBytes, int fallbackLength, Span<char> char
 216        {
 217            if (Fallback(remainingBytes.Slice(0, fallbackLength).ToArray(), index: _originalByteCount - remainingBytes.L
 218            {
 219                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
 226                charsWritten = 0;
 227                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
 238            char ch = GetNextChar();
 239            if (!Rune.TryCreate(ch, out Rune rune) && !Rune.TryCreate(ch, GetNextChar(), out rune))
 240            {
 241                throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 242            }
 243
 244            return rune;
 245        }
 246
 247        internal int DrainRemainingDataForGetCharCount()
 248        {
 249            int totalCharCount = 0;
 250
 251            Rune thisRune;
 252            while ((thisRune = GetNextRune()).Value != 0)
 253            {
 254                // We need to check for overflow while tallying the fallback char count.
 255
 256                totalCharCount += thisRune.Utf16SequenceLength;
 257                if (totalCharCount < 0)
 258                {
 259                    InternalReset();
 260                    Encoding.ThrowConversionOverflow();
 261                }
 262            }
 263
 264            return totalCharCount;
 265        }
 266
 267        internal bool TryDrainRemainingDataForGetChars(Span<char> chars, out int charsWritten)
 268        {
 269            int originalCharCount = chars.Length;
 270
 271            Rune thisRune;
 272            while ((thisRune = GetNextRune()).Value != 0)
 273            {
 274                if (thisRune.TryEncodeToUtf16(chars, out int charsWrittenJustNow))
 275                {
 276                    chars = chars.Slice(charsWrittenJustNow);
 277                    continue;
 278                }
 279                else
 280                {
 281                    InternalReset();
 282                    charsWritten = default;
 283                    return false;
 284                }
 285            }
 286
 287            charsWritten = originalCharCount - chars.Length;
 288            return true;
 289        }
 290
 291        // private helper methods
 292        [DoesNotReturn]
 293        internal static void ThrowLastBytesRecursive(byte[] bytesUnknown)
 294        {
 295            bytesUnknown ??= [];
 296
 297            // Create a string representation of our bytes.
 298            StringBuilder strBytes = new StringBuilder(bytesUnknown.Length * 3);
 299            int i;
 300            for (i = 0; i < bytesUnknown.Length && i < 20; i++)
 301            {
 302                if (strBytes.Length > 0)
 303                    strBytes.Append(' ');
 304                strBytes.Append($"\\x{bytesUnknown[i]:X2}");
 305            }
 306            // In case the string's really long
 307            if (i == 20)
 308                strBytes.Append(" ...");
 309
 310            // Throw it, using our complete bytes
 311            throw new ArgumentException(
 312                SR.Format(SR.Argument_RecursiveFallbackBytes,
 313                    strBytes.ToString()), nameof(bytesUnknown));
 314        }
 315    }
 316}
 317