From e1b4cbc422efc2cc6c5bbbf26645dbb5c001cb49 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Tue, 14 May 2013 22:31:26 -0500 Subject: when arguments are cells clear the locals slot (backport of #17927) --- Lib/test/test_scope.py | 29 +++++++++++++++++++++++++++++ Lib/test/test_super.py | 13 +++++++++++++ Misc/NEWS | 3 +++ Objects/typeobject.c | 12 ++++++++++++ Python/ceval.c | 8 ++++++-- 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_scope.py b/Lib/test/test_scope.py index 129a18a..41678b5 100644 --- a/Lib/test/test_scope.py +++ b/Lib/test/test_scope.py @@ -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(): diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index f6469cf..1e272ee 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -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) diff --git a/Misc/NEWS b/Misc/NEWS index c616045..b7e749e 100644 --- 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 diff --git a/Objects/typeobject.c b/Objects/typeobject.c index f40dd10..a55d977 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -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"); diff --git a/Python/ceval.c b/Python/ceval.c index f427841..e59c39d 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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); -- cgit v0.12