#17927: Keep frame from referencing cell-ified arguments.

This commit is contained in:
Guido van Rossum 2013-05-10 08:47:42 -07:00
parent 8c01ffa6ed
commit 6832c81d5d
4 changed files with 59 additions and 5 deletions

View 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)

View File

@ -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.

View 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");

View 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) {