Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6d65df1

Browse files
committed
Internal plumbing changes for float parsing:
- check for nans and infs within PyOS_ascii_strtod - simplify parsing in PyFloat_FromString, and handle out-of-memory errors properly
1 parent 35f1c94 commit 6d65df1

2 files changed

Lines changed: 78 additions & 87 deletions

File tree

Objects/floatobject.c

Lines changed: 21 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ PyFloat_FromDouble(double fval)
162162
PyObject *
163163
PyFloat_FromString(PyObject *v)
164164
{
165-
const char *s, *last, *end, *sp;
165+
const char *s, *last, *end;
166166
double x;
167167
char buffer[256]; /* for errors */
168168
char *s_buffer = NULL;
@@ -186,76 +186,40 @@ PyFloat_FromString(PyObject *v)
186186
"float() argument must be a string or a number");
187187
return NULL;
188188
}
189-
190189
last = s + len;
190+
191191
while (*s && isspace(Py_CHARMASK(*s)))
192192
s++;
193-
if (*s == '\0') {
194-
PyErr_SetString(PyExc_ValueError, "empty string for float()");
195-
goto error;
196-
}
197-
sp = s;
198-
/* We don't care about overflow or underflow. If the platform supports
199-
* them, infinities and signed zeroes (on underflow) are fine.
200-
* However, strtod can return 0 for denormalized numbers. Note that
201-
* whether strtod sets errno on underflow is not defined, so we can't
202-
* key off errno.
203-
*/
193+
/* We don't care about overflow or underflow. If the platform
194+
* supports them, infinities and signed zeroes (on underflow) are
195+
* fine. */
196+
errno = 0;
204197
PyFPE_START_PROTECT("strtod", goto error)
205198
x = PyOS_ascii_strtod(s, (char **)&end);
206199
PyFPE_END_PROTECT(x)
207-
errno = 0;
208-
/* Believe it or not, Solaris 2.6 can move end *beyond* the null
209-
byte at the end of the string, when the input is inf(inity). */
210-
if (end > last)
211-
end = last;
212-
/* Check for inf and nan. This is done late because it rarely happens. */
213200
if (end == s) {
214-
char *p = (char*)sp;
215-
int sign = 1;
216-
217-
if (*p == '-') {
218-
sign = -1;
219-
p++;
220-
}
221-
if (*p == '+') {
222-
p++;
223-
}
224-
if (PyOS_strnicmp(p, "inf", 4) == 0) {
225-
if (s_buffer != NULL)
226-
PyMem_FREE(s_buffer);
227-
Py_RETURN_INF(sign);
228-
}
229-
if (PyOS_strnicmp(p, "infinity", 9) == 0) {
230-
if (s_buffer != NULL)
231-
PyMem_FREE(s_buffer);
232-
Py_RETURN_INF(sign);
233-
}
234-
#ifdef Py_NAN
235-
if(PyOS_strnicmp(p, "nan", 4) == 0) {
236-
if (s_buffer != NULL)
237-
PyMem_FREE(s_buffer);
238-
Py_RETURN_NAN;
201+
if (errno == ENOMEM)
202+
PyErr_NoMemory();
203+
else {
204+
PyOS_snprintf(buffer, sizeof(buffer),
205+
"invalid literal for float(): %.200s", s);
206+
PyErr_SetString(PyExc_ValueError, buffer);
239207
}
240-
#endif
241-
PyOS_snprintf(buffer, sizeof(buffer),
242-
"invalid literal for float(): %.200s", s);
243-
PyErr_SetString(PyExc_ValueError, buffer);
244208
goto error;
245209
}
246210
/* Since end != s, the platform made *some* kind of sense out
247211
of the input. Trust it. */
248212
while (*end && isspace(Py_CHARMASK(*end)))
249213
end++;
250-
if (*end != '\0') {
251-
PyOS_snprintf(buffer, sizeof(buffer),
252-
"invalid literal for float(): %.200s", s);
253-
PyErr_SetString(PyExc_ValueError, buffer);
254-
goto error;
255-
}
256-
else if (end != last) {
257-
PyErr_SetString(PyExc_ValueError,
258-
"null byte in argument for float()");
214+
if (end != last) {
215+
if (*end == '\0')
216+
PyErr_SetString(PyExc_ValueError,
217+
"null byte in argument for float()");
218+
else {
219+
PyOS_snprintf(buffer, sizeof(buffer),
220+
"invalid literal for float(): %.200s", s);
221+
PyErr_SetString(PyExc_ValueError, buffer);
222+
}
259223
goto error;
260224
}
261225
result = PyFloat_FromDouble(x);

Python/pystrtod.c

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ PyOS_ascii_strtod(const char *nptr, char **endptr)
9494

9595
decimal_point_pos = NULL;
9696

97+
/* Set errno to zero, so that we can distinguish zero results
98+
and underflows */
99+
errno = 0;
100+
97101
/* We process any leading whitespace and the optional sign manually,
98102
then pass the remainder to the system strtod. This ensures that
99103
the result of an underflow has the correct sign. (bug #1725) */
@@ -107,34 +111,61 @@ PyOS_ascii_strtod(const char *nptr, char **endptr)
107111
if (*p == '-') {
108112
negate = 1;
109113
p++;
110-
} else if (*p == '+') {
114+
}
115+
else if (*p == '+') {
111116
p++;
112117
}
113118

114-
/* What's left should begin with a digit, a decimal point, or one of
115-
the letters i, I, n, N. It should not begin with 0x or 0X */
116-
if ((!ISDIGIT(*p) &&
117-
*p != '.' && *p != 'i' && *p != 'I' && *p != 'n' && *p != 'N')
118-
||
119-
(*p == '0' && (p[1] == 'x' || p[1] == 'X')))
120-
{
121-
if (endptr)
122-
*endptr = (char*)nptr;
123-
errno = EINVAL;
124-
return val;
119+
/* Parse infinities and nans */
120+
if (*p == 'i' || *p == 'I') {
121+
if (PyOS_strnicmp(p, "inf", 3) == 0) {
122+
val = Py_HUGE_VAL;
123+
if (PyOS_strnicmp(p+3, "inity", 5) == 0)
124+
fail_pos = (char *)p+8;
125+
else
126+
fail_pos = (char *)p+3;
127+
goto got_val;
128+
}
129+
else
130+
goto invalid_string;
125131
}
126-
digits_pos = p;
132+
#ifdef Py_NAN
133+
if (*p == 'n' || *p == 'N') {
134+
if (PyOS_strnicmp(p, "nan", 3) == 0) {
135+
val = Py_NAN;
136+
fail_pos = (char *)p+3;
137+
goto got_val;
138+
}
139+
else
140+
goto invalid_string;
141+
}
142+
#endif
143+
144+
/* Some platform strtods accept hex floats; Python shouldn't (at the
145+
moment), so we check explicitly for strings starting with '0x'. */
146+
if (*p == '0' && (*(p+1) == 'x' || *(p+1) == 'X'))
147+
goto invalid_string;
127148

128-
if (decimal_point[0] != '.' ||
149+
/* Check that what's left begins with a digit or decimal point */
150+
if (!ISDIGIT(*p) && *p != '.')
151+
goto invalid_string;
152+
153+
digits_pos = p;
154+
if (decimal_point[0] != '.' ||
129155
decimal_point[1] != 0)
130156
{
157+
/* Look for a '.' in the input; if present, it'll need to be
158+
swapped for the current locale's decimal point before we
159+
call strtod. On the other hand, if we find the current
160+
locale's decimal point then the input is invalid. */
131161
while (ISDIGIT(*p))
132162
p++;
133163

134164
if (*p == '.')
135165
{
136166
decimal_point_pos = p++;
137167

168+
/* locate end of number */
138169
while (ISDIGIT(*p))
139170
p++;
140171

@@ -147,27 +178,16 @@ PyOS_ascii_strtod(const char *nptr, char **endptr)
147178
end = p;
148179
}
149180
else if (strncmp(p, decimal_point, decimal_point_len) == 0)
150-
{
151181
/* Python bug #1417699 */
152-
if (endptr)
153-
*endptr = (char*)nptr;
154-
errno = EINVAL;
155-
return val;
156-
}
182+
goto invalid_string;
157183
/* For the other cases, we need not convert the decimal
158184
point */
159185
}
160186

161-
/* Set errno to zero, so that we can distinguish zero results
162-
and underflows */
163-
errno = 0;
164-
165-
if (decimal_point_pos)
166-
{
187+
if (decimal_point_pos) {
167188
char *copy, *c;
168-
169-
/* We need to convert the '.' to the locale specific decimal
170-
point */
189+
/* Create a copy of the input, with the '.' converted to the
190+
locale-specific decimal point */
171191
copy = (char *)PyMem_MALLOC(end - digits_pos +
172192
1 + decimal_point_len);
173193
if (copy == NULL) {
@@ -208,15 +228,22 @@ PyOS_ascii_strtod(const char *nptr, char **endptr)
208228
}
209229

210230
if (fail_pos == digits_pos)
211-
fail_pos = (char *)nptr;
231+
goto invalid_string;
212232

233+
got_val:
213234
if (negate && fail_pos != nptr)
214235
val = -val;
215236

216237
if (endptr)
217238
*endptr = fail_pos;
218239

219240
return val;
241+
242+
invalid_string:
243+
if (endptr)
244+
*endptr = (char*)nptr;
245+
errno = EINVAL;
246+
return -1.0;
220247
}
221248

222249
#endif

0 commit comments

Comments
 (0)