diff -r 505593490f4c Doc/c-api/exceptions.rst --- a/Doc/c-api/exceptions.rst Fri Dec 18 13:23:33 2015 +0200 +++ b/Doc/c-api/exceptions.rst Fri Dec 18 18:03:41 2015 +0200 @@ -570,6 +570,10 @@ Exception Objects it. There is no type check to make sure that *ctx* is an exception instance. This steals a reference to *ctx*. + .. versionchanged:: 3.5.2 + If *ctx* is *ex*, the function has no any effect. If *ex* is in the + context chain started from *ctx*, it is moved to the start of the chain. + .. c:function:: PyObject* PyException_GetCause(PyObject *ex) diff -r 505593490f4c Doc/library/exceptions.rst --- a/Doc/library/exceptions.rst Fri Dec 18 13:23:33 2015 +0200 +++ b/Doc/library/exceptions.rst Fri Dec 18 18:03:41 2015 +0200 @@ -66,6 +66,12 @@ In either case, the exception itself is exceptions so that the final line of the traceback always shows the last exception that was raised. +.. versionchanged:: 3.5.2 + + Setting :attr:`__context__` to self has no any effect. If set + ``__context__`` to the chain contained original exception, it is moved to + the start of the context chain. + Base classes ------------ diff -r 505593490f4c Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Fri Dec 18 13:23:33 2015 +0200 +++ b/Lib/test/test_exceptions.py Fri Dec 18 18:03:41 2015 +0200 @@ -625,6 +625,26 @@ class ExceptionTests(unittest.TestCase): obj = wr() self.assertTrue(obj is None, "%s" % obj) + def test_context_loop(self): + exc1 = Exception(1) + exc1.__context__ = exc1 + self.assertIsNone(exc1.__context__) + + exc2 = Exception(2) + exc2.__context__ = exc1 + self.assertIs(exc2.__context__, exc1) + exc1.__context__ = exc2 + self.assertIs(exc1.__context__, exc2) + self.assertIsNone(exc2.__context__) + + exc3 = Exception(3) + exc3.__context__ = exc1 + self.assertIs(exc3.__context__, exc1) + exc1.__context__ = exc3 + self.assertIs(exc1.__context__, exc3) + self.assertIs(exc3.__context__, exc2) + self.assertIsNone(exc2.__context__) + 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 505593490f4c Objects/exceptions.c --- a/Objects/exceptions.c Fri Dec 18 13:23:33 2015 +0200 +++ b/Objects/exceptions.c Fri Dec 18 18:03:41 2015 +0200 @@ -346,8 +346,28 @@ PyException_GetContext(PyObject *self) { /* Steals a reference to context */ void -PyException_SetContext(PyObject *self, PyObject *context) { +PyException_SetContext(PyObject *self, PyObject *context) +{ PyObject *old_context = ((PyBaseExceptionObject *)self)->context; + PyObject *exc = context; + PyObject *prev_exc = NULL; + + while (exc != NULL) { + if (exc == self) { + if (prev_exc != NULL) { + /* Move self to the start of the chain. */ + ((PyBaseExceptionObject *)prev_exc)->context = old_context; + ((PyBaseExceptionObject *)self)->context = context; + } + assert(Py_REFCNT(self) > 1); + Py_DECREF(self); + return; + } + assert(PyExceptionInstance_Check(exc)); + prev_exc = exc; + exc = ((PyBaseExceptionObject *)exc)->context; + } + ((PyBaseExceptionObject *)self)->context = context; Py_XDECREF(old_context); }