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.
diff --git a/Lib/test/crashers/dangerous_subclassing.py b/Lib/test/crashers/dangerous_subclassing.py
deleted file mode 100644
index 0479952..0000000
--- 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 11c6721..0000000
--- a/Lib/test/crashers/modify_dict_attr.py
+++ /dev/null
@@ -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
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 53181ac..57cef44 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$',
@@ -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:
diff --git a/Misc/NEWS b/Misc/NEWS
index 36a18c2..2a1ee0a 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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
-------
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 89d2d4f..7735851 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -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__");