Skip to main content

Python/ceval.c (part 87)

Source:

cpython 3.14 @ ab2d84fe1023/Python/ceval.c

This annotation covers the exception handling opcodes. See python_ceval86_detail for call specializations.

Map

LinesSymbolRole
1-80PUSH_EXC_INFOPush the current exception onto the stack
81-160POP_EXCEPTRestore the previous active exception
161-240RERAISERe-raise the current or a saved exception
241-340CLEANUP_THROWClean up after a throw() into a generator
341-500Exception dispatchHow the eval loop finds the handler

Reading

PUSH_EXC_INFO

// CPython: Python/ceval.c:4780 PUSH_EXC_INFO
inst(PUSH_EXC_INFO, (new_exc -- prev_exc, new_exc)) {
/* Save the currently active exception and set new_exc as active */
_PyErr_StackItem *exc_info = tstate->exc_info;
prev_exc = exc_info->exc_value; /* push old one */
exc_info->exc_value = Py_NewRef(new_exc); /* set new active */
}

CPython maintains a per-thread exception stack (exc_info). PUSH_EXC_INFO saves the old active exception (which will be restored by POP_EXCEPT when the handler block exits) and installs the new exception as the active one. This enables except clauses to call sys.exc_info() and get the correct exception.

POP_EXCEPT

// CPython: Python/ceval.c:4820 POP_EXCEPT
inst(POP_EXCEPT, (exc_value --)) {
_PyErr_StackItem *exc_info = tstate->exc_info;
PyObject *value = exc_info->exc_value;
exc_info->exc_value = exc_value; /* restore saved exception */
Py_XDECREF(value); /* drop the one that just handled */
}

POP_EXCEPT is emitted at the end of every except block. It swaps back the previously saved exception, undoing what PUSH_EXC_INFO did.

RERAISE

// CPython: Python/ceval.c:4860 RERAISE
inst(RERAISE, (values[oparg], exc --)) {
/* oparg == 1: restore lasti from saved frame info */
if (oparg) {
/* re-raise original from 'raise' without args inside except */
PyObject *lasti = values[0];
frame->f_lasti = PyLong_AsLong(lasti);
}
assert(PyExceptionInstance_Check(exc));
_PyErr_SetRaisedException(tstate, Py_NewRef(exc));
goto exception_unwind;
}

raise without arguments inside an except clause compiles to RERAISE 1. RERAISE 0 is used in finally blocks to propagate an active exception through the block.

Exception dispatch

// CPython: Python/ceval.c:5100 exception_unwind
exception_unwind:
{
/* Walk the exception table to find a handler */
_PyInterpreterFrame *frame = tstate->current_frame;
int handler = _PyCode_FindHandlerTarget(frame);
if (handler < 0) {
/* No handler in this frame: unwind and return */
...
goto exit_unwind;
}
/* Jump to the handler */
JUMPBY(handler - (int)(frame->instr_ptr - _PyCode_CODE(frame->f_code)));
}

When an exception occurs, the eval loop jumps to exception_unwind. It consults the co_exceptiontable (a compact binary table) to find the handler offset. If no handler exists in the current frame, it unwinds to the caller.

gopy notes

PUSH_EXC_INFO and POP_EXCEPT are in vm/eval_unwind.go. The exception stack is vm.Thread.ExcInfo. RERAISE calls vm.SetException. Exception dispatch reads objects.CodeObject.ExceptionTable using vm.FindHandlerTarget.