Index: Objects/weakrefobject.c =================================================================== --- Objects/weakrefobject.c (revision 64224) +++ Objects/weakrefobject.c (working copy) @@ -907,7 +907,8 @@ current->wr_callback = NULL; clear_weakref(current); if (callback != NULL) { - handle_callback(current, callback); + if(current->ob_refcnt > 0) + handle_callback(current, callback); Py_DECREF(callback); } } @@ -925,9 +926,15 @@ for (i = 0; i < count; ++i) { PyWeakReference *next = current->wr_next; - Py_INCREF(current); - PyTuple_SET_ITEM(tuple, i * 2, (PyObject *) current); - PyTuple_SET_ITEM(tuple, i * 2 + 1, current->wr_callback); + if(current->ob_refcnt > 0) + { + Py_INCREF(current); + PyTuple_SET_ITEM(tuple, i * 2, (PyObject *) current); + PyTuple_SET_ITEM(tuple, i * 2 + 1, current->wr_callback); + } + else { + Py_DECREF(current->wr_callback); + } current->wr_callback = NULL; clear_weakref(current); current = next; @@ -935,6 +942,7 @@ for (i = 0; i < count; ++i) { PyObject *callback = PyTuple_GET_ITEM(tuple, i * 2 + 1); + /* The tuple may have slots left to NULL */ if (callback != NULL) { PyObject *item = PyTuple_GET_ITEM(tuple, i * 2); handle_callback((PyWeakReference *)item, callback); Index: Lib/test/test_weakref.py =================================================================== --- Lib/test/test_weakref.py (revision 64224) +++ Lib/test/test_weakref.py (working copy) @@ -666,7 +666,7 @@ w = Target() -class SubclassableWeakrefTestCase(unittest.TestCase): +class SubclassableWeakrefTestCase(TestBase): def test_subclass_refs(self): class MyRef(weakref.ref): @@ -730,7 +730,43 @@ self.assertEqual(r.meth(), "abcdef") self.failIf(hasattr(r, "__dict__")) + def _test_subclass_refs_with_cycle(self): + # Bug #3110 + # An instance of a weakref subclass can have attributes. + # If such a weakref holds the only strong reference to the object, + # deleting the weakref will delete the object. In this case, + # the callback must not be called, because the ref object is being deleted. + class MyRef(weakref.ref): + pass + # Use a local callback, for "regrtest -R::" to detect refcounting problems + def callback(w): + pass + + o = C() + r1 = MyRef(o, callback) + r1.o = o + del o + + del r1 # Used to crash here + + self.assertEqual(self.cbcalled, 0) + + # Same test, with two weakrefs to the same object + # (since code paths are different) + o = C() + r1 = MyRef(o, callback) + r2 = MyRef(o, callback) + r1.r = r2 + r2.o = o + del o + del r2 + + del r1 # Used to crash here + + self.assertEqual(self.cbcalled, 0) + + class Object: def __init__(self, arg): self.arg = arg @@ -1171,6 +1207,7 @@ MappingTestCase, WeakValueDictionaryTestCase, WeakKeyDictionaryTestCase, + SubclassableWeakrefTestCase, ) test_support.run_doctest(sys.modules[__name__])