Index: Python/ast.c =================================================================== --- Python/ast.c (revision 72233) +++ Python/ast.c (working copy) @@ -3162,18 +3162,18 @@ #ifndef WITHOUT_COMPLEX if (imflag) { compl.real = 0.; - PyFPE_START_PROTECT("atof", return 0) - compl.imag = PyOS_ascii_atof(s); - PyFPE_END_PROTECT(c) - return PyComplex_FromCComplex(compl); + compl.imag = PyOS_string_to_double(s, (char **)&end, NULL); + if (compl.imag == -1.0 && PyErr_Occurred()) + return NULL; + return PyComplex_FromCComplex(compl); } else #endif { - PyFPE_START_PROTECT("atof", return 0) - dx = PyOS_ascii_atof(s); - PyFPE_END_PROTECT(dx) - return PyFloat_FromDouble(dx); + dx = PyOS_string_to_double(s, NULL, NULL); + if (dx == -1.0 && PyErr_Occurred()) + return NULL; + return PyFloat_FromDouble(dx); } } Index: Python/dtoa.c =================================================================== --- Python/dtoa.c (revision 72233) +++ Python/dtoa.c (working copy) @@ -61,6 +61,9 @@ * that hasn't been MALLOC'ed, private_mem should only be used when k <= * Kmax. * + * 7. _Py_dg_strtod has been modified so that it doesn't accept strings with + * leading whitespace + * ***************************************************************/ /* Please send bug reports for the original dtoa.c code to David M. Gay (dmg @@ -1355,6 +1358,7 @@ /* no break */ case 0: goto ret0; + /* modify original dtoa.c so that it doesn't accept leading whitespace case '\t': case '\n': case '\v': @@ -1362,6 +1366,7 @@ case '\r': case ' ': continue; + */ default: goto break2; } Index: Python/marshal.c =================================================================== --- Python/marshal.c (revision 72233) +++ Python/marshal.c (working copy) @@ -670,18 +670,17 @@ { char buf[256]; double dx; + retval = NULL; n = r_byte(p); if (n == EOF || r_string(buf, (int)n, p) != n) { PyErr_SetString(PyExc_EOFError, "EOF read where object expected"); - retval = NULL; break; } buf[n] = '\0'; - retval = NULL; - PyFPE_START_PROTECT("atof", break) - dx = PyOS_ascii_atof(buf); - PyFPE_END_PROTECT(dx) + dx = PyOS_string_to_double(buf, NULL, NULL); + if (dx == -1.0 && PyErr_Occurred()) + break; retval = PyFloat_FromDouble(dx); break; } @@ -710,29 +709,27 @@ { char buf[256]; Py_complex c; + retval = NULL; n = r_byte(p); if (n == EOF || r_string(buf, (int)n, p) != n) { PyErr_SetString(PyExc_EOFError, "EOF read where object expected"); - retval = NULL; break; } buf[n] = '\0'; - retval = NULL; - PyFPE_START_PROTECT("atof", break;) - c.real = PyOS_ascii_atof(buf); - PyFPE_END_PROTECT(c) + c.real = PyOS_string_to_double(buf, NULL, NULL); + if (c.real == -1.0 && PyErr_Occurred()) + break; n = r_byte(p); if (n == EOF || r_string(buf, (int)n, p) != n) { PyErr_SetString(PyExc_EOFError, "EOF read where object expected"); - retval = NULL; break; } buf[n] = '\0'; - PyFPE_START_PROTECT("atof", break) - c.imag = PyOS_ascii_atof(buf); - PyFPE_END_PROTECT(c) + c.imag = PyOS_string_to_double(buf, NULL, NULL); + if (c.imag == -1.0 && PyErr_Occurred()) + break; retval = PyComplex_FromCComplex(c); break; } Index: Python/pystrtod.c =================================================================== --- Python/pystrtod.c (revision 72233) +++ Python/pystrtod.c (working copy) @@ -35,7 +35,7 @@ #ifndef PY_NO_SHORT_FLOAT_REPR double -PyOS_ascii_strtod(const char *nptr, char **endptr) +_PyOS_ascii_strtod(const char *nptr, char **endptr) { double result; _Py_SET_53BIT_PRECISION_HEADER; @@ -64,7 +64,7 @@ */ double -PyOS_ascii_strtod(const char *nptr, char **endptr) +_PyOS_ascii_strtod(const char *nptr, char **endptr) { char *fail_pos; double val = -1.0; @@ -92,15 +92,10 @@ and underflows */ errno = 0; - /* We process any leading whitespace and the optional sign manually, - then pass the remainder to the system strtod. This ensures that - the result of an underflow has the correct sign. (bug #1725) */ - + /* We process the optional sign manually, then pass the remainder to + the system strtod. This ensures that the result of an underflow + has the correct sign. (bug #1725) */ p = nptr; - /* Skip leading space */ - while (Py_ISSPACE(*p)) - p++; - /* Process leading sign, if present */ if (*p == '-') { negate = 1; @@ -185,8 +180,7 @@ copy = (char *)PyMem_MALLOC(end - digits_pos + 1 + decimal_point_len); if (copy == NULL) { - if (endptr) - *endptr = (char *)nptr; + *endptr = (char *)nptr; errno = ENOMEM; return val; } @@ -227,15 +221,12 @@ got_val: if (negate && fail_pos != nptr) val = -val; + *endptr = fail_pos; - if (endptr) - *endptr = fail_pos; - return val; invalid_string: - if (endptr) - *endptr = (char*)nptr; + *endptr = (char*)nptr; errno = EINVAL; return -1.0; } @@ -243,12 +234,108 @@ #endif double +PyOS_ascii_strtod(const char *nptr, char **endptr) +{ + char *fail_pos; + const char *p; + double x; + + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "PyOS_ascii_strtod and PyOS_ascii_atof are " + "deprecated. Use PyOS_string_to_double " + "instead.", 1) < 0) + return -1.0; + + /* _PyOS_ascii_strtod already does everything that we want, + except that it doesn't parse leading whitespace */ + p = nptr; + while (Py_ISSPACE(*p)) + p++; + x = _PyOS_ascii_strtod(p, &fail_pos); + if (fail_pos == p) + fail_pos = (char *)nptr; + if (endptr) + *endptr = (char *)fail_pos; + return x; +} + +double PyOS_ascii_atof(const char *nptr) { return PyOS_ascii_strtod(nptr, NULL); } +/* PyOS_string_to_double is the recommended replacement for PyOS_ascii_strtod + and PyOS_ascii_atof. It converts a null-terminated byte string s + (interpreted as a string of ASCII characters) to a float. The string + should not have leading or trailing whitespace (in contrast, + PyOS_ascii_strtod allows leading whitespace but not trailing whitespace). + The conversion is independent of the current locale. + If endptr is NULL, try to convert the whole string. Raise ValueError and + return -1.0 if the string is not a valid representation of a floating-point + number. + + If endptr is non-NULL, try to convert as much of the string as possible. + If no initial segment of the string is the valid representation of a + floating-point number then *endptr is set to point to the beginning of the + string, -1.0 is returned and again ValueError is raised. + + On overflow (e.g., when trying to convert '1e500' on an IEEE 754 machine), + if overflow_exception is NULL then +-Py_HUGE_VAL is returned, and no Python + exception is raised. Otherwise, overflow_exception should point to a + a Python exception, this exception will be raised, -1.0 will be returned, + and *endptr will point just past the end of the converted value. + + If any other failure occurs (for example lack of memory), -1.0 is returned + and the appropriate Python exception will have been set. +*/ + +double +PyOS_string_to_double(const char *s, + char **endptr, + PyObject *overflow_exception) +{ + double x, result=-1.0; + char *fail_pos; + char buffer[256]; + + errno = 0; + PyFPE_START_PROTECT("PyOS_string_to_double", return -1.0) + x = _PyOS_ascii_strtod(s, &fail_pos); + PyFPE_END_PROTECT(x) + + if (errno == ENOMEM) { + PyErr_NoMemory(); + fail_pos = (char *)s; + } + else if (!endptr && (fail_pos == s || *fail_pos != '\0')) { + /* endptr is NULL and the string is not a valid float */ + PyOS_snprintf(buffer, sizeof(buffer), + "could not convert string to float: " + "%.200s", s); + PyErr_SetString(PyExc_ValueError, buffer); + } + else if (fail_pos == s) { + PyOS_snprintf(buffer, sizeof(buffer), + "could not convert string to float: " + "%.200s", s); + PyErr_SetString(PyExc_ValueError, buffer); + } + else if (errno == ERANGE && fabs(x) >= 1.0 && overflow_exception) { + PyOS_snprintf(buffer, sizeof(buffer), + "value too large to convert to float: " + "%.200s", s); + PyErr_SetString(overflow_exception, buffer); + } + else + result = x; + + if (endptr != NULL) + *endptr = fail_pos; + return result; +} + /* Given a string that may have a decimal point in the current locale, change it back to a dot. Since the string cannot get longer, no need for a maximum buffer size parameter. */ Index: Include/pystrtod.h =================================================================== --- Include/pystrtod.h (revision 72233) +++ Include/pystrtod.h (working copy) @@ -9,6 +9,9 @@ PyAPI_FUNC(double) PyOS_ascii_strtod(const char *str, char **ptr); PyAPI_FUNC(double) PyOS_ascii_atof(const char *str); PyAPI_FUNC(char *) PyOS_ascii_formatd(char *buffer, size_t buf_len, const char *format, double d); +PyAPI_FUNC(double) PyOS_string_to_double(const char *str, + char **endptr, + PyObject *overflow_exception); /* The caller is responsible for calling PyMem_Free to free the buffer that's is returned. */ Index: Objects/complexobject.c =================================================================== --- Objects/complexobject.c (revision 72233) +++ Objects/complexobject.c (working copy) @@ -799,25 +799,26 @@ */ /* first look for forms starting with */ - errno = 0; - z = PyOS_ascii_strtod(s, &end); - if (end == s && errno == ENOMEM) - return PyErr_NoMemory(); - if (errno == ERANGE && fabs(z) >= 1.0) - goto overflow; - + z = PyOS_string_to_double(s, &end, PyExc_OverflowError); + if (z == -1.0 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_ValueError)) + PyErr_Clear(); + else + return NULL; + } if (end != s) { /* all 4 forms starting with land here */ s = end; if (*s == '+' || *s == '-') { /* j | j */ x = z; - errno = 0; - y = PyOS_ascii_strtod(s, &end); - if (end == s && errno == ENOMEM) - return PyErr_NoMemory(); - if (errno == ERANGE && fabs(y) >= 1.0) - goto overflow; + y = PyOS_string_to_double(s, &end, PyExc_OverflowError); + if (y == -1.0 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_ValueError)) + PyErr_Clear(); + else + return NULL; + } if (end != s) /* j */ s = end; @@ -877,11 +878,6 @@ PyErr_SetString(PyExc_ValueError, "complex() arg is a malformed string"); return NULL; - - overflow: - PyErr_SetString(PyExc_OverflowError, - "complex() arg overflow"); - return NULL; } static PyObject * Index: Objects/floatobject.c =================================================================== --- Objects/floatobject.c (revision 72233) +++ Objects/floatobject.c (working copy) @@ -193,36 +193,20 @@ /* We don't care about overflow or underflow. If the platform * supports them, infinities and signed zeroes (on underflow) are * fine. */ - errno = 0; - PyFPE_START_PROTECT("strtod", goto error) - x = PyOS_ascii_strtod(s, (char **)&end); - PyFPE_END_PROTECT(x) - if (end == s) { - if (errno == ENOMEM) - PyErr_NoMemory(); - else { - PyOS_snprintf(buffer, sizeof(buffer), - "invalid literal for float(): %.200s", s); - PyErr_SetString(PyExc_ValueError, buffer); - } + x = PyOS_string_to_double(s, (char **)&end, NULL); + if (x == -1.0 && PyErr_Occurred()) goto error; - } - /* Since end != s, the platform made *some* kind of sense out - of the input. Trust it. */ while (*end && isspace(Py_CHARMASK(*end))) end++; - if (end != last) { - if (*end == '\0') - PyErr_SetString(PyExc_ValueError, - "null byte in argument for float()"); - else { - PyOS_snprintf(buffer, sizeof(buffer), - "invalid literal for float(): %.200s", s); - PyErr_SetString(PyExc_ValueError, buffer); - } - goto error; + if (end == last) + result = PyFloat_FromDouble(x); + else { + PyOS_snprintf(buffer, sizeof(buffer), + "invalid literal for float(): %.200s", s); + PyErr_SetString(PyExc_ValueError, buffer); + result = NULL; } - result = PyFloat_FromDouble(x); + error: if (s_buffer) PyMem_FREE(s_buffer); Index: Doc/c-api/conversion.rst =================================================================== --- Doc/c-api/conversion.rst (revision 72233) +++ Doc/c-api/conversion.rst (working copy) @@ -62,7 +62,42 @@ See the Unix man page :manpage:`strtod(2)` for details. + .. deprecated:: 3.1 + Use :cfunc:`PyOS_string_to_double` instead. + +.. cfunction:: double PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception) + + Convert a string ``s`` to a :ctype:`double`, raising a Python + exception on failure. The set of accepted strings matches the + strings accepted by Python's :func:`float` constructor, except that + ``s`` may not have leading or trailing whitespace. The conversion + is independent of the current locale. + + If endptr is NULL, convert the whole string. Raise ValueError and + return -1.0 if the string is not a valid representation of a + floating-point number. + + If endptr is non-NULL, convert as much of the string as possible + and set *endptr to point to the first unconverted character. If no + initial segment of the string is the valid representation of a + floating-point number, set *endptr to point to the beginning of the + string, raise ValueError, and return -1.0. + + If ``s`` represents a value that's too large to store in a float + (for example, "1e500" is such a string on many platforms) then if + overflow_exception is NULL return +-Py_HUGE_VAL and don't set any + exception. Otherwise, overflow_exception must point to a Python + exception object; raise that exception and return -1.0. In both + cases, set *endptr to point just after the converted value. + + If any other error occurs during the conversion (for example an + out-of-memory error), set the appropriate Python exception and + return -1.0. + + .. versionadded:: 3.1 + + .. cfunction:: char* PyOS_ascii_formatd(char *buffer, size_t buf_len, const char *format, double d) Convert a :ctype:`double` to a string using the ``'.'`` as the decimal @@ -117,7 +152,10 @@ See the Unix man page :manpage:`atof(2)` for details. + .. deprecated:: 3.1 + Use PyOS_string_to_double instead. + .. cfunction:: char* PyOS_stricmp(char *s1, char *s2) Case insensitive comparison of strings. The function works almost Index: Modules/_pickle.c =================================================================== --- Modules/_pickle.c (revision 72233) +++ Modules/_pickle.c (working copy) @@ -2971,20 +2971,20 @@ return bad_readline(); errno = 0; - d = PyOS_ascii_strtod(s, &endptr); - - if ((errno == ERANGE && !(fabs(d) <= 1.0)) || - (endptr[0] != '\n') || (endptr[1] != '\0')) { + d = PyOS_string_to_double(s, &endptr, PyExc_OverflowError); + if (d == -1.0 && PyErr_Occurred()) + return -1; + if ((endptr[0] != '\n') || (endptr[1] != '\0')) { PyErr_SetString(PyExc_ValueError, "could not convert string to float"); return -1; } - - if ((value = PyFloat_FromDouble(d)) == NULL) + value = PyFloat_FromDouble(d); + if (value == NULL) return -1; PDATA_PUSH(self->stack, value, -1); return 0; -} + } static int load_binfloat(UnpicklerObject *self)