mirror of https://github.com/python/cpython
Issue #24912: Prevent __class__ assignment to immutable built-in objects.
This commit is contained in:
parent
1b66910537
commit
7d293ee97d
|
@ -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):
|
||||||
|
|
|
@ -10,6 +10,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
|
||||||
|
|
|
@ -3666,6 +3666,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);
|
||||||
|
|
Loading…
Reference in New Issue