]> granicus.if.org Git - python/commitdiff
allow cycles throught the __dict__ slot to be cleared (closes #1469629)
authorBenjamin Peterson <benjamin@python.org>
Thu, 8 Mar 2012 00:41:11 +0000 (18:41 -0600)
committerBenjamin Peterson <benjamin@python.org>
Thu, 8 Mar 2012 00:41:11 +0000 (18:41 -0600)
Patch from Armin, test from me.

Lib/test/test_descr.py
Misc/NEWS
Objects/typeobject.c

index 964cc5cf564bf8549fb46d3096d506b1c897d54f..4050524e4c51d83b7cfffd62efbf80b83174e2ed 100644 (file)
@@ -1,7 +1,9 @@
 import __builtin__
+import gc
 import sys
 import types
 import unittest
+import weakref
 
 from copy import deepcopy
 from test import test_support
@@ -1127,7 +1129,6 @@ order (MRO) for bases """
         self.assertEqual(Counted.counter, 0)
 
         # Test lookup leaks [SF bug 572567]
-        import gc
         if hasattr(gc, 'get_objects'):
             class G(object):
                 def __cmp__(self, other):
@@ -4541,7 +4542,6 @@ order (MRO) for bases """
         self.assertRaises(AttributeError, getattr, C(), "attr")
         self.assertEqual(descr.counter, 4)
 
-        import gc
         class EvilGetattribute(object):
             # This used to segfault
             def __getattr__(self, name):
@@ -4590,6 +4590,21 @@ order (MRO) for bases """
         foo = Foo()
         str(foo)
 
+    def test_cycle_through_dict(self):
+        # See bug #1469629
+        class X(dict):
+            def __init__(self):
+                dict.__init__(self)
+                self.__dict__ = self
+        x = X()
+        x.attr = 42
+        wr = weakref.ref(x)
+        del x
+        test_support.gc_collect()
+        self.assertIsNone(wr())
+        for o in gc.get_objects():
+            self.assertIsNot(type(o), X)
+
 class DictProxyTests(unittest.TestCase):
     def setUp(self):
         class C(object):
index 89b647170fb70ae7c7c8a017beab9a2a21a5a47b..bbf224b7cbe01b40c88eea2d53919b7d3090462f 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -9,6 +9,9 @@ What's New in Python 2.7.3 release candidate 1?
 Core and Builtins
 -----------------
 
+- Issue #1469629: Allow cycles through an object's __dict__ slot to be
+  collected. (For example if ``x.__dict__ is x``).
+
 - Issue #13521: dict.setdefault() now does only one lookup for the given key,
   making it "atomic" for many purposes.  Patch by Filip GruszczyƄski.
 
index 3db02edaed1ed4dcc442dda2def760b11f63972e..ce1d42b6ae743449ae92007febf30f76bdadde28 100644 (file)
@@ -876,8 +876,13 @@ subtype_clear(PyObject *self)
         assert(base);
     }
 
-    /* There's no need to clear the instance dict (if any);
-       the collector will call its tp_clear handler. */
+    /* Clear the instance dict (if any), to break cycles involving only
+       __dict__ slots (as in the case 'self.__dict__ is self'). */
+    if (type->tp_dictoffset != base->tp_dictoffset) {
+        PyObject **dictptr = _PyObject_GetDictPtr(self);
+        if (dictptr && *dictptr)
+            Py_CLEAR(*dictptr);
+    }
 
     if (baseclear)
         return baseclear(self);