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
22 changes: 22 additions & 0 deletions Lib/test/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,28 @@ def test_precision_c_limits(self):
with self.assertRaises(ValueError) as cm:
format(c, ".%sf" % (INT_MAX + 1))

@support.cpython_only
def test_precision_near_int_max(self):
# gh-158446: Precisions just below INT_MAX are rejected before any
# output buffer size is computed from them.
_testcapi = import_module("_testcapi")
INT_MAX = _testcapi.INT_MAX

f = 1e300
c = complex(f)
for prec in (INT_MAX, INT_MAX - 1023):
for code in "feg":
spec = ".%d%s" % (prec, code)
with self.subTest(spec=spec):
with self.assertRaises(ValueError):
format(f, spec)
with self.assertRaises(ValueError):
format(c, spec)
with self.assertRaises(ValueError):
("%" + spec) % f
with self.assertRaises(ValueError):
("%" + spec).encode() % f

def test_g_format_has_no_trailing_zeros(self):
# regression test for bugs.python.org/issue40780
self.assertEqual("%.3g" % 1505.0, "1.5e+03")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix a crash or incorrect output that could occur when formatting a
:class:`float` or :class:`complex` with a precision close to the platform's
``INT_MAX``. :c:func:`PyOS_double_to_string` now raises :exc:`ValueError` for
any precision of that magnitude, regardless of presentation type or value, as
the format string parsers already did for precisions above ``INT_MAX``.
16 changes: 15 additions & 1 deletion Python/dtoa.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
* 8. A corner case where _Py_dg_dtoa didn't strip trailing zeros has been
* fixed. (bugs.python.org/issue40780)
*
* 9. _Py_dg_dtoa clamps ndigits in modes 3 and 5 so that its buffer size
* arithmetic cannot exceed the int range, and rv_alloc's size doubling
* uses size_t. (gh-158446)
*
***************************************************************/

/* Please send bug reports for the original dtoa.c code to David M. Gay (dmg
Expand Down Expand Up @@ -2110,7 +2114,8 @@ _Py_dg_strtod(const char *s00, char **se)
static char *
rv_alloc(int i)
{
int j, k, *r;
int k, *r;
size_t j; /* size_t so that j <<= 1 cannot overflow for i near INT_MAX */

j = sizeof(ULong);
for(k = 0;
Expand Down Expand Up @@ -2374,6 +2379,15 @@ _Py_dg_dtoa(double dd, int mode, int ndigits,
leftright = 0;
_Py_FALLTHROUGH;
case 5:
/* -330 < k < 330 for any finite nonzero double. Clamp ndigits so
that ndigits + k + 1 stays within int range; no double has
anywhere near this many decimal digits so the digits returned
are unaffected (*decpt saturates in the no_digits case). Same
bound as DOUBLE_TO_STRING_PRECISION_MAX in pystrtod.c. */
if (ndigits > INT_MAX - 1024)
ndigits = INT_MAX - 1024;
else if (ndigits < -(INT_MAX - 1024))
ndigits = -(INT_MAX - 1024);
i = ndigits + k + 1;
ilim = i;
ilim1 = i - 1;
Expand Down
23 changes: 23 additions & 0 deletions Python/pystrtod.c
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,15 @@ _Py_string_to_number_with_underscores(
return NULL;
}

/* Largest precision magnitude accepted by PyOS_double_to_string(). The
output buffer sizes computed below and within _Py_dg_dtoa() use int and
Py_ssize_t arithmetic on roughly precision + (digits before the point, at
most DBL_MAX_10_EXP + 1 == 309) + a few bytes of sign, point and exponent.
Staying this far inside the int range keeps all of those sums in range.
(Only C callers can pass a negative precision.) _Py_dg_dtoa() applies
the same bound to its ndigits argument. */
#define DOUBLE_TO_STRING_PRECISION_MAX (INT_MAX - 1024)

#if _PY_SHORT_FLOAT_REPR == 0

/* Given a string that may have a decimal point in the current
Expand Down Expand Up @@ -766,6 +775,13 @@ char * PyOS_double_to_string(double val,
int t, exp;
int upper = 0;

if (precision > DOUBLE_TO_STRING_PRECISION_MAX
|| precision < -DOUBLE_TO_STRING_PRECISION_MAX)
{
PyErr_SetString(PyExc_ValueError, "precision too big");
return NULL;
}

/* Validate format_code, and map upper and lower case */
switch (format_code) {
case 'e': /* exponent */
Expand Down Expand Up @@ -1227,6 +1243,13 @@ char * PyOS_double_to_string(double val,
const char * const *float_strings = lc_float_strings;
int mode;

if (precision > DOUBLE_TO_STRING_PRECISION_MAX
|| precision < -DOUBLE_TO_STRING_PRECISION_MAX)
{
PyErr_SetString(PyExc_ValueError, "precision too big");
return NULL;
}

/* Validate format_code, and map upper and lower case. Compute the
mode and make any adjustments as needed. */
switch (format_code) {
Expand Down
Loading