[Python-checkins] cpython (merge 3.5 -> 3.5): Issue #24912: Prevent __class__ assignment to immutable built-in objects.

guido.van.rossum python-checkins at python.org
Sun Sep 6 00:21:41 CEST 2015


https://hg.python.org/cpython/rev/1c55f169f4ee
changeset:   97684:1c55f169f4ee
branch:      3.5
parent:      97681:3594400f3202
parent:      97683:27cc5cce0292
user:        Guido van Rossum <guido at python.org>
date:        Sat Sep 05 15:20:08 2015 -0700
summary:
  Issue #24912: Prevent __class__ assignment to immutable built-in objects. (Merge 3.5.0 -> 3.5)

files:
  Lib/test/test_descr.py |  45 ++++++++++++++++++++++
  Misc/NEWS              |   2 +
  Objects/typeobject.c   |  59 ++++++++++++++++++++++++++++++
  3 files changed, 106 insertions(+), 0 deletions(-)


diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -1036,6 +1036,51 @@
         self.assertTrue(m.__class__ is types.ModuleType)
         self.assertFalse(hasattr(m, "a"))
 
+        # Make sure that builtin immutable objects don't support __class__
+        # assignment, because the object instances may be interned.
+        # We set __slots__ = () to ensure that the subclasses are
+        # memory-layout compatible, and thus otherwise reasonable candidates
+        # for __class__ assignment.
+
+        # The following types have immutable instances, but are not
+        # subclassable and thus don't need to be checked:
+        #   NoneType, bool
+
+        class MyInt(int):
+            __slots__ = ()
+        with self.assertRaises(TypeError):
+            (1).__class__ = MyInt
+
+        class MyFloat(float):
+            __slots__ = ()
+        with self.assertRaises(TypeError):
+            (1.0).__class__ = MyFloat
+
+        class MyComplex(complex):
+            __slots__ = ()
+        with self.assertRaises(TypeError):
+            (1 + 2j).__class__ = MyComplex
+
+        class MyStr(str):
+            __slots__ = ()
+        with self.assertRaises(TypeError):
+            "a".__class__ = MyStr
+
+        class MyBytes(bytes):
+            __slots__ = ()
+        with self.assertRaises(TypeError):
+            b"a".__class__ = MyBytes
+
+        class MyTuple(tuple):
+            __slots__ = ()
+        with self.assertRaises(TypeError):
+            ().__class__ = MyTuple
+
+        class MyFrozenSet(frozenset):
+            __slots__ = ()
+        with self.assertRaises(TypeError):
+            frozenset().__class__ = MyFrozenSet
+
     def test_slots(self):
         # Testing __slots__...
         class C0(object):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -85,6 +85,8 @@
 Core and Builtins
 -----------------
 
+- Issue #24912: Prevent __class__ assignment to immutable built-in objects.
+
 - Issue #24975: Fix AST compilation for PEP 448 syntax.
 
 Library
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3666,6 +3666,65 @@
         return -1;
     }
     newto = (PyTypeObject *)value;
+    /* In versions of CPython prior to 3.5, the code in
+       compatible_for_assignment was not set up to correctly check for memory
+       layout / slot / etc. compatibility for non-HEAPTYPE classes, so we just
+       disallowed __class__ assignment in any case that wasn't HEAPTYPE ->
+       HEAPTYPE.
+
+       During the 3.5 development cycle, we fixed the code in
+       compatible_for_assignment to correctly check compatibility between
+       arbitrary types, and started allowing __class__ assignment in all cases
+       where the old and new types did in fact have compatible slots and
+       memory layout (regardless of whether they were implemented as HEAPTYPEs
+       or not).
+
+       Just before 3.5 was released, though, we discovered that this led to
+       problems with immutable types like int, where the interpreter assumes
+       they are immutable and interns some values. Formerly this wasn't a
+       problem, because they really were immutable -- in particular, all the
+       types where the interpreter applied this interning trick happened to
+       also be statically allocated, so the old HEAPTYPE rules were
+       "accidentally" stopping them from allowing __class__ assignment. But
+       with the changes to __class__ assignment, we started allowing code like
+
+         class MyInt(int):
+             ...
+         # Modifies the type of *all* instances of 1 in the whole program,
+         # including future instances (!), because the 1 object is interned.
+         (1).__class__ = MyInt
+
+       (see https://bugs.python.org/issue24912).
+
+       In theory the proper fix would be to identify which classes rely on
+       this invariant and somehow disallow __class__ assignment only for them,
+       perhaps via some mechanism like a new Py_TPFLAGS_IMMUTABLE flag (a
+       "blacklisting" approach). But in practice, since this problem wasn't
+       noticed late in the 3.5 RC cycle, we're taking the conservative
+       approach and reinstating the same HEAPTYPE->HEAPTYPE check that we used
+       to have, plus a "whitelist". For now, the whitelist consists only of
+       ModuleType subtypes, since those are the cases that motivated the patch
+       in the first place -- see https://bugs.python.org/issue22986 -- and
+       since module objects are mutable we can be sure that they are
+       definitely not being interned. So now we allow HEAPTYPE->HEAPTYPE *or*
+       ModuleType subtype -> ModuleType subtype.
+
+       So far as we know, all the code beyond the following 'if' statement
+       will correctly handle non-HEAPTYPE classes, and the HEAPTYPE check is
+       needed only to protect that subset of non-HEAPTYPE classes for which
+       the interpreter has baked in the assumption that all instances are
+       truly immutable.
+    */
+    if (!(PyType_IsSubtype(newto, &PyModule_Type) &&
+          PyType_IsSubtype(oldto, &PyModule_Type)) &&
+        (!(newto->tp_flags & Py_TPFLAGS_HEAPTYPE) ||
+         !(oldto->tp_flags & Py_TPFLAGS_HEAPTYPE))) {
+        PyErr_Format(PyExc_TypeError,
+                     "__class__ assignment only supported for heap types "
+                     "or ModuleType subclasses");
+        return -1;
+    }
+
     if (compatible_for_assignment(oldto, newto, "__class__")) {
         if (newto->tp_flags & Py_TPFLAGS_HEAPTYPE)
             Py_INCREF(newto);

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


More information about the Python-checkins mailing list