diff --git a/Lib/test/test_pep3151.py b/Lib/test/test_pep3151.py index e327f423014..8af9e0c5f65 100644 --- a/Lib/test/test_pep3151.py +++ b/Lib/test/test_pep3151.py @@ -12,6 +12,23 @@ from test import support class SubOSError(OSError): pass +class SubOSErrorWithInit(OSError): + def __init__(self, message, bar): + self.bar = bar + super().__init__(message) + +class SubOSErrorWithNew(OSError): + def __new__(cls, message, baz): + self = super().__new__(cls, message) + self.baz = baz + return self + +class SubOSErrorCombinedInitFirst(SubOSErrorWithInit, SubOSErrorWithNew): + pass + +class SubOSErrorCombinedNewFirst(SubOSErrorWithNew, SubOSErrorWithInit): + pass + class HierarchyTest(unittest.TestCase): @@ -74,11 +91,6 @@ class HierarchyTest(unittest.TestCase): e = OSError(errcode, "Some message") self.assertIs(type(e), OSError) - def test_OSError_subclass_mapping(self): - # When constructing an OSError subclass, errno mapping isn't done - e = SubOSError(EEXIST, "Bad file descriptor") - self.assertIs(type(e), SubOSError) - def test_try_except(self): filename = "some_hopefully_non_existing_file" @@ -144,6 +156,44 @@ class AttributesTest(unittest.TestCase): # XXX VMSError not tested +class ExplicitSubclassingTest(unittest.TestCase): + + def test_errno_mapping(self): + # When constructing an OSError subclass, errno mapping isn't done + e = SubOSError(EEXIST, "Bad file descriptor") + self.assertIs(type(e), SubOSError) + + def test_init_overriden(self): + e = SubOSErrorWithInit("some message", "baz") + self.assertEqual(e.bar, "baz") + self.assertEqual(e.args, ("some message",)) + + def test_init_kwdargs(self): + e = SubOSErrorWithInit("some message", bar="baz") + self.assertEqual(e.bar, "baz") + self.assertEqual(e.args, ("some message",)) + + def test_new_overriden(self): + e = SubOSErrorWithNew("some message", "baz") + self.assertEqual(e.baz, "baz") + self.assertEqual(e.args, ("some message",)) + + def test_new_kwdargs(self): + e = SubOSErrorWithNew("some message", baz="baz") + self.assertEqual(e.baz, "baz") + self.assertEqual(e.args, ("some message",)) + + def test_init_new_overriden(self): + e = SubOSErrorCombinedInitFirst("some message", "baz") + self.assertEqual(e.bar, "baz") + self.assertEqual(e.baz, "baz") + self.assertEqual(e.args, ("some message",)) + e = SubOSErrorCombinedNewFirst("some message", "baz") + self.assertEqual(e.bar, "baz") + self.assertEqual(e.baz, "baz") + self.assertEqual(e.args, ("some message",)) + + def test_main(): support.run_unittest(__name__) diff --git a/Misc/NEWS b/Misc/NEWS index d2fbdfc3361..cd9b0614f54 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1? Core and Builtins ----------------- +- Fix OSError.__init__ and OSError.__new__ so that each of them can be + overriden and take additional arguments (followup to issue #12555). + - Fix the fix for issue #12149: it was incorrect, although it had the side effect of appearing to resolve the issue. Thanks to Mark Shannon for noticing. diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 37b78757736..331811500d8 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -58,7 +58,7 @@ BaseException_init(PyBaseExceptionObject *self, PyObject *args, PyObject *kwds) if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds)) return -1; - Py_DECREF(self->args); + Py_XDECREF(self->args); self->args = args; Py_INCREF(self->args); @@ -587,37 +587,34 @@ SimpleExtendsException(PyExc_Exception, ImportError, * when it was supplied. */ -static PyObject * -OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - PyOSErrorObject *self = NULL; - Py_ssize_t nargs; - - PyObject *myerrno = NULL, *strerror = NULL, *filename = NULL; - PyObject *subslice = NULL; +/* This function doesn't cleanup on error, the caller should */ +static int +oserror_parse_args(PyObject **p_args, + PyObject **myerrno, PyObject **strerror, + PyObject **filename #ifdef MS_WINDOWS - PyObject *winerror = NULL; - long winerrcode = 0; + , PyObject **winerror #endif + ) +{ + Py_ssize_t nargs; + PyObject *args = *p_args; - if (!_PyArg_NoKeywords(type->tp_name, kwds)) - return NULL; - Py_INCREF(args); nargs = PyTuple_GET_SIZE(args); #ifdef MS_WINDOWS if (nargs >= 2 && nargs <= 4) { if (!PyArg_UnpackTuple(args, "OSError", 2, 4, - &myerrno, &strerror, &filename, &winerror)) - goto error; - if (winerror && PyLong_Check(winerror)) { - long errcode; + myerrno, strerror, filename, winerror)) + return -1; + if (*winerror && PyLong_Check(*winerror)) { + long errcode, winerrcode; PyObject *newargs; Py_ssize_t i; - winerrcode = PyLong_AsLong(winerror); + winerrcode = PyLong_AsLong(*winerror); if (winerrcode == -1 && PyErr_Occurred()) - goto error; + return -1; /* Set errno to the corresponding POSIX errno (overriding first argument). Windows Socket error codes (>= 10000) have the same value as their POSIX counterparts. @@ -626,59 +623,55 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) errcode = winerror_to_errno(winerrcode); else errcode = winerrcode; - myerrno = PyLong_FromLong(errcode); - if (!myerrno) - goto error; + *myerrno = PyLong_FromLong(errcode); + if (!*myerrno) + return -1; newargs = PyTuple_New(nargs); if (!newargs) - goto error; - PyTuple_SET_ITEM(newargs, 0, myerrno); + return -1; + PyTuple_SET_ITEM(newargs, 0, *myerrno); for (i = 1; i < nargs; i++) { PyObject *val = PyTuple_GET_ITEM(args, i); Py_INCREF(val); PyTuple_SET_ITEM(newargs, i, val); } Py_DECREF(args); - args = newargs; + args = *p_args = newargs; } } #else if (nargs >= 2 && nargs <= 3) { if (!PyArg_UnpackTuple(args, "OSError", 2, 3, - &myerrno, &strerror, &filename)) - goto error; + myerrno, strerror, filename)) + return -1; } #endif - if (myerrno && PyLong_Check(myerrno) && - errnomap && (PyObject *) type == PyExc_OSError) { - PyObject *newtype; - newtype = PyDict_GetItem(errnomap, myerrno); - if (newtype) { - assert(PyType_Check(newtype)); - type = (PyTypeObject *) newtype; - } - else if (PyErr_Occurred()) - goto error; - } - self = (PyOSErrorObject *) type->tp_alloc(type, 0); - if (!self) - goto error; + return 0; +} - self->dict = NULL; - self->traceback = self->cause = self->context = NULL; - self->written = -1; +static int +oserror_init(PyOSErrorObject *self, PyObject **p_args, + PyObject *myerrno, PyObject *strerror, + PyObject *filename +#ifdef MS_WINDOWS + , PyObject *winerror +#endif + ) +{ + PyObject *args = *p_args; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); /* self->filename will remain Py_None otherwise */ if (filename && filename != Py_None) { - if ((PyObject *) type == PyExc_BlockingIOError && + if (Py_TYPE(self) == (PyTypeObject *) PyExc_BlockingIOError && PyNumber_Check(filename)) { /* BlockingIOError's 3rd argument can be the number of * characters written. */ self->written = PyNumber_AsSsize_t(filename, PyExc_ValueError); if (self->written == -1 && PyErr_Occurred()) - goto error; + return -1; } else { Py_INCREF(filename); @@ -687,20 +680,15 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (nargs >= 2 && nargs <= 3) { /* filename is removed from the args tuple (for compatibility purposes, see test_exceptions.py) */ - subslice = PyTuple_GetSlice(args, 0, 2); + PyObject *subslice = PyTuple_GetSlice(args, 0, 2); if (!subslice) - goto error; + return -1; Py_DECREF(args); /* replacing args */ - args = subslice; + *p_args = args = subslice; } } } - - /* Steals the reference to args */ - self->args = args; - args = NULL; - Py_XINCREF(myerrno); self->myerrno = myerrno; @@ -712,6 +700,90 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->winerror = winerror; #endif + /* Steals the reference to args */ + self->args = args; + args = NULL; + + return 0; +} + +static PyObject * +OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds); +static int +OSError_init(PyOSErrorObject *self, PyObject *args, PyObject *kwds); + +static int +oserror_use_init(PyTypeObject *type) +{ + /* When __init__ is defined in a OSError subclass, we want any + extraneous argument to __new__ to be ignored. The only reasonable + solution, given __new__ takes a variable number of arguments, + is to defer arg parsing and initialization to __init__. + + But when __new__ is overriden as well, it should call our __new__ + with the right arguments. + + (see http://bugs.python.org/issue12555#msg148829 ) + */ + if (type->tp_init != (initproc) OSError_init && + type->tp_new == (newfunc) OSError_new) { + assert((PyObject *) type != PyExc_OSError); + return 1; + } + return 0; +} + +static PyObject * +OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyOSErrorObject *self = NULL; + PyObject *myerrno = NULL, *strerror = NULL, *filename = NULL; +#ifdef MS_WINDOWS + PyObject *winerror = NULL; +#endif + + if (!oserror_use_init(type)) { + if (!_PyArg_NoKeywords(type->tp_name, kwds)) + return NULL; + + Py_INCREF(args); + if (oserror_parse_args(&args, &myerrno, &strerror, &filename +#ifdef MS_WINDOWS + , &winerror +#endif + )) + goto error; + + if (myerrno && PyLong_Check(myerrno) && + errnomap && (PyObject *) type == PyExc_OSError) { + PyObject *newtype; + newtype = PyDict_GetItem(errnomap, myerrno); + if (newtype) { + assert(PyType_Check(newtype)); + type = (PyTypeObject *) newtype; + } + else if (PyErr_Occurred()) + goto error; + } + } + + self = (PyOSErrorObject *) type->tp_alloc(type, 0); + if (!self) + goto error; + + self->dict = NULL; + self->traceback = self->cause = self->context = NULL; + self->written = -1; + + if (!oserror_use_init(type)) { + if (oserror_init(self, &args, myerrno, strerror, filename +#ifdef MS_WINDOWS + , winerror +#endif + )) + goto error; + } + return (PyObject *) self; error: @@ -721,10 +793,40 @@ error: } static int -OSError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds) +OSError_init(PyOSErrorObject *self, PyObject *args, PyObject *kwds) { - /* Everything already done in OSError_new */ + PyObject *myerrno = NULL, *strerror = NULL, *filename = NULL; +#ifdef MS_WINDOWS + PyObject *winerror = NULL; +#endif + + if (!oserror_use_init(Py_TYPE(self))) + /* Everything already done in OSError_new */ + return 0; + + if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds)) + return -1; + + Py_INCREF(args); + if (oserror_parse_args(&args, &myerrno, &strerror, &filename +#ifdef MS_WINDOWS + , &winerror +#endif + )) + goto error; + + if (oserror_init(self, &args, myerrno, strerror, filename +#ifdef MS_WINDOWS + , winerror +#endif + )) + goto error; + return 0; + +error: + Py_XDECREF(args); + return -1; } static int