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.
This commit is contained in:
Benjamin Peterson 2011-07-03 16:25:11 -05:00
parent 9cf960c94f
commit ac91341333
3 changed files with 51 additions and 4 deletions

View File

@ -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():

View File

@ -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.

View File

@ -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) {