summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Peterson <benjamin@python.org>2008-06-11 15:59:43 (GMT)
committerBenjamin Peterson <benjamin@python.org>2008-06-11 15:59:43 (GMT)
commiteec3d7137929611b98dd593cd2f122cd91b723b2 (patch)
tree42721419d4fe3f53961ecfd7c1dea3224188ae40
parente8465f2b413174084fcc2dc4cd7a53122c62ce4b (diff)
downloadcpython-eec3d7137929611b98dd593cd2f122cd91b723b2.zip
cpython-eec3d7137929611b98dd593cd2f122cd91b723b2.tar.gz
cpython-eec3d7137929611b98dd593cd2f122cd91b723b2.tar.bz2
#3021: Antoine Pitrou's Lexical exception handlers
-rw-r--r--Doc/library/dis.rst36
-rw-r--r--Doc/library/inspect.rst11
-rw-r--r--Doc/library/sys.rst4
-rw-r--r--Doc/reference/datamodel.rst13
-rw-r--r--Include/frameobject.h14
-rw-r--r--Include/opcode.h8
-rw-r--r--Lib/doctest.py7
-rw-r--r--Lib/inspect.py3
-rw-r--r--Lib/opcode.py1
-rw-r--r--Lib/test/test_exceptions.py108
-rw-r--r--Lib/test/test_raise.py73
-rw-r--r--Misc/NEWS4
-rw-r--r--Misc/cheatsheet3
-rw-r--r--Objects/frameobject.c3
-rw-r--r--Python/ceval.c391
-rw-r--r--Python/compile.c31
-rw-r--r--Python/import.c3
17 files changed, 410 insertions, 303 deletions
diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst
index 125a80f..6b2de49 100644
--- a/Doc/library/dis.rst
+++ b/Doc/library/dis.rst
@@ -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!
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index a87651f..98ecbcb 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -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 |
+-----------+-----------------+---------------------------+
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index b69de3f..42c36a6 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -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
diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst
index 6562b00..4e24df7 100644
--- a/Doc/reference/datamodel.rst
+++ b/Doc/reference/datamodel.rst
@@ -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::
diff --git a/Include/frameobject.h b/Include/frameobject.h
index d2afe8b..65ebd2a 100644
--- a/Include/frameobject.h
+++ b/Include/frameobject.h
@@ -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;
diff --git a/Include/opcode.h b/Include/opcode.h
index 7bdf1c9..6da9877 100644
--- a/Include/opcode.h
+++ b/Include/opcode.h
@@ -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};
diff --git a/Lib/doctest.py b/Lib/doctest.py
index dad8333..74be21e 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -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)
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 5758abd..e89b5f0 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -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
diff --git a/Lib/opcode.py b/Lib/opcode.py
index eac0b63..50e10ee 100644
--- a/Lib/opcode.py
+++ b/Lib/opcode.py
@@ -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:
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 41b9413..9068554 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -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)
diff --git a/Lib/test/test_raise.py b/Lib/test/test_raise.py
index 89e2190..5f0070e 100644
--- a/Lib/test/test_raise.py
+++ b/Lib/test/test_raise.py
@@ -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()
diff --git a/Misc/NEWS b/Misc/NEWS
index 410b668..4c8d368 100644
--- 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
-----------------
diff --git a/Misc/cheatsheet b/Misc/cheatsheet
index 50ffc22..0f18ac3 100644
--- a/Misc/cheatsheet
+++ b/Misc/cheatsheet
@@ -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)
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 062e906..48fc2ca 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -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 */
};
diff --git a/Python/ceval.c b/Python/ceval.c
index 3ea9059..bcb15e7 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -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
diff --git a/Python/compile.c b/Python/compile.c
index c8a4f85..6017b2c 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -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. */
diff --git a/Python/import.c b/Python/import.c
index f1d8188..dadae2e 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -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