From ac91341333d27bf39dd8b8c1c3164b5bdc19f03b Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sun, 3 Jul 2011 16:25:11 -0500 Subject: [PATCH] never retain a generator's caller's exception state on the generator after a yield/return This requires some trickery to properly save the exception state if the generator creates its own exception state. --- Lib/test/test_exceptions.py | 12 +++++++++++ Misc/NEWS | 3 +++ Python/ceval.c | 40 +++++++++++++++++++++++++++++++++---- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index ad9a19e6fd4..46ddc826a23 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -581,6 +581,18 @@ class ExceptionTests(unittest.TestCase): pass self.assertEqual(sys.exc_info(), (None, None, None)) + def test_generator_doesnt_retain_old_exc(self): + def g(): + self.assertIsInstance(sys.exc_info()[1], RuntimeError) + yield + self.assertEqual(sys.exc_info(), (None, None, None)) + it = g() + try: + raise RuntimeError + except RuntimeError: + next(it) + self.assertRaises(StopIteration, next, it) + def test_generator_finalizing_and_exc_info(self): # See #7173 def simple_gen(): diff --git a/Misc/NEWS b/Misc/NEWS index d763e0fc296..250acc2913e 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.2.2? Core and Builtins ----------------- +- When a generator yields, do not retain the caller's exception state on the + generator. + - Issue #12475: Prevent generators from leaking their exception state into the caller's frame as they return for the last time. diff --git a/Python/ceval.c b/Python/ceval.c index c0f2874a8f2..5c3bb832cda 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1145,6 +1145,23 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) f->f_exc_traceback = tmp; \ } +#define RESTORE_AND_CLEAR_EXC_STATE() \ + { \ + PyObject *type, *value, *tb; \ + type = tstate->exc_type; \ + value = tstate->exc_value; \ + tb = tstate->exc_traceback; \ + tstate->exc_type = f->f_exc_type; \ + tstate->exc_value = f->f_exc_value; \ + tstate->exc_traceback = f->f_exc_traceback; \ + f->f_exc_type = NULL; \ + f->f_exc_value = NULL; \ + f->f_exc_traceback = NULL; \ + Py_XDECREF(type); \ + Py_XDECREF(value); \ + Py_XDECREF(tb); \ + } + /* Start of code */ if (f == NULL) @@ -3017,10 +3034,25 @@ fast_block_end: retval = NULL; fast_yield: - if (co->co_flags & CO_GENERATOR && (why == WHY_YIELD || why == WHY_RETURN)) - /* Put aside the current exception state and restore that of the - calling frame. */ - SWAP_EXC_STATE(); + if (co->co_flags & CO_GENERATOR && (why == WHY_YIELD || why == WHY_RETURN)) { + /* The purpose of this block is to put aside the generator's exception + state and restore that of the calling frame. If the current + exception state is from the caller, we clear the exception values + on the generator frame, so they are not swapped back in latter. The + origin of the current exception state is determined by checking for + except handler blocks, which we must be in iff a new exception + state came into existence in this frame. (An uncaught exception + would have why == WHY_EXCEPTION, and we wouldn't be here). */ + int i; + for (i = 0; i < f->f_iblock; i++) + if (f->f_blockstack[i].b_type == EXCEPT_HANDLER) + break; + if (i == f->f_iblock) + /* We did not create this exception. */ + RESTORE_AND_CLEAR_EXC_STATE() + else + SWAP_EXC_STATE() + } if (tstate->use_tracing) { if (tstate->c_tracefunc) {