< Summary

Line coverage
12%
Covered lines: 33
Uncovered lines: 229
Coverable lines: 262
Total lines: 838
Line coverage: 12.5%
Branch coverage
7%
Covered branches: 10
Total branches: 126
Branch coverage: 7.9%
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/Unicode/Utf8.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.ComponentModel;
 6using System.Diagnostics;
 7using System.Runtime.CompilerServices;
 8using System.Runtime.InteropServices;
 9
 10namespace System.Text.Unicode
 11{
 12    /// <summary>
 13    /// Provides static methods that convert chunked data between UTF-8 and UTF-16 encodings, and methods that validate 
 14    /// </summary>
 15    public static class Utf8
 16    {
 17        /*
 18         * OperationStatus-based APIs for transcoding of chunked data.
 19         * This method is similar to Encoding.UTF8.GetBytes / GetChars but has a
 20         * different calling convention, different error handling mechanisms, and
 21         * different performance characteristics.
 22         *
 23         * If 'replaceInvalidSequences' is true, the method will replace any ill-formed
 24         * subsequence in the source with U+FFFD when transcoding to the destination,
 25         * then it will continue processing the remainder of the buffers. Otherwise
 26         * the method will return OperationStatus.InvalidData.
 27         *
 28         * If the method does return an error code, the out parameters will represent
 29         * how much of the data was successfully transcoded, and the location of the
 30         * ill-formed subsequence can be deduced from these values.
 31         *
 32         * If 'replaceInvalidSequences' is true, the method is guaranteed never to return
 33         * OperationStatus.InvalidData. If 'isFinalBlock' is true, the method is
 34         * guaranteed never to return OperationStatus.NeedMoreData.
 35         */
 36
 37        /// <summary>
 38        /// Transcodes the UTF-16 <paramref name="source"/> buffer to <paramref name="destination"/> as UTF-8.
 39        /// </summary>
 40        /// <remarks>
 41        /// If <paramref name="replaceInvalidSequences"/> is <see langword="true"/>, invalid UTF-16 sequences
 42        /// in <paramref name="source"/> will be replaced with U+FFFD in <paramref name="destination"/>, and
 43        /// this method will not return <see cref="OperationStatus.InvalidData"/>.
 44        /// </remarks>
 45        public static unsafe OperationStatus FromUtf16(ReadOnlySpan<char> source, Span<byte> destination, out int charsR
 046        {
 047            fixed (char* pOriginalSource = &MemoryMarshal.GetReference(source))
 048            fixed (byte* pOriginalDestination = &MemoryMarshal.GetReference(destination))
 49            {
 50                // We're going to bulk transcode as much as we can in a loop, iterating
 51                // every time we see bad data that requires replacement.
 52
 053                OperationStatus operationStatus = OperationStatus.Done;
 054                char* pInputBufferRemaining = pOriginalSource;
 055                byte* pOutputBufferRemaining = pOriginalDestination;
 56
 057                while (!source.IsEmpty)
 58                {
 59                    // We've pinned the spans at the entry point to this method.
 60                    // It's safe for us to use Unsafe.AsPointer on them during this loop.
 61
 062                    operationStatus = Utf8Utility.TranscodeToUtf8(
 063                        pInputBuffer: (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)),
 064                        inputLength: source.Length,
 065                        pOutputBuffer: (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)),
 066                        outputBytesRemaining: destination.Length,
 067                        pInputBufferRemaining: out pInputBufferRemaining,
 068                        pOutputBufferRemaining: out pOutputBufferRemaining);
 69
 70                    // If we finished the operation entirely or we ran out of space in the destination buffer,
 71                    // or if we need more input data and the caller told us that there's possibly more data
 72                    // coming, return immediately.
 73
 074                    if (operationStatus <= OperationStatus.DestinationTooSmall
 075                        || (operationStatus == OperationStatus.NeedMoreData && !isFinalBlock))
 76                    {
 77                        break;
 78                    }
 79
 80                    // We encountered invalid data, or we need more data but the caller told us we're
 81                    // at the end of the stream. In either case treat this as truly invalid.
 82                    // If the caller didn't tell us to replace invalid sequences, return immediately.
 83
 084                    if (!replaceInvalidSequences)
 85                    {
 086                        operationStatus = OperationStatus.InvalidData; // status code may have been NeedMoreData - force
 087                        break;
 88                    }
 89
 90                    // We're going to attempt to write U+FFFD to the destination buffer.
 91                    // Do we even have enough space to do so?
 92
 093                    destination = destination.Slice((int)(pOutputBufferRemaining - (byte*)Unsafe.AsPointer(ref MemoryMar
 94
 095                    if (destination.Length <= 2)
 96                    {
 097                        operationStatus = OperationStatus.DestinationTooSmall;
 098                        break;
 99                    }
 100
 0101                    destination[0] = 0xEF; // U+FFFD = [ EF BF BD ] in UTF-8
 0102                    destination[1] = 0xBF;
 0103                    destination[2] = 0xBD;
 0104                    destination = destination.Slice(3);
 105
 106                    // Invalid UTF-16 sequences are always of length 1. Just skip the next character.
 107
 0108                    source = source.Slice((int)(pInputBufferRemaining - (char*)Unsafe.AsPointer(ref MemoryMarshal.GetRef
 109
 0110                    operationStatus = OperationStatus.Done; // we patched the error - if we're about to break out of the
 0111                    pInputBufferRemaining = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source));
 0112                    pOutputBufferRemaining = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination));
 113                }
 114
 115                // Not possible to make any further progress - report to our caller how far we got.
 116
 0117                charsRead = (int)(pInputBufferRemaining - pOriginalSource);
 0118                bytesWritten = (int)(pOutputBufferRemaining - pOriginalDestination);
 0119                return operationStatus;
 120            }
 121        }
 122
 123        /// <summary>
 124        /// Transcodes the UTF-8 <paramref name="source"/> buffer to <paramref name="destination"/> as UTF-16.
 125        /// </summary>
 126        /// <remarks>
 127        /// If <paramref name="replaceInvalidSequences"/> is <see langword="true"/>, invalid UTF-8 sequences
 128        /// in <paramref name="source"/> will be replaced with U+FFFD in <paramref name="destination"/>, and
 129        /// this method will not return <see cref="OperationStatus.InvalidData"/>.
 130        /// </remarks>
 131        public static unsafe OperationStatus ToUtf16(ReadOnlySpan<byte> source, Span<char> destination, out int bytesRea
 686509132        {
 133            // NOTE: Changes to this method should be kept in sync with ToUtf16PreservingReplacement below.
 134            // See it for an explanation of the differences
 135
 136            // We'll be mutating these values throughout our loop.
 137
 686509138            fixed (byte* pOriginalSource = &MemoryMarshal.GetReference(source))
 686509139            fixed (char* pOriginalDestination = &MemoryMarshal.GetReference(destination))
 140            {
 141                // We're going to bulk transcode as much as we can in a loop, iterating
 142                // every time we see bad data that requires replacement.
 143
 686509144                OperationStatus operationStatus = OperationStatus.Done;
 686509145                byte* pInputBufferRemaining = pOriginalSource;
 686509146                char* pOutputBufferRemaining = pOriginalDestination;
 147
 1687159148                while (!source.IsEmpty)
 149                {
 150                    // We've pinned the spans at the entry point to this method.
 151                    // It's safe for us to use Unsafe.AsPointer on them during this loop.
 152
 1678738153                    operationStatus = Utf8Utility.TranscodeToUtf16(
 1678738154                        pInputBuffer: (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)),
 1678738155                        inputLength: source.Length,
 1678738156                        pOutputBuffer: (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)),
 1678738157                        outputCharsRemaining: destination.Length,
 1678738158                        pInputBufferRemaining: out pInputBufferRemaining,
 1678738159                        pOutputBufferRemaining: out pOutputBufferRemaining);
 160
 161                    // If we finished the operation entirely or we ran out of space in the destination buffer,
 162                    // or if we need more input data and the caller told us that there's possibly more data
 163                    // coming, return immediately.
 164
 1678738165                    if (operationStatus <= OperationStatus.DestinationTooSmall
 1678738166                        || (operationStatus == OperationStatus.NeedMoreData && !isFinalBlock))
 167                    {
 168                        break;
 169                    }
 170
 171                    // We encountered invalid data, or we need more data but the caller told us we're
 172                    // at the end of the stream. In either case treat this as truly invalid.
 173                    // If the caller didn't tell us to replace invalid sequences, return immediately.
 174
 1667750175                    if (!replaceInvalidSequences)
 176                    {
 667100177                        operationStatus = OperationStatus.InvalidData; // status code may have been NeedMoreData - force
 667100178                        break;
 179                    }
 180
 181                    // We're going to attempt to write U+FFFD to the destination buffer.
 182                    // Do we even have enough space to do so?
 183
 1000650184                    destination = destination.Slice((int)(pOutputBufferRemaining - (char*)Unsafe.AsPointer(ref MemoryMar
 185
 1000650186                    if (destination.IsEmpty)
 187                    {
 0188                        operationStatus = OperationStatus.DestinationTooSmall;
 0189                        break;
 190                    }
 191
 1000650192                    destination[0] = (char)UnicodeUtility.ReplacementChar;
 1000650193                    destination = destination.Slice(1);
 194
 195                    // Now figure out how many bytes of the source we must skip over before we should retry
 196                    // the operation. This might be more than 1 byte.
 197
 1000650198                    source = source.Slice((int)(pInputBufferRemaining - (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetRef
 1000650199                    Debug.Assert(!source.IsEmpty, "Expected 'Done' if source is fully consumed.");
 200
 1000650201                    Rune.DecodeFromUtf8(source, out _, out int bytesConsumedJustNow);
 1000650202                    source = source.Slice(bytesConsumedJustNow);
 203
 1000650204                    operationStatus = OperationStatus.Done; // we patched the error - if we're about to break out of the
 1000650205                    pInputBufferRemaining = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source));
 1000650206                    pOutputBufferRemaining = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination));
 207                }
 208
 209                // Not possible to make any further progress - report to our caller how far we got.
 210
 686509211                bytesRead = (int)(pInputBufferRemaining - pOriginalSource);
 686509212                charsWritten = (int)(pOutputBufferRemaining - pOriginalDestination);
 686509213                return operationStatus;
 214            }
 215        }
 216
 217#if NET
 218        internal static unsafe OperationStatus ToUtf16PreservingReplacement(ReadOnlySpan<byte> source, Span<char> destin
 0219        {
 220            // NOTE: Changes to this method should be kept in sync with ToUtf16 above.
 221            //
 222            // This method exists to allow certain internal comparisons to function as expected under ICU.
 223            // Essentially, ICU treats invalid UTF-16 sequences as opaque characters that only compare
 224            // equal to themselves. This means "\uD800\uD801".StartsWith("\uD800") returns true. To support
 225            // similar for UTF-8 and allow comparisons like "\xFF\xFE"u8.CultureAwareStartsWith("\xFF"u8)
 226            // to also return true, we replace each character in an invalid UTF-8 sequence such that it
 227            // becomes 0xDF?? where ?? is the individual UTF-8 byte. Thus the above becomes 0xDFFF, 0xDFFE.
 228            // This allows them to compare as invalid UTF-16 sequences and thus only match with the same
 229            // invalid sequence.
 230
 231            // We'll be mutating these values throughout our loop.
 232
 0233            fixed (byte* pOriginalSource = &MemoryMarshal.GetReference(source))
 0234            fixed (char* pOriginalDestination = &MemoryMarshal.GetReference(destination))
 235            {
 236                // We're going to bulk transcode as much as we can in a loop, iterating
 237                // every time we see bad data that requires replacement.
 238
 0239                OperationStatus operationStatus = OperationStatus.Done;
 0240                byte* pInputBufferRemaining = pOriginalSource;
 0241                char* pOutputBufferRemaining = pOriginalDestination;
 242
 0243                while (!source.IsEmpty)
 244                {
 245                    // We've pinned the spans at the entry point to this method.
 246                    // It's safe for us to use Unsafe.AsPointer on them during this loop.
 247
 0248                    operationStatus = Utf8Utility.TranscodeToUtf16(
 0249                        pInputBuffer: (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)),
 0250                        inputLength: source.Length,
 0251                        pOutputBuffer: (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)),
 0252                        outputCharsRemaining: destination.Length,
 0253                        pInputBufferRemaining: out pInputBufferRemaining,
 0254                        pOutputBufferRemaining: out pOutputBufferRemaining);
 255
 256                    // If we finished the operation entirely or we ran out of space in the destination buffer,
 257                    // or if we need more input data and the caller told us that there's possibly more data
 258                    // coming, return immediately.
 259
 0260                    if (operationStatus <= OperationStatus.DestinationTooSmall
 0261                        || (operationStatus == OperationStatus.NeedMoreData && !isFinalBlock))
 262                    {
 263                        break;
 264                    }
 265
 266                    // We encountered invalid data, or we need more data but the caller told us we're
 267                    // at the end of the stream. In either case treat this as truly invalid.
 268                    // If the caller didn't tell us to replace invalid sequences, return immediately.
 269
 0270                    if (!replaceInvalidSequences)
 271                    {
 0272                        operationStatus = OperationStatus.InvalidData; // status code may have been NeedMoreData - force
 0273                        break;
 274                    }
 275
 276                    // We're going to attempt to write U+DF?? to the destination buffer for each invalid byte
 277                    //
 278                    // Figure out how many bytes of the source we must skip over before we should retry
 279                    // the operation. This might be more than 1 byte.
 280                    //
 281                    // Check if we even have enough space to do so?
 282
 0283                    source = source.Slice((int)(pInputBufferRemaining - (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetRef
 0284                    destination = destination.Slice((int)(pOutputBufferRemaining - (char*)Unsafe.AsPointer(ref MemoryMar
 285
 0286                    Debug.Assert(!source.IsEmpty, "Expected 'Done' if source is fully consumed.");
 0287                    Rune.DecodeFromUtf8(source, out _, out int bytesConsumedJustNow);
 288
 0289                    if (destination.Length < bytesConsumedJustNow)
 290                    {
 0291                        operationStatus = OperationStatus.DestinationTooSmall;
 0292                        break;
 293                    }
 294
 0295                    for (int i = 0; i < bytesConsumedJustNow; i++)
 296                    {
 0297                        destination[i] = (char)(0xDF00 | source[i]);
 298                    }
 299
 0300                    destination = destination.Slice(bytesConsumedJustNow);
 0301                    source = source.Slice(bytesConsumedJustNow);
 302
 0303                    operationStatus = OperationStatus.Done; // we patched the error - if we're about to break out of the
 304
 0305                    pInputBufferRemaining = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source));
 0306                    pOutputBufferRemaining = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination));
 307                }
 308
 309                // Not possible to make any further progress - report to our caller how far we got.
 310
 0311                bytesRead = (int)(pInputBufferRemaining - pOriginalSource);
 0312                charsWritten = (int)(pOutputBufferRemaining - pOriginalDestination);
 0313                return operationStatus;
 314            }
 315        }
 316
 317        /// <summary>Writes the specified interpolated string to the UTF-8 byte span.</summary>
 318        /// <param name="destination">The span to which the interpolated string should be formatted.</param>
 319        /// <param name="handler">The interpolated string.</param>
 320        /// <param name="bytesWritten">The number of characters written to the span.</param>
 321        /// <returns>true if the entire interpolated string could be formatted successfully; otherwise, false.</returns>
 322        public static bool TryWrite(Span<byte> destination, [InterpolatedStringHandlerArgument(nameof(destination))] ref
 323        {
 324            // The span argument isn't used directly in the method; rather, it'll be used by the compiler to create the 
 325            // We could validate here that span == handler._destination, but that doesn't seem necessary.
 0326            if (handler._success)
 327            {
 0328                bytesWritten = handler._pos;
 0329                return true;
 330            }
 331
 0332            bytesWritten = 0;
 0333            return false;
 334        }
 335
 336        /// <summary>Writes the specified interpolated string to the UTF-8 byte span.</summary>
 337        /// <param name="destination">The span to which the interpolated string should be formatted.</param>
 338        /// <param name="provider">An object that supplies culture-specific formatting information.</param>
 339        /// <param name="handler">The interpolated string.</param>
 340        /// <param name="bytesWritten">The number of characters written to the span.</param>
 341        /// <returns>true if the entire interpolated string could be formatted successfully; otherwise, false.</returns>
 342        public static bool TryWrite(Span<byte> destination, IFormatProvider? provider, [InterpolatedStringHandlerArgumen
 343            // The provider is passed to the handler by the compiler, so the actual implementation of the method
 344            // is the same as the non-provider overload.
 0345            TryWrite(destination, ref handler, out bytesWritten);
 346
 347        /// <summary>Provides a handler used by the language compiler to format interpolated strings into UTF-8 byte spa
 348        [EditorBrowsable(EditorBrowsableState.Never)]
 349        [InterpolatedStringHandler]
 350        public ref struct TryWriteInterpolatedStringHandler
 351        {
 352            /// <summary>The destination UTF-8 buffer.</summary>
 353            private readonly Span<byte> _destination;
 354            /// <summary>Optional provider to pass to IFormattable.ToString, ISpanFormattable.TryFormat, and IUtf8SpanFo
 355            private readonly IFormatProvider? _provider;
 356            /// <summary>The number of bytes written to <see cref="_destination"/>.</summary>
 357            internal int _pos;
 358            /// <summary>true if all formatting operations have succeeded; otherwise, false.</summary>
 359            internal bool _success;
 360            /// <summary>Whether <see cref="_provider"/> provides an ICustomFormatter.</summary>
 361            private readonly bool _hasCustomFormatter;
 362
 363            /// <summary>Creates a handler used to write an interpolated string into a UTF-8 <see cref="Span{Byte}"/>.</
 364            /// <param name="literalLength">The number of constant characters outside of interpolation expressions in th
 365            /// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
 366            /// <param name="destination">The destination buffer.</param>
 367            /// <param name="shouldAppend">Upon return, true if the destination may be long enough to support the format
 368            /// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as t
 369            public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, Span<byte> destination, out 
 370            {
 0371                _destination = destination;
 0372                _provider = null;
 0373                _pos = 0;
 0374                _success = shouldAppend = destination.Length >= literalLength; // UTF8 encoding never produces fewer byt
 0375                _hasCustomFormatter = false;
 0376            }
 377
 378            /// <summary>Creates a handler used to write an interpolated string into a UTF-8 <see cref="Span{Byte}"/>.</
 379            /// <param name="literalLength">The number of constant characters outside of interpolation expressions in th
 380            /// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
 381            /// <param name="destination">The destination buffer.</param>
 382            /// <param name="provider">An object that supplies culture-specific formatting information.</param>
 383            /// <param name="shouldAppend">Upon return, true if the destination may be long enough to support the format
 384            /// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as t
 385            public TryWriteInterpolatedStringHandler(int literalLength, int formattedCount, Span<byte> destination, IFor
 386            {
 0387                _destination = destination;
 0388                _provider = provider;
 0389                _pos = 0;
 0390                _success = shouldAppend = destination.Length >= literalLength; // UTF8 encoding never produces fewer byt
 0391                _hasCustomFormatter = provider is not null && DefaultInterpolatedStringHandler.HasCustomFormatter(provid
 0392            }
 393
 394            /// <summary>Writes the specified string to the handler.</summary>
 395            /// <param name="value">The string to write.</param>
 396            /// <returns>true if the value could be formatted to the span; otherwise, false.</returns>
 397            [MethodImpl(MethodImplOptions.AggressiveInlining)] // we want 'value' exposed to the JIT as a constant
 398            public bool AppendLiteral(string value)
 399            {
 0400                if (value is not null)
 401                {
 0402                    Span<byte> dest = _destination.Slice(_pos);
 403
 404                    // The 99.999% for AppendLiteral is to be called with a const string.
 405                    // ReadUtf8 is a JIT intrinsic that can do the UTF8 encoding at JIT time.
 0406                    int bytesWritten = UTF8Encoding.UTF8EncodingSealed.ReadUtf8(
 0407                        ref value.GetRawStringData(), value.Length,
 0408                        ref MemoryMarshal.GetReference(dest), dest.Length);
 0409                    if (bytesWritten < 0)
 410                    {
 0411                        return Fail();
 412                    }
 413
 0414                    _pos += bytesWritten;
 415                }
 416
 0417                return true;
 418            }
 419
 420            /// <summary>Writes the specified value to the handler.</summary>
 421            /// <param name="value">The value to write.</param>
 422            /// <typeparam name="T">The type of the value to write.</typeparam>
 423            public bool AppendFormatted<T>(T value)
 424            {
 425                // This method could delegate to AppendFormatted with a null format, but explicitly passing
 426                // default as the format to TryFormat helps to improve code quality in some cases when TryFormat is inli
 427                // e.g. for Int32 it enables the JIT to eliminate code in the inlined method based on a length check on 
 428
 429                // If there's a custom formatter, always use it.
 0430                if (_hasCustomFormatter)
 431                {
 0432                    return AppendCustomFormatter(value, format: null);
 433                }
 434
 0435                if (value is null)
 436                {
 0437                    return true;
 438                }
 439
 440                // Special-case enums to avoid boxing them.
 0441                if (typeof(T).IsEnum)
 442                {
 443                    // TODO https://github.com/dotnet/runtime/issues/81500:
 444                    // Once Enum.TryFormat provides direct UTF8 support, use that here instead.
 0445                    return AppendEnum(value, format: null);
 446                }
 447
 448                // If the value can format itself directly into our buffer, do so.
 0449                if (value is IUtf8SpanFormattable)
 450                {
 0451                    if (((IUtf8SpanFormattable)value).TryFormat(_destination.Slice(_pos), out int bytesWritten, format: 
 452                    {
 0453                        _pos += bytesWritten;
 0454                        return true;
 455                    }
 456
 0457                    return Fail();
 458                }
 459
 460                string? s;
 0461                if (value is IFormattable)
 462                {
 463                    // If the value can format itself directly into a UTF16 buffer, do so, then transcode.
 0464                    if (value is ISpanFormattable)
 465                    {
 0466                        return AppendSpanFormattable(value, format: null);
 467                    }
 468
 469                    // If the value can ToString with the format / provider, get the resulting string, then append that.
 0470                    s = ((IFormattable)value).ToString(null, _provider);
 471                }
 472                else
 473                {
 474                    // Fall back to a normal ToString and append that.
 0475                    s = value.ToString();
 476                }
 477
 0478                return AppendFormatted(s.AsSpan());
 479            }
 480
 481            /// <summary>Writes the specified value to the handler.</summary>
 482            /// <param name="value">The value to write.</param>
 483            /// <param name="format">The format string.</param>
 484            /// <typeparam name="T">The type of the value to write.</typeparam>
 485            public bool AppendFormatted<T>(T value, string? format)
 486            {
 487                // If there's a custom formatter, always use it.
 0488                if (_hasCustomFormatter)
 489                {
 0490                    return AppendCustomFormatter(value, format);
 491                }
 492
 0493                if (value is null)
 494                {
 0495                    return true;
 496                }
 497
 498                // Special-case enums to avoid boxing them.
 0499                if (typeof(T).IsEnum)
 500                {
 501                    // TODO https://github.com/dotnet/runtime/issues/81500:
 502                    // Once Enum.TryFormat provides direct UTF8 support, use that here instead.
 0503                    return AppendEnum(value, format);
 504                }
 505
 506                // If the value can format itself directly into our buffer, do so.
 0507                if (value is IUtf8SpanFormattable)
 508                {
 0509                    if (((IUtf8SpanFormattable)value).TryFormat(_destination.Slice(_pos), out int bytesWritten, format, 
 510                    {
 0511                        _pos += bytesWritten;
 0512                        return true;
 513                    }
 514
 0515                    return Fail();
 516                }
 517
 518                string? s;
 0519                if (value is IFormattable)
 520                {
 521                    // If the value can format itself directly into a UTF16 buffer, do so, then transcode.
 0522                    if (value is ISpanFormattable)
 523                    {
 0524                        return AppendSpanFormattable(value, format);
 525                    }
 526
 527                    // If the value can ToString with the format / provider, get the resulting string, then append that.
 0528                    s = ((IFormattable)value).ToString(format, _provider);
 529                }
 530                else
 531                {
 532                    // Fall back to a normal ToString and append that.
 0533                    s = value.ToString();
 534                }
 535
 0536                return AppendFormatted(s.AsSpan());
 537            }
 538
 539            /// <summary>Writes the specified value to the handler.</summary>
 540            /// <param name="value">The value to write.</param>
 541            /// <param name="alignment">Minimum number of characters that should be written for this value.  If the valu
 542            /// <typeparam name="T">The type of the value to write.</typeparam>
 543            public bool AppendFormatted<T>(T value, int alignment)
 544            {
 0545                int startingPos = _pos;
 0546                if (AppendFormatted(value))
 547                {
 0548                    return alignment == 0 || TryAppendOrInsertAlignmentIfNeeded(startingPos, alignment);
 549                }
 550
 0551                return Fail();
 552            }
 553
 554            /// <summary>Writes the specified value to the handler.</summary>
 555            /// <param name="value">The value to write.</param>
 556            /// <param name="format">The format string.</param>
 557            /// <param name="alignment">Minimum number of characters that should be written for this value.  If the valu
 558            /// <typeparam name="T">The type of the value to write.</typeparam>
 559            public bool AppendFormatted<T>(T value, int alignment, string? format)
 560            {
 0561                int startingPos = _pos;
 0562                if (AppendFormatted(value, format))
 563                {
 0564                    return alignment == 0 || TryAppendOrInsertAlignmentIfNeeded(startingPos, alignment);
 565                }
 566
 0567                return Fail();
 568            }
 569
 570            /// <summary>Writes the specified character span to the handler.</summary>
 571            /// <param name="value">The span to write.</param>
 572            public bool AppendFormatted(scoped ReadOnlySpan<char> value)
 573            {
 0574                if (Encoding.UTF8.TryGetBytes(value, _destination.Slice(_pos), out int bytesWritten))
 575                {
 0576                    _pos += bytesWritten;
 0577                    return true;
 578                }
 579
 0580                return Fail();
 581            }
 582
 583            /// <summary>Writes the specified string of chars to the handler.</summary>
 584            /// <param name="value">The span to write.</param>
 585            /// <param name="alignment">Minimum number of characters that should be written for this value.  If the valu
 586            /// <param name="format">The format string.</param>
 587            public bool AppendFormatted(scoped ReadOnlySpan<char> value, int alignment = 0, string? format = null)
 588            {
 0589                int startingPos = _pos;
 0590                if (AppendFormatted(value))
 591                {
 0592                    return alignment == 0 || TryAppendOrInsertAlignmentIfNeeded(startingPos, alignment);
 593                }
 594
 0595                return Fail();
 596            }
 597
 598            /// <summary>Writes the specified span of UTF-8 bytes to the handler.</summary>
 599            /// <param name="utf8Value">The span to write.</param>
 600            public bool AppendFormatted(scoped ReadOnlySpan<byte> utf8Value)
 601            {
 0602                if (utf8Value.TryCopyTo(_destination.Slice(_pos)))
 603                {
 0604                    _pos += utf8Value.Length;
 0605                    return true;
 606                }
 607
 0608                return Fail();
 609            }
 610
 611            /// <summary>Writes the specified span of UTF-8 bytes to the handler.</summary>
 612            /// <param name="utf8Value">The span to write.</param>
 613            /// <param name="alignment">Minimum number of characters that should be written for this value.  If the valu
 614            /// <param name="format">The format string.</param>
 615            public bool AppendFormatted(scoped ReadOnlySpan<byte> utf8Value, int alignment = 0, string? format = null)
 616            {
 0617                int startingPos = _pos;
 0618                if (AppendFormatted(utf8Value))
 619                {
 0620                    return alignment == 0 || TryAppendOrInsertAlignmentIfNeeded(startingPos, alignment);
 621                }
 622
 0623                return Fail();
 624            }
 625
 626            /// <summary>Writes the specified value to the handler.</summary>
 627            /// <param name="value">The value to write.</param>
 628            public bool AppendFormatted(string? value) =>
 0629                _hasCustomFormatter ? AppendCustomFormatter(value, format: null) :
 0630                AppendFormatted(value.AsSpan());
 631
 632            /// <summary>Writes the specified value to the handler.</summary>
 633            /// <param name="value">The value to write.</param>
 634            /// <param name="alignment">Minimum number of characters that should be written for this value.  If the valu
 635            /// <param name="format">The format string.</param>
 636            public bool AppendFormatted(string? value, int alignment = 0, string? format = null) =>
 637                // Format is meaningless for strings and doesn't make sense for someone to specify.  We have the overloa
 638                // simply to disambiguate between ROS and object, just in case someone does specify a format, as
 639                // string is implicitly convertible to both. Just delegate to the T-based implementation.
 0640                AppendFormatted<string?>(value, alignment, format);
 641
 642            /// <summary>Writes the specified value to the handler.</summary>
 643            /// <param name="value">The value to write.</param>
 644            /// <param name="alignment">Minimum number of characters that should be written for this value.  If the valu
 645            /// <param name="format">The format string.</param>
 646            public bool AppendFormatted(object? value, int alignment = 0, string? format = null) =>
 647                // This overload is expected to be used rarely, only if either a) something strongly typed as object is
 648                // formatted with both an alignment and a format, or b) the compiler is unable to target type to T. It
 649                // exists purely to help make cases from (b) compile. Just delegate to the T-based implementation.
 0650                AppendFormatted<object?>(value, alignment, format);
 651
 652            /// <summary>Formats the value using the custom formatter from the provider.</summary>
 653            /// <param name="value">The value to write.</param>
 654            /// <param name="format">The format string.</param>
 655            /// <typeparam name="T">The type of the value to write.</typeparam>
 656            [MethodImpl(MethodImplOptions.NoInlining)]
 657            private bool AppendCustomFormatter<T>(T value, string? format)
 658            {
 659                // This case is very rare, but we need to handle it prior to the other checks in case
 660                // a provider was used that supplied an ICustomFormatter which wanted to intercept the particular value.
 661                // We do the cast here rather than in the ctor, even though this could be executed multiple times per
 662                // formatting, to make the cast pay for play.
 0663                Debug.Assert(_hasCustomFormatter);
 0664                Debug.Assert(_provider is not null);
 665
 0666                ICustomFormatter? formatter = (ICustomFormatter?)_provider.GetFormat(typeof(ICustomFormatter));
 0667                Debug.Assert(formatter is not null, "An incorrectly written provider said it implemented ICustomFormatte
 668
 0669                if (formatter is not null &&
 0670                    formatter.Format(format, value, _provider) is string customFormatted)
 671                {
 0672                    return AppendFormatted(customFormatted.AsSpan());
 673                }
 674
 0675                return true;
 676            }
 677
 678            /// <summary>Writes the specified ISpanFormattable to the handler.</summary>
 679            /// <param name="value">The value to write. It must be an ISpanFormattable but isn't constrained because the
 680            /// <param name="format">The format string.</param>
 681            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 682            private unsafe bool AppendSpanFormattable<T>(T value, string? format)
 683            {
 0684                Debug.Assert(value is ISpanFormattable);
 685
 0686                Span<char> utf16 = stackalloc char[256];
 0687                return ((ISpanFormattable)value).TryFormat(utf16, out int charsWritten, format, _provider) ?
 0688                    AppendFormatted(utf16.Slice(0, charsWritten)) :
 0689                    GrowAndAppendFormatted(ref this, value, utf16.Length, out charsWritten, format);
 690
 691                [MethodImpl(MethodImplOptions.NoInlining)]
 692                static bool GrowAndAppendFormatted(scoped ref TryWriteInterpolatedStringHandler thisRef, T value, int le
 693                {
 0694                    Debug.Assert(value is ISpanFormattable);
 695
 696                    while (true)
 697                    {
 0698                        int newLength = length * 2;
 0699                        if ((uint)newLength > Array.MaxLength)
 700                        {
 0701                            newLength = length == Array.MaxLength ?
 0702                                Array.MaxLength + 1 : // force OOM
 0703                                Array.MaxLength;
 704                        }
 0705                        length = newLength;
 706
 0707                        char[] array = ArrayPool<char>.Shared.Rent(length);
 708                        try
 709                        {
 0710                            if (((ISpanFormattable)value).TryFormat(array, out charsWritten, format, thisRef._provider))
 711                            {
 0712                                return thisRef.AppendFormatted(array.AsSpan(0, charsWritten));
 713                            }
 0714                        }
 715                        finally
 716                        {
 0717                            ArrayPool<char>.Shared.Return(array);
 0718                        }
 719                    }
 0720                }
 721            }
 722
 723            // TODO https://github.com/dotnet/runtime/issues/81500:
 724            // Remove once Enum.TryFormat(Span<byte>, ...) is available.
 725            /// <summary>Writes the specified enum to the handler.</summary>
 726            /// <param name="value">The value to write. It must be an enum but isn't constrained because the caller does
 727            /// <param name="format">The format string.</param>
 728            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 729            private unsafe bool AppendEnum<T>(T value, string? format)
 730            {
 0731                Debug.Assert(typeof(T).IsEnum);
 732
 0733                Span<char> utf16 = stackalloc char[256];
 0734                return Enum.TryFormatUnconstrained(value, utf16, out int charsWritten, format) ?
 0735                    AppendFormatted(utf16.Slice(0, charsWritten)) :
 0736                    GrowAndAppendFormatted(ref this, value, utf16.Length, out charsWritten, format);
 737
 738                [MethodImpl(MethodImplOptions.NoInlining)]
 739                static bool GrowAndAppendFormatted(scoped ref TryWriteInterpolatedStringHandler thisRef, T value, int le
 740                {
 0741                    Debug.Assert(value is ISpanFormattable);
 742
 743                    while (true)
 744                    {
 0745                        int newLength = length * 2;
 0746                        if ((uint)newLength > Array.MaxLength)
 747                        {
 0748                            newLength = length == Array.MaxLength ?
 0749                                Array.MaxLength + 1 : // force OOM
 0750                                Array.MaxLength;
 751                        }
 0752                        length = newLength;
 753
 0754                        char[] array = ArrayPool<char>.Shared.Rent(length);
 755                        try
 756                        {
 0757                            if (Enum.TryFormatUnconstrained(value, array, out charsWritten, format))
 758                            {
 0759                                return thisRef.AppendFormatted(array.AsSpan(0, charsWritten));
 760                            }
 0761                        }
 762                        finally
 763                        {
 0764                            ArrayPool<char>.Shared.Return(array);
 0765                        }
 766                    }
 0767                }
 768            }
 769
 770            /// <summary>Handles adding any padding required for aligning a formatted value in an interpolation expressi
 771            /// <param name="startingPos">The position at which the written value started.</param>
 772            /// <param name="alignment">Non-zero minimum number of characters that should be written for this value.  If
 773            private bool TryAppendOrInsertAlignmentIfNeeded(int startingPos, int alignment)
 774            {
 0775                Debug.Assert(startingPos >= 0 && startingPos <= _pos);
 0776                Debug.Assert(alignment != 0);
 777
 0778                int bytesWritten = _pos - startingPos;
 779
 0780                bool leftAlign = false;
 0781                if (alignment < 0)
 782                {
 0783                    leftAlign = true;
 0784                    alignment = -alignment;
 785                }
 786
 0787                int paddingNeeded = alignment - bytesWritten;
 0788                if (paddingNeeded <= 0)
 789                {
 0790                    return true;
 791                }
 792
 0793                if (paddingNeeded <= _destination.Length - _pos)
 794                {
 0795                    if (leftAlign)
 796                    {
 0797                        _destination.Slice(_pos, paddingNeeded).Fill((byte)' ');
 798                    }
 799                    else
 800                    {
 0801                        _destination.Slice(startingPos, bytesWritten).CopyTo(_destination.Slice(startingPos + paddingNee
 0802                        _destination.Slice(startingPos, paddingNeeded).Fill((byte)' ');
 803                    }
 804
 0805                    _pos += paddingNeeded;
 0806                    return true;
 807                }
 808
 0809                return Fail();
 810            }
 811
 812            /// <summary>Marks formatting as having failed and returns false.</summary>
 813            private bool Fail()
 814            {
 0815                _success = false;
 0816                return false;
 817            }
 818        }
 819
 820        /// <summary>
 821        /// Finds the index of the first invalid UTF-8 subsequence.
 822        /// </summary>
 823        /// <param name="value">The <see cref="ReadOnlySpan{T}"/> containing the UTF-8 input text to examine.</param>
 824        /// <returns>The index of the first invalid UTF-8 subsequence, or <c>-1</c> if the entire input is valid.</retur
 825        public static int IndexOfInvalidSubsequence(ReadOnlySpan<byte> value) =>
 0826            Utf8Utility.GetIndexOfFirstInvalidUtf8Sequence(value, out _);
 827#endif
 828
 829        /// <summary>
 830        /// Validates that the value is well-formed UTF-8.
 831        /// </summary>
 832        /// <param name="value">The <see cref="ReadOnlySpan{T}"/> string.</param>
 833        /// <returns><c>true</c> if value is well-formed UTF-8, <c>false</c> otherwise.</returns>
 834        public static bool IsValid(ReadOnlySpan<byte> value) =>
 0835            Utf8Utility.GetIndexOfFirstInvalidUtf8Sequence(value, out _) < 0;
 836    }
 837}
 838

Methods/Properties

FromUtf16(System.ReadOnlySpan`1<System.Char>,System.Span`1<System.Byte>,System.Int32&,System.Int32&,System.Boolean,System.Boolean)
ToUtf16(System.ReadOnlySpan`1<System.Byte>,System.Span`1<System.Char>,System.Int32&,System.Int32&,System.Boolean,System.Boolean)
ToUtf16PreservingReplacement(System.ReadOnlySpan`1<System.Byte>,System.Span`1<System.Char>,System.Int32&,System.Int32&,System.Boolean,System.Boolean)
TryWrite(System.Span`1<System.Byte>,System.Text.Unicode.Utf8/TryWriteInterpolatedStringHandler&,System.Int32&)
TryWrite(System.Span`1<System.Byte>,System.IFormatProvider,System.Text.Unicode.Utf8/TryWriteInterpolatedStringHandler&,System.Int32&)
.ctor(System.Int32,System.Int32,System.Span`1<System.Byte>,System.Boolean&)
.ctor(System.Int32,System.Int32,System.Span`1<System.Byte>,System.IFormatProvider,System.Boolean&)
AppendLiteral(System.String)
AppendFormatted(T)
AppendFormatted(T,System.String)
AppendFormatted(T,System.Int32)
AppendFormatted(T,System.Int32,System.String)
AppendFormatted(System.ReadOnlySpan`1<System.Char>)
AppendFormatted(System.ReadOnlySpan`1<System.Char>,System.Int32,System.String)
AppendFormatted(System.ReadOnlySpan`1<System.Byte>)
AppendFormatted(System.ReadOnlySpan`1<System.Byte>,System.Int32,System.String)
AppendFormatted(System.String)
AppendFormatted(System.String,System.Int32,System.String)
AppendFormatted(System.Object,System.Int32,System.String)
AppendCustomFormatter(T,System.String)
AppendSpanFormattable(T,System.String)
GrowAndAppendFormatted(System.Text.Unicode.Utf8/TryWriteInterpolatedStringHandler&,T,System.Int32,System.Int32&,System.String)
AppendEnum(T,System.String)
GrowAndAppendFormatted(System.Text.Unicode.Utf8/TryWriteInterpolatedStringHandler&,T,System.Int32,System.Int32&,System.String)
TryAppendOrInsertAlignmentIfNeeded(System.Int32,System.Int32)
Fail()
IndexOfInvalidSubsequence(System.ReadOnlySpan`1<System.Byte>)
IsValid(System.ReadOnlySpan`1<System.Byte>)