Skip to content

Commit a853a8b

Browse files
authored
bpo-31373: fix undefined floating-point demotions (#3396)
1 parent c988ae0 commit a853a8b

File tree

5 files changed

+52
-31
lines changed

5 files changed

+52
-31
lines changed

Include/pymath.h

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,14 @@ PyAPI_FUNC(void) _Py_set_387controlword(unsigned short);
187187
* result.
188188
* Caution:
189189
* This isn't reliable. C99 no longer requires libm to set errno under
190-
* any exceptional condition, but does require +- HUGE_VAL return
191-
* values on overflow. A 754 box *probably* maps HUGE_VAL to a
192-
* double infinity, and we're cool if that's so, unless the input
193-
* was an infinity and an infinity is the expected result. A C89
194-
* system sets errno to ERANGE, so we check for that too. We're
195-
* out of luck if a C99 754 box doesn't map HUGE_VAL to +Inf, or
196-
* if the returned result is a NaN, or if a C89 box returns HUGE_VAL
197-
* in non-overflow cases.
190+
* any exceptional condition, but does require +- HUGE_VAL return
191+
* values on overflow. A 754 box *probably* maps HUGE_VAL to a
192+
* double infinity, and we're cool if that's so, unless the input
193+
* was an infinity and an infinity is the expected result. A C89
194+
* system sets errno to ERANGE, so we check for that too. We're
195+
* out of luck if a C99 754 box doesn't map HUGE_VAL to +Inf, or
196+
* if the returned result is a NaN, or if a C89 box returns HUGE_VAL
197+
* in non-overflow cases.
198198
* X is evaluated more than once.
199199
* Some platforms have better way to spell this, so expect some #ifdef'ery.
200200
*
@@ -211,8 +211,20 @@ PyAPI_FUNC(void) _Py_set_387controlword(unsigned short);
211211
#define Py_OVERFLOWED(X) isinf(X)
212212
#else
213213
#define Py_OVERFLOWED(X) ((X) != 0.0 && (errno == ERANGE || \
214-
(X) == Py_HUGE_VAL || \
215-
(X) == -Py_HUGE_VAL))
216-
#endif
214+
(X) == Py_HUGE_VAL || \
215+
(X) == -Py_HUGE_VAL))
216+
#endif
217+
218+
/* Return whether integral type *type* is signed or not. */
219+
#define _Py_IntegralTypeSigned(type) ((type)(-1) < 0)
220+
/* Return the maximum value of integral type *type*. */
221+
#define _Py_IntegralTypeMax(type) ((_Py_IntegralTypeSigned(type)) ? (((((type)1 << (sizeof(type)*CHAR_BIT - 2)) - 1) << 1) + 1) : ~(type)0)
222+
/* Return the minimum value of integral type *type*. */
223+
#define _Py_IntegralTypeMin(type) ((_Py_IntegralTypeSigned(type)) ? -_Py_IntegralTypeMax(type) - 1 : 0)
224+
/* Check whether *v* is in the range of integral type *type*. This is most
225+
* useful if *v* is floating-point, since demoting a floating-point *v* to an
226+
* integral type that cannot represent *v*'s integral part is undefined
227+
* behavior. */
228+
#define _Py_InIntegralTypeRange(type, v) (_Py_IntegralTypeMin(type) <= v && v <= _Py_IntegralTypeMax(type))
217229

218230
#endif /* Py_PYMATH_H */
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix several possible instances of undefined behavior due to floating-point
2+
demotions.

Objects/floatobject.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2233,21 +2233,23 @@ _PyFloat_Pack4(double x, unsigned char *p, int le)
22332233

22342234
}
22352235
else {
2236-
float y = (float)x;
2237-
const unsigned char *s = (unsigned char*)&y;
22382236
int i, incr = 1;
22392237

2240-
if (Py_IS_INFINITY(y) && !Py_IS_INFINITY(x))
2238+
if (fabs(x) > FLT_MAX && !Py_IS_INFINITY(x))
22412239
goto Overflow;
22422240

2241+
unsigned char s[sizeof(float)];
2242+
float y = (float)x;
2243+
memcpy(s, &y, sizeof(float));
2244+
22432245
if ((float_format == ieee_little_endian_format && !le)
22442246
|| (float_format == ieee_big_endian_format && le)) {
22452247
p += 3;
22462248
incr = -1;
22472249
}
22482250

22492251
for (i = 0; i < 4; i++) {
2250-
*p = *s++;
2252+
*p = s[i];
22512253
p += incr;
22522254
}
22532255
return 0;

Python/getargs.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Python.h"
55

66
#include <ctype.h>
7+
#include <float.h>
78

89

910
#ifdef __cplusplus
@@ -858,6 +859,10 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags,
858859
double dval = PyFloat_AsDouble(arg);
859860
if (PyErr_Occurred())
860861
RETURN_ERR_OCCURRED;
862+
else if (dval > FLT_MAX)
863+
*p = (float)INFINITY;
864+
else if (dval < -FLT_MAX)
865+
*p = (float)-INFINITY;
861866
else
862867
*p = (float) dval;
863868
break;

Python/pytime.c

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ static int
9797
_PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator,
9898
double denominator, _PyTime_round_t round)
9999
{
100-
double intpart, err;
100+
double intpart;
101101
/* volatile avoids optimization changing how numbers are rounded */
102102
volatile double floatpart;
103103

@@ -115,14 +115,13 @@ _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator,
115115
}
116116
assert(0.0 <= floatpart && floatpart < denominator);
117117

118-
*sec = (time_t)intpart;
119-
*numerator = (long)floatpart;
120-
121-
err = intpart - (double)*sec;
122-
if (err <= -1.0 || err >= 1.0) {
118+
if (!_Py_InIntegralTypeRange(time_t, intpart)) {
123119
error_time_t_overflow();
124120
return -1;
125121
}
122+
*sec = (time_t)intpart;
123+
*numerator = (long)floatpart;
124+
126125
return 0;
127126
}
128127

@@ -150,20 +149,19 @@ int
150149
_PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round)
151150
{
152151
if (PyFloat_Check(obj)) {
153-
double intpart, err;
152+
double intpart;
154153
/* volatile avoids optimization changing how numbers are rounded */
155154
volatile double d;
156155

157156
d = PyFloat_AsDouble(obj);
158157
d = _PyTime_Round(d, round);
159158
(void)modf(d, &intpart);
160159

161-
*sec = (time_t)intpart;
162-
err = intpart - (double)*sec;
163-
if (err <= -1.0 || err >= 1.0) {
160+
if (!_Py_InIntegralTypeRange(time_t, intpart)) {
164161
error_time_t_overflow();
165162
return -1;
166163
}
164+
*sec = (time_t)intpart;
167165
return 0;
168166
}
169167
else {
@@ -180,7 +178,9 @@ _PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec,
180178
{
181179
int res;
182180
res = _PyTime_ObjectToDenominator(obj, sec, nsec, 1e9, round);
183-
assert(0 <= *nsec && *nsec < SEC_TO_NS);
181+
if (res == 0) {
182+
assert(0 <= *nsec && *nsec < SEC_TO_NS);
183+
}
184184
return res;
185185
}
186186

@@ -190,7 +190,9 @@ _PyTime_ObjectToTimeval(PyObject *obj, time_t *sec, long *usec,
190190
{
191191
int res;
192192
res = _PyTime_ObjectToDenominator(obj, sec, usec, 1e6, round);
193-
assert(0 <= *usec && *usec < SEC_TO_US);
193+
if (res == 0) {
194+
assert(0 <= *usec && *usec < SEC_TO_US);
195+
}
194196
return res;
195197
}
196198

@@ -276,7 +278,6 @@ static int
276278
_PyTime_FromFloatObject(_PyTime_t *t, double value, _PyTime_round_t round,
277279
long unit_to_ns)
278280
{
279-
double err;
280281
/* volatile avoids optimization changing how numbers are rounded */
281282
volatile double d;
282283

@@ -285,12 +286,11 @@ _PyTime_FromFloatObject(_PyTime_t *t, double value, _PyTime_round_t round,
285286
d *= (double)unit_to_ns;
286287
d = _PyTime_Round(d, round);
287288

288-
*t = (_PyTime_t)d;
289-
err = d - (double)*t;
290-
if (fabs(err) >= 1.0) {
289+
if (!_Py_InIntegralTypeRange(_PyTime_t, d)) {
291290
_PyTime_overflow();
292291
return -1;
293292
}
293+
*t = (_PyTime_t)d;
294294
return 0;
295295
}
296296

0 commit comments

Comments
 (0)