Skip to content

Commit eec3d71

Browse files
committed
#3021: Antoine Pitrou's Lexical exception handlers
1 parent e8465f2 commit eec3d71

File tree

17 files changed

+410
-303
lines changed

17 files changed

+410
-303
lines changed

Doc/library/dis.rst

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,14 @@ Miscellaneous opcodes.
397397
denoting nested loops, try statements, and such.
398398

399399

400+
.. opcode:: POP_EXCEPT ()
401+
402+
Removes one block from the block stack. The popped block must be an exception
403+
handler block, as implicitly created when entering an except handler.
404+
In addition to popping extraneous values from the frame stack, the
405+
last three popped values are used to restore the exception state.
406+
407+
400408
.. opcode:: END_FINALLY ()
401409

402410
Terminates a :keyword:`finally` clause. The interpreter recalls whether the
@@ -412,24 +420,22 @@ Miscellaneous opcodes.
412420

413421
.. opcode:: WITH_CLEANUP ()
414422

415-
Cleans up the stack when a :keyword:`with` statement block exits. On top of
416-
the stack are 1--3 values indicating how/why the finally clause was entered:
417-
418-
* TOP = ``None``
419-
* (TOP, SECOND) = (``WHY_{RETURN,CONTINUE}``), retval
420-
* TOP = ``WHY_*``; no retval below it
421-
* (TOP, SECOND, THIRD) = exc_info()
423+
Cleans up the stack when a :keyword:`with` statement block exits. TOS is
424+
the context manager's :meth:`__exit__` bound method. Below TOS are 1--3
425+
values indicating how/why the finally clause was entered:
422426

423-
Under them is EXIT, the context manager's :meth:`__exit__` bound method.
427+
* SECOND = ``None``
428+
* (SECOND, THIRD) = (``WHY_{RETURN,CONTINUE}``), retval
429+
* SECOND = ``WHY_*``; no retval below it
430+
* (SECOND, THIRD, FOURTH) = exc_info()
424431

425-
In the last case, ``EXIT(TOP, SECOND, THIRD)`` is called, otherwise
426-
``EXIT(None, None, None)``.
432+
In the last case, ``TOS(SECOND, THIRD, FOURTH)`` is called, otherwise
433+
``TOS(None, None, None)``. In addition, TOS is removed from the stack.
427434

428-
EXIT is removed from the stack, leaving the values above it in the same
429-
order. In addition, if the stack represents an exception, *and* the function
430-
call returns a 'true' value, this information is "zapped", to prevent
431-
``END_FINALLY`` from re-raising the exception. (But non-local gotos should
432-
still be resumed.)
435+
If the stack represents an exception, *and* the function call returns
436+
a 'true' value, this information is "zapped" and replaced with a single
437+
``WHY_SILENCED`` to prevent ``END_FINALLY`` from re-raising the exception.
438+
(But non-local gotos will still be resumed.)
433439

434440
.. XXX explain the WHY stuff!
435441

Doc/library/inspect.rst

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,6 @@ attributes:
9494
| | f_code | code object being |
9595
| | | executed in this frame |
9696
+-----------+-----------------+---------------------------+
97-
| | f_exc_traceback | traceback if raised in |
98-
| | | this frame, or ``None`` |
99-
+-----------+-----------------+---------------------------+
100-
| | f_exc_type | exception type if raised |
101-
| | | in this frame, or |
102-
| | | ``None`` |
103-
+-----------+-----------------+---------------------------+
104-
| | f_exc_value | exception value if raised |
105-
| | | in this frame, or |
106-
| | | ``None`` |
107-
+-----------+-----------------+---------------------------+
10897
| | f_globals | global namespace seen by |
10998
| | | this frame |
11099
+-----------+-----------------+---------------------------+

Doc/library/sys.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ always available.
136136
frame is not handling an exception, the information is taken from the calling
137137
stack frame, or its caller, and so on until a stack frame is found that is
138138
handling an exception. Here, "handling an exception" is defined as "executing
139-
or having executed an except clause." For any stack frame, only information
140-
about the most recently handled exception is accessible.
139+
an except clause." For any stack frame, only information about the exception
140+
being currently handled is accessible.
141141

142142
.. index:: object: traceback
143143

Doc/reference/datamodel.rst

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -875,19 +875,14 @@ Internal types
875875

876876
.. index::
877877
single: f_trace (frame attribute)
878-
single: f_exc_type (frame attribute)
879-
single: f_exc_value (frame attribute)
880-
single: f_exc_traceback (frame attribute)
881878
single: f_lineno (frame attribute)
882879

883880
Special writable attributes: :attr:`f_trace`, if not ``None``, is a function
884881
called at the start of each source code line (this is used by the debugger);
885-
:attr:`f_exc_type`, :attr:`f_exc_value`, :attr:`f_exc_traceback` represent the
886-
last exception raised in the parent frame provided another exception was ever
887-
raised in the current frame (in all other cases they are None); :attr:`f_lineno`
888-
is the current line number of the frame --- writing to this from within a trace
889-
function jumps to the given line (only for the bottom-most frame). A debugger
890-
can implement a Jump command (aka Set Next Statement) by writing to f_lineno.
882+
:attr:`f_lineno` is the current line number of the frame --- writing to this
883+
from within a trace function jumps to the given line (only for the bottom-most
884+
frame). A debugger can implement a Jump command (aka Set Next Statement)
885+
by writing to f_lineno.
891886

892887
Traceback objects
893888
.. index::

Include/frameobject.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ typedef struct _frame {
2727
PyObject **f_stacktop;
2828
PyObject *f_trace; /* Trace function */
2929

30-
/* If an exception is raised in this frame, the next three are used to
31-
* record the exception info (if any) originally in the thread state. See
32-
* comments before set_exc_info() -- it's not obvious.
33-
* Invariant: if _type is NULL, then so are _value and _traceback.
34-
* Desired invariant: all three are NULL, or all three are non-NULL. That
35-
* one isn't currently true, but "should be".
36-
*/
30+
/* In a generator, we need to be able to swap between the exception
31+
state inside the generator and the exception state of the calling
32+
frame (which shouldn't be impacted when the generator "yields"
33+
from an except handler).
34+
These three fields exist exactly for that, and are unused for
35+
non-generator frames. See the SAVE_EXC_STATE and SWAP_EXC_STATE
36+
macros in ceval.c for details of their use. */
3737
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
3838

3939
PyThreadState *f_tstate;

Include/opcode.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ extern "C" {
7070
#define YIELD_VALUE 86
7171
#define POP_BLOCK 87
7272
#define END_FINALLY 88
73+
#define POP_EXCEPT 89
7374

7475
#define HAVE_ARGUMENT 90 /* Opcodes from here have an argument: */
7576

@@ -133,6 +134,13 @@ extern "C" {
133134
#define EXTENDED_ARG 143
134135

135136

137+
/* EXCEPT_HANDLER is a special, implicit block type which is created when
138+
entering an except handler. It is not an opcode but we define it here
139+
as we want it to be available to both frameobject.c and ceval.c, while
140+
remaining private.*/
141+
#define EXCEPT_HANDLER 257
142+
143+
136144
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,
137145
PyCmp_IN, PyCmp_NOT_IN, PyCmp_IS, PyCmp_IS_NOT, PyCmp_EXC_MATCH, PyCmp_BAD};
138146

Lib/doctest.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,10 +1242,9 @@ def __run(self, test, compileflags, out):
12421242

12431243
# The example raised an exception: check if it was expected.
12441244
else:
1245-
exc_info = sys.exc_info()
1246-
exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
1245+
exc_msg = traceback.format_exception_only(*exception[:2])[-1]
12471246
if not quiet:
1248-
got += _exception_traceback(exc_info)
1247+
got += _exception_traceback(exception)
12491248

12501249
# If `example.exc_msg` is None, then we weren't expecting
12511250
# an exception.
@@ -1275,7 +1274,7 @@ def __run(self, test, compileflags, out):
12751274
elif outcome is BOOM:
12761275
if not quiet:
12771276
self.report_unexpected_exception(out, test, example,
1278-
exc_info)
1277+
exception)
12791278
failures += 1
12801279
else:
12811280
assert False, ("unknown outcome", outcome)

Lib/inspect.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,6 @@ def isframe(object):
197197
f_back next outer frame object (this frame's caller)
198198
f_builtins built-in namespace seen by this frame
199199
f_code code object being executed in this frame
200-
f_exc_traceback traceback if raised in this frame, or None
201-
f_exc_type exception type if raised in this frame, or None
202-
f_exc_value exception value if raised in this frame, or None
203200
f_globals global namespace seen by this frame
204201
f_lasti index of last attempted instruction in bytecode
205202
f_lineno current line number in Python source code

Lib/opcode.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def jabs_op(name, op):
105105
def_op('YIELD_VALUE', 86)
106106
def_op('POP_BLOCK', 87)
107107
def_op('END_FINALLY', 88)
108+
def_op('POP_EXCEPT', 89)
108109

109110
HAVE_ARGUMENT = 90 # Opcodes from here have an argument:
110111

Lib/test/test_exceptions.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ def inner_raising_func():
427427
local_ref = obj
428428
raise MyException(obj)
429429

430+
# Qualified "except" with "as"
430431
obj = MyObj()
431432
wr = weakref.ref(obj)
432433
try:
@@ -437,6 +438,113 @@ def inner_raising_func():
437438
obj = wr()
438439
self.failUnless(obj is None, "%s" % obj)
439440

441+
# Qualified "except" without "as"
442+
obj = MyObj()
443+
wr = weakref.ref(obj)
444+
try:
445+
inner_raising_func()
446+
except MyException:
447+
pass
448+
obj = None
449+
obj = wr()
450+
self.failUnless(obj is None, "%s" % obj)
451+
452+
# Bare "except"
453+
obj = MyObj()
454+
wr = weakref.ref(obj)
455+
try:
456+
inner_raising_func()
457+
except:
458+
pass
459+
obj = None
460+
obj = wr()
461+
self.failUnless(obj is None, "%s" % obj)
462+
463+
# "except" with premature block leave
464+
obj = MyObj()
465+
wr = weakref.ref(obj)
466+
for i in [0]:
467+
try:
468+
inner_raising_func()
469+
except:
470+
break
471+
obj = None
472+
obj = wr()
473+
self.failUnless(obj is None, "%s" % obj)
474+
475+
# "except" block raising another exception
476+
obj = MyObj()
477+
wr = weakref.ref(obj)
478+
try:
479+
try:
480+
inner_raising_func()
481+
except:
482+
raise KeyError
483+
except KeyError:
484+
obj = None
485+
obj = wr()
486+
self.failUnless(obj is None, "%s" % obj)
487+
488+
# Some complicated construct
489+
obj = MyObj()
490+
wr = weakref.ref(obj)
491+
try:
492+
inner_raising_func()
493+
except MyException:
494+
try:
495+
try:
496+
raise
497+
finally:
498+
raise
499+
except MyException:
500+
pass
501+
obj = None
502+
obj = wr()
503+
self.failUnless(obj is None, "%s" % obj)
504+
505+
# Inside an exception-silencing "with" block
506+
class Context:
507+
def __enter__(self):
508+
return self
509+
def __exit__ (self, exc_type, exc_value, exc_tb):
510+
return True
511+
obj = MyObj()
512+
wr = weakref.ref(obj)
513+
with Context():
514+
inner_raising_func()
515+
obj = None
516+
obj = wr()
517+
self.failUnless(obj is None, "%s" % obj)
518+
519+
def test_generator_leaking(self):
520+
# Test that generator exception state doesn't leak into the calling
521+
# frame
522+
def yield_raise():
523+
try:
524+
raise KeyError("caught")
525+
except KeyError:
526+
yield sys.exc_info()[0]
527+
yield sys.exc_info()[0]
528+
yield sys.exc_info()[0]
529+
g = yield_raise()
530+
self.assertEquals(next(g), KeyError)
531+
self.assertEquals(sys.exc_info()[0], None)
532+
self.assertEquals(next(g), KeyError)
533+
self.assertEquals(sys.exc_info()[0], None)
534+
self.assertEquals(next(g), None)
535+
536+
# Same test, but inside an exception handler
537+
try:
538+
raise TypeError("foo")
539+
except TypeError:
540+
g = yield_raise()
541+
self.assertEquals(next(g), KeyError)
542+
self.assertEquals(sys.exc_info()[0], TypeError)
543+
self.assertEquals(next(g), KeyError)
544+
self.assertEquals(sys.exc_info()[0], TypeError)
545+
self.assertEquals(next(g), TypeError)
546+
del g
547+
self.assertEquals(sys.exc_info()[0], TypeError)
440548

441549
def test_main():
442550
run_unittest(ExceptionTests)

0 commit comments

Comments
 (0)