diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 5d5b8e415f3cd2..966ef10114faf6 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -7509,6 +7509,35 @@ 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.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 59af7afcfcc644..7a39439bde1055 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -141,7 +141,10 @@ get_current_module(PyInterpreterState *interp) } if (ref != NULL) { if (ref != Py_None) { - (void)PyWeakref_GetRef(ref, &mod); + if (PyWeakref_GetRef(ref, &mod) < 0) { + Py_DECREF(ref); + goto error; + } if (mod == Py_None) { Py_CLEAR(mod); } @@ -163,7 +166,6 @@ _get_current_state(PyObject **p_mod) PyInterpreterState *interp = PyInterpreterState_Get(); PyObject *mod = get_current_module(interp); if (mod == NULL) { - assert(!PyErr_Occurred()); if (PyErr_Occurred()) { return NULL; } @@ -2130,6 +2132,10 @@ delta_to_microseconds(PyDateTime_Delta *self) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { + assert(current_mod == NULL); + return NULL; + } x1 = PyLong_FromLong(GET_TD_DAYS(self)); if (x1 == NULL) @@ -2209,6 +2215,10 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { + assert(current_mod == NULL); + return NULL; + } tuple = checked_divmod(pyus, CONST_US_PER_SECOND(st)); if (tuple == NULL) { @@ -2817,6 +2827,10 @@ delta_new_impl(PyTypeObject *type, PyObject *days, PyObject *seconds, PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { + assert(current_mod == NULL); + return NULL; + } PyObject *x = NULL; /* running sum of microseconds */ PyObject *y = NULL; /* temp sum of microseconds */ @@ -3016,6 +3030,11 @@ delta_total_seconds(PyObject *op, PyObject *Py_UNUSED(dummy)) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { + assert(current_mod == NULL); + Py_DECREF(total_microseconds); + return NULL; + } total_seconds = PyNumber_TrueDivide(total_microseconds, CONST_US_PER_SECOND(st)); @@ -3869,6 +3888,10 @@ date_isocalendar(PyObject *self, PyObject *Py_UNUSED(dummy)) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { + assert(current_mod == NULL); + return NULL; + } PyObject *v = iso_calendar_date_new_impl(ISOCALENDAR_DATE_TYPE(st), year, week + 1, day + 1); @@ -6802,6 +6825,10 @@ local_timezone(PyDateTime_DateTime *utc_time) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { + assert(current_mod == NULL); + return NULL; + } delta = datetime_subtract((PyObject *)utc_time, CONST_EPOCH(st)); RELEASE_CURRENT_STATE(st, current_mod); @@ -7049,6 +7076,10 @@ datetime_timestamp(PyObject *op, PyObject *Py_UNUSED(dummy)) if (HASTZINFO(self) && self->tzinfo != Py_None) { PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { + assert(current_mod == NULL); + return NULL; + } PyObject *delta; delta = datetime_subtract(op, CONST_EPOCH(st));