From ad47da072a436751c0cfd95a21eccea1c39b35f1 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 12 Aug 2002 19:05:44 +0000 Subject: Refactor how __dict__ and __weakref__ interact with __slots__. 1. You can now have __dict__ and/or __weakref__ in your __slots__ (before only __weakref__ was supported). This is treated differently than before: it merely sets a flag that the object should support the corresponding magic. 2. Dynamic types now always have descriptors __dict__ and __weakref__ thrust upon them. If the type in fact does not support one or the other, that descriptor's __get__ method will raise AttributeError. 3. (This is the reason for all this; it fixes SF bug 575229, reported by Cesar Douady.) Given this code: class A(object): __slots__ = [] class B(object): pass class C(A, B): __slots__ = [] the class object for C was broken; its size was less than that of B, and some descriptors on B could cause a segfault. C now correctly inherits __weakrefs__ and __dict__ from B, even though A is the "primary" base (C.__base__ is A). 4. Some code cleanup, and a few comments added. --- Objects/typeobject.c | 202 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 147 insertions(+), 55 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fddde51..766318e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -927,9 +927,38 @@ subtype_setdict(PyObject *obj, PyObject *value, void *context) return 0; } +static PyObject * +subtype_getweakref(PyObject *obj, void *context) +{ + PyObject **weaklistptr; + PyObject *result; + + if (obj->ob_type->tp_weaklistoffset == 0) { + PyErr_SetString(PyExc_AttributeError, + "This object has no __weaklist__"); + return NULL; + } + assert(obj->ob_type->tp_weaklistoffset > 0); + assert(obj->ob_type->tp_weaklistoffset + sizeof(PyObject *) <= + obj->ob_type->tp_basicsize); + weaklistptr = (PyObject **) + ((void *)obj + obj->ob_type->tp_weaklistoffset); + if (*weaklistptr == NULL) + result = Py_None; + else + result = *weaklistptr; + Py_INCREF(result); + return result; +} + static PyGetSetDef subtype_getsets[] = { - {"__dict__", subtype_dict, subtype_setdict, NULL}, - {0}, + /* Not all objects have these attributes! + The descriptor's __get__ method may raise AttributeError. */ + {"__dict__", subtype_dict, subtype_setdict, + "dictionary for instance variables (if defined)"}, + {"__weakref__", subtype_getweakref, NULL, + "list of weak references to the object (if defined)"}, + {0} }; /* bozo: __getstate__ that raises TypeError */ @@ -985,6 +1014,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) etype *et; PyMemberDef *mp; int i, nbases, nslots, slotoffset, add_dict, add_weak; + int j, may_add_dict, may_add_weak; assert(args != NULL && PyTuple_Check(args)); assert(kwds == NULL || PyDict_Check(kwds)); @@ -1072,7 +1102,19 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) nslots = 0; add_dict = 0; add_weak = 0; - if (slots != NULL) { + may_add_dict = base->tp_dictoffset == 0; + may_add_weak = base->tp_weaklistoffset == 0 && base->tp_itemsize == 0; + if (slots == NULL) { + if (may_add_dict) { + add_dict++; + } + if (may_add_weak) { + add_weak++; + } + } + else { + /* Have slots */ + /* Make it into a tuple */ if (PyString_Check(slots)) slots = Py_BuildValue("(O)", slots); @@ -1080,70 +1122,125 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) slots = PySequence_Tuple(slots); if (slots == NULL) return NULL; + assert(PyTuple_Check(slots)); + + /* Are slots allowed? */ nslots = PyTuple_GET_SIZE(slots); if (nslots > 0 && base->tp_itemsize != 0) { PyErr_Format(PyExc_TypeError, "nonempty __slots__ " "not supported for subtype of '%s'", base->tp_name); + bad_slots: + Py_DECREF(slots); return NULL; } + + /* Check for valid slot names and two special cases */ for (i = 0; i < nslots; i++) { - if (!valid_identifier(PyTuple_GET_ITEM(slots, i))) { - Py_DECREF(slots); - return NULL; + PyObject *tmp = PyTuple_GET_ITEM(slots, i); + char *s; + if (!valid_identifier(tmp)) + goto bad_slots; + assert(PyString_Check(tmp)); + s = PyString_AS_STRING(tmp); + if (strcmp(s, "__dict__") == 0) { + if (!may_add_dict || add_dict) { + PyErr_SetString(PyExc_TypeError, + "__dict__ slot disallowed: " + "we already got one"); + goto bad_slots; + } + add_dict++; + } + if (strcmp(s, "__weakref__") == 0) { + if (!may_add_weak || add_weak) { + PyErr_SetString(PyExc_TypeError, + "__weakref__ slot disallowed: " + "either we already got one, " + "or __itemsize__ != 0"); + goto bad_slots; + } + add_weak++; } } - newslots = PyTuple_New(nslots); + /* Copy slots into yet another tuple, demangling names */ + newslots = PyTuple_New(nslots - add_dict - add_weak); if (newslots == NULL) - return NULL; - for (i = 0; i < nslots; i++) { + goto bad_slots; + for (i = j = 0; i < nslots; i++) { + char *s; tmp = PyTuple_GET_ITEM(slots, i); + s = PyString_AS_STRING(tmp); + if ((add_dict && strcmp(s, "__dict__") == 0) || + (add_weak && strcmp(s, "__weakref__") == 0)) + continue; if (_Py_Mangle(PyString_AS_STRING(name), - PyString_AS_STRING(tmp), - buffer, sizeof(buffer))) + PyString_AS_STRING(tmp), + buffer, sizeof(buffer))) { tmp = PyString_FromString(buffer); } else { Py_INCREF(tmp); } - PyTuple_SET_ITEM(newslots, i, tmp); + PyTuple_SET_ITEM(newslots, j, tmp); + j++; } + assert(j == nslots - add_dict - add_weak); + nslots = j; Py_DECREF(slots); slots = newslots; - } - if (slots != NULL) { /* See if *this* class defines __getstate__ */ - PyObject *getstate = PyDict_GetItemString(dict, - "__getstate__"); - if (getstate == NULL) { + if (PyDict_GetItemString(dict, "__getstate__") == NULL) { /* If not, provide a bozo that raises TypeError */ if (bozo_obj == NULL) { bozo_obj = PyCFunction_New(&bozo_ml, NULL); - if (bozo_obj == NULL) { - /* XXX decref various things */ - return NULL; - } + if (bozo_obj == NULL) + goto bad_slots; } if (PyDict_SetItemString(dict, "__getstate__", - bozo_obj) < 0) { - /* XXX decref various things */ - return NULL; + bozo_obj) < 0) + { + Py_DECREF(bozo_obj); + goto bad_slots; + } + } + + /* Secondary bases may provide weakrefs or dict */ + if (nbases > 1 && + ((may_add_dict && !add_dict) || + (may_add_weak && !add_weak))) { + for (i = 0; i < nbases; i++) { + tmp = PyTuple_GET_ITEM(bases, i); + if (tmp == (PyObject *)base) + continue; /* Skip primary base */ + if (PyClass_Check(tmp)) { + /* Classic base class provides both */ + if (may_add_dict && !add_dict) + add_dict++; + if (may_add_weak && !add_weak) + add_weak++; + break; + } + assert(PyType_Check(tmp)); + tmptype = (PyTypeObject *)tmp; + if (may_add_dict && !add_dict && + tmptype->tp_dictoffset != 0) + add_dict++; + if (may_add_weak && !add_weak && + tmptype->tp_weaklistoffset != 0) + add_weak++; + if (may_add_dict && !add_dict) + continue; + if (may_add_weak && !add_weak) + continue; + /* Nothing more to check */ + break; } } - } - if (slots == NULL && base->tp_dictoffset == 0 && - (base->tp_setattro == PyObject_GenericSetAttr || - base->tp_setattro == NULL)) { - add_dict++; - } - if (slots == NULL && base->tp_weaklistoffset == 0 && - base->tp_itemsize == 0) { - nslots++; - add_weak++; } /* XXX From here until type is safely allocated, @@ -1151,8 +1248,10 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) /* Allocate the type object */ type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots); - if (type == NULL) + if (type == NULL) { + Py_XDECREF(slots); return NULL; + } /* Keep name and slots alive in the extended type object */ et = (etype *)type; @@ -1245,6 +1344,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) mp->offset = slotoffset; if (base->tp_weaklistoffset == 0 && strcmp(mp->name, "__weakref__") == 0) { + add_weak++; mp->type = T_OBJECT; mp->flags = READONLY; type->tp_weaklistoffset = slotoffset; @@ -1252,30 +1352,22 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) slotoffset += sizeof(PyObject *); } } - else { - if (add_dict) { - if (base->tp_itemsize) - type->tp_dictoffset = - -(long)sizeof(PyObject *); - else - type->tp_dictoffset = slotoffset; - slotoffset += sizeof(PyObject *); - type->tp_getset = subtype_getsets; - } - if (add_weak) { - assert(!base->tp_itemsize); - type->tp_weaklistoffset = slotoffset; - mp->name = "__weakref__"; - mp->type = T_OBJECT; - mp->offset = slotoffset; - mp->flags = READONLY; - mp++; - slotoffset += sizeof(PyObject *); - } + if (add_dict) { + if (base->tp_itemsize) + type->tp_dictoffset = -(long)sizeof(PyObject *); + else + type->tp_dictoffset = slotoffset; + slotoffset += sizeof(PyObject *); + } + if (add_weak) { + assert(!base->tp_itemsize); + type->tp_weaklistoffset = slotoffset; + slotoffset += sizeof(PyObject *); } type->tp_basicsize = slotoffset; type->tp_itemsize = base->tp_itemsize; type->tp_members = et->members; + type->tp_getset = subtype_getsets; /* Special case some slots */ if (type->tp_dictoffset != 0 || nslots > 0) { -- cgit v0.12