]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-71587: Establish global state in `_datetime` (#110475)
authorErlend E. Aasland <erlend@python.org>
Thu, 12 Oct 2023 08:28:10 +0000 (10:28 +0200)
committerGitHub <noreply@github.com>
Thu, 12 Oct 2023 08:28:10 +0000 (10:28 +0200)
* Use explicit initialiser for m_base
* Add module state stub; establish global state on stack
* Put conversion factors in state struct
* Move PyDateTime_TimeZone_UTC to state
* Move PyDateTime_Epoch to state struct
* Fix ref leaks in and clean up initialisation

Modules/_datetimemodule.c
Tools/c-analyzer/cpython/globals-to-fix.tsv

index 0d356779cfe1920622f1820ff0008367da6b0766..684a628806fbb54c858cbd7f2dd25dad34e95568 100644 (file)
 
 #define PyTimezone_Check(op) PyObject_TypeCheck(op, &PyDateTime_TimeZoneType)
 
+typedef struct {
+    /* Conversion factors. */
+    PyObject *us_per_ms;       // 1_000
+    PyObject *us_per_second;   // 1_000_000
+    PyObject *us_per_minute;   // 1e6 * 60 as Python int
+    PyObject *us_per_hour;     // 1e6 * 3600 as Python int
+    PyObject *us_per_day;      // 1e6 * 3600 * 24 as Python int
+    PyObject *us_per_week;     // 1e6 * 3600 * 24 * 7 as Python int
+    PyObject *seconds_per_day; // 3600 * 24 as Python int
+
+    /* The interned UTC timezone instance */
+    PyObject *utc;
+
+    /* The interned Unix epoch datetime instance */
+    PyObject *epoch;
+} datetime_state;
+
+static datetime_state _datetime_global_state;
+
+#define STATIC_STATE() (&_datetime_global_state)
+
 /*[clinic input]
 module datetime
 class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType"
@@ -1139,11 +1160,6 @@ typedef struct
     PyObject *name;
 } PyDateTime_TimeZone;
 
-/* The interned UTC timezone instance */
-static PyObject *PyDateTime_TimeZone_UTC;
-/* The interned Epoch datetime instance */
-static PyObject *PyDateTime_Epoch;
-
 /* Create new timezone instance checking offset range.  This
    function does not check the name argument.  Caller must assure
    that offset is a timedelta instance and name is either NULL
@@ -1177,7 +1193,8 @@ new_timezone(PyObject *offset, PyObject *name)
     assert(name == NULL || PyUnicode_Check(name));
 
     if (name == NULL && delta_bool((PyDateTime_Delta *)offset) == 0) {
-        return Py_NewRef(PyDateTime_TimeZone_UTC);
+        datetime_state *st = STATIC_STATE();
+        return Py_NewRef(st->utc);
     }
     if ((GET_TD_DAYS(offset) == -1 &&
             GET_TD_SECONDS(offset) == 0 &&
@@ -1390,7 +1407,8 @@ tzinfo_from_isoformat_results(int rv, int tzoffset, int tz_useconds)
     if (rv == 1) {
         // Create a timezone from offset in seconds (0 returns UTC)
         if (tzoffset == 0) {
-            return Py_NewRef(PyDateTime_TimeZone_UTC);
+            datetime_state *st = STATIC_STATE();
+            return Py_NewRef(st->utc);
         }
 
         PyObject *delta = new_delta(0, tzoffset, tz_useconds, 1);
@@ -1807,19 +1825,6 @@ cmperror(PyObject *a, PyObject *b)
     return NULL;
 }
 
-/* ---------------------------------------------------------------------------
- * Cached Python objects; these are set by the module init function.
- */
-
-/* Conversion factors. */
-static PyObject *us_per_ms = NULL;      /* 1000 */
-static PyObject *us_per_second = NULL;  /* 1000000 */
-static PyObject *us_per_minute = NULL;  /* 1e6 * 60 as Python int */
-static PyObject *us_per_hour = NULL;    /* 1e6 * 3600 as Python int */
-static PyObject *us_per_day = NULL;     /* 1e6 * 3600 * 24 as Python int */
-static PyObject *us_per_week = NULL;    /* 1e6*3600*24*7 as Python int */
-static PyObject *seconds_per_day = NULL; /* 3600*24 as Python int */
-
 /* ---------------------------------------------------------------------------
  * Class implementations.
  */
@@ -1845,7 +1850,8 @@ delta_to_microseconds(PyDateTime_Delta *self)
     x1 = PyLong_FromLong(GET_TD_DAYS(self));
     if (x1 == NULL)
         goto Done;
-    x2 = PyNumber_Multiply(x1, seconds_per_day);        /* days in seconds */
+    datetime_state *st = STATIC_STATE();
+    x2 = PyNumber_Multiply(x1, st->seconds_per_day);        /* days in seconds */
     if (x2 == NULL)
         goto Done;
     Py_SETREF(x1, NULL);
@@ -1862,7 +1868,7 @@ delta_to_microseconds(PyDateTime_Delta *self)
     /* x1 = */ x2 = NULL;
 
     /* x3 has days+seconds in seconds */
-    x1 = PyNumber_Multiply(x3, us_per_second);          /* us */
+    x1 = PyNumber_Multiply(x3, st->us_per_second);          /* us */
     if (x1 == NULL)
         goto Done;
     Py_SETREF(x3, NULL);
@@ -1917,7 +1923,8 @@ microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
     PyObject *num = NULL;
     PyObject *result = NULL;
 
-    tuple = checked_divmod(pyus, us_per_second);
+    datetime_state *st = STATIC_STATE();
+    tuple = checked_divmod(pyus, st->us_per_second);
     if (tuple == NULL) {
         goto Done;
     }
@@ -1935,7 +1942,7 @@ 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, seconds_per_day);
+    tuple = checked_divmod(num, st->seconds_per_day);
     if (tuple == NULL)
         goto Done;
     Py_DECREF(num);
@@ -2535,28 +2542,29 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
         y = accum("microseconds", x, us, _PyLong_GetOne(), &leftover_us);
         CLEANUP;
     }
+    datetime_state *st = STATIC_STATE();
     if (ms) {
-        y = accum("milliseconds", x, ms, us_per_ms, &leftover_us);
+        y = accum("milliseconds", x, ms, st->us_per_ms, &leftover_us);
         CLEANUP;
     }
     if (second) {
-        y = accum("seconds", x, second, us_per_second, &leftover_us);
+        y = accum("seconds", x, second, st->us_per_second, &leftover_us);
         CLEANUP;
     }
     if (minute) {
-        y = accum("minutes", x, minute, us_per_minute, &leftover_us);
+        y = accum("minutes", x, minute, st->us_per_minute, &leftover_us);
         CLEANUP;
     }
     if (hour) {
-        y = accum("hours", x, hour, us_per_hour, &leftover_us);
+        y = accum("hours", x, hour, st->us_per_hour, &leftover_us);
         CLEANUP;
     }
     if (day) {
-        y = accum("days", x, day, us_per_day, &leftover_us);
+        y = accum("days", x, day, st->us_per_day, &leftover_us);
         CLEANUP;
     }
     if (week) {
-        y = accum("weeks", x, week, us_per_week, &leftover_us);
+        y = accum("weeks", x, week, st->us_per_week, &leftover_us);
         CLEANUP;
     }
     if (leftover_us) {
@@ -2711,7 +2719,8 @@ delta_total_seconds(PyObject *self, PyObject *Py_UNUSED(ignored))
     if (total_microseconds == NULL)
         return NULL;
 
-    total_seconds = PyNumber_TrueDivide(total_microseconds, us_per_second);
+    datetime_state *st = STATIC_STATE();
+    total_seconds = PyNumber_TrueDivide(total_microseconds, st->us_per_second);
 
     Py_DECREF(total_microseconds);
     return total_seconds;
@@ -3943,8 +3952,10 @@ timezone_repr(PyDateTime_TimeZone *self)
        to use Py_TYPE(self)->tp_name here. */
     const char *type_name = Py_TYPE(self)->tp_name;
 
-    if (((PyObject *)self) == PyDateTime_TimeZone_UTC)
+    datetime_state *st = STATIC_STATE();
+    if (((PyObject *)self) == st->utc) {
         return PyUnicode_FromFormat("%s.utc", type_name);
+    }
 
     if (self->name == NULL)
         return PyUnicode_FromFormat("%s(%R)", type_name, self->offset);
@@ -3964,11 +3975,14 @@ timezone_str(PyDateTime_TimeZone *self)
     if (self->name != NULL) {
         return Py_NewRef(self->name);
     }
-    if ((PyObject *)self == PyDateTime_TimeZone_UTC ||
+    datetime_state *st = STATIC_STATE();
+    if ((PyObject *)self == st->utc ||
            (GET_TD_DAYS(self->offset) == 0 &&
             GET_TD_SECONDS(self->offset) == 0 &&
             GET_TD_MICROSECONDS(self->offset) == 0))
+    {
         return PyUnicode_FromString("UTC");
+    }
     /* Offset is normalized, so it is negative if days < 0 */
     if (GET_TD_DAYS(self->offset) < 0) {
         sign = '-';
@@ -6134,7 +6148,8 @@ local_timezone(PyDateTime_DateTime *utc_time)
     PyObject *one_second;
     PyObject *seconds;
 
-    delta = datetime_subtract((PyObject *)utc_time, PyDateTime_Epoch);
+    datetime_state *st = STATIC_STATE();
+    delta = datetime_subtract((PyObject *)utc_time, st->epoch);
     if (delta == NULL)
         return NULL;
     one_second = new_delta(0, 1, 0, 0);
@@ -6246,6 +6261,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
     if (result == NULL)
         return NULL;
 
+    datetime_state *st = STATIC_STATE();
     /* Make sure result is aware and UTC. */
     if (!HASTZINFO(result)) {
         temp = (PyObject *)result;
@@ -6257,7 +6273,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
                                     DATE_GET_MINUTE(result),
                                     DATE_GET_SECOND(result),
                                     DATE_GET_MICROSECOND(result),
-                                    PyDateTime_TimeZone_UTC,
+                                    st->utc,
                                     DATE_GET_FOLD(result),
                                     Py_TYPE(result));
         Py_DECREF(temp);
@@ -6266,7 +6282,7 @@ datetime_astimezone(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
     }
     else {
         /* Result is already aware - just replace tzinfo. */
-        Py_SETREF(result->tzinfo, Py_NewRef(PyDateTime_TimeZone_UTC));
+        Py_SETREF(result->tzinfo, Py_NewRef(st->utc));
     }
 
     /* Attach new tzinfo and let fromutc() do the rest. */
@@ -6370,8 +6386,9 @@ datetime_timestamp(PyDateTime_DateTime *self, PyObject *Py_UNUSED(ignored))
     PyObject *result;
 
     if (HASTZINFO(self) && self->tzinfo != Py_None) {
+        datetime_state *st = STATIC_STATE();
         PyObject *delta;
-        delta = datetime_subtract((PyObject *)self, PyDateTime_Epoch);
+        delta = datetime_subtract((PyObject *)self, st->epoch);
         if (delta == NULL)
             return NULL;
         result = delta_total_seconds(delta, NULL);
@@ -6692,10 +6709,11 @@ get_datetime_capi(void)
     capi->Date_FromTimestamp = datetime_date_fromtimestamp_capi;
     capi->DateTime_FromDateAndTimeAndFold = new_datetime_ex2;
     capi->Time_FromTimeAndFold = new_time_ex2;
-    // Make sure this function is called after PyDateTime_TimeZone_UTC has
+    // Make sure this function is called after utc has
     // been initialized.
-    assert(PyDateTime_TimeZone_UTC != NULL);
-    capi->TimeZone_UTC = PyDateTime_TimeZone_UTC; // borrowed ref
+    datetime_state *st = STATIC_STATE();
+    assert(st->utc != NULL);
+    capi->TimeZone_UTC = st->utc; // borrowed ref
     return capi;
 }
 
@@ -6706,6 +6724,85 @@ datetime_destructor(PyObject *op)
     PyMem_Free(ptr);
 }
 
+static int
+datetime_clear(PyObject *module)
+{
+    datetime_state *st = STATIC_STATE();
+
+    Py_CLEAR(st->us_per_ms);
+    Py_CLEAR(st->us_per_second);
+    Py_CLEAR(st->us_per_minute);
+    Py_CLEAR(st->us_per_hour);
+    Py_CLEAR(st->us_per_day);
+    Py_CLEAR(st->us_per_week);
+    Py_CLEAR(st->seconds_per_day);
+    Py_CLEAR(st->utc);
+    Py_CLEAR(st->epoch);
+    return 0;
+}
+
+static PyObject *
+create_timezone_from_delta(int days, int sec, int ms, int normalize)
+{
+    PyObject *delta = new_delta(days, sec, ms, normalize);
+    if (delta == NULL) {
+        return NULL;
+    }
+    PyObject *tz = create_timezone(delta, NULL);
+    Py_DECREF(delta);
+    return tz;
+}
+
+static int
+init_state(datetime_state *st)
+{
+    st->us_per_ms = PyLong_FromLong(1000);
+    if (st->us_per_ms == NULL) {
+        return -1;
+    }
+    st->us_per_second = PyLong_FromLong(1000000);
+    if (st->us_per_second == NULL) {
+        return -1;
+    }
+    st->us_per_minute = PyLong_FromLong(60000000);
+    if (st->us_per_minute == NULL) {
+        return -1;
+    }
+    st->seconds_per_day = PyLong_FromLong(24 * 3600);
+    if (st->seconds_per_day == NULL) {
+        return -1;
+    }
+
+    /* 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);
+    if (st->us_per_hour == NULL) {
+        return -1;
+    }
+    st->us_per_day = PyLong_FromDouble(86400000000.0);
+    if (st->us_per_day == NULL) {
+        return -1;
+    }
+    st->us_per_week = PyLong_FromDouble(604800000000.0);
+    if (st->us_per_week == NULL) {
+        return -1;
+    }
+
+    /* Init UTC timezone */
+    st->utc = create_timezone_from_delta(0, 0, 0, 0);
+    if (st->utc == NULL) {
+        return -1;
+    }
+
+    /* Init Unix epoch */
+    st->epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0, st->utc, 0);
+    if (st->epoch == NULL) {
+        return -1;
+    }
+    return 0;
+}
+
 static int
 _datetime_exec(PyObject *module)
 {
@@ -6727,23 +6824,23 @@ _datetime_exec(PyObject *module)
 
     for (size_t i = 0; i < Py_ARRAY_LENGTH(types); i++) {
         if (PyModule_AddType(module, types[i]) < 0) {
-            return -1;
+            goto error;
         }
     }
 
     if (PyType_Ready(&PyDateTime_IsoCalendarDateType) < 0) {
-        return -1;
+        goto error;
     }
 
 #define DATETIME_ADD_MACRO(dict, c, value_expr)         \
     do {                                                \
         PyObject *value = (value_expr);                 \
         if (value == NULL) {                            \
-            return -1;                                  \
+            goto error;                                 \
         }                                               \
         if (PyDict_SetItemString(dict, c, value) < 0) { \
             Py_DECREF(value);                           \
-            return -1;                                  \
+            goto error;                                 \
         }                                               \
         Py_DECREF(value);                               \
     } while(0)
@@ -6775,77 +6872,54 @@ _datetime_exec(PyObject *module)
                                               999999, Py_None, 0));
     DATETIME_ADD_MACRO(d, "resolution", new_delta(0, 0, 1, 0));
 
-    /* timezone values */
-    d = PyDateTime_TimeZoneType.tp_dict;
-    PyObject *delta = new_delta(0, 0, 0, 0);
-    if (delta == NULL) {
-        return -1;
+    datetime_state *st = STATIC_STATE();
+    if (init_state(st) < 0) {
+        goto error;
     }
 
-    PyObject *x = create_timezone(delta, NULL);
-    Py_DECREF(delta);
-    if (x == NULL) {
-        return -1;
-    }
-    if (PyDict_SetItemString(d, "utc", x) < 0) {
-        Py_DECREF(x);
-        return -1;
+    /* timezone values */
+    d = PyDateTime_TimeZoneType.tp_dict;
+    if (PyDict_SetItemString(d, "utc", st->utc) < 0) {
+        goto error;
     }
 
-    PyDateTime_TimeZone_UTC = x;
-
     /* bpo-37642: These attributes are rounded to the nearest minute for backwards
      * compatibility, even though the constructor will accept a wider range of
      * values. This may change in the future.*/
-    delta = new_delta(-1, 60, 0, 1); /* -23:59 */
-    if (delta == NULL) {
-        return -1;
-    }
 
-    x = create_timezone(delta, NULL);
-    Py_DECREF(delta);
-    DATETIME_ADD_MACRO(d, "min", x);
-
-    delta = new_delta(0, (23 * 60 + 59) * 60, 0, 0); /* +23:59 */
-    if (delta == NULL) {
-        return -1;
-    }
+    /* -23:59 */
+    PyObject *min = create_timezone_from_delta(-1, 60, 0, 1);
+    DATETIME_ADD_MACRO(d, "min", min);
 
-    x = create_timezone(delta, NULL);
-    Py_DECREF(delta);
-    DATETIME_ADD_MACRO(d, "max", x);
+    /* +23:59 */
+    PyObject *max = create_timezone_from_delta(0, (23 * 60 + 59) * 60, 0, 0);
+    DATETIME_ADD_MACRO(d, "max", max);
 
-    /* Epoch */
-    PyDateTime_Epoch = new_datetime(1970, 1, 1, 0, 0, 0, 0,
-                                    PyDateTime_TimeZone_UTC, 0);
-    if (PyDateTime_Epoch == NULL) {
-        return -1;
-    }
-
-    /* module initialization */
+    /* Add module level attributes */
     if (PyModule_AddIntMacro(module, MINYEAR) < 0) {
-        return -1;
+        goto error;
     }
     if (PyModule_AddIntMacro(module, MAXYEAR) < 0) {
-        return -1;
+        goto error;
+    }
+    if (PyModule_AddObjectRef(module, "UTC", st->utc) < 0) {
+        goto error;
     }
 
+    /* At last, set up and add the encapsulated C API */
     PyDateTime_CAPI *capi = get_datetime_capi();
     if (capi == NULL) {
-        return -1;
+        goto error;
     }
-    x = PyCapsule_New(capi, PyDateTime_CAPSULE_NAME, datetime_destructor);
-    if (x == NULL) {
+    PyObject *capsule = PyCapsule_New(capi, PyDateTime_CAPSULE_NAME,
+                                      datetime_destructor);
+    if (capsule == NULL) {
         PyMem_Free(capi);
-        return -1;
-    }
-
-    if (PyModule_Add(module, "datetime_CAPI", x) < 0) {
-        return -1;
+        goto error;
     }
-
-    if (PyModule_AddObjectRef(module, "UTC", PyDateTime_TimeZone_UTC) < 0) {
-        return -1;
+    if (PyModule_Add(module, "datetime_CAPI", capsule) < 0) {
+        PyMem_Free(capi);
+        goto error;
     }
 
     /* A 4-year cycle has an extra leap day over what we'd get from
@@ -6866,54 +6940,16 @@ _datetime_exec(PyObject *module)
     static_assert(DI100Y == 25 * DI4Y - 1, "DI100Y");
     assert(DI100Y == days_before_year(100+1));
 
-    us_per_ms = PyLong_FromLong(1000);
-    if (us_per_ms == NULL) {
-        goto error;
-    }
-    us_per_second = PyLong_FromLong(1000000);
-    if (us_per_second == NULL) {
-        goto error;
-    }
-    us_per_minute = PyLong_FromLong(60000000);
-    if (us_per_minute == NULL) {
-        goto error;
-    }
-    seconds_per_day = PyLong_FromLong(24 * 3600);
-    if (seconds_per_day == NULL) {
-        goto error;
-    }
-
-    /* The rest are too big for 32-bit ints, but even
-     * us_per_week fits in 40 bits, so doubles should be exact.
-     */
-    us_per_hour = PyLong_FromDouble(3600000000.0);
-    if (us_per_hour == NULL) {
-        goto error;
-    }
-    us_per_day = PyLong_FromDouble(86400000000.0);
-    if (us_per_day == NULL) {
-        goto error;
-    }
-    us_per_week = PyLong_FromDouble(604800000000.0);
-    if (us_per_week == NULL) {
-        goto error;
-    }
-
     return 0;
 
 error:
-    Py_XDECREF(us_per_ms);
-    Py_XDECREF(us_per_second);
-    Py_XDECREF(us_per_minute);
-    Py_XDECREF(us_per_hour);
-    Py_XDECREF(us_per_day);
-    Py_XDECREF(us_per_week);
-    Py_XDECREF(seconds_per_day);
+    datetime_clear(module);
     return -1;
 }
+#undef DATETIME_ADD_MACRO
 
 static struct PyModuleDef datetimemodule = {
-    PyModuleDef_HEAD_INIT,
+    .m_base = PyModuleDef_HEAD_INIT,
     .m_name = "_datetime",
     .m_doc = "Fast implementation of the datetime type.",
     .m_size = -1,
index bb85fba895bc25d9a471808105c032ebc887d771..aa8ce49ae8637681f4bf3e8543b1f138496d24a2 100644 (file)
@@ -418,20 +418,12 @@ Modules/_ctypes/_ctypes.c -       _unpickle       -
 Modules/_ctypes/_ctypes.c      PyCArrayType_from_ctype cache   -
 Modules/_cursesmodule.c        -       ModDict -
 Modules/_datetimemodule.c      datetime_strptime       module  -
-Modules/_datetimemodule.c      -       PyDateTime_TimeZone_UTC -
-Modules/_datetimemodule.c      -       PyDateTime_Epoch        -
-Modules/_datetimemodule.c      -       us_per_ms       -
-Modules/_datetimemodule.c      -       us_per_second   -
-Modules/_datetimemodule.c      -       us_per_minute   -
-Modules/_datetimemodule.c      -       us_per_hour     -
-Modules/_datetimemodule.c      -       us_per_day      -
-Modules/_datetimemodule.c      -       us_per_week     -
-Modules/_datetimemodule.c      -       seconds_per_day -
 
 ## state
 Modules/_ctypes/_ctypes.c      -       _ctypes_ptrtype_cache   -
 Modules/_ctypes/_ctypes.c      -       global_state    -
 Modules/_ctypes/ctypes.h       -       global_state    -
+Modules/_datetimemodule.c      -       _datetime_global_state  -
 Modules/_tkinter.c     -       tcl_lock        -
 Modules/_tkinter.c     -       excInCmd        -
 Modules/_tkinter.c     -       valInCmd        -