diff -r 27e02518993b Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Wed Oct 01 19:42:09 2014 +0200 +++ b/Lib/test/test_exceptions.py Thu Oct 02 12:07:28 2014 +0200 @@ -6,6 +6,7 @@ import unittest import pickle import weakref import errno +import abc from test.support import (TESTFN, captured_output, check_impl_detail, check_warnings, cpython_only, gc_collect, run_unittest, @@ -473,6 +474,60 @@ class ExceptionTests(unittest.TestCase): return -1 self.assertRaises(RuntimeError, g) + def testVirtualExceptionSubclass(self): + # Test that exceptions that register with abc are properly caught + # as a base of the original exception. See #12029. + class MyException(Exception, metaclass=abc.ABCMeta): + pass + + class MyOriginalException(Exception): + pass + + MyException.register(MyOriginalException) + try: + raise MyOriginalException() + except MyException: + pass + except: + self.fail('MyException not caught') + + def testExceptionSubclasscheck(self): + # Test that a misbehaved __subclasscheck__ doesn't cause havoc + + class RaisingMeta(type): + def __subclasscheck__(self, cls): + raise RuntimeError + + class RaisingCatchingMeta(type): + def __subclasscheck__(self, cls): + try: + raise RuntimeError + except RuntimeError: + pass + + def recursion(): + return recursion() + class RecursingMeta(type): + def __subclasscheck__(self, cls): + recursion() + + reclimit = sys.getrecursionlimit() + try: + sys.setrecursionlimit(1000) + for metaclass in [RaisingMeta, RaisingCatchingMeta, RecursingMeta]: + BadException = metaclass('BadException', (Exception,), {}) + try: + raise ValueError + # this handler should cause an exception that is ignored + except BadException: + self.fail('caught a bad exception but shouldn\'t') + except ValueError: + pass + else: + self.fail('nothing raised') + finally: + sys.setrecursionlimit(reclimit) + def test_str(self): # Make sure both instances and classes have a str representation. self.assertTrue(str(Exception)) diff -r 27e02518993b Python/errors.c --- a/Python/errors.c Wed Oct 01 19:42:09 2014 +0200 +++ b/Python/errors.c Thu Oct 02 12:07:28 2014 +0200 @@ -163,23 +163,20 @@ PyErr_Occurred(void) } -int -PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc) +static int +given_exception_matches_inner(PyObject *err, PyObject *exc, int allow_new_exception) { - if (err == NULL || exc == NULL) { - /* maybe caused by "import exceptions" that failed early on */ - return 0; - } if (PyTuple_Check(exc)) { Py_ssize_t i, n; + int res; n = PyTuple_Size(exc); for (i = 0; i < n; i++) { /* Test recursively */ - if (PyErr_GivenExceptionMatches( - err, PyTuple_GET_ITEM(exc, i))) - { - return 1; - } + res = given_exception_matches_inner(err, PyTuple_GET_ITEM(exc, i), + allow_new_exception); + if (res != 0) { + return res; + } } return 0; } @@ -188,18 +185,23 @@ PyErr_GivenExceptionMatches(PyObject *er err = PyExceptionInstance_Class(err); if (PyExceptionClass_Check(err) && PyExceptionClass_Check(exc)) { - int res = 0; PyObject *exception, *value, *tb; + int res; PyErr_Fetch(&exception, &value, &tb); - /* PyObject_IsSubclass() can recurse and therefore is - not safe (see test_bad_getattr in test.pickletester). */ - res = PyType_IsSubtype((PyTypeObject *)err, (PyTypeObject *)exc); - /* This function must not fail, so print the error here */ - if (res == -1) { - PyErr_WriteUnraisable(err); - res = 0; + /* This function may not change if an exception was set if + allow_new_exception is not true. + + If PyObject_IsSubclass raised (potentially a recursion error), + _and_ we had an exception set previously, keep the new exception + in all cases, else restore the original one (if any). */ + res = PyObject_IsSubclass(err, exc); + if (!(res < 0 && (exception || allow_new_exception))) { + PyErr_Restore(exception, value, tb); + } else { + Py_XDECREF(exception); + Py_XDECREF(value); + Py_XDECREF(tb); } - PyErr_Restore(exception, value, tb); return res; } @@ -208,6 +210,24 @@ PyErr_GivenExceptionMatches(PyObject *er int +PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc) +{ + int res; + if (err == NULL || exc == NULL) { + /* maybe caused by "import exceptions" that failed early on */ + return 0; + } + res = given_exception_matches_inner(err, exc, 0); + if (res < 0) { + /* treat "exception occurred while testing exceptions" as no match: + this exception should bubble up the chain */ + res = 0; + } + return res; +} + + +int PyErr_ExceptionMatches(PyObject *exc) { return PyErr_GivenExceptionMatches(PyErr_Occurred(), exc);