Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -700,19 +700,19 @@ internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int dig
}
}

// Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99,
// but it can begin with any number of 0s, and thus we may need to check more than two
// Fallback for symbol and any length digits. The digits value must be >= 0 && <= 999_999_999,
// but it can begin with any number of 0s, and thus we may need to check more than 9
// digits. Further, for compat, we need to stop when we hit a null char.
int n = 0;
int i = 1;
while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i]))
{
int temp = (n * 10) + format[i++] - '0';
if (temp < n)
// Check if we are about to overflow past our limit of 9 digits
if (n >= 100_000_000)
{
throw new FormatException(SR.Argument_BadFormatSpecifier);
}
n = temp;
n = ((n * 10) + format[i++] - '0');
}

// If we're at the end of the digits rather than having stopped because we hit something
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2318,19 +2318,19 @@ internal static unsafe char ParseFormatSpecifier(ReadOnlySpan<char> format, out
}
}

// Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99,
// but it can begin with any number of 0s, and thus we may need to check more than two
// Fallback for symbol and any length digits. The digits value must be >= 0 && <= 999_999_999,
// but it can begin with any number of 0s, and thus we may need to check more than 9
// digits. Further, for compat, we need to stop when we hit a null char.
int n = 0;
int i = 1;
while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i]))
{
int temp = ((n * 10) + format[i++] - '0');
if (temp < n)
// Check if we are about to overflow past our limit of 9 digits
if (n >= 100_000_000)
{
throw new FormatException(SR.Argument_BadFormatSpecifier);
}
n = temp;
n = ((n * 10) + format[i++] - '0');
}

// If we're at the end of the digits rather than having stopped because we hit something
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,6 @@ void MultiplyAdd(ref Span<uint> currentBuffer, uint multiplier, uint addValue)
}
}

// This function is consistent with VM\COMNumber.cpp!COMNumber::ParseFormatSpecifier
internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int digits)
{
digits = -1;
Expand All @@ -867,22 +866,23 @@ internal static char ParseFormatSpecifier(ReadOnlySpan<char> format, out int dig
char ch = format[i];
if (char.IsAsciiLetter(ch))
{
// The digits value must be >= 0 && <= 999_999_999,
// but it can begin with any number of 0s, and thus we may need to check more than 9
// digits. Further, for compat, we need to stop when we hit a null char.
i++;
int n = -1;

if (i < format.Length && char.IsAsciiDigit(format[i]))
int n = 0;
while ((uint)i < (uint)format.Length && char.IsAsciiDigit(format[i]))
{
n = format[i++] - '0';
while (i < format.Length && char.IsAsciiDigit(format[i]))
// Check if we are about to overflow past our limit of 9 digits
if (n >= 100_000_000)
{
int temp = n * 10 + (format[i++] - '0');
if (temp < n)
{
throw new FormatException(SR.Argument_BadFormatSpecifier);
}
n = temp;
throw new FormatException(SR.Argument_BadFormatSpecifier);
}
n = ((n * 10) + format[i++] - '0');
}

// If we're at the end of the digits rather than having stopped because we hit something
// other than a digit or overflowed, return the standard format info.
if (i >= format.Length || format[i] == '\0')
{
digits = n;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,46 @@ public static void CustomFormatPerMille()
RunCustomFormatToStringTests(s_random, "#\u2030000000", CultureInfo.CurrentCulture.NumberFormat.NegativeSign, 6, PerMilleSymbolFormatter);
}

[Fact]
public static void ToString_InvalidFormat_ThrowsFormatException()
{
BigInteger b = new BigInteger(123456789000m);

// Format precision limit is 999_999_999 (9 digits). Anything larger should throw.
// Check ParseFormatSpecifier in FormatProvider.Number.cs with `E` format
Assert.Throws<FormatException>(() => b.ToString("E" + int.MaxValue.ToString()));
long intMaxPlus1 = (long)int.MaxValue + 1;
string intMaxPlus1String = intMaxPlus1.ToString();
Assert.Throws<FormatException>(() => b.ToString("E" + intMaxPlus1String));
Assert.Throws<FormatException>(() => b.ToString("E4772185890"));
Assert.Throws<FormatException>(() => b.ToString("E1000000000"));
Assert.Throws<FormatException>(() => b.ToString("E000001000000000"));

// Check ParseFormatSpecifier in BigNumber.cs with `G` format
Assert.Throws<FormatException>(() => b.ToString("G" + int.MaxValue.ToString()));
Assert.Throws<FormatException>(() => b.ToString("G" + intMaxPlus1String));
Assert.Throws<FormatException>(() => b.ToString("G4772185890"));
Assert.Throws<FormatException>(() => b.ToString("G1000000000"));
Assert.Throws<FormatException>(() => b.ToString("G000001000000000"));
}

[Fact]
[OuterLoop("Takes a long time, allocates a lot of memory")]
public static void ToString_ValidLargeFormat()
{
BigInteger b = new BigInteger(123456789000m);

// Format precision limit is 999_999_999 (9 digits). Anything larger should throw.

// Check ParseFormatSpecifier in FormatProvider.Number.cs with `E` format
b.ToString("E999999999"); // Should not throw
b.ToString("E00000999999999"); // Should not throw

// Check ParseFormatSpecifier in BigNumber.cs with `G` format
b.ToString("G999999999"); // Should not throw
b.ToString("G00000999999999"); // Should not throw
}

private static void RunSimpleProviderToStringTests(Random random, string format, NumberFormatInfo provider, int precision, StringFormatter formatter)
{
string test;
Expand Down
18 changes: 18 additions & 0 deletions src/libraries/System.Runtime/tests/System/DoubleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -827,9 +827,27 @@ public static void ToString_InvalidFormat_ThrowsFormatException()
double d = 123.0;
Assert.Throws<FormatException>(() => d.ToString("Y")); // Invalid format
Assert.Throws<FormatException>(() => d.ToString("Y", null)); // Invalid format

// Format precision limit is 999_999_999 (9 digits). Anything larger should throw.
Assert.Throws<FormatException>(() => d.ToString("E" + int.MaxValue.ToString()));
long intMaxPlus1 = (long)int.MaxValue + 1;
string intMaxPlus1String = intMaxPlus1.ToString();
Assert.Throws<FormatException>(() => d.ToString("E" + intMaxPlus1String));
Assert.Throws<FormatException>(() => d.ToString("E4772185890"));
Assert.Throws<FormatException>(() => d.ToString("E1000000000"));
Assert.Throws<FormatException>(() => d.ToString("E000001000000000"));
}

[Fact]
[OuterLoop("Takes a long time, allocates a lot of memory")]
public static void ToString_ValidLargeFormat()
{
double d = 123.0;

// Format precision limit is 999_999_999 (9 digits). Anything larger should throw.
d.ToString("E999999999"); // Should not throw
d.ToString("E00000999999999"); // Should not throw

}

[Theory]
Expand Down