< Summary

Line coverage
12%
Covered lines: 17
Uncovered lines: 114
Coverable lines: 131
Total lines: 397
Line coverage: 12.9%
Branch coverage
5%
Covered branches: 3
Total branches: 57
Branch coverage: 5.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%
Reset()50%2266.66%
GetByteCount(...)0%220%
GetByteCount(...)100%110%
GetBytes(...)0%660%
GetBytes(...)100%11100%
Convert(...)0%440%
Convert(...)0%880%
ClearMustFlush()100%110%
DrainLeftoverDataForGetByteCount(...)0%14140%
TryDrainLeftoverDataForGetBytes(...)0%17170%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Text/EncoderNLS.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    // An Encoder is used to encode a sequence of blocks of characters into
 12    // a sequence of blocks of bytes. Following instantiation of an encoder,
 13    // sequential blocks of characters are converted into blocks of bytes through
 14    // calls to the GetBytes method. The encoder maintains state between the
 15    // conversions, allowing it to correctly encode character sequences that span
 16    // adjacent blocks.
 17    //
 18    // Instances of specific implementations of the Encoder abstract base
 19    // class are typically obtained through calls to the GetEncoder method
 20    // of Encoding objects.
 21    //
 22
 23    internal class EncoderNLS : Encoder
 24    {
 25        // Need a place for the last left over character, most of our encodings use this
 26        internal char _charLeftOver;
 27        private readonly Encoding _encoding;
 28        private bool _mustFlush;
 29        internal bool _throwOnOverflow;
 30        internal int _charsUsed;
 31
 2191532        internal EncoderNLS(Encoding encoding)
 33        {
 2191534            _encoding = encoding;
 2191535            _fallback = _encoding.EncoderFallback;
 2191536            this.Reset();
 2191537        }
 38
 39        public override void Reset()
 40        {
 1851241            _charLeftOver = (char)0;
 1851242            _fallbackBuffer?.Reset();
 043        }
 44
 45        public override unsafe int GetByteCount(char[] chars, int index, int count, bool flush)
 46        {
 047            ArgumentNullException.ThrowIfNull(chars);
 48
 049            ArgumentOutOfRangeException.ThrowIfNegative(index);
 050            ArgumentOutOfRangeException.ThrowIfNegative(count);
 51
 052            if (chars.Length - index < count)
 053                throw new ArgumentOutOfRangeException(nameof(chars),
 054                      SR.ArgumentOutOfRange_IndexCountBuffer);
 55
 56            // Just call the pointer version
 057            int result = -1;
 058            fixed (char* pChars = &MemoryMarshal.GetArrayDataReference(chars))
 59            {
 060                result = GetByteCount(pChars + index, count, flush);
 61            }
 062            return result;
 63        }
 64
 65        public override unsafe int GetByteCount(char* chars, int count, bool flush)
 66        {
 067            ArgumentNullException.ThrowIfNull(chars);
 68
 069            ArgumentOutOfRangeException.ThrowIfNegative(count);
 70
 071            _mustFlush = flush;
 072            _throwOnOverflow = true;
 073            Debug.Assert(_encoding is not null);
 074            return _encoding.GetByteCount(chars, count, this);
 75        }
 76
 77        public override unsafe int GetBytes(char[] chars, int charIndex, int charCount,
 78                                            byte[] bytes, int byteIndex, bool flush)
 79        {
 080            ArgumentNullException.ThrowIfNull(chars);
 081            ArgumentNullException.ThrowIfNull(bytes);
 82
 083            ArgumentOutOfRangeException.ThrowIfNegative(charIndex);
 084            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 85
 086            if (chars.Length - charIndex < charCount)
 087                throw new ArgumentOutOfRangeException(nameof(chars),
 088                      SR.ArgumentOutOfRange_IndexCountBuffer);
 89
 090            if (byteIndex < 0 || byteIndex > bytes.Length)
 091                throw new ArgumentOutOfRangeException(nameof(byteIndex),
 092                     SR.ArgumentOutOfRange_IndexMustBeLessOrEqual);
 93
 094            int byteCount = bytes.Length - byteIndex;
 95
 96            // Just call pointer version
 097            fixed (char* pChars = &MemoryMarshal.GetArrayDataReference(chars))
 098            fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes))
 99            {
 100                // Remember that charCount is # to decode, not size of array.
 0101                return GetBytes(pChars + charIndex, charCount,
 0102                                pBytes + byteIndex, byteCount, flush);
 103            }
 104        }
 105
 106        public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount, bool flush)
 107        {
 21917108            ArgumentNullException.ThrowIfNull(chars);
 21917109            ArgumentNullException.ThrowIfNull(bytes);
 110
 21917111            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 21917112            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 113
 21917114            _mustFlush = flush;
 21917115            _throwOnOverflow = true;
 21917116            Debug.Assert(_encoding is not null);
 21917117            return _encoding.GetBytes(chars, charCount, bytes, byteCount, this);
 118        }
 119
 120        // This method is used when your output buffer might not be large enough for the entire result.
 121        // Just call the pointer version.  (This gets bytes)
 122        public override unsafe void Convert(char[] chars, int charIndex, int charCount,
 123                                            byte[] bytes, int byteIndex, int byteCount, bool flush,
 124                                            out int charsUsed, out int bytesUsed, out bool completed)
 125        {
 0126            ArgumentNullException.ThrowIfNull(chars);
 0127            ArgumentNullException.ThrowIfNull(bytes);
 128
 0129            ArgumentOutOfRangeException.ThrowIfNegative(charIndex);
 0130            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 131
 0132            ArgumentOutOfRangeException.ThrowIfNegative(byteIndex);
 0133            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 134
 0135            if (chars.Length - charIndex < charCount)
 0136                throw new ArgumentOutOfRangeException(nameof(chars),
 0137                      SR.ArgumentOutOfRange_IndexCountBuffer);
 138
 0139            if (bytes.Length - byteIndex < byteCount)
 0140                throw new ArgumentOutOfRangeException(nameof(bytes),
 0141                      SR.ArgumentOutOfRange_IndexCountBuffer);
 142
 143            // Just call the pointer version (can't do this for non-msft encoders)
 0144            fixed (char* pChars = &MemoryMarshal.GetArrayDataReference(chars))
 0145            fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes))
 146            {
 0147                Convert(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, flush,
 0148                    out charsUsed, out bytesUsed, out completed);
 149            }
 0150        }
 151
 152        // This is the version that uses pointers.  We call the base encoding worker function
 153        // after setting our appropriate internal variables.  This is getting bytes
 154        public override unsafe void Convert(char* chars, int charCount,
 155                                            byte* bytes, int byteCount, bool flush,
 156                                            out int charsUsed, out int bytesUsed, out bool completed)
 157        {
 0158            ArgumentNullException.ThrowIfNull(chars);
 0159            ArgumentNullException.ThrowIfNull(bytes);
 160
 0161            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 0162            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 163
 164            // We don't want to throw
 0165            _mustFlush = flush;
 0166            _throwOnOverflow = false;
 0167            _charsUsed = 0;
 168
 169            // Do conversion
 0170            Debug.Assert(_encoding is not null);
 0171            bytesUsed = _encoding.GetBytes(chars, charCount, bytes, byteCount, this);
 0172            charsUsed = _charsUsed;
 173
 174            // If the 'completed' out parameter is set to false, it means one of two things:
 175            // a) this call to Convert did not consume the entire source buffer; or
 176            // b) this call to Convert did consume the entire source buffer, but there's
 177            //    still pending data that needs to be written to the destination buffer.
 178            //
 179            // In either case, the caller should slice the input buffer, provide a fresh
 180            // destination buffer, and call Convert again in a loop until 'completed' is true.
 181            //
 182            // The caller *must* specify flush = true on the final iteration(s) of the loop
 183            // and iterate until 'completed' is set to true. Otherwise data loss may occur.
 184            //
 185            // Technically, the expected logic is detailed below.
 186            //
 187            // If 'flush' = false, the 'completed' parameter MUST be set to false if not all
 188            // elements of the source buffer have been consumed. The 'completed' parameter MUST
 189            // be set to true once the entire source buffer has been consumed and there is no
 190            // pending data for the destination buffer. (In other words, the 'completed' parameter
 191            // MUST be set to true if passing a zero-length source buffer and an infinite-length
 192            // destination buffer will make no forward progress.) The 'completed' parameter value
 193            // is undefined for the case where all source data has been consumed but there remains
 194            // pending data for the destination buffer.
 195            //
 196            // If 'flush' = true, the 'completed' parameter is set to true IF AND ONLY IF:
 197            // a) all elements of the source buffer have been transcoded into the destination buffer; AND
 198            // b) there remains no internal partial read state within this instance; AND
 199            // c) there remains no pending data for the destination buffer.
 200            //
 201            // In other words, if 'flush' = true, then when 'completed' is set to true it should mean
 202            // that all data has been converted and that this instance is indistinguishable from a
 203            // freshly-reset instance.
 204
 0205            completed = (charsUsed == charCount)
 0206                && (!flush || !this.HasState)
 0207                && (_fallbackBuffer is null || _fallbackBuffer.Remaining == 0);
 0208        }
 209
 210        public Encoding Encoding
 211        {
 212            get
 213            {
 0214                Debug.Assert(_encoding is not null);
 0215                return _encoding;
 216            }
 217        }
 218
 10846219        public bool MustFlush => _mustFlush;
 220
 221        /// <summary>
 222        /// States whether a call to <see cref="Encoding.GetBytes(char*, int, byte*, int, EncoderNLS)"/> must first drai
 223        /// </summary>
 10556224        internal bool HasLeftoverData => _charLeftOver != default || (_fallbackBuffer is not null && _fallbackBuffer.Rem
 225
 226        // Anything left in our encoder?
 0227        internal virtual bool HasState => _charLeftOver != (char)0;
 228
 229        // Allow encoding to clear our must flush instead of throwing (in ThrowBytesOverflow)
 230        internal void ClearMustFlush()
 231        {
 0232            _mustFlush = false;
 0233        }
 234
 235        internal int DrainLeftoverDataForGetByteCount(ReadOnlySpan<char> chars, out int charsConsumed)
 236        {
 237            // Quick check: we _should not_ have leftover fallback data from a previous invocation,
 238            // as we'd end up consuming any such data and would corrupt whatever Convert call happens
 239            // to be in progress.
 240
 0241            if (_fallbackBuffer is not null && _fallbackBuffer.Remaining > 0)
 242            {
 0243                throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, Encoding.EncodingName, _fallb
 244            }
 245
 246            // If we have a leftover high surrogate from a previous operation, consume it now.
 247            // We won't clear the _charLeftOver field since GetByteCount is supposed to be
 248            // a non-mutating operation, and we need the field to retain its value for the
 249            // next call to Convert.
 250
 0251            charsConsumed = 0; // could be incorrect, will fix up later in the method
 252
 0253            if (_charLeftOver == default)
 254            {
 0255                return 0; // no leftover high surrogate char - short-circuit and finish
 256            }
 257            else
 258            {
 0259                char secondChar = default;
 260
 0261                if (chars.IsEmpty)
 262                {
 263                    // If the input buffer is empty and we're not being asked to flush, no-op and return
 264                    // success to our caller. If we're being asked to flush, the leftover high surrogate from
 265                    // the previous operation will go through the fallback mechanism by itself.
 266
 0267                    if (!MustFlush)
 268                    {
 0269                        return 0; // no-op = success
 270                    }
 271                }
 272                else
 273                {
 0274                    secondChar = chars[0];
 275                }
 276
 277                // If we have to fallback the chars we're reading immediately below, populate the
 278                // fallback buffer with the invalid data. We'll just fall through to the "consume
 279                // fallback buffer" logic at the end of the method.
 280
 0281                if (Rune.TryCreate(_charLeftOver, secondChar, out Rune rune))
 282                {
 0283                    charsConsumed = 1; // consumed the leftover high surrogate + the first char in the input buffer
 284
 0285                    Debug.Assert(_encoding is not null);
 0286                    if (_encoding.TryGetByteCount(rune, out int byteCount))
 287                    {
 0288                        Debug.Assert(byteCount >= 0, "Encoding shouldn't have returned a negative byte count.");
 0289                        return byteCount;
 290                    }
 291                    else
 292                    {
 293                        // The fallback mechanism relies on a negative index to convey "the start of the invalid
 294                        // sequence was some number of chars back before the current buffer." In this block and
 295                        // in the block immediately thereafter, we know we have a single leftover high surrogate
 296                        // character from a previous operation, so we provide an index of -1 to convey that the
 297                        // char immediately before the current buffer was the start of the invalid sequence.
 298
 0299                        FallbackBuffer.Fallback(_charLeftOver, secondChar, index: -1);
 300                    }
 301                }
 302                else
 303                {
 0304                    FallbackBuffer.Fallback(_charLeftOver, index: -1);
 305                }
 306
 307                // Now tally the number of bytes that would've been emitted as part of fallback.
 0308                Debug.Assert(_fallbackBuffer is not null);
 0309                return _fallbackBuffer.DrainRemainingDataForGetByteCount();
 310            }
 311        }
 312
 313        internal bool TryDrainLeftoverDataForGetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, out int charsConsumed,
 314        {
 315            // We may have a leftover high surrogate data from a previous invocation, or we may have leftover
 316            // data in the fallback buffer, or we may have neither, but we will never have both. Check for these
 317            // conditions and handle them now.
 318
 0319            charsConsumed = 0; // could be incorrect, will fix up later in the method
 0320            bytesWritten = 0; // could be incorrect, will fix up later in the method
 321
 0322            if (_charLeftOver != default)
 323            {
 0324                char secondChar = default;
 325
 0326                if (chars.IsEmpty)
 327                {
 328                    // If the input buffer is empty and we're not being asked to flush, no-op and return
 329                    // success to our caller. If we're being asked to flush, the leftover high surrogate from
 330                    // the previous operation will go through the fallback mechanism by itself.
 331
 0332                    if (!MustFlush)
 333                    {
 0334                        charsConsumed = 0;
 0335                        bytesWritten = 0;
 0336                        return true; // no-op = success
 337                    }
 338                }
 339                else
 340                {
 0341                    secondChar = chars[0];
 342                }
 343
 344                // We're about to consume the leftover char. Make a local copy of it and clear
 345                // the backing field. We don't bother restoring its value if an exception occurs
 346                // because exceptional code paths corrupt instance state anyway (e.g., by
 347                // mutating the fallback buffer contents).
 348
 0349                char charLeftOver = _charLeftOver;
 0350                _charLeftOver = default;
 351
 352                // If we have to fallback the chars we're reading immediately below, populate the
 353                // fallback buffer with the invalid data. We'll just fall through to the "consume
 354                // fallback buffer" logic at the end of the method.
 355
 0356                if (Rune.TryCreate(charLeftOver, secondChar, out Rune rune))
 357                {
 0358                    charsConsumed = 1; // at the very least, we consumed 1 char from the input
 0359                    Debug.Assert(_encoding is not null);
 0360                    switch (_encoding.EncodeRune(rune, bytes, out bytesWritten))
 361                    {
 362                        case OperationStatus.Done:
 0363                            return true; // that's all - we've handled the leftover data
 364
 365                        case OperationStatus.DestinationTooSmall:
 0366                            _encoding.ThrowBytesOverflow(this, nothingEncoded: true); // will throw
 0367                            break;
 368
 369                        case OperationStatus.InvalidData:
 0370                            FallbackBuffer.Fallback(charLeftOver, secondChar, index: -1); // see comment in DrainLeftove
 0371                            break;
 372
 373                        default:
 0374                            Debug.Fail("Unknown return value.");
 375                            break;
 376                    }
 377                }
 378                else
 379                {
 0380                    FallbackBuffer.Fallback(charLeftOver, index: -1); // see comment in DrainLeftoverDataForGetByteCount
 381                }
 382            }
 383
 384            // Now check the fallback buffer for any remaining data.
 385
 0386            if (_fallbackBuffer is not null && _fallbackBuffer.Remaining > 0)
 387            {
 0388                return _fallbackBuffer.TryDrainRemainingDataForGetBytes(bytes, out bytesWritten);
 389            }
 390
 391            // And we're done!
 392
 0393            return true; // success
 394        }
 395    }
 396}
 397