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.
This commit is contained in:
Victor Stinner 2021-09-30 02:11:41 +02:00 committed by GitHub
parent 8d3e7eff09
commit 09796f2f14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 270 additions and 92 deletions

View File

@ -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. */

View File

@ -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")

View File

@ -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);

View File

@ -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},

View File

@ -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);

View File

@ -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

View File

@ -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
@ -514,16 +566,40 @@ _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,21 +623,44 @@ 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;
}
_PyTime_t
_PyTime_AsNanoseconds(_PyTime_t t)
{
@ -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);
if (pytime_as_timeval(t, &secs, us, round) < 0) {
pytime_time_t_overflow();
return -1;
}
*p_secs = (time_t)secs;
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 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;
_PyTime_t tv_sec, tv_nsec;
int res = pytime_divmod(ns, SEC_TO_NS, &tv_sec, &tv_nsec);
int res2 = _PyTime_AsTime_t(tv_sec, &ts->tv_sec);
if (res2 < 0) {
tv_nsec = 0;
}
ts->tv_sec = (time_t)tv_sec;
assert(0 <= tv_nsec && tv_nsec < SEC_TO_NS);
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 {

View File

@ -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). */
}