/* 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__ */