]> granicus.if.org Git - python/commitdiff
never retain a generator's caller's exception state on the generator after a yield...
authorBenjamin Peterson <benjamin@python.org>
Sun, 3 Jul 2011 21:25:11 +0000 (16:25 -0500)
committerBenjamin Peterson <benjamin@python.org>
Sun, 3 Jul 2011 21:25:11 +0000 (16:25 -0500)
This requires some trickery to properly save the exception state if the
generator creates its own exception state.

Lib/test/test_exceptions.py
Misc/NEWS
Python/ceval.c

index ad9a19e6fd4102e9b9bbf2027ef6fab185387fdd..46ddc826a2329e74740627940aee9962de3677f2 100644 (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():
index d763e0fc2967c01a47633fdcd169f372a93c812f..250acc2913e9e935ab0eb1b84f95a81d1b3dbe73 100644 (file)
--- 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.
 
index c0f2874a8f2398a26df2785ed8ee3dd834257f72..5c3bb832cda50bb473513a453afa603b4fdef656 100644 (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) {