Skip to content

Commit b263387

Browse files
gh-117398: Statically Allocate the Datetime C-API (GH-119472)
1 parent 7322ff1 commit b263387

File tree

3 files changed

+89
-34
lines changed

3 files changed

+89
-34
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Objects in the datetime C-API are now all statically allocated, which means
2+
better memory safety, especially when the module is reloaded. This should be
3+
transparent to users.

Modules/_datetimemodule.c

Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,8 @@ new_time_subclass_fold_ex(int hour, int minute, int second, int usecond,
11721172
return t;
11731173
}
11741174

1175+
static PyDateTime_Delta * look_up_delta(int, int, int, PyTypeObject *);
1176+
11751177
/* Create a timedelta instance. Normalize the members iff normalize is
11761178
* true. Passing false is a speed optimization, if you know for sure
11771179
* that seconds and microseconds are already in their proper ranges. In any
@@ -1192,6 +1194,12 @@ new_delta_ex(int days, int seconds, int microseconds, int normalize,
11921194
if (check_delta_day_range(days) < 0)
11931195
return NULL;
11941196

1197+
self = look_up_delta(days, seconds, microseconds, type);
1198+
if (self != NULL) {
1199+
return (PyObject *)self;
1200+
}
1201+
assert(!PyErr_Occurred());
1202+
11951203
self = (PyDateTime_Delta *) (type->tp_alloc(type, 0));
11961204
if (self != NULL) {
11971205
self->hashcode = -1;
@@ -1213,6 +1221,8 @@ typedef struct
12131221
PyObject *name;
12141222
} PyDateTime_TimeZone;
12151223

1224+
static PyDateTime_TimeZone * look_up_timezone(PyObject *offset, PyObject *name);
1225+
12161226
/* Create new timezone instance checking offset range. This
12171227
function does not check the name argument. Caller must assure
12181228
that offset is a timedelta instance and name is either NULL
@@ -1227,6 +1237,12 @@ create_timezone(PyObject *offset, PyObject *name)
12271237
assert(PyDelta_Check(offset));
12281238
assert(name == NULL || PyUnicode_Check(name));
12291239

1240+
self = look_up_timezone(offset, name);
1241+
if (self != NULL) {
1242+
return (PyObject *)self;
1243+
}
1244+
assert(!PyErr_Occurred());
1245+
12301246
self = (PyDateTime_TimeZone *)(type->tp_alloc(type, 0));
12311247
if (self == NULL) {
12321248
return NULL;
@@ -2885,6 +2901,25 @@ static PyTypeObject PyDateTime_DeltaType = {
28852901
0, /* tp_free */
28862902
};
28872903

2904+
// XXX Can we make this const?
2905+
static PyDateTime_Delta zero_delta = {
2906+
PyObject_HEAD_INIT(&PyDateTime_DeltaType)
2907+
/* Letting this be set lazily is a benign race. */
2908+
.hashcode = -1,
2909+
};
2910+
2911+
static PyDateTime_Delta *
2912+
look_up_delta(int days, int seconds, int microseconds, PyTypeObject *type)
2913+
{
2914+
if (days == 0 && seconds == 0 && microseconds == 0
2915+
&& type == zero_delta.ob_base.ob_type)
2916+
{
2917+
return &zero_delta;
2918+
}
2919+
return NULL;
2920+
}
2921+
2922+
28882923
/*
28892924
* PyDateTime_Date implementation.
28902925
*/
@@ -4175,6 +4210,23 @@ static PyTypeObject PyDateTime_TimeZoneType = {
41754210
timezone_new, /* tp_new */
41764211
};
41774212

4213+
// XXX Can we make this const?
4214+
static PyDateTime_TimeZone utc_timezone = {
4215+
PyObject_HEAD_INIT(&PyDateTime_TimeZoneType)
4216+
.offset = (PyObject *)&zero_delta,
4217+
.name = NULL,
4218+
};
4219+
4220+
static PyDateTime_TimeZone *
4221+
look_up_timezone(PyObject *offset, PyObject *name)
4222+
{
4223+
if (offset == utc_timezone.offset && name == NULL) {
4224+
return &utc_timezone;
4225+
}
4226+
return NULL;
4227+
}
4228+
4229+
41784230
/*
41794231
* PyDateTime_Time implementation.
41804232
*/
@@ -6706,44 +6758,42 @@ static PyMethodDef module_methods[] = {
67066758
{NULL, NULL}
67076759
};
67086760

6761+
6762+
/* The C-API is process-global. This violates interpreter isolation
6763+
* due to the objects stored here. Thus each of those objects must
6764+
* be managed carefully. */
6765+
// XXX Can we make this const?
6766+
static PyDateTime_CAPI capi = {
6767+
/* The classes must be readied before used here.
6768+
* That will happen the first time the module is loaded.
6769+
* They aren't safe to be shared between interpreters,
6770+
* but that's okay as long as the module is single-phase init. */
6771+
.DateType = &PyDateTime_DateType,
6772+
.DateTimeType = &PyDateTime_DateTimeType,
6773+
.TimeType = &PyDateTime_TimeType,
6774+
.DeltaType = &PyDateTime_DeltaType,
6775+
.TZInfoType = &PyDateTime_TZInfoType,
6776+
6777+
.TimeZone_UTC = (PyObject *)&utc_timezone,
6778+
6779+
.Date_FromDate = new_date_ex,
6780+
.DateTime_FromDateAndTime = new_datetime_ex,
6781+
.Time_FromTime = new_time_ex,
6782+
.Delta_FromDelta = new_delta_ex,
6783+
.TimeZone_FromTimeZone = new_timezone,
6784+
.DateTime_FromTimestamp = datetime_fromtimestamp,
6785+
.Date_FromTimestamp = datetime_date_fromtimestamp_capi,
6786+
.DateTime_FromDateAndTimeAndFold = new_datetime_ex2,
6787+
.Time_FromTimeAndFold = new_time_ex2,
6788+
};
6789+
67096790
/* Get a new C API by calling this function.
67106791
* Clients get at C API via PyDateTime_IMPORT, defined in datetime.h.
67116792
*/
67126793
static inline PyDateTime_CAPI *
67136794
get_datetime_capi(void)
67146795
{
6715-
PyDateTime_CAPI *capi = PyMem_Malloc(sizeof(PyDateTime_CAPI));
6716-
if (capi == NULL) {
6717-
PyErr_NoMemory();
6718-
return NULL;
6719-
}
6720-
capi->DateType = &PyDateTime_DateType;
6721-
capi->DateTimeType = &PyDateTime_DateTimeType;
6722-
capi->TimeType = &PyDateTime_TimeType;
6723-
capi->DeltaType = &PyDateTime_DeltaType;
6724-
capi->TZInfoType = &PyDateTime_TZInfoType;
6725-
capi->Date_FromDate = new_date_ex;
6726-
capi->DateTime_FromDateAndTime = new_datetime_ex;
6727-
capi->Time_FromTime = new_time_ex;
6728-
capi->Delta_FromDelta = new_delta_ex;
6729-
capi->TimeZone_FromTimeZone = new_timezone;
6730-
capi->DateTime_FromTimestamp = datetime_fromtimestamp;
6731-
capi->Date_FromTimestamp = datetime_date_fromtimestamp_capi;
6732-
capi->DateTime_FromDateAndTimeAndFold = new_datetime_ex2;
6733-
capi->Time_FromTimeAndFold = new_time_ex2;
6734-
// Make sure this function is called after utc has
6735-
// been initialized.
6736-
datetime_state *st = STATIC_STATE();
6737-
assert(st->utc != NULL);
6738-
capi->TimeZone_UTC = st->utc; // borrowed ref
6739-
return capi;
6740-
}
6741-
6742-
static void
6743-
datetime_destructor(PyObject *op)
6744-
{
6745-
void *ptr = PyCapsule_GetPointer(op, PyDateTime_CAPSULE_NAME);
6746-
PyMem_Free(ptr);
6796+
return &capi;
67476797
}
67486798

67496799
static int
@@ -6933,8 +6983,7 @@ _datetime_exec(PyObject *module)
69336983
if (capi == NULL) {
69346984
goto error;
69356985
}
6936-
PyObject *capsule = PyCapsule_New(capi, PyDateTime_CAPSULE_NAME,
6937-
datetime_destructor);
6986+
PyObject *capsule = PyCapsule_New(capi, PyDateTime_CAPSULE_NAME, NULL);
69386987
if (capsule == NULL) {
69396988
PyMem_Free(capi);
69406989
goto error;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ Python/crossinterp_exceptions.h - PyExc_InterpreterNotFoundError -
304304
##-----------------------
305305
## singletons
306306

307+
Modules/_datetimemodule.c - zero_delta -
308+
Modules/_datetimemodule.c - utc_timezone -
309+
Modules/_datetimemodule.c - capi -
307310
Objects/boolobject.c - _Py_FalseStruct -
308311
Objects/boolobject.c - _Py_TrueStruct -
309312
Objects/dictobject.c - empty_keys_struct -

0 commit comments

Comments
 (0)