Advertisement
cfrantz

Exceptions in C

Apr 28th, 2015
225
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 8.12 KB | None | 0 0
  1. #ifndef EXCEPTION_H
  2. #define EXCEPTION_H
  3. /*
  4.  * Simple try/except/finally implementation for C
  5.  *
  6.  * Inspired by http://www.adomas.org/excc/
  7.  * Additional preprocessor abuse from http://www.chiark.greenend.org.uk/~sgtatham/mp/
  8.  *
  9.  * These macros aren't *that* crazy, but if you're unfamiliar with this
  10.  * type of macro abuse, they can be scary.
  11.  *
  12.  * The for loops are abused for creating and shadowing variables as well
  13.  * as for providing a construction that can consume the user's statement.
  14.  * The also (and importantly) provide a way to execute our code before and
  15.  * after execution of the user's statement.
  16.  *
  17.  * After decoding the macro abuse, this boils down to using setjmp/longjmp
  18.  * in a rather conventional way to provide a stack of contexts to which
  19.  * we can do non-local gotos (e.g. raise the exception) and then a loop
  20.  * variable which determines if we are in the try/except phase or the finally
  21.  * phase of the try/except/finally block.
  22.  *
  23.  * To facilitate execution of finally blocks in the event of return, I am
  24.  * using a stack of computed gotos which record the address of the
  25.  * finally block an allow a return macro to execute all nested finally blocks
  26.  * during an early exit.  Note: recording the address of the finally
  27.  * block is particularly ingenious and nastly:  I use GCC's statement
  28.  * expression to label the expression-3 portion of the try's controlling
  29.  * for-loop.  This will decrement the control varible to 1, forcing us
  30.  * into the finally phase of the block.  In the case where there is no
  31.  * finally clause, we simply fall off the bottom of the try/except clause.
  32.  * Using this trick allows me to avoid needing a label specifier as an
  33.  * argument to the try and finally macros.
  34.  *
  35.  * Similarly, the stack of computed gotos is used to get rid of
  36.  * reraise in favor of a raise function that can figure out if it is
  37.  * inside of a except block.  This is accomplished by careful shadowing
  38.  * of the global const int __exbody which has a value of zero.
  39.  *
  40.  * Every except clause shadows __exbody with a value of one.  To allow
  41.  * nesting try blocks inside of except blocks, every try block also
  42.  * shadows __exbody with a value of zero.  The raise() macro examines
  43.  * the value of __exbody and either uses goto or longjmp depending on
  44.  * the value.  GCC can figure out the const value of __exbody and simply
  45.  * eliminate the unused code path.
  46.  */
  47. #include <npc/psetjmp.h>
  48.  
  49. //#define THREAD __thread
  50. #define THREAD
  51.  
  52. typedef struct {
  53.     jmp_buf jb;
  54. //    BaseException *ex;
  55. } exc_stack;
  56.  
  57. extern const int __body, __exbody;
  58. extern int __postfinally;
  59. extern BaseException *__ex;
  60. extern THREAD int __exc_depth;
  61. extern THREAD exc_stack *__exc_stack;
  62.  
  63. extern exc_stack *exc_stack_new(int depth);
  64. extern void exc_stack_init(int depth);
  65.  
  66. #define EXPASTE1(x, y) x ## y
  67. #define EXPASTE(x, y) EXPASTE1(x, y)
  68.  
  69. #define INIT_EXCEPTIONS() \
  70.     const int __body = 0, __exbody = 0; \
  71.     int __postfinally = 0; \
  72.     BaseException *__ex = 0; \
  73.     THREAD int __exc_depth; \
  74.     THREAD exc_stack *__exc_stack;
  75.  
  76.  
  77. #define NOEX    0
  78. #define ANYEX   0
  79.  
  80. // Helpers for managing function return from within try/except/finally
  81. // blocks.rt
  82. #define __exitinit(n) \
  83.                              any_t __retval = (any_t)-1UL; \
  84.                              int __retstk = 0; \
  85.                              int __retlbl[(n)+1] = { 0, }
  86.  
  87. #define __rettype(type) \
  88.     __builtin_choose_expr(__builtin_types_compatible_p(type, Boolean), __retval.b, \
  89.     __builtin_choose_expr(__builtin_types_compatible_p(type, Int), __retval.i, \
  90.     __builtin_choose_expr(__builtin_types_compatible_p(type, Float), __retval.f, \
  91.     __builtin_choose_expr(__builtin_types_compatible_p(type, any_t), __retval, \
  92.     __builtin_choose_expr(__builtin_types_compatible_p(type, uint64_t), __retval.raw, \
  93.             __retval.p)))))
  94.  
  95. #define __exitfini(type)     __exitproc: return (type)__rettype(type)
  96.  
  97.  
  98. #define __exitpush(labelid)  ({__retlbl[++__retstk] = EXPASTE(&&__exitproc, labelid) - &&__exitproc; 0;})
  99. #define __exitpop()          (__retstk--)
  100.  
  101. #define __exit_tgt           *(&&__exitproc + __retlbl[__retstk])
  102. #define __exitchk()          ({ if (__retval.raw != -1UL) goto __exit_tgt; })
  103. #define RETURN(val)          ({__rettype(typeof(val))=val; goto __exit_tgt;})
  104. #define BREAK                if (__body) {__postfinally=1; continue;} else break
  105. #define CONTINUE             if (__body) {__postfinally=2; continue;} else continue
  106.  
  107. // Helpers for managing the jmp_buf stack
  108. #define __exc_push_old()         ({ \
  109.         exc_stack *es = &__exc_stack[++__exc_depth]; \
  110.         es->ex = NULL; \
  111.         setjmp(es->jb); \
  112.         es->ex; \
  113.     })
  114.  
  115. #define __exc_push()         ({ \
  116.         exc_stack *es = &__exc_stack[++__exc_depth]; \
  117.         (BaseException*)psetjmp(es->jb, NULL); \
  118.     })
  119.  
  120. #define __exc_pop()          (__exc_depth--)
  121.  
  122. // Raise an exception
  123. // Use _raise in functions that do not have __exit{init,fini}
  124. #define _raise_old(exc) ({ \
  125.         exc_stack *es = &__exc_stack[__exc_pop()]; \
  126.         es->ex = (BaseException*)exc; \
  127.         longjmp(es->jb, 1); \
  128.     })
  129. #define _raise(exc) ({ \
  130.         exc_stack *es = &__exc_stack[__exc_pop()]; \
  131.         plongjmp(es->jb, exc); \
  132.     })
  133.  
  134. #define raise(exc) ({ \
  135.         BaseException* $ex = (BaseException*)(exc); \
  136.         if ($ex) { \
  137.             if (__exbody) { \
  138.                 __ex=$ex; goto __exit_tgt; \
  139.             } else { \
  140.                 _raise($ex); \
  141.             } \
  142.         } \
  143.     })
  144.  
  145. // try/except/finally: these macros are really nasty
  146. //
  147. // The first if condition is always false, but computes where to go
  148. //     if we need to short circuit to the finally clause. It contains
  149. //     the post-finally break and continue clauses used when
  150. //     try/except/finally is used inside of other loops.  Lastly,
  151. //     it isolates the local namespace of the following
  152. //     statements and the except and finally clauses.
  153. // The first for simply declares/shadows and initializes our control variables.
  154. // The second for give us a local __ex to hold the current exception.
  155. // The next if checks for post-finally cleanup
  156. // The following else-if sets the longjmp target in the event of an exception.
  157. // The third for executes the user supplied body once and then pops the
  158. //     longjmp target.
  159. #define __try(_break, _continue) \
  160.     if (__exitpush(__LINE__)) { \
  161.         EXPASTE(__break, __LINE__): _break; \
  162.         EXPASTE(__continue, __LINE__): _continue; \
  163.     } else \
  164.     for(int __tryctl=3,__body=0,__exbody=0,__postfinally=0; __tryctl;) \
  165.     for(BaseException *__ex=NOEX; __tryctl; ({EXPASTE(__exitproc, __LINE__): __tryctl--;})) \
  166.         if (__tryctl==1) { \
  167.             if (__postfinally == 1) goto EXPASTE(__break, __LINE__); \
  168.             if (__postfinally == 2) goto EXPASTE(__continue, __LINE__); \
  169.         } else if (__tryctl==3 && (__ex=__exc_push()) == NOEX) \
  170.             for(__body=1; __body; __exc_pop(), __body=0)
  171.  
  172. // tryloop is just like try, but meant to be used inside of loops where the
  173. // user supplied blocks of try, except or finally contain a break or continue
  174. // statement.
  175. #define try     __try(0, 0)
  176. #define tryloop __try(break, continue)
  177.  
  178. // The else clause chains off of the second if in the try clause
  179. //     and checks if we have a matching exception.
  180. // The for loop executes the user supplied body exactly once and then clears
  181. //     the exception.  It also shadows __exbody so the raise macro will have
  182. //     the correct behavior within an except clause.
  183. #define except(cond) \
  184.         else if (__tryctl==3 && (cond == ANYEX || _isinstance1(__ex, cond))) \
  185.             for(int __body=1, __exbody=1; __body; __ex=NOEX, __body=0)
  186.  
  187. // The else clause chains off of the second if in the try clause
  188. //     and checks if we're in the finally phase of the try/except/finally
  189. //     construct.
  190. // The for loop pops the exit goto context, executes the user supplied body
  191. //     exactly once, then checks if we need to goto the exit or re-raise any
  192. //     unhandled exception.
  193. #define finally \
  194.         else if (__tryctl==2) \
  195.             for(__body=1, __exitpop(); __body; __exitchk(), raise(__ex), __body=0)
  196.  
  197. #endif
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement