465 lines
20 KiB
C++
465 lines
20 KiB
C++
/* try.h -- try / catch / throw exception handling for C99
|
|
Copyright (C) 2013, 2015 Mark Adler
|
|
Version 1.2 19 January 2015
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the author be held liable for any damages
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
|
|
Mark Adler madler@alumni.caltech.edu
|
|
*/
|
|
|
|
/*
|
|
Version History
|
|
1.0 7 Jan 2013 - First version
|
|
1.1 2 Nov 2013 - Use variadic macros and functions instead of partial
|
|
structure assignment, allowing arbitrary arguments
|
|
to printf()
|
|
1.2 19 Jan 2015 - Obey setjmp() invocation limits from C standard
|
|
*/
|
|
|
|
/* To use, include try.h in all source files that use these operations, and
|
|
compile and link try.c. If pthread threads are used, then there must be an
|
|
#include <pthread.h> in try.h to make the exception handling thread-safe.
|
|
(Uncomment the include below.) If threads other than pthread are being
|
|
used, then try.h and try.c must be modified to use that environment's
|
|
thread-local storage for the try_stack_ pointer. try.h and try.c assume
|
|
that the compiler and library conform to the C99 standard, at least with
|
|
respect to the use of variadic macro and function arguments. */
|
|
|
|
/*
|
|
try.h provides a try / catch / throw exception handler, which allows
|
|
catching exceptions across any number of levels of function calls. try
|
|
blocks can be nested as desired, with a throw going to the end of the
|
|
innermost enclosing try, passing the thrown information to the associated
|
|
catch block. A global try stack is used, to avoid having to pass exception
|
|
handler information through all of the functions down to the invocations of
|
|
throw. The try stack is thread-unique if requested by uncommenting the
|
|
pthread.h include below. In addition to the macros try, catch, and throw,
|
|
the macros always, retry, punt, and drop, and the type ball_t are created.
|
|
All other symbols are of the form try_*_ or TRY_*_, where the final
|
|
underscore should avoid conflicts with application symbols. The eight
|
|
exposed names can be changed easily in #defines below.
|
|
|
|
A try block encloses code that may throw an exception with the throw()
|
|
macro, either directly in the try block or in any function called directly
|
|
or indirectly from the try block. throw() must have at least one argument,
|
|
which is an integer. The try block is followed by a catch block whose code
|
|
will be executed when throw() is called with a non-zero first argument. If
|
|
the first argument of throw() is zero, then execution continues after the
|
|
catch block. If the try block completes normally, with no throw() being
|
|
called, then execution continues normally after the catch block.
|
|
|
|
There can be only one catch block. catch has one argument which must be a
|
|
ball_t type variable declared in the current function or block containing
|
|
the try and catch. That variable is loaded with the information sent by the
|
|
throw() for use in the catch block.
|
|
|
|
throw() may optionally include more information that is passed to the catch
|
|
block in the ball_t structure. throw() can have one or more arguments,
|
|
where the first (possibly only) argument is an integer code. The second
|
|
argument can be a pointer, which will be replaced by NULL in the ball_t
|
|
structure if not provided. The implementation of throw() in try.c assumes
|
|
that if the second argument is present and is not NULL, that it is a string.
|
|
If that string has any percent (%) signs in it, then throw() will run that
|
|
string through vsnprintf() with any other arguments provided after the
|
|
string in the throw() invocation, and save the resulting formatted string in
|
|
the ball_t structure. Information on whether or not the string was
|
|
allocated is also maintained in the ball_t structure.
|
|
|
|
throw() in try.c can be modified to not assume that the second argument is a
|
|
string. For example, an application may want to assume instead that the
|
|
second argument is a pointer to a set of information for use in the catch
|
|
block.
|
|
|
|
The catch block may conditionally do a punt(), where the argument of punt()
|
|
is the argument of catch. This passes the exception on to the next
|
|
enclosing try/catch handler.
|
|
|
|
If a catch block does not always end with a punt(), it should contain a
|
|
drop(), where the argument of drop() is the argument of catch. This frees
|
|
the allocated string made if vsnprintf() was used by throw() to generate the
|
|
string. If printf() format strings are never used, then drop() is not
|
|
required.
|
|
|
|
An always block may be placed between the try and catch block. The
|
|
statements in that block will be executed regardless of whether or not the
|
|
try block completed normally. As indicated by the ordering, the always
|
|
block will be executed before the catch block. This block is not named
|
|
"finally", since it is different from the finally block in other languages
|
|
which is executed after the catch block.
|
|
|
|
A naked break or continue in a try or always block will go directly to the
|
|
end of that block.
|
|
|
|
A retry from the try block or from any function called from the try block at
|
|
any level of nesting will restart the try block from the beginning.
|
|
|
|
try is thread-safe when compiled with pthread.h. A throw() in a thread can
|
|
only be caught in the same thread. If a throw() is attempted from a thread
|
|
without an enclosing try in that thread, even if in another thread there is
|
|
a try around the pthread_create() that spawned this thread, then the throw
|
|
will fail on an assert. Each thread has its own thread-unique try stack,
|
|
which starts off empty.
|
|
|
|
If an intermediate function does not have a need for operations in a catch
|
|
block other than punt, and does not need an always block, then that function
|
|
does not need a try block. "try { block } catch (err) { punt(err); }" is
|
|
the same as just "block". More precisely, it's equivalent to "do { block }
|
|
while (0);", which replicates the behavior of a naked break or continue in a
|
|
block when it follows try. throw() can be used from a function that has no
|
|
try. All that is necessary is that there is a try somewhere up the function
|
|
chain that called the current function in the current thread.
|
|
|
|
There must not be a return in any try block, nor a goto in any try block
|
|
that leaves that block. The always block does not catch a return from the
|
|
try block. There is no check or protection for an improper use of return or
|
|
goto. It is up to the user to assure that this doesn't happen. If it does
|
|
happen, then the reference to the current try block is left on the try
|
|
stack, and the next throw which is supposed to go to an enclosing try would
|
|
instead go to this try, possibly after the enclosing function has returned.
|
|
Mayhem will then ensue. This may be caught by the longjmp() implementation,
|
|
which would report "longjmp botch" and then abort.
|
|
|
|
Any automatic storage variables that are modified in the try block and used
|
|
in the catch or always block must be declared volatile. Otherwise their
|
|
value in the catch or always block is indeterminate.
|
|
|
|
Any statements between try and always, between try and catch if there is no
|
|
always, or between always and catch are part of those respective try or
|
|
always blocks. Use of { } to enclose those blocks is optional, but { }
|
|
should be used anyway for clarity, style, and to inform smart source editors
|
|
that the enclosed code is to be indented. Enclosing the catch block with {
|
|
} is not optional if there is more than one statement in the block.
|
|
However, even if there is just one statement in the catch block, it should
|
|
be enclosed in { } anyway for style and editing convenience.
|
|
|
|
The contents of the ball_t structure after the first element (int code) can
|
|
be customized for the application. If ball_t is customized, then the code
|
|
in try.c should be updated accordingly. If there is no memory allocation in
|
|
throw(), then drop() can be eliminated.
|
|
|
|
Example usage:
|
|
|
|
ball_t err;
|
|
volatile char *temp = NULL;
|
|
try {
|
|
... do something ...
|
|
if (ret == -1)
|
|
throw(1, "bad thing happened to %s\n", me);
|
|
temp = malloc(sizeof(me) + 1);
|
|
if (temp == NULL)
|
|
throw(2, "out of memory");
|
|
... do more ...
|
|
if (ret == -1)
|
|
throw(3, "worse thing happened to %s\n", temp);
|
|
... some more code ...
|
|
}
|
|
always {
|
|
free(temp);
|
|
}
|
|
catch (err) {
|
|
fputs(err.why, stderr);
|
|
drop(err);
|
|
return err.code;
|
|
}
|
|
... end up here if nothing bad happened ...
|
|
|
|
|
|
More involved example:
|
|
|
|
void check_part(void)
|
|
{
|
|
ball_t err;
|
|
|
|
try {
|
|
...
|
|
if (part == bad1)
|
|
throw(1);
|
|
...
|
|
if (part == bad2)
|
|
throw(1);
|
|
...
|
|
}
|
|
catch (err) {
|
|
drop(err);
|
|
throw(3, "part was bad");
|
|
}
|
|
}
|
|
|
|
void check_input(void)
|
|
{
|
|
...
|
|
if (input == wrong)
|
|
throw(4, "input was wrong");
|
|
...
|
|
if (input == stupid)
|
|
throw(5, "input was stupid");
|
|
...
|
|
check_part();
|
|
...
|
|
}
|
|
|
|
void *build_something(void)
|
|
{
|
|
ball_t err;
|
|
volatile void *thing;
|
|
try {
|
|
thing = malloc(sizeof(struct thing));
|
|
... build up thing ...
|
|
check_input();
|
|
... finish building it ...
|
|
}
|
|
catch (err) {
|
|
free(thing);
|
|
punt(err);
|
|
}
|
|
return thing;
|
|
}
|
|
|
|
int grand_central(void)
|
|
{
|
|
ball_t err;
|
|
void *thing;
|
|
try {
|
|
thing = build_something();
|
|
}
|
|
catch (err) {
|
|
fputs(err.why, stderr);
|
|
drop(err);
|
|
return err.code;
|
|
}
|
|
... use thing ...
|
|
free(thing);
|
|
return 0;
|
|
}
|
|
|
|
*/
|
|
|
|
#ifndef _TRY_H
|
|
#define _TRY_H
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <setjmp.h>
|
|
|
|
/* If pthreads are used, uncomment this include to make try thread-safe. */
|
|
#ifndef NOTHREAD
|
|
# include <pthread.h>
|
|
#endif
|
|
|
|
/* The exposed names can be changed here. */
|
|
#define ball_t try_ball_t_
|
|
#define try TRY_TRY_
|
|
#define always TRY_ALWAYS_
|
|
#define catch TRY_CATCH_
|
|
#define throw TRY_THROW_
|
|
#define retry TRY_RETRY_
|
|
#define punt TRY_PUNT_
|
|
#define drop TRY_DROP_
|
|
|
|
/* Package of an integer code and any other data to be thrown and caught. Here,
|
|
why is a string with information to be displayed to indicate why an
|
|
exception was thrown. free is true if why was allocated and should be freed
|
|
when no longer needed. This structure can be customized as needed, but it
|
|
must start with an int code. If it is customized, the try_throw_() function
|
|
in try.c must also be updated accordingly. As an example, why could be a
|
|
structure with information for use in the catch block. */
|
|
typedef struct {
|
|
int code; /* integer code (required) */
|
|
int free; /* if true, the message string was allocated */
|
|
char *why; /* informational string or NULL */
|
|
} try_ball_t_;
|
|
|
|
/* Element in the global try stack (a linked list). */
|
|
typedef struct try_s_ try_t_;
|
|
struct try_s_ {
|
|
jmp_buf env; /* state information for longjmp() to jump back */
|
|
try_ball_t_ ball; /* data passed from the throw() */
|
|
try_t_ *next; /* link to the next enclosing try_t, or NULL */
|
|
};
|
|
|
|
/* Global try stack. try.c must be compiled and linked to provide the stack
|
|
pointer. Use thread-local storage if pthread.h is included before this.
|
|
Note that a throw can only be caught within the same thread. A new and
|
|
unique try stack is created for each thread, so any attempt to throw across
|
|
threads will fail with an assert, by virtue of reaching the end of the
|
|
stack. */
|
|
#ifdef PTHREAD_ONCE_INIT
|
|
extern pthread_key_t try_key_;
|
|
void try_setup_(void);
|
|
# define try_stack_ ((try_t_ *)pthread_getspecific(try_key_))
|
|
# define try_stack_set_(next) \
|
|
do { \
|
|
int try_ret_ = pthread_setspecific(try_key_, next); \
|
|
assert(try_ret_ == 0 && "try: pthread_setspecific() failed"); \
|
|
} while (0)
|
|
#else /* !PTHREAD_ONCE_INIT */
|
|
extern try_t_ *try_stack_;
|
|
# define try_setup_()
|
|
# define try_stack_set_(next) try_stack_ = (next)
|
|
#endif /* PTHREAD_ONCE_INIT */
|
|
|
|
/* Try a block. The block should follow the invocation of try enclosed in { }.
|
|
The block must be immediately followed by an always or a catch. You must
|
|
not goto or return out of the try block. A naked break or continue in the
|
|
try block will go to the end of the block. */
|
|
#define TRY_TRY_ \
|
|
do { \
|
|
try_t_ try_this_; \
|
|
int try_pushed_ = 1; \
|
|
try_this_.ball.code = 0; \
|
|
try_this_.ball.free = 0; \
|
|
try_this_.ball.why = NULL; \
|
|
try_setup_(); \
|
|
try_this_.next = try_stack_; \
|
|
try_stack_set_(&try_this_); \
|
|
if (setjmp(try_this_.env) < 2) \
|
|
do { \
|
|
|
|
/* Execute the code between always and catch, whether or not something was
|
|
thrown. An always block is optional. If present, the always block must
|
|
follow a try block and be followed by a catch block. The always block
|
|
should be enclosed in { }. A naked break or continue in the always block
|
|
will go to the end of the block. It is permitted to use throw in the always
|
|
block, which will fall up to the next enclosing try. However this will
|
|
result in a memory leak if the original throw() allocated space for the
|
|
informational string. So it's best to not throw() in an always block. Keep
|
|
the always block simple.
|
|
|
|
Great care must be taken if the always block uses an automatic storage
|
|
variable local to the enclosing function that can be modified in the try
|
|
block. Such variables must be declared volatile. If such a variable is not
|
|
declared volatile, and if the compiler elects to keep that variable in a
|
|
register, then the throw will restore that variable to its state at the
|
|
beginning of the try block, wiping out any change that occurred in the try
|
|
block. This can cause very confusing bugs until you remember that you
|
|
didn't follow this rule. */
|
|
#define TRY_ALWAYS_ \
|
|
} while (0); \
|
|
if (try_pushed_) { \
|
|
try_stack_set_(try_this_.next); \
|
|
try_pushed_ = 0; \
|
|
} \
|
|
do {
|
|
|
|
/* Catch an error thrown in the preceding try block. The catch block must
|
|
follow catch and its parameter, and must be enclosed in { }. The catch must
|
|
immediately follow the try or always block. It is permitted to use throw()
|
|
in the catch block, which will fall up to the next enclosing try. However
|
|
the ball_t passed by throw() must be freed using drop() before doing another
|
|
throw, to avoid a potential memory leak. The parameter of catch must be a
|
|
ball_t declared in the function or block containing the catch. It is set to
|
|
the parameters of the throw() that jumped to the catch. The catch block is
|
|
not executed if the first parameter of the throw() was zero.
|
|
|
|
A catch block should end with either a punt() or a drop().
|
|
|
|
Great care must be taken if the catch block uses an automatic storage
|
|
variable local to the enclosing function that can be modified in the try
|
|
block. Such variables must be declared volatile. If such a variable is not
|
|
declared volatile, and if the compiler elects to keep that variable in a
|
|
register, then the throw will restore that variable to its state at the
|
|
beginning of the try block, wiping out any change that occurred in the try
|
|
block. This can cause very confusing bugs until you remember that you
|
|
didn't follow this rule. */
|
|
#define TRY_CATCH_(try_ball_) \
|
|
} while (0); \
|
|
if (try_pushed_) { \
|
|
try_stack_set_(try_this_.next); \
|
|
try_pushed_ = 0; \
|
|
} \
|
|
try_ball_ = try_this_.ball; \
|
|
} while (0); \
|
|
if (try_ball_.code)
|
|
|
|
/* Throw an error. This can be in the try block or in any function called from
|
|
the try block, at any level of nesting. This will fall back to the end of
|
|
the first enclosing try block in the same thread, invoking the associated
|
|
catch block with a ball_t set to the arguments of throw(). throw() will
|
|
abort the program with an assert() if there is no nesting try. Make sure
|
|
that there's a nesting try!
|
|
|
|
try may have one or more arguments, where the first argument is an int, the
|
|
optional second argument is a string, and the remaining optional arguments
|
|
are referred to by printf() formatting commands in the string. If there are
|
|
formatting commands in the string, i.e. any percent (%) signs, then
|
|
vsnprintf() is used to generate the formatted string from the arguments
|
|
before jumping to the enclosing try block. This allows throw() to use
|
|
information on the stack in the scope of the throw() statement, which will
|
|
be lost after jumping back to the enclosing try block. That formatted
|
|
string will use allocated memory, which is why it is important to use drop()
|
|
in catch blocks to free that memory, or punt() to pass the string on to
|
|
another catch block. Eventually some catch block down the chain will have
|
|
to drop() it.
|
|
|
|
If a memory allocation fails during the execution of a throw(), then the
|
|
string provided to the catch block is not the formatted string at all, but
|
|
rather the string: "try: out of memory", with the integer code from the
|
|
throw() unchanged.
|
|
|
|
If the first argument of throw is zero, then the catch block is not
|
|
executed. A throw(0) from a function called in the try block is equivalent
|
|
to a break or continue in the try block. A throw(0) should not have any
|
|
other arguments, to avoid a potential memory leak. There is no opportunity
|
|
to make use of any arguments after the 0 anyway.
|
|
|
|
try.c must be compiled and linked to provide the try_throw_() function. */
|
|
void try_throw_(int code, char *fmt, ...);
|
|
#define TRY_THROW_(...) try_throw_(__VA_ARGS__, NULL)
|
|
|
|
/* Retry the try block. This will start over at the beginning of the try
|
|
block. This can be used in the try block or in any function called from the
|
|
try block at any level of nesting, just like throw. retry has no argument.
|
|
If there is a retry in the always or catch block, then it will retry the
|
|
next enclosing try, not the immediately preceding try.
|
|
|
|
If you use this, make sure you have carefully thought through how it will
|
|
work. It can be tricky to correctly rerun a chunk of code that has been
|
|
partially executed. Especially if there are different degrees of progress
|
|
that could have been made. Also note that automatic variables changed in
|
|
the try block and not declared volatile will have indeterminate values.
|
|
|
|
We use 1 here instead of 0, since some implementations prevent returning a
|
|
zero value from longjmp() to setjmp(). */
|
|
#define TRY_RETRY_ \
|
|
do { \
|
|
try_setup_(); \
|
|
assert(try_stack_ != NULL && "try: naked retry"); \
|
|
longjmp(try_stack_->env, 1); \
|
|
} while (0)
|
|
|
|
/* Punt a caught error on to the next enclosing catcher. This is normally used
|
|
in a catch block with same argument as the catch. */
|
|
#define TRY_PUNT_(try_ball_) \
|
|
do { \
|
|
try_setup_(); \
|
|
assert(try_stack_ != NULL && "try: naked punt"); \
|
|
try_stack_->ball = try_ball_; \
|
|
longjmp(try_stack_->env, 2); \
|
|
} while (0)
|
|
|
|
/* Clean up at the end of the line in a catch (no more punts). */
|
|
#define TRY_DROP_(try_ball_) \
|
|
do { \
|
|
if (try_ball_.free) { \
|
|
free(try_ball_.why); \
|
|
try_ball_.free = 0; \
|
|
try_ball_.why = NULL; \
|
|
} \
|
|
} while (0)
|
|
|
|
#endif /* _TRY_H */
|