Skip to content

Commit 32674ae

Browse files
committed
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.
1 parent 1620e0f commit 32674ae

4 files changed

Lines changed: 71 additions & 26 deletions

File tree

Include/internal/pycore_floatobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ extern double _Py_parse_inf_or_nan(const char *p, char **endptr);
4141

4242
extern int _Py_convert_int_to_double(PyObject **v, double *dbl);
4343

44+
// Export for '_json' shared extension.
45+
PyAPI_FUNC(PyObject*) _PyFloat_FromString(const char *str, Py_ssize_t len);
46+
4447
/* Should match endianness of the platform in most (all?) cases. */
4548

4649
#ifdef DOUBLE_IS_BIG_ENDIAN_IEEE754

Modules/_json.c

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "pycore_ceval.h" // _Py_EnterRecursiveCall()
1313
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST()
1414
#include "pycore_dict.h" // _PyDict_SetItem_Take2()
15+
#include "pycore_floatobject.h" // _PyFloat_FromString()
1516
#include "pycore_list.h" // _PyList_AppendTakeRef()
1617
#include "pycore_global_strings.h" // _Py_ID()
1718
#include "pycore_pyerrors.h" // _PyErr_FormatNote
@@ -997,7 +998,6 @@ _match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_
997998
Py_ssize_t idx = start;
998999
int is_float = 0;
9991000
PyObject *rval;
1000-
PyObject *numstr = NULL;
10011001
PyObject *custom_func;
10021002

10031003
str = PyUnicode_DATA(pystr);
@@ -1064,32 +1064,39 @@ _match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_
10641064

10651065
if (custom_func) {
10661066
/* copy the section we determined to be a number */
1067-
numstr = PyUnicode_FromKindAndData(kind,
1068-
(char*)str + kind * start,
1069-
idx - start);
1070-
if (numstr == NULL)
1067+
PyObject *numstr = PyUnicode_FromKindAndData(kind,
1068+
(char*)str + kind * start,
1069+
idx - start);
1070+
if (numstr == NULL) {
10711071
return NULL;
1072+
}
10721073
rval = PyObject_CallOneArg(custom_func, numstr);
1074+
Py_DECREF(numstr);
10731075
}
10741076
else {
1075-
Py_ssize_t i, n;
1076-
char *buf;
10771077
/* Straight conversion to ASCII, to avoid costly conversion of
10781078
decimal unicode digits (which cannot appear here) */
1079-
n = idx - start;
1080-
numstr = PyBytes_FromStringAndSize(NULL, n);
1081-
if (numstr == NULL)
1079+
Py_ssize_t n = idx - start;
1080+
char *buf = PyMem_Malloc(n + 1);
1081+
if (buf == NULL) {
1082+
PyErr_NoMemory();
10821083
return NULL;
1083-
buf = PyBytes_AS_STRING(numstr);
1084-
for (i = 0; i < n; i++) {
1085-
buf[i] = (char) PyUnicode_READ(kind, str, i + start);
10861084
}
1087-
if (is_float)
1088-
rval = PyFloat_FromString(numstr);
1089-
else
1085+
1086+
for (Py_ssize_t i = 0; i < n; i++) {
1087+
Py_UCS4 ch = PyUnicode_READ(kind, str, i + start);
1088+
assert(ch <= 127);
1089+
buf[i] = (char)ch;
1090+
}
1091+
buf[n] = '\0';
1092+
if (is_float) {
1093+
rval = _PyFloat_FromString(buf, n);
1094+
}
1095+
else {
10901096
rval = PyLong_FromString(buf, NULL, 10);
1097+
}
1098+
PyMem_Free(buf);
10911099
}
1092-
Py_DECREF(numstr);
10931100
*next_idx_ptr = idx;
10941101
return rval;
10951102
}

Objects/floatobject.c

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,20 +137,18 @@ PyFloat_FromDouble(double fval)
137137
}
138138

139139
static PyObject *
140-
float_from_string_inner(const char *s, Py_ssize_t len, void *obj)
140+
float_from_string_inner(const char *s, Py_ssize_t len, void *data)
141141
{
142142
double x;
143+
const char *orig_s = s;
143144
const char *end;
144145
const char *last = s + len;
145146
/* strip leading whitespace */
146147
while (s < last && Py_ISSPACE(*s)) {
147148
s++;
148149
}
149150
if (s == last) {
150-
PyErr_Format(PyExc_ValueError,
151-
"could not convert string to float: "
152-
"%R", obj);
153-
return NULL;
151+
goto error;
154152
}
155153

156154
/* strip trailing whitespace */
@@ -163,19 +161,46 @@ float_from_string_inner(const char *s, Py_ssize_t len, void *obj)
163161
* fine. */
164162
x = PyOS_string_to_double(s, (char **)&end, NULL);
165163
if (end != last) {
166-
PyErr_Format(PyExc_ValueError,
167-
"could not convert string to float: "
168-
"%R", obj);
169-
return NULL;
164+
goto error;
170165
}
171166
else if (x == -1.0 && PyErr_Occurred()) {
172167
return NULL;
173168
}
174169
else {
175170
return PyFloat_FromDouble(x);
176171
}
172+
173+
error:
174+
PyObject *obj = (PyObject*)data;
175+
if (obj == NULL) {
176+
obj = PyBytes_FromStringAndSize(orig_s, len);
177+
if (obj == NULL) {
178+
return NULL;
179+
}
180+
}
181+
else {
182+
Py_INCREF(obj);
183+
}
184+
PyErr_Format(PyExc_ValueError,
185+
"could not convert string to float: "
186+
"%R", obj);
187+
Py_DECREF(obj);
188+
return NULL;
189+
}
190+
191+
192+
// Internal API similar to PyFloat_FromString() but doesn't require a Python
193+
// object. str[len] must be the NUL byte.
194+
PyObject*
195+
_PyFloat_FromString(const char *str, Py_ssize_t len)
196+
{
197+
assert(len >= 1);
198+
assert(str[len] == '\0');
199+
return _Py_string_to_number_with_underscores(str, len, "float", NULL, NULL,
200+
float_from_string_inner);
177201
}
178202

203+
179204
PyObject *
180205
PyFloat_FromString(PyObject *v)
181206
{

Python/pystrtod.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,19 @@ _Py_string_to_number_with_underscores(
395395

396396
error:
397397
PyMem_Free(dup);
398+
if (obj == NULL) {
399+
obj = PyBytes_FromStringAndSize(s, orig_len);
400+
if (obj == NULL) {
401+
return NULL;
402+
}
403+
}
404+
else {
405+
Py_INCREF(obj);
406+
}
398407
PyErr_Format(PyExc_ValueError,
399408
"could not convert string to %s: "
400409
"%R", what, obj);
410+
Py_DECREF(obj);
401411
return NULL;
402412
}
403413

0 commit comments

Comments
 (0)