From 32674aeff77f6cef0da19633ec4e6842491e2066 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 13 Aug 2026 19:47:25 +0200 Subject: [PATCH 1/2] gh-155742: Add internal _PyFloat_FromString() Similar to PyFloat_FromString(), but don't require a Python object. Use _PyFloat_FromString() in _json to avoid creating a temporary bytes objects. _Py_string_to_number_with_underscores() can now be called with NULL object and float_from_string_inner() can now be called with NULL data; if needed they create a temporary bytes objects to format the error message. --- Include/internal/pycore_floatobject.h | 3 ++ Modules/_json.c | 41 ++++++++++++++----------- Objects/floatobject.c | 43 +++++++++++++++++++++------ Python/pystrtod.c | 10 +++++++ 4 files changed, 71 insertions(+), 26 deletions(-) diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index 62501cdaf44f072..864cc4d36869892 100644 --- a/Include/internal/pycore_floatobject.h +++ b/Include/internal/pycore_floatobject.h @@ -41,6 +41,9 @@ extern double _Py_parse_inf_or_nan(const char *p, char **endptr); extern int _Py_convert_int_to_double(PyObject **v, double *dbl); +// Export for '_json' shared extension. +PyAPI_FUNC(PyObject*) _PyFloat_FromString(const char *str, Py_ssize_t len); + /* Should match endianness of the platform in most (all?) cases. */ #ifdef DOUBLE_IS_BIG_ENDIAN_IEEE754 diff --git a/Modules/_json.c b/Modules/_json.c index 3a724a3e72b185b..cfb0a623ae12458 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -12,6 +12,7 @@ #include "pycore_ceval.h" // _Py_EnterRecursiveCall() #include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST() #include "pycore_dict.h" // _PyDict_SetItem_Take2() +#include "pycore_floatobject.h" // _PyFloat_FromString() #include "pycore_list.h" // _PyList_AppendTakeRef() #include "pycore_global_strings.h" // _Py_ID() #include "pycore_pyerrors.h" // _PyErr_FormatNote @@ -997,7 +998,6 @@ _match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ Py_ssize_t idx = start; int is_float = 0; PyObject *rval; - PyObject *numstr = NULL; PyObject *custom_func; str = PyUnicode_DATA(pystr); @@ -1064,32 +1064,39 @@ _match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ if (custom_func) { /* copy the section we determined to be a number */ - numstr = PyUnicode_FromKindAndData(kind, - (char*)str + kind * start, - idx - start); - if (numstr == NULL) + PyObject *numstr = PyUnicode_FromKindAndData(kind, + (char*)str + kind * start, + idx - start); + if (numstr == NULL) { return NULL; + } rval = PyObject_CallOneArg(custom_func, numstr); + Py_DECREF(numstr); } else { - Py_ssize_t i, n; - char *buf; /* Straight conversion to ASCII, to avoid costly conversion of decimal unicode digits (which cannot appear here) */ - n = idx - start; - numstr = PyBytes_FromStringAndSize(NULL, n); - if (numstr == NULL) + Py_ssize_t n = idx - start; + char *buf = PyMem_Malloc(n + 1); + if (buf == NULL) { + PyErr_NoMemory(); return NULL; - buf = PyBytes_AS_STRING(numstr); - for (i = 0; i < n; i++) { - buf[i] = (char) PyUnicode_READ(kind, str, i + start); } - if (is_float) - rval = PyFloat_FromString(numstr); - else + + for (Py_ssize_t i = 0; i < n; i++) { + Py_UCS4 ch = PyUnicode_READ(kind, str, i + start); + assert(ch <= 127); + buf[i] = (char)ch; + } + buf[n] = '\0'; + if (is_float) { + rval = _PyFloat_FromString(buf, n); + } + else { rval = PyLong_FromString(buf, NULL, 10); + } + PyMem_Free(buf); } - Py_DECREF(numstr); *next_idx_ptr = idx; return rval; } diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 17e6a729dcd83fc..debde669ad697b5 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -137,9 +137,10 @@ PyFloat_FromDouble(double fval) } static PyObject * -float_from_string_inner(const char *s, Py_ssize_t len, void *obj) +float_from_string_inner(const char *s, Py_ssize_t len, void *data) { double x; + const char *orig_s = s; const char *end; const char *last = s + len; /* strip leading whitespace */ @@ -147,10 +148,7 @@ float_from_string_inner(const char *s, Py_ssize_t len, void *obj) s++; } if (s == last) { - PyErr_Format(PyExc_ValueError, - "could not convert string to float: " - "%R", obj); - return NULL; + goto error; } /* strip trailing whitespace */ @@ -163,10 +161,7 @@ float_from_string_inner(const char *s, Py_ssize_t len, void *obj) * fine. */ x = PyOS_string_to_double(s, (char **)&end, NULL); if (end != last) { - PyErr_Format(PyExc_ValueError, - "could not convert string to float: " - "%R", obj); - return NULL; + goto error; } else if (x == -1.0 && PyErr_Occurred()) { return NULL; @@ -174,8 +169,38 @@ float_from_string_inner(const char *s, Py_ssize_t len, void *obj) else { return PyFloat_FromDouble(x); } + +error: + PyObject *obj = (PyObject*)data; + if (obj == NULL) { + obj = PyBytes_FromStringAndSize(orig_s, len); + if (obj == NULL) { + return NULL; + } + } + else { + Py_INCREF(obj); + } + PyErr_Format(PyExc_ValueError, + "could not convert string to float: " + "%R", obj); + Py_DECREF(obj); + return NULL; +} + + +// Internal API similar to PyFloat_FromString() but doesn't require a Python +// object. str[len] must be the NUL byte. +PyObject* +_PyFloat_FromString(const char *str, Py_ssize_t len) +{ + assert(len >= 1); + assert(str[len] == '\0'); + return _Py_string_to_number_with_underscores(str, len, "float", NULL, NULL, + float_from_string_inner); } + PyObject * PyFloat_FromString(PyObject *v) { diff --git a/Python/pystrtod.c b/Python/pystrtod.c index e8aca939d1fb98c..bb2ff0e2b9fd08f 100644 --- a/Python/pystrtod.c +++ b/Python/pystrtod.c @@ -395,9 +395,19 @@ _Py_string_to_number_with_underscores( error: PyMem_Free(dup); + if (obj == NULL) { + obj = PyBytes_FromStringAndSize(s, orig_len); + if (obj == NULL) { + return NULL; + } + } + else { + Py_INCREF(obj); + } PyErr_Format(PyExc_ValueError, "could not convert string to %s: " "%R", what, obj); + Py_DECREF(obj); return NULL; } From 68ecdf378bb7e403f5a9dcd935933c1dcd45293f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 3 Sep 2026 16:38:08 +0200 Subject: [PATCH 2/2] Fix C23 warning Fix the compiler warning: {'file': 'Objects/floatobject.c', 'line': '174', 'column': '5', 'message': 'label followed by a declaration is a C23 extension', 'option': '-Wc23-extensions'} --- Objects/floatobject.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index debde669ad697b5..a0249b853273191 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -171,20 +171,23 @@ float_from_string_inner(const char *s, Py_ssize_t len, void *data) } error: - PyObject *obj = (PyObject*)data; - if (obj == NULL) { - obj = PyBytes_FromStringAndSize(orig_s, len); + // Label followed by a declaration is a C23 extension, so use a sub-scope + { + PyObject *obj = (PyObject*)data; if (obj == NULL) { - return NULL; + obj = PyBytes_FromStringAndSize(orig_s, len); + if (obj == NULL) { + return NULL; + } } + else { + Py_INCREF(obj); + } + PyErr_Format(PyExc_ValueError, + "could not convert string to float: " + "%R", obj); + Py_DECREF(obj); } - else { - Py_INCREF(obj); - } - PyErr_Format(PyExc_ValueError, - "could not convert string to float: " - "%R", obj); - Py_DECREF(obj); return NULL; }