From eec3d7137929611b98dd593cd2f122cd91b723b2 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Wed, 11 Jun 2008 15:59:43 +0000 Subject: [PATCH] #3021: Antoine Pitrou's Lexical exception handlers --- Doc/library/dis.rst | 36 ++-- Doc/library/inspect.rst | 11 - Doc/library/sys.rst | 4 +- Doc/reference/datamodel.rst | 13 +- Include/frameobject.h | 14 +- Include/opcode.h | 8 + Lib/doctest.py | 7 +- Lib/inspect.py | 3 - Lib/opcode.py | 1 + Lib/test/test_exceptions.py | 108 ++++++++++ Lib/test/test_raise.py | 73 ++++++- Misc/NEWS | 4 + Misc/cheatsheet | 3 - Objects/frameobject.c | 3 - Python/ceval.c | 391 ++++++++++++++---------------------- Python/compile.c | 31 ++- Python/import.c | 3 +- 17 files changed, 410 insertions(+), 303 deletions(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 125a80f9b02..6b2de49616c 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: + 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: - * TOP = ``None`` - * (TOP, SECOND) = (``WHY_{RETURN,CONTINUE}``), retval - * TOP = ``WHY_*``; no retval below it - * (TOP, SECOND, THIRD) = exc_info() + * SECOND = ``None`` + * (SECOND, THIRD) = (``WHY_{RETURN,CONTINUE}``), retval + * SECOND = ``WHY_*``; no retval below it + * (SECOND, THIRD, FOURTH) = exc_info() - Under them is EXIT, the context manager's :meth:`__exit__` bound method. + In the last case, ``TOS(SECOND, THIRD, FOURTH)`` is called, otherwise + ``TOS(None, None, None)``. In addition, TOS is removed from the stack. - In the last case, ``EXIT(TOP, SECOND, THIRD)`` is called, otherwise - ``EXIT(None, None, None)``. - - EXIT is removed from the stack, leaving the values above it in the same - order. In addition, if the stack represents an exception, *and* the function - call returns a 'true' value, this information is "zapped", to prevent - ``END_FINALLY`` from re-raising the exception. (But non-local gotos should - still be resumed.) + 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 a87651f4a25..98ecbcb1de4 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 b69de3f7f1c..42c36a6fa3d 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 6562b00595f..4e24df73ab4 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 d2afe8b6d5b..65ebd2ad523 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 7bdf1c9ad82..6da9877098b 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 dad8333bbd4..74be21ed7f3 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 5758abd3d89..e89b5f0a328 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 eac0b63162b..50e10ee6127 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 41b9413f419..906855403e4 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 89e219048b2..5f0070e25d0 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 410b668f1c0..4c8d368ea42 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 50ffc2285f7..0f18ac3d7b4 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 062e9061a3c..48fc2ca0eea 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 3ea90599bd6..bcb15e79640 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_EXCEPT: + { + 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; + break; + } + UNWIND_EXCEPT_HANDLER(b); + } + continue; + case POP_BLOCK: { PyTryBlock *b = PyFrame_BlockPop(f); - while (STACK_LEVEL() > b->b_level) { - v = POP(); - Py_DECREF(v); - } + 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 c8a4f854d5d..6017b2ca436 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 f1d81882bda..dadae2e9c72 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