Commit c86ac884 authored by Benjamin Peterson's avatar Benjamin Peterson

#3021: Antoine Pitrou's Lexical exception handlers

parent 55d03e06
...@@ -397,6 +397,14 @@ Miscellaneous opcodes. ...@@ -397,6 +397,14 @@ Miscellaneous opcodes.
denoting nested loops, try statements, and such. 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 () .. opcode:: END_FINALLY ()
Terminates a :keyword:`finally` clause. The interpreter recalls whether the Terminates a :keyword:`finally` clause. The interpreter recalls whether the
...@@ -412,24 +420,22 @@ Miscellaneous opcodes. ...@@ -412,24 +420,22 @@ Miscellaneous opcodes.
.. opcode:: WITH_CLEANUP () .. opcode:: WITH_CLEANUP ()
Cleans up the stack when a :keyword:`with` statement block exits. On top of Cleans up the stack when a :keyword:`with` statement block exits. TOS is
the stack are 1--3 values indicating how/why the finally clause was entered: the context manager's :meth:`__exit__` bound method. Below TOS 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. * 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 In the last case, ``TOS(SECOND, THIRD, FOURTH)`` is called, otherwise
``EXIT(None, None, None)``. ``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 If the stack represents an exception, *and* the function call returns
order. In addition, if the stack represents an exception, *and* the function a 'true' value, this information is "zapped" and replaced with a single
call returns a 'true' value, this information is "zapped", to prevent ``WHY_SILENCED`` to prevent ``END_FINALLY`` from re-raising the exception.
``END_FINALLY`` from re-raising the exception. (But non-local gotos should (But non-local gotos will still be resumed.)
still be resumed.)
.. XXX explain the WHY stuff! .. XXX explain the WHY stuff!
......
...@@ -94,17 +94,6 @@ attributes: ...@@ -94,17 +94,6 @@ attributes:
| | f_code | code object being | | | f_code | code object being |
| | | executed in this frame | | | | 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 | | | f_globals | global namespace seen by |
| | | this frame | | | | this frame |
+-----------+-----------------+---------------------------+ +-----------+-----------------+---------------------------+
......
...@@ -136,8 +136,8 @@ always available. ...@@ -136,8 +136,8 @@ always available.
frame is not handling an exception, the information is taken from the calling 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 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 handling an exception. Here, "handling an exception" is defined as "executing
or having executed an except clause." For any stack frame, only information an except clause." For any stack frame, only information about the exception
about the most recently handled exception is accessible. being currently handled is accessible.
.. index:: object: traceback .. index:: object: traceback
......
...@@ -875,19 +875,14 @@ Internal types ...@@ -875,19 +875,14 @@ Internal types
.. index:: .. index::
single: f_trace (frame attribute) 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) single: f_lineno (frame attribute)
Special writable attributes: :attr:`f_trace`, if not ``None``, is a function 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); 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 :attr:`f_lineno` is the current line number of the frame --- writing to this
last exception raised in the parent frame provided another exception was ever from within a trace function jumps to the given line (only for the bottom-most
raised in the current frame (in all other cases they are None); :attr:`f_lineno` frame). A debugger can implement a Jump command (aka Set Next Statement)
is the current line number of the frame --- writing to this from within a trace by writing to f_lineno.
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 Traceback objects
.. index:: .. index::
......
...@@ -27,13 +27,13 @@ typedef struct _frame { ...@@ -27,13 +27,13 @@ typedef struct _frame {
PyObject **f_stacktop; PyObject **f_stacktop;
PyObject *f_trace; /* Trace function */ PyObject *f_trace; /* Trace function */
/* If an exception is raised in this frame, the next three are used to /* In a generator, we need to be able to swap between the exception
* record the exception info (if any) originally in the thread state. See state inside the generator and the exception state of the calling
* comments before set_exc_info() -- it's not obvious. frame (which shouldn't be impacted when the generator "yields"
* Invariant: if _type is NULL, then so are _value and _traceback. from an except handler).
* Desired invariant: all three are NULL, or all three are non-NULL. That These three fields exist exactly for that, and are unused for
* one isn't currently true, but "should be". 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; PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
PyThreadState *f_tstate; PyThreadState *f_tstate;
......
...@@ -70,6 +70,7 @@ extern "C" { ...@@ -70,6 +70,7 @@ extern "C" {
#define YIELD_VALUE 86 #define YIELD_VALUE 86
#define POP_BLOCK 87 #define POP_BLOCK 87
#define END_FINALLY 88 #define END_FINALLY 88
#define POP_EXCEPT 89
#define HAVE_ARGUMENT 90 /* Opcodes from here have an argument: */ #define HAVE_ARGUMENT 90 /* Opcodes from here have an argument: */
...@@ -133,6 +134,13 @@ extern "C" { ...@@ -133,6 +134,13 @@ extern "C" {
#define EXTENDED_ARG 143 #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, 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}; PyCmp_IN, PyCmp_NOT_IN, PyCmp_IS, PyCmp_IS_NOT, PyCmp_EXC_MATCH, PyCmp_BAD};
......
...@@ -1242,10 +1242,9 @@ class DocTestRunner: ...@@ -1242,10 +1242,9 @@ class DocTestRunner:
# The example raised an exception: check if it was expected. # The example raised an exception: check if it was expected.
else: else:
exc_info = sys.exc_info() exc_msg = traceback.format_exception_only(*exception[:2])[-1]
exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
if not quiet: if not quiet:
got += _exception_traceback(exc_info) got += _exception_traceback(exception)
# If `example.exc_msg` is None, then we weren't expecting # If `example.exc_msg` is None, then we weren't expecting
# an exception. # an exception.
...@@ -1275,7 +1274,7 @@ class DocTestRunner: ...@@ -1275,7 +1274,7 @@ class DocTestRunner:
elif outcome is BOOM: elif outcome is BOOM:
if not quiet: if not quiet:
self.report_unexpected_exception(out, test, example, self.report_unexpected_exception(out, test, example,
exc_info) exception)
failures += 1 failures += 1
else: else:
assert False, ("unknown outcome", outcome) assert False, ("unknown outcome", outcome)
......
...@@ -197,9 +197,6 @@ def isframe(object): ...@@ -197,9 +197,6 @@ def isframe(object):
f_back next outer frame object (this frame's caller) f_back next outer frame object (this frame's caller)
f_builtins built-in namespace seen by this frame f_builtins built-in namespace seen by this frame
f_code code object being executed in 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_globals global namespace seen by this frame
f_lasti index of last attempted instruction in bytecode f_lasti index of last attempted instruction in bytecode
f_lineno current line number in Python source code f_lineno current line number in Python source code
......
...@@ -105,6 +105,7 @@ def_op('MAKE_BYTES', 85) ...@@ -105,6 +105,7 @@ def_op('MAKE_BYTES', 85)
def_op('YIELD_VALUE', 86) def_op('YIELD_VALUE', 86)
def_op('POP_BLOCK', 87) def_op('POP_BLOCK', 87)
def_op('END_FINALLY', 88) def_op('END_FINALLY', 88)
def_op('POP_EXCEPT', 89)
HAVE_ARGUMENT = 90 # Opcodes from here have an argument: HAVE_ARGUMENT = 90 # Opcodes from here have an argument:
......
...@@ -427,6 +427,7 @@ class ExceptionTests(unittest.TestCase): ...@@ -427,6 +427,7 @@ class ExceptionTests(unittest.TestCase):
local_ref = obj local_ref = obj
raise MyException(obj) raise MyException(obj)
# Qualified "except" with "as"
obj = MyObj() obj = MyObj()
wr = weakref.ref(obj) wr = weakref.ref(obj)
try: try:
...@@ -437,6 +438,113 @@ class ExceptionTests(unittest.TestCase): ...@@ -437,6 +438,113 @@ class ExceptionTests(unittest.TestCase):
obj = wr() obj = wr()
self.failUnless(obj is None, "%s" % obj) 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(): def test_main():
run_unittest(ExceptionTests) run_unittest(ExceptionTests)
......
...@@ -16,6 +16,13 @@ def get_tb(): ...@@ -16,6 +16,13 @@ def get_tb():
return sys.exc_info()[2] 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): class TestRaise(unittest.TestCase):
def test_invalid_reraise(self): def test_invalid_reraise(self):
try: try:
...@@ -37,6 +44,71 @@ class TestRaise(unittest.TestCase): ...@@ -37,6 +44,71 @@ class TestRaise(unittest.TestCase):
else: else:
self.fail("No exception raised") 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): def test_erroneous_exception(self):
class MyException(Exception): class MyException(Exception):
def __init__(self): def __init__(self):
...@@ -158,6 +230,5 @@ class TestRemovedFunctionality(unittest.TestCase): ...@@ -158,6 +230,5 @@ class TestRemovedFunctionality(unittest.TestCase):
def test_main(): def test_main():
support.run_unittest(__name__) support.run_unittest(__name__)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
...@@ -49,6 +49,10 @@ Core and Builtins ...@@ -49,6 +49,10 @@ Core and Builtins
Exception (KeyboardInterrupt, and SystemExit) propagate instead of Exception (KeyboardInterrupt, and SystemExit) propagate instead of
ignoring them. 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 Extension Modules
----------------- -----------------
......
...@@ -1262,9 +1262,6 @@ Special informative state attributes for some types: ...@@ -1262,9 +1262,6 @@ Special informative state attributes for some types:
f_lineno (int, R/O): current line number f_lineno (int, R/O): current line number
f_lasti (int, R/O): precise instruction (index into bytecode) 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_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: Tracebacks:
tb_next (frame/None, R/O): next level in stack trace (toward the frame where tb_next (frame/None, R/O): next level in stack trace (toward the frame where
the exception occurred) the exception occurred)
......
...@@ -20,9 +20,6 @@ static PyMemberDef frame_memberlist[] = { ...@@ -20,9 +20,6 @@ static PyMemberDef frame_memberlist[] = {
{"f_builtins", T_OBJECT, OFF(f_builtins),READONLY}, {"f_builtins", T_OBJECT, OFF(f_builtins),READONLY},
{"f_globals", T_OBJECT, OFF(f_globals), READONLY}, {"f_globals", T_OBJECT, OFF(f_globals), READONLY},
{"f_lasti", T_INT, OFF(f_lasti), 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 */ {NULL} /* Sentinel */
}; };
......
...@@ -116,8 +116,6 @@ static int maybe_call_line_trace(Py_tracefunc, PyObject *, ...@@ -116,8 +116,6 @@ static int maybe_call_line_trace(Py_tracefunc, PyObject *,
static PyObject * cmp_outcome(int, PyObject *, PyObject *); static PyObject * cmp_outcome(int, PyObject *, PyObject *);
static PyObject * import_from(PyObject *, PyObject *); static PyObject * import_from(PyObject *, PyObject *);
static int import_all_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 void format_exc_check_arg(PyObject *, const char *, PyObject *);
static PyObject * unicode_concatenate(PyObject *, PyObject *, static PyObject * unicode_concatenate(PyObject *, PyObject *,
PyFrameObject *, unsigned char *); PyFrameObject *, unsigned char *);
...@@ -483,7 +481,8 @@ enum why_code { ...@@ -483,7 +481,8 @@ enum why_code {
WHY_RETURN = 0x0008, /* 'return' statement */ WHY_RETURN = 0x0008, /* 'return' statement */
WHY_BREAK = 0x0010, /* 'break' statement */ WHY_BREAK = 0x0010, /* 'break' statement */
WHY_CONTINUE = 0x0020, /* 'continue' 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 *); static enum why_code do_raise(PyObject *, PyObject *);
...@@ -692,6 +691,53 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) ...@@ -692,6 +691,53 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
GETLOCAL(i) = value; \ GETLOCAL(i) = value; \
Py_XDECREF(tmp); } while (0) 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 */ /* Start of code */
if (f == NULL) if (f == NULL)
...@@ -765,6 +811,18 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) ...@@ -765,6 +811,18 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
assert(stack_pointer != NULL); assert(stack_pointer != NULL);
f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */ 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 #ifdef LLTRACE
lltrace = PyDict_GetItemString(f->f_globals, "__lltrace__") != NULL; lltrace = PyDict_GetItemString(f->f_globals, "__lltrace__") != NULL;
#endif #endif
...@@ -1443,15 +1501,29 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) ...@@ -1443,15 +1501,29 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
retval = POP(); retval = POP();
f->f_stacktop = stack_pointer; f->f_stacktop = stack_pointer;
why = WHY_YIELD; 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; goto fast_yield;
case POP_BLOCK: case POP_EXCEPT:
{ {
PyTryBlock *b = PyFrame_BlockPop(f); PyTryBlock *b = PyFrame_BlockPop(f);
while (STACK_LEVEL() > b->b_level) { if (b->b_type != EXCEPT_HANDLER) {
v = POP(); PyErr_SetString(PyExc_SystemError,
Py_DECREF(v); "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; continue;
...@@ -1464,6 +1536,22 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) ...@@ -1464,6 +1536,22 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
if (why == WHY_RETURN || if (why == WHY_RETURN ||
why == WHY_CONTINUE) why == WHY_CONTINUE)
retval = POP(); 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)) { else if (PyExceptionClass_Check(v)) {
w = POP(); w = POP();
...@@ -1477,19 +1565,6 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) ...@@ -1477,19 +1565,6 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
"'finally' pops bad exception"); "'finally' pops bad exception");
why = WHY_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); Py_DECREF(v);
break; break;
...@@ -2056,59 +2131,33 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) ...@@ -2056,59 +2131,33 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
should still be resumed.) should still be resumed.)
*/ */
PyObject *exit_func; PyObject *exit_func = POP();
u = TOP();
u = POP();
if (u == Py_None) { if (u == Py_None) {
exit_func = TOP();
SET_TOP(u);
v = w = Py_None; v = w = Py_None;
} }
else if (PyLong_Check(u)) { 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; u = v = w = Py_None;
} }
else { else {
v = TOP(); v = SECOND();
w = SECOND(); w = THIRD();
exit_func = THIRD();
SET_TOP(u);
SET_SECOND(v);
SET_THIRD(w);
} }
/* XXX Not the fastest way to call it... */ /* XXX Not the fastest way to call it... */
x = PyObject_CallFunctionObjArgs(exit_func, u, v, w, x = PyObject_CallFunctionObjArgs(exit_func, u, v, w,
NULL); NULL);
if (x == NULL) { Py_DECREF(exit_func);
Py_DECREF(exit_func); if (x == NULL)
break; /* Go to error exit */ break; /* Go to error exit */
}
if (u != Py_None && PyObject_IsTrue(x)) { 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); STACKADJ(-2);
Py_INCREF(Py_None); SET_TOP(PyLong_FromLong((long) WHY_SILENCED));
SET_TOP(Py_None);
Py_DECREF(u); Py_DECREF(u);
Py_DECREF(v); Py_DECREF(v);
Py_DECREF(w); Py_DECREF(w);
} else {
/* The stack was rearranged to remove EXIT
above. Let END_FINALLY do its thing */
} }
Py_DECREF(x); Py_DECREF(x);
Py_DECREF(exit_func);
PREDICT(END_FINALLY); PREDICT(END_FINALLY);
break; break;
} }
...@@ -2370,50 +2419,63 @@ fast_block_end: ...@@ -2370,50 +2419,63 @@ fast_block_end:
break; break;
} }
while (STACK_LEVEL() > b->b_level) { if (b->b_type == EXCEPT_HANDLER) {
v = POP(); UNWIND_EXCEPT_HANDLER(b);
Py_XDECREF(v); 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) { if (b->b_type == SETUP_LOOP && why == WHY_BREAK) {
why = WHY_NOT; why = WHY_NOT;
JUMPTO(b->b_handler); JUMPTO(b->b_handler);
break; break;
} }
if (b->b_type == SETUP_FINALLY || if (why == WHY_EXCEPTION && (b->b_type == SETUP_EXCEPT
(b->b_type == SETUP_EXCEPT && || b->b_type == SETUP_FINALLY)) {
why == WHY_EXCEPTION)) { PyObject *exc, *val, *tb;
if (why == WHY_EXCEPTION) { int handler = b->b_handler;
PyObject *exc, *val, *tb; /* Beware, this invalidates all b->b_* fields */
PyErr_Fetch(&exc, &val, &tb); PyFrame_BlockSetup(f, EXCEPT_HANDLER, -1, STACK_LEVEL());
if (val == NULL) { PUSH(tstate->exc_traceback);
val = Py_None; PUSH(tstate->exc_value);
Py_INCREF(val); if (tstate->exc_type != NULL) {
} PUSH(tstate->exc_type);
/* 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);
} }
else { else {
if (why & (WHY_RETURN | WHY_CONTINUE)) Py_INCREF(Py_None);
PUSH(retval); PUSH(Py_None);
v = PyLong_FromLong((long)why);
PUSH(v);
} }
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; why = WHY_NOT;
JUMPTO(b->b_handler); JUMPTO(b->b_handler);
break; break;
...@@ -2471,13 +2533,6 @@ fast_yield: ...@@ -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 */ /* pop frame */
exit_eval_frame: exit_eval_frame:
Py_LeaveRecursiveCall(); Py_LeaveRecursiveCall();
...@@ -2757,150 +2812,6 @@ fail: /* Jump here from prelude on failure */ ...@@ -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). /* Logic for the raise statement (too complicated for inlining).
This *consumes* a reference count to each of its arguments. */ This *consumes* a reference count to each of its arguments. */
static enum why_code static enum why_code
......
...@@ -760,6 +760,8 @@ opcode_stack_effect(int opcode, int oparg) ...@@ -760,6 +760,8 @@ opcode_stack_effect(int opcode, int oparg)
case POP_BLOCK: case POP_BLOCK:
return 0; return 0;
case POP_EXCEPT:
return 0; /* -3 except if bad bytecode */
case END_FINALLY: case END_FINALLY:
return -1; /* or -2 or -3 if exception occurred */ return -1; /* or -2 or -3 if exception occurred */
...@@ -818,7 +820,8 @@ opcode_stack_effect(int opcode, int oparg) ...@@ -818,7 +820,8 @@ opcode_stack_effect(int opcode, int oparg)
return 0; return 0;
case SETUP_EXCEPT: case SETUP_EXCEPT:
case SETUP_FINALLY: 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: case LOAD_FAST:
return 1; return 1;
...@@ -2031,6 +2034,7 @@ compiler_try_except(struct compiler *c, stmt_ty s) ...@@ -2031,6 +2034,7 @@ compiler_try_except(struct compiler *c, stmt_ty s)
/* second # body */ /* second # body */
VISIT_SEQ(c, stmt, handler->v.ExceptHandler.body); VISIT_SEQ(c, stmt, handler->v.ExceptHandler.body);
ADDOP(c, POP_BLOCK); ADDOP(c, POP_BLOCK);
ADDOP(c, POP_EXCEPT);
compiler_pop_fblock(c, FINALLY_TRY, cleanup_body); compiler_pop_fblock(c, FINALLY_TRY, cleanup_body);
/* finally: */ /* finally: */
...@@ -2050,9 +2054,20 @@ compiler_try_except(struct compiler *c, stmt_ty s) ...@@ -2050,9 +2054,20 @@ compiler_try_except(struct compiler *c, stmt_ty s)
compiler_pop_fblock(c, FINALLY_END, cleanup_end); compiler_pop_fblock(c, FINALLY_END, cleanup_end);
} }
else { 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);
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); 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); ADDOP_JREL(c, JUMP_FORWARD, end);
compiler_use_next_block(c, except); compiler_use_next_block(c, except);
...@@ -3109,7 +3124,7 @@ compiler_with(struct compiler *c, stmt_ty s) ...@@ -3109,7 +3124,7 @@ compiler_with(struct compiler *c, stmt_ty s)
{ {
static identifier enter_attr, exit_attr; static identifier enter_attr, exit_attr;
basicblock *block, *finally; basicblock *block, *finally;
identifier tmpvalue = NULL; identifier tmpvalue = NULL, tmpexit = NULL;
assert(s->kind == With_kind); assert(s->kind == With_kind);
...@@ -3144,6 +3159,10 @@ compiler_with(struct compiler *c, stmt_ty s) ...@@ -3144,6 +3159,10 @@ compiler_with(struct compiler *c, stmt_ty s)
return 0; return 0;
PyArena_AddPyObject(c->c_arena, tmpvalue); PyArena_AddPyObject(c->c_arena, tmpvalue);
} }
tmpexit = compiler_new_tmpname(c);
if (tmpexit == NULL)
return 0;
PyArena_AddPyObject(c->c_arena, tmpexit);
/* Evaluate EXPR */ /* Evaluate EXPR */
VISIT(c, expr, s->v.With.context_expr); VISIT(c, expr, s->v.With.context_expr);
...@@ -3151,7 +3170,8 @@ compiler_with(struct compiler *c, stmt_ty s) ...@@ -3151,7 +3170,8 @@ compiler_with(struct compiler *c, stmt_ty s)
/* Squirrel away context.__exit__ by stuffing it under context */ /* Squirrel away context.__exit__ by stuffing it under context */
ADDOP(c, DUP_TOP); ADDOP(c, DUP_TOP);
ADDOP_O(c, LOAD_ATTR, exit_attr, names); ADDOP_O(c, LOAD_ATTR, exit_attr, names);
ADDOP(c, ROT_TWO); if (!compiler_nameop(c, tmpexit, Store))
return 0;
/* Call context.__enter__() */ /* Call context.__enter__() */
ADDOP_O(c, LOAD_ATTR, enter_attr, names); ADDOP_O(c, LOAD_ATTR, enter_attr, names);
...@@ -3198,6 +3218,9 @@ compiler_with(struct compiler *c, stmt_ty s) ...@@ -3198,6 +3218,9 @@ compiler_with(struct compiler *c, stmt_ty s)
/* Finally block starts; context.__exit__ is on the stack under /* Finally block starts; context.__exit__ is on the stack under
the exception or return information. Just issue our magic the exception or return information. Just issue our magic
opcode. */ opcode. */
if (!compiler_nameop(c, tmpexit, Load) ||
!compiler_nameop(c, tmpexit, Del))
return 0;
ADDOP(c, WITH_CLEANUP); ADDOP(c, WITH_CLEANUP);
/* Finally block ends. */ /* Finally block ends. */
......
...@@ -86,8 +86,9 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *); ...@@ -86,8 +86,9 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *);
3100 (merge from 2.6a0, see 62151) 3100 (merge from 2.6a0, see 62151)
3102 (__file__ points to source file) 3102 (__file__ points to source file)
Python 3.0a4: 3110 (WITH_CLEANUP optimization). 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 /* Magic word as global; note that _PyImport_Init() can change the
value of this global to accommodate for alterations of how the value of this global to accommodate for alterations of how the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment