diff --git a/src/persistent/_compat.h b/src/persistent/_compat.h deleted file mode 100644 index 18d0e3b..0000000 --- a/src/persistent/_compat.h +++ /dev/null @@ -1,32 +0,0 @@ -/***************************************************************************** - - Copyright (c) 2012 Zope Foundation and Contributors. - All Rights Reserved. - - This software is subject to the provisions of the Zope Public License, - Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. - THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED - WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS - FOR A PARTICULAR PURPOSE - - ****************************************************************************/ - -#ifndef PERSISTENT__COMPAT_H -#define PERSISTENT__COMPAT_H - -#include "Python.h" - -#define INTERN PyUnicode_InternFromString -#define INTERN_INPLACE PyUnicode_InternInPlace -#define NATIVE_CHECK_EXACT PyUnicode_CheckExact -#define NATIVE_FROM_STRING_AND_SIZE PyUnicode_FromStringAndSize - -#define Py_TPFLAGS_HAVE_RICHCOMPARE 0 - -#define INT_FROM_LONG(x) PyLong_FromLong(x) -#define INT_CHECK(x) PyLong_Check(x) -#define INT_AS_LONG(x) PyLong_AsLong(x) -#define CAPI_CAPSULE_NAME "persistent.cPersistence.CAPI" - -#endif diff --git a/src/persistent/_timestamp.c b/src/persistent/_timestamp.c index b0cda3f..2a5428b 100644 --- a/src/persistent/_timestamp.c +++ b/src/persistent/_timestamp.c @@ -16,16 +16,30 @@ #include "Python.h" #include "bytesobject.h" #include -#include "_compat.h" - -PyObject *TimeStamp_FromDate(int, int, int, int, int, double); -PyObject *TimeStamp_FromString(const char *); - -static char TimeStampModule_doc[] = -"A 64-bit TimeStamp used as a ZODB serial number.\n" -"\n" -"$Id$\n"; +#if PY_VERSION_HEX < 0x030b0000 +#define USE_HEAP_ALLOCATED_TYPE 0 +#define USE_STATIC_TYPE 1 +#define USE_STATIC_MODULE_INIT 1 +#define USE_MULTIPHASE_MOD_INIT 0 +#else +#define USE_HEAP_ALLOCATED_TYPE 1 +#define USE_STATIC_TYPE 0 +#define USE_STATIC_MODULE_INIT 0 +#define USE_MULTIPHASE_MOD_INIT 1 +#endif + +static char timestamp_module_doc[] = +"A 64-bit TimeStamp used as a ZODB serial number.\n"; + +/* + * Forward declarations + */ +static PyObject *TimeStamp_FromDate(PyObject*, int, int, int, int, int, double); +static PyObject *TimeStamp_FromString(PyObject*, const char *); +static PyObject* _get_module(PyTypeObject *typeobj); +static int _get_gmoff(PyObject* module, double* target); +static PyTypeObject* _get_timestamp_type(PyObject* module); /* A magic constant having the value 0.000000013969839. When an @@ -83,23 +97,6 @@ static char TimeStampModule_doc[] = */ #define TS_UNPACK_UINT32_FROM_BYTES(bytes) (*(bytes) * 0x1000000U + *(bytes + 1) * 0x10000U + *(bytes + 2) * 0x100U + *(bytes + 3)) -typedef struct -{ - PyObject_HEAD - /* - The first four bytes of data store the year, month, day, hour, and - minute as the number of minutes since Jan 1 00:00. - - The final four bytes store the seconds since 00:00 as - the number of microseconds. - - Both are normalized into those four bytes the same way with - TS_[UN]PACK_UINT32_INTO|FROM_BYTES. - */ - - unsigned char data[8]; -} TimeStamp; - /* The first dimension of the arrays below is non-leapyear / leapyear */ static char month_len[2][12] = @@ -114,8 +111,6 @@ static short joff[2][12] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335} }; -static double gmoff=0; - static int leap(int year) @@ -130,7 +125,7 @@ days_in_month(int year, int month) } static double -TimeStamp_yad(int y) +_yad(int y) { double d, s; @@ -139,22 +134,23 @@ TimeStamp_yad(int y) d = (y - 1) * 365; if (y > 0) { s = 1.0; - y -= 1; + y -= 1; } else { - s = -1.0; - y = -y; + s = -1.0; + y = -y; } return d + s * (y / 4 - y / 100 + (y + 300) / 400); } static double -TimeStamp_abst(int y, int mo, int d, int m, int s) +_abst(int y, int mo, int d, int m, int s) { - return (TimeStamp_yad(y) + joff[leap(y)][mo] + d) * 86400 + m * 60 + s; + return (_yad(y) + joff[leap(y)][mo] + d) * 86400 + m * 60 + s; } + static int -TimeStamp_init_gmoff(void) +_init_gmoff(double *p_gmoff) { struct tm *t; time_t z=0; @@ -166,17 +162,46 @@ TimeStamp_init_gmoff(void) return -1; } - gmoff = TimeStamp_abst(t->tm_year + TS_BASE_YEAR, t->tm_mon, t->tm_mday - 1, - t->tm_hour * 60 + t->tm_min, t->tm_sec); + *p_gmoff = _abst( + t->tm_year + TS_BASE_YEAR, + t->tm_mon, + t->tm_mday - 1, + t->tm_hour * 60 + t->tm_min, + t->tm_sec + ); return 0; } -static void -TimeStamp_dealloc(TimeStamp *ts) +/* + * TimeStamp type + */ + +typedef struct { - PyObject_Del(ts); + PyObject_HEAD + /* + The first four bytes of data store the year, month, day, hour, and + minute as the number of minutes since Jan 1 00:00. + + The final four bytes store the seconds since 00:00 as + the number of microseconds. + + Both are normalized into those four bytes the same way with + TS_[UN]PACK_UINT32_INTO|FROM_BYTES. + */ + + unsigned char data[8]; +} TimeStamp; + +#if USE_HEAP_ALLOCATED_TYPE +static int +TimeStamp_traverse(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(self)); + return 0; } +#endif static PyObject* TimeStamp_richcompare(TimeStamp *self, TimeStamp *other, int op) @@ -219,7 +244,7 @@ TimeStamp_richcompare(TimeStamp *self, TimeStamp *other, int op) static Py_hash_t -TimeStamp_hash(TimeStamp *self) +TimeStamp_hash(TimeStamp* self) { register unsigned char *p = (unsigned char *)self->data; register int len = 8; @@ -268,7 +293,7 @@ TimeStamp_year(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); - return INT_FROM_LONG(p.y); + return PyLong_FromLong(p.y); } static PyObject * @@ -276,7 +301,7 @@ TimeStamp_month(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); - return INT_FROM_LONG(p.m); + return PyLong_FromLong(p.m); } static PyObject * @@ -284,7 +309,7 @@ TimeStamp_day(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); - return INT_FROM_LONG(p.d); + return PyLong_FromLong(p.d); } static PyObject * @@ -292,7 +317,7 @@ TimeStamp_hour(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); - return INT_FROM_LONG(p.mi / 60); + return PyLong_FromLong(p.mi / 60); } static PyObject * @@ -300,7 +325,7 @@ TimeStamp_minute(TimeStamp *self) { TimeStampParts p; TimeStamp_unpack(self, &p); - return INT_FROM_LONG(p.mi % 60); + return PyLong_FromLong(p.mi % 60); } static PyObject * @@ -312,10 +337,22 @@ TimeStamp_second(TimeStamp *self) static PyObject * TimeStamp_timeTime(TimeStamp *self) { + PyObject* obj_self = (PyObject*)self; + PyObject* module; + double gmoff; + + module = _get_module(Py_TYPE(obj_self)); + if (module == NULL) + return NULL; + + if (_get_gmoff(module, &gmoff) < 0) + return NULL; + TimeStampParts p; TimeStamp_unpack(self, &p); - return PyFloat_FromDouble(TimeStamp_abst(p.y, p.m - 1, p.d - 1, p.mi, 0) - + TimeStamp_sec(self) - gmoff); + return PyFloat_FromDouble( + _abst(p.y, p.m - 1, p.d - 1, p.mi, 0) + TimeStamp_sec(self) - gmoff + ); } static PyObject * @@ -325,7 +362,7 @@ TimeStamp_raw(TimeStamp *self) } static PyObject * -TimeStamp_repr(TimeStamp *self) +TimeStamp_repr(TimeStamp* self) { PyObject *raw, *result; raw = TimeStamp_raw(self); @@ -335,24 +372,26 @@ TimeStamp_repr(TimeStamp *self) } static PyObject * -TimeStamp_str(TimeStamp *self) +TimeStamp_str(TimeStamp* self) { char buf[128]; TimeStampParts p; int len; TimeStamp_unpack(self, &p); - len =sprintf(buf, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%09.6f", + len = snprintf(buf, 128, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%09.6f", p.y, p.m, p.d, p.mi / 60, p.mi % 60, TimeStamp_sec(self)); - return NATIVE_FROM_STRING_AND_SIZE(buf, len); + return PyUnicode_FromStringAndSize(buf, len); } static PyObject * TimeStamp_laterThan(TimeStamp *self, PyObject *obj) { + PyObject* obj_self = (PyObject*)self; + PyObject* module; TimeStamp *o = NULL; TimeStampParts p; unsigned char new[8]; @@ -363,6 +402,7 @@ TimeStamp_laterThan(TimeStamp *self, PyObject *obj) PyErr_SetString(PyExc_TypeError, "expected TimeStamp object"); return NULL; } + o = (TimeStamp *)obj; if (memcmp(self->data, o->data, 8) > 0) { @@ -370,6 +410,10 @@ TimeStamp_laterThan(TimeStamp *self, PyObject *obj) return (PyObject *)self; } + module = _get_module(Py_TYPE(obj_self)); + if (module == NULL) + return NULL; + memcpy(new, o->data, 8); for (i = 7; i > 3; i--) { @@ -378,7 +422,7 @@ TimeStamp_laterThan(TimeStamp *self, PyObject *obj) else { new[i]++; - return TimeStamp_FromString((const char*)new); + return TimeStamp_FromString(module, (const char*)new); } } @@ -405,7 +449,7 @@ TimeStamp_laterThan(TimeStamp *self, PyObject *obj) else p.mi++; - return TimeStamp_FromDate(p.y, p.m, p.d, p.mi / 60, p.mi % 60, 0); + return TimeStamp_FromDate(module, p.y, p.m, p.d, p.mi / 60, p.mi % 60, 0); } static struct PyMethodDef TimeStamp_methods[] = @@ -422,53 +466,75 @@ static struct PyMethodDef TimeStamp_methods[] = {NULL, NULL}, }; -#define DEFERRED_ADDRESS(ADDR) 0 - -static PyTypeObject TimeStamp_type = -{ - PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(NULL), 0) - "persistent.TimeStamp", - sizeof(TimeStamp), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)TimeStamp_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - (reprfunc)TimeStamp_repr, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - (hashfunc)TimeStamp_hash, /* tp_hash */ - 0, /* tp_call */ - (reprfunc)TimeStamp_str, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_HAVE_RICHCOMPARE, /* tp_flags */ - 0, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - (richcmpfunc)&TimeStamp_richcompare, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - TimeStamp_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ +static char TimeStamp__name__[] = "persistent.TimeStamp"; +static char TimeStamp__doc__[] = "Timestamp object used as object ID"; + +#if USE_STATIC_TYPE + +/* + * Static type: TimeStamp + */ + +static PyTypeObject TimeStamp_type_def = +{ + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = TimeStamp__name__, + .tp_doc = TimeStamp__doc__, + .tp_basicsize = sizeof(TimeStamp), + .tp_flags = Py_TPFLAGS_DEFAULT | + Py_TPFLAGS_BASETYPE, + .tp_str = (reprfunc)TimeStamp_str, + .tp_repr = (reprfunc)TimeStamp_repr, + .tp_hash = (hashfunc)TimeStamp_hash, + .tp_richcompare = (richcmpfunc)TimeStamp_richcompare, + .tp_methods = TimeStamp_methods, +}; + +#else + +/* + * Heap type: TimeStamp + */ + +static PyType_Slot TimeStamp_type_slots[] = { + {Py_tp_doc, TimeStamp__doc__}, + {Py_tp_str, TimeStamp_str}, + {Py_tp_repr, TimeStamp_repr}, + {Py_tp_hash, TimeStamp_hash}, + {Py_tp_richcompare, TimeStamp_richcompare}, + {Py_tp_traverse, TimeStamp_traverse}, + {Py_tp_methods, TimeStamp_methods}, + {0, NULL} +}; + +static PyType_Spec TimeStamp_type_spec = { + .name = TimeStamp__name__, + .basicsize = sizeof(TimeStamp), + .flags = Py_TPFLAGS_DEFAULT | + Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, + .slots = TimeStamp_type_slots }; +#endif + + +/* + * Module functions + */ + PyObject * -TimeStamp_FromString(const char *buf) +TimeStamp_FromString(PyObject* module, const char *buf) { /* buf must be exactly 8 characters */ - TimeStamp *ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type); + PyTypeObject* timestamp_type; + TimeStamp *ts; + + timestamp_type = _get_timestamp_type(module); + if (timestamp_type == NULL) + return NULL; + + ts = (TimeStamp *)timestamp_type->tp_alloc(timestamp_type, 0); memcpy(ts->data, buf, 8); return (PyObject *)ts; } @@ -480,12 +546,18 @@ TimeStamp_FromString(const char *buf) } PyObject * -TimeStamp_FromDate(int year, int month, int day, int hour, int min, - double sec) +TimeStamp_FromDate( + PyObject* module, + int year, + int month, + int day, + int hour, + int min, + double sec) { + PyTypeObject* timestamp_type; TimeStamp *ts = NULL; - int d; unsigned int years_since_base; unsigned int months_since_base; unsigned int days_since_base; @@ -497,10 +569,7 @@ TimeStamp_FromDate(int year, int month, int day, int hour, int min, return PyErr_Format(PyExc_ValueError, "year must be greater than %d: %d", TS_BASE_YEAR, year); CHECK_RANGE(month, 1, 12); - d = days_in_month(year, month - 1); - if (day < 1 || day > d) - return PyErr_Format(PyExc_ValueError, - "day must be between 1 and %d: %d", d, day); + CHECK_RANGE(day, 1, days_in_month(year, month - 1)); CHECK_RANGE(hour, 0, 23); CHECK_RANGE(min, 0, 59); /* Seconds are allowed to be anything, so chill @@ -509,7 +578,12 @@ TimeStamp_FromDate(int year, int month, int day, int hour, int min, return PyErr_Format(PyExc_ValueError, "second must be between 0 and 59: %f", sec); */ - ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type); + timestamp_type = _get_timestamp_type(module); + if (timestamp_type == NULL) + return NULL; + + ts = (TimeStamp *)timestamp_type->tp_alloc(timestamp_type, 0); + /* months come in 1-based, hours and minutes come in 0-based */ /* The base time is Jan 1, 00:00 of TS_BASE_YEAR */ years_since_base = year - TS_BASE_YEAR; @@ -527,7 +601,7 @@ TimeStamp_FromDate(int year, int month, int day, int hour, int min, } PyObject * -TimeStamp_TimeStamp(PyObject *obj, PyObject *args) +TimeStamp_TimeStamp(PyObject *module, PyObject *args) { char *buf = NULL; Py_ssize_t len = 0; @@ -542,54 +616,171 @@ TimeStamp_TimeStamp(PyObject *obj, PyObject *args) "8-byte array expected"); return NULL; } - return TimeStamp_FromString(buf); + return TimeStamp_FromString(module, buf); } PyErr_Clear(); if (!PyArg_ParseTuple(args, "iii|iid", &y, &mo, &d, &h, &m, &sec)) return NULL; - return TimeStamp_FromDate(y, mo, d, h, m, sec); + return TimeStamp_FromDate(module, y, mo, d, h, m, sec); +} + + +/* + * Module initialization + */ + +static struct PyModuleDef timestamp_module_def; + +typedef struct { + PyTypeObject* timestamp_type; + double gmoff; +} timestamp_module_state; + +static int +timestamp_module_traverse(PyObject *module, visitproc visit, void *arg) +{ + timestamp_module_state* state = PyModule_GetState(module); + Py_VISIT(state->timestamp_type); + return 0; } -static PyMethodDef TimeStampModule_functions[] = +static int +timestamp_module_clear(PyObject *module) +{ + timestamp_module_state* state = PyModule_GetState(module); + Py_CLEAR(state->timestamp_type); + return 0; +} + +static PyObject* +_get_module(PyTypeObject *typeobj) +{ +#if USE_STATIC_MODULE_INIT + return PyState_FindModule(×tamp_module_def); +#else + if (PyType_Check(typeobj)) { + /* Only added in Python 3.9 */ + return PyType_GetModule(typeobj); + } + + PyErr_SetString(PyExc_TypeError, "_get_module: called w/ non-type"); + return NULL; +#endif +} + +static int +_get_gmoff(PyObject* module, double* target) +{ + timestamp_module_state* state = PyModule_GetState(module); + if (state == NULL) + return -1; + + *target = state->gmoff; + return 0; +} + +static PyTypeObject* +_get_timestamp_type(PyObject* module) +{ + timestamp_module_state* state = PyModule_GetState(module); + if (state == NULL) + return NULL; + + return state->timestamp_type; +} + +static PyMethodDef timestamp_module_functions[] = { {"TimeStamp", TimeStamp_TimeStamp, METH_VARARGS}, {NULL, NULL}, }; -static struct PyModuleDef moduledef = +static int +timestamp_module_exec(PyObject* module) +{ + PyTypeObject* timestamp_type; + timestamp_module_state* state; + double my_gmoff; + + if (_init_gmoff(&my_gmoff) < 0) + return -1; + + state = (timestamp_module_state*)PyModule_GetState(module); + if (state == NULL) { + return -1; + } + state->gmoff = my_gmoff; + +#if USE_STATIC_MODULE_INIT + if (PyType_Ready(&TimeStamp_type_def) < 0) + return -1; + + timestamp_type = &TimeStamp_type_def; +#else + timestamp_type = (PyTypeObject*)PyType_FromModuleAndSpec( + module, &TimeStamp_type_spec, NULL); + + if (timestamp_type == NULL) + return -1; +#endif + + state->timestamp_type = timestamp_type; + return 0; +} + +#if USE_MULTIPHASE_MOD_INIT +/* Slot definitions for multi-phase initialization + * + * See: https://docs.python.org/3/c-api/module.html#multi-phase-initialization + * and: https://peps.python.org/pep-0489 + */ +static PyModuleDef_Slot timestamp_module_slots[] = { + {Py_mod_exec, timestamp_module_exec}, + {0, NULL} +}; +#endif + +static struct PyModuleDef timestamp_module_def = { PyModuleDef_HEAD_INIT, - "_timestamp", /* m_name */ - TimeStampModule_doc, /* m_doc */ - -1, /* m_size */ - TimeStampModule_functions, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ + .m_name = "_timestamp", + .m_doc = timestamp_module_doc, + .m_size = sizeof(timestamp_module_state), + .m_traverse = timestamp_module_traverse, + .m_clear = timestamp_module_clear, + .m_methods = timestamp_module_functions, +#if USE_MULTIPHASE_MOD_INIT + .m_slots = timestamp_module_slots, +#endif }; static PyObject* -module_init(void) +timestamp_module_init(void) { + +#if USE_STATIC_MODULE_INIT PyObject *module; - if (TimeStamp_init_gmoff() < 0) - return NULL; + module = PyModule_Create(×tamp_module_def); - module = PyModule_Create(&moduledef); if (module == NULL) return NULL; - ((PyObject*)&TimeStamp_type)->ob_type = &PyType_Type; - TimeStamp_type.tp_getattro = PyObject_GenericGetAttr; + if (timestamp_module_exec(module) < 0) + return NULL; return module; + +#else + return PyModuleDef_Init(×tamp_module_def); +#endif + } -PyMODINIT_FUNC PyInit__timestamp(void) +PyMODINIT_FUNC +PyInit__timestamp(void) { - return module_init(); + return timestamp_module_init(); } diff --git a/src/persistent/cPersistence.c b/src/persistent/cPersistence.c index adb2dd6..45f4a5f 100644 --- a/src/persistent/cPersistence.c +++ b/src/persistent/cPersistence.c @@ -11,37 +11,62 @@ FOR A PARTICULAR PURPOSE ****************************************************************************/ -static char cPersistence_doc_string[] = - "Defines Persistent mixin class for persistent objects.\n" - "\n" - "$Id$\n"; +static char CP_module__name__[] = "cPersistence"; +static char CP_module__doc__[] = + "Defines Persistent mixin class for persistent objects."; + + +#include +#include #define PY_SSIZE_T_CLEAN #include "cPersistence.h" #include "structmember.h" + +#if PY_VERSION_HEX < 0x030b0000 +#define USE_HEAP_ALLOCATED_TYPE 0 +#define USE_STATIC_TYPE 1 +#define USE_STATIC_MOD_INIT 1 +#define USE_MULTIPHASE_MOD_INIT 0 +#else +#define USE_HEAP_ALLOCATED_TYPE 1 +#define USE_STATIC_TYPE 0 +#define USE_STATIC_MOD_INIT 0 +#define USE_MULTIPHASE_MOD_INIT 1 +#endif + struct ccobject_head_struct { CACHE_HEAD }; -#include -#include - /* Strings initialized by init_strings() below. */ -static PyObject *py_keys, *py_setstate, *py___dict__, *py_timeTime; -static PyObject *py__p_changed, *py__p_deactivate; -static PyObject *py___getattr__, *py___setattr__, *py___delattr__; -static PyObject *py___slotnames__, *copy_reg_slotnames, *__newobj__; -static PyObject *py___getnewargs__, *py___getstate__; -static PyObject *py_unsaved, *py_ghost, *py_saved, *py_changed, *py_sticky; - +static PyObject *py_keys; +static PyObject *py_setstate; +static PyObject *py___dict__; +static PyObject *py_timeTime; +static PyObject *py__p_changed; +static PyObject *py__p_deactivate; +static PyObject *py___getattr__; +static PyObject *py___setattr__; +static PyObject *py___delattr__; +static PyObject *py___slotnames__; +static PyObject *py___getnewargs__; +static PyObject *py___getstate__; +static PyObject *py_unsaved; +static PyObject *py_ghost; +static PyObject *py_saved; +static PyObject *py_changed; +static PyObject *py_sticky; +static PyObject *py_register; +static PyObject *py_readCurrent; static int init_strings(void) { #define INIT_STRING(S) \ - if (!(py_ ## S = INTERN(#S))) \ + if (!(py_ ## S = PyUnicode_InternFromString(#S))) \ return -1; INIT_STRING(keys); INIT_STRING(setstate); @@ -60,10 +85,16 @@ init_strings(void) INIT_STRING(saved); INIT_STRING(changed); INIT_STRING(sticky); + INIT_STRING(register); + INIT_STRING(readCurrent); #undef INIT_STRING return 0; } +static cPersistenceCAPIstruct* _get_capi_struct(PyTypeObject *typeobj); +static PyObject* _get_copyreg_slotnames(PyTypeObject *typeobj); +static PyObject* _get_copyreg___newobj__(PyTypeObject *typeobj); + #ifdef Py_DEBUG static void fatal_1350(cPersistentObject *self, const char *caller, const char *detail) @@ -139,7 +170,7 @@ unghostify(cPersistentObject *self) /****************************************************************************/ -static PyTypeObject Pertype; +static PyTypeObject Per_type_def; static void accessed(cPersistentObject *self) @@ -195,7 +226,7 @@ ghostify(cPersistentObject *self) /* clear all slots besides _p_* * ( for backward-compatibility reason we do this only if class does not * override __new__ ) */ - if (Py_TYPE(self)->tp_new == Pertype.tp_new) + if (Py_TYPE(self)->tp_new == Per_type_def.tp_new) { /* later we might clear an AttributeError but * if we have a pending exception that still needs to be @@ -255,11 +286,8 @@ changed(cPersistentObject *self) && self->jar) { PyObject *meth, *arg, *result; - static PyObject *s_register; - if (s_register == NULL) - s_register = INTERN("register"); - meth = PyObject_GetAttr((PyObject *)self->jar, s_register); + meth = PyObject_GetAttr((PyObject *)self->jar, py_register); if (meth == NULL) return -1; arg = PyTuple_New(1); @@ -290,13 +318,9 @@ readCurrent(cPersistentObject *self) self->state == cPersistent_STICKY_STATE) && self->jar && self->oid) { - static PyObject *s_readCurrent=NULL; PyObject *r; - if (s_readCurrent == NULL) - s_readCurrent = INTERN("readCurrent"); - - r = PyObject_CallMethodObjArgs(self->jar, s_readCurrent, self, NULL); + r = PyObject_CallMethodObjArgs(self->jar, py_readCurrent, self, NULL); if (r == NULL) return -1; @@ -362,6 +386,7 @@ Per__p_invalidate(cPersistentObject *self) static PyObject * pickle_slotnames(PyTypeObject *cls) { + PyObject *copyreg_slotnames; PyObject *slotnames; slotnames = PyDict_GetItem(cls->tp_dict, py___slotnames__); @@ -377,7 +402,9 @@ pickle_slotnames(PyTypeObject *cls) return slotnames; } - slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames, + copyreg_slotnames = _get_copyreg_slotnames(cls); + + slotnames = PyObject_CallFunctionObjArgs(copyreg_slotnames, (PyObject*)cls, NULL); if (slotnames && !(slotnames == Py_None || PyList_Check(slotnames))) { @@ -602,9 +629,9 @@ pickle___setstate__(PyObject *self, PyObject *state) while (PyDict_Next(state, &i, &d_key, &d_value)) { /* normally the keys for instance attributes are interned. we should try to do that here. */ - if (NATIVE_CHECK_EXACT(d_key)) { + if (PyUnicode_CheckExact(d_key)) { Py_INCREF(d_key); - INTERN_INPLACE(&d_key); + PyUnicode_InternInPlace(&d_key); Py_DECREF(d_key); } if (PyObject_SetItem(*dict, d_key, d_value) < 0) @@ -647,9 +674,9 @@ pickle___setstate__(PyObject *self, PyObject *state) return NULL; } - if (NATIVE_CHECK_EXACT(d_key)) { + if (PyUnicode_CheckExact(d_key)) { Py_INCREF(d_key); - INTERN_INPLACE(&d_key); + PyUnicode_InternInPlace(&d_key); Py_DECREF(d_key); } Py_DECREF(item); @@ -677,8 +704,13 @@ static char pickle___reduce__doc[] = static PyObject * pickle___reduce__(PyObject *self) { - PyObject *args=NULL, *bargs=NULL, *state=NULL, *getnewargs=NULL; - int l, i; + PyObject *args=NULL; + PyObject *bargs=NULL; + PyObject *state=NULL; + PyObject *getnewargs=NULL; + PyObject *copyreg___newobj__; + int l; + int i; getnewargs = PyObject_GetAttr(self, py___getnewargs__); if (getnewargs) @@ -713,7 +745,8 @@ pickle___reduce__(PyObject *self) if (!state) goto end; - state = Py_BuildValue("(OON)", __newobj__, args, state); + copyreg___newobj__ = _get_copyreg___newobj__(Py_TYPE(self)); + state = Py_BuildValue("(OON)", copyreg___newobj__, args, state); end: Py_XDECREF(bargs); @@ -760,6 +793,9 @@ Per__getstate__(cPersistentObject *self) static void Per_dealloc(cPersistentObject *self) { + cPersistenceCAPIstruct* capi_struct; + PyTypeObject* type = Py_TYPE(self); + PyObject_GC_UnTrack((PyObject *)self); if (self->state >= 0) { @@ -778,30 +814,30 @@ Per_dealloc(cPersistentObject *self) } if (self->cache) - cPersistenceCAPI->percachedel(self->cache, self->oid); + { + capi_struct = _get_capi_struct(Py_TYPE((PyObject*)self)); + if (capi_struct != NULL) + capi_struct->percachedel(self->cache, self->oid); + } Py_XDECREF(self->cache); Py_XDECREF(self->jar); Py_XDECREF(self->oid); - Py_TYPE(self)->tp_free(self); + type->tp_free(self); +#if USE_HEAP_ALLOCATED_TYPE + Py_DECREF(type); +#endif } static int Per_traverse(cPersistentObject *self, visitproc visit, void *arg) { - int err; - -#define VISIT(SLOT) \ - if (SLOT) { \ - err = visit((PyObject *)(SLOT), arg); \ - if (err) \ - return err; \ - } - - VISIT(self->jar); - VISIT(self->oid); - VISIT(self->cache); +#if USE_HEAP_ALLOCATED_TYPE + Py_VISIT(Py_TYPE((PyObject*)self)); +#endif + Py_VISIT(self->jar); + Py_VISIT(self->oid); + Py_VISIT(self->cache); -#undef VISIT return 0; } @@ -958,7 +994,9 @@ Per_setattro(cPersistentObject *self, PyObject *name, PyObject *v) { if (unghostify(self) < 0) goto Done; + accessed(self); + if (strncmp(s, "_v_", 3) != 0 && self->state != cPersistent_CHANGED_STATE) { @@ -1262,13 +1300,13 @@ Per_get_mtime(cPersistentObject *self) static PyObject * Per_get_state(cPersistentObject *self) { - return INT_FROM_LONG(self->state); + return PyLong_FromLong(self->state); } static PyObject * Per_get_estimated_size(cPersistentObject *self) { - return INT_FROM_LONG(_estimated_size_in_bytes(self->estimated_size)); + return PyLong_FromLong(_estimated_size_in_bytes(self->estimated_size)); } static int @@ -1276,9 +1314,9 @@ Per_set_estimated_size(cPersistentObject *self, PyObject *v) { if (v) { - if (INT_CHECK(v)) + if (PyLong_Check(v)) { - long lv = INT_AS_LONG(v); + long lv = PyLong_AsLong(v); if (lv < 0) { PyErr_SetString(PyExc_ValueError, @@ -1571,41 +1609,61 @@ static struct PyMethodDef Per_methods[] = { */ #define DEFERRED_ADDRESS(ADDR) 0 -static PyTypeObject Pertype = { +static char Per__name__[] = "persistent.Persistent"; +static char Per__doc__[] = "Base class for persistent objects"; + +#if USE_HEAP_ALLOCATED_TYPE + +/* + * Heap type: Persistent + */ + +static PyType_Slot Per_type_slots[] = { + {Py_tp_doc, Per__doc__}, + {Py_tp_repr, Per_repr}, + {Py_tp_getattro, Per_getattro}, + {Py_tp_setattro, Per_setattro}, + {Py_tp_traverse, Per_traverse}, + {Py_tp_dealloc, Per_dealloc}, + {Py_tp_methods, Per_methods}, + {Py_tp_getset, Per_getsets}, + {0, NULL} +}; + +static PyType_Spec Per_type_spec = { + .name = Per__name__, + .basicsize = sizeof(cPersistentObject), + .flags = Py_TPFLAGS_DEFAULT | + Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, + .slots = Per_type_slots +}; + +#else + +/* + * Static type: Persistent + */ + +static PyTypeObject Per_type_def = { PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) - "persistent.Persistent", /* tp_name */ - sizeof(cPersistentObject), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)Per_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - (reprfunc)Per_repr, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - (getattrofunc)Per_getattro, /* tp_getattro */ - (setattrofunc)Per_setattro, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_HAVE_GC, /* tp_flags */ - 0, /* tp_doc */ - (traverseproc)Per_traverse, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Per_methods, /* tp_methods */ - 0, /* tp_members */ - Per_getsets, /* tp_getset */ + .tp_name = Per__name__, + .tp_doc = Per__doc__, + .tp_basicsize = sizeof(cPersistentObject), + .tp_flags = Py_TPFLAGS_DEFAULT | + Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, + .tp_repr = (reprfunc)Per_repr, + .tp_getattro = (getattrofunc)Per_getattro, + .tp_setattro = (setattrofunc)Per_setattro, + .tp_traverse = (traverseproc)Per_traverse, + .tp_dealloc = (destructor)Per_dealloc, + .tp_methods = Per_methods, + .tp_getset = Per_getsets, }; +#endif + /* End of code for Persistent objects */ /* -------------------------------------------------------- */ @@ -1621,102 +1679,248 @@ Per_setstate(cPersistentObject *self) return 0; } -static PyMethodDef cPersistence_methods[] = +/* + * Module methods + */ + +static PyMethodDef CP_module_methods[] = { {NULL, NULL} }; -static cPersistenceCAPIstruct -truecPersistenceCAPI = { - &Pertype, - (getattrofunc)Per_getattro, /*tp_getattr with object key*/ - (setattrofunc)Per_setattro, /*tp_setattr with object key*/ - changed, - accessed, - ghostify, - (intfunctionwithpythonarg)Per_setstate, - NULL, /* The percachedel slot is initialized in cPickleCache.c when - the module is loaded. It uses a function in a different - shared library. */ - readCurrent -}; +/* + * Module initialization + */ -static struct PyModuleDef moduledef = +typedef struct { + cPersistenceCAPIstruct capi_struct; + PyObject *copyreg_slotnames; + PyObject *copyreg___newobj__; +} CP_module_state; + +static int +CP_module_traverse(PyObject *module, visitproc visit, void *arg) { - PyModuleDef_HEAD_INIT, - "cPersistence", /* m_name */ - cPersistence_doc_string, /* m_doc */ - -1, /* m_size */ - cPersistence_methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; + CP_module_state* state = PyModule_GetState(module); + Py_VISIT(state->capi_struct.pertype); + Py_VISIT(state->copyreg_slotnames); + Py_VISIT(state->copyreg___newobj__); + return 0; +} + +static int +CP_module_clear(PyObject *module) +{ + CP_module_state* state = PyModule_GetState(module); + Py_CLEAR(state->capi_struct.pertype); + Py_CLEAR(state->copyreg_slotnames); + Py_CLEAR(state->copyreg___newobj__); + return 0; +} + +static struct PyModuleDef CP_module_def; static PyObject* -module_init(void) +_get_module(PyTypeObject *typeobj) { - PyObject *module, *capi; - PyObject *copy_reg; +#if USE_STATIC_TYPE + return PyState_FindModule(&CP_module_def); +#else + if (PyType_Check(typeobj)) { + /* Only added in Python 3.11 */ + return PyType_GetModuleByDef(typeobj, &CP_module_def); + } - if (init_strings() < 0) + PyErr_SetString(PyExc_TypeError, "_get_module: called w/ non-type"); + return NULL; +#endif +} + +static cPersistenceCAPIstruct* +_get_capi_struct(PyTypeObject *typeobj) +{ + PyObject* module = _get_module(typeobj); + if (module == NULL) return NULL; - module = PyModule_Create(&moduledef); + CP_module_state* state = PyModule_GetState(module); + return &(state->capi_struct); +} - ((PyObject*)&Pertype)->ob_type = &PyType_Type; - Pertype.tp_new = PyType_GenericNew; - if (PyType_Ready(&Pertype) < 0) +static PyObject* +_get_copyreg_slotnames(PyTypeObject *typeobj) +{ + PyObject* module = _get_module(typeobj); + if (module == NULL) return NULL; - if (PyModule_AddObject(module, "Persistent", (PyObject *)&Pertype) < 0) + + CP_module_state* state = PyModule_GetState(module); + return state->copyreg_slotnames; +} + +static PyObject* +_get_copyreg___newobj__(PyTypeObject *typeobj) +{ + PyObject* module = _get_module(typeobj); + if (module == NULL) return NULL; - cPersistenceCAPI = &truecPersistenceCAPI; - capi = PyCapsule_New(cPersistenceCAPI, CAPI_CAPSULE_NAME, NULL); + CP_module_state* state = PyModule_GetState(module); + return state->copyreg___newobj__; +} + +static int +CP_module_exec(PyObject* module) +{ + PyObject *capi; + PyObject *copy_reg; + CP_module_state* state = PyModule_GetState(module); + cPersistenceCAPIstruct* capi_struct = &(state->capi_struct); + + Per_type_def.tp_new = PyType_GenericNew; + + capi_struct->getattro = (getattrofunc)Per_getattro; + capi_struct->setattro = (setattrofunc)Per_setattro; + capi_struct->changed = changed; + capi_struct->accessed = accessed; + capi_struct->ghostify = ghostify; + capi_struct->setstate = (intfunctionwithpythonarg)Per_setstate, + /* The percachedel slot is initialized in cPickleCache.c when + * the module is loaded. It uses a function in a different shared + * library. + */ + capi_struct->percachedel = NULL, + capi_struct->readCurrent = readCurrent; + +#if USE_HEAP_ALLOCATED_TYPE + + capi_struct->pertype = (PyTypeObject*)PyType_FromModuleAndSpec( + module, &Per_type_spec, NULL); + + /* ugly hack: 'ghostify' wants to check wheter subclasses have + * overriden 'tp_new', and expects to be able to find the original version + * on .&Per_type_def'. + * + * With heap types, the version we set above gets replaced, so copy it + * back. + */ + Per_type_def.tp_new = capi_struct->pertype->tp_new; + +#else + if (PyType_Ready(&Per_type_def) < 0) + return -1; + + capi_struct->pertype = &Per_type_def; + +#endif + + if (PyModule_AddObject( + module, "Persistent", (PyObject*)capi_struct->pertype) < 0) + return -1; + + Py_INCREF(capi_struct->pertype); /* restore stolen ref */ + + capi = PyCapsule_New(capi_struct, CAPI_CAPSULE_NAME, NULL); if (!capi) - return NULL; + return -1; + if (PyModule_AddObject(module, "CAPI", capi) < 0) - return NULL; + return -1; if (PyModule_AddIntConstant(module, "GHOST", cPersistent_GHOST_STATE) < 0) - return NULL; + return -1; if (PyModule_AddIntConstant(module, "UPTODATE", cPersistent_UPTODATE_STATE) < 0) - return NULL; + return -1; if (PyModule_AddIntConstant(module, "CHANGED", cPersistent_CHANGED_STATE) < 0) - return NULL; + return -1; if (PyModule_AddIntConstant(module, "STICKY", cPersistent_STICKY_STATE) < 0) - return NULL; + return -1; copy_reg = PyImport_ImportModule("copyreg"); if (!copy_reg) - return NULL; + return -1; - copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames"); - if (!copy_reg_slotnames) + state->copyreg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames"); + if (!state->copyreg_slotnames) { - Py_DECREF(copy_reg); - return NULL; + Py_DECREF(copy_reg); + return -1; } - __newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__"); - if (!__newobj__) + state->copyreg___newobj__ = PyObject_GetAttrString( + copy_reg, "__newobj__"); + if (!state->copyreg___newobj__) { - Py_DECREF(copy_reg); - return NULL; + Py_DECREF(copy_reg); + return -1; } + Py_DECREF(copy_reg); + return 0; +} + +#if USE_MULTIPHASE_MOD_INIT +/* Slot definitions for multi-phase initialization + * + * See: https://docs.python.org/3/c-api/module.html#multi-phase-initialization + * and: https://peps.python.org/pep-0489 + */ +static PyModuleDef_Slot CP_module_slots[] = { + {Py_mod_exec, CP_module_exec}, + {0, NULL} +}; +#endif + +static struct PyModuleDef CP_module_def = +{ + PyModuleDef_HEAD_INIT, + .m_name = CP_module__name__, + .m_doc = CP_module__doc__, + .m_size = sizeof(CP_module_state), + .m_traverse = CP_module_traverse, + .m_clear = CP_module_clear, + .m_methods = CP_module_methods, +#if USE_MULTIPHASE_MOD_INIT + .m_slots = CP_module_slots, +#endif +}; + +static PyObject* +CP_module_init(void) +{ + if (init_strings() < 0) + return NULL; + +#if USE_STATIC_MOD_INIT + + PyObject *module; + + module = PyModule_Create(&CP_module_def); + + if (module == NULL) + return NULL; + + if (CP_module_exec(module) < 0) + return NULL; + return module; + +#else + + return PyModuleDef_Init(&CP_module_def); + +#endif } -PyMODINIT_FUNC PyInit_cPersistence(void) +PyMODINIT_FUNC +PyInit_cPersistence(void) { - return module_init(); + return CP_module_init(); } diff --git a/src/persistent/cPersistence.h b/src/persistent/cPersistence.h index e8c61c6..e2fca29 100644 --- a/src/persistent/cPersistence.h +++ b/src/persistent/cPersistence.h @@ -15,8 +15,8 @@ #ifndef CPERSISTENCE_H #define CPERSISTENCE_H -#include "_compat.h" -#include "bytesobject.h" +#include "Python.h" +#define CAPI_CAPSULE_NAME "persistent.cPersistence.CAPI" #include "ring.h" @@ -108,10 +108,6 @@ typedef struct { #define cPersistenceType cPersistenceCAPI->pertype -#ifndef DONT_USE_CPERSISTENCECAPI -static cPersistenceCAPIstruct *cPersistenceCAPI; -#endif - #define cPersistanceModuleName "cPersistence" #define PER_TypeCheck(O) PyObject_TypeCheck((O), cPersistenceCAPI->pertype) diff --git a/src/persistent/cPickleCache.c b/src/persistent/cPickleCache.c index 931cd3d..0351cc7 100644 --- a/src/persistent/cPickleCache.c +++ b/src/persistent/cPickleCache.c @@ -92,21 +92,45 @@ static char cPickleCache_doc_string[] = "\n" "$Id$\n"; -#define DONT_USE_CPERSISTENCECAPI #include "cPersistence.h" #include "structmember.h" #include #include #undef Py_FindMethod +#if PY_VERSION_HEX < 0x030b0000 +#define USE_HEAP_ALLOCATED_TYPE 0 +#define USE_STATIC_MODULE_INIT 1 +#define USE_MULTIPHASE_MODULE_INIT 0 +#else +#define USE_HEAP_ALLOCATED_TYPE 1 +#define USE_STATIC_MODULE_INIT 0 +#define USE_MULTIPHASE_MODULE_INIT 1 +#endif + /* Python string objects to speed lookups; set by module init. */ static PyObject *py__p_changed; static PyObject *py__p_deactivate; +static PyObject *py__p_invalidate; static PyObject *py__p_jar; static PyObject *py__p_oid; -static cPersistenceCAPIstruct *cPersistenceCAPI; +static int +define_static_strings(void) +{ +#define INIT_STRING(S) \ + if (!(py_ ## S = PyUnicode_InternFromString(#S))) \ + return -1; + INIT_STRING(_p_changed); + INIT_STRING(_p_deactivate); + INIT_STRING(_p_invalidate); + INIT_STRING(_p_jar); + INIT_STRING(_p_oid); +#undef INIT_STRING + return 0; +} +static cPersistenceCAPIstruct* _get_capi_struct(PyTypeObject* type); /* This object is the pickle cache. The CACHE_HEAD macro guarantees that layout of this struct is the same as the start of @@ -364,27 +388,12 @@ cc_minimize(ccobject *self, PyObject *args) static int _invalidate(ccobject *self, PyObject *key) { - static PyObject *_p_invalidate = NULL; PyObject *meth, *v; v = PyDict_GetItem(self->data, key); if (v == NULL) return 0; - if (_p_invalidate == NULL) - { - _p_invalidate = INTERN("_p_invalidate"); - if (_p_invalidate == NULL) - { - /* It doesn't make any sense to ignore this error, but - the caller ignores all errors. - - TODO: and why does it do that? This should be fixed - */ - return -1; - } - } - if (v->ob_refcnt <= 1 && PyType_Check(v)) { /* This looks wrong, but it isn't. We use strong references to types @@ -397,7 +406,7 @@ _invalidate(ccobject *self, PyObject *key) return PyDict_DelItem(self->data, key); } - meth = PyObject_GetAttr(v, _p_invalidate); + meth = PyObject_GetAttr(v, py__p_invalidate); if (meth == NULL) return -1; @@ -519,7 +528,12 @@ cc_klass_items(ccobject *self) static PyObject * cc_debug_info(ccobject *self) { - PyObject *l,*k,*v; + PyObject* obj_self = (PyObject*)self; + cPersistenceCAPIstruct* cPersistenceCAPI = + _get_capi_struct(Py_TYPE(obj_self)); + PyObject *l; + PyObject *k; + PyObject *v; Py_ssize_t p = 0; l = PyList_New(0); @@ -532,7 +546,7 @@ cc_debug_info(ccobject *self) v = Py_BuildValue("Oi", k, v->ob_refcnt); else if (! PyType_Check(v) && - PER_TypeCheck(v) + PyObject_TypeCheck(v, cPersistenceCAPI->pertype) ) v = Py_BuildValue("Oisi", k, v->ob_refcnt, v->ob_type->tp_name, @@ -623,7 +637,7 @@ cc_oid_unreferenced(ccobject *self, PyObject *oid) dead_pers_obj = (cPersistentObject*)PyDict_GetItem(self->data, oid); assert(dead_pers_obj); - assert(dead_pers_obj->ob_refcnt == 0); + assert(Py_REFCNT(dead_pers_obj) == 0); dead_pers_obj_ref_to_self = (ccobject*)dead_pers_obj->cache; assert(dead_pers_obj_ref_to_self == self); @@ -633,7 +647,7 @@ cc_oid_unreferenced(ccobject *self, PyObject *oid) */ Py_INCREF(dead_pers_obj); - assert(dead_pers_obj->ob_refcnt == 1); + assert(Py_REFCNT(dead_pers_obj) == 1); /* Incremement the refcount again, because delitem is going to DECREF it. If its refcount reached zero again, we'd call back to the dealloc function that called us. @@ -662,7 +676,7 @@ cc_oid_unreferenced(ccobject *self, PyObject *oid) Py_DECREF(dead_pers_obj_ref_to_self); dead_pers_obj->cache = NULL; - assert(dead_pers_obj->ob_refcnt == 1); + assert(Py_REFCNT(dead_pers_obj) == 1); /* Don't DECREF the object, because this function is called from the object's dealloc function. If the refcnt reaches zero (again), it @@ -679,7 +693,7 @@ cc_ringlen(ccobject *self) for (here = self->ring_home.r_next; here != &self->ring_home; here = here->r_next) c++; - return INT_FROM_LONG(c); + return PyLong_FromLong(c); } static PyObject * @@ -716,7 +730,12 @@ cc_update_object_size_estimation(ccobject *self, PyObject *args) static PyObject* cc_new_ghost(ccobject *self, PyObject *args) { - PyObject *tmp, *key, *v; + PyObject* obj_self = (PyObject*)self; + cPersistenceCAPIstruct* cPersistenceCAPI = + _get_capi_struct(Py_TYPE(obj_self)); + PyObject *tmp; + PyObject *key; + PyObject *v; if (!PyArg_ParseTuple(args, "OO:new_ghost", &key, &v)) return NULL; @@ -726,7 +745,7 @@ cc_new_ghost(ccobject *self, PyObject *args) { /* Its a persistent class, such as a ZClass. Thats ok. */ } - else if (! PER_TypeCheck(v)) + else if (! PyObject_TypeCheck(v, cPersistenceCAPI->pertype)) { /* If it's not an instance of a persistent class, (ie Python classes that derive from persistent.Persistent, BTrees, @@ -1039,6 +1058,9 @@ cc_subscript(ccobject *self, PyObject *key) static int cc_add_item(ccobject *self, PyObject *key, PyObject *v) { + PyObject* obj_self = (PyObject*)self; + cPersistenceCAPIstruct* cPersistenceCAPI = + _get_capi_struct(Py_TYPE(obj_self)); int result; PyObject *oid, *object_again, *jar; cPersistentObject *p; @@ -1048,7 +1070,7 @@ cc_add_item(ccobject *self, PyObject *key, PyObject *v) { /* Its a persistent class, such as a ZClass. Thats ok. */ } - else if (! PER_TypeCheck(v)) + else if (! PyObject_TypeCheck(v, cPersistenceCAPI->pertype)) { /* If it's not an instance of a persistent class, (ie Python classes that derive from persistent.Persistent, BTrees, @@ -1235,13 +1257,6 @@ cc_ass_sub(ccobject *self, PyObject *key, PyObject *v) return cc_del_item(self, key); } -static PyMappingMethods cc_as_mapping = -{ - (lenfunc)cc_length, /* mp_length */ - (binaryfunc)cc_subscript, /* mp_subscript */ - (objobjargproc)cc_ass_sub, /* mp_ass_subscript */ -}; - static PyObject * cc_cache_data(ccobject *self, void *context) { @@ -1269,6 +1284,9 @@ static PyMemberDef cc_members[] = {NULL} }; +static char cc__name__[] = "persistent.PickleCache"; +static char cc__doc__[] = "Cache holding pickles"; + /* This module is compiled as a shared library. Some compilers don't allow addresses of Python objects defined in other libraries to be used in static initializers here. The DEFERRED_ADDRESS macro is @@ -1278,104 +1296,230 @@ static PyMemberDef cc_members[] = */ #define DEFERRED_ADDRESS(ADDR) 0 -static PyTypeObject Cctype = +#if USE_HEAP_ALLOCATED_TYPE + +/* + * Heap type: PickleCache + */ + +static PyType_Slot CC_type_slots[] = { + {Py_tp_doc, cc__doc__}, + {Py_tp_init, cc_init}, + {Py_mp_length, (lenfunc)cc_length}, + {Py_mp_subscript, (binaryfunc)cc_subscript}, + {Py_mp_ass_subscript, (objobjargproc)cc_ass_sub}, + {Py_tp_traverse, cc_traverse}, + {Py_tp_clear, cc_clear}, + {Py_tp_dealloc, cc_dealloc}, + {Py_tp_methods, cc_methods}, + {Py_tp_members, cc_members}, + {Py_tp_getset, cc_getsets}, + {0, NULL} +}; + +static PyType_Spec CC_type_spec = { + .name = cc__name__, + .basicsize = sizeof(ccobject), + .flags = Py_TPFLAGS_DEFAULT | + Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, + .slots = CC_type_slots +}; + +#else + +/* + * Static type: PickleCache + */ +static PyMappingMethods cc_as_mapping = { - PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) - "persistent.PickleCache", /* tp_name */ - sizeof(ccobject), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)cc_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - &cc_as_mapping, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_HAVE_GC, /* tp_flags */ - 0, /* tp_doc */ - (traverseproc)cc_traverse, /* tp_traverse */ - (inquiry)cc_clear, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - cc_methods, /* tp_methods */ - cc_members, /* tp_members */ - cc_getsets, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)cc_init, /* tp_init */ + (lenfunc)cc_length, /* mp_length */ + (binaryfunc)cc_subscript, /* mp_subscript */ + (objobjargproc)cc_ass_sub, /* mp_ass_subscript */ }; -static struct PyModuleDef moduledef = +static PyTypeObject CC_type_def = { - PyModuleDef_HEAD_INIT, - "cPickleCache", /* m_name */ - cPickleCache_doc_string, /* m_doc */ - -1, /* m_size */ - NULL, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ + PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) + .tp_name = cc__name__, + .tp_doc = cc__doc__, + .tp_basicsize = sizeof(ccobject), + .tp_flags = Py_TPFLAGS_DEFAULT | + Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, + .tp_new = DEFERRED_ADDRESS(&PyType_GenericNew), + .tp_init = (initproc)cc_init, + .tp_as_mapping = &cc_as_mapping, + .tp_traverse = (traverseproc)cc_traverse, + .tp_clear = (inquiry)cc_clear, + .tp_dealloc = (destructor)cc_dealloc, + .tp_methods = cc_methods, + .tp_members = cc_members, + .tp_getset = cc_getsets, }; -static PyObject* -module_init(void) +#endif + + +/* + * Module initialization + */ +static struct PyModuleDef CC_module_def; + +typedef struct { + PyTypeObject* cc_type; + cPersistenceCAPIstruct* capi_struct; +} CC_module_state; + +static int +CC_module_traverse(PyObject *module, visitproc visit, void *arg) { - PyObject *module; + CC_module_state* state = PyModule_GetState(module); + Py_VISIT(state->cc_type); + return 0; +} - ((PyObject*)&Cctype)->ob_type = &PyType_Type; - Cctype.tp_new = &PyType_GenericNew; - if (PyType_Ready(&Cctype) < 0) - { - return NULL; +static int +CC_module_clear(PyObject *module) +{ + CC_module_state* state = PyModule_GetState(module); + Py_CLEAR(state->cc_type); + return 0; +} + +static int +init_cpersistence_capi(PyObject* module, percachedelfunc func) +{ + CC_module_state* state = PyModule_GetState(module); + state->capi_struct = (cPersistenceCAPIstruct *)PyCapsule_Import( + CAPI_CAPSULE_NAME, 0 + ); + if (state->capi_struct == NULL) + return -1; + + state->capi_struct->percachedel = func; + return 0; +} + +static PyObject* +_get_module(PyTypeObject *typeobj) +{ +#if USE_STATIC_MODULE_INIT + return PyState_FindModule(&CC_module_def); +#else + if (PyType_Check(typeobj)) { + /* Only added in Python 3.9 */ + return PyType_GetModule(typeobj); } - module = PyModule_Create(&moduledef); + PyErr_SetString(PyExc_TypeError, "_get_module: called w/ non-type"); + return NULL; +#endif +} - cPersistenceCAPI = (cPersistenceCAPIstruct *)PyCapsule_Import(CAPI_CAPSULE_NAME, 0); - if (!cPersistenceCAPI) +static cPersistenceCAPIstruct* +_get_capi_struct(PyTypeObject* type) +{ + PyObject* module = _get_module(type); + if (module == NULL) return NULL; - cPersistenceCAPI->percachedel = (percachedelfunc)cc_oid_unreferenced; - py__p_changed = INTERN("_p_changed"); - if (!py__p_changed) - return NULL; - py__p_deactivate = INTERN("_p_deactivate"); - if (!py__p_deactivate) - return NULL; - py__p_jar = INTERN("_p_jar"); - if (!py__p_jar) - return NULL; - py__p_oid = INTERN("_p_oid"); - if (!py__p_oid) + CC_module_state* state = PyModule_GetState(module); + if (state == NULL) return NULL; + return state->capi_struct; +} + +static int +CC_module_exec(PyObject* module) +{ + CC_module_state* state = PyModule_GetState(module); + + if (init_cpersistence_capi( + module, (percachedelfunc)cc_oid_unreferenced) < 0) + return -1; + if (PyModule_AddStringConstant(module, "cache_variant", "stiff/c") < 0) + return -1; + +#if USE_HEAP_ALLOCATED_TYPE + + state->cc_type = (PyTypeObject*) PyType_FromModuleAndSpec( + module, &CC_type_spec, NULL); + +#else + + ((PyObject*)&CC_type_def)->ob_type = &PyType_Type; + CC_type_def.tp_new = &PyType_GenericNew; + + if (PyType_Ready(&CC_type_def) < 0) + return -1; + + state->cc_type = &CC_type_def; +#endif + + /* We hold one ref to the type in module state; the module dict + * holds another. + */ + if (PyModule_AddObject( + module, "PickleCache", (PyObject*)state->cc_type) < 0) + return -1; + + Py_INCREF(state->cc_type); /* restore stolen ref */ + + return 0; +} + +#if USE_MULTIPHASE_MODULE_INIT +/* Slot definitions for multi-phase initialization + * + * See: https://docs.python.org/3/c-api/module.html#multi-phase-initialization + * and: https://peps.python.org/pep-0489 + */ +static PyModuleDef_Slot CC_module_slots[] = { + {Py_mod_exec, CC_module_exec}, + {0, NULL} +}; +#endif + +static struct PyModuleDef CC_module_def = +{ + PyModuleDef_HEAD_INIT, + .m_name = "cPickleCache", + .m_doc = cPickleCache_doc_string, + .m_size = sizeof(CC_module_state), + .m_traverse = CC_module_traverse, + .m_clear = CC_module_clear, +#if USE_MULTIPHASE_MODULE_INIT + .m_slots = CC_module_slots, +#endif +}; + +static PyObject* +CC_module_init(void) +{ + if (define_static_strings() < 0) + return NULL; + +#if USE_STATIC_MODULE_INIT + + PyObject* module = PyModule_Create(&CC_module_def); + if (module == NULL) return NULL; - /* This leaks a reference to Cctype, but it doesn't matter. */ - if (PyModule_AddObject(module, "PickleCache", (PyObject *)&Cctype) < 0) + if (CC_module_exec(module) < 0) return NULL; return module; + +#else + return PyModuleDef_Init(&CC_module_def); +#endif } -PyMODINIT_FUNC PyInit_cPickleCache(void) +PyMODINIT_FUNC +PyInit_cPickleCache(void) { - return module_init(); + return CC_module_init(); } diff --git a/src/persistent/picklecache.py b/src/persistent/picklecache.py index 9fe4bef..bef3280 100644 --- a/src/persistent/picklecache.py +++ b/src/persistent/picklecache.py @@ -243,6 +243,8 @@ def __setitem__(self, oid, value): # Ensure we begin monitoring for ``value`` to # be deallocated. self.ring.ring_node_for(value) + else: # pragma: no cover + pass def __delitem__(self, oid): """ See IPickleCache. @@ -460,9 +462,8 @@ def _sweep(self, target, target_size_bytes=0): # actions if our _p_deactivate ran, in case of buggy # subclasses. see _persistent_deactivate_ran. - if not had_weak_refs: - had_weak_refs |= getattr( - value, '__weakref__', None) is not None + had_weak_refs |= getattr( + value, '__weakref__', None) is not None value._p_deactivate() if (self._persistent_deactivate_ran diff --git a/src/persistent/tests/test_persistence.py b/src/persistent/tests/test_persistence.py index 6200c44..f4ef3be 100644 --- a/src/persistent/tests/test_persistence.py +++ b/src/persistent/tests/test_persistence.py @@ -1057,38 +1057,38 @@ class Derived(Base): self.assertEqual(inst.baz, 'bam') self.assertEqual(inst.qux, 'spam') - if not PYPY: - def test___setstate___interns_dict_keys(self): - class Derived(self._getTargetClass()): - pass - inst1 = Derived() - inst2 = Derived() - key1 = 'key' - key2 = 'ke' - key2 += 'y' # construct in a way that won't intern the literal - self.assertIsNot(key1, key2) - inst1.__setstate__({key1: 1}) - inst2.__setstate__({key2: 2}) - key1 = list(inst1.__dict__.keys())[0] - key2 = list(inst2.__dict__.keys())[0] - self.assertIs(key1, key2) - - inst1 = Derived() - inst2 = Derived() - key1 = 'key' - key2 = 'ke' - key2 += 'y' # construct in a way that won't intern the literal - self.assertIsNot(key1, key2) - state1 = IterableUserDict({key1: 1}) - state2 = IterableUserDict({key2: 2}) - k1 = list(state1.keys())[0] - k2 = list(state2.keys())[0] - self.assertIsNot(k1, k2) # verify - inst1.__setstate__(state1) - inst2.__setstate__(state2) - key1 = list(inst1.__dict__.keys())[0] - key2 = list(inst2.__dict__.keys())[0] - self.assertIs(key1, key2) + @unittest.skipIf(PYPY, "interning doesn't happen") + def test___setstate___interns_dict_keys(self): + class Derived(self._getTargetClass()): + pass + inst1 = Derived() + inst2 = Derived() + key1 = 'key' + key2 = 'ke' + key2 += 'y' # construct in a way that won't intern the literal + self.assertIsNot(key1, key2) + inst1.__setstate__({key1: 1}) + inst2.__setstate__({key2: 2}) + key1 = list(inst1.__dict__.keys())[0] + key2 = list(inst2.__dict__.keys())[0] + self.assertIs(key1, key2) + + inst1 = Derived() + inst2 = Derived() + key1 = 'key' + key2 = 'ke' + key2 += 'y' # construct in a way that won't intern the literal + self.assertIsNot(key1, key2) + state1 = IterableUserDict({key1: 1}) + state2 = IterableUserDict({key2: 2}) + k1 = list(state1.keys())[0] + k2 = list(state2.keys())[0] + self.assertIsNot(k1, k2) # verify + inst1.__setstate__(state1) + inst2.__setstate__(state2) + key1 = list(inst1.__dict__.keys())[0] + key2 = list(inst2.__dict__.keys())[0] + self.assertIs(key1, key2) def test___setstate___doesnt_fail_on_non_string_keys(self): class Derived(self._getTargetClass()): @@ -2033,8 +2033,13 @@ def __repr__(self): def test__p_repr_in_instance_ignored(self): class P(self._getTargetClass()): pass + + def _repr(): + raise NotImplementedError + p = P() - p._p_repr = lambda: "Instance" + + p._p_repr = _repr result = self._normalized_repr(p) self.assertEqual( result, @@ -2138,8 +2143,8 @@ class Jar: accessed = False def __getattr__(self, name): - if name == '_cache': - self.accessed = True + assert name == '_cache' + self.accessed = True raise AttributeError(name) def register(self, *args): diff --git a/src/persistent/tests/test_picklecache.py b/src/persistent/tests/test_picklecache.py index d701187..f938285 100644 --- a/src/persistent/tests/test_picklecache.py +++ b/src/persistent/tests/test_picklecache.py @@ -1016,8 +1016,8 @@ class Jar: was_set = False def __setattr__(self, name, value): - if name == '_cache': - object.__setattr__(self, 'was_set', True) + assert name == '_cache' + object.__setattr__(self, 'was_set', True) raise AttributeError(name) jar = Jar() @@ -1211,7 +1211,7 @@ def test_interpreter_finalization_ffi_cleanup(self): unraised = [] try: old_hook = sys.unraisablehook - except AttributeError: + except AttributeError: # pragma: no cover pass else: # pragma: no cover sys.unraisablehook = unraised.append @@ -1224,7 +1224,7 @@ def test_interpreter_finalization_ffi_cleanup(self): o = cache[oid] = self._makePersist(oid=oid) # noqa: F841 # Clear the dict, or at least part of it. # This is coupled to ``cleanup_hook`` - if cache.data.cleanup_hook: + if cache.data.cleanup_hook: # pragma: no cover del cache.data._addr_to_oid del cache[oid] diff --git a/src/persistent/tests/test_timestamp.py b/src/persistent/tests/test_timestamp.py index a9db98a..d976cf2 100644 --- a/src/persistent/tests/test_timestamp.py +++ b/src/persistent/tests/test_timestamp.py @@ -213,18 +213,19 @@ def __init__(self): self.MUT = MUT self.orig_maxint = MUT._MAXINT - self.is_32_bit_hash = self.orig_maxint == self.MAX_32_BITS - - self.orig_c_long = None - self.c_int64 = None - self.c_int32 = None - if MUT.c_long is not None: + if MUT.c_long is None: # pragma: no cover + self.orig_c_long = None + self.c_int64 = None + self.c_int32 = None + self.is_32_bit_hash = self.orig_maxint == self.MAX_32_BITS + else: import ctypes self.orig_c_long = MUT.c_long self.c_int32 = ctypes.c_int32 self.c_int64 = ctypes.c_int64 # win32, even on 64-bit long, has funny sizes self.is_32_bit_hash = self.c_int32 == ctypes.c_long + self.expected_hash = ( self.bit_32_hash if self.is_32_bit_hash else self.bit_64_hash)