## ## 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