summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Hylton <jeremy@alum.mit.edu>2007-02-27 18:29:45 (GMT)
committerJeremy Hylton <jeremy@alum.mit.edu>2007-02-27 18:29:45 (GMT)
commitfa955697fa0986b19abac7b026c8f00b4393adf9 (patch)
tree33731c5bb7b7e6fee121763b7c29439f1cc97779
parent2d1f5c93bbe3ed6202e14e9d3c3708174b62f8e6 (diff)
downloadcpython-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.py19
-rw-r--r--Lib/test/test_descr.py148
-rw-r--r--Objects/typeobject.c126
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)