Index: Lib/test/crashers/dangerous_subclassing.py =================================================================== --- Lib/test/crashers/dangerous_subclassing.py (revision 54845) +++ Lib/test/crashers/dangerous_subclassing.py (working copy) @@ -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 Index: Lib/test/crashers/modify_dict_attr.py =================================================================== --- Lib/test/crashers/modify_dict_attr.py (revision 54845) +++ Lib/test/crashers/modify_dict_attr.py (working copy) @@ -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 Index: Lib/test/test_descr.py =================================================================== --- Lib/test/test_descr.py (revision 54845) +++ Lib/test/test_descr.py (working copy) @@ -3,6 +3,7 @@ from test.test_support import verify, vereq, verbose, TestFailed, TESTFN, get_original_stdout, run_doctest 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): @@ -1503,15 +1514,6 @@ 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. @@ -2931,9 +2933,58 @@ 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" + + def pickles(): if verbose: print "Testing pickling and copying new-style classes and objects..." Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 54845) +++ Misc/NEWS (working copy) @@ -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,12 +202,9 @@ - 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. +- Assignment to __bases__ must not change the metaclass. Many more + illegal assignments to __bases__ are now checked and raise TypeErrors. + This change fixed at least one known crash. Library ------- Index: Objects/typeobject.c =================================================================== --- Objects/typeobject.c (revision 54845) +++ Objects/typeobject.c (working copy) @@ -1436,10 +1436,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; @@ -1476,12 +1478,6 @@ { 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; @@ -1495,13 +1491,6 @@ "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)) @@ -1517,13 +1506,6 @@ "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; } @@ -1532,12 +1514,74 @@ 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__"); @@ -1553,9 +1597,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__");