[Python-Dev] setjmp/longjmp exception handling (was: More informative error messages)

Brett C. bac at OCF.Berkeley.EDU
Thu Oct 23 02:48:03 EDT 2003


Tim Peters wrote:
<SNIP>
> An internal PyExc_AttributeError isn't the same as a user-visible
> AttributeError, though -- a class instance isn't created unless and until
> PyErr_NormalizeException() gets called because the exception needs to be
> made user-visible.  If the latter never happens, setting and clearing
> exceptions internally is pretty cheap (a pointer to the global
> PyExc_AttributeError object is stuffed into the thread state).  OTOH, almost
> every call to a C API function has to test+branch for an error-return value,
> and I've often wondered whether a setjmp/longjmp-based hack might allow for
> cleaner and more optimizable code (hand-rolled "real exception handling").
> 

For some odd reason (maybe because of all the code touch-ups I did to 
Python/ast.c in the AST branch), the idea of doing exception handling in 
C using setjmp/longjmp really appealed to me.

So, being a programmer with an itch that needed to be scratched, I came 
up with a possible solution.  Even if the idea would work (which I don't 
know if it will just because I am not sure how thread-safe it is nor if 
the code will work; this was a mental exercise that doesn't compile 
because of casting of jmp_buf and such), I doubt it will ever be 
incorporated into Python just because it would require so much change to 
the C code.  But hey, who knows.

The basic idea is to keep a stack of jmp_buf points.  They are pushed on 
to the stack when a chunk of code wants to handle an exception.  The 
basic code is in the function try_except(); have an 'if' that calls a 
function that pushes on to the stack a new jmp_buf and register it in 
the conditional check.  When an exception is raised a function is called 
(makejmp()) that pops the stack and jumps to the jmp_buf that is popped. 
  Continue until the last item on the stack is reached which should be 
PyErr_NormalizeException() (I think that is the function that exposes an 
exception to Python code).

I have no clue how much performance benefit/loss there would be from 
this, but code would be cleaner since you wouldn't have to do constant 
``if (fxn() == NULL) return NULL;`` checks for raised exceptions.

But in case anyone cares, here is the *very* rough C code:


#include <stddef.h>
#include <setjmp.h>

/* Basically just a stack item */
typedef struct jmp_stack_item_struct {
     jmp_buf jmp_point;
     struct jmp_stack_item_struct *previous;
} jmp_stack_item;




/* Global stack of jmp points to exception handlers */
jmp_stack_item *jmp_stack;

void
try_except(void)
{
     jmp_stack = NULL;

     /* try: */
     if (!setjmp(allocjmp())) {
         ;
     }
     /* except: */
     else {
         ;
     }
}

/* returning jmp_buf like this makes gcc unhappy since it is an array */
jmp_buf
allocjmp(void)
{
     /* malloc jmp_buf and put on top of stack */
     /* return malloc'ed jmp_buf */
     jmp_stack_item *new_jmp = (jmp_stack_item *) 
malloc(sizeof(jmp_stack_item));

     if (!jmp_stack) {
         new_jmp->previous = NULL;
     }
     else {
         new_jmp->previous = jmp_stack;
     }
     jmp_stack = new_jmp;

     return new_jmp->jmp_point;
}

void
raise(void)
{
     /* Exception set; now call... */
     makejmp();
}

void
makejmp(void)
{
     jmp_stack_item *top_jmp = jmp_stack;
     jmp_buf jmp_to;

     if (!jmp_stack->previous)
         longjmp(jmp_stack->jmp_point, 1);
     else {
         memmove(jmp_to, top_jmp->jmp_point, sizeof(jmp_to));
         jmp_stack = top_jmp->previous;
         free(top_jmp);

         longjmp(jmp_to, 1);
     }
}




More information about the Python-Dev mailing list