cpython: check local class namespace before reaching for cells (closes #17853)
http://hg.python.org/cpython/rev/cf65c7a75f55 changeset: 83572:cf65c7a75f55 user: Benjamin Peterson <benjamin@python.org> date: Tue Apr 30 09:41:40 2013 -0400 summary: check local class namespace before reaching for cells (closes #17853) files: Doc/library/dis.rst | 7 + Include/opcode.h | 1 + Lib/importlib/_bootstrap.py | 4 +- Lib/opcode.py | 3 + Lib/test/test_scope.py | 13 + Misc/NEWS | 3 + Python/ceval.c | 33 +++ Python/compile.c | 5 +- Python/importlib.h | 238 ++++++++++++------------ Python/opcode_targets.h | 2 +- 10 files changed, 187 insertions(+), 122 deletions(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -722,6 +722,13 @@ Pushes a reference to the object the cell contains on the stack. +.. opcode:: LOAD_CLASSDEREF (i) + + Much like :opcode:`LOAD_DEREF` but first checks the locals dictionary before + consulting the cell. This is used for loading free variables in class + bodies. + + .. opcode:: STORE_DEREF (i) Stores TOS into the cell contained in slot *i* of the cell and free variable diff --git a/Include/opcode.h b/Include/opcode.h --- a/Include/opcode.h +++ b/Include/opcode.h @@ -140,6 +140,7 @@ #define SET_ADD 146 #define MAP_ADD 147 +#define LOAD_CLASSDEREF 148 /* EXCEPT_HANDLER is a special, implicit block type which is created when entering an except handler. It is not an opcode but we define it here diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -388,12 +388,14 @@ # Python 3.3a4 3230 (revert changes to implicit __class__ closure) # Python 3.4a1 3250 (evaluate positional default arguments before # keyword-only defaults) +# Python 3.4a1 3260 (add LOAD_CLASSDEREF; allow locals of class to override +# free vars) # # MAGIC must change whenever the bytecode emitted by the compiler may no # longer be understood by older implementations of the eval loop (usually # due to the addition of new opcodes). -_MAGIC_BYTES = (3250).to_bytes(2, 'little') + b'\r\n' +_MAGIC_BYTES = (3260).to_bytes(2, 'little') + b'\r\n' _RAW_MAGIC_NUMBER = int.from_bytes(_MAGIC_BYTES, 'little') _PYCACHE = '__pycache__' diff --git a/Lib/opcode.py b/Lib/opcode.py --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -179,6 +179,9 @@ def_op('SET_ADD', 146) def_op('MAP_ADD', 147) +def_op('LOAD_CLASSDEREF', 148) +hasfree.append(148) + def_op('EXTENDED_ARG', 144) EXTENDED_ARG = 144 diff --git a/Lib/test/test_scope.py b/Lib/test/test_scope.py --- a/Lib/test/test_scope.py +++ b/Lib/test/test_scope.py @@ -714,6 +714,19 @@ global a + def testClassNamespaceOverridesClosure(self): + # See #17853. + x = 42 + class X: + locals()["x"] = 43 + y = x + self.assertEqual(X.y, 43) + class X: + locals()["x"] = 43 + del x + self.assertFalse(hasattr(X, "x")) + self.assertEqual(x, 42) + def test_main(): run_unittest(ScopeTests) diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ Core and Builtins ----------------- +- Issue #17853: Ensure locals of a class that shadow free variables always win + over the closures. + - Issue #17863: In the interactive console, don't loop forever if the encoding can't be fetched from stdin. diff --git a/Python/ceval.c b/Python/ceval.c --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2260,6 +2260,39 @@ DISPATCH(); } + TARGET(LOAD_CLASSDEREF) { + PyObject *name, *value, *locals = f->f_locals; + int idx; + assert(locals); + assert(oparg >= PyTuple_GET_SIZE(co->co_cellvars)); + idx = oparg - PyTuple_GET_SIZE(co->co_cellvars); + assert(idx >= 0 && idx < PyTuple_GET_SIZE(co->co_freevars)); + name = PyTuple_GET_ITEM(co->co_freevars, idx); + if (PyDict_CheckExact(locals)) { + value = PyDict_GetItem(locals, name); + Py_XINCREF(value); + } + else { + value = PyObject_GetItem(locals, name); + if (value == NULL && PyErr_Occurred()) { + if (!PyErr_ExceptionMatches(PyExc_KeyError)) + goto error; + PyErr_Clear(); + } + } + if (!value) { + PyObject *cell = freevars[oparg]; + value = PyCell_GET(cell); + if (value == NULL) { + format_exc_unbound(co, oparg); + goto error; + } + Py_INCREF(value); + } + PUSH(value); + DISPATCH(); + } + TARGET(LOAD_DEREF) { PyObject *cell = freevars[oparg]; PyObject *value = PyCell_GET(cell); diff --git a/Python/compile.c b/Python/compile.c --- a/Python/compile.c +++ b/Python/compile.c @@ -970,6 +970,7 @@ case LOAD_CLOSURE: return 1; case LOAD_DEREF: + case LOAD_CLASSDEREF: return 1; case STORE_DEREF: return -1; @@ -2677,7 +2678,9 @@ switch (optype) { case OP_DEREF: switch (ctx) { - case Load: op = LOAD_DEREF; break; + case Load: + op = (c->u->u_ste->ste_type == ClassBlock) ? LOAD_CLASSDEREF : LOAD_DEREF; + break; case Store: op = STORE_DEREF; break; case AugLoad: case AugStore: diff --git a/Python/importlib.h b/Python/importlib.h --- a/Python/importlib.h +++ b/Python/importlib.h [stripped] diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -147,7 +147,7 @@ &&TARGET_LIST_APPEND, &&TARGET_SET_ADD, &&TARGET_MAP_ADD, - &&_unknown_opcode, + &&TARGET_LOAD_CLASSDEREF, &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode, -- Repository URL: http://hg.python.org/cpython
participants (1)
-
benjamin.peterson