From 9be91f6a20ed2fd9b491c3e779dc45c7392f60ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:21:39 +0200 Subject: [PATCH 1/4] gh-116946: fully implement GC protocol for `bz2` objects (#138266) --- Modules/_bz2module.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c index 914172684158a1..d988901933703e 100644 --- a/Modules/_bz2module.c +++ b/Modules/_bz2module.c @@ -381,13 +381,14 @@ _bz2_BZ2Compressor_impl(PyTypeObject *type, int compresslevel) static void BZ2Compressor_dealloc(PyObject *op) { + PyTypeObject *tp = Py_TYPE(op); + PyObject_GC_UnTrack(op); BZ2Compressor *self = _BZ2Compressor_CAST(op); BZ2_bzCompressEnd(&self->bzs); if (self->lock != NULL) { PyThread_free_lock(self->lock); } - PyTypeObject *tp = Py_TYPE(self); - tp->tp_free((PyObject *)self); + tp->tp_free(self); Py_DECREF(tp); } @@ -420,7 +421,7 @@ static PyType_Spec bz2_compressor_type_spec = { // bz2_compressor_type_spec does not have Py_TPFLAGS_BASETYPE flag // which prevents to create a subclass. // So calling PyType_GetModuleState() in this file is always safe. - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC), .slots = bz2_compressor_type_slots, }; @@ -687,9 +688,11 @@ _bz2_BZ2Decompressor_impl(PyTypeObject *type) static void BZ2Decompressor_dealloc(PyObject *op) { + PyTypeObject *tp = Py_TYPE(op); + PyObject_GC_UnTrack(op); BZ2Decompressor *self = _BZ2Decompressor_CAST(op); - if(self->input_buffer != NULL) { + if (self->input_buffer != NULL) { PyMem_Free(self->input_buffer); } BZ2_bzDecompressEnd(&self->bzs); @@ -697,9 +700,7 @@ BZ2Decompressor_dealloc(PyObject *op) if (self->lock != NULL) { PyThread_free_lock(self->lock); } - - PyTypeObject *tp = Py_TYPE(self); - tp->tp_free((PyObject *)self); + tp->tp_free(self); Py_DECREF(tp); } @@ -751,7 +752,7 @@ static PyType_Spec bz2_decompressor_type_spec = { // bz2_decompressor_type_spec does not have Py_TPFLAGS_BASETYPE flag // which prevents to create a subclass. // So calling PyType_GetModuleState() in this file is always safe. - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC), .slots = bz2_decompressor_type_slots, }; From 3ea16f990f81e1e3b2892f1dfd213937b1df2a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:22:43 +0200 Subject: [PATCH 2/4] gh-116946: fully implement GC protocol for `lzma` objects (#138288) --- Modules/_lzmamodule.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c index 0b0b1bc765bbc9..bcc9ea7a7bba68 100644 --- a/Modules/_lzmamodule.c +++ b/Modules/_lzmamodule.c @@ -866,12 +866,13 @@ Compressor_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) static void Compressor_dealloc(PyObject *op) { + PyTypeObject *tp = Py_TYPE(op); + PyObject_GC_UnTrack(op); Compressor *self = Compressor_CAST(op); lzma_end(&self->lzs); if (self->lock != NULL) { PyThread_free_lock(self->lock); } - PyTypeObject *tp = Py_TYPE(self); tp->tp_free(self); Py_DECREF(tp); } @@ -933,7 +934,7 @@ static PyType_Spec lzma_compressor_type_spec = { // lzma_compressor_type_spec does not have Py_TPFLAGS_BASETYPE flag // which prevents to create a subclass. // So calling PyType_GetModuleState() in this file is always safe. - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC), .slots = lzma_compressor_type_slots, }; @@ -1314,6 +1315,8 @@ _lzma_LZMADecompressor_impl(PyTypeObject *type, int format, static void Decompressor_dealloc(PyObject *op) { + PyTypeObject *tp = Py_TYPE(op); + PyObject_GC_UnTrack(op); Decompressor *self = Decompressor_CAST(op); if(self->input_buffer != NULL) PyMem_Free(self->input_buffer); @@ -1323,7 +1326,6 @@ Decompressor_dealloc(PyObject *op) if (self->lock != NULL) { PyThread_free_lock(self->lock); } - PyTypeObject *tp = Py_TYPE(self); tp->tp_free(self); Py_DECREF(tp); } @@ -1381,7 +1383,7 @@ static PyType_Spec lzma_decompressor_type_spec = { // lzma_decompressor_type_spec does not have Py_TPFLAGS_BASETYPE flag // which prevents to create a subclass. // So calling PyType_GetModuleState() in this file is always safe. - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC), .slots = lzma_decompressor_type_slots, }; From 6f1dd9551a69c8c76d066a04e94db6dbc6c7597c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:23:01 +0200 Subject: [PATCH 3/4] gh-116946: fully implement GC protocol for `_hashlib` objects (#138289) --- Modules/_hashopenssl.c | 51 +++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index a6496d0f04f2d0..9d79fc08dcfcac 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -752,7 +752,9 @@ py_wrapper_EVP_MD_CTX_new(void) static HASHobject * new_hash_object(PyTypeObject *type) { - HASHobject *retval = PyObject_New(HASHobject, type); + assert(type != NULL); + assert(type->tp_alloc != NULL); + HASHobject *retval = (HASHobject *)type->tp_alloc(type, 0); if (retval == NULL) { return NULL; } @@ -792,13 +794,21 @@ _hashlib_HASH_hash(HASHobject *self, const void *vp, Py_ssize_t len) static void _hashlib_HASH_dealloc(PyObject *op) { + PyTypeObject *tp = Py_TYPE(op); + PyObject_GC_UnTrack(op); HASHobject *self = HASHobject_CAST(op); - PyTypeObject *tp = Py_TYPE(self); EVP_MD_CTX_free(self->ctx); - PyObject_Free(self); + tp->tp_free(self); Py_DECREF(tp); } +static int +_hashlib_HASH_traverse(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(op)); + return 0; +} + static int _hashlib_HASH_copy_locked(HASHobject *self, EVP_MD_CTX *new_ctx_p) { @@ -993,6 +1003,7 @@ PyDoc_STRVAR(HASHobject_type_doc, static PyType_Slot HASHobject_type_slots[] = { {Py_tp_dealloc, _hashlib_HASH_dealloc}, + {Py_tp_traverse, _hashlib_HASH_traverse}, {Py_tp_repr, _hashlib_HASH_repr}, {Py_tp_doc, (char *)HASHobject_type_doc}, {Py_tp_methods, HASH_methods}, @@ -1008,6 +1019,7 @@ static PyType_Spec HASHobject_type_spec = { | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE + | Py_TPFLAGS_HAVE_GC ), .slots = HASHobject_type_slots }; @@ -1165,6 +1177,8 @@ PyDoc_STRVAR(HASHXOFobject_type_doc, "digest_size -- number of bytes in this hashes output"); static PyType_Slot HASHXOFobject_type_slots[] = { + {Py_tp_dealloc, _hashlib_HASH_dealloc}, + {Py_tp_traverse, _hashlib_HASH_traverse}, {Py_tp_doc, (char *)HASHXOFobject_type_doc}, {Py_tp_methods, HASHXOFobject_methods}, {Py_tp_getset, HASHXOFobject_getsets}, @@ -1179,6 +1193,7 @@ static PyType_Spec HASHXOFobject_type_spec = { | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE + | Py_TPFLAGS_HAVE_GC ), .slots = HASHXOFobject_type_slots }; @@ -1902,7 +1917,8 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, goto error; } - self = PyObject_New(HMACobject, state->HMAC_type); + assert(state->HMAC_type != NULL); + self = (HMACobject *)state->HMAC_type->tp_alloc(state->HMAC_type, 0); if (self == NULL) { goto error; } @@ -2008,7 +2024,8 @@ _hashlib_HMAC_copy_impl(HMACobject *self) return NULL; } - retval = PyObject_New(HMACobject, Py_TYPE(self)); + PyTypeObject *type = Py_TYPE(self); + retval = (HMACobject *)type->tp_alloc(type, 0); if (retval == NULL) { HMAC_CTX_free(ctx); return NULL; @@ -2022,16 +2039,24 @@ _hashlib_HMAC_copy_impl(HMACobject *self) static void _hmac_dealloc(PyObject *op) { + PyTypeObject *tp = Py_TYPE(op); + PyObject_GC_UnTrack(op); HMACobject *self = HMACobject_CAST(op); - PyTypeObject *tp = Py_TYPE(self); if (self->ctx != NULL) { HMAC_CTX_free(self->ctx); self->ctx = NULL; } - PyObject_Free(self); + tp->tp_free(self); Py_DECREF(tp); } +static int +_hashlib_HMAC_traverse(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(op)); + return 0; +} + static PyObject * _hmac_repr(PyObject *op) { @@ -2198,15 +2223,21 @@ static PyType_Slot HMACtype_slots[] = { {Py_tp_doc, (char *)hmactype_doc}, {Py_tp_repr, _hmac_repr}, {Py_tp_dealloc, _hmac_dealloc}, + {Py_tp_traverse, _hashlib_HMAC_traverse}, {Py_tp_methods, HMAC_methods}, {Py_tp_getset, HMAC_getset}, {0, NULL} }; PyType_Spec HMACtype_spec = { - "_hashlib.HMAC", /* name */ - sizeof(HMACobject), /* basicsize */ - .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE, + .name = "_hashlib.HMAC", + .basicsize = sizeof(HMACobject), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + | Py_TPFLAGS_HAVE_GC + ), .slots = HMACtype_slots, }; From 2a54acf3c3d9f388c3d878a17ea804a801affca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:24:23 +0200 Subject: [PATCH 4/4] gh-116946: fully implement GC protocol for `zlib` objects (#138290) --- Modules/zlibmodule.c | 76 +++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index e88c9de93ba2b7..049dc6690ed7d9 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -273,7 +273,9 @@ static compobject * newcompobject(PyTypeObject *type) { compobject *self; - self = PyObject_New(compobject, type); + assert(type != NULL); + assert(type->tp_alloc != NULL); + self = _compobject_CAST(type->tp_alloc(type, 0)); if (self == NULL) return NULL; self->eof = 0; @@ -716,33 +718,41 @@ zlib_decompressobj_impl(PyObject *module, int wbits, PyObject *zdict) } static void -Dealloc(compobject *self) +compobject_dealloc_impl(PyObject *op, int (*dealloc)(z_streamp)) { - PyTypeObject *type = Py_TYPE(self); + PyTypeObject *type = Py_TYPE(op); + PyObject_GC_UnTrack(op); + compobject *self = _compobject_CAST(op); + if (self->is_initialised) { + (void)dealloc(&self->zst); + } PyThread_free_lock(self->lock); Py_XDECREF(self->unused_data); Py_XDECREF(self->unconsumed_tail); Py_XDECREF(self->zdict); - PyObject_Free(self); + type->tp_free(self); Py_DECREF(type); } +static int +compobject_traverse(PyObject *op, visitproc visit, void *arg) +{ + compobject *self = _compobject_CAST(op); + Py_VISIT(Py_TYPE(op)); + Py_VISIT(self->zdict); + return 0; +} + static void Comp_dealloc(PyObject *op) { - compobject *self = _compobject_CAST(op); - if (self->is_initialised) - (void)deflateEnd(&self->zst); - Dealloc(self); + compobject_dealloc_impl(op, &deflateEnd); } static void Decomp_dealloc(PyObject *op) { - compobject *self = _compobject_CAST(op); - if (self->is_initialised) - (void)inflateEnd(&self->zst); - Dealloc(self); + compobject_dealloc_impl(op, &inflateEnd); } /*[clinic input] @@ -1368,6 +1378,8 @@ typedef struct { char needs_input; } ZlibDecompressor; +#define ZlibDecompressor_CAST(op) ((ZlibDecompressor *)(op)) + /*[clinic input] class zlib._ZlibDecompressor "ZlibDecompressor *" "&ZlibDecompressorType" [clinic start generated code]*/ @@ -1376,8 +1388,9 @@ class zlib._ZlibDecompressor "ZlibDecompressor *" "&ZlibDecompressorType" static void ZlibDecompressor_dealloc(PyObject *op) { - ZlibDecompressor *self = (ZlibDecompressor*)op; - PyObject *type = (PyObject *)Py_TYPE(self); + PyTypeObject *type = Py_TYPE(op); + PyObject_GC_UnTrack(op); + ZlibDecompressor *self = ZlibDecompressor_CAST(op); PyThread_free_lock(self->lock); if (self->is_initialised) { inflateEnd(&self->zst); @@ -1385,10 +1398,19 @@ ZlibDecompressor_dealloc(PyObject *op) PyMem_Free(self->input_buffer); Py_CLEAR(self->unused_data); Py_CLEAR(self->zdict); - PyObject_Free(self); + type->tp_free(self); Py_DECREF(type); } +static int +ZlibDecompressor_traverse(PyObject *op, visitproc visit, void *arg) +{ + ZlibDecompressor *self = ZlibDecompressor_CAST(op); + Py_VISIT(Py_TYPE(op)); + Py_VISIT(self->zdict); + return 0; +} + static int set_inflate_zdict_ZlibDecompressor(zlibstate *state, ZlibDecompressor *self) { @@ -1731,8 +1753,9 @@ static PyObject * zlib__ZlibDecompressor_impl(PyTypeObject *type, int wbits, PyObject *zdict) /*[clinic end generated code: output=1065607df0d33baa input=9ebad0be6de226e2]*/ { + assert(type != NULL && type->tp_alloc != NULL); zlibstate *state = PyType_GetModuleState(type); - ZlibDecompressor *self = PyObject_New(ZlibDecompressor, type); + ZlibDecompressor *self = ZlibDecompressor_CAST(type->tp_alloc(type, 0)); if (self == NULL) { return NULL; } @@ -2015,6 +2038,7 @@ static PyMethodDef zlib_methods[] = static PyType_Slot Comptype_slots[] = { {Py_tp_dealloc, Comp_dealloc}, + {Py_tp_traverse, compobject_traverse}, {Py_tp_methods, comp_methods}, {0, 0}, }; @@ -2022,12 +2046,17 @@ static PyType_Slot Comptype_slots[] = { static PyType_Spec Comptype_spec = { .name = "zlib.Compress", .basicsize = sizeof(compobject), - .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_HAVE_GC + ), .slots= Comptype_slots, }; static PyType_Slot Decomptype_slots[] = { {Py_tp_dealloc, Decomp_dealloc}, + {Py_tp_traverse, compobject_traverse}, {Py_tp_methods, Decomp_methods}, {Py_tp_members, Decomp_members}, {0, 0}, @@ -2036,12 +2065,17 @@ static PyType_Slot Decomptype_slots[] = { static PyType_Spec Decomptype_spec = { .name = "zlib.Decompress", .basicsize = sizeof(compobject), - .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_HAVE_GC + ), .slots = Decomptype_slots, }; static PyType_Slot ZlibDecompressor_type_slots[] = { {Py_tp_dealloc, ZlibDecompressor_dealloc}, + {Py_tp_traverse, ZlibDecompressor_traverse}, {Py_tp_members, ZlibDecompressor_members}, {Py_tp_new, zlib__ZlibDecompressor}, {Py_tp_doc, (char *)zlib__ZlibDecompressor__doc__}, @@ -2056,7 +2090,11 @@ static PyType_Spec ZlibDecompressor_type_spec = { // ZlibDecompressor_type_spec does not have Py_TPFLAGS_BASETYPE flag // which prevents to create a subclass. // So calling PyType_GetModuleState() in this file is always safe. - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_IMMUTABLETYPE + | Py_TPFLAGS_HAVE_GC + ), .slots = ZlibDecompressor_type_slots, };