diff -r 1ff29b57628e Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Wed Dec 02 14:37:35 2015 +0100 +++ b/Lib/test/test_exceptions.py Wed Dec 02 14:33:59 2015 -0500 @@ -625,6 +625,32 @@ obj = wr() self.assertTrue(obj is None, "%s" % obj) + def testExceptionChainedContext(self): + class Err1(Exception): pass + class Err2(Exception): pass + + err1 = Err1() + with self.assertRaisesRegex(TypeError, 'cycle in exception context'): + err1.__context__ = err1 + + err2 = Err2() + err2.__context__ = err1 + with self.assertRaisesRegex(TypeError, 'cycle in exception context'): + err1.__context__ = err2 + + def testExceptionChainedCause(self): + class Err1(Exception): pass + class Err2(Exception): pass + + err1 = Err1() + with self.assertRaisesRegex(TypeError, 'cycle in exception cause'): + err1.__cause__ = err1 + + err2 = Err2() + err2.__cause__ = err1 + with self.assertRaisesRegex(TypeError, 'cycle in exception cause'): + err1.__cause__ = err2 + def test_exception_target_in_nested_scope(self): # issue 4617: This used to raise a SyntaxError # "can not delete variable 'e' referenced in nested scope" diff -r 1ff29b57628e Objects/exceptions.c --- a/Objects/exceptions.c Wed Dec 02 14:37:35 2015 +0100 +++ b/Objects/exceptions.c Wed Dec 02 14:33:59 2015 -0500 @@ -265,6 +265,10 @@ Py_INCREF(arg); } PyException_SetContext(self, arg); + if (arg != NULL && ((PyBaseExceptionObject *)self)->context == NULL) { + PyErr_SetString(PyExc_TypeError, "cycle in exception context chain"); + return -1; + } return 0; } @@ -292,6 +296,10 @@ Py_INCREF(arg); } PyException_SetCause(self, arg); + if (arg != NULL && ((PyBaseExceptionObject *)self)->cause == NULL) { + PyErr_SetString(PyExc_TypeError, "cycle in exception cause chain"); + return -1; + } return 0; } @@ -331,6 +339,14 @@ /* Steals a reference to cause */ void PyException_SetCause(PyObject *self, PyObject *cause) { + PyObject *exc = cause; + while (exc != NULL) { + if (exc == self) { + cause = NULL; + break; + } + exc = ((PyBaseExceptionObject *)exc)->cause; + } PyObject *old_cause = ((PyBaseExceptionObject *)self)->cause; ((PyBaseExceptionObject *)self)->cause = cause; ((PyBaseExceptionObject *)self)->suppress_context = 1; @@ -346,7 +362,16 @@ /* Steals a reference to context */ void -PyException_SetContext(PyObject *self, PyObject *context) { +PyException_SetContext(PyObject *self, PyObject *context) +{ + PyObject *exc = context; + while (exc != NULL) { + if (exc == self) { + context = NULL; + break; + } + exc = ((PyBaseExceptionObject *)exc)->context; + } PyObject *old_context = ((PyBaseExceptionObject *)self)->context; ((PyBaseExceptionObject *)self)->context = context; Py_XDECREF(old_context);