[Python-checkins] r53997 - in python/trunk: Lib/test/crashers/modify_dict_attr.py Lib/test/test_descr.py Objects/typeobject.c

jeremy.hylton python-checkins at python.org
Tue Feb 27 19:29:50 CET 2007


Author: jeremy.hylton
Date: Tue Feb 27 19:29:45 2007
New Revision: 53997

Removed:
   python/trunk/Lib/test/crashers/modify_dict_attr.py
Modified:
   python/trunk/Lib/test/test_descr.py
   python/trunk/Objects/typeobject.c
Log:
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.



Deleted: /python/trunk/Lib/test/crashers/modify_dict_attr.py
==============================================================================
--- /python/trunk/Lib/test/crashers/modify_dict_attr.py	Tue Feb 27 19:29:45 2007
+++ (empty 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

Modified: python/trunk/Lib/test/test_descr.py
==============================================================================
--- python/trunk/Lib/test/test_descr.py	(original)
+++ python/trunk/Lib/test/test_descr.py	Tue Feb 27 19:29:45 2007
@@ -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 @@
     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 @@
     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
+    """Test that type can't be placed after an instance of type in bases.
 
-    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")
+    >>> 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 @@
     slots()
     slotspecials()
     dynamics()
-    errors()
     classmethods()
     classmethods_in_c()
     staticmethods()
@@ -4247,6 +4302,9 @@
     methodwrapper()
     notimplemented()
 
+    from test import test_descr
+    run_doctest(test_descr, verbosity=True)
+
     if verbose: print "All OK"
 
 if __name__ == "__main__":

Modified: python/trunk/Objects/typeobject.c
==============================================================================
--- python/trunk/Objects/typeobject.c	(original)
+++ python/trunk/Objects/typeobject.c	Tue Feb 27 19:29:45 2007
@@ -127,6 +127,7 @@
 	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 @@
 	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 @@
 		}
 	}
 
+
+        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 @@
 
 
 /* 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 @@
 		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 @@
 	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 @@
 		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 @@
 					 &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)


More information about the Python-checkins mailing list