Skip to content

gh-135552: Clear weakrefs to types in GC after garbage finalization not before #135728

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from 8 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
32 changes: 32 additions & 0 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,37 @@ def test_something(self):
""")
assert_python_ok("-c", source)

def test_do_not_cleanup_type_subclasses_before_finalization(self):
# https://github.com/python/cpython/issues/135552
code = textwrap.dedent("""
class BaseNode:
def __del__(self):
BaseNode.next = BaseNode.next.next

class Node(BaseNode):
pass

BaseNode.next = Node()
BaseNode.next.next = Node()
""")
assert_python_ok("-c", code)

code_inside_function = textwrap.dedent("""
def test():
class BaseNode:
def __del__(self):
BaseNode.next = BaseNode.next.next

class Node(BaseNode):
pass

BaseNode.next = Node()
BaseNode.next.next = Node()

test()
""")
assert_python_ok("-c", code_inside_function)


class IncrementalGCTests(unittest.TestCase):
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
Expand Down Expand Up @@ -1518,6 +1549,7 @@ def test_ast_fini(self):
assert_python_ok("-c", code)



def setUpModule():
global enabled, debug
enabled = gc.isenabled()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Split the handling of weak references in the GC if both types and instances
within a generation are unreachable. This prevent the clearing of the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
within a generation are unreachable. This prevent the clearing of the
within a generation are unreachable. This prevent the clearing of the

type's subclasses list too early. This fix a segfault that occurs when
:meth:`~object.__del__` modifies the base's attributes and tries to access them from self.
39 changes: 39 additions & 0 deletions Python/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,21 @@ move_legacy_finalizer_reachable(PyGC_Head *finalizers)
}
}

/* Move types from unreachable set to prevent clearing of type's subclasses */
static void
move_types_from_unreachable(PyGC_Head *unreachable, PyGC_Head *to)
{
PyGC_Head *gc, *next;
for(gc = GC_NEXT(unreachable); gc != unreachable; gc = next) {
PyObject *op = FROM_GC(gc);
next = GC_NEXT(gc);

if (PyType_Check(op)) {
gc_list_move(gc, to);
}
}
}

/* Clear all weakrefs to unreachable objects, and if such a weakref has a
* callback, invoke it if necessary. Note that it's possible for such
* weakrefs to be outside the unreachable set -- indeed, those are precisely
Expand Down Expand Up @@ -1698,6 +1713,7 @@ gc_collect_region(PyThreadState *tstate,
{
PyGC_Head unreachable; /* non-problematic unreachable trash */
PyGC_Head finalizers; /* objects with, & reachable from, __del__ */
PyGC_Head types; /* unreachable types */
PyGC_Head *gc; /* initialize to prevent a compiler warning */
GCState *gcstate = &tstate->interp->gc;

Expand Down Expand Up @@ -1736,6 +1752,15 @@ gc_collect_region(PyThreadState *tstate,
}
}

/* All types in the unreachable set should be handled after the
* instances of those types are finalized. Otherwise, when we clear
* the weak references, the subclasses list will also be cleared, and
* the type's cache will not be properly invalidated from
* within the __del__ method.
*/
gc_list_init(&types);
move_types_from_unreachable(&unreachable, &types);

/* Clear weakrefs and invoke callbacks as necessary. */
stats->collected += handle_weakrefs(&unreachable, to);
gc_list_validate_space(to, gcstate->visited_space);
Expand All @@ -1744,6 +1769,20 @@ gc_collect_region(PyThreadState *tstate,

/* Call tp_finalize on objects which have one. */
finalize_garbage(tstate, &unreachable);

/* Clear weakrefs to types and invoke callbacks as necessary. */
stats->collected += handle_weakrefs(&types, to);
gc_list_validate_space(to, gcstate->visited_space);
validate_list(to, collecting_clear_unreachable_clear);

/* Call tp_finalize on types. */
finalize_garbage(tstate, &types);

/* Merge types back to unreachable to properly process resurected
* objects and so on.
*/
gc_list_merge(&types, &unreachable);

/* Handle any objects that may have resurrected after the call
* to 'finalize_garbage' and continue the collection with the
* objects that are still unreachable */
Expand Down
Loading