< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 138
Coverable lines: 138
Total lines: 411
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 58
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
Reset()0%220%
GetCharCount(...)100%110%
GetCharCount(...)0%220%
GetCharCount(...)100%110%
GetChars(...)100%110%
GetChars(...)0%660%
GetChars(...)100%110%
Convert(...)0%440%
Convert(...)0%880%
ClearMustFlush()100%110%
GetLeftoverData()100%110%
SetLeftoverData(...)100%110%
ClearLeftoverData()100%110%
DrainLeftoverDataForGetCharCount(...)0%11110%
DrainLeftoverDataForGetChars(...)0%17170%
ConcatInto(...)0%880%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Text/DecoderNLS.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;
 7using System.Runtime.InteropServices;
 8
 9namespace System.Text
 10{
 11    // A Decoder is used to decode a sequence of blocks of bytes into a
 12    // sequence of blocks of characters. Following instantiation of a decoder,
 13    // sequential blocks of bytes are converted into blocks of characters through
 14    // calls to the GetChars method. The decoder maintains state between the
 15    // conversions, allowing it to correctly decode byte sequences that span
 16    // adjacent blocks.
 17    //
 18    // Instances of specific implementations of the Decoder abstract base
 19    // class are typically obtained through calls to the GetDecoder method
 20    // of Encoding objects.
 21
 22    internal class DecoderNLS : Decoder
 23    {
 24        // Remember our encoding
 25        private readonly Encoding _encoding;
 26        private bool _mustFlush;
 27        internal bool _throwOnOverflow;
 28        internal int _bytesUsed;
 29        private int _leftoverBytes; // leftover data from a previous invocation of GetChars (up to 4 bytes)
 30        private int _leftoverByteCount; // number of bytes of actual data in _leftoverBytes
 31
 032        internal DecoderNLS(Encoding encoding)
 33        {
 034            _encoding = encoding;
 035            _fallback = this._encoding.DecoderFallback;
 036            this.Reset();
 037        }
 38
 39        public override void Reset()
 40        {
 041            ClearLeftoverData();
 042            _fallbackBuffer?.Reset();
 043        }
 44
 45        public override int GetCharCount(byte[] bytes, int index, int count)
 46        {
 047            return GetCharCount(bytes, index, count, false);
 48        }
 49
 50        public override unsafe int GetCharCount(byte[] bytes, int index, int count, bool flush)
 51        {
 052            ArgumentNullException.ThrowIfNull(bytes);
 53
 054            ArgumentOutOfRangeException.ThrowIfNegative(index);
 055            ArgumentOutOfRangeException.ThrowIfNegative(count);
 56
 057            if (bytes.Length - index < count)
 058                throw new ArgumentOutOfRangeException(nameof(bytes),
 059                    SR.ArgumentOutOfRange_IndexCountBuffer);
 60
 61            // Just call pointer version
 062            fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes))
 063                return GetCharCount(pBytes + index, count, flush);
 64        }
 65
 66        public override unsafe int GetCharCount(byte* bytes, int count, bool flush)
 67        {
 068            ArgumentNullException.ThrowIfNull(bytes);
 69
 070            ArgumentOutOfRangeException.ThrowIfNegative(count);
 71
 72            // Remember the flush
 073            _mustFlush = flush;
 074            _throwOnOverflow = true;
 75
 76            // By default just call the encoding version, no flush by default
 077            Debug.Assert(_encoding is not null);
 078            return _encoding.GetCharCount(bytes, count, this);
 79        }
 80
 81        public override int GetChars(byte[] bytes, int byteIndex, int byteCount,
 82                                             char[] chars, int charIndex)
 83        {
 084            return GetChars(bytes, byteIndex, byteCount, chars, charIndex, false);
 85        }
 86
 87        public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
 88                                            char[] chars, int charIndex, bool flush)
 89        {
 090            ArgumentNullException.ThrowIfNull(bytes);
 091            ArgumentNullException.ThrowIfNull(chars);
 92
 093            ArgumentOutOfRangeException.ThrowIfNegative(byteIndex);
 094            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 95
 096            if (bytes.Length - byteIndex < byteCount)
 097                throw new ArgumentOutOfRangeException(nameof(bytes),
 098                    SR.ArgumentOutOfRange_IndexCountBuffer);
 99
 0100            if (charIndex < 0 || charIndex > chars.Length)
 0101                throw new ArgumentOutOfRangeException(nameof(charIndex),
 0102                    SR.ArgumentOutOfRange_IndexMustBeLessOrEqual);
 103
 0104            int charCount = chars.Length - charIndex;
 105
 106            // Just call pointer version
 0107            fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes))
 0108            fixed (char* pChars = &MemoryMarshal.GetArrayDataReference(chars))
 109            {
 110                // Remember that charCount is # to decode, not size of array
 0111                return GetChars(pBytes + byteIndex, byteCount,
 0112                                pChars + charIndex, charCount, flush);
 113            }
 114        }
 115
 116        public override unsafe int GetChars(byte* bytes, int byteCount,
 117                                            char* chars, int charCount, bool flush)
 118        {
 0119            ArgumentNullException.ThrowIfNull(bytes);
 0120            ArgumentNullException.ThrowIfNull(chars);
 121
 0122            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 0123            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 124
 125            // Remember our flush
 0126            _mustFlush = flush;
 0127            _throwOnOverflow = true;
 128
 129            // By default just call the encodings version
 0130            Debug.Assert(_encoding is not null);
 0131            return _encoding.GetChars(bytes, byteCount, chars, charCount, this);
 132        }
 133
 134        // This method is used when the output buffer might not be big enough.
 135        // Just call the pointer version.  (This gets chars)
 136        public override unsafe void Convert(byte[] bytes, int byteIndex, int byteCount,
 137                                            char[] chars, int charIndex, int charCount, bool flush,
 138                                            out int bytesUsed, out int charsUsed, out bool completed)
 139        {
 0140            ArgumentNullException.ThrowIfNull(bytes);
 0141            ArgumentNullException.ThrowIfNull(chars);
 142
 0143            ArgumentOutOfRangeException.ThrowIfNegative(byteIndex);
 0144            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 145
 0146            ArgumentOutOfRangeException.ThrowIfNegative(charIndex);
 0147            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 148
 0149            if (bytes.Length - byteIndex < byteCount)
 0150                throw new ArgumentOutOfRangeException(nameof(bytes),
 0151                      SR.ArgumentOutOfRange_IndexCountBuffer);
 152
 0153            if (chars.Length - charIndex < charCount)
 0154                throw new ArgumentOutOfRangeException(nameof(chars),
 0155                      SR.ArgumentOutOfRange_IndexCountBuffer);
 156
 157            // Just call the pointer version (public overrides can't do this)
 0158            fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes))
 0159            fixed (char* pChars = &MemoryMarshal.GetArrayDataReference(chars))
 160            {
 0161                Convert(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, flush,
 0162                    out bytesUsed, out charsUsed, out completed);
 163            }
 0164        }
 165
 166        // This is the version that used pointers.  We call the base encoding worker function
 167        // after setting our appropriate internal variables.  This is getting chars
 168        public override unsafe void Convert(byte* bytes, int byteCount,
 169                                              char* chars, int charCount, bool flush,
 170                                              out int bytesUsed, out int charsUsed, out bool completed)
 171        {
 0172            ArgumentNullException.ThrowIfNull(bytes);
 0173            ArgumentNullException.ThrowIfNull(chars);
 174
 0175            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 0176            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 177
 178            // We don't want to throw
 0179            _mustFlush = flush;
 0180            _throwOnOverflow = false;
 0181            _bytesUsed = 0;
 182
 183            // Do conversion
 0184            Debug.Assert(_encoding is not null);
 0185            charsUsed = _encoding.GetChars(bytes, byteCount, chars, charCount, this);
 0186            bytesUsed = _bytesUsed;
 187
 188            // See comment in EncoderNLS.Convert for the details of the logic below.
 189
 0190            completed = (bytesUsed == byteCount)
 0191                && (!flush || !this.HasState)
 0192                && (_fallbackBuffer is null || _fallbackBuffer.Remaining == 0);
 0193        }
 194
 0195        public bool MustFlush => _mustFlush;
 196
 197        // Anything left in our decoder?
 0198        internal virtual bool HasState => _leftoverByteCount != 0;
 199
 200        // Allow encoding to clear our must flush instead of throwing (in ThrowCharsOverflow)
 201        internal void ClearMustFlush()
 202        {
 0203            _mustFlush = false;
 0204        }
 205
 206        internal ReadOnlySpan<byte> GetLeftoverData() =>
 0207            MemoryMarshal.AsBytes(new ReadOnlySpan<int>(in _leftoverBytes)).Slice(0, _leftoverByteCount);
 208
 209        internal void SetLeftoverData(ReadOnlySpan<byte> bytes)
 210        {
 0211            bytes.CopyTo(MemoryMarshal.AsBytes(new Span<int>(ref _leftoverBytes)));
 0212            _leftoverByteCount = bytes.Length;
 0213        }
 214
 0215        internal bool HasLeftoverData => _leftoverByteCount != 0;
 216
 217        internal void ClearLeftoverData()
 218        {
 0219            _leftoverByteCount = 0;
 0220        }
 221
 222        internal int DrainLeftoverDataForGetCharCount(ReadOnlySpan<byte> bytes, out int bytesConsumed)
 223        {
 224            // Quick check: we _should not_ have leftover fallback data from a previous invocation,
 225            // as we'd end up consuming any such data and would corrupt whatever Convert call happens
 226            // to be in progress. Unlike EncoderNLS, this is simply a Debug.Assert. No exception is thrown.
 227
 0228            Debug.Assert(_fallbackBuffer is null || _fallbackBuffer.Remaining == 0, "Should have no data remaining in th
 0229            Debug.Assert(HasLeftoverData, "Caller shouldn't invoke this routine unless there's leftover data in the deco
 230
 231            // Copy the existing leftover data plus as many bytes as possible of the new incoming data
 232            // into a temporary concated buffer, then get its char count by decoding it.
 233
 0234            Span<byte> combinedBuffer = [0, 0, 0, 0];
 0235            combinedBuffer = combinedBuffer.Slice(0, ConcatInto(GetLeftoverData(), bytes, combinedBuffer));
 0236            int charCount = 0;
 237
 0238            Debug.Assert(_encoding is not null);
 0239            switch (_encoding.DecodeFirstRune(combinedBuffer, out Rune value, out int combinedBufferBytesConsumed))
 240            {
 241                case OperationStatus.Done:
 0242                    charCount = value.Utf16SequenceLength;
 0243                    goto Finish; // successfully transcoded bytes -> chars
 244
 245                case OperationStatus.NeedMoreData:
 0246                    if (MustFlush)
 247                    {
 0248                        goto case OperationStatus.InvalidData; // treat as equivalent to bad data
 249                    }
 250                    else
 251                    {
 252                        goto Finish; // consumed some bytes, output 0 chars
 253                    }
 254
 255                case OperationStatus.InvalidData:
 256                    break;
 257
 258                default:
 0259                    Debug.Fail("Unexpected OperationStatus return value.");
 260                    break;
 261            }
 262
 263            // Couldn't decode the buffer. Fallback the buffer instead. See comment in DrainLeftoverDataForGetChars
 264            // for more information on why a negative index is provided.
 265
 0266            if (FallbackBuffer.Fallback(combinedBuffer.Slice(0, combinedBufferBytesConsumed).ToArray(), index: -_leftove
 267            {
 0268                charCount = _fallbackBuffer!.DrainRemainingDataForGetCharCount();
 0269                Debug.Assert(charCount >= 0, "Fallback buffer shouldn't have returned a negative char count.");
 270            }
 271
 272        Finish:
 273
 0274            bytesConsumed = combinedBufferBytesConsumed - _leftoverByteCount; // amount of 'bytes' buffer consumed just 
 0275            return charCount;
 276        }
 277
 278        internal int DrainLeftoverDataForGetChars(ReadOnlySpan<byte> bytes, Span<char> chars, out int bytesConsumed)
 279        {
 280            // Quick check: we _should not_ have leftover fallback data from a previous invocation,
 281            // as we'd end up consuming any such data and would corrupt whatever Convert call happens
 282            // to be in progress. Unlike EncoderNLS, this is simply a Debug.Assert. No exception is thrown.
 283
 0284            Debug.Assert(_fallbackBuffer is null || _fallbackBuffer.Remaining == 0, "Should have no data remaining in th
 0285            Debug.Assert(HasLeftoverData, "Caller shouldn't invoke this routine unless there's leftover data in the deco
 286
 287            // Copy the existing leftover data plus as many bytes as possible of the new incoming data
 288            // into a temporary concated buffer, then transcode it from bytes to chars.
 289
 0290            Span<byte> combinedBuffer = [0, 0, 0, 0];
 0291            combinedBuffer = combinedBuffer.Slice(0, ConcatInto(GetLeftoverData(), bytes, combinedBuffer));
 0292            int charsWritten = 0;
 293
 0294            bool persistNewCombinedBuffer = false;
 295
 0296            Debug.Assert(_encoding is not null);
 0297            switch (_encoding.DecodeFirstRune(combinedBuffer, out Rune value, out int combinedBufferBytesConsumed))
 298            {
 299                case OperationStatus.Done:
 0300                    if (value.TryEncodeToUtf16(chars, out charsWritten))
 301                    {
 0302                        goto Finish; // successfully transcoded bytes -> chars
 303                    }
 304                    else
 305                    {
 306                        goto DestinationTooSmall;
 307                    }
 308
 309                case OperationStatus.NeedMoreData:
 0310                    if (MustFlush)
 311                    {
 312                        goto case OperationStatus.InvalidData; // treat as equivalent to bad data
 313                    }
 314                    else
 315                    {
 0316                        persistNewCombinedBuffer = true;
 0317                        goto Finish; // successfully consumed some bytes, output no chars
 318                    }
 319
 320                case OperationStatus.InvalidData:
 321                    break;
 322
 323                default:
 0324                    Debug.Fail("Unexpected OperationStatus return value.");
 325                    break;
 326            }
 327
 328            // Couldn't decode the buffer. Fallback the buffer instead. The fallback mechanism relies
 329            // on a negative index to convey "the start of the invalid sequence was some number of
 330            // bytes back before the current buffer." Since we know the invalid sequence must have
 331            // started at the beginning of our leftover byte buffer, we can signal to our caller that
 332            // they must backtrack that many bytes to find the real start of the invalid sequence.
 333
 0334            if (FallbackBuffer.Fallback(combinedBuffer.Slice(0, combinedBufferBytesConsumed).ToArray(), index: -_leftove
 0335                && !_fallbackBuffer!.TryDrainRemainingDataForGetChars(chars, out charsWritten))
 336            {
 337                goto DestinationTooSmall;
 338            }
 339
 340        Finish:
 341
 342            // Report back the number of bytes (from the new incoming span) we consumed just now.
 343            // This calculation is simple: it's the difference between the original leftover byte
 344            // count and the number of bytes from the combined buffer we needed to decode the first
 345            // scalar value. We need to report this before the call to SetLeftoverData /
 346            // ClearLeftoverData because those methods will overwrite the _leftoverByteCount field.
 347
 0348            bytesConsumed = combinedBufferBytesConsumed - _leftoverByteCount;
 349
 0350            if (persistNewCombinedBuffer)
 351            {
 0352                Debug.Assert(combinedBufferBytesConsumed == combinedBuffer.Length, "We should be asked to persist the en
 0353                SetLeftoverData(combinedBuffer); // the buffer still only contains partial data; a future call to Conver
 354            }
 355            else
 356            {
 0357                ClearLeftoverData(); // the buffer contains no partial data; we'll go down the normal paths
 358            }
 359
 0360            return charsWritten;
 361
 362        DestinationTooSmall:
 363
 364            // If we got to this point, we're trying to write chars to the output buffer, but we're unable to do
 365            // so. Unlike EncoderNLS, this type does not allow partial writes to the output buffer. Since we know
 366            // draining leftover data is the first operation performed by any DecoderNLS API, there was no
 367            // opportunity for any code before us to make forward progress, so we must fail immediately.
 368
 0369            _encoding.ThrowCharsOverflow(this, nothingDecoded: true);
 0370            throw null!; // will never reach this point
 371        }
 372
 373        /// <summary>
 374        /// Given a byte buffer <paramref name="dest"/>, concatenates as much of <paramref name="srcLeft"/> followed
 375        /// by <paramref name="srcRight"/> into it as will fit, then returns the total number of bytes copied.
 376        /// </summary>
 377        private static int ConcatInto(ReadOnlySpan<byte> srcLeft, ReadOnlySpan<byte> srcRight, Span<byte> dest)
 378        {
 0379            int total = 0;
 380
 0381            for (int i = 0; i < srcLeft.Length; i++)
 382            {
 0383                if ((uint)total >= (uint)dest.Length)
 384                {
 385                    goto Finish;
 386                }
 387                else
 388                {
 0389                    dest[total++] = srcLeft[i];
 390                }
 391            }
 392
 0393            for (int i = 0; i < srcRight.Length; i++)
 394            {
 0395                if ((uint)total >= (uint)dest.Length)
 396                {
 397                    goto Finish;
 398                }
 399                else
 400                {
 0401                    dest[total++] = srcRight[i];
 402                }
 403            }
 404
 405        Finish:
 406
 0407            return total;
 408        }
 409    }
 410}
 411