[Python-3000-checkins] r64121 - in python/branches/py3k: 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

benjamin.peterson python-3000-checkins at python.org
Wed Jun 11 17:59:44 CEST 2008


Author: benjamin.peterson
Date: Wed Jun 11 17:59:43 2008
New Revision: 64121

Log:
#3021: Antoine Pitrou's Lexical exception handlers


Modified:
   python/branches/py3k/Doc/library/dis.rst
   python/branches/py3k/Doc/library/inspect.rst
   python/branches/py3k/Doc/library/sys.rst
   python/branches/py3k/Doc/reference/datamodel.rst
   python/branches/py3k/Include/frameobject.h
   python/branches/py3k/Include/opcode.h
   python/branches/py3k/Lib/doctest.py
   python/branches/py3k/Lib/inspect.py
   python/branches/py3k/Lib/opcode.py
   python/branches/py3k/Lib/test/test_exceptions.py
   python/branches/py3k/Lib/test/test_raise.py
   python/branches/py3k/Misc/NEWS
   python/branches/py3k/Misc/cheatsheet
   python/branches/py3k/Objects/frameobject.c
   python/branches/py3k/Python/ceval.c
   python/branches/py3k/Python/compile.c
   python/branches/py3k/Python/import.c

Modified: python/branches/py3k/Doc/library/dis.rst
==============================================================================
--- python/branches/py3k/Doc/library/dis.rst	(original)
+++ python/branches/py3k/Doc/library/dis.rst	Wed Jun 11 17:59:43 2008
@@ -397,6 +397,14 @@
    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 @@
 
 .. 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()
-
-   Under them is EXIT, the context manager's :meth:`__exit__` bound method.
-
-   In the last case, ``EXIT(TOP, SECOND, THIRD)`` is called, otherwise
-   ``EXIT(None, None, None)``.
-
-   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.)
+   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:
+
+   * SECOND = ``None``
+   * (SECOND, THIRD) = (``WHY_{RETURN,CONTINUE}``), retval
+   * SECOND = ``WHY_*``; no retval below it
+   * (SECOND, THIRD, FOURTH) = exc_info()
+
+   In the last case, ``TOS(SECOND, THIRD, FOURTH)`` is called, otherwise
+   ``TOS(None, None, None)``.  In addition, TOS is removed from the stack.
+
+   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!
 

Modified: python/branches/py3k/Doc/library/inspect.rst
==============================================================================
--- python/branches/py3k/Doc/library/inspect.rst	(original)
+++ python/branches/py3k/Doc/library/inspect.rst	Wed Jun 11 17:59:43 2008
@@ -94,17 +94,6 @@
 |           | 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                |
 +-----------+-----------------+---------------------------+

Modified: python/branches/py3k/Doc/library/sys.rst
==============================================================================
--- python/branches/py3k/Doc/library/sys.rst	(original)
+++ python/branches/py3k/Doc/library/sys.rst	Wed Jun 11 17:59:43 2008
@@ -136,8 +136,8 @@
    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
 

Modified: python/branches/py3k/Doc/reference/datamodel.rst
==============================================================================
--- python/branches/py3k/Doc/reference/datamodel.rst	(original)
+++ python/branches/py3k/Doc/reference/datamodel.rst	Wed Jun 11 17:59:43 2008
@@ -875,19 +875,14 @@
 
       .. 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::

Modified: python/branches/py3k/Include/frameobject.h
==============================================================================
--- python/branches/py3k/Include/frameobject.h	(original)
+++ python/branches/py3k/Include/frameobject.h	Wed Jun 11 17:59:43 2008
@@ -27,13 +27,13 @@
     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;

Modified: python/branches/py3k/Include/opcode.h
==============================================================================
--- python/branches/py3k/Include/opcode.h	(original)
+++ python/branches/py3k/Include/opcode.h	Wed Jun 11 17:59:43 2008
@@ -70,6 +70,7 @@
 #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 @@
 #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};
 

Modified: python/branches/py3k/Lib/doctest.py
==============================================================================
--- python/branches/py3k/Lib/doctest.py	(original)
+++ python/branches/py3k/Lib/doctest.py	Wed Jun 11 17:59:43 2008
@@ -1242,10 +1242,9 @@
 
             # 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 @@
             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)

Modified: python/branches/py3k/Lib/inspect.py
==============================================================================
--- python/branches/py3k/Lib/inspect.py	(original)
+++ python/branches/py3k/Lib/inspect.py	Wed Jun 11 17:59:43 2008
@@ -197,9 +197,6 @@
         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

Modified: python/branches/py3k/Lib/opcode.py
==============================================================================
--- python/branches/py3k/Lib/opcode.py	(original)
+++ python/branches/py3k/Lib/opcode.py	Wed Jun 11 17:59:43 2008
@@ -105,6 +105,7 @@
 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:
 

Modified: python/branches/py3k/Lib/test/test_exceptions.py
==============================================================================
--- python/branches/py3k/Lib/test/test_exceptions.py	(original)
+++ python/branches/py3k/Lib/test/test_exceptions.py	Wed Jun 11 17:59:43 2008
@@ -427,6 +427,7 @@
             local_ref = obj
             raise MyException(obj)
 
+        # Qualified "except" with "as"
         obj = MyObj()
         wr = weakref.ref(obj)
         try:
@@ -437,6 +438,113 @@
         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)

Modified: python/branches/py3k/Lib/test/test_raise.py
==============================================================================
--- python/branches/py3k/Lib/test/test_raise.py	(original)
+++ python/branches/py3k/Lib/test/test_raise.py	Wed Jun 11 17:59:43 2008
@@ -16,6 +16,13 @@
         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 @@
         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 @@
 def test_main():
     support.run_unittest(__name__)
 
-
 if __name__ == "__main__":
     unittest.main()

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Wed Jun 11 17:59:43 2008
@@ -49,6 +49,10 @@
   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
 -----------------
 

Modified: python/branches/py3k/Misc/cheatsheet
==============================================================================
--- python/branches/py3k/Misc/cheatsheet	(original)
+++ python/branches/py3k/Misc/cheatsheet	Wed Jun 11 17:59:43 2008
@@ -1262,9 +1262,6 @@
         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)

Modified: python/branches/py3k/Objects/frameobject.c
==============================================================================
--- python/branches/py3k/Objects/frameobject.c	(original)
+++ python/branches/py3k/Objects/frameobject.c	Wed Jun 11 17:59:43 2008
@@ -20,9 +20,6 @@
 	{"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 */
 };
 

Modified: python/branches/py3k/Python/ceval.c
==============================================================================
--- python/branches/py3k/Python/ceval.c	(original)
+++ python/branches/py3k/Python/ceval.c	Wed Jun 11 17:59:43 2008
@@ -116,8 +116,6 @@
 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 @@
 		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 @@
 				     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 @@
 	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 @@
 			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 @@
 				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 @@
 					"'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 @@
 			   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 @@
 				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 @@
 		}
 	}
 
-	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 @@
 }
 
 
-/* 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

Modified: python/branches/py3k/Python/compile.c
==============================================================================
--- python/branches/py3k/Python/compile.c	(original)
+++ python/branches/py3k/Python/compile.c	Wed Jun 11 17:59:43 2008
@@ -760,6 +760,8 @@
 
 		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 @@
 			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 @@
             /* 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_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 @@
 {
     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 @@
 	    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 @@
     /* 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 @@
     /* 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. */

Modified: python/branches/py3k/Python/import.c
==============================================================================
--- python/branches/py3k/Python/import.c	(original)
+++ python/branches/py3k/Python/import.c	Wed Jun 11 17:59:43 2008
@@ -86,8 +86,9 @@
 		      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


More information about the Python-3000-checkins mailing list