diff -r 304c61263ae6 Doc/c-api/exceptions.rst --- a/Doc/c-api/exceptions.rst Mon Jun 13 09:24:11 2016 +0300 +++ b/Doc/c-api/exceptions.rst Mon Jun 13 23:14:23 2016 +0300 @@ -578,6 +578,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 304c61263ae6 Doc/library/exceptions.rst --- a/Doc/library/exceptions.rst Mon Jun 13 09:24:11 2016 +0300 +++ b/Doc/library/exceptions.rst Mon Jun 13 23:14:23 2016 +0300 @@ -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 304c61263ae6 Lib/test/test_contextlib.py --- a/Lib/test/test_contextlib.py Mon Jun 13 09:24:11 2016 +0300 +++ b/Lib/test/test_contextlib.py Mon Jun 13 23:14:23 2016 +0300 @@ -795,6 +795,40 @@ class TestExitStack(unittest.TestCase): stack.push(cm) self.assertIs(stack._exit_callbacks[-1], cm) + def test_dont_reraise_RuntimeError(self): + """https://bugs.python.org/issue27122""" + class UniqueException(Exception): pass + + @contextmanager + def second(): + try: + yield 1 + except Exception as exc: + raise UniqueException("new exception") from exc + + @contextmanager + def first(): + try: + yield 1 + except Exception as exc: + raise exc + + # The RuntimeError should be caught by second()'s exception + # handler which chain raised a new UniqueException. + with self.assertRaises(UniqueException) as err_ctx: + with ExitStack() as es_ctx: + es_ctx.enter_context(second()) + es_ctx.enter_context(first()) + raise RuntimeError("please no infinite loop.") + + self.assertEqual(err_ctx.exception.args[0], "new exception") + self.assertEqual(err_ctx.exception.__context__.args[0], + "please no infinite loop.") + self.assertIs(err_ctx.exception.__context__, + err_ctx.exception.__cause__) + self.assertIsNone(err_ctx.exception.__cause__.__context__) + self.assertIsNone(err_ctx.exception.__cause__.__cause__) + class TestRedirectStream: diff -r 304c61263ae6 Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Mon Jun 13 09:24:11 2016 +0300 +++ b/Lib/test/test_exceptions.py Mon Jun 13 23:14:23 2016 +0300 @@ -629,6 +629,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 304c61263ae6 Objects/exceptions.c --- a/Objects/exceptions.c Mon Jun 13 09:24:11 2016 +0300 +++ b/Objects/exceptions.c Mon Jun 13 23:14:23 2016 +0300 @@ -341,6 +341,26 @@ PyException_GetContext(PyObject *self) { void PyException_SetContext(PyObject *self, PyObject *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 = + ((PyBaseExceptionObject *)self)->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; + } + Py_XSETREF(((PyBaseExceptionObject *)self)->context, context); }