#17927: Keep frame from referencing cell-ified arguments.
This commit is contained in:
parent
8c01ffa6ed
commit
6832c81d5d
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue