https://github.com/python/cpython/commit/9fdbade99e6bcc607d9f12416bfca5bbf94022b9
commit: 9fdbade99e6bcc607d9f12416bfca5bbf94022b9
branch: main
author: sobolevn <[email protected]>
committer: sobolevn <[email protected]>
date: 2026-06-09T11:44:37Z
summary:
gh-151039: Fix a crash when `_datetime` types outlive `_datetime` module
(#151044)
files:
A Misc/NEWS.d/next/Library/2026-06-07-17-29-33.gh-issue-151039.AZ0qBn.rst
M Lib/test/datetimetester.py
M Modules/_datetimemodule.c
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 5d5b8e415f3cd2..d26e41982deb81 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -7509,6 +7509,36 @@ def func():
self.assertEqual(out, b"a" * 8)
self.assertEqual(err, b"")
+ @support.cpython_only
+ @support.subTests(("setup", "call"), [
+ ("obj = _datetime.timedelta", "obj(seconds=2)"),
+ ("obj = _datetime.timedelta(seconds=2)", "obj.total_seconds()"),
+ ("obj = _datetime.date(2026, 6, 7)", "obj.isocalendar()"),
+ ])
+ def test_static_datetime_types_outlive_collected_module(self, setup, call):
+ # gh-151039: This code used to crash
+ script = f"""if True:
+ import sys, gc
+ import _datetime
+
+ {setup} # static C type, survives the
module
+ del sys.modules['_datetime']
+ del _datetime
+ sys.modules['_datetime'] = None # block re-import
+ gc.collect() # module object is collected
+
+ try:
+ {call} # used to be a segmentation fault
+ except ImportError:
+ pass
+ else:
+ raise AssertionError("ImportError not raised")
+ """
+ rc, out, err = script_helper.assert_python_ok("-c", script)
+ self.assertEqual(rc, 0)
+ self.assertEqual(out, b'')
+ self.assertEqual(err, b'')
+
def load_tests(loader, standard_tests, pattern):
standard_tests.addTest(ZoneInfoCompleteTest())
diff --git
a/Misc/NEWS.d/next/Library/2026-06-07-17-29-33.gh-issue-151039.AZ0qBn.rst
b/Misc/NEWS.d/next/Library/2026-06-07-17-29-33.gh-issue-151039.AZ0qBn.rst
new file mode 100644
index 00000000000000..1e99567f555057
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-07-17-29-33.gh-issue-151039.AZ0qBn.rst
@@ -0,0 +1 @@
+Fix a crash when static :mod:`datetime` types outlive the ``_datetime`` module.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 30317ea823ff7e..82b1f898a3c674 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -126,8 +126,8 @@ get_module_state(PyObject *module)
#define INTERP_KEY ((PyObject *)&_Py_ID(cached_datetime_module))
-static PyObject *
-get_current_module(PyInterpreterState *interp)
+static int
+get_current_module(PyInterpreterState *interp, PyObject **p_mod)
{
PyObject *mod = NULL;
@@ -139,20 +139,24 @@ get_current_module(PyInterpreterState *interp)
if (PyDict_GetItemRef(dict, INTERP_KEY, &ref) < 0) {
goto error;
}
- if (ref != NULL) {
- if (ref != Py_None) {
- (void)PyWeakref_GetRef(ref, &mod);
- if (mod == Py_None) {
- Py_CLEAR(mod);
- }
+ if (ref != NULL && ref != Py_None) {
+ if (PyWeakref_GetRef(ref, &mod) < 0) {
Py_DECREF(ref);
+ goto error;
+ }
+ if (mod == Py_None) {
+ Py_CLEAR(mod);
}
+ Py_DECREF(ref);
}
- return mod;
+ assert(!PyErr_Occurred());
+ *p_mod = mod;
+ return mod != NULL;
error:
assert(PyErr_Occurred());
- return NULL;
+ *p_mod = NULL;
+ return -1;
}
static PyModuleDef datetimemodule;
@@ -161,22 +165,26 @@ static datetime_state *
_get_current_state(PyObject **p_mod)
{
PyInterpreterState *interp = PyInterpreterState_Get();
- PyObject *mod = get_current_module(interp);
+ PyObject *mod;
+ if (get_current_module(interp, &mod) < 0) {
+ goto error;
+ }
if (mod == NULL) {
- assert(!PyErr_Occurred());
- if (PyErr_Occurred()) {
- return NULL;
- }
/* The static types can outlive the module,
* so we must re-import the module. */
mod = PyImport_ImportModule("_datetime");
if (mod == NULL) {
- return NULL;
+ goto error;
}
}
datetime_state *st = get_module_state(mod);
*p_mod = mod;
return st;
+
+error:
+ assert(PyErr_Occurred());
+ *p_mod = NULL;
+ return NULL;
}
#define GET_CURRENT_STATE(MOD_VAR) \
@@ -2128,8 +2136,11 @@ delta_to_microseconds(PyDateTime_Delta *self)
PyObject *x3 = NULL;
PyObject *result = NULL;
- PyObject *current_mod = NULL;
+ PyObject *current_mod;
datetime_state *st = GET_CURRENT_STATE(current_mod);
+ if (st == NULL) {
+ return NULL;
+ }
x1 = PyLong_FromLong(GET_TD_DAYS(self));
if (x1 == NULL)
@@ -2207,8 +2218,11 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject
*type)
PyObject *num = NULL;
PyObject *result = NULL;
- PyObject *current_mod = NULL;
+ PyObject *current_mod;
datetime_state *st = GET_CURRENT_STATE(current_mod);
+ if (st == NULL) {
+ return NULL;
+ }
tuple = checked_divmod(pyus, CONST_US_PER_SECOND(st));
if (tuple == NULL) {
@@ -2815,8 +2829,11 @@ delta_new_impl(PyTypeObject *type, PyObject *days,
PyObject *seconds,
{
PyObject *self = NULL;
- PyObject *current_mod = NULL;
+ PyObject *current_mod;
datetime_state *st = GET_CURRENT_STATE(current_mod);
+ if (st == NULL) {
+ return NULL;
+ }
PyObject *x = NULL; /* running sum of microseconds */
PyObject *y = NULL; /* temp sum of microseconds */
@@ -3014,8 +3031,12 @@ delta_total_seconds(PyObject *op, PyObject
*Py_UNUSED(dummy))
if (total_microseconds == NULL)
return NULL;
- PyObject *current_mod = NULL;
+ PyObject *current_mod;
datetime_state *st = GET_CURRENT_STATE(current_mod);
+ if (st == NULL) {
+ Py_DECREF(total_microseconds);
+ return NULL;
+ }
total_seconds = PyNumber_TrueDivide(total_microseconds,
CONST_US_PER_SECOND(st));
@@ -3867,8 +3888,11 @@ date_isocalendar(PyObject *self, PyObject
*Py_UNUSED(dummy))
week = 0;
}
- PyObject *current_mod = NULL;
+ PyObject *current_mod;
datetime_state *st = GET_CURRENT_STATE(current_mod);
+ if (st == NULL) {
+ return NULL;
+ }
PyObject *v = iso_calendar_date_new_impl(ISOCALENDAR_DATE_TYPE(st),
year, week + 1, day + 1);
@@ -6800,8 +6824,11 @@ local_timezone(PyDateTime_DateTime *utc_time)
PyObject *one_second;
PyObject *seconds;
- PyObject *current_mod = NULL;
+ PyObject *current_mod;
datetime_state *st = GET_CURRENT_STATE(current_mod);
+ if (st == NULL) {
+ return NULL;
+ }
delta = datetime_subtract((PyObject *)utc_time, CONST_EPOCH(st));
RELEASE_CURRENT_STATE(st, current_mod);
@@ -7047,8 +7074,11 @@ datetime_timestamp(PyObject *op, PyObject
*Py_UNUSED(dummy))
PyObject *result;
if (HASTZINFO(self) && self->tzinfo != Py_None) {
- PyObject *current_mod = NULL;
+ PyObject *current_mod;
datetime_state *st = GET_CURRENT_STATE(current_mod);
+ if (st == NULL) {
+ return NULL;
+ }
PyObject *delta;
delta = datetime_subtract(op, CONST_EPOCH(st));
@@ -7581,9 +7611,8 @@ _datetime_exec(PyObject *module)
datetime_state *st = get_module_state(module);
PyInterpreterState *interp = PyInterpreterState_Get();
- PyObject *old_module = get_current_module(interp);
- if (PyErr_Occurred()) {
- assert(old_module == NULL);
+ PyObject *old_module;
+ if (get_current_module(interp, &old_module) < 0) {
goto error;
}
/* We actually set the "current" module right before a successful return.
*/
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]