From 52c424343d625d3e795bd670aaaf542dfa63b7c7 Mon Sep 17 00:00:00 2001
From: Benjamin Peterson <benjamin@python.org>
Date: Wed, 7 Mar 2012 18:41:11 -0600
Subject: allow cycles throught the __dict__ slot to be cleared (closes
 #1469629)

Patch from Armin, test from me.
---
 Lib/test/test_descr.py | 19 +++++++++++++++++--
 Misc/NEWS              |  3 +++
 Objects/typeobject.c   |  9 +++++++--
 3 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 92304b4..4aeb77f 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -1,8 +1,10 @@
 import builtins
+import gc
 import sys
 import types
 import math
 import unittest
+import weakref
 
 from copy import deepcopy
 from test import support
@@ -1186,7 +1188,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 __eq__(self, other):
@@ -4380,7 +4381,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):
@@ -4429,6 +4429,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
+        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):
diff --git a/Misc/NEWS b/Misc/NEWS
index 44683f5..119dfd7 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@ What's New in Python 3.2.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 #14172: Fix reference leak when marshalling a buffer-like object
   (other than a bytes object).
 
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index c3822ab..e006694 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -830,8 +830,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);
-- 
cgit v0.12