diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -780,24 +780,32 @@ finalize_garbage(PyGC_Head *collectable, PyGC_Head *old) { destructor finalize; - PyGC_Head *gc = collectable->gc.gc_next; + PyGC_Head seen; - for (; gc != collectable; gc = gc->gc.gc_next) { + /* While we're going through the loop, `finalize(op)` may cause op, or + * other objects, to be reclaimed via refcounts falling to zero. So + ^ there's little we can rely on about the structure of the input + ^ `collectable` list across iterations. For safety, we always take the + * first object in that list and move it to a temporary `seen` list. + * If objects vanish from the `collectable` and `seen` lists we don't + ^ care. + */ + gc_list_init(&seen); + + while (!gc_list_is_empty(collectable)) { + PyGC_Head *gc = collectable->gc.gc_next; PyObject *op = FROM_GC(gc); - + gc_list_move(gc, &seen); if (!_PyGCHead_FINALIZED(gc) && - PyType_HasFeature(Py_TYPE(op), Py_TPFLAGS_HAVE_FINALIZE) && - (finalize = Py_TYPE(op)->tp_finalize) != NULL) { + PyType_HasFeature(Py_TYPE(op), Py_TPFLAGS_HAVE_FINALIZE) && + (finalize = Py_TYPE(op)->tp_finalize) != NULL) { _PyGCHead_SET_FINALIZED(gc, 1); Py_INCREF(op); finalize(op); - if (Py_REFCNT(op) == 1) { - /* op will be destroyed */ - gc = gc->gc.gc_prev; - } Py_DECREF(op); } } + gc_list_merge(&seen, collectable); } /* Walk the collectable list and check that they are really unreachable