diff options
author | Jeremy Hylton <jeremy@alum.mit.edu> | 2007-02-27 18:29:45 (GMT) |
---|---|---|
committer | Jeremy Hylton <jeremy@alum.mit.edu> | 2007-02-27 18:29:45 (GMT) |
commit | fa955697fa0986b19abac7b026c8f00b4393adf9 (patch) | |
tree | 33731c5bb7b7e6fee121763b7c29439f1cc97779 | |
parent | 2d1f5c93bbe3ed6202e14e9d3c3708174b62f8e6 (diff) | |
download | cpython-fa955697fa0986b19abac7b026c8f00b4393adf9.zip cpython-fa955697fa0986b19abac7b026c8f00b4393adf9.tar.gz cpython-fa955697fa0986b19abac7b026c8f00b4393adf9.tar.bz2 |
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.
-rw-r--r-- | Lib/test/crashers/modify_dict_attr.py | 19 | ||||
-rw-r--r-- | Lib/test/test_descr.py | 148 | ||||
-rw-r--r-- | Objects/typeobject.c | 126 |
3 files changed, 197 insertions, 96 deletions
diff --git a/Lib/test/crashers/modify_dict_attr.py b/Lib/test/crashers/modify_dict_attr.py deleted file mode 100644 index dfce467..0000000 --- a/Lib/test/crashers/modify_dict_attr.py +++ /dev/null @@ -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 diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index fcc7c13..208201c 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1,6 +1,6 @@ # 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 import warnings @@ -820,6 +820,22 @@ def metaclass(): except TypeError: pass 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(): if verbose: print "Testing Python subclass of module..." log = [] @@ -1411,49 +1427,89 @@ def dynamics(): verify(someclass != object) def errors(): - if verbose: print "Testing errors..." - - try: - class C(list, dict): - pass - except TypeError: - pass - else: - verify(0, "inheritance from both list and dict should be illegal") - - try: - class C(object, None): - pass - except TypeError: - pass - else: - verify(0, "inheritance from non-type should be illegal") - class Classic: - pass - - try: - class C(type(len)): - pass - except TypeError: - pass - else: - verify(0, "inheritance from CFunction should be illegal") - - try: - class C(object): - __slots__ = 1 - except TypeError: - pass - else: - verify(0, "__slots__ = 1 should be illegal") - - try: - class C(object): - __slots__ = [1] - except TypeError: - pass - else: - verify(0, "__slots__ = [1] should be illegal") + """Test that type can't be placed after an instance of type in bases. + + >>> class C(list, dict): + ... pass + Traceback (most recent call last): + TypeError: Error when calling the metaclass bases + multiple bases have instance lay-out conflict + + >>> class C(object, None): + ... pass + Traceback (most recent call last): + TypeError: Error when calling the metaclass bases + bases must be types + + >>> class C(type(len)): + ... pass + Traceback (most recent call last): + TypeError: Error when calling the metaclass bases + type 'builtin_function_or_method' is not an acceptable base type + + >>> class Classic: + ... def __init__(*args): pass + >>> class C(object): + ... __metaclass__ = Classic + + >>> class C(object): + ... __slots__ = 1 + Traceback (most recent call last): + TypeError: Error when calling the metaclass bases + 'int' object is not iterable + + >>> 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(): if verbose: print "Testing class methods..." @@ -4179,7 +4235,6 @@ def test_main(): slots() slotspecials() dynamics() - errors() classmethods() classmethods_in_c() staticmethods() @@ -4247,6 +4302,9 @@ def test_main(): methodwrapper() notimplemented() + from test import test_descr + run_doctest(test_descr, verbosity=True) + if verbose: print "All OK" if __name__ == "__main__": diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 32a727b..c2ed4b5 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -127,6 +127,7 @@ type_get_bases(PyTypeObject *type, void *context) return type->tp_bases; } +static PyTypeObject *most_derived_metaclass(PyTypeObject *, PyObject *); static PyTypeObject *best_base(PyObject *); static int mro_internal(PyTypeObject *); 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; int r = 0; PyObject *ob, *temp; - PyTypeObject *new_base, *old_base; + PyTypeObject *new_base, *old_base, *metatype; PyObject *old_bases, *old_mro; 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); if (!new_base) { @@ -1355,7 +1367,14 @@ mro_internal(PyTypeObject *type) /* 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 * best_base(PyObject *bases) @@ -1373,12 +1392,7 @@ best_base(PyObject *bases) base_proto = PyTuple_GET_ITEM(bases, i); if (PyClass_Check(base_proto)) continue; - if (!PyType_Check(base_proto)) { - PyErr_SetString( - PyExc_TypeError, - "bases must be types"); - return NULL; - } + assert(PyType_Check(base_proto)); base_i = (PyTypeObject *)base_proto; if (base_i->tp_dict == NULL) { if (PyType_Ready(base_i) < 0) @@ -1431,6 +1445,8 @@ extra_ivars(PyTypeObject *type, PyTypeObject *base) return t_size != b_size; } +/* Return the type object that will determine the layout of the instance. */ + static PyTypeObject * solid_base(PyTypeObject *type) { @@ -1446,6 +1462,71 @@ solid_base(PyTypeObject *type) 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 int object_init(PyObject *, PyObject *, PyObject *); static int update_slot(PyTypeObject *, PyObject *); @@ -1642,37 +1723,18 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) &PyDict_Type, &dict)) return NULL; - /* Determine the proper metatype to deal with this, - and check for metatype conflicts while we're at it. - 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"); + winner = most_derived_metaclass(metatype, bases); + if (winner == NULL) return NULL; - } 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); + } metatype = winner; } /* Adjust for empty tuple bases */ + nbases = PyTuple_GET_SIZE(bases); if (nbases == 0) { bases = PyTuple_Pack(1, &PyBaseObject_Type); if (bases == NULL) |