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
.. 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!
| | 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 |
+-----------+-----------------+---------------------------+
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::
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::
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;
#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: */
#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};
# 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.
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)
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
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:
local_ref = obj
raise MyException(obj)
+ # Qualified "except" with "as"
obj = MyObj()
wr = weakref.ref(obj)
try:
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)
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:
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):
def test_main():
support.run_unittest(__name__)
-
if __name__ == "__main__":
unittest.main()
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
-----------------
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)
{"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 */
};
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 *);
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 *);
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)
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
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;
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();
"'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;
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;
}
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;
}
}
- 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();
}
-/* 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
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 */
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;
/* 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: */
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);
{
static identifier enter_attr, exit_attr;
basicblock *block, *finally;
- identifier tmpvalue = NULL;
+ identifier tmpvalue = NULL, tmpexit = NULL;
assert(s->kind == With_kind);
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);
/* 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);
/* 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. */
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