From 83afa76e5b26d0d12e1e20e7a12caf73fc2ad59d Mon Sep 17 00:00:00 2001 From: Rajhans Jadhao Date: Tue, 9 Jun 2026 18:41:31 +0530 Subject: [PATCH] gh-148722: Make code objects support cyclic GC --- Lib/test/test_code.py | 13 +++++++++++ Lib/test/test_marshal.py | 3 +-- ...-06-05-14-45-00.gh-issue-148722.A7mV9e.rst | 2 ++ Objects/codeobject.c | 23 +++---------------- 4 files changed, 19 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-05-14-45-00.gh-issue-148722.A7mV9e.rst diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 3588872ed23ac49..f1f48633ff6b0a8 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -1282,6 +1282,19 @@ def callback(code): self.assertFalse(bool(coderef())) self.assertTrue(self.called) + def test_code_const_reference_cycle_collected(self): + def f(): + return 1234.5 + + a = [] + code = f.__code__.replace(co_consts=f.__code__.co_consts + (a,)) + coderef = weakref.ref(code) + a.append(code) + + del code, a + gc_collect() + self.assertIsNone(coderef()) + # Python implementation of location table parsing algorithm def read(it): return next(it) diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 9c4d91c456dc5d9..29bc65bbf28215d 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -357,8 +357,7 @@ def f(): code = f.__code__ a = [] code = code.replace(co_consts=code.co_consts + (a,)) - # This test creates a reference loop which leads to reference leaks, - # so we need to break the loop manually. See gh-148722. + # Break the cycle without relying on cyclic GC after the test. self.addCleanup(a.clear) a.append(code) for v in range(marshal.version + 1): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-05-14-45-00.gh-issue-148722.A7mV9e.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-05-14-45-00.gh-issue-148722.A7mV9e.rst new file mode 100644 index 000000000000000..04ede292bd5ee23 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-05-14-45-00.gh-issue-148722.A7mV9e.rst @@ -0,0 +1,2 @@ +Fix a reference leak when a code object's constants contain a reference cycle +back to the code object. diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 4ede8de6e8adc5f..2849c749a974e89 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -731,12 +731,7 @@ _PyCode_New(struct _PyCodeConstructor *con) } Py_ssize_t size = PyBytes_GET_SIZE(con->code) / sizeof(_Py_CODEUNIT); - PyCodeObject *co; -#ifdef Py_GIL_DISABLED - co = PyObject_GC_NewVar(PyCodeObject, &PyCode_Type, size); -#else - co = PyObject_NewVar(PyCodeObject, &PyCode_Type, size); -#endif + PyCodeObject *co = PyObject_GC_NewVar(PyCodeObject, &PyCode_Type, size); if (co == NULL) { Py_XDECREF(replacement_locations); PyErr_NoMemory(); @@ -750,8 +745,8 @@ _PyCode_New(struct _PyCodeConstructor *con) #ifdef Py_GIL_DISABLED co->_co_unique_id = _PyObject_AssignUniqueId((PyObject *)co); - _PyObject_GC_TRACK(co); #endif + _PyObject_GC_TRACK(co); Py_XDECREF(replacement_locations); return co; } @@ -2402,9 +2397,7 @@ code_dealloc(PyObject *self) return; } -#ifdef Py_GIL_DISABLED PyObject_GC_UnTrack(co); -#endif _PyFunction_ClearCodeByVersion(co->co_version); if (co->co_extra != NULL) { @@ -2459,10 +2452,9 @@ code_dealloc(PyObject *self) } PyMem_Free(co->co_tlbc); #endif - PyObject_Free(co); + PyObject_GC_Del(co); } -#ifdef Py_GIL_DISABLED static int code_traverse(PyObject *self, visitproc visit, void *arg) { @@ -2470,7 +2462,6 @@ code_traverse(PyObject *self, visitproc visit, void *arg) Py_VISIT(co->co_consts); return 0; } -#endif static PyObject * code_repr(PyObject *self) @@ -2890,17 +2881,9 @@ PyTypeObject PyCode_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ -#ifdef Py_GIL_DISABLED Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ -#else - Py_TPFLAGS_DEFAULT, /* tp_flags */ -#endif code_new__doc__, /* tp_doc */ -#ifdef Py_GIL_DISABLED code_traverse, /* tp_traverse */ -#else - 0, /* tp_traverse */ -#endif 0, /* tp_clear */ code_richcompare, /* tp_richcompare */ offsetof(PyCodeObject, co_weakreflist), /* tp_weaklistoffset */