]> granicus.if.org Git - python/commitdiff
when arguments are cells clear the locals slot (backport of #17927)
authorBenjamin Peterson <benjamin@python.org>
Wed, 15 May 2013 03:31:26 +0000 (22:31 -0500)
committerBenjamin Peterson <benjamin@python.org>
Wed, 15 May 2013 03:31:26 +0000 (22:31 -0500)
Lib/test/test_scope.py
Lib/test/test_super.py
Misc/NEWS
Objects/typeobject.c
Python/ceval.c

index 129a18aade6b73ed56bc16d314be2164854bf16a..41678b51e7469e6ddf8e91c76c3d716b70274486 100644 (file)
@@ -1,4 +1,6 @@
 import unittest
+import weakref
+
 from test.support import check_syntax_error, cpython_only, run_unittest
 
 
@@ -713,6 +715,33 @@ class ScopeTests(unittest.TestCase):
         def b():
             global a
 
+    @cpython_only
+    def testCellLeak(self):
+        # Issue 17927.
+        #
+        # The issue was that if self was part of a cycle involving the
+        # frame of a method call, *and* the method contained a nested
+        # function referencing self, thereby forcing 'self' into a
+        # cell, setting self to None would not be enough to break the
+        # frame -- the frame had another reference to the instance,
+        # which could not be cleared by the code running in the frame
+        # (though it will be cleared when the frame is collected).
+        # Without the lambda, setting self to None is enough to break
+        # the cycle.
+        class Tester:
+            def dig(self):
+                if 0:
+                    lambda: self
+                try:
+                    1/0
+                except Exception as exc:
+                    self.exc = exc
+                self = None  # Break the cycle
+        tester = Tester()
+        tester.dig()
+        ref = weakref.ref(tester)
+        del tester
+        self.assertIsNone(ref())
 
 
 def test_main():
index f6469cf26392d726bc3e26d999c2c92a8c98f32e..1e272ee2cf575f627f9bf4e82d1a291706fdb017 100644 (file)
@@ -130,6 +130,19 @@ class TestSuper(unittest.TestCase):
                 super()
         self.assertRaises(RuntimeError, X().f)
 
+    def test_cell_as_self(self):
+        class X:
+            def meth(self):
+                super()
+
+        def f():
+            k = X()
+            def g():
+                return k
+            return g
+        c = f().__closure__[0]
+        self.assertRaises(TypeError, X.meth, c)
+
 
 def test_main():
     support.run_unittest(TestSuper)
index c616045921c6afa2b382b9e77ccc9ee747fbb544..b7e749eba38828223368bfd32512f3a8c05fbab7 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@ What's New in Python 3.3.2?
 Core and Builtins
 -----------------
 
+- Issue #17927: Frame objects kept arguments alive if they had been copied into
+  a cell, even if the cell was cleared.
+
 - Issue #17237: Fix crash in the ASCII decoder on m68k.
 
 - Issue #17408: Avoid using an obsolete instance of the copyreg module when
index f40dd10a34041a581a7ed36235d35cae26225c1e..a55d9775ded5be389fa71147d21bd2e67f961629 100644 (file)
@@ -6519,6 +6519,18 @@ super_init(PyObject *self, PyObject *args, PyObject *kwds)
             return -1;
         }
         obj = f->f_localsplus[0];
+        if (obj == NULL && co->co_cell2arg) {
+            /* The first argument might be a cell. */
+            n = PyTuple_GET_SIZE(co->co_cellvars);
+            for (i = 0; i < n; i++) {
+                if (co->co_cell2arg[i] == 0) {
+                    PyObject *cell = f->f_localsplus[co->co_nlocals + i];
+                    assert(PyCell_Check(cell));
+                    obj = PyCell_GET(cell);
+                    break;
+                }
+            }
+        }
         if (obj == NULL) {
             PyErr_SetString(PyExc_RuntimeError,
                             "super(): arg[0] deleted");
index f42784141d7f58a2bb651888f267122cea352e42..e59c39ddd7e186ffc16ff6654250e87eb05c3b20 100644 (file)
@@ -3403,10 +3403,14 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
         int arg;
         /* Possibly account for the cell variable being an argument. */
         if (co->co_cell2arg != NULL &&
-            (arg = co->co_cell2arg[i]) != CO_CELL_NOT_AN_ARG)
+            (arg = co->co_cell2arg[i]) != CO_CELL_NOT_AN_ARG) {
             c = PyCell_New(GETLOCAL(arg));
-        else
+            /* Clear the local copy. */
+            SETLOCAL(arg, NULL);
+        }
+        else {
             c = PyCell_New(NULL);
+        }
         if (c == NULL)
             goto fail;
         SETLOCAL(co->co_nlocals + i, c);