Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 62a0d6e

Browse files
committed
Issue #16602: When a weakref's target was part of a long deallocation chain, the object could remain reachable through its weakref even though its refcount had dropped to zero.
Thanks to Eugene Toder for diagnosing and reporting the issue.
1 parent 09974b4 commit 62a0d6e

5 files changed

Lines changed: 39 additions & 4 deletions

File tree

Include/weakrefobject.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,17 @@ PyAPI_FUNC(Py_ssize_t) _PyWeakref_GetWeakrefCount(PyWeakReference *head);
7070
PyAPI_FUNC(void) _PyWeakref_ClearRef(PyWeakReference *self);
7171
#endif
7272

73-
#define PyWeakref_GET_OBJECT(ref) (((PyWeakReference *)(ref))->wr_object)
73+
/* Explanation for the Py_REFCNT() check: when a weakref's target is part
74+
of a long chain of deallocations which triggers the trashcan mechanism,
75+
clearing the weakrefs can be delayed long after the target's refcount
76+
has dropped to zero. In the meantime, code accessing the weakref will
77+
be able to "see" the target object even though it is supposed to be
78+
unreachable. See issue #16602. */
79+
80+
#define PyWeakref_GET_OBJECT(ref) \
81+
(Py_REFCNT(((PyWeakReference *)(ref))->wr_object) > 0 \
82+
? ((PyWeakReference *)(ref))->wr_object \
83+
: Py_None)
7484

7585

7686
#ifdef __cplusplus

Lib/test/test_weakref.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,27 @@ def test_hashing(self):
776776
self.assertEqual(hash(a), hash(42))
777777
self.assertRaises(TypeError, hash, b)
778778

779+
def test_trashcan_16602(self):
780+
# Issue #16602: when a weakref's target was part of a long
781+
# deallocation chain, the trashcan mechanism could delay clearing
782+
# of the weakref and make the target object visible from outside
783+
# code even though its refcount had dropped to 0. A crash ensued.
784+
class C:
785+
def __init__(self, parent):
786+
if not parent:
787+
return
788+
wself = weakref.ref(self)
789+
def cb(wparent):
790+
o = wself()
791+
self.wparent = weakref.ref(parent, cb)
792+
793+
d = weakref.WeakKeyDictionary()
794+
root = c = C(None)
795+
for n in range(100):
796+
d[c] = c = C(c)
797+
del root
798+
gc.collect()
799+
779800

780801
class SubclassableWeakrefTestCase(TestBase):
781802

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,7 @@ Jim Tittsler
10671067
Frank J. Tobin
10681068
R Lindsay Todd
10691069
Bennett Todd
1070+
Eugene Toder
10701071
Erik Tollerud
10711072
Matias Torchinsky
10721073
Sandro Tosi

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ What's New in Python 3.2.4
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #16602: When a weakref's target was part of a long deallocation
14+
chain, the object could remain reachable through its weakref even though
15+
its refcount had dropped to zero.
16+
1317
- Issue #16416: On Mac OS X, operating system data are now always
1418
encoded/decoded to/from UTF-8/surrogateescape, instead of the locale encoding
1519
(which may be ASCII if no locale environment variable is set), to avoid

Objects/weakrefobject.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@ clear_weakref(PyWeakReference *self)
5252
{
5353
PyObject *callback = self->wr_callback;
5454

55-
if (PyWeakref_GET_OBJECT(self) != Py_None) {
56-
PyWeakReference **list = GET_WEAKREFS_LISTPTR(
57-
PyWeakref_GET_OBJECT(self));
55+
if (self->wr_object != Py_None) {
56+
PyWeakReference **list = GET_WEAKREFS_LISTPTR(self->wr_object);
5857

5958
if (*list == self)
6059
/* If 'self' is the end of the list (and thus self->wr_next == NULL)

0 commit comments

Comments
 (0)