diff --git a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs index 0e11b9b186d42a..688e587609dee2 100644 --- a/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs +++ b/src/libraries/Common/src/System/Globalization/FormatProvider.Number.cs @@ -515,17 +515,26 @@ private static unsafe bool ParseNumber(ref char* str, char* strEnd, NumberStyles int exp = 0; do { - exp = exp * 10 + (ch - '0'); - ch = ++p < strEnd ? *p : '\0'; - if (exp > 1000) + // Check if we are about to overflow past our limit of 9 digits + if (exp >= 100_000_000) { - exp = 9999; + // Set exp to Int.MaxValue to signify the requested exponent is too large. This will lead to an OverflowException later. + exp = int.MaxValue; + number.scale = 0; + + // Finish parsing the number, a FormatException could still occur later on. while (char.IsAsciiDigit(ch)) { ch = ++p < strEnd ? *p : '\0'; } + break; } + + exp = exp * 10 + (ch - '0'); + ch = ++p < strEnd ? *p : '\0'; + } while (char.IsAsciiDigit(ch)); + if (negExp) { exp = -exp; diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs index 6f348d3803785a..4956fac177a403 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigInteger.cs @@ -684,7 +684,7 @@ public static bool TryParse([NotNullWhen(true)] string? value, out BigInteger re public static bool TryParse([NotNullWhen(true)] string? value, NumberStyles style, IFormatProvider? provider, out BigInteger result) { - return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result); + return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result) == BigNumber.ParsingStatus.OK; } public static BigInteger Parse(ReadOnlySpan value, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) @@ -699,7 +699,7 @@ public static bool TryParse(ReadOnlySpan value, out BigInteger result) public static bool TryParse(ReadOnlySpan value, NumberStyles style, IFormatProvider? provider, out BigInteger result) { - return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result); + return BigNumber.TryParseBigInteger(value, style, NumberFormatInfo.GetInstance(provider), out result) == BigNumber.ParsingStatus.OK; } public static int Compare(BigInteger left, BigInteger right) diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs index ff6fbca60184d9..83fad3f14b8894 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -288,6 +288,22 @@ internal static class BigNumber | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier); private static readonly uint[] s_uint32PowersOfTen = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + internal enum ParsingStatus + { + OK, + Failed, + Overflow + } + + [DoesNotReturn] + internal static void ThrowOverflowOrFormatException(ParsingStatus status) => throw GetException(status); + + private static Exception GetException(ParsingStatus status) + { + return status == ParsingStatus.Failed + ? new FormatException(SR.Overflow_ParseBigInteger) + : new OverflowException(SR.Overflow_ParseBigInteger); + } private struct BigNumberBuffer { @@ -325,18 +341,18 @@ internal static bool TryValidateParseStyleInteger(NumberStyles style, [NotNullWh return true; } - internal static bool TryParseBigInteger(string? value, NumberStyles style, NumberFormatInfo info, out BigInteger result) + internal static ParsingStatus TryParseBigInteger(string? value, NumberStyles style, NumberFormatInfo info, out BigInteger result) { if (value == null) { result = default; - return false; + return ParsingStatus.Failed; } return TryParseBigInteger(value.AsSpan(), style, info, out result); } - internal static bool TryParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) + internal static ParsingStatus TryParseBigInteger(ReadOnlySpan value, NumberStyles style, NumberFormatInfo info, out BigInteger result) { if (!TryValidateParseStyleInteger(style, out ArgumentException? e)) { @@ -347,7 +363,7 @@ internal static bool TryParseBigInteger(ReadOnlySpan value, NumberStyles s if (!FormatProvider.TryStringToBigInteger(value, style, info, bigNumber.digits, out bigNumber.precision, out bigNumber.scale, out bigNumber.sign)) { result = default; - return false; + return ParsingStatus.Failed; } if ((style & NumberStyles.AllowHexSpecifier) != 0) @@ -373,19 +389,22 @@ internal static BigInteger ParseBigInteger(ReadOnlySpan value, NumberStyle { throw e; } - if (!TryParseBigInteger(value, style, info, out BigInteger result)) + + ParsingStatus status = TryParseBigInteger(value, style, info, out BigInteger result); + if (status != ParsingStatus.OK) { - throw new FormatException(SR.Overflow_ParseBigInteger); + ThrowOverflowOrFormatException(status); } + return result; } - private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) + private static ParsingStatus HexNumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) { if (number.digits == null || number.digits.Length == 0) { result = default; - return false; + return ParsingStatus.Failed; } const int DigitsPerBlock = 8; @@ -480,7 +499,7 @@ private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInt } result = new BigInteger(sign, bits); - return true; + return ParsingStatus.OK; } finally { @@ -499,7 +518,7 @@ private static bool HexNumberToBigInteger(ref BigNumberBuffer number, out BigInt // a divide-and-conquer algorithm with a running time of O(NlogN). // private static int s_naiveThreshold = 20000; - private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) + private static ParsingStatus NumberToBigInteger(ref BigNumberBuffer number, out BigInteger result) { int currentBufferSize = 0; @@ -510,10 +529,17 @@ private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigIntege const uint TenPowMaxPartial = 1000000000; int[]? arrayFromPoolForResultBuffer = null; + + if (numberScale == int.MaxValue) + { + result = default; + return ParsingStatus.Overflow; + } + if (numberScale < 0) { result = default; - return false; + return ParsingStatus.Failed; } try @@ -535,7 +561,7 @@ private static bool NumberToBigInteger(ref BigNumberBuffer number, out BigIntege } } - bool Naive(ref BigNumberBuffer number, out BigInteger result) + ParsingStatus Naive(ref BigNumberBuffer number, out BigInteger result) { Span stackBuffer = stackalloc uint[BigIntegerCalculator.StackAllocThreshold]; Span currentBuffer = stackBuffer; @@ -547,7 +573,7 @@ bool Naive(ref BigNumberBuffer number, out BigInteger result) if (!ProcessChunk(digitsChunk.Span, ref currentBuffer)) { result = default; - return false; + return ParsingStatus.Failed; } } @@ -557,7 +583,7 @@ bool Naive(ref BigNumberBuffer number, out BigInteger result) } result = NumberBufferToBigInteger(currentBuffer, number.sign); - return true; + return ParsingStatus.OK; bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) { @@ -619,7 +645,7 @@ bool ProcessChunk(ReadOnlySpan chunkDigits, ref Span currentBuffer) } } - bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result) + ParsingStatus DivideAndConquer(ref BigNumberBuffer number, out BigInteger result) { Span currentBuffer; int[]? arrayFromPoolForMultiplier = null; @@ -674,7 +700,7 @@ bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result) if (digitChar != '0') { result = default; - return false; + return ParsingStatus.Failed; } } } @@ -776,7 +802,7 @@ bool DivideAndConquer(ref BigNumberBuffer number, out BigInteger result) ArrayPool.Shared.Return(arrayFromPoolForMultiplier); } } - return true; + return ParsingStatus.OK; } BigInteger NumberBufferToBigInteger(Span currentBuffer, bool signa) diff --git a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs index 78b47e967784c0..f1f6071127fc51 100644 --- a/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntegerToStringTests.cs @@ -439,6 +439,42 @@ public static void CustomFormatPerMille() RunCustomFormatToStringTests(s_random, "#\u2030000000", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 6, PerMilleSymbolFormatter); } + public static IEnumerable RunFormatScientificNotationToBigIntegerAndViceVersaData() + { + yield return new object[] { "1E+1000", "1E+1000" }; + yield return new object[] { "1E+1001", "1E+1001" }; + yield return new object[] { "1E+10001", "1E+10001" }; + yield return new object[] { "1E+100001", "1E+100001" }; + yield return new object[] { "1E+99999", "1E+99999" }; + } + + [Theory] + [MemberData(nameof(RunFormatScientificNotationToBigIntegerAndViceVersaData))] + public static void RunFormatScientificNotationToBigIntegerAndViceVersa(string testingValue, string expectedResult) + { + BigInteger parsedValue; + string actualResult; + + parsedValue = BigInteger.Parse(testingValue, NumberStyles.AllowExponent); + actualResult = parsedValue.ToString("E0"); + + Assert.Equal(expectedResult, actualResult); + } + + public static IEnumerable RunFormatScientificNotationToBigIntegerThrowsExceptionData() + { + yield return new object[] { "1E+1000000000" }; + yield return new object[] { "1E+2147483647" }; + yield return new object[] { "1E+21474836492" }; + } + + [Theory] + [MemberData(nameof(RunFormatScientificNotationToBigIntegerThrowsExceptionData))] + public static void RunFormatScientificNotationToBigIntegerThrowsException(string testingValue) + { + Assert.Throws(() => BigInteger.Parse(testingValue, NumberStyles.AllowExponent)); + } + [Fact] public static void ToString_InvalidFormat_ThrowsFormatException() {