Ensure that INumberBase implements IUtf8SpanFormattable (#88840)
authorTanner Gooding <tagoo@outlook.com>
Fri, 14 Jul 2023 05:06:07 +0000 (22:06 -0700)
committerGitHub <noreply@github.com>
Fri, 14 Jul 2023 05:06:07 +0000 (22:06 -0700)
* 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 <mihazupan.zupan1@gmail.com>
* Update src/libraries/System.Private.CoreLib/src/Resources/Strings.resx

Co-authored-by: Dan Moseley <danmose@microsoft.com>
---------

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
Co-authored-by: Dan Moseley <danmose@microsoft.com>
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs
src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs
src/libraries/System.Runtime/ref/System.Runtime.cs

index 60ef1fb..79f75cc 100644 (file)
   <data name="InvalidOperation_HandleIsNotPinned" xml:space="preserve">
     <value>Handle is not pinned.</value>
   </data>
+  <data name="InvalidOperation_InvalidUtf8" xml:space="preserve">
+    <value>Formatted string contains characters not representable as valid UTF-8.</value>
+  </data>
   <data name="InvalidOperation_HashInsertFailed" xml:space="preserve">
     <value>Hashtable insert failed. Load factor too high. The most common cause is multiple threads writing to the Hashtable simultaneously.</value>
   </data>
index 4d943b1..4368605 100644 (file)
@@ -27,6 +27,7 @@ namespace System.Numerics
           ISubtractionOperators<TSelf, TSelf, TSelf>,
           IUnaryPlusOperators<TSelf, TSelf>,
           IUnaryNegationOperators<TSelf, TSelf>,
+          IUtf8SpanFormattable,
           IUtf8SpanParsable<TSelf>
         where TSelf : INumberBase<TSelf>?
     {
@@ -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<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
+        {
+            char[]? utf16DestinationArray;
+            scoped Span<char> utf16Destination;
+            int destinationMaxCharCount = Encoding.UTF8.GetMaxCharCount(utf8Destination.Length);
+
+            if (destinationMaxCharCount < 256)
+            {
+                utf16DestinationArray = null;
+                utf16Destination = stackalloc char[256];
+            }
+            else
+            {
+                utf16DestinationArray = ArrayPool<char>.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<char>.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<char>.Shared.Return(utf16DestinationArray);
+            }
+
+            if (utf8DestinationStatus == OperationStatus.Done)
+            {
+                return true;
+            }
+
+            if (utf8DestinationStatus != OperationStatus.DestinationTooSmall)
+            {
+                ThrowHelper.ThrowInvalidOperationException_InvalidUtf8();
+            }
+
+            bytesWritten = 0;
+            return false;
+        }
+
         static TSelf IUtf8SpanParsable<TSelf>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider)
         {
             // Convert text using stackalloc for <= 256 characters and ArrayPool otherwise
index 71e2904..2d47005 100644 (file)
@@ -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);
index 08e6907..58438a4 100644 (file)
@@ -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<TSelf> : System.IEquatable<TSelf>, System.IFormattable, System.IParsable<TSelf>, System.ISpanFormattable, System.ISpanParsable<TSelf>, System.Numerics.IAdditionOperators<TSelf, TSelf, TSelf>, System.Numerics.IAdditiveIdentity<TSelf, TSelf>, System.Numerics.IDecrementOperators<TSelf>, System.Numerics.IDivisionOperators<TSelf, TSelf, TSelf>, System.Numerics.IEqualityOperators<TSelf, TSelf, bool>, System.Numerics.IIncrementOperators<TSelf>, System.Numerics.IMultiplicativeIdentity<TSelf, TSelf>, System.Numerics.IMultiplyOperators<TSelf, TSelf, TSelf>, System.Numerics.ISubtractionOperators<TSelf, TSelf, TSelf>, System.Numerics.IUnaryNegationOperators<TSelf, TSelf>, System.Numerics.IUnaryPlusOperators<TSelf, TSelf>, System.IUtf8SpanParsable<TSelf> where TSelf : System.Numerics.INumberBase<TSelf>?
+    public partial interface INumberBase<TSelf> : System.IEquatable<TSelf>, System.IFormattable, System.IParsable<TSelf>, System.ISpanFormattable, System.ISpanParsable<TSelf>, System.Numerics.IAdditionOperators<TSelf, TSelf, TSelf>, System.Numerics.IAdditiveIdentity<TSelf, TSelf>, System.Numerics.IDecrementOperators<TSelf>, System.Numerics.IDivisionOperators<TSelf, TSelf, TSelf>, System.Numerics.IEqualityOperators<TSelf, TSelf, bool>, System.Numerics.IIncrementOperators<TSelf>, System.Numerics.IMultiplicativeIdentity<TSelf, TSelf>, System.Numerics.IMultiplyOperators<TSelf, TSelf, TSelf>, System.Numerics.ISubtractionOperators<TSelf, TSelf, TSelf>, System.Numerics.IUnaryNegationOperators<TSelf, TSelf>, System.Numerics.IUnaryPlusOperators<TSelf, TSelf>, System.IUtf8SpanFormattable, System.IUtf8SpanParsable<TSelf> where TSelf : System.Numerics.INumberBase<TSelf>?
     {
         static abstract TSelf One { get; }
         static abstract int Radix { get; }
@@ -10835,6 +10835,7 @@ namespace System.Numerics
         static virtual TSelf Parse(System.ReadOnlySpan<byte> utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; }
         static abstract TSelf Parse(System.ReadOnlySpan<char> 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<byte> utf8Destination, out int bytesWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
         static TSelf System.IUtf8SpanParsable<TSelf>.Parse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider) { throw null; }
         static bool System.IUtf8SpanParsable<TSelf>.TryParse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) { throw null; }
         protected static abstract bool TryConvertFromChecked<TOther>(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result)