cpython: 27cc5cce0292 (original) (raw)
Mercurial > cpython
changeset 97683:27cc5cce0292 3.5
Issue #24912: Prevent __class__ assignment to immutable built-in objects. [#24912]
Guido van Rossum guido@dropbox.com | |
---|---|
date | Fri, 04 Sep 2015 20:54:07 -0700 |
parents | 438dde69871d |
children | 1c55f169f4ee 09b62202d9b7 |
files | Lib/test/test_descr.py Misc/NEWS Objects/typeobject.c |
diffstat | 3 files changed, 106 insertions(+), 0 deletions(-)[+] [-] Lib/test/test_descr.py 45 Misc/NEWS 2 Objects/typeobject.c 59 |
line wrap: on
line diff
--- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1036,6 +1036,51 @@ order (MRO) for bases """ self.assertTrue(m.class is types.ModuleType) self.assertFalse(hasattr(m, "a"))
# Make sure that builtin immutable objects don't support __class__[](#l1.7)
# assignment, because the object instances may be interned.[](#l1.8)
# We set __slots__ = () to ensure that the subclasses are[](#l1.9)
# memory-layout compatible, and thus otherwise reasonable candidates[](#l1.10)
# for __class__ assignment.[](#l1.11)
# The following types have immutable instances, but are not[](#l1.13)
# subclassable and thus don't need to be checked:[](#l1.14)
# NoneType, bool[](#l1.15)
class MyInt(int):[](#l1.17)
__slots__ = ()[](#l1.18)
with self.assertRaises(TypeError):[](#l1.19)
(1).__class__ = MyInt[](#l1.20)
class MyFloat(float):[](#l1.22)
__slots__ = ()[](#l1.23)
with self.assertRaises(TypeError):[](#l1.24)
(1.0).__class__ = MyFloat[](#l1.25)
class MyComplex(complex):[](#l1.27)
__slots__ = ()[](#l1.28)
with self.assertRaises(TypeError):[](#l1.29)
(1 + 2j).__class__ = MyComplex[](#l1.30)
class MyStr(str):[](#l1.32)
__slots__ = ()[](#l1.33)
with self.assertRaises(TypeError):[](#l1.34)
"a".__class__ = MyStr[](#l1.35)
class MyBytes(bytes):[](#l1.37)
__slots__ = ()[](#l1.38)
with self.assertRaises(TypeError):[](#l1.39)
b"a".__class__ = MyBytes[](#l1.40)
class MyTuple(tuple):[](#l1.42)
__slots__ = ()[](#l1.43)
with self.assertRaises(TypeError):[](#l1.44)
().__class__ = MyTuple[](#l1.45)
class MyFrozenSet(frozenset):[](#l1.47)
__slots__ = ()[](#l1.48)
with self.assertRaises(TypeError):[](#l1.49)
frozenset().__class__ = MyFrozenSet[](#l1.50)
+ def test_slots(self): # Testing slots... class C0(object):
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,8 @@ Release date: 2015-09-06 Core and Builtins ----------------- +- Issue #24912: Prevent class assignment to immutable built-in objects. +
--- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3666,6 +3666,65 @@ object_set_class(PyObject *self, PyObjec 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[](#l3.8)
layout / slot / etc. compatibility for non-HEAPTYPE classes, so we just[](#l3.9)
disallowed __class__ assignment in any case that wasn't HEAPTYPE ->[](#l3.10)
HEAPTYPE.[](#l3.11)
During the 3.5 development cycle, we fixed the code in[](#l3.13)
compatible_for_assignment to correctly check compatibility between[](#l3.14)
arbitrary types, and started allowing __class__ assignment in all cases[](#l3.15)
where the old and new types did in fact have compatible slots and[](#l3.16)
memory layout (regardless of whether they were implemented as HEAPTYPEs[](#l3.17)
or not).[](#l3.18)
Just before 3.5 was released, though, we discovered that this led to[](#l3.20)
problems with immutable types like int, where the interpreter assumes[](#l3.21)
they are immutable and interns some values. Formerly this wasn't a[](#l3.22)
problem, because they really were immutable -- in particular, all the[](#l3.23)
types where the interpreter applied this interning trick happened to[](#l3.24)
also be statically allocated, so the old HEAPTYPE rules were[](#l3.25)
"accidentally" stopping them from allowing __class__ assignment. But[](#l3.26)
with the changes to __class__ assignment, we started allowing code like[](#l3.27)
class MyInt(int):[](#l3.29)
...[](#l3.30)
# Modifies the type of *all* instances of 1 in the whole program,[](#l3.31)
# including future instances (!), because the 1 object is interned.[](#l3.32)
(1).__class__ = MyInt[](#l3.33)
(see https://bugs.python.org/issue24912).[](#l3.35)
In theory the proper fix would be to identify which classes rely on[](#l3.37)
this invariant and somehow disallow __class__ assignment only for them,[](#l3.38)
perhaps via some mechanism like a new Py_TPFLAGS_IMMUTABLE flag (a[](#l3.39)
"blacklisting" approach). But in practice, since this problem wasn't[](#l3.40)
noticed late in the 3.5 RC cycle, we're taking the conservative[](#l3.41)
approach and reinstating the same HEAPTYPE->HEAPTYPE check that we used[](#l3.42)
to have, plus a "whitelist". For now, the whitelist consists only of[](#l3.43)
ModuleType subtypes, since those are the cases that motivated the patch[](#l3.44)
in the first place -- see https://bugs.python.org/issue22986 -- and[](#l3.45)
since module objects are mutable we can be sure that they are[](#l3.46)
definitely not being interned. So now we allow HEAPTYPE->HEAPTYPE *or*[](#l3.47)
ModuleType subtype -> ModuleType subtype.[](#l3.48)
So far as we know, all the code beyond the following 'if' statement[](#l3.50)
will correctly handle non-HEAPTYPE classes, and the HEAPTYPE check is[](#l3.51)
needed only to protect that subset of non-HEAPTYPE classes for which[](#l3.52)
the interpreter has baked in the assumption that all instances are[](#l3.53)
truly immutable.[](#l3.54)
- */
- if (!(PyType_IsSubtype(newto, &PyModule_Type) &&
PyType_IsSubtype(oldto, &PyModule_Type)) &&[](#l3.57)
(!(newto->tp_flags & Py_TPFLAGS_HEAPTYPE) ||[](#l3.58)
!(oldto->tp_flags & Py_TPFLAGS_HEAPTYPE))) {[](#l3.59)
PyErr_Format(PyExc_TypeError,[](#l3.60)
"__class__ assignment only supported for heap types "[](#l3.61)
"or ModuleType subclasses");[](#l3.62)
return -1;[](#l3.63)
- }
+ if (compatible_for_assignment(oldto, newto, "class")) { if (newto->tp_flags & Py_TPFLAGS_HEAPTYPE) Py_INCREF(newto);