Skip to content

Commit f7f9e99

Browse files
committed
subtype_dealloc(): A more complete fix for critical bug 840829 +
expanded the test case with a piece that needs the more-complete fix. I'll backport this to 2.3 maint.
1 parent 981a918 commit f7f9e99

File tree

2 files changed

+38
-6
lines changed

2 files changed

+38
-6
lines changed

Lib/test/test_weakref.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,25 @@ class C(object):
318318
wr = weakref.ref(c, lambda ignore: gc.collect())
319319
del c
320320

321+
# There endeth the first part. It gets worse.
322+
del wr
323+
324+
c1 = C()
325+
c1.i = C()
326+
wr = weakref.ref(c1.i, lambda ignore: gc.collect())
327+
328+
c2 = C()
329+
c2.c1 = c1
330+
del c1 # still alive because c2 points to it
331+
332+
# Now when subtype_dealloc gets called on c2, it's not enough just
333+
# that c2 is immune from gc while the weakref callbacks associated
334+
# with c2 execute (there are none in this 2nd half of the test, btw).
335+
# subtype_dealloc goes on to call the base classes' deallocs too,
336+
# so any gc triggered by weakref callbacks associated with anything
337+
# torn down by a base class dealloc can also trigger double
338+
# deallocation of c2.
339+
del c2
321340

322341
class Object:
323342
def __init__(self, arg):

Objects/typeobject.c

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -639,10 +639,10 @@ subtype_dealloc(PyObject *self)
639639
++_PyTrash_delete_nesting;
640640
Py_TRASHCAN_SAFE_BEGIN(self);
641641
--_PyTrash_delete_nesting;
642-
/* DO NOT restore GC tracking at this point. The weakref callback
643-
* (if any) may trigger GC, and if self is tracked at that point,
644-
* it will look like trash to GC and GC will try to delete it
645-
* again. Double-deallocation is a subtle disaster.
642+
/* DO NOT restore GC tracking at this point. weakref callbacks
643+
* (if any, and whether directly here or indirectly in something we
644+
* call) may trigger GC, and if self is tracked at that point, it
645+
* will look like trash to GC and GC will try to delete self again.
646646
*/
647647

648648
/* Find the nearest base with a different tp_dealloc */
@@ -658,13 +658,15 @@ subtype_dealloc(PyObject *self)
658658

659659
if (type->tp_weaklistoffset && !base->tp_weaklistoffset)
660660
PyObject_ClearWeakRefs(self);
661-
_PyObject_GC_TRACK(self); /* We'll untrack for real later */
662661

663662
/* Maybe call finalizer; exit early if resurrected */
664663
if (type->tp_del) {
664+
_PyObject_GC_TRACK(self);
665665
type->tp_del(self);
666666
if (self->ob_refcnt > 0)
667-
goto endlabel;
667+
goto endlabel; /* resurrected */
668+
else
669+
_PyObject_GC_UNTRACK(self);
668670
}
669671

670672
/* Clear slots up to the nearest base with a different tp_dealloc */
@@ -689,6 +691,7 @@ subtype_dealloc(PyObject *self)
689691
}
690692

691693
/* Finalize GC if the base doesn't do GC and we do */
694+
_PyObject_GC_TRACK(self);
692695
if (!PyType_IS_GC(base))
693696
_PyObject_GC_UNTRACK(self);
694697

@@ -730,6 +733,16 @@ subtype_dealloc(PyObject *self)
730733
trashcan begin
731734
GC track
732735
736+
Q. Why did the last question say "immediately GC-track again"?
737+
It's nowhere near immediately.
738+
739+
A. Because the code *used* to re-track immediately. Bad Idea.
740+
self has a refcount of 0, and if gc ever gets its hands on it
741+
(which can happen if any weakref callback gets invoked), it
742+
looks like trash to gc too, and gc also tries to delete self
743+
then. But we're already deleting self. Double dealloction is
744+
a subtle disaster.
745+
733746
Q. Why the bizarre (net-zero) manipulation of
734747
_PyTrash_delete_nesting around the trashcan macros?
735748

0 commit comments

Comments
 (0)