diff -r b15c5a66213f Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Thu Oct 02 12:39:02 2014 +0200 +++ b/Lib/test/test_exceptions.py Thu Oct 02 13:28:26 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 b15c5a66213f Python/errors.c --- a/Python/errors.c Thu Oct 02 12:39:02 2014 +0200 +++ b/Python/errors.c Thu Oct 02 13:28:26 2014 +0200 @@ -163,47 +163,54 @@ PyErr_Occurred(void) } +static int +given_exception_matches_inner(PyObject *err, PyObject *exc, int allow_new_exception) +{ + int res = 0; + PyObject *exception, *value, *tb; + /* PyObject_IsSubclass must not be called with an exception set. */ + PyErr_Fetch(&exception, &value, &tb); + /* PyErr_GivenExceptionMatches must be guaranteed not to change _if_ + an exception is set in the thread state. However, it may change + the exception. + + 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); + } + return res; +} + + 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; } - if (PyTuple_Check(exc)) { - Py_ssize_t i, n; - n = PyTuple_Size(exc); - for (i = 0; i < n; i++) { - /* Test recursively */ - if (PyErr_GivenExceptionMatches( - err, PyTuple_GET_ITEM(exc, i))) - { - return 1; - } - } - return 0; - } /* err might be an instance, so check its class. */ if (PyExceptionInstance_Check(err)) err = PyExceptionInstance_Class(err); + res = given_exception_matches_inner(err, exc, 0); + if (res < 0) { + /* Treat "exception raised while matching exceptions" as no match. - if (PyExceptionClass_Check(err) && PyExceptionClass_Check(exc)) { - int res = 0; - PyObject *exception, *value, *tb; - 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; - } - PyErr_Restore(exception, value, tb); - return res; + If by chance the newly raised exception did match "exc", it would + still not be logical to return true since the exception was not + raised by whatever triggered the exception check. */ + res = 0; } - - return err == exc; + return res; }