diff options
-rw-r--r-- | Include/timefuncs.h | 23 | ||||
-rw-r--r-- | Lib/test/test_datetime.py | 27 | ||||
-rw-r--r-- | Lib/test/test_time.py | 8 | ||||
-rw-r--r-- | Misc/NEWS | 15 | ||||
-rw-r--r-- | Modules/datetimemodule.c | 20 | ||||
-rw-r--r-- | Modules/timemodule.c | 12 |
6 files changed, 90 insertions, 15 deletions
diff --git a/Include/timefuncs.h b/Include/timefuncs.h new file mode 100644 index 0000000..553142d --- /dev/null +++ b/Include/timefuncs.h @@ -0,0 +1,23 @@ +/* timefuncs.h + */ + +/* Utility function related to timemodule.c. */ + +#ifndef TIMEFUNCS_H +#define TIMEFUNCS_H +#ifdef __cplusplus +extern "C" { +#endif + + +/* Cast double x to time_t, but raise ValueError if x is too large + * to fit in a time_t. ValueError is set on return iff the return + * value is (time_t)-1 and PyErr_Occurred(). + */ +PyAPI_FUNC(time_t) _PyTime_DoubleToTimet(double x); + + +#ifdef __cplusplus +} +#endif +#endif /* TIMEFUNCS_H */ diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index f7fec57..2a6aca2 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -730,6 +730,15 @@ class TestDate(HarmlessMixedComparison): self.assertEqual(d.month, month) self.assertEqual(d.day, day) + def test_insane_fromtimestamp(self): + # It's possible that some platform maps time_t to double, + # and that this test will fail there. This test should + # exempt such platforms (provided they return reasonable + # results!). + for insane in -1e200, 1e200: + self.assertRaises(ValueError, self.theclass.fromtimestamp, + insane) + def test_today(self): import time @@ -1380,6 +1389,24 @@ class TestDateTime(TestDate): got = self.theclass.utcfromtimestamp(ts) self.verify_field_equality(expected, got) + def test_insane_fromtimestamp(self): + # It's possible that some platform maps time_t to double, + # and that this test will fail there. This test should + # exempt such platforms (provided they return reasonable + # results!). + for insane in -1e200, 1e200: + self.assertRaises(ValueError, self.theclass.fromtimestamp, + insane) + + def test_insane_utcfromtimestamp(self): + # It's possible that some platform maps time_t to double, + # and that this test will fail there. This test should + # exempt such platforms (provided they return reasonable + # results!). + for insane in -1e200, 1e200: + self.assertRaises(ValueError, self.theclass.utcfromtimestamp, + insane) + def test_utcnow(self): import time diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 9e16d0b..64f1f01 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -176,6 +176,14 @@ class TimeTestCase(unittest.TestCase): del environ['TZ'] time.tzset() + def test_insane_timestamps(self): + # It's possible that some platform maps time_t to double, + # and that this test will fail there. This test should + # exempt such platforms (provided they return reasonable + # results!). + for func in time.ctime, time.gmtime, time.localtime: + for unreasonable in -1e200, 1e200: + self.assertRaises(ValueError, func, unreasonable) def test_main(): test_support.run_unittest(TimeTestCase) @@ -228,9 +228,13 @@ Core and builtins Extension modules ----------------- -- time module code that deals with time_t timestamps will now raise a - ValueError if more than a second is lost in precision from time_t being less - precise than a double. Closes bug #919012. +- time module code that deals with input POSIX timestamps will now raise + ValueError if more than a second is lost in precision when the + timestamp is cast to the platform C time_t type. There's no chance + that the platform will do anything sensible with the result in such + cases. This includes ctime(), localtime() and gmtime(). Assorted + fromtimestamp() and utcfromtimestamp() methods in the datetime module + were also protected. Closes bugs #919012 and 975996. - fcntl.ioctl now warns if the mutate flag is not specified. @@ -555,6 +559,11 @@ Build C API ----- +- Private function _PyTime_DoubleToTimet added, to convert a Python + timestamp (C double) to platform time_t with some out-of-bounds + checking. Declared in new header file timefuncs.h. It would be + good to expose some other internal timemodule.c functions there. + - New public functions PyEval_EvaluateFrame and PyGen_New to expose generator objects. diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index 7f38d1a..d9cf604 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -8,6 +8,7 @@ #include <time.h> +#include "timefuncs.h" #include "datetime.h" /* We require that C int be at least 32 bits, and use int virtually @@ -2226,11 +2227,15 @@ date_new(PyTypeObject *type, PyObject *args, PyObject *kw) /* Return new date from localtime(t). */ static PyObject * -date_local_from_time_t(PyObject *cls, time_t t) +date_local_from_time_t(PyObject *cls, double ts) { struct tm *tm; + time_t t; PyObject *result = NULL; + t = _PyTime_DoubleToTimet(ts); + if (t == (time_t)-1 && PyErr_Occurred()) + return NULL; tm = localtime(&t); if (tm) result = PyObject_CallFunction(cls, "iii", @@ -2278,7 +2283,7 @@ date_fromtimestamp(PyObject *cls, PyObject *args) PyObject *result = NULL; if (PyArg_ParseTuple(args, "d:fromtimestamp", ×tamp)) - result = date_local_from_time_t(cls, (time_t)timestamp); + result = date_local_from_time_t(cls, timestamp); return result; } @@ -3654,10 +3659,15 @@ static PyObject * datetime_from_timestamp(PyObject *cls, TM_FUNC f, double timestamp, PyObject *tzinfo) { - time_t timet = (time_t)timestamp; - double fraction = timestamp - (double)timet; - int us = (int)round_to_long(fraction * 1e6); + time_t timet; + double fraction; + int us; + timet = _PyTime_DoubleToTimet(timestamp); + if (timet == (time_t)-1 && PyErr_Occurred()) + return NULL; + fraction = timestamp - (double)timet; + us = (int)round_to_long(fraction * 1e6); return datetime_from_timet_and_us(cls, f, timet, us, tzinfo); } diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 0783f7f..f855796 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -3,6 +3,7 @@ #include "Python.h" #include "structseq.h" +#include "timefuncs.h" #include <ctype.h> @@ -84,11 +85,8 @@ static double floattime(void); /* For Y2K check */ static PyObject *moddict; -/* Cast double x to time_t, but raise ValueError if x is too large - * to fit in a time_t. ValueError is set on return iff the return - * value is (time_t)-1 and PyErr_Occurred(). - */ -static time_t +/* Exposed in timefuncs.h. */ +time_t _PyTime_DoubleToTimet(double x) { time_t result; @@ -382,7 +380,7 @@ time_strftime(PyObject *self, PyObject *args) /* Checks added to make sure strftime() does not crash Python by indexing blindly into some array for a textual representation by some bad index (fixes bug #897625). - + No check for year since handled in gettmarg(). */ if (buf.tm_mon < 0 || buf.tm_mon > 11) { @@ -583,7 +581,7 @@ time_tzset(PyObject *self, PyObject *args) /* Reset timezone, altzone, daylight and tzname */ inittimezone(m); Py_DECREF(m); - + Py_INCREF(Py_None); return Py_None; } |