##
## Copyright (c) 2001-2002 The OSSP Project
## Copyright (c) 2001-2002 Cable & Wireless Deutschland
##
## This file is part of OSSP ex, an exception library
## which can be found at http://www.ossp.org/pkg/ex/.
##
## Permission to use, copy, modify, and distribute this software for
## any purpose with or without fee is hereby granted, provided that
## the above copyright notice and this permission notice appear in all
## copies.
##
## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
## 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.
##
## ex.pod: exception library manual page
##
=pod
=head1 NAME
B - Exception Library
=head1 SYNOPSIS
B I;
B { ... } B (I) { ... }
B(I, I, I);
B;
B { ... };
if (B) ...
if (B) ...
=head1 DESCRIPTION
B 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 in without making your program less robust. This is achieved
by directly transferring exceptional return codes (and the program
control flow) from the location where it was 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.
=head2 EXCEPTIONS
An exception is a triple EI,I,IE where
I identifies the class of the exception thrower, I
identifies the particular class instance of the exception thrower, and
I is the exceptional return code the thrower wants to send to the
catcher. All three parts are of type C internally, but every
value can be used which can be (automatically) casted to a C.
Exceptions are created on-the-fly by the B command.
=head2 APPLICATION PROGRAMMER INTERFACE (API)
The B API consists of four different elements:
=over 4
=item B I;
This is the declaration of an exception variable. It is usually never
initialized manually. Instead it is initialized by an B block
and just used read-only inside the block. Such a variable of type
B consists of six attributes:
=over 2
=item CI
This is the I argument of the B call which created
the exception. This globally and uniquely identifies the class of
I. Usually this is a pointer to a static object (variable,
structure or function) which identifies the thrower.
=item CI
This is the I argument of the B call which created
the exception. This globally and uniquely identifies the class instance
of I (in case multiple instances exists). Usually this a
pointer to a dynamic object (structure) which identifiers the particular
instance of the thrower.
=item CI
This is the I argument of the B call which created the
exception. This is the exceptional return code which uniquely identifies
the type of exception. Usually this is the value which is Ced
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.
=item CI
This is the file name of the source where the B call
was performed. It is provided as an additional information about
the throw point and is intended mainly for tracing and debugging
purposes.
=item C I
This is the line number inside the source file name where the
B call was performed. It is provided as an additional
information about the throw point and is intended mainly for tracing and
debugging purposes.
=item CI
This is the function name (if determinable, else "C<#NA#>") inside
the source file name where the B call was performed. It is
provided as an additional information about the throw point and is
intended mainly for tracing and debugging purposes.
=back
=item B { ... } B (I) { ... }
This is the main construct provided by B. It is modeled after
the ISO-C++ C-C construct which in turn is very similar to
an ISO-C C-C construct. It consists of an B block
which forms the dynamic scope of the exception handling (i.e. exceptions
thrown there are or from routines called from there are catched) and a
B block where the caught exceptions are handled.
The B and B cannot be used seperately, they work only
in combination. In contrast to ISO-C++ there is only one B
block and not multiple ones (all B exceptions are of the
same ISO-C type B). If an exception is caught, it is stored in
I for inspection (or re-throwing) inside the B
block. Although declared outside, the I is only valid
within the B block. But it can be re-used in following
B/B constructs, of course.
The B block is a regular ISO-C language block, but it is not
allowed to jump into it via C or C(3) or out of it via
C, C or C(3) because there is some hidden setup
and cleanup that needs to be done by B regardless of whether an
exception is caught. Jumping into an B 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 facility. Nevertheless you are
allowed to nest B clauses.
The B block is a regular ISO-C language block without any
restrictions.
=item B(I, I, I);
This is second main construct. It builds an exception from the supplied
arguments and throws it. If an B/B clause exists in
the dynamic scope of the B call, this exception is copied into
the I of B and the program control flow is continued
in the B block. If no B/B clause exists in
the dynamic scope of the B call, the program C(3)s. The
B can be performed everywhere, including inside B and
B blocks.
=item B;
This is only valid within an B block and re-throws
the current exception (in I). It is similar to the
call B(I.ex_class, I.ex_object,
I.ex_value) but with the difference that the C,
C and C elements of the thrown exception are kept.
=item B { ... }
This directive executes its block while shielding against the throwing
of exceptions, i.e., in the dynamic scope of B any
B operation is ignored.
=item B
This is a flag which can be tested inside a block to test whether the
current scope is exception catching (by B somewhere in the
dynamic scope) or not.
=item B
This is a flag which can be tested inside a block to test whether the
current scope is exception shielding (by B somewhere in the
dynamic scope) or not.
=back
=head1 IMPLEMENTATION CONTROL
B uses a very light-weight but still flexible exception
facility implementation. The following adjustments can be made before
including the F header:
=head2 Machine Context
In order to move the program control flow from the exception throw point
(B) to the catch point (B), B uses four
macros:
=over 4
=item B<__ex_mctx_struct>
This is the contents of the machine context structure. A pointer to such
a machine context is passed to the following macros as I.
=item B<__ex_mctx_save>(I)
This is called by the prolog of B to save the current machine
context in I. This function has to return true (not C<0>) after
saving. If the machine context is restored (by B<__ex_mctx_restore>) it
has to return false (C<0>).
=item B<__ex_mctx_restored>(I)
This is called by the epilog of B to perform additional
operations at the new (restored) machine context after an exception was
cought. Usually this is a no-operation macro.
=item B<__ex_mctx_restore>(I)
This is called by B at the old machine context in order to
restore the machine context of the B/B clause which
will catch the exception.
=back
The default implementation (define C<__EX_MCTX_USE_SJLJ__> or as long
C<__EX_MCTX_USE_CUSTOM__> is not defined) uses B jmp_buf(3):
#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 one can define C<__EX_MCTX_USE_SSJLJ__> to use B
sigjmp_buf(3) and C<__EX_MCTX_USE_MCSC__> to use B ucontext(3).
For using a custom implementation define C<__EX_MCTX_USE_CUSTOM__> and
provide own macro definitions for the four B<__ex_mctx_xxxx>.
=head2 Exception Context
In order to maintain the exception catching stack and for passing the
exception between the throw point and the catch point, B uses
a global exception context macro B<__ex_ctx> holding a pointer to a
structure of type B. This is a private data structure and
should be treated as opaque by the user.
By default (define C<__EX_CTX_USE_GLOBAL__> or as long as
C<__EX_CTX_USE_CUSTOM__> is not defined) this references a global
variable C<__ex_ctx_global>. This variable is either defined by the user
by placing the directive "C" macro somewhere outside of
any functions in the application or alternatively link the F
library to the application.
Alternatively one can define C<__EX_CTX_USE_STATIC__> to get a static
declaration directly inside the header file (for small environments
where F is included once only) or define C<__EX_CTX_USE_CUSTOM__>
and provide an own macro definition for B<__ex_ctx>.
To initialize an exception context structure there are two macros
defined: B for static initialization and
B(BI) for dynamic initialization.
=head2 Termination Handler
In case there is an exception thrown which is not caught by
any B/B clauses, B calls the macro
B<__ex_terminate>(C). It receives a pointer to the exception
object which should have been thrown.
By default (define C<__EX_TERMINATE_USE_ABORT__> or as long as
C<__EX_TERMINATE_USE_CUSTOM__> is not defined) this prints a message of
the form "C<**EX: UNCAUGHT EXCEPTION: class=0xXXXXXXXX object=0xXXXXXXXX
value=0xXXXXXXX [file:123:func]>" to F and then calls abort(3)
in order to terminate the application.
Alternatively one can define C<__EX_TERMINATE_USE_NOOP__> to ignore the
exception or or define C<__EX_TERMINATE_USE_CUSTOM__> and provide an own
macro definition for B<__ex_terminate>.
=head2 Namespace Mapping
B implementation consistently uses the B, B<__ex_>
and B<__EX_> prefixes for namespace protection. But at least the
B prefix for the API macros B, B, B,
B and B sometimes have a unpleasant optical
appearance. Especially because B is modeled after the exception
facility in ISO C++ where there is no such prefix on the directives.
For this B optionally provides the ability to provide
additional namespace mappings for those API macros. By default (define
C<__EX_NS_USE_CXX__> o or as long as C<__EX_CTX_USE_CUSTOM__> and
C<__cplusplus> is not defined) you can additional C++ style macros named
B, B, B and B. As an alternative you
can define C<__EX_NS_USE_UCCXX__> to get the same but with an (more
namespace safe) upper case first letter.
=head1 MULTITHREADING ENVIRONMENTS
B 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 B to work correctly in a
multi-threading environment like B or B.
There are 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. Here are two examples:
=head2 GNU pth
Using B with B is straight-forward. One just have to
wrap pth_init() and pth_spawn() to tie the exception facility to the
threading facility.
=over 4
=item F
#ifndef __PTH_EX_H__
#define __PTH_EX_H__
/* include GNU pth API */
#include "pth.h"
/* configure and include OSSP ex API */
#define __EX_CTX_USE_CUSTOM__
#define __ex_ctx \
(ex_ctx_t *)pth_getkey(ex_ctx_key)
#define __ex_terminate(e) \
pth_exit(e->ex_value)
#include "ex.h"
int pth_init_ex(void);
pth_t pth_spawn_ex(pth_attr_t attr, void *(*entry)(void *), void *arg);
#ifndef PTH_EX_C
#define pth_init pth_ex_init
#define pth_spawn pth_ex_spawn
#endif
#endif /* __PTH_EX_H__ */
=item F
#define PTH_EX_C
#include "pth_ex.h"
/* context storage key */
static pth_key_t ex_ctx_key;
/* context destructor */
void ex_ctx_destroy(void *data)
{
if (data != NULL)
free(data);
return;
}
/* pth_init() wrapper */
int pth_init_ex(void)
{
int rc;
rc = pth_init();
pth_key_create(&ex_ctx_key, ex_ctx_destroy);
return rc;
}
/* internal thread entry wrapper information */
typedef struct {
void *(*entry)(void *);
void *arg;
} pth_spawn_ex_t;
/* internal thread entry wrapper */
static void *pth_spawn_wrapper(void *arg)
{
pth_spawn_ex_t *wrapper;
ex_ctx_t *ex_ctx;
wrapper = (pth_spawn_ex_t *)arg;
ex_ctx = (ex_ctx_t *)malloc(sizeof(ex_ctx_t));
EX_CTX_INITIALIZE(ex_ctx);
pth_key_setdata(ex_ctx_key, ex_ctx);
return wrapper.entry(wrapper.arg);
}
/* pth_spawn() wrapper */
pth_t pth_spawn_ex(pth_attr_t attr, void *(*entry)(void *), void *arg)
{
pth_t tid;
pth_spawn_ex_t wrapper;
wrapper.entry = entry;
wrapper.arg = arg;
tid = pth_spawn(attr, pth_spawn_wrapper, &wrapper);
return tid;
}
=back
=head2 POSIX pthreads
Using B inside a B environment is identical to
using it inside B. You can use the same solution as above by
just replacing all B prefixes with B prefixes.
=head1 EXAMPLES
As a full real-life example we will look how you can add optional B based exception handling support to a library B. The original
library looks like this:
=over 2
=item F
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);
=item F
#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;
}
=back
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:
=over 4
=item F
...
foo_rc_t foo_ex(foo_t *foo, int use);
...
=item F
#include "foo.h"
#ifdef WITH_EX
#include "ex.h"
char foo_ex_class = "foo"; /* class identifier */
int foo_ex_use = 0; /* class disable flag */
#endif
struct foo_st {
#ifdef WITH_EX
int ex_use; /* object disable flag */
#endif
...
}
#ifdef WITH_EX
#define FOO_ERR(ctx,err) \
(( ex_shielding \
|| !foo_ex_use \
|| ((ctx) != NULL && !(ctx)->ex_use))
? FOO_ERR_##err \
: ex_throw(&foo_ex_class, ctx, FOO_ERR_##err))
#else
#define FOO_ERR(ctx,err) \
(FOO_ERR_##err)
#endif
foo_rc_t foo_create(foo_t **foo)
{
if ((*foo = (foo_t)malloc(sizeof(foo))) == NULL)
return FOO_ERR(NULL, SYS);
(*foo)->ex_use = 0;
(*foo)->... = ...
return FOO_OK;
}
foo_rc_t foo_ex(foo_t *foo, int use)
{
if (foo == NULL)
return FOO_ERR(foo, ARG);
#ifndef WITH_EX
return FOO_ERR_IMP;
#else
foo->ex_use = use;
return FOO_OK;
#endif
}
foo_rc_t foo_perform(foo_t *foo)
{
if (foo == NULL)
return FOO_ERR(foo, ARG);
if (...)
return FOO_ERR(foo, XXX);
return FOO_OK;
}
foo_rc_t foo_destroy(foo_t *foo)
{
if (foo == NULL)
return FOO_ERR(foo, ARG);
free(foo);
return FOO_OK;
}
=back
This way the library by default is still exactly the same. If you now
compile it with C<-DWITH_EX> you build optional exception handling
support into it, although it is still disabled and the library behaviour
is the same. But if you now enable execptions via C
the library throws exceptions where C is the C
instead of returning this value. Notice that in our example we allow the
enabling/disabling also on a per object basis, i.e., some B objects
could have exception handling enabled and others not.
=head1 SEE ALSO
C++ try/catch/throw language directives;
setjmp(3), longjmp(3), sigsetjmp(3), siglongjmp(3).
=head1 HISTORY
B was invented in January 2002 by Ralf S. Engelschall
for use inside the OSSP project. Its creation was prompted by
the requirement to reduce the error handling inside B. The core B/B clause implementation is
derived from B 2.0.0, a similar library written 2000 by
Adam M. Costello Eamc@cs.berkeley.eduE and Cosmin Truta
Ecosmin@cs.toronto.eduE.
=head1 AUTHORS
Ralf S. Engelschall
rse@engelschall.com
www.engelschall.com
=cut