OSSP CVS Repository

ossp - ossp-pkg/ex/ex.pod 1.3
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/ex/ex.pod 1.3
##
##  Copyright (c) 2001-2002 The OSSP Project <http://www.ossp.org/>
##  Copyright (c) 2001-2002 Cable & Wireless Deutschland <http://www.cw.com/de/>
##
##  This file is part of OSSP ex, an exception library
##  which can be found at http://www.ossp.org/pkg/ex/.
##
##  This program is free software; you can redistribute it and/or
##  modify it under the terms of the GNU General Public  License
##  as published by the Free Software Foundation; either version
##  2.0 of the License, or (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
##  General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this file; if not, write to the Free Software
##  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
##  USA, or contact the OSSP project <ossp@ossp.org>.
##
##  ex.pod: exception library manual page
##

=pod

=head1 NAME

B<OSSP ex> - Exception Library

=head1 SYNOPSIS

B<ex_t> I<variable>;

B<ex_try> { ... } B<ex_catch> (I<variable>) { ... }

B<ex_throw>(I<class>, I<object>, I<value>);

B<ex_rethrow>;

B<ex_shield> { ... };

if (B<ex_shielded>) ...

=head1 DESCRIPTION

B<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 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 E<lt>I<class>,I<object>,I<value>E<gt> where
I<class> identifies the class of the exception thrower, I<object>
identifies the particular class instance of the exception thrower, and
I<value> is the exceptional return code the thrower wants to send to the
catcher. All three parts are of type C<void *> internally, but every
value can be used which can be (automatically) casted to a C<void *>.
Exceptions are created on-the-fly by the B<ex_throw> command.

=head2 APPLICATION PROGRAMMER INTERFACE (API)

The B<OSSP ex> API consists of four different elements:

=over 4

=item B<ex_t> I<variable>;

This is the declaration of an exception variable. It is usually never
initialized manually. Instead it is initialized by an B<ex_catch> block
and just used read-only inside the block. Such a variable of type
B<ex_t> consists of six attributes:

=over 2

=item C<void *>I<ex_class>

This is the I<class> argument of the B<ex_throw> call which created
the exception. This globally and uniquely identifies the class of
I<ex_value>. Usually this is a pointer to a static object (variable,
structure or function) which identifies the thrower.

=item C<void *>I<ex_object>

This is the I<object> argument of the B<ex_throw> call which created
the exception. This globally and uniquely identifies the class instance
of I<ex_value> (in case multiple instances exists). Usually this a
pointer to a dynamic object (structure) which identifiers the particular
instance of the thrower.

=item C<void *>I<ex_value>

This is the I<value> argument of the B<ex_throw> 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 C<return>ed
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 C<char *>I<ex_file> 

This is the file name of the source where the B<ex_throw> 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<int> I<ex_line>

This is the line number inside the source file name where the
B<ex_throw> 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<char *>I<ex_func>

This is the function name (if determinable, else "C<#NA#>") inside
the source file name where the B<ex_throw> 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<ex_try> { ... } B<ex_catch> (I<variable>) { ... }

This is the main construct provided by B<OSSP ex>. It is modeled after
the ISO-C++ C<try>-C<catch> construct which in turn is very similar to
an ISO-C C<if>-C<else> construct. It consists of an B<ex_try> 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<ex_catch> block where the caught exceptions are handled.

The B<ex_trx> and B<ex_catch> cannot be used seperately, they work only
in combination. In contrast to ISO-C++ there is only one B<ex_catch>
block and not multiple ones (all B<OSSP ex> exceptions are of the
same ISO-C type B<ex_t>). If an exception is caught, it is stored in
I<variable> for inspection (or re-throwing) inside the B<ex_catch>
block. Although declared outside, the I<variable> is only valid
within the B<ex_catch> block. But it can be re-used in following
B<ex_try>/B<ex_catch> constructs, of course.

The B<ex_try> block is a regular ISO-C language block, but it is not
allowed to jump into it via C<goto> or C<longjmp>(3) or out of it via
C<return>, C<goto> or C<longjmp>(3) because there is some hidden setup
and cleanup that needs to be done by B<OSSP ex> regardless of whether an
exception is caught. Jumping into an B<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 facility. Nevertheless you are
allowed to nest B<ex_try> clauses.

The B<ex_catch> block is a regular ISO-C language block without any
restrictions.

=item B<ex_throw>(I<class>, I<object>, I<value>);

This is second main construct. It builds an exception from the supplied
arguments and throws it. If an B<ex_try>/B<ex_catch> clause exists in
the dynamic scope of the B<ex_throw> call, this exception is copied into
the I<variable> of B<ex_catch> and the program control flow is continued
in the B<ex_catch> block. If no B<ex_try>/B<ex_catch> clause exists in
the dynamic scope of the B<ex_throw> call, the program C<abort>(3)s. The
B<ex_throw> can be performed everywhere, including inside B<ex_try> and
B<ex_catch> blocks.

=item B<ex_rethrow>;

This is only valid within an B<ex_catch> block and re-throws
the current exception (in I<variable>). It is similar to the
call B<ex_throw>(I<variable>.ex_class, I<variable>.ex_object,
I<variable>.ex_value) but with the difference that the C<ex_file>,
C<ex_line> and C<ex_func> elements of the thrown exception are kept.

=item B<ex_shield> { ... }

This directive executes its block while shielding against the throwing
of exceptions, i.e., in the dynamic scope of B<ex_shield> any
B<ex_throw> operation is ignored. 

=item B<ex_shielded>

This is a flag which can be tested inside a block to test whether the
current scope is exception shielded (by B<ex_shield> somewhere in the
dynamic scope) or not.

=back

=head1 IMPLEMENTATION CONTROL

B<OSSP ex> uses a very light-weight but still flexible exception
facility implementation. The following adjustments can be made before
including the F<ex.h> header:

=head2 Machine Context

In order to move the program control flow from the exception throw point
(B<ex_throw>) to the catch point (B<ex_catch>), B<OSSP ex> 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<mctx>. 

=item B<__ex_mctx_save>(I<mctx>)

This is called by the prolog of B<ex_try> to save the current machine
context in I<mctx>. 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<mctx>)

This is called by the epilog of B<ex_try> 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<mctx>)

This is called by B<ex_throw> at the old machine context in order to
restore the machine context of the B<ex_try>/B<ex_catch> 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<ISO C> 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<POSIX.1>
sigjmp_buf(3) and C<__EX_MCTX_USE_MCSC__> to use B<POSIX.1> 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<OSSP ex> uses
a global exception context macro B<__ex_ctx> holding a pointer to a
structure of type B<ex_ctx_t>. 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<EX_CTX_GLOBAL>" macro somewhere outside of
any functions in the application or alternatively link the F<libex>
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<ex.h> 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<EX_CTX_INITIALIZER> for static initialization and
B<EX_CTX_INITIALIZE>(B<ex_ctx_t *>I<ctx>) for dynamic initialization.

=head2 Termination Handler

In case there is an exception thrown which is not caught by
any B<ex_try>/B<ex_catch> clauses, B<OSSP ex> calls the macro
B<__ex_terminate>(C<ex_t *>). 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<stderr> 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<OSSP ex> implementation consistently uses the B<ex_>, B<__ex_>
and B<__EX_> prefixes for namespace protection. But at least the
B<ex_> prefix for the API macros B<ex_try>, B<ex_catch>, B<ex_throw>,
B<ex_rethrow>, B<ex_shield> and B<ex_shielded> sometimes have a
unpleasant optical appearance. Especially because B<OSSP rc> is modeled
after the exception facility in ISO C++ where there is no such prefix on
the directives.

For this B<OSSP ex> 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<catch>, B<throw>, B<rethrow>, B<shield> and B<shielded>. 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<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 B<OSSP ex> to work correctly in a
multi-threading environment like B<POSIX pthreads> or B<GNU pth>.

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<OSSP ex> with B<GNU pth> 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<pth_ex.h>

 #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<pth_ex.c>

 #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<OSSP ex> inside a B<POSIX pthreads> environment is identical to
using it inside B<GNU pth>. You can use the same solution as above by
just replacing all B<pth_> prefixes with B<pthread_> prefixes.

=head1 EXAMPLES

As a full real-life example we will look how you can add optional B<OSSP
ex> based exception handling support to a library B<foo>. The original
library looks like this:

=over 2

=item F<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);

=item F<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;
 }

=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.h>

 ...
 foo_rc_t foo_ex(foo_t *foo, int use);
 ...

=item F<foo.c>

 #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_shielded \
       || !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<foo_ex(foo, 1);>
the library throws exceptions where C<ex_value> is the C<foo_rc_t>
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<foo> 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

<OSSP ex> 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<OSSP
lmtp2nntp>. The core B<try>/B<catch> clause implementation is
derived from B<cexcept> 2.0.0, a similar library written 2000 by
Adam M. Costello E<lt>amc@cs.berkeley.eduE<gt> and Cosmin Truta
E<lt>cosmin@cs.toronto.eduE<gt>.

=head1 AUTHORS

 Ralf S. Engelschall
 rse@engelschall.com
 www.engelschall.com

=cut


CVSTrac 2.0.1