/* Copyright 2010 Jan-Mark Wams. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY JAN-MARK WAMS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JAN-MARK WAMS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The views and conclusions contained in the software and * documentation are those of the authors and should not be * interpreted as representing official policies, either expressed or * implied, of Jan-Mark Wams. * ***************************************************************************** * prefix: _X * CPP implementation of Java like try/catch/finally. * - Keyword "ensure" replaces "finally" (like in Ruby). * - Using positive integer values for exceptions. * - Allow nesting and propagation. * - The try/catch construct is a single statement. * - Ensure part is not mandatory. * * The following "keywords", functions and variables defined are: * try, catch, ensure, retry, upagate(), raise(), excepno. * * * * * * * * * * * * * * W A R N I N G S * * * * * * * * * * * * * * * - Raise(), upagate() and retry necessitate volatile type * modification of autovariables (i.e., non static local variables and * arguments) in the scope of the try/catch statement if their content * is important. This is because variables might be kept in a register * and, thus, the longjmp() might restore it, so the content of any * auto, non-volatile variable is undefined if the compiler uses * register optimization. On gcc one might try the -traditional flag. * * - The following lines should behave the same (print ?!) however the * bottom one suffers the dangling else problem, and does not print !. * try raise(3); catch(3) { if(1) printf("?"); } ensure printf("!"); * try raise(3); catch(3) if(1) printf("?"); ensure printf("!"); * Therefor, always use curly brackets! * * - Also, don't do the obvious bad things like using 'return' in a * try, catch or ensure block. Or even more obvious using 'retry' in a * ensure block. There are many such examples, but they are inherent * to the try/catch statement, not this implementation. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Example code: * * try { if(!d) raise(3); e = 1/d; } * catch(3) { printf("Seen exception three.\n"); d = 1; retry; } * catch(*) { printf("Unknown exception %d raised!\n", excepno); upagate(5); } * ensure { if (e < 0) e *= -1; } * * The core technique used in this header file looks like a state * machine wrapped in a push-pop. Expanded by the pre-processor it resembles: * * PUSH() a new buffer on the stack. * while(1) * if (0 == (x = setjmp())) { // Try. * Try part. * x = ENSURE; * } * else if (SOME_EXCEPTION_NO == x) { // Catch * Handle SOME_EXCEPTION_NO; * x = ENSURE * } * : * : * else { // Ensure * Do the ensure part. * break; * } * POP() the buffer off the stack. * if(FINISH != x) raise(x) to PROPAGATE the exception. * * It is just that the PUSH() and POP(),PROPAGATE are put in a * prepending for-statment, and the break for the Ensure part is * replaced by changing the conditional in the "while(1)" because the * Ensure part is not mandatory. So the firt two likes look more like this: * * for(PUSH();;POP(),PROPAGATE()) * while(x-- > ENSURE) * * In the try part, or in a function that is called from the try part * (even in an other source file), an exception is raised by calling: * longjump(SOME_EXCEPTION_NO); * * There is one particular CPP construct that might not be clear at * first reading. This construction allows for a macro to accept a * star as well as a integer expression as an argument. Both catch(2) * and catch(*) will expand to a correct C expression. The construct * has two main parts. First a statement is needed to distinguish * between the two types of arguments, and second an expression that * evaluates to the value of the given integer and any value for * *. The first uses the CPP make-string (#) operator to check the * first character of the argument: (#x[0] == '*'). Note that * expression x will be quoted, so it will not be exectued. The second * uses the fact that * can mean a pointer dereferance and & can mean * both pointer-of and bitwise-and: (x & all-ones). It expands either * in (* & all-ones) == all-ones == -1 or in (2 & all-ones) == 2. Note * that & all-ones does not have any side effects, nomatter what the * integer expression. See the definition of catch() and upagate(). */ #ifndef __TRYCATCH_H__ #define __TRYCATCH_H__ 1 #include <stdlib.h> #include <stdio.h> #include <setjmp.h> /***************************************************************************** * Prefixed private defines. */ /* The heart of the try/catch/ensure construct is a state machine, * where the exceptions are states too. Non-positive values are used as * internal state and are disallowed as arguments to raise(). Since * (postfix) decrement is used to change the state from ENSURE to * FINISH, _X_FINISH should be one less than _X_ENSURE and unique like * all states. */ #define _X_TRY 0 /* Has to be 0, because of setjmp(). */ #define _X_RETRY -1 #define _X_ENSURE -2 #define _X_FINISH (_X_ENSURE - 1) /* To allow longjmp() and exit() in comma expressions, some compilers * need them to return an integer, since both do not reach the return, * it does not make sense, but this just causes less warnings. Note * the static. This means every file has it's own version. This can be * optimized by the compiler, but it saves a separate global * definition. */ static int _X___longjmp(jmp_buf jb, int i) { longjmp(jb, i); return 0; } #define longjmp _X___longjmp static int _X___exit(int n) { exit(n); return 0; } #define exit _X___exit /* Since try/catch/ensure statments can nest, some jump buffer stack * is needed. The simplest possible form suffices. */ typedef struct _X_s_node *_X_node_t; typedef struct _X_s_node { jmp_buf jb; _X_node_t next; } _X_s_node_t; /* A lot of global variables are needed and despite the prefix there * is name space pollution. So all global variables are put in one * global struct. */ struct _X_s_globs { _X_node_t root; /* Pointer to root node. */ _X_node_t tmp; /* Temp node pointer to enable push and pop. */ int no; /* The current exception number. */ int upa; /* The exception number to be upagated. */ int mo; /* Constand set to -1 (~0 actually). */ }; /* Unfortunately, since a stack is needed, as well as some current * values and temporary place holders, there has to be one file that * declares and instanciates them. Normally a special .c file is * created to put all the declarations. However for this one struct * that is kind of overkill. * Therefor, it is left to the programmer. To use the try/catch/ensure * construct one (and only one) source file needs to have the * declarations. One (and only one) source file should contain * DECLARE_TRY_GLOBALS;, it is not very elegant, but the alternative * is making a static declaration. However, that would limit matching * raise() and catch() pairs to the same file scope. * In an attempt to make the undeclared variable warning more * readable, the _X variable is re-defined by a descriptive (long) * name. This will break some very old compilers that allow only 8 * character variables, but not the ones that chop at 8 characters. */ #define _X Please_put_DECLARE_TRYCATCH_GLOBALS_in_one_source_file #define DECLARE_TRYCATCH_GLOBALS struct _X_s_globs _X = {NULL, NULL, 0, 0, ~0} extern struct _X_s_globs _X; /* Some form of giving up is needed. This should be integrated with * the error handling that the application uses. But as a stand in, it * is ok. Note it is an expression with a value (thanks to redifining * exit) and, thus, it can be used in a comma list. */ #define _X_exit(s) ( \ (_X.no ? \ fprintf(stderr,"%s:%d: Exception %d %s. (quiting)\n", \ __FILE__, __LINE__, _X.no, (s)): \ fprintf(stderr,"%s:%d: Error %s. (quiting)\n", \ __FILE__, __LINE__, (s))), \ exit(_X.no ? _X.no : -1) \ ) /* Clasic way to get a new node, by malloc(). */ #define _X_new_node(p) \ ((*(p)= malloc(sizeof(_X_s_node_t))) ? 1:(_X.no= 0,_X_exit("out of memory"))) /* Corresponging delete node by free(). */ #define _X_del_node(p) \ free(*(p)) /* Pushing is a matter of prepending to a linked list. */ #define _X_PUSH \ (_X.tmp=_X.root,_X_new_node(&_X.root),_X.root->next=_X.tmp) /* Poping is a matter of chopping off the head of the linked list. */ #define _X_POP \ (_X.tmp=_X.root->next,_X_del_node(&_X.root),_X.root=_X.tmp) /* Raising an exception is a call to longjmp(). An extra check is * needed to see if there is a setjmp() location to goto. If not, * there is no choice but to report the error and abort. Note there * is no check for negative values, so it can be used to raise the * internal (negative) exceptions. Below the API raise() definition * does check for this. */ #define _X_RAISE(x) \ (_X.root ? longjmp(_X.root->jb,(x)) : _X_exit("uncaught")) /* For cpp hacking, the for-loop is a god's gift. It allows code to be * exectuted after a (compound) statement to be put before the * statement it self. Since it also repeats this can sometimes * necessitate some extra measures, but with a goto like sigjump() * this can be ignored. Defined this way, _X_GO_ENSURE can be put * between an if-statement (and others) and the (compound) statement * to set the state to ENSURE after the statement. */ #define _X_GO_ENSURE \ for(;_X.no > _X_ENSURE; _X.no = _X_ENSURE) /* This macro is best read bottom line first. It is meant to be placed * just after the _X_POP statement. Translated the second line states: * If the current exception number (_X.no) is not _X_ENSURE, the * exception has not been handled by the current construct, so it is * raised again for the enclosing construct. Since it has to be placed * just after the _X_POP statement, simply raising the exception * number suffies. The first line sets the current exception number to * _X.upa if it is non zero (and zero's it out). If this happens, the * next line will propagate the exception to the enclosing try/catch * construct. This is used to implement upagate() (see below) that * propagates the current (or given) exception to the enclosing * construct, but not after a possible eventually part is exectued. */ #define _X_PROPAGATE \ ((_X.upa ? _X.no = _X.upa, _X.upa = 0 :0), \ (_X.no > _X_ENSURE ? _X_RAISE(_X.no) : 0)) /* This macro is just to declutter the definition of try. It call's * setjmp() with the top-of-stack jump buffer and returns true is it * is the first time (setjmp() returns 0) or if it was called with a * retry statement (setjmp() returns state _X_RETRY). */ #define _X_SETJMP_SAYS_TRY() \ (_X_TRY == (_X.no = setjmp(_X.root->jb)) || _X_RETRY == _X.no) /***************************************************************************** * Clear named defines. */ /* Try: * Use a for-loop to put a _X_PUSH and _X_POP (and _X_PROPAGATE) * around the whole construct and make sure (by using !_X.no) it does * not loop. Followed by a while() loop to go from state to state, * until FINISHEed. Note that in the while loop, ENSURE is decremented * into FINISH whether or not there is a ensure part. This guarantees * that the loop is exectuted with the state ENSURE exactly once. * Followed by an if-construct that does a setjmp() and checks if the * try (re-try) compound statement has to be executed. Followed by a * _X_GO_ENSURE. */ #define try \ for(_X_PUSH,_X.no = _X_TRY; !_X.no; _X_POP,_X_PROPAGATE) \ while((_X_ENSURE == _X.no ? _X.no-- : _X.no) != _X_FINISH) \ if(_X.no != _X_FINISH && _X_SETJMP_SAYS_TRY()) \ _X_GO_ENSURE /* Catch: * An else-if block that checks to see if the ensuing compound has to * be executed. First (line) negative exception numbers never match, * they are special for "internal" usage. Second (line) a '*' or a * matching exception argument causes the ensuing compound statement * to be executed, followed by a _X_GO_ENSURE. */ #define catch(x) \ else if (_X.no > 0 && \ ((#x[0] == '*') || ((x & _X.mo) == _X.no))) \ _X_GO_ENSURE /* Ensure: * The ensure is just a simple else, it can be put at the end of the * if(){} [ else if() {} ] ... construction to execute some code just * before the POP. Since it has no if-part, it will always be executed * when provided after the exception has been dealt with or ignored * (in which case it will be propagated by _X_PROPAGATE). */ #define ensure \ else /* Excepno: * Analogous to the global variable errno, the excepno "variable" is * defined to be the current exception number. However a negative * values are bumped to 0 and it can not be used as a lval. */ #define excepno \ (_X.no > 0 ? _X.no : 0) /* Longjmp macro's: raise(), upagate() and retry might necessitate * volatile autovars. See warning on top of file. */ /* Raise: * Raise is like the internal _X_RAISE() with a additional check for * negative arguments. The _X_RAISE() basically is a longjump using * the current (top of stack) buffer. Note that x is assigned to * _X.no to prevent side effect from happening twice. */ #define raise(x) \ ((_X.no = (x)) > 0 ? _X_RAISE(_X.no) :_X_exit("illegal non-positive value")) /* Upagate: * Propagating up to the enclosing try/catch construct basically means * setting a special variable to either the current exception number * or the number provided to the macro and signalling that there are * no exceptions left to process by jumping to the first if-statment * with a _X_ENSURE. The _X_PROPAGATE macro will check (after the _X_POP) * if this upagate variable _X.upa is set, and if so will call * _X_RAISE() on the enclosing try/catch construction. */ #define upagate(x) \ ((_X.upa = #x[0] != '*' ? (x & _X.mo) : _X.no), _X_RAISE(_X_FINISH)) /* Retry: * Retry should be as simple as longjmp(buf, 0), but not all systems * will propagate the '0', so a standin is used and a double check is * done in the try macro. A simple _X_RAISE(_X_RETRY) would do the * trick nicely, however to enhance the error messages the long jump * is used with a check for the top of the stack. */ #define retry \ (_X.root ? longjmp(_X.root->jb,(_X_RETRY)) : _X_exit("uncaught retry")) /* That's all fokes! */ #endif /* __TRYCATCH_H__ */