Skip to content

Commit bdaf152

Browse files
[3.14] gh-151039: Fix a crash when _datetime types outlive _datetime module (GH-151044) (#151144)
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 cb96d5e commit bdaf152

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
@@ -7313,6 +7313,36 @@ def func():
73137313
self.assertEqual(out, b"a" * 8)
73147314
self.assertEqual(err, b"")
73157315

7316+
@support.cpython_only
7317+
@support.subTests(("setup", "call"), [
7318+
("obj = _datetime.timedelta", "obj(seconds=2)"),
7319+
("obj = _datetime.timedelta(seconds=2)", "obj.total_seconds()"),
7320+
("obj = _datetime.date(2026, 6, 7)", "obj.isocalendar()"),
7321+
])
7322+
def test_static_datetime_types_outlive_collected_module(self, setup, call):
7323+
# gh-151039: This code used to crash
7324+
script = f"""if True:
7325+
import sys, gc
7326+
import _datetime
7327+
7328+
{setup} # static C type, survives the module
7329+
del sys.modules['_datetime']
7330+
del _datetime
7331+
sys.modules['_datetime'] = None # block re-import
7332+
gc.collect() # module object is collected
7333+
7334+
try:
7335+
{call} # used to be a segmentation fault
7336+
except ImportError:
7337+
pass
7338+
else:
7339+
raise AssertionError("ImportError not raised")
7340+
"""
7341+
rc, out, err = script_helper.assert_python_ok("-c", script)
7342+
self.assertEqual(rc, 0)
7343+
self.assertEqual(out, b'')
7344+
self.assertEqual(err, b'')
7345+
73167346

73177347
def load_tests(loader, standard_tests, pattern):
73187348
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
@@ -125,8 +125,8 @@ get_module_state(PyObject *module)
125125

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

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

@@ -138,20 +138,24 @@ get_current_module(PyInterpreterState *interp)
138138
if (PyDict_GetItemRef(dict, INTERP_KEY, &ref) < 0) {
139139
goto error;
140140
}
141-
if (ref != NULL) {
142-
if (ref != Py_None) {
143-
(void)PyWeakref_GetRef(ref, &mod);
144-
if (mod == Py_None) {
145-
Py_CLEAR(mod);
146-
}
141+
if (ref != NULL && ref != Py_None) {
142+
if (PyWeakref_GetRef(ref, &mod) < 0) {
147143
Py_DECREF(ref);
144+
goto error;
145+
}
146+
if (mod == Py_None) {
147+
Py_CLEAR(mod);
148148
}
149+
Py_DECREF(ref);
149150
}
150-
return mod;
151+
assert(!PyErr_Occurred());
152+
*p_mod = mod;
153+
return mod != NULL;
151154

152155
error:
153156
assert(PyErr_Occurred());
154-
return NULL;
157+
*p_mod = NULL;
158+
return -1;
155159
}
156160

157161
static PyModuleDef datetimemodule;
@@ -160,22 +164,26 @@ static datetime_state *
160164
_get_current_state(PyObject **p_mod)
161165
{
162166
PyInterpreterState *interp = PyInterpreterState_Get();
163-
PyObject *mod = get_current_module(interp);
167+
PyObject *mod;
168+
if (get_current_module(interp, &mod) < 0) {
169+
goto error;
170+
}
164171
if (mod == NULL) {
165-
assert(!PyErr_Occurred());
166-
if (PyErr_Occurred()) {
167-
return NULL;
168-
}
169172
/* The static types can outlive the module,
170173
* so we must re-import the module. */
171174
mod = PyImport_ImportModule("_datetime");
172175
if (mod == NULL) {
173-
return NULL;
176+
goto error;
174177
}
175178
}
176179
datetime_state *st = get_module_state(mod);
177180
*p_mod = mod;
178181
return st;
182+
183+
error:
184+
assert(PyErr_Occurred());
185+
*p_mod = NULL;
186+
return NULL;
179187
}
180188

181189
#define GET_CURRENT_STATE(MOD_VAR) \
@@ -2120,8 +2128,11 @@ delta_to_microseconds(PyDateTime_Delta *self)
21202128
PyObject *x3 = NULL;
21212129
PyObject *result = NULL;
21222130

2123-
PyObject *current_mod = NULL;
2131+
PyObject *current_mod;
21242132
datetime_state *st = GET_CURRENT_STATE(current_mod);
2133+
if (st == NULL) {
2134+
return NULL;
2135+
}
21252136

21262137
x1 = PyLong_FromLong(GET_TD_DAYS(self));
21272138
if (x1 == NULL)
@@ -2199,8 +2210,11 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
21992210
PyObject *num = NULL;
22002211
PyObject *result = NULL;
22012212

2202-
PyObject *current_mod = NULL;
2213+
PyObject *current_mod;
22032214
datetime_state *st = GET_CURRENT_STATE(current_mod);
2215+
if (st == NULL) {
2216+
return NULL;
2217+
}
22042218

22052219
tuple = checked_divmod(pyus, CONST_US_PER_SECOND(st));
22062220
if (tuple == NULL) {
@@ -2786,8 +2800,11 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
27862800
{
27872801
PyObject *self = NULL;
27882802

2789-
PyObject *current_mod = NULL;
2803+
PyObject *current_mod;
27902804
datetime_state *st = GET_CURRENT_STATE(current_mod);
2805+
if (st == NULL) {
2806+
return NULL;
2807+
}
27912808

27922809
/* Argument objects. */
27932810
PyObject *day = NULL;
@@ -3005,8 +3022,12 @@ delta_total_seconds(PyObject *op, PyObject *Py_UNUSED(dummy))
30053022
if (total_microseconds == NULL)
30063023
return NULL;
30073024

3008-
PyObject *current_mod = NULL;
3025+
PyObject *current_mod;
30093026
datetime_state *st = GET_CURRENT_STATE(current_mod);
3027+
if (st == NULL) {
3028+
Py_DECREF(total_microseconds);
3029+
return NULL;
3030+
}
30103031

30113032
total_seconds = PyNumber_TrueDivide(total_microseconds, CONST_US_PER_SECOND(st));
30123033

@@ -3788,8 +3809,11 @@ date_isocalendar(PyObject *self, PyObject *Py_UNUSED(dummy))
37883809
week = 0;
37893810
}
37903811

3791-
PyObject *current_mod = NULL;
3812+
PyObject *current_mod;
37923813
datetime_state *st = GET_CURRENT_STATE(current_mod);
3814+
if (st == NULL) {
3815+
return NULL;
3816+
}
37933817

37943818
PyObject *v = iso_calendar_date_new_impl(ISOCALENDAR_DATE_TYPE(st),
37953819
year, week + 1, day + 1);
@@ -6621,8 +6645,11 @@ local_timezone(PyDateTime_DateTime *utc_time)
66216645
PyObject *one_second;
66226646
PyObject *seconds;
66236647

6624-
PyObject *current_mod = NULL;
6648+
PyObject *current_mod;
66256649
datetime_state *st = GET_CURRENT_STATE(current_mod);
6650+
if (st == NULL) {
6651+
return NULL;
6652+
}
66266653

66276654
delta = datetime_subtract((PyObject *)utc_time, CONST_EPOCH(st));
66286655
RELEASE_CURRENT_STATE(st, current_mod);
@@ -6865,8 +6892,11 @@ datetime_timestamp(PyObject *op, PyObject *Py_UNUSED(dummy))
68656892
PyObject *result;
68666893

68676894
if (HASTZINFO(self) && self->tzinfo != Py_None) {
6868-
PyObject *current_mod = NULL;
6895+
PyObject *current_mod;
68696896
datetime_state *st = GET_CURRENT_STATE(current_mod);
6897+
if (st == NULL) {
6898+
return NULL;
6899+
}
68706900

68716901
PyObject *delta;
68726902
delta = datetime_subtract(op, CONST_EPOCH(st));
@@ -7430,9 +7460,8 @@ _datetime_exec(PyObject *module)
74307460
datetime_state *st = get_module_state(module);
74317461

74327462
PyInterpreterState *interp = PyInterpreterState_Get();
7433-
PyObject *old_module = get_current_module(interp);
7434-
if (PyErr_Occurred()) {
7435-
assert(old_module == NULL);
7463+
PyObject *old_module;
7464+
if (get_current_module(interp, &old_module) < 0) {
74367465
goto error;
74377466
}
74387467
/* We actually set the "current" module right before a successful return. */

0 commit comments

Comments
 (0)