diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 93b3382b9c654e..668e1b42d6eaa0 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -7295,6 +7295,25 @@ def test_update_type_cache(self): """) script_helper.assert_python_ok('-c', script) + def test_static_type_at_shutdown(self): + # gh-132413 + script = textwrap.dedent(""" + import _datetime + timedelta = _datetime.timedelta + + def gen(): + try: + yield + finally: + # sys.modules is empty + _datetime.timedelta(days=1) + timedelta(days=1) + + it = gen() + next(it) + """) + script_helper.assert_python_ok('-c', script) + def load_tests(loader, standard_tests, pattern): standard_tests.addTest(ZoneInfoCompleteTest()) diff --git a/Misc/NEWS.d/next/Library/2025-04-16-14-34-04.gh-issue-132413.TvpIn2.rst b/Misc/NEWS.d/next/Library/2025-04-16-14-34-04.gh-issue-132413.TvpIn2.rst new file mode 100644 index 00000000000000..3e778bf54f8e02 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-16-14-34-04.gh-issue-132413.TvpIn2.rst @@ -0,0 +1 @@ +Fix crash in C version of :mod:`datetime` when used during interpreter shutdown. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index eb90be81c8d34c..f4abef607a90e3 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -32,12 +32,10 @@ static PyTypeObject PyDateTime_TimeType; static PyTypeObject PyDateTime_DeltaType; static PyTypeObject PyDateTime_TZInfoType; static PyTypeObject PyDateTime_TimeZoneType; +static PyTypeObject PyDateTime_IsoCalendarDateType; typedef struct { - /* Module heap types. */ - PyTypeObject *isocalendar_date_type; - /* Conversion factors. */ PyObject *us_per_ms; // 1_000 PyObject *us_per_second; // 1_000_000 @@ -75,7 +73,7 @@ typedef struct { #define DELTA_TYPE(st) &PyDateTime_DeltaType #define TZINFO_TYPE(st) &PyDateTime_TZInfoType #define TIMEZONE_TYPE(st) &PyDateTime_TimeZoneType -#define ISOCALENDAR_DATE_TYPE(st) st->isocalendar_date_type +#define ISOCALENDAR_DATE_TYPE(st) &PyDateTime_IsoCalendarDateType #define PyDate_CAST(op) ((PyDateTime_Date *)(op)) #define PyDate_Check(op) PyObject_TypeCheck(op, DATE_TYPE(NO_STATE)) @@ -102,17 +100,17 @@ typedef struct { #define PyIsoCalendarDate_CAST(op) ((PyDateTime_IsoCalendarDate *)(op)) -#define CONST_US_PER_MS(st) st->us_per_ms -#define CONST_US_PER_SECOND(st) st->us_per_second -#define CONST_US_PER_MINUTE(st) st->us_per_minute -#define CONST_US_PER_HOUR(st) st->us_per_hour -#define CONST_US_PER_DAY(st) st->us_per_day -#define CONST_US_PER_WEEK(st) st->us_per_week -#define CONST_SEC_PER_DAY(st) st->seconds_per_day -#define CONST_EPOCH(st) st->epoch #define CONST_UTC(st) ((PyObject *)&utc_timezone) - -static datetime_state * +static inline PyObject *get_const_us_per_ms(datetime_state *st); +static inline PyObject *get_const_us_per_second(datetime_state *st); +static inline PyObject *get_const_us_per_minute(datetime_state *st); +static inline PyObject *get_const_us_per_hour(datetime_state *st); +static inline PyObject *get_const_us_per_day(datetime_state *st); +static inline PyObject *get_const_us_per_week(datetime_state *st); +static inline PyObject *get_const_sec_per_day(datetime_state *st); +static inline PyObject *get_const_epoch(datetime_state *st); + +static inline datetime_state * get_module_state(PyObject *module) { void *state = _PyModule_GetState(module); @@ -173,6 +171,7 @@ _get_current_state(PyObject **p_mod) * so we must re-import the module. */ mod = PyImport_ImportModule("_datetime"); if (mod == NULL) { + PyErr_Clear(); return NULL; } } @@ -184,7 +183,7 @@ _get_current_state(PyObject **p_mod) #define GET_CURRENT_STATE(MOD_VAR) \ _get_current_state(&MOD_VAR) #define RELEASE_CURRENT_STATE(ST_VAR, MOD_VAR) \ - Py_DECREF(MOD_VAR) + Py_XDECREF(MOD_VAR) static int set_current_module(PyInterpreterState *interp, PyObject *mod) @@ -2119,7 +2118,12 @@ delta_to_microseconds(PyDateTime_Delta *self) x1 = PyLong_FromLong(GET_TD_DAYS(self)); if (x1 == NULL) goto Done; - x2 = PyNumber_Multiply(x1, CONST_SEC_PER_DAY(st)); /* days in seconds */ + PyObject *sec_per_day = get_const_sec_per_day(st); + if (sec_per_day == NULL) { + goto Done; + } + x2 = PyNumber_Multiply(x1, sec_per_day); /* days in seconds */ + Py_DECREF(sec_per_day); if (x2 == NULL) goto Done; Py_SETREF(x1, NULL); @@ -2136,7 +2140,12 @@ delta_to_microseconds(PyDateTime_Delta *self) /* x1 = */ x2 = NULL; /* x3 has days+seconds in seconds */ - x1 = PyNumber_Multiply(x3, CONST_US_PER_SECOND(st)); /* us */ + PyObject *us_per_sec = get_const_us_per_second(st); + if (us_per_sec == NULL) { + goto Done; + } + x1 = PyNumber_Multiply(x3, us_per_sec); /* us */ + Py_DECREF(us_per_sec); if (x1 == NULL) goto Done; Py_SETREF(x3, NULL); @@ -2195,7 +2204,12 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); - tuple = checked_divmod(pyus, CONST_US_PER_SECOND(st)); + PyObject *us_per_sec = get_const_us_per_second(st); + if (us_per_sec == NULL) { + goto Done; + } + tuple = checked_divmod(pyus, us_per_sec); + Py_DECREF(us_per_sec); if (tuple == NULL) { goto Done; } @@ -2213,7 +2227,12 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type) num = Py_NewRef(PyTuple_GET_ITEM(tuple, 0)); /* leftover seconds */ Py_DECREF(tuple); - tuple = checked_divmod(num, CONST_SEC_PER_DAY(st)); + PyObject *sec_per_day = get_const_sec_per_day(st); + if (sec_per_day == NULL) { + goto Done; + } + tuple = checked_divmod(num, sec_per_day); + Py_DECREF(sec_per_day); if (tuple == NULL) goto Done; Py_DECREF(num); @@ -2819,27 +2838,57 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw) CLEANUP; } if (ms) { - y = accum("milliseconds", x, ms, CONST_US_PER_MS(st), &leftover_us); + PyObject *us_per_ms = get_const_us_per_ms(st); + if (us_per_ms == NULL) { + goto Done; + } + y = accum("milliseconds", x, ms, us_per_ms, &leftover_us); + Py_DECREF(us_per_ms); CLEANUP; } if (second) { - y = accum("seconds", x, second, CONST_US_PER_SECOND(st), &leftover_us); + PyObject *us_per_sec = get_const_us_per_second(st); + if (us_per_sec == NULL) { + goto Done; + } + y = accum("seconds", x, second, us_per_sec, &leftover_us); + Py_DECREF(us_per_sec); CLEANUP; } if (minute) { - y = accum("minutes", x, minute, CONST_US_PER_MINUTE(st), &leftover_us); + PyObject *us_per_min = get_const_us_per_minute(st); + if (us_per_min == NULL) { + goto Done; + } + y = accum("minutes", x, minute, us_per_min, &leftover_us); + Py_DECREF(us_per_min); CLEANUP; } if (hour) { - y = accum("hours", x, hour, CONST_US_PER_HOUR(st), &leftover_us); + PyObject *us_per_hour = get_const_us_per_hour(st); + if (us_per_hour == NULL) { + goto Done; + } + y = accum("hours", x, hour, us_per_hour, &leftover_us); + Py_DECREF(us_per_hour); CLEANUP; } if (day) { - y = accum("days", x, day, CONST_US_PER_DAY(st), &leftover_us); + PyObject *us_per_day = get_const_us_per_day(st); + if (us_per_day == NULL) { + goto Done; + } + y = accum("days", x, day, us_per_day, &leftover_us); + Py_DECREF(us_per_day); CLEANUP; } if (week) { - y = accum("weeks", x, week, CONST_US_PER_WEEK(st), &leftover_us); + PyObject *us_per_week = get_const_us_per_week(st); + if (us_per_week == NULL) { + goto Done; + } + y = accum("weeks", x, week, us_per_week, &leftover_us); + Py_DECREF(us_per_week); CLEANUP; } if (leftover_us) { @@ -2989,7 +3038,7 @@ delta_getstate(PyDateTime_Delta *self) static PyObject * delta_total_seconds(PyObject *op, PyObject *Py_UNUSED(dummy)) { - PyObject *total_seconds; + PyObject *total_seconds = NULL; PyObject *total_microseconds; total_microseconds = delta_to_microseconds(PyDelta_CAST(op)); @@ -2999,8 +3048,13 @@ delta_total_seconds(PyObject *op, PyObject *Py_UNUSED(dummy)) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); - total_seconds = PyNumber_TrueDivide(total_microseconds, CONST_US_PER_SECOND(st)); - + PyObject *us_per_sec = get_const_us_per_second(st); + if (us_per_sec == NULL) { + goto finally; + } + total_seconds = PyNumber_TrueDivide(total_microseconds, us_per_sec); + Py_DECREF(us_per_sec); +finally: RELEASE_CURRENT_STATE(st, current_mod); Py_DECREF(total_microseconds); return total_seconds; @@ -3697,40 +3751,19 @@ static PyMethodDef iso_calendar_date_methods[] = { {NULL, NULL}, }; -static int -iso_calendar_date_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return PyTuple_Type.tp_traverse(self, visit, arg); -} - -static void -iso_calendar_date_dealloc(PyObject *self) -{ - PyTypeObject *tp = Py_TYPE(self); - PyTuple_Type.tp_dealloc(self); // delegate GC-untrack as well - Py_DECREF(tp); -} - -static PyType_Slot isocal_slots[] = { - {Py_tp_repr, iso_calendar_date_repr}, - {Py_tp_doc, (void *)iso_calendar_date__doc__}, - {Py_tp_methods, iso_calendar_date_methods}, - {Py_tp_getset, iso_calendar_date_getset}, - {Py_tp_new, iso_calendar_date_new}, - {Py_tp_dealloc, iso_calendar_date_dealloc}, - {Py_tp_traverse, iso_calendar_date_traverse}, - {0, NULL}, +static PyTypeObject PyDateTime_IsoCalendarDateType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "datetime.IsoCalendarDate", + .tp_basicsize = sizeof(PyDateTime_IsoCalendarDate), + .tp_repr = (reprfunc) iso_calendar_date_repr, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = iso_calendar_date__doc__, + .tp_methods = iso_calendar_date_methods, + .tp_getset = iso_calendar_date_getset, + // .tp_base = &PyTuple_Type, // filled in PyInit__datetime + .tp_new = iso_calendar_date_new, }; -static PyType_Spec isocal_spec = { - .name = "datetime.IsoCalendarDate", - .basicsize = sizeof(PyDateTime_IsoCalendarDate), - .flags = (Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_IMMUTABLETYPE), - .slots = isocal_slots, -}; /*[clinic input] @classmethod @@ -3779,12 +3812,8 @@ date_isocalendar(PyObject *self, PyObject *Py_UNUSED(dummy)) week = 0; } - PyObject *current_mod = NULL; - datetime_state *st = GET_CURRENT_STATE(current_mod); - - PyObject *v = iso_calendar_date_new_impl(ISOCALENDAR_DATE_TYPE(st), + PyObject *v = iso_calendar_date_new_impl(&PyDateTime_IsoCalendarDateType, year, week + 1, day + 1); - RELEASE_CURRENT_STATE(st, current_mod); if (v == NULL) { return NULL; } @@ -6617,7 +6646,13 @@ local_timezone(PyDateTime_DateTime *utc_time) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); - delta = datetime_subtract((PyObject *)utc_time, CONST_EPOCH(st)); + PyObject *epoch = get_const_epoch(st); + if (epoch == NULL) { + RELEASE_CURRENT_STATE(st, current_mod); + return NULL; + } + delta = datetime_subtract((PyObject *)utc_time, epoch); + Py_DECREF(epoch); RELEASE_CURRENT_STATE(st, current_mod); if (delta == NULL) return NULL; @@ -6861,8 +6896,13 @@ datetime_timestamp(PyObject *op, PyObject *Py_UNUSED(dummy)) PyObject *current_mod = NULL; datetime_state *st = GET_CURRENT_STATE(current_mod); - PyObject *delta; - delta = datetime_subtract(op, CONST_EPOCH(st)); + PyObject *epoch = get_const_epoch(st); + if (epoch == NULL) { + RELEASE_CURRENT_STATE(st, current_mod); + return NULL; + } + PyObject *delta = datetime_subtract(op, epoch); + Py_DECREF(epoch); RELEASE_CURRENT_STATE(st, current_mod); if (delta == NULL) return NULL; @@ -7171,6 +7211,8 @@ static PyTypeObject * const capi_types[] = { &PyDateTime_TZInfoType, /* Indirectly, via the utc object. */ &PyDateTime_TimeZoneType, + /* Not exposed */ + &PyDateTime_IsoCalendarDateType, }; /* The C-API is process-global. This violates interpreter isolation @@ -7227,28 +7269,67 @@ create_timezone_from_delta(int days, int sec, int ms, int normalize) * Module state lifecycle. */ +static inline PyObject * +get_const_us_per_ms(datetime_state *st) { + return st && st->us_per_ms ? Py_NewRef(st->us_per_ms) + : PyLong_FromLong(1000); +} + +static inline PyObject * +get_const_us_per_second(datetime_state *st) { + return st && st->us_per_second ? Py_NewRef(st->us_per_second) + : PyLong_FromLong(1000000); +} + +static inline PyObject * +get_const_us_per_minute(datetime_state *st) { + return st && st->us_per_minute ? Py_NewRef(st->us_per_minute) + : PyLong_FromLong(60000000); +} + +static inline PyObject * +get_const_sec_per_day(datetime_state *st) { + return st && st->seconds_per_day ? Py_NewRef(st->seconds_per_day) + : PyLong_FromLong(24 * 3600); +} + +/* The rest are too big for 32-bit ints, but even + * us_per_week fits in 40 bits, so doubles should be exact. + */ +static inline PyObject * +get_const_us_per_hour(datetime_state *st) { + return st && st->us_per_hour ? Py_NewRef(st->us_per_hour) + : PyLong_FromDouble(3600000000.0); +} + +static inline PyObject * +get_const_us_per_day(datetime_state *st) { + return st && st->us_per_day ? Py_NewRef(st->us_per_day) + : PyLong_FromDouble(86400000000.0); +} + +static inline PyObject * +get_const_us_per_week(datetime_state *st) { + return st && st->us_per_week ? Py_NewRef(st->us_per_week) + : PyLong_FromDouble(604800000000.0); +} + + +static inline PyObject * +get_const_epoch(datetime_state *st) { + return st && st->epoch ? Py_NewRef(st->epoch) + : new_datetime(1970, 1, 1, 0, 0, 0, 0, + (PyObject *)&utc_timezone, 0); +} + + static int init_state(datetime_state *st, PyObject *module, PyObject *old_module) { - /* Each module gets its own heap types. */ -#define ADD_TYPE(FIELD, SPEC, BASE) \ - do { \ - PyObject *cls = PyType_FromModuleAndSpec( \ - module, SPEC, (PyObject *)BASE); \ - if (cls == NULL) { \ - return -1; \ - } \ - st->FIELD = (PyTypeObject *)cls; \ - } while (0) - - ADD_TYPE(isocalendar_date_type, &isocal_spec, &PyTuple_Type); -#undef ADD_TYPE - if (old_module != NULL) { assert(old_module != module); datetime_state *st_old = get_module_state(old_module); *st = (datetime_state){ - .isocalendar_date_type = st->isocalendar_date_type, .us_per_ms = Py_NewRef(st_old->us_per_ms), .us_per_second = Py_NewRef(st_old->us_per_second), .us_per_minute = Py_NewRef(st_old->us_per_minute), @@ -7261,19 +7342,19 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module) return 0; } - st->us_per_ms = PyLong_FromLong(1000); + st->us_per_ms = get_const_us_per_ms(NULL); if (st->us_per_ms == NULL) { return -1; } - st->us_per_second = PyLong_FromLong(1000000); + st->us_per_second = get_const_us_per_second(NULL); if (st->us_per_second == NULL) { return -1; } - st->us_per_minute = PyLong_FromLong(60000000); + st->us_per_minute = get_const_us_per_minute(NULL); if (st->us_per_minute == NULL) { return -1; } - st->seconds_per_day = PyLong_FromLong(24 * 3600); + st->seconds_per_day = get_const_sec_per_day(NULL); if (st->seconds_per_day == NULL) { return -1; } @@ -7281,22 +7362,21 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module) /* The rest are too big for 32-bit ints, but even * us_per_week fits in 40 bits, so doubles should be exact. */ - st->us_per_hour = PyLong_FromDouble(3600000000.0); + st->us_per_hour = get_const_us_per_hour(NULL); if (st->us_per_hour == NULL) { return -1; } - st->us_per_day = PyLong_FromDouble(86400000000.0); + st->us_per_day = get_const_us_per_day(NULL); if (st->us_per_day == NULL) { return -1; } - st->us_per_week = PyLong_FromDouble(604800000000.0); + st->us_per_week = get_const_us_per_week(NULL); if (st->us_per_week == NULL) { return -1; } /* Init Unix epoch */ - st->epoch = new_datetime( - 1970, 1, 1, 0, 0, 0, 0, (PyObject *)&utc_timezone, 0); + st->epoch = get_const_epoch(NULL); if (st->epoch == NULL) { return -1; } @@ -7307,16 +7387,12 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module) static int traverse_state(datetime_state *st, visitproc visit, void *arg) { - /* heap types */ - Py_VISIT(st->isocalendar_date_type); - return 0; } static int clear_state(datetime_state *st) { - Py_CLEAR(st->isocalendar_date_type); Py_CLEAR(st->us_per_ms); Py_CLEAR(st->us_per_second); Py_CLEAR(st->us_per_minute); @@ -7339,6 +7415,7 @@ init_static_types(PyInterpreterState *interp, int reloading) // `&...` is not a constant expression according to a strict reading // of C standards. Fill tp_base at run-time rather than statically. // See https://bugs.python.org/issue40777 + PyDateTime_IsoCalendarDateType.tp_base = &PyTuple_Type; PyDateTime_TimeZoneType.tp_base = &PyDateTime_TZInfoType; PyDateTime_DateTimeType.tp_base = &PyDateTime_DateType; @@ -7385,6 +7462,9 @@ _datetime_exec(PyObject *module) for (size_t i = 0; i < Py_ARRAY_LENGTH(capi_types); i++) { PyTypeObject *type = capi_types[i]; + if (type == &PyDateTime_IsoCalendarDateType) { + continue; + } const char *name = _PyType_Name(type); assert(name != NULL); if (PyModule_AddObjectRef(module, name, (PyObject *)type) < 0) {