Index: ossp-pkg/ex/ex.pod RCS File: /v/ossp/cvs/ossp-pkg/ex/ex.pod,v rcsdiff -q -kk '-r1.16' '-r1.17' -u '/v/ossp/cvs/ossp-pkg/ex/ex.pod,v' 2>/dev/null --- ex.pod 2002/01/30 13:39:41 1.16 +++ ex.pod 2002/01/31 20:22:03 1.17 @@ -53,23 +53,24 @@ B is a small B style exception handling library for use in the B 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. +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. =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 value 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 the -B type "C". Exceptions are created on-the-fly by the +An B 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 value the thrower wants to communicate. All three parts are +of type "C" internally, but every value is useable which can +be lossless casted this type. Exceptions are created on-the-fly by the B command. =head2 APPLICATION PROGRAMMER INTERFACE (API) @@ -81,8 +82,8 @@ =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 +initialized manually. Instead it is initialized by an B clause +and just used read-only inside its block. Such a variable of type B consists of six attributes: =over 2 @@ -90,108 +91,111 @@ =item CI This is the I argument of the B call which created -the exception. This globally and uniquely identifies the class of +the exception. This should globally and uniquely identify the class of I. Usually this is a pointer to a static object (variable, -structure or function) which identifies the thrower. +structure or function) which identifies the class of the thrower and +allows the catcher to correctly handle I. =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. +the exception. This should globally and uniquely identify the class +instance of I (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 +information to I. =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. +This is the I argument of the B 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 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. +This is the file name of the B 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 +This is the line number inside the B 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 +This is the function name (if determinable, else "C<#NA#>") inside the +B 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 I [B I] B (I) I -This is the main construct provided by B. It is modeled after -the B C-C clause which in turn is very similar to -an B C-C clause. It consists of an B block -I which forms the dynamic scope of the exception handling (i.e. -exceptions directly thrown there or thrown from its sub-routines are -catched), an optional B block I for performing -cleanup operations and an B block I where the caught -exceptions are handled. +This is the primary syntactical construct provided by B. It +is modeled after the B C-C clause which in turn +is very similar to an B C-C clause. It consists of an +B block I which forms the dynamic scope for exception +handling (i.e. exceptions directly thrown there or thrown from its +sub-routines are caught), an optional B block I for +performing cleanup operations and an B block I where +the caught exceptions are handled. The control flow in case no exception is thrown is simply I, -optionally followed by I. I is skipped. The control flow -in case an exception is thrown is: I (up to the statement where -the exception is thrown), optionally followed by I, followed by -I. +optionally followed by I; I is skipped. The control +flow in case an exception is thrown is: I (up to the statement +where the exception is thrown only), optionally followed by I, +followed by I. -The B, B and B cannot be used seperately, -they work only in combination. And in contrast to B there -is only one B block and not multiple ones (all B -exceptions are of the same B 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 B language block of -statement(s), 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 +The B, B and B cannot be used separately, +they work only in combination because they form a language clause +as a whole. In contrast to B there is only one B +block and not multiple ones (all B exceptions are of the same +B type B). If an exception is caught, it is stored in +I for inspection inside the B block. Although having +to be declared outside, the I value is only valid within +the B block. But the variable can be re-used in subsequent +B clauses, of course. + +The B block is a regular B language statement block, but +it is not allowed to jump into it via C or C(3) or out +of it via C, 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 +handling facility. Nevertheless you are allowed to nest B clauses. The B and B blocks are regular B language -block of statement(s) without any restrictions. You are even allowed to -throw (and in the B block to re-throw) an exception. +statement blocks without any restrictions. You are even allowed to throw +(and in the B block to re-throw) an exception. There is just one subtle portability detail you have to remember about B blocks: all accessible B objects have the (expected) -values as of the time B was called, except that the values of -objects of automatic storage invocation duration that do not have the -C storage class I have been changed between the B -invocation and B are indeterminate. This is both because -you usually do not know which commands in the B were already -successful before the exception was thrown and because the underlying -B setjmp(3) facility applies those restrictions. +values as of the time B was called, except that the values +of objects of automatic storage invocation duration that do not have +the C storage class I have been changed between the +B invocation and B are indeterminate. This is because +both you usually do not know which commands in the B were +already successful before the exception was thrown (logically speaking) +and because the underlying B setjmp(3) facility applies those +restrictions (technically speaking). =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 +This builds an exception from the supplied arguments and throws it. +If an B/B clause formed the dynamic scope of the +B call, this exception is copied into the I of its +B clause 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. @@ -200,26 +204,27 @@ 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. +I.ex_value) except for the difference that the C, +C and C elements of the caught exception are passed +through as it would have been never caught. =item B I This directive executes I while shielding it against the throwing of exceptions, i.e., inside the dynamic scope of B all -B operations are ignored. +B operations are silently ignored. =item B -This is a boolean 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. +This is a boolean flag which can be checked inside the dynamic scope +of an B clause to test whether the current scope is exception +catching. =item B -This is a boolean 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. +This is a boolean flag which can be checked inside the dynamic scope of +an B clause to test whether the current scope is exception +shielding. =back @@ -239,23 +244,25 @@ =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. +This holds 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) +=item B<__ex_mctx_save>(__ex_mctx_struct *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>). +has to return false (C<0>). In other words, this function has to return +twice and indicate the particular situation with the provided return +code. -=item B<__ex_mctx_restored>(I) +=item B<__ex_mctx_restored>(__ex_mctx_struct *I) This is called by the epilog of B to perform additional operations at the new (restored) machine context after an exception was caught. Usually this is a no-operation macro. -=item B<__ex_mctx_restore>(I) +=item B<__ex_mctx_restore>(__ex_mctx_struct *I) This is called by B at the old machine context in order to restore the machine context of the B/B clause which @@ -263,66 +270,69 @@ =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): +The default implementation (define C<__EX_MCTX_SJLJ__> or as long +C<__EX_MCTX_CUSTOM__> is not defined) uses the B 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 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>. +Alternatively, you can define C<__EX_MCTX_SSJLJ__> to use B +sigjmp_buf(3) or C<__EX_MCTX_MCSC__> to use B ucontext(3). For +using a custom implementation define C<__EX_MCTX_CUSTOM__> and provide +own definitions for the four B<__ex_mctx_xxxx> macros. =head2 Exception Context In order to maintain the exception catching stack and for passing the exception between the throw and the catch point, B uses a -global exception context, returned on-the-fly by the callback "ex_ctx_t -*(*B<__ex_ctx>)(void)". +global exception context, returned on-the-fly by the callback "CB<__ex_ctx>C<)(void)>". -The default B<__ex_ctx> (B<__ex_ctx_default> as provided by F) -returns a pointer to a static B context. For use in -multi-threading environments, this should be overwritten with a -per-thread context structure. +By default, B<__ex_ctx> (which is B<__ex_ctx_default> as provided by +F) returns a pointer to a static C context. For use in +multi-threading environments, this should be overwritten with a callback +function returning a per-thread context structure. To initialize an exception context structure there are two macros -defined: B for static initialization and -B(BI) for dynamic initialization. +defined: "B" for static initialization and +"CBC<(ex_ctx_t *)>" 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 callback "void -(*B<__ex_terminate>)(C)". It receives a pointer to the exception -object which should have been thrown. - -The default B<__ex_terminate> (B<__ex_terminate_default> as provided by -F) 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. -For use in multi-threading environments, this should be overwritten with -a callback function which terminates only the current thread. +B/B clauses, B calls the callback "CB<__ex_terminate>C<)(ex_t *)>". It receives a pointer to the +exception object which should have been thrown. + +By default, B<__ex_terminate> (which is B<__ex_terminate_default> +as provided by F) prints a message of the form "C<**EX: +UNCAUGHT EXCEPTION: class=0xXXXXXXXX object=0xXXXXXXXX value=0xXXXXXXX +[xxxx:NNN:xxxx]>" to F 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. =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, B and B sometimes have a unpleasant -optical appearance. Especially because B is modeled after the -exception facility in B 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__> or as long as C<__EX_NS_USE_CUSTOM__> and -C<__cplusplus> is not defined) you can additional C++ style macros -named B, 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. +The B implementation consistently uses the "C", "C<__ex_>" +and "C<__EX_>" prefixes for namespace protection. But at least +the "C" prefix for the API macros B, B, +B, B, B and B sometimes have +an unpleasant optical appearance. Especially because B is +modeled after the exception facility of B where there is no +such prefix on the language directives, of course. + +For this, B optionally provides the ability to provide +additional namespace mappings for those API elements. By default (define +C<__EX_NS_CXX__> or as long as C<__EX_NS_CUSTOM__> and C<__cplusplus> +is not defined) you can additionally use the B style names +B, B, B, B and B. As an +alternative you can define C<__EX_NS_UCCXX__> to get the same but with a +more namespace safe upper case first letter. =head1 MULTITHREADING ENVIRONMENTS @@ -331,26 +341,29 @@ 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 +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. +dispatching mechanism. =head2 GNU pth -Using B with B is straight-forward, because B -1.5 (or higher) already has support for B built-in. All which -is needed is that B is configured with the Autoconf option -C<--with-ex>. Then each thread has its own B exception context. +Using B together with B is straight-forward, because +B 1.5 (and higher) already has support for B built-in. +All which is needed is that B is configured with the B option C<--with-ex>. Then each B user-space thread +has its own B exception context automatically. The default +of using B jmp_buf(3) does not conflict with the thread +dispatching mechanisms used by B. =head2 POSIX pthreads -Using B inside a standard B environment is -also straight-forward, although it requires additional coding. What -you basically have to do is to make sure that the B<__ex_ctx> becomes -a per-thread context and that B<__ex_terminate> terminates only the -current thread. To get an impression, a small utility library for this -follows: +Using B inside an arbitrary B standard +compliant environment is also straight-forward, although it requires +extra coding. What you basically have to do is to make sure that the +B<__ex_ctx> becomes a per-thread context and that B<__ex_terminate> +terminates only the current thread. To get an impression, a small +utility library for this follows: =over 4 @@ -362,7 +375,7 @@ #include int pthread_init_ex (void); -int pthread_create_ex (pthread_t *thread, const pthread_attr_t *attr, void *(*entry)(void *), void *arg); +int pthread_create_ex (pthread_t *, const pthread_attr_t *, void *(*)(void *), void *); #ifndef PTHREAD_EX_INTERNAL #define pthread_init pthread_init_ex @@ -451,13 +464,13 @@ =back -Now all which is required is that you include F after -the standard F header and to call B() once at -startup of your program. +Now all which is required is that you include F after the +standard F header and to call B once at startup +of your program. =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: @@ -555,72 +568,49 @@ =item F ... - foo_rc_t foo_ex(foo_t *foo, int use); + extern const char foo_id[]; ... =item F #include "foo.h" + const char foo_id[] = "foo 1.0"; + #ifdef WITH_EX #include "ex.h" - char foo_ex_class = "foo"; /* class identifier */ - int foo_ex_use = 0; /* class disable flag */ + #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 { - #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; + return FOO_RC(FOO_ERR_SYS); (*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); + return FOO_RC(FOO_ERR_ARG); if (...) - return FOO_ERR(foo, XXX); + return FOO_RC(FOO_ERR_XXX); return FOO_OK; } foo_rc_t foo_destroy(foo_t *foo) { if (foo == NULL) - return FOO_ERR(foo, ARG); + return FOO_RC(FOO_ERR_ARG); free(foo); return FOO_OK; } @@ -628,29 +618,30 @@ =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. +compile it with C<-DWITH_EX> you activate exception handling support. +This means that all API functions throw exceptions where C is +the C instead of returning this value. =head1 SEE ALSO -C++ try/catch/throw language directives; -setjmp(3), longjmp(3), sigsetjmp(3), siglongjmp(3). +B C/C/C; +B C/C/C/C; +B jmp_buf(3)/setjmp(3)/longjmp(3); +B sigjmp_buf(3)/sigsetjmp(3)/siglongjump(3); +B ucontext(3)/setcontext(3)/getcontext(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 is inspired by B and the 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. The B clause is inspired by -the B C clause. +B was invented in January 2002 by Ralf S. Engelschall +Erse@engelschall.comE for use inside the B project. +Its creation was prompted by the requirement to reduce the error +handling inside B. The core B/B clause +was inspired by B and the implementation was partly +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. The B clause was inspired by +the B C clause. The B feature was inspired by an +C shielding facility used in the B implementation. =head1 AUTHORS