]> granicus.if.org Git - python/commitdiff
#3021: Antoine Pitrou's Lexical exception handlers
authorBenjamin Peterson <benjamin@python.org>
Wed, 11 Jun 2008 15:59:43 +0000 (15:59 +0000)
committerBenjamin Peterson <benjamin@python.org>
Wed, 11 Jun 2008 15:59:43 +0000 (15:59 +0000)
17 files changed:
Doc/library/dis.rst
Doc/library/inspect.rst
Doc/library/sys.rst
Doc/reference/datamodel.rst
Include/frameobject.h
Include/opcode.h
Lib/doctest.py
Lib/inspect.py
Lib/opcode.py
Lib/test/test_exceptions.py
Lib/test/test_raise.py
Misc/NEWS
Misc/cheatsheet
Objects/frameobject.c
Python/ceval.c
Python/compile.c
Python/import.c

index 125a80f9b0259781c86bfc6d8cd814e43c577433..6b2de49616ce62743595280dda1cf54042d0bbd4 100644 (file)
@@ -397,6 +397,14 @@ Miscellaneous opcodes.
    denoting nested loops, try statements, and such.
 
 
+.. opcode:: POP_EXCEPT ()
+
+   Removes one block from the block stack. The popped block must be an exception
+   handler block, as implicitly created when entering an except handler.
+   In addition to popping extraneous values from the frame stack, the
+   last three popped values are used to restore the exception state.
+
+
 .. opcode:: END_FINALLY ()
 
    Terminates a :keyword:`finally` clause.  The interpreter recalls whether the
@@ -412,24 +420,22 @@ Miscellaneous opcodes.
 
 .. opcode:: WITH_CLEANUP ()
 
-   Cleans up the stack when a :keyword:`with` statement block exits.  On top of
-   the stack are 1--3 values indicating how/why the finally clause was entered:
-
-   * TOP = ``None``
-   * (TOP, SECOND) = (``WHY_{RETURN,CONTINUE}``), retval
-   * TOP = ``WHY_*``; no retval below it
-   * (TOP, SECOND, THIRD) = exc_info()
+   Cleans up the stack when a :keyword:`with` statement block exits.  TOS is
+   the context manager's :meth:`__exit__` bound method. Below TOS are 1--3
+   values indicating how/why the finally clause was entered:
 
-   Under them is EXIT, the context manager's :meth:`__exit__` bound method.
+   * SECOND = ``None``
+   * (SECOND, THIRD) = (``WHY_{RETURN,CONTINUE}``), retval
+   * SECOND = ``WHY_*``; no retval below it
+   * (SECOND, THIRD, FOURTH) = exc_info()
 
-   In the last case, ``EXIT(TOP, SECOND, THIRD)`` is called, otherwise
-   ``EXIT(None, None, None)``.
+   In the last case, ``TOS(SECOND, THIRD, FOURTH)`` is called, otherwise
+   ``TOS(None, None, None)``.  In addition, TOS is removed from the stack.
 
-   EXIT is removed from the stack, leaving the values above it in the same
-   order. In addition, if the stack represents an exception, *and* the function
-   call returns a 'true' value, this information is "zapped", to prevent
-   ``END_FINALLY`` from re-raising the exception.  (But non-local gotos should
-   still be resumed.)
+   If the stack represents an exception, *and* the function call returns
+   a 'true' value, this information is "zapped" and replaced with a single
+   ``WHY_SILENCED`` to prevent ``END_FINALLY`` from re-raising the exception.
+   (But non-local gotos will still be resumed.)
 
    .. XXX explain the WHY stuff!
 
index a87651f4a250530ff720f03a4eac9ae673fb5462..98ecbcb1de45ba831c5c90749e84ba4ca996b803 100644 (file)
@@ -94,17 +94,6 @@ attributes:
 |           | f_code          | code object being         |
 |           |                 | executed in this frame    |
 +-----------+-----------------+---------------------------+
-|           | f_exc_traceback | traceback if raised in    |
-|           |                 | this frame, or ``None``   |
-+-----------+-----------------+---------------------------+
-|           | f_exc_type      | exception type if raised  |
-|           |                 | in this frame, or         |
-|           |                 | ``None``                  |
-+-----------+-----------------+---------------------------+
-|           | f_exc_value     | exception value if raised |
-|           |                 | in this frame, or         |
-|           |                 | ``None``                  |
-+-----------+-----------------+---------------------------+
 |           | f_globals       | global namespace seen by  |
 |           |                 | this frame                |
 +-----------+-----------------+---------------------------+
index b69de3f7f1cbb3d1c698c0009622e6f69f29c791..42c36a6fa3defc4c5afae3b1a2cad81f6669ab8a 100644 (file)
@@ -136,8 +136,8 @@ always available.
    frame is not handling an exception, the information is taken from the calling
    stack frame, or its caller, and so on until a stack frame is found that is
    handling an exception.  Here, "handling an exception" is defined as "executing
-   or having executed an except clause."  For any stack frame, only information
-   about the most recently handled exception is accessible.
+   an except clause."  For any stack frame, only information about the exception
+   being currently handled is accessible.
 
    .. index:: object: traceback
 
index 6562b00595f4309d9614619a75b91ded2561c549..4e24df73ab49eeaf42056b1101dcaecfff558b09 100644 (file)
@@ -875,19 +875,14 @@ Internal types
 
       .. index::
          single: f_trace (frame attribute)
-         single: f_exc_type (frame attribute)
-         single: f_exc_value (frame attribute)
-         single: f_exc_traceback (frame attribute)
          single: f_lineno (frame attribute)
 
       Special writable attributes: :attr:`f_trace`, if not ``None``, is a function
       called at the start of each source code line (this is used by the debugger);
-      :attr:`f_exc_type`, :attr:`f_exc_value`, :attr:`f_exc_traceback` represent the
-      last exception raised in the parent frame provided another exception was ever
-      raised in the current frame (in all other cases they are None); :attr:`f_lineno`
-      is the current line number of the frame --- writing to this from within a trace
-      function jumps to the given line (only for the bottom-most frame).  A debugger
-      can implement a Jump command (aka Set Next Statement) by writing to f_lineno.
+      :attr:`f_lineno` is the current line number of the frame --- writing to this
+      from within a trace function jumps to the given line (only for the bottom-most
+      frame).  A debugger can implement a Jump command (aka Set Next Statement)
+      by writing to f_lineno.
 
    Traceback objects
       .. index::
index d2afe8b6d5bf99d65a2ce9e7381dd2627cdf2b8d..65ebd2ad523e900feafa9044fe6d68f30874b99a 100644 (file)
@@ -27,13 +27,13 @@ typedef struct _frame {
     PyObject **f_stacktop;
     PyObject *f_trace;         /* Trace function */
 
-    /* If an exception is raised in this frame, the next three are used to
-     * record the exception info (if any) originally in the thread state.  See
-     * comments before set_exc_info() -- it's not obvious.
-     * Invariant:  if _type is NULL, then so are _value and _traceback.
-     * Desired invariant:  all three are NULL, or all three are non-NULL.  That
-     * one isn't currently true, but "should be".
-     */
+       /* In a generator, we need to be able to swap between the exception
+          state inside the generator and the exception state of the calling
+          frame (which shouldn't be impacted when the generator "yields"
+          from an except handler).
+          These three fields exist exactly for that, and are unused for
+          non-generator frames. See the SAVE_EXC_STATE and SWAP_EXC_STATE
+          macros in ceval.c for details of their use. */
     PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
 
     PyThreadState *f_tstate;
index 7bdf1c9ad825c4a8d1f055ce737a0c322e68bb63..6da9877098b2c14daaa5988963f6521ed42d6d00 100644 (file)
@@ -70,6 +70,7 @@ extern "C" {
 #define YIELD_VALUE    86
 #define POP_BLOCK      87
 #define END_FINALLY    88
+#define POP_EXCEPT     89
 
 #define HAVE_ARGUMENT  90      /* Opcodes from here have an argument: */
 
@@ -133,6 +134,13 @@ extern "C" {
 #define EXTENDED_ARG  143
 
 
+/* EXCEPT_HANDLER is a special, implicit block type which is created when
+   entering an except handler. It is not an opcode but we define it here
+   as we want it to be available to both frameobject.c and ceval.c, while
+   remaining private.*/
+#define EXCEPT_HANDLER 257
+
+
 enum cmp_op {PyCmp_LT=Py_LT, PyCmp_LE=Py_LE, PyCmp_EQ=Py_EQ, PyCmp_NE=Py_NE, PyCmp_GT=Py_GT, PyCmp_GE=Py_GE,
             PyCmp_IN, PyCmp_NOT_IN, PyCmp_IS, PyCmp_IS_NOT, PyCmp_EXC_MATCH, PyCmp_BAD};
 
index dad8333bbd4e842c6d3ae354e294b6ce0617d204..74be21ed7f3550e3f4cf45cdea07eed9889d4631 100644 (file)
@@ -1242,10 +1242,9 @@ class DocTestRunner:
 
             # The example raised an exception:  check if it was expected.
             else:
-                exc_info = sys.exc_info()
-                exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
+                exc_msg = traceback.format_exception_only(*exception[:2])[-1]
                 if not quiet:
-                    got += _exception_traceback(exc_info)
+                    got += _exception_traceback(exception)
 
                 # If `example.exc_msg` is None, then we weren't expecting
                 # an exception.
@@ -1275,7 +1274,7 @@ class DocTestRunner:
             elif outcome is BOOM:
                 if not quiet:
                     self.report_unexpected_exception(out, test, example,
-                                                     exc_info)
+                                                     exception)
                 failures += 1
             else:
                 assert False, ("unknown outcome", outcome)
index 5758abd3d89851a21179d69bb4c08715fc7b0de9..e89b5f0a3283d99af1a2271eea0d10dd72696768 100644 (file)
@@ -197,9 +197,6 @@ def isframe(object):
         f_back          next outer frame object (this frame's caller)
         f_builtins      built-in namespace seen by this frame
         f_code          code object being executed in this frame
-        f_exc_traceback traceback if raised in this frame, or None
-        f_exc_type      exception type if raised in this frame, or None
-        f_exc_value     exception value if raised in this frame, or None
         f_globals       global namespace seen by this frame
         f_lasti         index of last attempted instruction in bytecode
         f_lineno        current line number in Python source code
index eac0b63162b5523598ece0ec8aa406e027868db8..50e10ee61278ea18f5a02c7b330a7bddc0081bf2 100644 (file)
@@ -105,6 +105,7 @@ def_op('MAKE_BYTES', 85)
 def_op('YIELD_VALUE', 86)
 def_op('POP_BLOCK', 87)
 def_op('END_FINALLY', 88)
+def_op('POP_EXCEPT', 89)
 
 HAVE_ARGUMENT = 90              # Opcodes from here have an argument:
 
index 41b9413f419fef97fdf1063de1a86b598d6ac104..906855403e4fcc5cf5ce56f86f88cf68d957a746 100644 (file)
@@ -427,6 +427,7 @@ class ExceptionTests(unittest.TestCase):
             local_ref = obj
             raise MyException(obj)
 
+        # Qualified "except" with "as"
         obj = MyObj()
         wr = weakref.ref(obj)
         try:
@@ -437,6 +438,113 @@ class ExceptionTests(unittest.TestCase):
         obj = wr()
         self.failUnless(obj is None, "%s" % obj)
 
+        # Qualified "except" without "as"
+        obj = MyObj()
+        wr = weakref.ref(obj)
+        try:
+            inner_raising_func()
+        except MyException:
+            pass
+        obj = None
+        obj = wr()
+        self.failUnless(obj is None, "%s" % obj)
+
+        # Bare "except"
+        obj = MyObj()
+        wr = weakref.ref(obj)
+        try:
+            inner_raising_func()
+        except:
+            pass
+        obj = None
+        obj = wr()
+        self.failUnless(obj is None, "%s" % obj)
+
+        # "except" with premature block leave
+        obj = MyObj()
+        wr = weakref.ref(obj)
+        for i in [0]:
+            try:
+                inner_raising_func()
+            except:
+                break
+        obj = None
+        obj = wr()
+        self.failUnless(obj is None, "%s" % obj)
+
+        # "except" block raising another exception
+        obj = MyObj()
+        wr = weakref.ref(obj)
+        try:
+            try:
+                inner_raising_func()
+            except:
+                raise KeyError
+        except KeyError:
+            obj = None
+            obj = wr()
+            self.failUnless(obj is None, "%s" % obj)
+
+        # Some complicated construct
+        obj = MyObj()
+        wr = weakref.ref(obj)
+        try:
+            inner_raising_func()
+        except MyException:
+            try:
+                try:
+                    raise
+                finally:
+                    raise
+            except MyException:
+                pass
+        obj = None
+        obj = wr()
+        self.failUnless(obj is None, "%s" % obj)
+
+        # Inside an exception-silencing "with" block
+        class Context:
+            def __enter__(self):
+                return self
+            def __exit__ (self, exc_type, exc_value, exc_tb):
+                return True
+        obj = MyObj()
+        wr = weakref.ref(obj)
+        with Context():
+            inner_raising_func()
+        obj = None
+        obj = wr()
+        self.failUnless(obj is None, "%s" % obj)
+
+    def test_generator_leaking(self):
+        # Test that generator exception state doesn't leak into the calling
+        # frame
+        def yield_raise():
+            try:
+                raise KeyError("caught")
+            except KeyError:
+                yield sys.exc_info()[0]
+                yield sys.exc_info()[0]
+            yield sys.exc_info()[0]
+        g = yield_raise()
+        self.assertEquals(next(g), KeyError)
+        self.assertEquals(sys.exc_info()[0], None)
+        self.assertEquals(next(g), KeyError)
+        self.assertEquals(sys.exc_info()[0], None)
+        self.assertEquals(next(g), None)
+
+        # Same test, but inside an exception handler
+        try:
+            raise TypeError("foo")
+        except TypeError:
+            g = yield_raise()
+            self.assertEquals(next(g), KeyError)
+            self.assertEquals(sys.exc_info()[0], TypeError)
+            self.assertEquals(next(g), KeyError)
+            self.assertEquals(sys.exc_info()[0], TypeError)
+            self.assertEquals(next(g), TypeError)
+            del g
+            self.assertEquals(sys.exc_info()[0], TypeError)
 
 def test_main():
     run_unittest(ExceptionTests)
index 89e219048b2ed3824b94aeb0fa37e9097ab389a5..5f0070e25d0cbd718aa32d32aad0cf3681e4a5dd 100644 (file)
@@ -16,6 +16,13 @@ def get_tb():
         return sys.exc_info()[2]
 
 
+class Context:
+    def __enter__(self):
+        return self
+    def __exit__(self, exc_type, exc_value, exc_tb):
+        return True
+
+
 class TestRaise(unittest.TestCase):
     def test_invalid_reraise(self):
         try:
@@ -37,6 +44,71 @@ class TestRaise(unittest.TestCase):
         else:
             self.fail("No exception raised")
 
+    def test_except_reraise(self):
+        def reraise():
+            try:
+                raise TypeError("foo")
+            except:
+                try:
+                    raise KeyError("caught")
+                except KeyError:
+                    pass
+                raise
+        self.assertRaises(TypeError, reraise)
+
+    def test_finally_reraise(self):
+        def reraise():
+            try:
+                raise TypeError("foo")
+            except:
+                try:
+                    raise KeyError("caught")
+                finally:
+                    raise
+        self.assertRaises(KeyError, reraise)
+
+    def test_nested_reraise(self):
+        def nested_reraise():
+            raise
+        def reraise():
+            try:
+                raise TypeError("foo")
+            except:
+                nested_reraise()
+        self.assertRaises(TypeError, reraise)
+
+    def test_with_reraise1(self):
+        def reraise():
+            try:
+                raise TypeError("foo")
+            except:
+                with Context():
+                    pass
+                raise
+        self.assertRaises(TypeError, reraise)
+
+    def test_with_reraise2(self):
+        def reraise():
+            try:
+                raise TypeError("foo")
+            except:
+                with Context():
+                    raise KeyError("caught")
+                raise
+        self.assertRaises(TypeError, reraise)
+
+    def test_yield_reraise(self):
+        def reraise():
+            try:
+                raise TypeError("foo")
+            except:
+                yield 1
+                raise
+        g = reraise()
+        next(g)
+        self.assertRaises(TypeError, lambda: next(g))
+        self.assertRaises(StopIteration, lambda: next(g))
+
     def test_erroneous_exception(self):
         class MyException(Exception):
             def __init__(self):
@@ -158,6 +230,5 @@ class TestRemovedFunctionality(unittest.TestCase):
 def test_main():
     support.run_unittest(__name__)
 
-
 if __name__ == "__main__":
     unittest.main()
index 410b668f1c0f198f80b02f62ceedc06616e904b1..4c8d368ea4294ab4fb0a3767a5a1617dc9d7ad1d 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -49,6 +49,10 @@ Core and Builtins
   Exception (KeyboardInterrupt, and SystemExit) propagate instead of
   ignoring them.
 
+- #3021 Exception reraising sematics have been significantly improved.  However,
+  f_exc_type, f_exc_value, and f_exc_traceback cannot be accessed from Python
+  code anymore.
+
 Extension Modules
 -----------------
 
index 50ffc2285f72575585734a03c145ce5e4ec0a956..0f18ac3d7b453d46998bd3fdeb726eeaec1f7fb0 100644 (file)
@@ -1262,9 +1262,6 @@ Special informative state attributes for some types:
         f_lineno (int, R/O): current line number
         f_lasti (int, R/O): precise instruction (index into bytecode)
         f_trace (function/None, R/W): debug hook called at start of each source line
-        f_exc_type (Type/None, R/W): Most recent exception type
-        f_exc_value (any, R/W): Most recent exception value
-        f_exc_traceback (traceback/None, R/W): Most recent exception traceback
     Tracebacks:
         tb_next (frame/None, R/O): next level in stack trace (toward the frame where
                                   the exception occurred)
index 062e9061a3cc949d15851d272a783c3eb087b99c..48fc2ca0eeaa899f9ae8d372afc331a1ec6b7aa9 100644 (file)
@@ -20,9 +20,6 @@ static PyMemberDef frame_memberlist[] = {
        {"f_builtins",  T_OBJECT,       OFF(f_builtins),READONLY},
        {"f_globals",   T_OBJECT,       OFF(f_globals), READONLY},
        {"f_lasti",     T_INT,          OFF(f_lasti),   READONLY},
-       {"f_exc_type",  T_OBJECT,       OFF(f_exc_type)},
-       {"f_exc_value", T_OBJECT,       OFF(f_exc_value)},
-       {"f_exc_traceback", T_OBJECT,   OFF(f_exc_traceback)},
        {NULL}  /* Sentinel */
 };
 
index 3ea90599bd69ca54ec5782971f9784d7000b12d0..bcb15e796409456cf7d2ab77853b1b3daea4db9e 100644 (file)
@@ -116,8 +116,6 @@ static int maybe_call_line_trace(Py_tracefunc, PyObject *,
 static PyObject * cmp_outcome(int, PyObject *, PyObject *);
 static PyObject * import_from(PyObject *, PyObject *);
 static int import_all_from(PyObject *, PyObject *);
-static void set_exc_info(PyThreadState *, PyObject *, PyObject *, PyObject *);
-static void reset_exc_info(PyThreadState *);
 static void format_exc_check_arg(PyObject *, const char *, PyObject *);
 static PyObject * unicode_concatenate(PyObject *, PyObject *,
                                       PyFrameObject *, unsigned char *);
@@ -483,7 +481,8 @@ enum why_code {
                WHY_RETURN =    0x0008, /* 'return' statement */
                WHY_BREAK =     0x0010, /* 'break' statement */
                WHY_CONTINUE =  0x0020, /* 'continue' statement */
-               WHY_YIELD =     0x0040  /* 'yield' operator */
+               WHY_YIELD =     0x0040, /* 'yield' operator */
+               WHY_SILENCED = 0x0080 /* Exception silenced by 'with' */
 };
 
 static enum why_code do_raise(PyObject *, PyObject *);
@@ -692,6 +691,53 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                                     GETLOCAL(i) = value; \
                                      Py_XDECREF(tmp); } while (0)
 
+
+#define UNWIND_BLOCK(b) \
+       while (STACK_LEVEL() > (b)->b_level) { \
+               PyObject *v = POP(); \
+               Py_XDECREF(v); \
+       }
+
+#define UNWIND_EXCEPT_HANDLER(b) \
+       assert(STACK_LEVEL() >= (b)->b_level + 3); \
+       while (STACK_LEVEL() > (b)->b_level + 3) { \
+               PyObject *v = POP(); \
+               Py_XDECREF(v); \
+       } \
+       Py_XDECREF(tstate->exc_type); \
+       tstate->exc_type = POP(); \
+       Py_XDECREF(tstate->exc_value); \
+       tstate->exc_value = POP(); \
+       Py_XDECREF(tstate->exc_traceback); \
+       tstate->exc_traceback = POP();
+
+#define SAVE_EXC_STATE() \
+       { \
+               Py_XINCREF(tstate->exc_type); \
+               Py_XINCREF(tstate->exc_value); \
+               Py_XINCREF(tstate->exc_traceback); \
+               Py_XDECREF(f->f_exc_type); \
+               Py_XDECREF(f->f_exc_value); \
+               Py_XDECREF(f->f_exc_traceback); \
+               f->f_exc_type = tstate->exc_type; \
+               f->f_exc_value = tstate->exc_value; \
+               f->f_exc_traceback = tstate->exc_traceback; \
+       }
+
+#define SWAP_EXC_STATE() \
+       { \
+               PyObject *tmp; \
+               tmp = tstate->exc_type; \
+               tstate->exc_type = f->f_exc_type; \
+               f->f_exc_type = tmp; \
+               tmp = tstate->exc_value; \
+               tstate->exc_value = f->f_exc_value; \
+               f->f_exc_value = tmp; \
+               tmp = tstate->exc_traceback; \
+               tstate->exc_traceback = f->f_exc_traceback; \
+               f->f_exc_traceback = tmp; \
+       }
+
 /* Start of code */
 
        if (f == NULL)
@@ -765,6 +811,18 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
        assert(stack_pointer != NULL);
        f->f_stacktop = NULL;   /* remains NULL unless yield suspends frame */
 
+       if (f->f_code->co_flags & CO_GENERATOR) {
+               if (f->f_exc_type != NULL && f->f_exc_type != Py_None) {
+                       /* We were in an except handler when we left,
+                          restore the exception state which was put aside
+                          (see YIELD_VALUE). */
+                       SWAP_EXC_STATE();
+               }
+               else {
+                       SAVE_EXC_STATE();
+               }
+       }
+
 #ifdef LLTRACE
        lltrace = PyDict_GetItemString(f->f_globals, "__lltrace__") != NULL;
 #endif
@@ -1443,15 +1501,29 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                        retval = POP();
                        f->f_stacktop = stack_pointer;
                        why = WHY_YIELD;
+                       /* Put aside the current exception state and restore
+                          that of the calling frame. This only serves when
+                          "yield" is used inside an except handler. */
+                       SWAP_EXC_STATE();
                        goto fast_yield;
 
-               case POP_BLOCK:
+               case POP_EXCEPT:
                        {
                                PyTryBlock *b = PyFrame_BlockPop(f);
-                               while (STACK_LEVEL() > b->b_level) {
-                                       v = POP();
-                                       Py_DECREF(v);
+                               if (b->b_type != EXCEPT_HANDLER) {
+                                       PyErr_SetString(PyExc_SystemError,
+                                               "popped block is not an except handler");
+                                       why = WHY_EXCEPTION;
+                                       break;
                                }
+                               UNWIND_EXCEPT_HANDLER(b);
+                       }
+                       continue;
+
+               case POP_BLOCK:
+                       {
+                               PyTryBlock *b = PyFrame_BlockPop(f);
+                               UNWIND_BLOCK(b);
                        }
                        continue;
 
@@ -1464,6 +1536,22 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                                if (why == WHY_RETURN ||
                                    why == WHY_CONTINUE)
                                        retval = POP();
+                               if (why == WHY_SILENCED) {
+                                       /* An exception was silenced by 'with', we must
+                                       manually unwind the EXCEPT_HANDLER block which was
+                                       created when the exception was caught, otherwise
+                                       the stack will be in an inconsistent state. */
+                                       PyTryBlock *b = PyFrame_BlockPop(f);
+                                       if (b->b_type != EXCEPT_HANDLER) {
+                                               PyErr_SetString(PyExc_SystemError,
+                                                       "popped block is not an except handler");
+                                               why = WHY_EXCEPTION;
+                                       }
+                                       else {
+                                               UNWIND_EXCEPT_HANDLER(b);
+                                               why = WHY_NOT;
+                                       }
+                               }
                        }
                        else if (PyExceptionClass_Check(v)) {
                                w = POP();
@@ -1477,19 +1565,6 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                                        "'finally' pops bad exception");
                                why = WHY_EXCEPTION;
                        }
-                       /*
-                         Make sure the exception state is cleaned up before
-                         the end of an except block. This ensures objects
-                         referenced by the exception state are not kept
-                         alive too long.
-                         See #2507.
-                       */
-                       if (tstate->frame->f_exc_type != NULL)
-                               reset_exc_info(tstate);
-                       else {
-                               assert(tstate->frame->f_exc_value == NULL);
-                               assert(tstate->frame->f_exc_traceback == NULL);
-                       }
                        Py_DECREF(v);
                        break;
 
@@ -2056,59 +2131,33 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                           should still be resumed.)
                        */
 
-                       PyObject *exit_func;
-
-                       u = POP();
+                       PyObject *exit_func = POP();
+                       u = TOP();
                        if (u == Py_None) {
-                               exit_func = TOP();
-                               SET_TOP(u);
                                v = w = Py_None;
                        }
                        else if (PyLong_Check(u)) {
-                               switch(PyLong_AS_LONG(u)) {
-                               case WHY_RETURN:
-                               case WHY_CONTINUE:
-                                       /* Retval in TOP. */
-                                       exit_func = SECOND();
-                                       SET_SECOND(TOP());
-                                       SET_TOP(u);
-                                       break;
-                               default:
-                                       exit_func = TOP();
-                                       SET_TOP(u);
-                                       break;
-                               }
                                u = v = w = Py_None;
                        }
                        else {
-                               v = TOP();
-                               w = SECOND();
-                               exit_func = THIRD();
-                               SET_TOP(u);
-                               SET_SECOND(v);
-                               SET_THIRD(w);
+                               v = SECOND();
+                               w = THIRD();
                        }
                        /* XXX Not the fastest way to call it... */
                        x = PyObject_CallFunctionObjArgs(exit_func, u, v, w,
                                                         NULL);
-                       if (x == NULL) {
-                               Py_DECREF(exit_func);
+                       Py_DECREF(exit_func);
+                       if (x == NULL)
                                break; /* Go to error exit */
-                       }
                        if (u != Py_None && PyObject_IsTrue(x)) {
-                               /* There was an exception and a true return */
+                               /* There was an exception and a True return */
                                STACKADJ(-2);
-                               Py_INCREF(Py_None);
-                               SET_TOP(Py_None);
+                               SET_TOP(PyLong_FromLong((long) WHY_SILENCED));
                                Py_DECREF(u);
                                Py_DECREF(v);
                                Py_DECREF(w);
-                       } else {
-                               /* The stack was rearranged to remove EXIT
-                                  above. Let END_FINALLY do its thing */
                        }
                        Py_DECREF(x);
-                       Py_DECREF(exit_func);
                        PREDICT(END_FINALLY);
                        break;
                }
@@ -2370,50 +2419,63 @@ fast_block_end:
                                break;
                        }
 
-                       while (STACK_LEVEL() > b->b_level) {
-                               v = POP();
-                               Py_XDECREF(v);
+                       if (b->b_type == EXCEPT_HANDLER) {
+                               UNWIND_EXCEPT_HANDLER(b);
+                               if (why == WHY_EXCEPTION) {
+                                       Py_CLEAR(tstate->exc_type);
+                                       Py_CLEAR(tstate->exc_value);
+                                       Py_CLEAR(tstate->exc_traceback);
+                               }
+                               continue;
                        }
+                       UNWIND_BLOCK(b);
                        if (b->b_type == SETUP_LOOP && why == WHY_BREAK) {
                                why = WHY_NOT;
                                JUMPTO(b->b_handler);
                                break;
                        }
-                       if (b->b_type == SETUP_FINALLY ||
-                           (b->b_type == SETUP_EXCEPT &&
-                            why == WHY_EXCEPTION)) {
-                               if (why == WHY_EXCEPTION) {
-                                       PyObject *exc, *val, *tb;
-                                       PyErr_Fetch(&exc, &val, &tb);
-                                       if (val == NULL) {
-                                               val = Py_None;
-                                               Py_INCREF(val);
-                                       }
-                                       /* Make the raw exception data
-                                          available to the handler,
-                                          so a program can emulate the
-                                          Python main loop.  Don't do
-                                          this for 'finally'. */
-                                       if (b->b_type == SETUP_EXCEPT) {
-                                               PyErr_NormalizeException(
-                                                       &exc, &val, &tb);
-                                               set_exc_info(tstate,
-                                                            exc, val, tb);
-                                       }
-                                       if (tb == NULL) {
-                                               Py_INCREF(Py_None);
-                                               PUSH(Py_None);
-                                       } else
-                                               PUSH(tb);
-                                       PUSH(val);
-                                       PUSH(exc);
+                       if (why == WHY_EXCEPTION && (b->b_type == SETUP_EXCEPT
+                               || b->b_type == SETUP_FINALLY)) {
+                               PyObject *exc, *val, *tb;
+                               int handler = b->b_handler;
+                               /* Beware, this invalidates all b->b_* fields */
+                               PyFrame_BlockSetup(f, EXCEPT_HANDLER, -1, STACK_LEVEL());
+                               PUSH(tstate->exc_traceback);
+                               PUSH(tstate->exc_value);
+                               if (tstate->exc_type != NULL) {
+                                       PUSH(tstate->exc_type);
                                }
                                else {
-                                       if (why & (WHY_RETURN | WHY_CONTINUE))
-                                               PUSH(retval);
-                                       v = PyLong_FromLong((long)why);
-                                       PUSH(v);
+                                       Py_INCREF(Py_None);
+                                       PUSH(Py_None);
                                }
+                               PyErr_Fetch(&exc, &val, &tb);
+                               /* Make the raw exception data
+                                  available to the handler,
+                                  so a program can emulate the
+                                  Python main loop. */
+                               PyErr_NormalizeException(
+                                       &exc, &val, &tb);
+                               PyException_SetTraceback(val, tb);
+                               Py_INCREF(exc);
+                               tstate->exc_type = exc;
+                               Py_INCREF(val);
+                               tstate->exc_value = val;
+                               tstate->exc_traceback = tb;
+                               if (tb == NULL)
+                                       tb = Py_None;
+                               Py_INCREF(tb);
+                               PUSH(tb);
+                               PUSH(val);
+                               PUSH(exc);
+                               why = WHY_NOT;
+                               JUMPTO(handler);
+                               break;
+                       }
+                       if (b->b_type == SETUP_FINALLY) {
+                               if (why & (WHY_RETURN | WHY_CONTINUE))
+                                       PUSH(retval);
+                               PUSH(PyLong_FromLong((long)why));
                                why = WHY_NOT;
                                JUMPTO(b->b_handler);
                                break;
@@ -2471,13 +2533,6 @@ fast_yield:
                }
        }
 
-       if (tstate->frame->f_exc_type != NULL)
-               reset_exc_info(tstate);
-       else {
-               assert(tstate->frame->f_exc_value == NULL);
-               assert(tstate->frame->f_exc_traceback == NULL);
-       }
-
        /* pop frame */
 exit_eval_frame:
        Py_LeaveRecursiveCall();
@@ -2757,150 +2812,6 @@ fail: /* Jump here from prelude on failure */
 }
 
 
-/* Implementation notes for set_exc_info() and reset_exc_info():
-
-- Below, 'exc_ZZZ' stands for 'exc_type', 'exc_value' and
-  'exc_traceback'.  These always travel together.
-
-- tstate->curexc_ZZZ is the "hot" exception that is set by
-  PyErr_SetString(), cleared by PyErr_Clear(), and so on.
-
-- Once an exception is caught by an except clause, it is transferred
-  from tstate->curexc_ZZZ to tstate->exc_ZZZ, from which sys.exc_info()
-  can pick it up.  This is the primary task of set_exc_info().
-  XXX That can't be right:  set_exc_info() doesn't look at tstate->curexc_ZZZ.
-
-- Now let me explain the complicated dance with frame->f_exc_ZZZ.
-
-  Long ago, when none of this existed, there were just a few globals:
-  one set corresponding to the "hot" exception, and one set
-  corresponding to sys.exc_ZZZ.  (Actually, the latter weren't C
-  globals; they were simply stored as sys.exc_ZZZ.  For backwards
-  compatibility, they still are!)  The problem was that in code like
-  this:
-
-     try:
-       "something that may fail"
-     except "some exception":
-       "do something else first"
-       "print the exception from sys.exc_ZZZ."
-
-  if "do something else first" invoked something that raised and caught
-  an exception, sys.exc_ZZZ were overwritten.  That was a frequent
-  cause of subtle bugs.  I fixed this by changing the semantics as
-  follows:
-
-    - Within one frame, sys.exc_ZZZ will hold the last exception caught
-      *in that frame*.
-
-    - But initially, and as long as no exception is caught in a given
-      frame, sys.exc_ZZZ will hold the last exception caught in the
-      previous frame (or the frame before that, etc.).
-
-  The first bullet fixed the bug in the above example.  The second
-  bullet was for backwards compatibility: it was (and is) common to
-  have a function that is called when an exception is caught, and to
-  have that function access the caught exception via sys.exc_ZZZ.
-  (Example: traceback.print_exc()).
-
-  At the same time I fixed the problem that sys.exc_ZZZ weren't
-  thread-safe, by introducing sys.exc_info() which gets it from tstate;
-  but that's really a separate improvement.
-
-  The reset_exc_info() function in ceval.c restores the tstate->exc_ZZZ
-  variables to what they were before the current frame was called.  The
-  set_exc_info() function saves them on the frame so that
-  reset_exc_info() can restore them.  The invariant is that
-  frame->f_exc_ZZZ is NULL iff the current frame never caught an
-  exception (where "catching" an exception applies only to successful
-  except clauses); and if the current frame ever caught an exception,
-  frame->f_exc_ZZZ is the exception that was stored in tstate->exc_ZZZ
-  at the start of the current frame.
-
-*/
-
-static void
-set_exc_info(PyThreadState *tstate,
-            PyObject *type, PyObject *value, PyObject *tb)
-{
-       PyFrameObject *frame = tstate->frame;
-       PyObject *tmp_type, *tmp_value, *tmp_tb;
-
-       assert(type != NULL);
-       assert(frame != NULL);
-       if (frame->f_exc_type == NULL) {
-               assert(frame->f_exc_value == NULL);
-               assert(frame->f_exc_traceback == NULL);
-               /* This frame didn't catch an exception before. */
-               /* Save previous exception of this thread in this frame. */
-               if (tstate->exc_type == NULL) {
-                       /* XXX Why is this set to Py_None? */
-                       Py_INCREF(Py_None);
-                       tstate->exc_type = Py_None;
-               }
-               Py_INCREF(tstate->exc_type);
-               Py_XINCREF(tstate->exc_value);
-               Py_XINCREF(tstate->exc_traceback);
-               frame->f_exc_type = tstate->exc_type;
-               frame->f_exc_value = tstate->exc_value;
-               frame->f_exc_traceback = tstate->exc_traceback;
-       }
-       /* Set new exception for this thread. */
-       tmp_type = tstate->exc_type;
-       tmp_value = tstate->exc_value;
-       tmp_tb = tstate->exc_traceback;
-       Py_INCREF(type);
-       Py_XINCREF(value);
-       Py_XINCREF(tb);
-       tstate->exc_type = type;
-       tstate->exc_value = value;
-       tstate->exc_traceback = tb;
-       PyException_SetTraceback(value, tb);
-       Py_XDECREF(tmp_type);
-       Py_XDECREF(tmp_value);
-       Py_XDECREF(tmp_tb);
-}
-
-static void
-reset_exc_info(PyThreadState *tstate)
-{
-       PyFrameObject *frame;
-       PyObject *tmp_type, *tmp_value, *tmp_tb;
-
-       /* It's a precondition that the thread state's frame caught an
-        * exception -- verify in a debug build.
-        */
-       assert(tstate != NULL);
-       frame = tstate->frame;
-       assert(frame != NULL);
-       assert(frame->f_exc_type != NULL);
-
-       /* Copy the frame's exception info back to the thread state. */
-       tmp_type = tstate->exc_type;
-       tmp_value = tstate->exc_value;
-       tmp_tb = tstate->exc_traceback;
-       Py_INCREF(frame->f_exc_type);
-       Py_XINCREF(frame->f_exc_value);
-       Py_XINCREF(frame->f_exc_traceback);
-       tstate->exc_type = frame->f_exc_type;
-       tstate->exc_value = frame->f_exc_value;
-       tstate->exc_traceback = frame->f_exc_traceback;
-       Py_XDECREF(tmp_type);
-       Py_XDECREF(tmp_value);
-       Py_XDECREF(tmp_tb);
-
-       /* Clear the frame's exception info. */
-       tmp_type = frame->f_exc_type;
-       tmp_value = frame->f_exc_value;
-       tmp_tb = frame->f_exc_traceback;
-       frame->f_exc_type = NULL;
-       frame->f_exc_value = NULL;
-       frame->f_exc_traceback = NULL;
-       Py_DECREF(tmp_type);
-       Py_XDECREF(tmp_value);
-       Py_XDECREF(tmp_tb);
-}
-
 /* Logic for the raise statement (too complicated for inlining).
    This *consumes* a reference count to each of its arguments. */
 static enum why_code
index c8a4f854d5d3043875920b54e77c4ac4edd4c5f8..6017b2ca436db72f9d31a5edeb64f556480dbf34 100644 (file)
@@ -760,6 +760,8 @@ opcode_stack_effect(int opcode, int oparg)
 
                case POP_BLOCK:
                        return 0;
+               case POP_EXCEPT:
+                       return 0;  /* -3 except if bad bytecode */
                case END_FINALLY:
                        return -1; /* or -2 or -3 if exception occurred */
 
@@ -818,7 +820,8 @@ opcode_stack_effect(int opcode, int oparg)
                        return 0;
                case SETUP_EXCEPT:
                case SETUP_FINALLY:
-                       return 3; /* actually pushed by an exception */
+                       return 6; /* can push 3 values for the new exception
+                               + 3 others for the previous exception state */
 
                case LOAD_FAST:
                        return 1;
@@ -2031,6 +2034,7 @@ compiler_try_except(struct compiler *c, stmt_ty s)
             /* second # body */
                VISIT_SEQ(c, stmt, handler->v.ExceptHandler.body);
                ADDOP(c, POP_BLOCK);
+               ADDOP(c, POP_EXCEPT);
                compiler_pop_fblock(c, FINALLY_TRY, cleanup_body);
 
             /* finally: */
@@ -2050,9 +2054,20 @@ compiler_try_except(struct compiler *c, stmt_ty s)
                compiler_pop_fblock(c, FINALLY_END, cleanup_end);
                }
                else {
+            basicblock *cleanup_body;
+
+            cleanup_body = compiler_new_block(c);
+            if(!cleanup_body)
+                return 0;
+
+                       ADDOP(c, POP_TOP);
             ADDOP(c, POP_TOP);
-            ADDOP(c, POP_TOP);
+               compiler_use_next_block(c, cleanup_body);
+               if (!compiler_push_fblock(c, FINALLY_TRY, cleanup_body))
+                       return 0;
                    VISIT_SEQ(c, stmt, handler->v.ExceptHandler.body);
+               ADDOP(c, POP_EXCEPT);
+               compiler_pop_fblock(c, FINALLY_TRY, cleanup_body);
                }
                ADDOP_JREL(c, JUMP_FORWARD, end);
                compiler_use_next_block(c, except);
@@ -3109,7 +3124,7 @@ compiler_with(struct compiler *c, stmt_ty s)
 {
     static identifier enter_attr, exit_attr;
     basicblock *block, *finally;
-    identifier tmpvalue = NULL;
+    identifier tmpvalue = NULL, tmpexit = NULL;
 
     assert(s->kind == With_kind);
 
@@ -3144,6 +3159,10 @@ compiler_with(struct compiler *c, stmt_ty s)
            return 0;
        PyArena_AddPyObject(c->c_arena, tmpvalue);
     }
+       tmpexit = compiler_new_tmpname(c);
+       if (tmpexit == NULL)
+           return 0;
+       PyArena_AddPyObject(c->c_arena, tmpexit);
 
     /* Evaluate EXPR */
     VISIT(c, expr, s->v.With.context_expr);
@@ -3151,7 +3170,8 @@ compiler_with(struct compiler *c, stmt_ty s)
     /* Squirrel away context.__exit__ by stuffing it under context */
     ADDOP(c, DUP_TOP);
     ADDOP_O(c, LOAD_ATTR, exit_attr, names);
-    ADDOP(c, ROT_TWO);
+       if (!compiler_nameop(c, tmpexit, Store))
+           return 0;
 
     /* Call context.__enter__() */
     ADDOP_O(c, LOAD_ATTR, enter_attr, names);
@@ -3198,6 +3218,9 @@ compiler_with(struct compiler *c, stmt_ty s)
     /* Finally block starts; context.__exit__ is on the stack under
        the exception or return information. Just issue our magic
        opcode. */
+       if (!compiler_nameop(c, tmpexit, Load) ||
+               !compiler_nameop(c, tmpexit, Del))
+               return 0;
     ADDOP(c, WITH_CLEANUP);
 
     /* Finally block ends. */
index f1d81882bda4b6b2fdcf3abe328ea6b04321ba09..dadae2e9c72dcfbb5b4434c4fe97e19bb24979ae 100644 (file)
@@ -86,8 +86,9 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *);
                      3100 (merge from 2.6a0, see 62151)
                      3102 (__file__ points to source file)
        Python 3.0a4: 3110 (WITH_CLEANUP optimization).
+       Python 3.0a5: 3130 (lexical exception stacking, including POP_EXCEPT)
 */
-#define MAGIC (3110 | ((long)'\r'<<16) | ((long)'\n'<<24))
+#define MAGIC (3130 | ((long)'\r'<<16) | ((long)'\n'<<24))
 
 /* Magic word as global; note that _PyImport_Init() can change the
    value of this global to accommodate for alterations of how the