Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_marshal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a reference leak when a code object's constants contain a reference cycle
back to the code object.
23 changes: 3 additions & 20 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -2459,18 +2452,16 @@ 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)
{
PyCodeObject *co = _PyCodeObject_CAST(self);
Py_VISIT(co->co_consts);
return 0;
}
#endif

static PyObject *
code_repr(PyObject *self)
Expand Down Expand Up @@ -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 */
Expand Down
Loading