From 11776982ccea895861e342863d647cc31b37af6f Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 13 Jul 2023 22:06:07 -0700 Subject: [PATCH] Ensure that INumberBase implements IUtf8SpanFormattable (#88840) * Ensure that INumberBase implements IUtf8SpanFormattable * Ensure we return the rented buffers in the IUtf8SpanFormattable.TryFormat DIM * Remember to slice the utf16Destination buffer and ensure we throw if we couldn't transcode back to valid UTF-8 in the DIM * Update src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs Co-authored-by: Miha Zupan * Update src/libraries/System.Private.CoreLib/src/Resources/Strings.resx Co-authored-by: Dan Moseley --------- Co-authored-by: Miha Zupan Co-authored-by: Dan Moseley --- .../src/Resources/Strings.resx | 3 ++ .../src/System/Numerics/INumberBase.cs | 59 +++++++++++++++++++++- .../src/System/ThrowHelper.cs | 6 +++ src/libraries/System.Runtime/ref/System.Runtime.cs | 3 +- 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 60ef1fb..79f75cc 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -2608,6 +2608,9 @@ Handle is not pinned. + + Formatted string contains characters not representable as valid UTF-8. + Hashtable insert failed. Load factor too high. The most common cause is multiple threads writing to the Hashtable simultaneously. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs index 4d943b1..4368605 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs @@ -27,6 +27,7 @@ namespace System.Numerics ISubtractionOperators, IUnaryPlusOperators, IUnaryNegationOperators, + IUtf8SpanFormattable, IUtf8SpanParsable where TSelf : INumberBase? { @@ -297,7 +298,7 @@ namespace System.Numerics if (textMaxCharCount < 256) { utf16TextArray = null; - utf16Text = stackalloc char[512]; + utf16Text = stackalloc char[256]; } else { @@ -425,7 +426,7 @@ namespace System.Numerics if (textMaxCharCount < 256) { utf16TextArray = null; - utf16Text = stackalloc char[512]; + utf16Text = stackalloc char[256]; } else { @@ -456,6 +457,60 @@ namespace System.Numerics return succeeded; } + bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) + { + char[]? utf16DestinationArray; + scoped Span utf16Destination; + int destinationMaxCharCount = Encoding.UTF8.GetMaxCharCount(utf8Destination.Length); + + if (destinationMaxCharCount < 256) + { + utf16DestinationArray = null; + utf16Destination = stackalloc char[256]; + } + else + { + utf16DestinationArray = ArrayPool.Shared.Rent(destinationMaxCharCount); + utf16Destination = utf16DestinationArray.AsSpan(0, destinationMaxCharCount); + } + + if (!TryFormat(utf16Destination, out int charsWritten, format, provider)) + { + if (utf16DestinationArray != null) + { + // Return rented buffers if necessary + ArrayPool.Shared.Return(utf16DestinationArray); + } + + bytesWritten = 0; + return false; + } + + // Make sure we slice the buffer to just the characters written + utf16Destination = utf16Destination.Slice(0, charsWritten); + + OperationStatus utf8DestinationStatus = Utf8.FromUtf16(utf16Destination, utf8Destination, out _, out bytesWritten, replaceInvalidSequences: false); + + if (utf16DestinationArray != null) + { + // Return rented buffers if necessary + ArrayPool.Shared.Return(utf16DestinationArray); + } + + if (utf8DestinationStatus == OperationStatus.Done) + { + return true; + } + + if (utf8DestinationStatus != OperationStatus.DestinationTooSmall) + { + ThrowHelper.ThrowInvalidOperationException_InvalidUtf8(); + } + + bytesWritten = 0; + return false; + } + static TSelf IUtf8SpanParsable.Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) { // Convert text using stackalloc for <= 256 characters and ArrayPool otherwise diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 71e2904..2d47005 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -530,6 +530,12 @@ namespace System } [DoesNotReturn] + internal static void ThrowInvalidOperationException_InvalidUtf8() + { + throw new InvalidOperationException(SR.InvalidOperation_InvalidUtf8); + } + + [DoesNotReturn] internal static void ThrowFormatException_BadFormatSpecifier() { throw new FormatException(SR.Argument_BadFormatSpecifier); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 08e6907..58438a4 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10793,7 +10793,7 @@ namespace System.Numerics static virtual TResult operator checked *(TSelf left, TOther right) { throw null; } static abstract TResult operator *(TSelf left, TOther right); } - public partial interface INumberBase : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanParsable where TSelf : System.Numerics.INumberBase? + public partial interface INumberBase : System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators, System.IUtf8SpanFormattable, System.IUtf8SpanParsable where TSelf : System.Numerics.INumberBase? { static abstract TSelf One { get; } static abstract int Radix { get; } @@ -10835,6 +10835,7 @@ namespace System.Numerics static virtual TSelf Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } static abstract TSelf Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider); static abstract TSelf Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider); + bool System.IUtf8SpanFormattable.TryFormat(System.Span utf8Destination, out int bytesWritten, System.ReadOnlySpan format, System.IFormatProvider? provider) { throw null; } static TSelf System.IUtf8SpanParsable.Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } static bool System.IUtf8SpanParsable.TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) { throw null; } protected static abstract bool TryConvertFromChecked(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) -- 2.7.4