Skip to content

Commit 3199f3b

Browse files
[3.15] gh-151039: Fix a crash when _datetime types outlive _datetime module (GH-151044) (#151143)
gh-151039: Fix a crash when `_datetime` types outlive `_datetime` module (GH-151044) (cherry picked from commit 9fdbade) Co-authored-by: sobolevn <mail@sobolevn.me>
1 parent 2d432f2 commit 3199f3b

3 files changed

Lines changed: 86 additions & 26 deletions

File tree

Lib/test/datetimetester.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7509,6 +7509,36 @@ def func():
75097509
self.assertEqual(out, b"a" * 8)
75107510
self.assertEqual(err, b"")
75117511

7512+
@support.cpython_only
7513+
@support.subTests(("setup", "call"), [
7514+
("obj = _datetime.timedelta", "obj(seconds=2)"),
7515+
("obj = _datetime.timedelta(seconds=2)", "obj.total_seconds()"),
7516+
("obj = _datetime.date(2026, 6, 7)", "obj.isocalendar()"),
7517+
])
7518+
def test_static_datetime_types_outlive_collected_module(self, setup, call):
7519+
# gh-151039: This code used to crash
7520+
script = f"""if True:
7521+
import sys, gc
7522+
import _datetime
7523+
7524+
{setup} # static C type, survives the module
7525+
del sys.modules['_datetime']
7526+
del _datetime
7527+
sys.modules['_datetime'] = None # block re-import
7528+
gc.collect() # module object is collected
7529+
7530+
try:
7531+
{call} # used to be a segmentation fault
7532+
except ImportError:
7533+
pass
7534+
else:
7535+
raise AssertionError("ImportError not raised")
7536+
"""
7537+
rc, out, err = script_helper.assert_python_ok("-c", script)
7538+
self.assertEqual(rc, 0)
7539+
self.assertEqual(out, b'')
7540+
self.assertEqual(err, b'')
7541+
75127542

75137543
def load_tests(loader, standard_tests, pattern):
75147544
standard_tests.addTest(ZoneInfoCompleteTest())
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a crash when static :mod:`datetime` types outlive the ``_datetime`` module.

Modules/_datetimemodule.c

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ get_module_state(PyObject *module)
126126

127127
#define INTERP_KEY ((PyObject *)&_Py_ID(cached_datetime_module))
128128

129-
static PyObject *
130-
get_current_module(PyInterpreterState *interp)
129+
static int
130+
get_current_module(PyInterpreterState *interp, PyObject **p_mod)
131131
{
132132
PyObject *mod = NULL;
133133

@@ -139,20 +139,24 @@ get_current_module(PyInterpreterState *interp)
139139
if (PyDict_GetItemRef(dict, INTERP_KEY, &ref) < 0) {
140140
goto error;
141141
}
142-
if (ref != NULL) {
143-
if (ref != Py_None) {
144-
(void)PyWeakref_GetRef(ref, &mod);
145-
if (mod == Py_None) {
146-
Py_CLEAR(mod);
147-
}
142+
if (ref != NULL && ref != Py_None) {
143+
if (PyWeakref_GetRef(ref, &mod) < 0) {
148144
Py_DECREF(ref);
145+
goto error;
146+
}
147+
if (mod == Py_None) {
148+
Py_CLEAR(mod);
149149
}
150+
Py_DECREF(ref);
150151
}
151-
return mod;
152+
assert(!PyErr_Occurred());
153+
*p_mod = mod;
154+
return mod != NULL;
152155

153156
error:
154157
assert(PyErr_Occurred());
155-
return NULL;
158+
*p_mod = NULL;
159+
return -1;
156160
}
157161

158162
static PyModuleDef datetimemodule;
@@ -161,22 +165,26 @@ static datetime_state *
161165
_get_current_state(PyObject **p_mod)
162166
{
163167
PyInterpreterState *interp = PyInterpreterState_Get();
164-
PyObject *mod = get_current_module(interp);
168+
PyObject *mod;
169+
if (get_current_module(interp, &mod) < 0) {
170+
goto error;
171+
}
165172
if (mod == NULL) {
166-
assert(!PyErr_Occurred());
167-
if (PyErr_Occurred()) {
168-
return NULL;
169-
}
170173
/* The static types can outlive the module,
171174
* so we must re-import the module. */
172175
mod = PyImport_ImportModule("_datetime");
173176
if (mod == NULL) {
174-
return NULL;
177+
goto error;
175178
}
176179
}
177180
datetime_state *st = get_module_state(mod);
178181
*p_mod = mod;
179182
return st;
183+
184+
error:
185+
assert(PyErr_Occurred());
186+
*p_mod = NULL;
187+
return NULL;
180188
}
181189

182190
#define GET_CURRENT_STATE(MOD_VAR) \
@@ -2128,8 +2136,11 @@ delta_to_microseconds(PyDateTime_Delta *self)
21282136
PyObject *x3 = NULL;
21292137
PyObject *result = NULL;
21302138

2131-
PyObject *current_mod = NULL;
2139+
PyObject *current_mod;
21322140
datetime_state *st = GET_CURRENT_STATE(current_mod);
2141+
if (st == NULL) {
2142+
return NULL;
2143+
}
21332144

21342145
x1 = PyLong_FromLong(GET_TD_DAYS(self));
21352146
if (x1 == NULL)
@@ -2207,8 +2218,11 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
22072218
PyObject *num = NULL;
22082219
PyObject *result = NULL;
22092220

2210-
PyObject *current_mod = NULL;
2221+
PyObject *current_mod;
22112222
datetime_state *st = GET_CURRENT_STATE(current_mod);
2223+
if (st == NULL) {
2224+
return NULL;
2225+
}
22122226

22132227
tuple = checked_divmod(pyus, CONST_US_PER_SECOND(st));
22142228
if (tuple == NULL) {
@@ -2815,8 +2829,11 @@ delta_new_impl(PyTypeObject *type, PyObject *days, PyObject *seconds,
28152829
{
28162830
PyObject *self = NULL;
28172831

2818-
PyObject *current_mod = NULL;
2832+
PyObject *current_mod;
28192833
datetime_state *st = GET_CURRENT_STATE(current_mod);
2834+
if (st == NULL) {
2835+
return NULL;
2836+
}
28202837

28212838
PyObject *x = NULL; /* running sum of microseconds */
28222839
PyObject *y = NULL; /* temp sum of microseconds */
@@ -3014,8 +3031,12 @@ delta_total_seconds(PyObject *op, PyObject *Py_UNUSED(dummy))
30143031
if (total_microseconds == NULL)
30153032
return NULL;
30163033

3017-
PyObject *current_mod = NULL;
3034+
PyObject *current_mod;
30183035
datetime_state *st = GET_CURRENT_STATE(current_mod);
3036+
if (st == NULL) {
3037+
Py_DECREF(total_microseconds);
3038+
return NULL;
3039+
}
30193040

30203041
total_seconds = PyNumber_TrueDivide(total_microseconds, CONST_US_PER_SECOND(st));
30213042

@@ -3867,8 +3888,11 @@ date_isocalendar(PyObject *self, PyObject *Py_UNUSED(dummy))
38673888
week = 0;
38683889
}
38693890

3870-
PyObject *current_mod = NULL;
3891+
PyObject *current_mod;
38713892
datetime_state *st = GET_CURRENT_STATE(current_mod);
3893+
if (st == NULL) {
3894+
return NULL;
3895+
}
38723896

38733897
PyObject *v = iso_calendar_date_new_impl(ISOCALENDAR_DATE_TYPE(st),
38743898
year, week + 1, day + 1);
@@ -6800,8 +6824,11 @@ local_timezone(PyDateTime_DateTime *utc_time)
68006824
PyObject *one_second;
68016825
PyObject *seconds;
68026826

6803-
PyObject *current_mod = NULL;
6827+
PyObject *current_mod;
68046828
datetime_state *st = GET_CURRENT_STATE(current_mod);
6829+
if (st == NULL) {
6830+
return NULL;
6831+
}
68056832

68066833
delta = datetime_subtract((PyObject *)utc_time, CONST_EPOCH(st));
68076834
RELEASE_CURRENT_STATE(st, current_mod);
@@ -7047,8 +7074,11 @@ datetime_timestamp(PyObject *op, PyObject *Py_UNUSED(dummy))
70477074
PyObject *result;
70487075

70497076
if (HASTZINFO(self) && self->tzinfo != Py_None) {
7050-
PyObject *current_mod = NULL;
7077+
PyObject *current_mod;
70517078
datetime_state *st = GET_CURRENT_STATE(current_mod);
7079+
if (st == NULL) {
7080+
return NULL;
7081+
}
70527082

70537083
PyObject *delta;
70547084
delta = datetime_subtract(op, CONST_EPOCH(st));
@@ -7581,9 +7611,8 @@ _datetime_exec(PyObject *module)
75817611
datetime_state *st = get_module_state(module);
75827612

75837613
PyInterpreterState *interp = PyInterpreterState_Get();
7584-
PyObject *old_module = get_current_module(interp);
7585-
if (PyErr_Occurred()) {
7586-
assert(old_module == NULL);
7614+
PyObject *old_module;
7615+
if (get_current_module(interp, &old_module) < 0) {
75877616
goto error;
75887617
}
75897618
/* We actually set the "current" module right before a successful return. */

0 commit comments

Comments
 (0)