Skip to content

Commit 1177698

Browse files
tannergoodingMihaZupandanmoseley
authored
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 <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>
1 parent d51a8d5 commit 1177698

4 files changed

Lines changed: 68 additions & 3 deletions

File tree

src/libraries/System.Private.CoreLib/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,9 @@
26082608
<data name="InvalidOperation_HandleIsNotPinned" xml:space="preserve">
26092609
<value>Handle is not pinned.</value>
26102610
</data>
2611+
<data name="InvalidOperation_InvalidUtf8" xml:space="preserve">
2612+
<value>Formatted string contains characters not representable as valid UTF-8.</value>
2613+
</data>
26112614
<data name="InvalidOperation_HashInsertFailed" xml:space="preserve">
26122615
<value>Hashtable insert failed. Load factor too high. The most common cause is multiple threads writing to the Hashtable simultaneously.</value>
26132616
</data>

src/libraries/System.Private.CoreLib/src/System/Numerics/INumberBase.cs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public interface INumberBase<TSelf>
2727
ISubtractionOperators<TSelf, TSelf, TSelf>,
2828
IUnaryPlusOperators<TSelf, TSelf>,
2929
IUnaryNegationOperators<TSelf, TSelf>,
30+
IUtf8SpanFormattable,
3031
IUtf8SpanParsable<TSelf>
3132
where TSelf : INumberBase<TSelf>?
3233
{
@@ -297,7 +298,7 @@ static virtual TSelf Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFor
297298
if (textMaxCharCount < 256)
298299
{
299300
utf16TextArray = null;
300-
utf16Text = stackalloc char[512];
301+
utf16Text = stackalloc char[256];
301302
}
302303
else
303304
{
@@ -425,7 +426,7 @@ static virtual bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IF
425426
if (textMaxCharCount < 256)
426427
{
427428
utf16TextArray = null;
428-
utf16Text = stackalloc char[512];
429+
utf16Text = stackalloc char[256];
429430
}
430431
else
431432
{
@@ -456,6 +457,60 @@ static virtual bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IF
456457
return succeeded;
457458
}
458459

460+
bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
461+
{
462+
char[]? utf16DestinationArray;
463+
scoped Span<char> utf16Destination;
464+
int destinationMaxCharCount = Encoding.UTF8.GetMaxCharCount(utf8Destination.Length);
465+
466+
if (destinationMaxCharCount < 256)
467+
{
468+
utf16DestinationArray = null;
469+
utf16Destination = stackalloc char[256];
470+
}
471+
else
472+
{
473+
utf16DestinationArray = ArrayPool<char>.Shared.Rent(destinationMaxCharCount);
474+
utf16Destination = utf16DestinationArray.AsSpan(0, destinationMaxCharCount);
475+
}
476+
477+
if (!TryFormat(utf16Destination, out int charsWritten, format, provider))
478+
{
479+
if (utf16DestinationArray != null)
480+
{
481+
// Return rented buffers if necessary
482+
ArrayPool<char>.Shared.Return(utf16DestinationArray);
483+
}
484+
485+
bytesWritten = 0;
486+
return false;
487+
}
488+
489+
// Make sure we slice the buffer to just the characters written
490+
utf16Destination = utf16Destination.Slice(0, charsWritten);
491+
492+
OperationStatus utf8DestinationStatus = Utf8.FromUtf16(utf16Destination, utf8Destination, out _, out bytesWritten, replaceInvalidSequences: false);
493+
494+
if (utf16DestinationArray != null)
495+
{
496+
// Return rented buffers if necessary
497+
ArrayPool<char>.Shared.Return(utf16DestinationArray);
498+
}
499+
500+
if (utf8DestinationStatus == OperationStatus.Done)
501+
{
502+
return true;
503+
}
504+
505+
if (utf8DestinationStatus != OperationStatus.DestinationTooSmall)
506+
{
507+
ThrowHelper.ThrowInvalidOperationException_InvalidUtf8();
508+
}
509+
510+
bytesWritten = 0;
511+
return false;
512+
}
513+
459514
static TSelf IUtf8SpanParsable<TSelf>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider)
460515
{
461516
// Convert text using stackalloc for <= 256 characters and ArrayPool otherwise

src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,12 @@ internal static void ThrowArraySegmentCtorValidationFailedExceptions(Array? arra
529529
throw GetArraySegmentCtorValidationFailedException(array, offset, count);
530530
}
531531

532+
[DoesNotReturn]
533+
internal static void ThrowInvalidOperationException_InvalidUtf8()
534+
{
535+
throw new InvalidOperationException(SR.InvalidOperation_InvalidUtf8);
536+
}
537+
532538
[DoesNotReturn]
533539
internal static void ThrowFormatException_BadFormatSpecifier()
534540
{

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10793,7 +10793,7 @@ public partial interface IMultiplyOperators<TSelf, TOther, TResult> where TSelf
1079310793
static virtual TResult operator checked *(TSelf left, TOther right) { throw null; }
1079410794
static abstract TResult operator *(TSelf left, TOther right);
1079510795
}
10796-
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>?
10796+
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>?
1079710797
{
1079810798
static abstract TSelf One { get; }
1079910799
static abstract int Radix { get; }
@@ -10835,6 +10835,7 @@ static virtual TSelf CreateTruncating<TOther>(TOther value)
1083510835
static virtual TSelf Parse(System.ReadOnlySpan<byte> utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; }
1083610836
static abstract TSelf Parse(System.ReadOnlySpan<char> s, System.Globalization.NumberStyles style, System.IFormatProvider? provider);
1083710837
static abstract TSelf Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider);
10838+
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
1083810839
static TSelf System.IUtf8SpanParsable<TSelf>.Parse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider) { throw null; }
1083910840
static bool System.IUtf8SpanParsable<TSelf>.TryParse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result) { throw null; }
1084010841
protected static abstract bool TryConvertFromChecked<TOther>(TOther value, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out TSelf result)

0 commit comments

Comments
 (0)