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) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy