Skip to content

Commit

Permalink
hide the __class__ closure from the class body (#12370)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminp committed May 15, 2013
1 parent 209ab9c commit 04b4e5b
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 152 deletions.
3 changes: 3 additions & 0 deletions Include/symtable.h
Expand Up @@ -53,6 +53,9 @@ typedef struct _symtable_entry {
unsigned ste_varkeywords : 1; /* true if block has varkeywords */
unsigned ste_returns_value : 1; /* true if namespace uses return with
an argument */
unsigned ste_needs_class_closure : 1; /* for class scopes, true if a
closure over __class__
should be created */
int ste_lineno; /* first line of block */
int ste_col_offset; /* offset of first line of block */
int ste_opt_lineno; /* lineno of last exec or import * */
Expand Down
3 changes: 2 additions & 1 deletion Lib/importlib/_bootstrap.py
Expand Up @@ -390,12 +390,13 @@ def _call_with_frames_removed(f, *args, **kwds):
# keyword-only defaults)
# Python 3.4a1 3260 (add LOAD_CLASSDEREF; allow locals of class to override
# free vars)
# Python 3.4a1 3270 (various tweaks to the __class_ closure)
#
# 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 = (3260).to_bytes(2, 'little') + b'\r\n'
_MAGIC_BYTES = (3270).to_bytes(2, 'little') + b'\r\n'
_RAW_MAGIC_NUMBER = int.from_bytes(_MAGIC_BYTES, 'little')

_PYCACHE = '__pycache__'
Expand Down
28 changes: 26 additions & 2 deletions Lib/test/test_super.py
Expand Up @@ -81,8 +81,7 @@ def nested():

self.assertEqual(E().f(), 'AE')

@unittest.expectedFailure
def test___class___set(self):
def test_various___class___pathologies(self):
# See issue #12370
class X(A):
def f(self):
Expand All @@ -91,6 +90,31 @@ def f(self):
x = X()
self.assertEqual(x.f(), 'A')
self.assertEqual(x.__class__, 413)
class X:
x = __class__
def f():
__class__
self.assertIs(X.x, type(self))
with self.assertRaises(NameError) as e:
exec("""class X:
__class__
def f():
__class__""", globals(), {})
self.assertIs(type(e.exception), NameError) # Not UnboundLocalError
class X:
global __class__
__class__ = 42
def f():
__class__
self.assertEqual(globals()["__class__"], 42)
del globals()["__class__"]
self.assertNotIn("__class__", X.__dict__)
class X:
nonlocal __class__
__class__ = 42
def f():
__class__
self.assertEqual(__class__, 42)

def test___class___instancemethod(self):
# See issue #14857
Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS
Expand Up @@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1?
Core and Builtins
-----------------

- Issue #12370: Prevent class bodies from interfering with the __class__
closure.

- Issue #17237: Fix crash in the ASCII decoder on m68k.

- Issue #17927: Frame objects kept arguments alive if they had been
Expand Down
64 changes: 49 additions & 15 deletions Python/compile.c
Expand Up @@ -535,6 +535,37 @@ compiler_enter_scope(struct compiler *c, identifier name,
compiler_unit_free(u);
return 0;
}
if (u->u_ste->ste_needs_class_closure) {
/* Cook up a implicit __class__ cell. */
_Py_IDENTIFIER(__class__);
PyObject *tuple, *name, *zero;
int res;
assert(u->u_scope_type == COMPILER_SCOPE_CLASS);
assert(PyDict_Size(u->u_cellvars) == 0);
name = _PyUnicode_FromId(&PyId___class__);
if (!name) {
compiler_unit_free(u);
return 0;
}
tuple = PyTuple_Pack(2, name, Py_TYPE(name));
if (!tuple) {
compiler_unit_free(u);
return 0;
}
zero = PyLong_FromLong(0);
if (!zero) {
Py_DECREF(tuple);
compiler_unit_free(u);
return 0;
}
res = PyDict_SetItem(u->u_cellvars, tuple, zero);
Py_DECREF(tuple);
Py_DECREF(zero);
if (res < 0) {
compiler_unit_free(u);
return 0;
}
}

u->u_freevars = dictbytype(u->u_ste->ste_symbols, FREE, DEF_FREE_CLASS,
PyDict_Size(u->u_cellvars));
Expand Down Expand Up @@ -1331,6 +1362,9 @@ compiler_mod(struct compiler *c, mod_ty mod)
static int
get_ref_type(struct compiler *c, PyObject *name)
{
if (c->u->u_scope_type == COMPILER_SCOPE_CLASS &&
!PyUnicode_CompareWithASCIIString(name, "__class__"))
return CELL;
int scope = PyST_GetScope(c->u->u_ste, name);
if (scope == 0) {
char buf[350];
Expand Down Expand Up @@ -1704,24 +1738,24 @@ compiler_class(struct compiler *c, stmt_ty s)
compiler_exit_scope(c);
return 0;
}
/* return the (empty) __class__ cell */
str = PyUnicode_InternFromString("__class__");
if (str == NULL) {
compiler_exit_scope(c);
return 0;
}
i = compiler_lookup_arg(c->u->u_cellvars, str);
Py_DECREF(str);
if (i == -1) {
/* This happens when nobody references the cell */
PyErr_Clear();
/* Return None */
ADDOP_O(c, LOAD_CONST, Py_None, consts);
}
else {
if (c->u->u_ste->ste_needs_class_closure) {
/* return the (empty) __class__ cell */
str = PyUnicode_InternFromString("__class__");
if (str == NULL) {
compiler_exit_scope(c);
return 0;
}
i = compiler_lookup_arg(c->u->u_cellvars, str);
Py_DECREF(str);
assert(i == 0);
/* Return the cell where to store __class__ */
ADDOP_I(c, LOAD_CLOSURE, i);
}
else {
assert(PyDict_Size(c->u->u_cellvars) == 0);
/* This happens when nobody references the cell. Return None. */
ADDOP_O(c, LOAD_CONST, Py_None, consts);
}
ADDOP_IN_SCOPE(c, RETURN_VALUE);
/* create the code object */
co = assemble(c, 1);
Expand Down

0 comments on commit 04b4e5b

Please sign in to comment.