diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 136ad29e20adf0..13f9d5958f9458 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -26,6 +26,8 @@ SEC_TO_NS = 10 ** 9 NS_TO_SEC = 10 ** 9 +INVALID_FLOATS = (float("nan"), float("-inf"), float("inf")) + class _PyTime(enum.IntEnum): # Round towards minus infinity (-inf) ROUND_FLOOR = 0 @@ -160,6 +162,8 @@ def test_sleep(self): self.assertRaises(ValueError, time.sleep, -2) self.assertRaises(ValueError, time.sleep, -1) time.sleep(1.2) + for value in INVALID_FLOATS: + self.assertRaises(ValueError, time.sleep, value) def test_strftime(self): tt = time.gmtime(self.t) @@ -545,8 +549,9 @@ def test_localtime_failure(self): self.assertRaises(OSError, time.ctime, invalid_time_t) # Issue #26669: check for localtime() failure - self.assertRaises(ValueError, time.localtime, float("nan")) - self.assertRaises(ValueError, time.ctime, float("nan")) + for value in INVALID_FLOATS: + self.assertRaises(ValueError, time.localtime, value) + self.assertRaises(ValueError, time.ctime, value) def test_get_clock_info(self): clocks = ['clock', 'monotonic', 'perf_counter', 'process_time', 'time'] @@ -884,10 +889,11 @@ def c_int_filter(secs): lambda secs: secs * SEC_TO_NS, value_filter=c_int_filter) - # test nan - for time_rnd, _ in ROUNDING_MODES: - with self.assertRaises(TypeError): - PyTime_FromSeconds(float('nan')) + # test invalid floats + for value in INVALID_FLOATS: + for time_rnd, _ in ROUNDING_MODES: + with self.assertRaises(TypeError): + PyTime_FromSeconds(value) def test_FromSecondsObject(self): from _testcapi import PyTime_FromSecondsObject @@ -900,10 +906,11 @@ def test_FromSecondsObject(self): PyTime_FromSecondsObject, lambda ns: self.decimal_round(ns * SEC_TO_NS)) - # test nan - for time_rnd, _ in ROUNDING_MODES: - with self.assertRaises(ValueError): - PyTime_FromSecondsObject(float('nan'), time_rnd) + # test invalid floats + for value in INVALID_FLOATS: + for time_rnd, _ in ROUNDING_MODES: + with self.assertRaises(ValueError): + PyTime_FromSecondsObject(value, time_rnd) def test_AsSecondsDouble(self): from _testcapi import PyTime_AsSecondsDouble @@ -918,10 +925,11 @@ def float_converter(ns): float_converter, NS_TO_SEC) - # test nan - for time_rnd, _ in ROUNDING_MODES: - with self.assertRaises(TypeError): - PyTime_AsSecondsDouble(float('nan')) + # test invalid floats + for value in INVALID_FLOATS: + for time_rnd, _ in ROUNDING_MODES: + with self.assertRaises(TypeError): + PyTime_AsSecondsDouble(value) def create_decimal_converter(self, denominator): denom = decimal.Decimal(denominator) @@ -1028,10 +1036,11 @@ def test_object_to_timeval(self): self.create_converter(SEC_TO_US), value_filter=self.time_t_filter) - # test nan - for time_rnd, _ in ROUNDING_MODES: - with self.assertRaises(ValueError): - pytime_object_to_timeval(float('nan'), time_rnd) + # test invalid floats + for value in INVALID_FLOATS: + for time_rnd, _ in ROUNDING_MODES: + with self.assertRaises(ValueError): + pytime_object_to_timeval(value, time_rnd) def test_object_to_timespec(self): from _testcapi import pytime_object_to_timespec @@ -1044,10 +1053,11 @@ def test_object_to_timespec(self): self.create_converter(SEC_TO_NS), value_filter=self.time_t_filter) - # test nan - for time_rnd, _ in ROUNDING_MODES: - with self.assertRaises(ValueError): - pytime_object_to_timespec(float('nan'), time_rnd) + # test invalid floats + for value in INVALID_FLOATS: + for time_rnd, _ in ROUNDING_MODES: + with self.assertRaises(ValueError): + pytime_object_to_timespec(value, time_rnd) if __name__ == "__main__": diff --git a/Misc/NEWS.d/next/Library/2019-01-10-17-38-40.bpo-26669.FBeAf4.rst b/Misc/NEWS.d/next/Library/2019-01-10-17-38-40.bpo-26669.FBeAf4.rst new file mode 100644 index 00000000000000..0e1f561191ac61 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-01-10-17-38-40.bpo-26669.FBeAf4.rst @@ -0,0 +1 @@ +Reject float infinity in time functions. diff --git a/Python/pytime.c b/Python/pytime.c index 68c49a86da25b6..1bd46e42df43ce 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -128,6 +128,18 @@ _PyTime_Round(double x, _PyTime_round_t round) return d; } +static const char* +pytime_check_double(double d) +{ + if (Py_IS_NAN(d)) { + return "invalid value: NaN"; + } + if (Py_IS_INFINITY(d)) { + return "invalid value: infinity"; + } + return NULL; +} + static int _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator, long idenominator, _PyTime_round_t round) @@ -137,8 +149,14 @@ _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator, /* volatile avoids optimization changing how numbers are rounded */ volatile double floatpart; - floatpart = modf(d, &intpart); + const char *errmsg = pytime_check_double(d); + if (errmsg) { + *numerator = 0; + PyErr_SetString(PyExc_ValueError, errmsg); + return -1; + } + floatpart = modf(d, &intpart); floatpart *= denominator; floatpart = _PyTime_Round(floatpart, round); if (floatpart >= denominator) { @@ -169,11 +187,6 @@ _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator, if (PyFloat_Check(obj)) { double d = PyFloat_AsDouble(obj); - if (Py_IS_NAN(d)) { - *numerator = 0; - PyErr_SetString(PyExc_ValueError, "Invalid value NaN (not a number)"); - return -1; - } return _PyTime_DoubleToDenominator(d, sec, numerator, denominator, round); } @@ -196,8 +209,10 @@ _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round) volatile double d; d = PyFloat_AsDouble(obj); - if (Py_IS_NAN(d)) { - PyErr_SetString(PyExc_ValueError, "Invalid value NaN (not a number)"); + + const char *errmsg = pytime_check_double(d); + if (errmsg) { + PyErr_SetString(PyExc_ValueError, errmsg); return -1; } @@ -384,6 +399,12 @@ _PyTime_FromDouble(_PyTime_t *t, double value, _PyTime_round_t round, /* volatile avoids optimization changing how numbers are rounded */ volatile double d; + const char *errmsg = pytime_check_double(value); + if (errmsg) { + PyErr_SetString(PyExc_ValueError, errmsg); + return -1; + } + /* convert to a number of nanoseconds */ d = value; d *= (double)unit_to_ns;