[Python-checkins] cpython: hide the __class__ closure from the class body (#12370)

benjamin.peterson python-checkins at python.org
Wed May 15 22:27:40 CEST 2013


http://hg.python.org/cpython/rev/3d858f1eef54
changeset:   83783:3d858f1eef54
user:        Benjamin Peterson <benjamin at python.org>
date:        Wed May 15 15:26:42 2013 -0500
summary:
  hide the __class__ closure from the class body (#12370)

files:
  Include/symtable.h          |    3 +
  Lib/importlib/_bootstrap.py |    3 +-
  Lib/test/test_super.py      |   28 ++-
  Misc/NEWS                   |    3 +
  Python/compile.c            |   64 ++++-
  Python/importlib.h          |  238 ++++++++++++------------
  Python/symtable.c           |   34 +-
  7 files changed, 221 insertions(+), 152 deletions(-)


diff --git a/Include/symtable.h b/Include/symtable.h
--- a/Include/symtable.h
+++ b/Include/symtable.h
@@ -53,6 +53,9 @@
     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 * */
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -390,12 +390,13 @@
 #                        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__'
diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py
--- a/Lib/test/test_super.py
+++ b/Lib/test/test_super.py
@@ -81,8 +81,7 @@
 
         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):
@@ -91,6 +90,31 @@
         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
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 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
diff --git a/Python/compile.c b/Python/compile.c
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -535,6 +535,37 @@
         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));
@@ -1331,6 +1362,9 @@
 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];
@@ -1704,23 +1738,23 @@
             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);
+        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 {
-            /* Return the cell where to store __class__ */
-            ADDOP_I(c, LOAD_CLOSURE, i);
+            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 */
diff --git a/Python/importlib.h b/Python/importlib.h
--- a/Python/importlib.h
+++ b/Python/importlib.h
[stripped]
diff --git a/Python/symtable.c b/Python/symtable.c
--- a/Python/symtable.c
+++ b/Python/symtable.c
@@ -77,6 +77,7 @@
     ste->ste_child_free = 0;
     ste->ste_generator = 0;
     ste->ste_returns_value = 0;
+    ste->ste_needs_class_closure = 0;
 
     if (PyDict_SetItem(st->st_blocks, ste->ste_id, (PyObject *)ste) < 0)
         goto fail;
@@ -514,13 +515,10 @@
 
    Note that the current block's free variables are included in free.
    That's safe because no name can be free and local in the same scope.
-
-   The 'restricted' argument may be set to a string to restrict the analysis
-   to the one variable whose name equals that string (e.g. "__class__").
 */
 
 static int
-analyze_cells(PyObject *scopes, PyObject *free, const char *restricted)
+analyze_cells(PyObject *scopes, PyObject *free)
 {
     PyObject *name, *v, *v_cell;
     int success = 0;
@@ -537,9 +535,6 @@
             continue;
         if (!PySet_Contains(free, name))
             continue;
-        if (restricted != NULL &&
-            PyUnicode_CompareWithASCIIString(name, restricted))
-            continue;
         /* Replace LOCAL with CELL for this name, and remove
            from free. It is safe to replace the value of name
            in the dict, because it will not cause a resize.
@@ -555,6 +550,20 @@
     return success;
 }
 
+static int
+drop_class_free(PySTEntryObject *ste, PyObject *free)
+{
+    int res;
+    if (!GET_IDENTIFIER(__class__))
+        return 0;
+    res = PySet_Discard(free, __class__);
+    if (res < 0)
+        return 0;
+    if (res)
+        ste->ste_needs_class_closure = 1;
+    return 1;
+}
+
 /* Check for illegal statements in unoptimized namespaces */
 static int
 check_unoptimized(const PySTEntryObject* ste) {
@@ -785,7 +794,6 @@
         /* Special-case __class__ */
         if (!GET_IDENTIFIER(__class__))
             goto error;
-        assert(PySet_Contains(local, __class__) == 1);
         if (PySet_Add(newbound, __class__) < 0)
             goto error;
     }
@@ -818,11 +826,9 @@
     Py_DECREF(temp);
 
     /* Check if any local variables must be converted to cell variables */
-    if (ste->ste_type == FunctionBlock && !analyze_cells(scopes, newfree,
-                                                         NULL))
+    if (ste->ste_type == FunctionBlock && !analyze_cells(scopes, newfree))
         goto error;
-    else if (ste->ste_type == ClassBlock && !analyze_cells(scopes, newfree,
-                                                           "__class__"))
+    else if (ste->ste_type == ClassBlock && !drop_class_free(ste, newfree))
         goto error;
     /* Records the results of the analysis in the symbol table entry */
     if (!update_symbols(ste->ste_symbols, scopes, bound, newfree,
@@ -1179,9 +1185,7 @@
         if (!symtable_enter_block(st, s->v.ClassDef.name, ClassBlock,
                                   (void *)s, s->lineno, s->col_offset))
             VISIT_QUIT(st, 0);
-        if (!GET_IDENTIFIER(__class__) ||
-            !symtable_add_def(st, __class__, DEF_LOCAL) ||
-            !GET_IDENTIFIER(__locals__) ||
+        if (!GET_IDENTIFIER(__locals__) ||
             !symtable_add_def(st, __locals__, DEF_PARAM)) {
             symtable_exit_block(st, s);
             VISIT_QUIT(st, 0);

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list