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

armin.rigo python-checkins at python.org
Wed May 2 21:23:38 CEST 2007


Author: armin.rigo
Date: Wed May  2 21:23:31 2007
New Revision: 55080

Removed:
   python/trunk/Lib/test/crashers/dangerous_subclassing.py
   python/trunk/Lib/test/crashers/modify_dict_attr.py
Modified:
   python/trunk/Lib/test/test_descr.py
   python/trunk/Misc/NEWS
   python/trunk/Objects/typeobject.c
Log:
Fix for #1303614 and #1174712:
- __dict__ descriptor abuse for subclasses of built-in types
- subclassing from both ModuleType and another built-in types

Thanks zseil for the patch.


Deleted: /python/trunk/Lib/test/crashers/dangerous_subclassing.py
==============================================================================
--- /python/trunk/Lib/test/crashers/dangerous_subclassing.py	Wed May  2 21:23:31 2007
+++ (empty file)
@@ -1,12 +0,0 @@
-
-# http://python.org/sf/1174712
-
-import types
-
-class X(types.ModuleType, str):
-    """Such a subclassing is incorrectly allowed --
-    see the SF bug report for explanations"""
-
-if __name__ == '__main__':
-    X('name')    # segfault: ModuleType.__init__() reads
-                 # the dict at the wrong offset

Deleted: /python/trunk/Lib/test/crashers/modify_dict_attr.py
==============================================================================
--- /python/trunk/Lib/test/crashers/modify_dict_attr.py	Wed May  2 21:23:31 2007
+++ (empty file)
@@ -1,20 +0,0 @@
-
-# http://python.org/sf/1303614
-
-class Y(object):
-    pass
-
-class type_with_modifiable_dict(type, Y):
-    pass
-
-class MyClass(object):
-    """This class has its __dict__ attribute indirectly
-    exposed via the __dict__ getter/setter of Y.
-    """
-    __metaclass__ = type_with_modifiable_dict
-
-
-if __name__ == '__main__':
-    dictattr = Y.__dict__['__dict__']
-    dictattr.__delete__(MyClass)  # 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	Wed May  2 21:23:31 2007
@@ -3,6 +3,7 @@
 from test.test_support import verify, vereq, verbose, TestFailed, TESTFN, get_original_stdout
 from copy import deepcopy
 import warnings
+import types
 
 warnings.filterwarnings("ignore",
          r'complex divmod\(\), // and % are deprecated$',
@@ -861,6 +862,16 @@
                 ("getattr", "foo"),
                 ("delattr", "foo")])
 
+    # http://python.org/sf/1174712
+    try:
+        class Module(types.ModuleType, str):
+            pass
+    except TypeError:
+        pass
+    else:
+        raise TestFailed("inheriting from ModuleType and str at the "
+                          "same time should fail")
+
 def multi():
     if verbose: print "Testing multiple inheritance..."
     class C(object):
@@ -2907,8 +2918,73 @@
     cant(a, [])
     cant(a, 1)
     del a.__dict__ # Deleting __dict__ is allowed
-    # Classes don't allow __dict__ assignment
-    cant(C, {})
+
+    class Base(object):
+        pass
+    def verify_dict_readonly(x):
+        """
+        x has to be an instance of a class inheriting from Base.
+        """
+        cant(x, {})
+        try:
+            del x.__dict__
+        except (AttributeError, TypeError):
+            pass
+        else:
+            raise TestFailed, "shouldn't allow del %r.__dict__" % x
+        dict_descr = Base.__dict__["__dict__"]
+        try:
+            dict_descr.__set__(x, {})
+        except (AttributeError, TypeError):
+            pass
+        else:
+            raise TestFailed, "dict_descr allowed access to %r's dict" % x
+
+    # Classes don't allow __dict__ assignment and have readonly dicts
+    class Meta1(type, Base):
+        pass
+    class Meta2(Base, type):
+        pass
+    class D(object):
+        __metaclass__ = Meta1
+    class E(object):
+        __metaclass__ = Meta2
+    for cls in C, D, E:
+        verify_dict_readonly(cls)
+        class_dict = cls.__dict__
+        try:
+            class_dict["spam"] = "eggs"
+        except TypeError:
+            pass
+        else:
+            raise TestFailed, "%r's __dict__ can be modified" % cls
+
+    # Modules also disallow __dict__ assignment
+    class Module1(types.ModuleType, Base):
+        pass
+    class Module2(Base, types.ModuleType):
+        pass
+    for ModuleType in Module1, Module2:
+        mod = ModuleType("spam")
+        verify_dict_readonly(mod)
+        mod.__dict__["spam"] = "eggs"
+
+    # Exception's __dict__ can be replaced, but not deleted
+    class Exception1(Exception, Base):
+        pass
+    class Exception2(Base, Exception):
+        pass
+    for ExceptionType in Exception, Exception1, Exception2:
+        e = ExceptionType()
+        e.__dict__ = {"a": 1}
+        vereq(e.a, 1)
+        try:
+            del e.__dict__
+        except (TypeError, AttributeError):
+            pass
+        else:
+            raise TestFaied, "%r's __dict__ can be deleted" % e
+
 
 def pickles():
     if verbose:

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Wed May  2 21:23:31 2007
@@ -12,6 +12,9 @@
 Core and builtins
 -----------------
 
+- Bug #1303614: don't expose object's __dict__ when the dict is
+  inherited from a builtin base.
+
 - When __slots__ are set to a unicode string, make it work the same as 
   setting a plain string, ie don't expand to single letter identifiers.
 
@@ -199,13 +202,6 @@
 
 - Bug #1664966: Fix crash in exec if Unicode filename can't be decoded.
 
-- Add new requirements for metaclasses.  1) If type or a subclass of type
-  occurs in __bases__, it must occur before any non-type bases, e.g.
-  before regular classes.  2) If you assign to __bases__, you may not
-  change the metaclass.  Many more illegal assignments to __bases__
-  are now checked and raise TypeErrors.  This changed fixed at least
-  one known crash.
-
 Library
 -------
 

Modified: python/trunk/Objects/typeobject.c
==============================================================================
--- python/trunk/Objects/typeobject.c	(original)
+++ python/trunk/Objects/typeobject.c	Wed May  2 21:23:31 2007
@@ -1423,10 +1423,12 @@
 			type->tp_itemsize != base->tp_itemsize;
 	}
 	if (type->tp_weaklistoffset && base->tp_weaklistoffset == 0 &&
-	    type->tp_weaklistoffset + sizeof(PyObject *) == t_size)
+	    type->tp_weaklistoffset + sizeof(PyObject *) == t_size &&
+	    type->tp_flags & Py_TPFLAGS_HEAPTYPE)
 		t_size -= sizeof(PyObject *);
 	if (type->tp_dictoffset && base->tp_dictoffset == 0 &&
-	    type->tp_dictoffset + sizeof(PyObject *) == t_size)
+	    type->tp_dictoffset + sizeof(PyObject *) == t_size &&
+	    type->tp_flags & Py_TPFLAGS_HEAPTYPE)
 		t_size -= sizeof(PyObject *);
 
 	return t_size != b_size;
@@ -1452,12 +1454,73 @@
 static int update_slot(PyTypeObject *, PyObject *);
 static void fixup_slot_dispatchers(PyTypeObject *);
 
+/*
+ * Helpers for  __dict__ descriptor.  We don't want to expose the dicts
+ * inherited from various builtin types.  The builtin base usually provides
+ * its own __dict__ descriptor, so we use that when we can.
+ */
+static PyTypeObject *
+get_builtin_base_with_dict(PyTypeObject *type)
+{
+	while (type->tp_base != NULL) {
+		if (type->tp_dictoffset != 0 &&
+		    !(type->tp_flags & Py_TPFLAGS_HEAPTYPE))
+			return type;
+		type = type->tp_base;
+	}
+	return NULL;
+}
+
+static PyObject *
+get_dict_descriptor(PyTypeObject *type)
+{
+	static PyObject *dict_str;
+	PyObject *descr;
+
+	if (dict_str == NULL) {
+		dict_str = PyString_InternFromString("__dict__");
+		if (dict_str == NULL)
+			return NULL;
+	}
+	descr = _PyType_Lookup(type, dict_str);
+	if (descr == NULL || !PyDescr_IsData(descr))
+		return NULL;
+
+	return descr;
+}
+
+static void
+raise_dict_descr_error(PyObject *obj)
+{
+	PyErr_Format(PyExc_TypeError,
+		     "this __dict__ descriptor does not support "
+		     "'%.200s' objects", obj->ob_type->tp_name);
+}
+
 static PyObject *
 subtype_dict(PyObject *obj, void *context)
 {
-	PyObject **dictptr = _PyObject_GetDictPtr(obj);
+	PyObject **dictptr;
 	PyObject *dict;
+	PyTypeObject *base;
+
+	base = get_builtin_base_with_dict(obj->ob_type);
+	if (base != NULL) {
+		descrgetfunc func;
+		PyObject *descr = get_dict_descriptor(base);
+		if (descr == NULL) {
+			raise_dict_descr_error(obj);
+			return NULL;
+		}
+		func = descr->ob_type->tp_descr_get;
+		if (func == NULL) {
+			raise_dict_descr_error(obj);
+			return NULL;
+		}
+		return func(descr, obj, (PyObject *)(obj->ob_type));
+	}
 
+	dictptr = _PyObject_GetDictPtr(obj);
 	if (dictptr == NULL) {
 		PyErr_SetString(PyExc_AttributeError,
 				"This object has no __dict__");
@@ -1473,9 +1536,27 @@
 static int
 subtype_setdict(PyObject *obj, PyObject *value, void *context)
 {
-	PyObject **dictptr = _PyObject_GetDictPtr(obj);
+	PyObject **dictptr;
 	PyObject *dict;
+	PyTypeObject *base;
+
+	base = get_builtin_base_with_dict(obj->ob_type);
+	if (base != NULL) {
+		descrsetfunc func;
+		PyObject *descr = get_dict_descriptor(base);
+		if (descr == NULL) {
+			raise_dict_descr_error(obj);
+			return -1;
+		}
+		func = descr->ob_type->tp_descr_set;
+		if (func == NULL) {
+			raise_dict_descr_error(obj);
+			return -1;
+		}
+		return func(descr, obj, value);
+	}
 
+	dictptr = _PyObject_GetDictPtr(obj);
 	if (dictptr == NULL) {
 		PyErr_SetString(PyExc_AttributeError,
 				"This object has no __dict__");


More information about the Python-checkins mailing list