diff --git a/Lib/test/crashers/dangerous_subclassing.py b/Lib/test/crashers/dangerous_subclassing.py deleted file mode 100644 index 0479952b2ca..00000000000 --- a/Lib/test/crashers/dangerous_subclassing.py +++ /dev/null @@ -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 diff --git a/Lib/test/crashers/modify_dict_attr.py b/Lib/test/crashers/modify_dict_attr.py deleted file mode 100644 index dfce467c8e1..00000000000 --- 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 a9ee5bcab69..3c29017ea49 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -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$', @@ -845,6 +846,16 @@ def pymods(): ("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): @@ -2815,8 +2826,73 @@ def setdict(): 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: diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 8a6f78266c1..fd33fa47b76 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1422,10 +1422,12 @@ extra_ivars(PyTypeObject *type, PyTypeObject *base) 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; @@ -1451,12 +1453,73 @@ static int object_init(PyObject *, PyObject *, PyObject *); 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__"); @@ -1472,9 +1535,27 @@ subtype_dict(PyObject *obj, void *context) 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__");