Add checking for a number of metaclass error conditions.

We add some new rules that are required for preserving internal
invariants of types.

1.  If type (or a subclass of type) appears in bases, it must appear
    before any non-type bases.  If a non-type base (like a regular
    new-style class) occurred first, it could trick type into
    allocating the new class an __dict__ which must be impossible.

2. There are several checks that are made of bases when creating a
   type.  Those checks are now repeated when assigning to __bases__.
   We also add the restriction that assignment to __bases__ may not
   change the metaclass of the type.

Add new tests for these cases and for a few other oddball errors that
were no previously tested.  Remove a crasher test that was fixed.

Also some internal refactoring:  Extract the code to find the most
derived metaclass of a type and its bases.  It is now needed in two
places.  Rewrite the TypeError checks in test_descr to use doctest.
The tests now clearly show what exception they expect to see.
This commit is contained in:
Jeremy Hylton 2007-02-27 18:29:45 +00:00
parent 2d1f5c93bb
commit fa955697fa
3 changed files with 192 additions and 91 deletions

View File

@ -1,19 +0,0 @@
# http://python.org/sf/1303614
class Y(object):
pass
class type_with_modifiable_dict(Y, type):
pass
class MyClass(object):
"""This class has its __dict__ attribute completely exposed:
user code can read, reassign and even delete it.
"""
__metaclass__ = type_with_modifiable_dict
if __name__ == '__main__':
del MyClass.__dict__ # if we set tp_dict to NULL,
print MyClass # doing anything with MyClass segfaults

View File

@ -1,6 +1,6 @@
# Test enhancements related to descriptors and new-style classes # Test enhancements related to descriptors and new-style classes
from test.test_support import verify, vereq, verbose, TestFailed, TESTFN, get_original_stdout from test.test_support import verify, vereq, verbose, TestFailed, TESTFN, get_original_stdout, run_doctest
from copy import deepcopy from copy import deepcopy
import warnings import warnings
@ -820,6 +820,22 @@ def metaclass():
except TypeError: pass except TypeError: pass
else: raise TestFailed, "calling object w/o call method should raise TypeError" else: raise TestFailed, "calling object w/o call method should raise TypeError"
# Testing code to find most derived baseclass
class A(type):
def __new__(*args, **kwargs):
return type.__new__(*args, **kwargs)
class B(object):
pass
class C(object):
__metaclass__ = A
# The most derived metaclass of D is A rather than type.
class D(B, C):
pass
def pymods(): def pymods():
if verbose: print "Testing Python subclass of module..." if verbose: print "Testing Python subclass of module..."
log = [] log = []
@ -1411,49 +1427,89 @@ def dynamics():
verify(someclass != object) verify(someclass != object)
def errors(): def errors():
if verbose: print "Testing errors..." """Test that type can't be placed after an instance of type in bases.
try: >>> class C(list, dict):
class C(list, dict): ... pass
pass Traceback (most recent call last):
except TypeError: TypeError: Error when calling the metaclass bases
pass multiple bases have instance lay-out conflict
else:
verify(0, "inheritance from both list and dict should be illegal")
try: >>> class C(object, None):
class C(object, None): ... pass
pass Traceback (most recent call last):
except TypeError: TypeError: Error when calling the metaclass bases
pass bases must be types
else:
verify(0, "inheritance from non-type should be illegal")
class Classic:
pass
try: >>> class C(type(len)):
class C(type(len)): ... pass
pass Traceback (most recent call last):
except TypeError: TypeError: Error when calling the metaclass bases
pass type 'builtin_function_or_method' is not an acceptable base type
else:
verify(0, "inheritance from CFunction should be illegal")
try: >>> class Classic:
class C(object): ... def __init__(*args): pass
__slots__ = 1 >>> class C(object):
except TypeError: ... __metaclass__ = Classic
pass
else:
verify(0, "__slots__ = 1 should be illegal")
try: >>> class C(object):
class C(object): ... __slots__ = 1
__slots__ = [1] Traceback (most recent call last):
except TypeError: TypeError: Error when calling the metaclass bases
pass 'int' object is not iterable
else:
verify(0, "__slots__ = [1] should be illegal") >>> class C(object):
... __slots__ = [1]
Traceback (most recent call last):
TypeError: Error when calling the metaclass bases
__slots__ items must be strings, not 'int'
>>> class A(object):
... pass
>>> class B(A, type):
... pass
Traceback (most recent call last):
TypeError: Error when calling the metaclass bases
metaclass conflict: type must occur in bases before other non-classic base classes
Create two different metaclasses in order to setup an error where
there is no inheritance relationship between the metaclass of a class
and the metaclass of its bases.
>>> class M1(type):
... pass
>>> class M2(type):
... pass
>>> class A1(object):
... __metaclass__ = M1
>>> class A2(object):
... __metaclass__ = M2
>>> class B(A1, A2):
... pass
Traceback (most recent call last):
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
>>> class B(A1):
... pass
Also check that assignment to bases is safe.
>>> B.__bases__ = A1, A2
Traceback (most recent call last):
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
>>> B.__bases__ = A2,
Traceback (most recent call last):
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
>>> class M3(M1):
... pass
>>> class C(object):
... __metaclass__ = M3
>>> B.__bases__ = C,
Traceback (most recent call last):
TypeError: assignment to __bases__ may not change metatype
"""
def classmethods(): def classmethods():
if verbose: print "Testing class methods..." if verbose: print "Testing class methods..."
@ -4179,7 +4235,6 @@ def test_main():
slots() slots()
slotspecials() slotspecials()
dynamics() dynamics()
errors()
classmethods() classmethods()
classmethods_in_c() classmethods_in_c()
staticmethods() staticmethods()
@ -4247,6 +4302,9 @@ def test_main():
methodwrapper() methodwrapper()
notimplemented() notimplemented()
from test import test_descr
run_doctest(test_descr, verbosity=True)
if verbose: print "All OK" if verbose: print "All OK"
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -127,6 +127,7 @@ type_get_bases(PyTypeObject *type, void *context)
return type->tp_bases; return type->tp_bases;
} }
static PyTypeObject *most_derived_metaclass(PyTypeObject *, PyObject *);
static PyTypeObject *best_base(PyObject *); static PyTypeObject *best_base(PyObject *);
static int mro_internal(PyTypeObject *); static int mro_internal(PyTypeObject *);
static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, char *); static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, char *);
@ -187,7 +188,7 @@ type_set_bases(PyTypeObject *type, PyObject *value, void *context)
Py_ssize_t i; Py_ssize_t i;
int r = 0; int r = 0;
PyObject *ob, *temp; PyObject *ob, *temp;
PyTypeObject *new_base, *old_base; PyTypeObject *new_base, *old_base, *metatype;
PyObject *old_bases, *old_mro; PyObject *old_bases, *old_mro;
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
@ -230,6 +231,17 @@ type_set_bases(PyTypeObject *type, PyObject *value, void *context)
} }
} }
metatype = most_derived_metaclass(type->ob_type, value);
if (metatype == NULL)
return -1;
if (metatype != type->ob_type) {
PyErr_SetString(PyExc_TypeError,
"assignment to __bases__ may not change "
"metatype");
return -1;
}
new_base = best_base(value); new_base = best_base(value);
if (!new_base) { if (!new_base) {
@ -1355,7 +1367,14 @@ mro_internal(PyTypeObject *type)
/* Calculate the best base amongst multiple base classes. /* Calculate the best base amongst multiple base classes.
This is the first one that's on the path to the "solid base". */ This is the first one that's on the path to the "solid base".
Requires that all base classes be types or classic classes.
Will return NULL with TypeError set if
1) the base classes have conflicting layout instances, or
2) all the bases are classic classes.
*/
static PyTypeObject * static PyTypeObject *
best_base(PyObject *bases) best_base(PyObject *bases)
@ -1373,12 +1392,7 @@ best_base(PyObject *bases)
base_proto = PyTuple_GET_ITEM(bases, i); base_proto = PyTuple_GET_ITEM(bases, i);
if (PyClass_Check(base_proto)) if (PyClass_Check(base_proto))
continue; continue;
if (!PyType_Check(base_proto)) { assert(PyType_Check(base_proto));
PyErr_SetString(
PyExc_TypeError,
"bases must be types");
return NULL;
}
base_i = (PyTypeObject *)base_proto; base_i = (PyTypeObject *)base_proto;
if (base_i->tp_dict == NULL) { if (base_i->tp_dict == NULL) {
if (PyType_Ready(base_i) < 0) if (PyType_Ready(base_i) < 0)
@ -1431,6 +1445,8 @@ extra_ivars(PyTypeObject *type, PyTypeObject *base)
return t_size != b_size; return t_size != b_size;
} }
/* Return the type object that will determine the layout of the instance. */
static PyTypeObject * static PyTypeObject *
solid_base(PyTypeObject *type) solid_base(PyTypeObject *type)
{ {
@ -1446,6 +1462,71 @@ solid_base(PyTypeObject *type)
return base; return base;
} }
/* Determine the proper metatype to deal with this, and check some
error cases while we're at it. Note that if some other metatype
wins to contract, it's possible that its instances are not types.
Error cases of interest: 1. The metaclass is not a subclass of a
base class. 2. A non-type, non-classic base class appears before
type.
*/
static PyTypeObject *
most_derived_metaclass(PyTypeObject *metatype, PyObject *bases)
{
Py_ssize_t nbases, i;
PyTypeObject *winner;
/* types_ordered: One of three states possible:
0 type is in bases
1 non-types also in bases
2 type follows non-type in bases (error)
*/
int types_ordered = 0;
nbases = PyTuple_GET_SIZE(bases);
winner = metatype;
for (i = 0; i < nbases; i++) {
PyObject *tmp = PyTuple_GET_ITEM(bases, i);
PyTypeObject *tmptype = tmp->ob_type;
if (tmptype == &PyClass_Type)
continue; /* Special case classic classes */
if (!PyType_Check(tmp)) {
PyErr_SetString(PyExc_TypeError,
"bases must be types");
return NULL;
}
if (PyObject_IsSubclass(tmp, (PyObject*)&PyType_Type)) {
if (types_ordered == 1) {
types_ordered = 2;
}
}
else if (!types_ordered)
types_ordered = 1;
if (winner == tmptype)
continue;
if (PyType_IsSubtype(winner, tmptype))
continue;
if (PyType_IsSubtype(tmptype, winner)) {
winner = tmptype;
continue;
}
PyErr_SetString(PyExc_TypeError,
"metaclass conflict: "
"the metaclass of a derived class "
"must be a (non-strict) subclass "
"of the metaclasses of all its bases");
return NULL;
}
if (types_ordered == 2) {
PyErr_SetString(PyExc_TypeError,
"metaclass conflict: "
"type must occur in bases before other "
"non-classic base classes");
return NULL;
}
return winner;
}
static void object_dealloc(PyObject *); static void object_dealloc(PyObject *);
static int object_init(PyObject *, PyObject *, PyObject *); static int object_init(PyObject *, PyObject *, PyObject *);
static int update_slot(PyTypeObject *, PyObject *); static int update_slot(PyTypeObject *, PyObject *);
@ -1642,37 +1723,18 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
&PyDict_Type, &dict)) &PyDict_Type, &dict))
return NULL; return NULL;
/* Determine the proper metatype to deal with this, winner = most_derived_metaclass(metatype, bases);
and check for metatype conflicts while we're at it. if (winner == NULL)
Note that if some other metatype wins to contract,
it's possible that its instances are not types. */
nbases = PyTuple_GET_SIZE(bases);
winner = metatype;
for (i = 0; i < nbases; i++) {
tmp = PyTuple_GET_ITEM(bases, i);
tmptype = tmp->ob_type;
if (tmptype == &PyClass_Type)
continue; /* Special case classic classes */
if (PyType_IsSubtype(winner, tmptype))
continue;
if (PyType_IsSubtype(tmptype, winner)) {
winner = tmptype;
continue;
}
PyErr_SetString(PyExc_TypeError,
"metaclass conflict: "
"the metaclass of a derived class "
"must be a (non-strict) subclass "
"of the metaclasses of all its bases");
return NULL; return NULL;
}
if (winner != metatype) { if (winner != metatype) {
if (winner->tp_new != type_new) /* Pass it to the winner */ if (winner->tp_new != type_new) /* Pass it to the winner */ {
return winner->tp_new(winner, args, kwds); return winner->tp_new(winner, args, kwds);
}
metatype = winner; metatype = winner;
} }
/* Adjust for empty tuple bases */ /* Adjust for empty tuple bases */
nbases = PyTuple_GET_SIZE(bases);
if (nbases == 0) { if (nbases == 0) {
bases = PyTuple_Pack(1, &PyBaseObject_Type); bases = PyTuple_Pack(1, &PyBaseObject_Type);
if (bases == NULL) if (bases == NULL)