]> granicus.if.org Git - python/commitdiff
#17927: Keep frame from referencing cell-ified arguments.
authorGuido van Rossum <guido@python.org>
Fri, 10 May 2013 15:47:42 +0000 (08:47 -0700)
committerGuido van Rossum <guido@python.org>
Fri, 10 May 2013 15:47:42 +0000 (08:47 -0700)
Lib/test/test_scope.py
Misc/NEWS
Objects/typeobject.c
Python/ceval.c

index f4ed2442c6b4f4d8fe0ddfc9b621e58550a69706..1c06224cba65a7dabb924a161bc61d8f4821e9ca 100644 (file)
@@ -1,3 +1,4 @@
+import gc
 import unittest
 from test.support import check_syntax_error, cpython_only, run_unittest
 
@@ -713,7 +714,6 @@ class ScopeTests(unittest.TestCase):
         def b():
             global a
 
-
     def testClassNamespaceOverridesClosure(self):
         # See #17853.
         x = 42
@@ -727,6 +727,45 @@ class ScopeTests(unittest.TestCase):
         self.assertFalse(hasattr(X, "x"))
         self.assertEqual(x, 42)
 
+    @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.
+        logs = []
+        class Canary:
+            def __del__(self):
+                logs.append('canary')
+        class Base:
+            def dig(self):
+                pass
+        class Tester(Base):
+            def __init__(self):
+                self.canary = Canary()
+            def dig(self):
+                if 0:
+                    lambda: self
+                try:
+                    1/0
+                except Exception as exc:
+                    self.exc = exc
+                super().dig()
+                self = None  # Break the cycle
+        tester = Tester()
+        tester.dig()
+        del tester
+        logs.append('collect')
+        gc.collect()
+        self.assertEqual(logs, ['canary', 'collect'])
+
 
 def test_main():
     run_unittest(ScopeTests)
index d56163d2ce42c62602a26d9a19e334bae43885c4..d807d0bc741a5956fee3c6d1e4d68535e55b1248 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1?
 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 #17807: Generators can now be finalized even when they are part of
   a reference cycle.
 
index aa67af81b09ef364cd9cc1e075bbdd83f239f770..e418a3a95748b3b757f9307f327cd774917903a1 100644 (file)
@@ -6510,6 +6510,10 @@ super_init(PyObject *self, PyObject *args, PyObject *kwds)
             return -1;
         }
         obj = f->f_localsplus[0];
+        if (obj != NULL && PyCell_Check(obj)) {
+            /* It might be a cell.  See cell var initialization in ceval.c. */
+            obj = PyCell_GET(obj);
+        }
         if (obj == NULL) {
             PyErr_SetString(PyExc_RuntimeError,
                             "super(): arg[0] deleted");
index d32b6fbf5846aa482563cf5bbea56e3b4d6fa89b..d6dba56b3c3857c2ad6f9871722e8307ef9a9ad9 100644 (file)
@@ -3519,12 +3519,20 @@ 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
+            if (c == NULL)
+                goto fail;
+            /* Reference the cell from the argument slot, for super().
+               See typeobject.c. */
+            Py_INCREF(c);
+            SETLOCAL(arg, c);
+        }
+        else {
             c = PyCell_New(NULL);
-        if (c == NULL)
-            goto fail;
+            if (c == NULL)
+                goto fail;
+        }
         SETLOCAL(co->co_nlocals + i, c);
     }
     for (i = 0; i < PyTuple_GET_SIZE(co->co_freevars); ++i) {