Issue #24912: Prevent __class__ assignment to immutable built-in objects. (Merge 3.5 -> 3.6)

This commit is contained in:
Guido van Rossum 2015-09-05 15:20:57 -07:00
commit ba5f59089a
3 changed files with 106 additions and 0 deletions

View File

@ -1036,6 +1036,51 @@ order (MRO) for bases """
self.assertTrue(m.__class__ is types.ModuleType) self.assertTrue(m.__class__ is types.ModuleType)
self.assertFalse(hasattr(m, "a")) 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): def test_slots(self):
# Testing __slots__... # Testing __slots__...
class C0(object): class C0(object):

View File

@ -169,6 +169,8 @@ Release date: 2015-09-06
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #24912: Prevent __class__ assignment to immutable built-in objects.
- Issue #24975: Fix AST compilation for PEP 448 syntax. - Issue #24975: Fix AST compilation for PEP 448 syntax.
Library Library

View File

@ -3674,6 +3674,65 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
return -1; return -1;
} }
newto = (PyTypeObject *)value; 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 (compatible_for_assignment(oldto, newto, "__class__")) {
if (newto->tp_flags & Py_TPFLAGS_HEAPTYPE) if (newto->tp_flags & Py_TPFLAGS_HEAPTYPE)
Py_INCREF(newto); Py_INCREF(newto);