From 09796f2f142fdb1214f34a3ca917959ecb32a88b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 30 Sep 2021 02:11:41 +0200 Subject: bpo-41710: Add _PyTime_AsTimespec_clamp() (GH-28629) Add the _PyTime_AsTimespec_clamp() function: similar to _PyTime_AsTimespec(), but clamp to _PyTime_t min/max and don't raise an exception. PyThread_acquire_lock_timed() now uses _PyTime_AsTimespec_clamp() to remove the Py_UNREACHABLE() code path. * Add _PyTime_AsTime_t() function. * Add PY_TIME_T_MIN and PY_TIME_T_MAX constants. * Replace _PyTime_AsTimeval_noraise() with _PyTime_AsTimeval_clamp(). * Add pytime_divide_round_up() function. * Fix integer overflow in pytime_divide(). * Add pytime_divmod() function. --- Include/cpython/pytime.h | 16 +++- Lib/test/test_time.py | 49 +++++++++- Modules/_ssl.c | 2 +- Modules/_testcapimodule.c | 45 ++++++++- Modules/selectmodule.c | 2 +- Modules/socketmodule.c | 2 +- Python/pytime.c | 238 +++++++++++++++++++++++++++++++--------------- Python/thread_pthread.h | 6 +- 8 files changed, 269 insertions(+), 91 deletions(-) diff --git a/Include/cpython/pytime.h b/Include/cpython/pytime.h index 8c29585..04c43ac 100644 --- a/Include/cpython/pytime.h +++ b/Include/cpython/pytime.h @@ -16,6 +16,7 @@ extern "C" { typedef int64_t _PyTime_t; #define _PyTime_MIN INT64_MIN #define _PyTime_MAX INT64_MAX +#define _SIZEOF_PYTIME_T 8 typedef enum { /* Round towards minus infinity (-inf). @@ -136,8 +137,9 @@ PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round); -/* Similar to _PyTime_AsTimeval(), but don't raise an exception on error. */ -PyAPI_FUNC(int) _PyTime_AsTimeval_noraise(_PyTime_t t, +/* Similar to _PyTime_AsTimeval() but don't raise an exception on overflow. + On overflow, clamp tv_sec to _PyTime_t min/max. */ +PyAPI_FUNC(void) _PyTime_AsTimeval_clamp(_PyTime_t t, struct timeval *tv, _PyTime_round_t round); @@ -162,6 +164,10 @@ PyAPI_FUNC(int) _PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts); tv_nsec is always positive. Raise an exception and return -1 on error, return 0 on success. */ PyAPI_FUNC(int) _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts); + +/* Similar to _PyTime_AsTimespec() but don't raise an exception on overflow. + On overflow, clamp tv_sec to _PyTime_t min/max. */ +PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts); #endif /* Compute ticks * mul / div. @@ -181,7 +187,7 @@ typedef struct { /* Get the current time from the system clock. If the internal clock fails, silently ignore the error and return 0. - On integer overflow, silently ignore the overflow and truncated the clock to + On integer overflow, silently ignore the overflow and clamp the clock to _PyTime_MIN or _PyTime_MAX. Use _PyTime_GetSystemClockWithInfo() to check for failure. */ @@ -201,7 +207,7 @@ PyAPI_FUNC(int) _PyTime_GetSystemClockWithInfo( results of consecutive calls is valid. If the internal clock fails, silently ignore the error and return 0. - On integer overflow, silently ignore the overflow and truncated the clock to + On integer overflow, silently ignore the overflow and clamp the clock to _PyTime_MIN or _PyTime_MAX. Use _PyTime_GetMonotonicClockWithInfo() to check for failure. */ @@ -232,7 +238,7 @@ PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm); measure a short duration. If the internal clock fails, silently ignore the error and return 0. - On integer overflow, silently ignore the overflow and truncated the clock to + On integer overflow, silently ignore the overflow and clamp the clock to _PyTime_MIN or _PyTime_MAX. Use _PyTime_GetPerfCounterWithInfo() to check for failure. */ diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 3258298..f7fd651 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -38,6 +38,10 @@ class _PyTime(enum.IntEnum): # Round away from zero ROUND_UP = 3 +# _PyTime_t is int64_t +_PyTime_MIN = -2 ** 63 +_PyTime_MAX = 2 ** 63 - 1 + # Rounding modes supported by PyTime ROUNDING_MODES = ( # (PyTime rounding method, decimal rounding method) @@ -960,6 +964,49 @@ class TestCPyTime(CPyTimeTestCase, unittest.TestCase): NS_TO_SEC, value_filter=self.time_t_filter) + @unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimeval_clamp'), + 'need _testcapi.PyTime_AsTimeval_clamp') + def test_AsTimeval_clamp(self): + from _testcapi import PyTime_AsTimeval_clamp + + if sys.platform == 'win32': + from _testcapi import LONG_MIN, LONG_MAX + tv_sec_max = LONG_MAX + tv_sec_min = LONG_MIN + else: + tv_sec_max = self.time_t_max + tv_sec_min = self.time_t_min + + for t in (_PyTime_MIN, _PyTime_MAX): + ts = PyTime_AsTimeval_clamp(t, _PyTime.ROUND_CEILING) + with decimal.localcontext() as context: + context.rounding = decimal.ROUND_CEILING + us = self.decimal_round(decimal.Decimal(t) / US_TO_NS) + tv_sec, tv_usec = divmod(us, SEC_TO_US) + if tv_sec_max < tv_sec: + tv_sec = tv_sec_max + tv_usec = 0 + elif tv_sec < tv_sec_min: + tv_sec = tv_sec_min + tv_usec = 0 + self.assertEqual(ts, (tv_sec, tv_usec)) + + @unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimespec_clamp'), + 'need _testcapi.PyTime_AsTimespec_clamp') + def test_AsTimespec_clamp(self): + from _testcapi import PyTime_AsTimespec_clamp + + for t in (_PyTime_MIN, _PyTime_MAX): + ts = PyTime_AsTimespec_clamp(t) + tv_sec, tv_nsec = divmod(t, NS_TO_SEC) + if self.time_t_max < tv_sec: + tv_sec = self.time_t_max + tv_nsec = 0 + elif tv_sec < self.time_t_min: + tv_sec = self.time_t_min + tv_nsec = 0 + self.assertEqual(ts, (tv_sec, tv_nsec)) + def test_AsMilliseconds(self): from _testcapi import PyTime_AsMilliseconds @@ -1062,7 +1109,7 @@ class TestTimeWeaklinking(unittest.TestCase): clock_names = [ "CLOCK_MONOTONIC", "clock_gettime", "clock_gettime_ns", "clock_settime", "clock_settime_ns", "clock_getres"] - + if mac_ver >= (10, 12): for name in clock_names: self.assertTrue(hasattr(time, name), f"time.{name} is not available") diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 6c63301..411314f 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2264,7 +2264,7 @@ PySSL_select(PySocketSockObject *s, int writing, _PyTime_t timeout) if (!_PyIsSelectable_fd(s->sock_fd)) return SOCKET_TOO_LARGE_FOR_SELECT; - _PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING); + _PyTime_AsTimeval_clamp(timeout, &tv, _PyTime_ROUND_CEILING); FD_ZERO(&fds); FD_SET(s->sock_fd, &fds); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 51323f0..e3eec0c 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4687,7 +4687,32 @@ test_PyTime_AsTimeval(PyObject *self, PyObject *args) if (seconds == NULL) { return NULL; } - return Py_BuildValue("Nl", seconds, tv.tv_usec); + return Py_BuildValue("Nl", seconds, (long)tv.tv_usec); +} + +static PyObject * +test_PyTime_AsTimeval_clamp(PyObject *self, PyObject *args) +{ + PyObject *obj; + int round; + if (!PyArg_ParseTuple(args, "Oi", &obj, &round)) { + return NULL; + } + if (check_time_rounding(round) < 0) { + return NULL; + } + _PyTime_t t; + if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { + return NULL; + } + struct timeval tv; + _PyTime_AsTimeval_clamp(t, &tv, round); + + PyObject *seconds = PyLong_FromLongLong(tv.tv_sec); + if (seconds == NULL) { + return NULL; + } + return Py_BuildValue("Nl", seconds, (long)tv.tv_usec); } #ifdef HAVE_CLOCK_GETTIME @@ -4708,6 +4733,22 @@ test_PyTime_AsTimespec(PyObject *self, PyObject *args) } return Py_BuildValue("Nl", _PyLong_FromTime_t(ts.tv_sec), ts.tv_nsec); } + +static PyObject * +test_PyTime_AsTimespec_clamp(PyObject *self, PyObject *args) +{ + PyObject *obj; + if (!PyArg_ParseTuple(args, "O", &obj)) { + return NULL; + } + _PyTime_t t; + if (_PyTime_FromNanosecondsObject(&t, obj) < 0) { + return NULL; + } + struct timespec ts; + _PyTime_AsTimespec_clamp(t, &ts); + return Py_BuildValue("Nl", _PyLong_FromTime_t(ts.tv_sec), ts.tv_nsec); +} #endif static PyObject * @@ -5872,8 +5913,10 @@ static PyMethodDef TestMethods[] = { {"PyTime_FromSecondsObject", test_pytime_fromsecondsobject, METH_VARARGS}, {"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS}, {"PyTime_AsTimeval", test_PyTime_AsTimeval, METH_VARARGS}, + {"PyTime_AsTimeval_clamp", test_PyTime_AsTimeval_clamp, METH_VARARGS}, #ifdef HAVE_CLOCK_GETTIME {"PyTime_AsTimespec", test_PyTime_AsTimespec, METH_VARARGS}, + {"PyTime_AsTimespec_clamp", test_PyTime_AsTimespec_clamp, METH_VARARGS}, #endif {"PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS}, {"PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS}, diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 3ecd0c3..b71b2c4 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -344,7 +344,7 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, n = 0; break; } - _PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING); + _PyTime_AsTimeval_clamp(timeout, &tv, _PyTime_ROUND_CEILING); /* retry select() with the recomputed timeout */ } } while (1); diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 83f05b7..f474869 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -758,7 +758,7 @@ internal_select(PySocketSockObject *s, int writing, _PyTime_t interval, Py_END_ALLOW_THREADS; #else if (interval >= 0) { - _PyTime_AsTimeval_noraise(interval, &tv, _PyTime_ROUND_CEILING); + _PyTime_AsTimeval_clamp(interval, &tv, _PyTime_ROUND_CEILING); tvp = &tv; } else diff --git a/Python/pytime.c b/Python/pytime.c index b47a573..f6ec191 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -35,6 +35,16 @@ #define NS_TO_US (1000) #define NS_TO_100NS (100) +#if SIZEOF_TIME_T == SIZEOF_LONG_LONG +# define PY_TIME_T_MAX LLONG_MAX +# define PY_TIME_T_MIN LLONG_MIN +#elif SIZEOF_TIME_T == SIZEOF_LONG +# define PY_TIME_T_MAX LONG_MAX +# define PY_TIME_T_MIN LONG_MIN +#else +# error "unsupported time_t size" +#endif + static void pytime_time_t_overflow(void) @@ -63,7 +73,7 @@ pytime_from_nanoseconds(_PyTime_t t) static inline _PyTime_t pytime_as_nanoseconds(_PyTime_t t) { - // _PyTime_t is a number of nanoseconds + // _PyTime_t is a number of nanoseconds: see pytime_from_nanoseconds() return t; } @@ -119,6 +129,48 @@ _PyLong_FromTime_t(time_t t) } +// Convert _PyTime_t to time_t. +// Return 0 on success. Return -1 and clamp the value on overflow. +static int +_PyTime_AsTime_t(_PyTime_t t, time_t *t2) +{ +#if SIZEOF_TIME_T < _SIZEOF_PYTIME_T + if ((_PyTime_t)PY_TIME_T_MAX < t) { + *t2 = PY_TIME_T_MAX; + return -1; + } + if (t < (_PyTime_t)PY_TIME_T_MIN) { + *t2 = PY_TIME_T_MIN; + return -1; + } +#endif + *t2 = (time_t)t; + return 0; +} + + +#ifdef MS_WINDOWS +// Convert _PyTime_t to long. +// Return 0 on success. Return -1 and clamp the value on overflow. +static int +_PyTime_AsLong(_PyTime_t t, long *t2) +{ +#if SIZEOF_LONG < _SIZEOF_PYTIME_T + if ((_PyTime_t)LONG_MAX < t) { + *t2 = LONG_MAX; + return -1; + } + if (t < (_PyTime_t)LONG_MIN) { + *t2 = LONG_MIN; + return -1; + } +#endif + *t2 = (long)t; + return 0; +} +#endif + + /* Round to nearest with ties going to nearest even integer (_PyTime_ROUND_HALF_EVEN) */ static double @@ -515,15 +567,39 @@ _PyTime_AsNanosecondsObject(_PyTime_t t) static _PyTime_t +pytime_divide_round_up(const _PyTime_t t, const _PyTime_t k) +{ + assert(k > 1); + if (t >= 0) { + // Don't use (t + k - 1) / k to avoid integer overflow + // if t=_PyTime_MAX + _PyTime_t q = t / k; + if (t % k) { + q += 1; + } + return q; + } + else { + // Don't use (t - (k - 1)) / k to avoid integer overflow + // if t=_PyTime_MIN + _PyTime_t q = t / k; + if (t % k) { + q -= 1; + } + return q; + } +} + + +static _PyTime_t pytime_divide(const _PyTime_t t, const _PyTime_t k, const _PyTime_round_t round) { assert(k > 1); if (round == _PyTime_ROUND_HALF_EVEN) { - _PyTime_t x, r, abs_r; - x = t / k; - r = t % k; - abs_r = Py_ABS(r); + _PyTime_t x = t / k; + _PyTime_t r = t % k; + _PyTime_t abs_r = Py_ABS(r); if (abs_r > k / 2 || (abs_r == k / 2 && (Py_ABS(x) & 1))) { if (t >= 0) { x++; @@ -536,7 +612,7 @@ pytime_divide(const _PyTime_t t, const _PyTime_t k, } else if (round == _PyTime_ROUND_CEILING) { if (t >= 0) { - return (t + k - 1) / k; + return pytime_divide_round_up(t, k); } else { return t / k; @@ -547,18 +623,41 @@ pytime_divide(const _PyTime_t t, const _PyTime_t k, return t / k; } else { - return (t - (k - 1)) / k; + return pytime_divide_round_up(t, k); } } else { assert(round == _PyTime_ROUND_UP); - if (t >= 0) { - return (t + k - 1) / k; - } - else { - return (t - (k - 1)) / k; + return pytime_divide_round_up(t, k); + } +} + + +// Compute (t / k, t % k) in (pq, pr). +// Make sure that 0 <= pr < k. +// Return 0 on success. +// Return -1 on underflow and store (_PyTime_MIN, 0) in (pq, pr). +static int +pytime_divmod(const _PyTime_t t, const _PyTime_t k, + _PyTime_t *pq, _PyTime_t *pr) +{ + assert(k > 1); + _PyTime_t q = t / k; + _PyTime_t r = t % k; + if (r < 0) { + if (q == _PyTime_MIN) { + *pq = _PyTime_MIN; + *pr = 0; + return -1; } + r += k; + q -= 1; } + assert(0 <= r && r < k); + + *pq = q; + *pr = r; + return 0; } @@ -596,64 +695,41 @@ _PyTime_AsMilliseconds(_PyTime_t t, _PyTime_round_t round) static int -pytime_as_timeval(_PyTime_t t, _PyTime_t *p_secs, int *p_us, +pytime_as_timeval(_PyTime_t t, _PyTime_t *ptv_sec, int *ptv_usec, _PyTime_round_t round) { - _PyTime_t ns, tv_sec; - ns = pytime_as_nanoseconds(t); - tv_sec = ns / SEC_TO_NS; - ns = ns % SEC_TO_NS; - - int tv_usec = (int)pytime_divide(ns, US_TO_NS, round); - int res = 0; - if (tv_usec < 0) { - tv_usec += SEC_TO_US; - if (tv_sec != _PyTime_MIN) { - tv_sec -= 1; - } - else { - res = -1; - } - } - else if (tv_usec >= SEC_TO_US) { - tv_usec -= SEC_TO_US; - if (tv_sec != _PyTime_MAX) { - tv_sec += 1; - } - else { - res = -1; - } - } - assert(0 <= tv_usec && tv_usec < SEC_TO_US); - - *p_secs = tv_sec; - *p_us = tv_usec; + _PyTime_t ns = pytime_as_nanoseconds(t); + _PyTime_t us = pytime_divide(ns, US_TO_NS, round); + _PyTime_t tv_sec, tv_usec; + int res = pytime_divmod(us, SEC_TO_US, &tv_sec, &tv_usec); + *ptv_sec = tv_sec; + *ptv_usec = (int)tv_usec; return res; } static int pytime_as_timeval_struct(_PyTime_t t, struct timeval *tv, - _PyTime_round_t round, int raise) + _PyTime_round_t round, int raise_exc) { - _PyTime_t secs, secs2; - int us; - int res; - - res = pytime_as_timeval(t, &secs, &us, round); + _PyTime_t tv_sec; + int tv_usec; + int res = pytime_as_timeval(t, &tv_sec, &tv_usec, round); + int res2; #ifdef MS_WINDOWS - tv->tv_sec = (long)secs; + // On Windows, timeval.tv_sec type is long + res2 = _PyTime_AsLong(tv_sec, &tv->tv_sec); #else - tv->tv_sec = secs; + res2 = _PyTime_AsTime_t(tv_sec, &tv->tv_sec); #endif - tv->tv_usec = us; + if (res2 < 0) { + tv_usec = 0; + } + tv->tv_usec = tv_usec; - secs2 = (_PyTime_t)tv->tv_sec; - if (res < 0 || secs2 != secs) { - if (raise) { - pytime_time_t_overflow(); - } + if (raise_exc && (res < 0 || res2 < 0)) { + pytime_time_t_overflow(); return -1; } return 0; @@ -667,10 +743,10 @@ _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round) } -int -_PyTime_AsTimeval_noraise(_PyTime_t t, struct timeval *tv, _PyTime_round_t round) +void +_PyTime_AsTimeval_clamp(_PyTime_t t, struct timeval *tv, _PyTime_round_t round) { - return pytime_as_timeval_struct(t, tv, round, 0); + (void)pytime_as_timeval_struct(t, tv, round, 0); } @@ -679,11 +755,12 @@ _PyTime_AsTimevalTime_t(_PyTime_t t, time_t *p_secs, int *us, _PyTime_round_t round) { _PyTime_t secs; - int res = pytime_as_timeval(t, &secs, us, round); - - *p_secs = (time_t)secs; + if (pytime_as_timeval(t, &secs, us, round) < 0) { + pytime_time_t_overflow(); + return -1; + } - if (res < 0 || (_PyTime_t)*p_secs != secs) { + if (_PyTime_AsTime_t(secs, p_secs) < 0) { pytime_time_t_overflow(); return -1; } @@ -692,28 +769,37 @@ _PyTime_AsTimevalTime_t(_PyTime_t t, time_t *p_secs, int *us, #if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_KQUEUE) -int -_PyTime_AsTimespec(_PyTime_t t, struct timespec *ts) +static int +pytime_as_timespec(_PyTime_t t, struct timespec *ts, int raise_exc) { + _PyTime_t ns = pytime_as_nanoseconds(t); _PyTime_t tv_sec, tv_nsec; + int res = pytime_divmod(ns, SEC_TO_NS, &tv_sec, &tv_nsec); - _PyTime_t ns = pytime_as_nanoseconds(t); - tv_sec = ns / SEC_TO_NS; - tv_nsec = ns % SEC_TO_NS; - if (tv_nsec < 0) { - tv_nsec += SEC_TO_NS; - tv_sec -= 1; - } - ts->tv_sec = (time_t)tv_sec; - assert(0 <= tv_nsec && tv_nsec < SEC_TO_NS); + int res2 = _PyTime_AsTime_t(tv_sec, &ts->tv_sec); + if (res2 < 0) { + tv_nsec = 0; + } ts->tv_nsec = tv_nsec; - if ((_PyTime_t)ts->tv_sec != tv_sec) { + if (raise_exc && (res < 0 || res2 < 0)) { pytime_time_t_overflow(); return -1; } return 0; } + +void +_PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts) +{ + (void)pytime_as_timespec(t, ts, 0); +} + +int +_PyTime_AsTimespec(_PyTime_t t, struct timespec *ts) +{ + return pytime_as_timespec(t, ts, 1); +} #endif @@ -918,7 +1004,7 @@ py_get_monotonic_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise) pytime_overflow(); return -1; } - // Truncate to _PyTime_MAX silently. + // Clamp to _PyTime_MAX silently. *tp = _PyTime_MAX; } else { diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index a45d842..7f04151 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -481,11 +481,7 @@ PyThread_acquire_lock_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds, } else if (dt > 0) { _PyTime_t realtime_deadline = _PyTime_GetSystemClock() + dt; - if (_PyTime_AsTimespec(realtime_deadline, &ts) < 0) { - /* Cannot occur thanks to (microseconds > PY_TIMEOUT_MAX) - check done above */ - Py_UNREACHABLE(); - } + _PyTime_AsTimespec_clamp(realtime_deadline, &ts); /* no need to update microseconds value, the code only care if (microseconds > 0 or (microseconds == 0). */ } -- cgit v0.12