Skip to content

Commit 5c5022b

Browse files
authored
pythongh-112567: Add _PyTimeFraction C API (python#112568)
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Divisor (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * No longer check "numer * denom <= _PyTime_MAX" in _PyTimeFraction_Set(). _PyTimeFraction_Mul() uses _PyTime_Mul() which handles integer overflow.
1 parent 05a370a commit 5c5022b

File tree

3 files changed

+130
-107
lines changed

3 files changed

+130
-107
lines changed

Include/internal/pycore_time.h

+26-7
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,6 @@ PyAPI_FUNC(void) _PyTime_AsTimespec_clamp(_PyTime_t t, struct timespec *ts);
253253
// Compute t1 + t2. Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
254254
extern _PyTime_t _PyTime_Add(_PyTime_t t1, _PyTime_t t2);
255255

256-
// Compute ticks * mul / div.
257-
// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
258-
// The caller must ensure that ((div - 1) * mul) cannot overflow.
259-
extern _PyTime_t _PyTime_MulDiv(_PyTime_t ticks,
260-
_PyTime_t mul,
261-
_PyTime_t div);
262-
263256
// Structure used by time.get_clock_info()
264257
typedef struct {
265258
const char *implementation;
@@ -355,6 +348,32 @@ PyAPI_FUNC(_PyTime_t) _PyDeadline_Init(_PyTime_t timeout);
355348
PyAPI_FUNC(_PyTime_t) _PyDeadline_Get(_PyTime_t deadline);
356349

357350

351+
// --- _PyTimeFraction -------------------------------------------------------
352+
353+
typedef struct {
354+
_PyTime_t numer;
355+
_PyTime_t denom;
356+
} _PyTimeFraction;
357+
358+
// Set a fraction.
359+
// Return 0 on success.
360+
// Return -1 if the fraction is invalid.
361+
extern int _PyTimeFraction_Set(
362+
_PyTimeFraction *frac,
363+
_PyTime_t numer,
364+
_PyTime_t denom);
365+
366+
// Compute ticks * frac.numer / frac.denom.
367+
// Clamp to [_PyTime_MIN; _PyTime_MAX] on overflow.
368+
extern _PyTime_t _PyTimeFraction_Mul(
369+
_PyTime_t ticks,
370+
const _PyTimeFraction *frac);
371+
372+
// Compute a clock resolution: frac.numer / frac.denom / 1e9.
373+
extern double _PyTimeFraction_Resolution(
374+
const _PyTimeFraction *frac);
375+
376+
358377
#ifdef __cplusplus
359378
}
360379
#endif

Modules/timemodule.c

+19-35
Original file line numberDiff line numberDiff line change
@@ -69,25 +69,6 @@ module time
6969
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=a668a08771581f36]*/
7070

7171

72-
#if defined(HAVE_TIMES) || defined(HAVE_CLOCK)
73-
static int
74-
check_ticks_per_second(long tps, const char *context)
75-
{
76-
/* Effectively, check that _PyTime_MulDiv(t, SEC_TO_NS, tps)
77-
cannot overflow. */
78-
if (tps >= 0 && (_PyTime_t)tps > _PyTime_MAX / SEC_TO_NS) {
79-
PyErr_Format(PyExc_OverflowError, "%s is too large", context);
80-
return -1;
81-
}
82-
if (tps < 1) {
83-
PyErr_Format(PyExc_RuntimeError, "invalid %s", context);
84-
return -1;
85-
}
86-
return 0;
87-
}
88-
#endif /* HAVE_TIMES || HAVE_CLOCK */
89-
90-
9172
/* Forward declarations */
9273
static int pysleep(_PyTime_t timeout);
9374

@@ -96,11 +77,11 @@ typedef struct {
9677
PyTypeObject *struct_time_type;
9778
#ifdef HAVE_TIMES
9879
// times() clock frequency in hertz
99-
long ticks_per_second;
80+
_PyTimeFraction times_base;
10081
#endif
10182
#ifdef HAVE_CLOCK
10283
// clock() frequency in hertz
103-
long clocks_per_second;
84+
_PyTimeFraction clock_base;
10485
#endif
10586
} time_module_state;
10687

@@ -174,10 +155,11 @@ Return the current time in nanoseconds since the Epoch.");
174155
static int
175156
py_clock(time_module_state *state, _PyTime_t *tp, _Py_clock_info_t *info)
176157
{
177-
long clocks_per_second = state->clocks_per_second;
158+
_PyTimeFraction *base = &state->clock_base;
159+
178160
if (info) {
179161
info->implementation = "clock()";
180-
info->resolution = 1.0 / (double)clocks_per_second;
162+
info->resolution = _PyTimeFraction_Resolution(base);
181163
info->monotonic = 1;
182164
info->adjustable = 0;
183165
}
@@ -189,7 +171,7 @@ py_clock(time_module_state *state, _PyTime_t *tp, _Py_clock_info_t *info)
189171
"or its value cannot be represented");
190172
return -1;
191173
}
192-
_PyTime_t ns = _PyTime_MulDiv(ticks, SEC_TO_NS, clocks_per_second);
174+
_PyTime_t ns = _PyTimeFraction_Mul(ticks, base);
193175
*tp = _PyTime_FromNanoseconds(ns);
194176
return 0;
195177
}
@@ -1257,7 +1239,7 @@ static int
12571239
process_time_times(time_module_state *state, _PyTime_t *tp,
12581240
_Py_clock_info_t *info)
12591241
{
1260-
long ticks_per_second = state->ticks_per_second;
1242+
_PyTimeFraction *base = &state->times_base;
12611243

12621244
struct tms process;
12631245
if (times(&process) == (clock_t)-1) {
@@ -1266,14 +1248,14 @@ process_time_times(time_module_state *state, _PyTime_t *tp,
12661248

12671249
if (info) {
12681250
info->implementation = "times()";
1251+
info->resolution = _PyTimeFraction_Resolution(base);
12691252
info->monotonic = 1;
12701253
info->adjustable = 0;
1271-
info->resolution = 1.0 / (double)ticks_per_second;
12721254
}
12731255

12741256
_PyTime_t ns;
1275-
ns = _PyTime_MulDiv(process.tms_utime, SEC_TO_NS, ticks_per_second);
1276-
ns += _PyTime_MulDiv(process.tms_stime, SEC_TO_NS, ticks_per_second);
1257+
ns = _PyTimeFraction_Mul(process.tms_utime, base);
1258+
ns += _PyTimeFraction_Mul(process.tms_stime, base);
12771259
*tp = _PyTime_FromNanoseconds(ns);
12781260
return 1;
12791261
}
@@ -1395,8 +1377,7 @@ py_process_time(time_module_state *state, _PyTime_t *tp,
13951377
// times() failed, ignore failure
13961378
#endif
13971379

1398-
/* clock */
1399-
/* Currently, Python 3 requires clock() to build: see issue #22624 */
1380+
/* clock(). Python 3 requires clock() to build (see gh-66814) */
14001381
return py_clock(state, tp, info);
14011382
#endif
14021383
}
@@ -2110,20 +2091,23 @@ time_exec(PyObject *module)
21102091
#endif
21112092

21122093
#ifdef HAVE_TIMES
2113-
if (_Py_GetTicksPerSecond(&state->ticks_per_second) < 0) {
2094+
long ticks_per_second;
2095+
if (_Py_GetTicksPerSecond(&ticks_per_second) < 0) {
21142096
PyErr_SetString(PyExc_RuntimeError,
21152097
"cannot read ticks_per_second");
21162098
return -1;
21172099
}
2118-
2119-
if (check_ticks_per_second(state->ticks_per_second, "_SC_CLK_TCK") < 0) {
2100+
if (_PyTimeFraction_Set(&state->times_base, SEC_TO_NS,
2101+
ticks_per_second) < 0) {
2102+
PyErr_Format(PyExc_OverflowError, "ticks_per_second is too large");
21202103
return -1;
21212104
}
21222105
#endif
21232106

21242107
#ifdef HAVE_CLOCK
2125-
state->clocks_per_second = CLOCKS_PER_SEC;
2126-
if (check_ticks_per_second(state->clocks_per_second, "CLOCKS_PER_SEC") < 0) {
2108+
if (_PyTimeFraction_Set(&state->clock_base, SEC_TO_NS,
2109+
CLOCKS_PER_SEC) < 0) {
2110+
PyErr_Format(PyExc_OverflowError, "CLOCKS_PER_SEC is too large");
21272111
return -1;
21282112
}
21292113
#endif

0 commit comments

Comments
 (0)