DragonFly On-Line Manual Pages
ex(3) Exception Handling ex(3)
NAME
OSSP ex - Exception Handling
VERSION
OSSP ex 1.0.5 (02-Oct-2005)
SYNOPSIS
ex_t variable;
ex_try BLOCK1 [ex_cleanup BLOCK2] ex_catch (variable) BLOCK3
ex_throw(class, object, value);
ex_rethrow;
ex_defer BLOCK
ex_shield BLOCK
if (ex_catching) ...
if (ex_deferred) ...
if (ex_shielding) ...
DESCRIPTION
OSSP ex is a small ISO-C++ style exception handling library for use in
the ISO-C language. It allows you to use the paradigm of throwing and
catching exceptions in order to reduce the amount of error handling
code without making your program less robust.
This is achieved by directly transferring exceptional return codes (and
the program control flow) from the location where the exception is
raised (throw point) to the location where it is handled (catch point)
-- usually from a deeply nested sub-routine to a parent routine. All
intermediate routines no longer have to make sure that the exceptional
return codes from sub-routines are correctly passed back to the parent.
EXCEPTIONS
An OSSP ex exception is a triple <class,object,value> where class
identifies the class of the exception thrower, object identifies the
particular class instance of the exception thrower, and value is the
exceptional return code value the thrower wants to communicate. All
three parts are of type "void *" internally, but every value which can
be lossless "casted" to this type is usable. Exceptions are created on-
the-fly by the ex_throw command.
APPLICATION PROGRAMMER INTERFACE (API)
The OSSP ex API consists of the following elements:
ex_t variable;
This is the declaration of an exception variable. It is usually
never initialized manually. Instead it is initialized by an
ex_catch clause and just used read-only inside its block. Such a
variable of type ex_t consists of six attributes:
void *ex_class
This is the class argument of the ex_throw call which created the
exception. This can globally and uniquely identify the class to
which ex_value belongs to. Usually this is a pointer to a static
object (variable, structure or function) which identifies the
class of the thrower and allows the catcher to correctly handle
ex_value. It is usually just an additional (optional) information
to ex_value.
void *ex_object
This is the object argument of the ex_throw call which created
the exception. This can globally and uniquely identify the class
instance ex_value belongs to (in case multiple instances exists
at all). Usually this a pointer to a dynamic object (structure)
which identifiers the particular instance of the thrower. It is
usually just an additional (optional) information to ex_value.
void *ex_value
This is the value argument of the ex_throw call which created the
exception. This is the exceptional return code value which has to
uniquely identify the type of exception. Usually this is the
value which is returned if no exceptions would be thrown. In the
simple case this is just a numerical return code. In the complex
case this can be a pointer to an arbitrary complex data structure
describing the exception.
char *ex_file
This is the file name of the ISO-C source where the ex_throw call
was performed. It is automatically provided as an additional
information about the throw point and is intended mainly for
tracing and debugging purposes.
int ex_line
This is the line number inside the ISO-C source file name where
the ex_throw call was performed. It is automatically provided as
an additional information about the throw point and is intended
mainly for tracing and debugging purposes.
char *ex_func
This is the function name (if determinable, else "#NA#") inside
the ISO-C source file name where the ex_throw call was performed.
It is automatically provided as an additional information about
the throw point and is intended mainly for tracing and debugging
purposes.
ex_try BLOCK1 [ex_cleanup BLOCK2] ex_catch (variable) BLOCK3
This is the primary syntactical construct provided by OSSP ex. It
is modeled after the ISO-C++ try-catch clause which in turn is very
similar to an ISO-C if-else clause. It consists of an ex_try block
BLOCK1 which forms the dynamic scope for exception handling (i.e.
exceptions directly thrown there or thrown from its sub-routines
are caught), an optional ex_cleanup block BLOCK2 for performing
cleanup operations and an ex_catch block BLOCK3 where the caught
exceptions are handled.
The control flow in case no exception is thrown is simply BLOCK1,
optionally followed by BLOCK2; BLOCK3 is skipped. The control flow
in case an exception is thrown is: BLOCK1 (up to the statement
where the exception is thrown only), optionally followed by BLOCK2,
followed by BLOCK3.
The ex_try, ex_cleanup and ex_catch cannot be used separately, they
work only in combination because they form a language clause as a
whole. In contrast to ISO-C++ there is only one ex_catch block and
not multiple ones (all OSSP ex exceptions are of the same ISO-C
type ex_t). If an exception is caught, it is stored in variable for
inspection inside the ex_catch block. Although having to be
declared outside, the variable value is only valid within the
ex_catch block. But the variable can be re-used in subsequent
ex_catch clauses, of course.
The ex_try block is a regular ISO-C language statement block, but
it is not allowed to jump into it via "goto" or longjmp(3) or out
of it via "break", "return", "goto" or longjmp(3) because there is
some hidden setup and cleanup that needs to be done by OSSP ex
regardless of whether an exception is caught. Jumping into an
ex_try clause would avoid doing the setup, and jumping out of it
would avoid doing the cleanup. In both cases the result is a broken
exception handling facility. Nevertheless you are allowed to nest
ex_try clauses.
The ex_cleanup and ex_catch blocks are regular ISO-C language
statement blocks without any restrictions. You are even allowed to
throw (and in the ex_catch block to re-throw) an exception.
There is just one subtle portability detail you have to remember
about ex_try blocks: all accessible ISO-C objects have the
(expected) values as of the time ex_throw was called, except that
the values of objects of automatic storage invocation duration that
do not have the "volatile" storage class and have been changed
between the ex_try invocation and ex_throw are indeterminate. This
is because both you usually do not know which commands in the
ex_try were already successful before the exception was thrown
(logically speaking) and because the underlying ISO-C setjmp(3)
facility applies those restrictions (technically speaking).
ex_throw(class, object, value);
This builds an exception from the supplied arguments and throws it.
If an ex_try/ex_catch clause formed the dynamic scope of the
ex_throw call, this exception is copied into the variable of its
ex_catch clause and the program control flow is continued in the
(optional ex_cleanup and then in the) ex_catch block. If no
ex_try/ex_catch clause exists in the dynamic scope of the ex_throw
call, the program calls abort(3). The ex_throw can be performed
everywhere, including inside ex_try, ex_cleanup and ex_catch
blocks.
ex_rethrow;
This is only valid within an ex_catch block and re-throws the
current exception (in variable). It is similar to the call
ex_throw(variable.ex_class, variable.ex_object, variable.ex_value)
except for the difference that the ex_file, ex_line and ex_func
elements of the caught exception are passed through as it would
have been never caught.
ex_defer BLOCK
This directive executes BLOCK while deferring the throwing of
exceptions, i.e., inside the dynamic scope of ex_defer all ex_throw
operations are remembered but deferred and on leaving the BLOCK the
first occurred exception is thrown. The second and subsequent
exceptions are ignored.
The ex_defer block BLOCK is a regular ISO-C language statement
block, but it is not allowed to jump into it via "goto" or
longjmp(3) or out of it via "break", "return", "goto" or longjmp(3)
because this would cause the deferral level to become out of sync.
Jumping into an ex_defer clause would avoid increasing the
exception deferral level, and jumping out of it would avoid
decreasing it. In both cases the result is an incorrect exception
deferral level. Nevertheless you are allowed to nest ex_defer
clauses.
ex_shield BLOCK
This directive executes BLOCK while shielding it against the
throwing of exceptions, i.e., inside the dynamic scope of ex_shield
all ex_throw operations are just silently ignored.
The ex_shield block is a regular ISO-C language statement block,
but it is not allowed to jump into it via "goto" or longjmp(3) or
out of it via "break", "return", "goto" or longjmp(3) because this
would cause the shielding level to become out of sync. Jumping
into an ex_shield clause would avoid increasing the exception
shielding level, and jumping out of it would avoid decreasing it.
In both cases the result is an incorrect exception shielding level.
Nevertheless you are allowed to nest ex_shield clauses.
ex_catching
This is a boolean flag which can be checked inside the dynamic
scope of an ex_try clause to test whether the current scope is
exception catching (see ex_try/ex_catch clause).
ex_deferred
This is a boolean flag which can be checked inside the dynamic
scope of an ex_defer clause to test whether the current scope is
exception deferring (see ex_defer clause).
ex_shielding
This is a boolean flag which can be checked inside the dynamic
scope of an ex_shield clause to test whether the current scope is
exception shielding (see ex_shield clause).
IMPLEMENTATION CONTROL
OSSP ex uses a very light-weight but still flexible exception facility
implementation. The following adjustments can be made before including
the ex.h header:
Machine Context
In order to move the program control flow from the exception throw
point (ex_throw) to the catch point (ex_catch), OSSP ex uses four
macros:
__ex_mctx_struct
This holds the contents of the machine context structure. A pointer
to such a machine context is passed to the following macros as
mctx.
__ex_mctx_save(__ex_mctx_struct *mctx)
This is called by the prolog of ex_try to save the current machine
context in mctx. This function has to return true (not 0) after
saving. If the machine context is restored (by __ex_mctx_restore)
it has to return false (0). In other words, this function has to
return twice and indicate the particular situation with the
provided return code.
__ex_mctx_restored(__ex_mctx_struct *mctx)
This is called by the epilog of ex_try to perform additional
operations at the new (restored) machine context after an exception
was caught. Usually this is a no-operation macro.
__ex_mctx_restore(__ex_mctx_struct *mctx)
This is called by ex_throw at the old machine context in order to
restore the machine context of the ex_try/ex_catch clause which
will catch the exception.
The default implementation (define __EX_MCTX_SJLJ__ or as long as
__EX_MCTX_CUSTOM__ is not defined) uses the ISO-C jmp_buf(3) facility:
#define __ex_mctx_struct jmp_buf jb;
#define __ex_mctx_save(mctx) (setjmp((mctx)->jb) == 0)
#define __ex_mctx_restored(mctx) /* noop */
#define __ex_mctx_restore(mctx) (void)longjmp((mctx)->jb, 1)
Alternatively, you can define __EX_MCTX_SSJLJ__ to use POSIX.1
sigjmp_buf(3) or __EX_MCTX_MCSC__ to use POSIX.1 ucontext(3). For using
a custom implementation define __EX_MCTX_CUSTOM__ and provide own
definitions for the four __ex_mctx_xxxx macros.
Exception Context
In order to maintain the exception catching stack and for passing the
exception between the throw and the catch point, OSSP ex uses a global
exception context, returned on-the-fly by the callback "ex_ctx_t
*(*__ex_ctx)(void)".
By default, __ex_ctx (which is __ex_ctx_default as provided by libex)
returns a pointer to a static ex_ctx_t context. For use in multi-
threading environments, this should be overwritten with a callback
function returning a per-thread context structure (see section
MULTITHREADING ENVIRONMENTS below).
To initialize an exception context structure there are two macros
defined: "EX_CTX_INITIALIZER" for static initialization and "void
EX_CTX_INITIALIZE(ex_ctx_t *)" for dynamic initialization.
Termination Handler
In case there is an exception thrown which is not caught by any
ex_try/ex_catch clauses, OSSP ex calls the callback "void
(*__ex_terminate)(ex_t *)". It receives a pointer to the exception
object which was thrown.
By default, __ex_terminate (which is __ex_terminate_default as provided
by libex) prints a message of the form "**EX: UNCAUGHT EXCEPTION:
class=0xXXXXXXXX object=0xXXXXXXXX value=0xXXXXXXX [xxxx:NNN:xxxx]" to
stderr and then calls abort(3) in order to terminate the application.
For use in multi-threading environments, this should be overwritten
with a callback function which terminates only the current thread. Even
better, a real application always should have a top-level
ex_try/ex_catch clause in its "main()" in order to more gracefully
terminate the application.
Namespace Mapping
The OSSP ex implementation consistently uses the "ex_", "__ex_" and
"__EX_" prefixes for namespace protection. But at least the "ex_"
prefix for the API macros ex_try, ex_cleanup, ex_catch, ex_throw,
ex_rethrow and ex_shield sometimes have an unpleasant optical
appearance. Especially because OSSP ex is modeled after the exception
facility of ISO-C++ where there is no such prefix on the language
directives, of course.
For this, OSSP ex optionally provides the ability to provide additional
namespace mappings for those API elements. By default (define
__EX_NS_CXX__ or as long as __EX_NS_CUSTOM__ and __cplusplus is not
defined) you can additionally use the ISO-C++ style names catch,
cleanup, throw, rethrow and shield. As an alternative you can define
__EX_NS_UCCXX__ to get the same but with a more namespace safe upper
case first letter.
PROGRAMMING PITFALLS
Exception handling is a very elegant and efficient way of dealing with
exceptional situation. Nevertheless it requires additional discipline
in programming and there are a few pitfalls one must be aware of. Look
the following code which shows some pitfalls and contains many errors
(assuming a mallocex() function which throws an exception if malloc(3)
fails):
/* BAD EXAMPLE */
ex_try {
char *cp1, *cp2, cp3;
cp1 = mallocex(SMALLAMOUNT);
globalcontext->first = cp1;
cp2 = mallocex(TOOBIG);
cp3 = mallocex(SMALLAMOUNT);
strcpy(cp1, "foo");
strcpy(cp2, "bar");
}
ex_cleanup {
if (cp3 != NULL) free(cp3);
if (cp2 != NULL) free(cp2);
if (cp1 != NULL) free(cp1);
}
ex_catch(ex) {
printf("cp3=%s", cp3);
ex_rethrow;
}
This example raises a few issues:
01: variable scope
Variables which are used in the ex_cleanup or ex_catch clauses must
be declared before the ex_try clause, otherwise they only exist
inside the ex_try block. In the example above, cp1, cp2 and cp3 are
automatic variables and only exist in the block of the ex_try
clause, the code in the ex_cleanup and ex_catch clauses does not
know anything about them.
02: variable initialization
Variables which are used in the ex_cleanup or ex_catch clauses must
be initialized before the point of the first possible ex_throw is
reached. In the example above, ex_cleanup would have trouble using
cp3 if mallocex() throws a exception when allocating a TOOBIG
buffer.
03: volatile variables
Variables which are used in the ex_cleanup or ex_catch clauses must
be declared with the storage class "volatile", otherwise they might
contain outdated information if ex_throw throws an exception. If
using a "free if unset" approach like the example does in the
ex_cleanup clause, the variables must be initialized (see 02) and
remain valid upon use.
04: clean before catch
The ex_cleanup clause is not only written down before the ex_catch
clause, it is also evaluated before the ex_catch clause. So,
resources being cleaned up must no longer be used in the ex_catch
block. The example above would have trouble referencing the
character strings in the printf(3) statement because these have
been freed before.
05: variable uninitialization
If resources are passed away and out of the scope of the
ex_try/ex_cleanup/ex_catch construct and the variables were
initialized for using a "free if unset" approach then they must be
uninitialized after being passed away. The example above would
free(3) cp1 in the ex_cleanup clause if mallocex() throws an
exception if allocating a TOOBIG buffer. The
globalcontext->first pointer hence becomes invalid.
The following is fixed version of the code (annotated with the pitfall
items for reference):
/* GOOD EXAMPLE */
{ /*01*/
char * volatile /*03*/ cp1 = NULL /*02*/;
char * volatile /*03*/ cp2 = NULL /*02*/;
char * volatile /*03*/ cp3 = NULL /*02*/;
try {
cp1 = mallocex(SMALLAMOUNT);
globalcontext->first = cp1;
cp1 = NULL /*05 give away*/;
cp2 = mallocex(TOOBIG);
cp3 = mallocex(SMALLAMOUNT);
strcpy(cp1, "foo");
strcpy(cp2, "bar");
}
clean { /*04*/
printf("cp3=%s", cp3 == NULL /*02*/ ? "" : cp3);
if (cp3 != NULL)
free(cp3);
if (cp2 != NULL)
free(cp2);
/*05 cp1 was given away */
}
catch(ex) {
/*05 global context untouched */
rethrow;
}
}
Alternatively, this could also be used:
/* ALTERNATIVE GOOD EXAMPLE */
{ /*01*/
char * volatile /*03*/ cp1 = NULL /*02*/;
char * volatile /*03*/ cp2 = NULL /*02*/;
char * volatile /*03*/ cp3 = NULL /*02*/;
try {
cp1 = mallocex(SMALLAMOUNT);
globalcontext->first = cp1;
/*05 keep responsibility*/
cp2 = mallocex(TOOBIG);
cp3 = mallocex(SMALLAMOUNT);
strcpy(cp1, "foo");
strcpy(cp2, "bar");
}
clean { /*04*/
printf("cp3=%s", cp3 == NULL /*02*/ ? "" : cp3);
if (cp3 != NULL)
free(cp3);
if (cp2 != NULL)
free(cp2);
if (cp1 != NULL)
free(cp1);
}
catch(ex) {
globalcontext->first = NULL;
rethrow;
}
}
MULTITHREADING ENVIRONMENTS
OSSP ex is designed to work both in single-threading and multi-
threading environments. The default is to support single-threading
only. But it is easy to configure OSSP ex to work correctly in a multi-
threading environment like POSIX pthreads or GNU pth.
There are only two issues: which machine context to use and where to
store the exception context to make sure exception throwing happens
only within a thread and does not conflict with the regular thread
dispatching mechanism.
GNU pth
Using OSSP ex together with GNU pth is straight-forward, because GNU
pth 2.0 (and higher) already has support for OSSP ex built-in. All
which is needed is that GNU pth is configured with the GNU Autoconf
option --with-ex. Then each GNU pth user-space thread has its own OSSP
ex exception context automatically. The default of using ISO-C
jmp_buf(3) does not conflict with the thread dispatching mechanisms
used by GNU pth.
POSIX pthreads
Using OSSP ex inside an arbitrary POSIX pthreads standard compliant
environment is also straight-forward, although it requires extra
coding. What you basically have to do is to make sure that the __ex_ctx
becomes a per-thread context and that __ex_terminate terminates only
the current thread. To get an impression, a small utility library for
this follows:
pthread_ex.h
#ifndef __PTHREAD_EX_H__
#define __PTHREAD_EX_H__
#include <pthread.h>
int pthread_init_ex (void);
int pthread_create_ex (pthread_t *, const pthread_attr_t *,
void *(*)(void *), void *);
#ifndef PTHREAD_EX_INTERNAL
#define pthread_init pthread_init_ex
#define pthread_create pthread_create_ex
#endif
#endif /* __PTHREAD_EX_H__ */
pthread_ex.c
#include <stdlib.h>
#include <pthread.h>
#define PTHREAD_EX_INTERNAL
#include "pthread_ex.h"
#include "ex.h"
/* context storage key */
static pthread_key_t pthread_ex_ctx_key;
/* context destructor */
static void pthread_ex_ctx_destroy(void *data)
{
if (data != NULL)
free(data);
return;
}
/* callback: context fetching */
static ex_ctx_t *pthread_ex_ctx(void)
{
return (ex_ctx_t *)
pthread_getspecific(pthread_ex_ctx_key);
}
/* callback: termination */
static void pthread_ex_terminate(ex_t *e)
{
pthread_exit(e->ex_value);
}
/* pthread init */
int pthread_init_ex(void)
{
int rc;
/* additionally create thread data key
and override OSSP ex callbacks */
pthread_key_create(&pthread_ex_ctx_key,
pthread_ex_ctx_destroy);
__ex_ctx = pthread_ex_ctx;
__ex_terminate = pthread_ex_terminate;
return rc;
}
/* internal thread entry wrapper information */
typedef struct {
void *(*entry)(void *);
void *arg;
} pthread_create_ex_t;
/* internal thread entry wrapper */
static void *pthread_create_wrapper(void *arg)
{
pthread_create_ex_t *wrapper;
ex_ctx_t *ex_ctx;
/* create per-thread exception context */
wrapper = (pthread_create_ex_t *)arg;
ex_ctx = (ex_ctx_t *)malloc(sizeof(ex_ctx_t));
EX_CTX_INITIALIZE(ex_ctx);
pthread_setspecific(pthread_ex_ctx_key, ex_ctx);
/* perform original operation */
return wrapper->entry(wrapper->arg);
}
/* pthread_create() wrapper */
int pthread_create_ex(pthread_t *thread,
const pthread_attr_t *attr,
void *(*entry)(void *), void *arg)
{
pthread_create_ex_t wrapper;
/* spawn thread but execute start
function through wrapper */
wrapper.entry = entry;
wrapper.arg = arg;
return pthread_create(thread, attr,
pthread_create_wrapper, &wrapper);
}
Now all which is required is that you include pthread_ex.h after the
standard pthread.h header and to call pthread_init once at startup of
your program.
EXAMPLES
As a real-life example we will look how you can add optional OSSP ex
based exception handling support to a library foo. The original library
looks like this:
foo.h
typedef enum {
FOO_OK,
FOO_ERR_ARG,
FOO_ERR_XXX,
FOO_ERR_SYS,
FOO_ERR_IMP,
...
} foo_rc_t;
struct foo_st;
typedef struct foo_st foo_t;
foo_rc_t foo_create (foo_t **foo);
foo_rc_t foo_perform (foo_t *foo);
foo_rc_t foo_destroy (foo_t *foo);
foo.c
#include "foo.h"
struct foo_st {
...
}
foo_rc_t foo_create(foo_t **foo)
{
if ((*foo = (foo_t)malloc(sizeof(foo))) == NULL)
return FOO_ERR_SYS;
(*foo)->... = ...
return FOO_OK;
}
foo_rc_t foo_perform(foo_t *foo)
{
if (foo == NULL)
return FOO_ERR_ARG;
if (...)
return FOO_ERR_XXX;
return FOO_OK;
}
foo_rc_t foo_destroy(foo_t *foo)
{
if (foo == NULL)
return FOO_ERR_ARG;
free(foo);
return FOO_OK;
}
Then the typical usage of this library is:
#include "foo.h"
...
foo_t foo;
foo_rc_t rc;
...
if ((rc = foo_create(&foo)) != FOO_OK)
die(rc);
if ((rc = foo_perform(foo)) != FOO_OK)
die(rc);
if ((rc = foo_destroy(foo)) != FOO_OK)
die(rc);
But what you really want, is to use exception handling to get rid of
the intermixed error handling code:
#include "foo.h"
#include "ex.h"
...
foo_t foo;
ex_t ex;
...
ex_try {
foo_create(&foo);
foo_perform(foo);
foo_destroy(foo);
}
ex_catch (ex) {
die((foo_rc_t)ex->ex_value);
}
You can achieve this very easily by changing the library as following:
foo.h
...
extern const char foo_id[];
...
foo.c
#include "foo.h"
const char foo_id[] = "foo 1.0";
#ifdef WITH_EX
#include "ex.h"
#define FOO_RC(rv) \
( (rv) != FOO_OK && (ex_catching && !ex_shielding) \
? (ex_throw(foo_id, NULL, (rv)), (rv)) : (rv) )
#else
#define FOO_RC(rv) (rv)
#endif
struct foo_st {
...
}
foo_rc_t foo_create(foo_t **foo)
{
if ((*foo = (foo_t)malloc(sizeof(foo))) == NULL)
return FOO_RC(FOO_ERR_SYS);
(*foo)->... = ...
return FOO_OK;
}
foo_rc_t foo_perform(foo_t *foo)
{
if (foo == NULL)
return FOO_RC(FOO_ERR_ARG);
if (...)
return FOO_RC(FOO_ERR_XXX);
return FOO_OK;
}
foo_rc_t foo_destroy(foo_t *foo)
{
if (foo == NULL)
return FOO_RC(FOO_ERR_ARG);
free(foo);
return FOO_OK;
}
This way the library by default is still exactly the same. If you now
compile it with -DWITH_EX you activate exception handling support.
This means that all API functions throw exceptions where ex_value is
the foo_rc_t instead of returning this value.
SEE ALSO
ISO-C++ try, catch, throw.
Java try, catch, finally, throw.
ISO-C jmp_buf(3), setjmp(3), longjmp(3).
POSIX.1 sigjmp_buf(3), sigsetjmp(3), siglongjump(3).
POSIX.1 ucontext(3), setcontext(3), getcontext(3).
HISTORY
OSSP ex was invented in January 2002 by Ralf S. Engelschall
<rse@engelschall.com> for use inside the OSSP project. Its creation was
prompted by the requirement to reduce the error handling inside OSSP
lmtp2nntp.
The core try/catch clause was inspired by ISO-C++ and the
implementation was partly derived from cexcept 2.0.0, a similar library
written 2000 by Adam M. Costello <amc@cs.berkeley.edu> and Cosmin Truta
<cosmin@cs.toronto.edu>.
The cleanup clause was inspired by the Java finally clause. The shield
feature was inspired by an "errno" shielding facility used in the GNU
pth implementation. The defer feature was invented to simplify an
application's cleanup handling if multiple independent resources are
allocated and have to be freed on error.
AUTHORS
Ralf S. Engelschall
rse@engelschall.com
www.engelschall.com
02-Oct-2005 OSSP ex 1.0.5 ex(3)