Index: Lib/test/test_exceptions.py =================================================================== --- Lib/test/test_exceptions.py (revision 56954) +++ Lib/test/test_exceptions.py (working copy) @@ -9,6 +9,27 @@ guard_warnings_filter) from test.test_pep352 import ignore_message_warning +class NaiveException(Exception): + def __init__(self, x): + self.x = x + +class SomewhatNaiveException(Exception): + def __init__(self, x): + self.x = x + Exception.__init__(self) + +class NotNaiveException(Exception): + def __init__(self, *args): + Exception.__init__(self, *args) + self.message = "changed" + self.args = ("also", "changed") + +class SlotedNaiveException(Exception): + __slots__ = ("x",) + def __init__(self, x): + self.x = x + + # XXX This is not really enough, each *operation* should be tested! class ExceptionTests(unittest.TestCase): @@ -263,6 +284,14 @@ {'message' : '', 'args' : (u'\u3042', 0, 1, 'ouch'), 'object' : u'\u3042', 'reason' : 'ouch', 'start' : 0, 'end' : 1}), + (NaiveException, ('foo',), + {'message': '', 'args': (), 'x': 'foo'}), + (SomewhatNaiveException, ('foo',), + {'message': '', 'args': (), 'x': 'foo'}), + (NotNaiveException, ('foo',), + {'message': 'changed', 'args': ('also', 'changed')}), + (SlotedNaiveException, ('foo',), + {'message': '', 'args': (), 'x': 'foo'}), ] try: exceptionList.append( @@ -283,7 +312,8 @@ if type(e) is not exc: raise # Verify module name - self.assertEquals(type(e).__module__, 'exceptions') + if not type(e).__name__.endswith('NaiveException'): + self.assertEquals(type(e).__module__, 'exceptions') # Verify no ref leaks in Exc_str() s = str(e) for checkArgName in expected: Index: Objects/exceptions.c =================================================================== --- Objects/exceptions.c (revision 56954) +++ Objects/exceptions.c (working copy) @@ -143,25 +143,138 @@ return repr; } -/* Pickling support */ +/* + * Pickling support. We use the __getstate__() and __setstate__() hooks + * for exception pickling. The __reduce_ex__() method is needed only to + * call object.__reduce_ex__() with protocol == 2, because other protocols + * complain about C types without custom __reduce__() methods. + */ static PyObject * -BaseException_reduce(PyBaseExceptionObject *self) +BaseException_reduce_ex(PyObject *self, PyObject *proto) { - if (self->args && self->dict) - return PyTuple_Pack(3, Py_Type(self), self->args, self->dict); - else - return PyTuple_Pack(2, Py_Type(self), self->args); + if (!PyIndex_Check(proto)) { + PyErr_Format(PyExc_TypeError, "BaseException.__reduce_ex__() requires " + "an integer argument, got '%s' object instead", + proto->ob_type->tp_name); + return NULL; + } + return PyObject_CallMethod((PyObject *)&PyBaseObject_Type, + "__reduce_ex__", "Oi", self, 2); } +/* Helpers for BaseException_getstate(). */ +static int +is_special_attribute(const char *name) +{ + return (strcmp(name, "__dict__") == 0 || + strcmp(name, "__weakref__") == 0 || + strcmp(name, "message") == 0); +} + /* - * Needed for backward compatibility, since exceptions used to store - * all their attributes in the __dict__. Code is taken from cPickle's - * load_build function. + * Store attribute named 'name' with value 'value' in the 'state' + * dictionary. Return 0 on success, -1 on failure. Also steals + * a reference to the 'value' and checks for attribute errors that + * might have happened during 'value' retrieval. */ +static int +store_attribute(PyObject *state, const char *name, PyObject *value) +{ + int res; + + if (value == NULL) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + return 0; + } + return -1; + } + res = PyDict_SetItemString(state, name, value); + Py_DECREF(value); + return res; +} + +/* + * __getstate__() hook. Because of backwards compatibility we can't + * use the __getnewargs__() or __reduce__() hooks for exception + * pickling, and __getstate__() really has to store all attributes + * in the state dictionary. We achieve this by copying exception's + * __dict__ and updating it with the values of tp_members and + * tp_getset descriptors of all types in exception's mro. + */ static PyObject * +BaseException_getstate(PyObject *self, PyObject *unused) +{ + Py_ssize_t i, n; + PyObject *state, *dict, *mro, *message; + + state = PyDict_New(); + if (state == NULL) + return NULL; + + dict = ((PyBaseExceptionObject *)self)->dict; + if (dict != NULL) { + if (PyDict_Update(state, dict) == -1) + goto error; + } + /* message has to be special cased, because it issues a warning. + See also is_special_attribute(). */ + message = ((PyBaseExceptionObject *)self)->message; + if (message != NULL) { + if (PyDict_SetItemString(state, "message", message) == -1) + goto error; + } + mro = self->ob_type->tp_mro; + if (mro == NULL) { + PyErr_Format(PyExc_SystemError, "'%s' doesn't have a __mro__ tuple", + self->ob_type->tp_name); + goto error; + } + n = PyTuple_GET_SIZE(mro); + for (i = 0; i < n; i++) { + PyObject *base = PyTuple_GET_ITEM(mro, i); + if (PyType_Check(base) && + PyObject_IsSubclass(base, PyExc_BaseException)) { + PyObject *value; + PyMemberDef *member = ((PyTypeObject *)base)->tp_members; + PyGetSetDef *getset = ((PyTypeObject *)base)->tp_getset; + if (member != NULL) { + for (; member->name != NULL; member++) { + if (!is_special_attribute(member->name)) { + value = PyMember_GetOne((char *)self, member); + if (store_attribute(state, member->name, value) == -1) + goto error; + } + } + } + if (getset != NULL) { + for (; getset->name != NULL; getset++) { + if (getset->get != NULL && + !is_special_attribute(getset->name)) { + value = getset->get(self, getset->closure); + if (store_attribute(state, getset->name, value) == -1) + goto error; + } + } + } + } + } + return state; + +error: + Py_DECREF(state); + return NULL; +} + +/* + * We have to use setattr() for attributes in the state, since some of + * the attributes aren't stored in the __dict__. Code is taken from + * cPickle's load_build() function. + */ +static PyObject * BaseException_setstate(PyObject *self, PyObject *state) { - PyObject *d_key, *d_value; + PyObject *d_key, *d_value, *message; Py_ssize_t i = 0; if (state != Py_None) { @@ -169,6 +282,16 @@ PyErr_SetString(PyExc_TypeError, "state is not a dictionary"); return NULL; } + /* message has to be special cased, because it issues a warning. */ + message = PyDict_GetItemString(state, "message"); + if (message != NULL) { + PyObject *tmp = ((PyBaseExceptionObject *)self)->message; + Py_INCREF(message); + ((PyBaseExceptionObject *)self)->message = message; + Py_DECREF(tmp); + if (PyDict_DelItemString(state, "message") == -1) + return NULL; + } while (PyDict_Next(state, &i, &d_key, &d_value)) { if (PyObject_SetAttr(self, d_key, d_value) < 0) return NULL; @@ -179,8 +302,12 @@ static PyMethodDef BaseException_methods[] = { - {"__reduce__", (PyCFunction)BaseException_reduce, METH_NOARGS }, - {"__setstate__", (PyCFunction)BaseException_setstate, METH_O }, + {"__reduce_ex__", (PyCFunction)BaseException_reduce_ex, + METH_O, "pickle helper"}, + {"__getstate__", (PyCFunction)BaseException_getstate, + METH_NOARGS, "pickle helper"}, + {"__setstate__", (PyCFunction)BaseException_setstate, + METH_O, "pickle helper"}, {NULL, NULL, 0, NULL}, }; @@ -699,48 +826,9 @@ }; -static PyObject * -EnvironmentError_reduce(PyEnvironmentErrorObject *self) -{ - PyObject *args = self->args; - PyObject *res = NULL, *tmp; - - /* self->args is only the first two real arguments if there was a - * file name given to EnvironmentError. */ - if (PyTuple_GET_SIZE(args) == 2 && self->filename) { - args = PyTuple_New(3); - if (!args) return NULL; - - tmp = PyTuple_GET_ITEM(self->args, 0); - Py_INCREF(tmp); - PyTuple_SET_ITEM(args, 0, tmp); - - tmp = PyTuple_GET_ITEM(self->args, 1); - Py_INCREF(tmp); - PyTuple_SET_ITEM(args, 1, tmp); - - Py_INCREF(self->filename); - PyTuple_SET_ITEM(args, 2, self->filename); - } else - Py_INCREF(args); - - if (self->dict) - res = PyTuple_Pack(3, Py_Type(self), args, self->dict); - else - res = PyTuple_Pack(2, Py_Type(self), args); - Py_DECREF(args); - return res; -} - - -static PyMethodDef EnvironmentError_methods[] = { - {"__reduce__", (PyCFunction)EnvironmentError_reduce, METH_NOARGS}, - {NULL} -}; - ComplexExtendsException(PyExc_StandardError, EnvironmentError, EnvironmentError, EnvironmentError_dealloc, - EnvironmentError_methods, EnvironmentError_members, + 0, EnvironmentError_members, EnvironmentError_str, "Base class for I/O related errors.");