Index: ossp-pkg/ex/TODO RCS File: /v/ossp/cvs/ossp-pkg/ex/Attic/TODO,v rcsdiff -q -kk '-r1.2' '-r1.3' -u '/v/ossp/cvs/ossp-pkg/ex/Attic/TODO,v' 2>/dev/null --- TODO 2002/01/25 18:32:25 1.2 +++ TODO 2002/01/25 22:23:17 1.3 @@ -1,3 +1,3 @@ -- default handler -- pth -- disable throwing +- devtool environment +- test thread examples +- overhault ex_text.c Index: ossp-pkg/ex/ex.h RCS File: /v/ossp/cvs/ossp-pkg/ex/ex.h,v rcsdiff -q -kk '-r1.3' '-r1.4' -u '/v/ossp/cvs/ossp-pkg/ex/ex.h,v' 2>/dev/null --- ex.h 2002/01/25 18:36:17 1.3 +++ ex.h 2002/01/25 22:23:17 1.4 @@ -26,9 +26,13 @@ #ifndef __EX_H__ #define __EX_H__ -/* the required ISO-C standard facilities */ -#include /* for NULL */ -#include /* for jmp_buf, setjmp(3), longjmp(3) */ +/* required ISO-C standard facilities */ +#include + +/* convinience define */ +#ifndef NULL +#define NULL (void *)0 +#endif /* determine how the current function name can be fetched */ #if ( defined(__STDC__) \ @@ -45,19 +49,30 @@ #endif /* the machine context */ -#ifndef __ex_mctx_st -#define __ex_mctx_st struct { jmp_buf jb; } -#endif -#ifndef __ex_mctx_save +#if defined(__EX_MCTX_USE_MCSC__) +#include /* POSIX.1 ucontext(3) */ +#define __ex_mctx_struct ucontext_t uc; +#define __ex_mctx_save(mctx) (getcontext(&(mctx)->uc) == 0) +#define __ex_mctx_restored(mctx) /* noop */ +#define __ex_mctx_restore(mctx) (void)setcontext(&(mctx)->uc) + +#elif defined(__EX_MCTX_USE_SSJLJ__) +#include /* POSIX.1 sigjmp_buf(3) */ +#define __ex_mctx_struct sigjmp_buf jb; +#define __ex_mctx_save(mctx) (sigsetjmp((mctx)->jb, 1) == 0) +#define __ex_mctx_restored(mctx) /* noop */ +#define __ex_mctx_restore(mctx) (void)siglongjmp((mctx)->jb, 1) + +#elif defined(__EX_MCTX_USE_SJLJ__) || !defined(__EX_MCTX_USE_CUSTOM__) +#include /* ISO C jmp_buf(3) */ +#define __ex_mctx_struct jmp_buf jb; #define __ex_mctx_save(mctx) (setjmp((mctx)->jb) == 0) -#endif -#ifndef __ex_mctx_restore +#define __ex_mctx_restored(mctx) /* noop */ #define __ex_mctx_restore(mctx) (void)longjmp((mctx)->jb, 1) #endif -#ifndef __ex_mctx_restored -#define __ex_mctx_restored(mctx) /* NOOP */ -#endif -typedef __ex_mctx_st __ex_mctx_t; + +/* declare the machine context type */ +typedef struct { __ex_mctx_struct } __ex_mctx_t; /* declare the exception type (public) */ typedef struct { @@ -71,34 +86,65 @@ char *ex_func; } ex_t; -/* declare the exception context type (private) */ +/* declare the context type (private) */ typedef struct { - __ex_mctx_t *ctx_mctx_prev; /* previous jump buffer */ - int ctx_caught; /* flag whether exception was caught */ - volatile ex_t ctx_ex; /* the exception temporary storage */ + __ex_mctx_t *ctx_mctx; /* permanent machine context of enclosing try/catch */ + int ctx_disabled; /* permanent flag whether exception handling is disabled */ + int ctx_caught; /* temporary flag whether exception was caught */ + volatile ex_t ctx_ex; /* temporary exception storage */ } ex_ctx_t; -/* exception context */ -#ifndef __ex_ctx +/* the static and dynamic initializers for a context structure */ +#define EX_CTX_INITIALIZER \ + { NULL, 0, 0, { NULL, NULL, NULL, NULL, 0, NULL } } +#define EX_CTX_INITIALIZE(ctx) \ + do { \ + (ctx)->ctx_mctx = NULL; \ + (ctx)->ctx_disabled = 0; \ + (ctx)->ctx_caught = 0; \ + (ctx)->ctx_ex.ex_class = NULL; \ + (ctx)->ctx_ex.ex_object = NULL; \ + (ctx)->ctx_ex.ex_value = NULL; \ + (ctx)->ctx_ex.ex_file = NULL; \ + (ctx)->ctx_ex.ex_line = 0; \ + (ctx)->ctx_ex.ex_func = NULL; \ + } while (0) + +/* the exception context */ +#if defined(__EX_CTX_USE_STATIC__) +static ex_ctx_t __ex_ctx_global; +#define __ex_ctx (&__ex_ctx_global) +#elif defined(__EX_CTX_USE_GLOBAL__) || !(__EX_CTX_USE_CUSTOM__) +#define EX_CTX_GLOBAL ex_ctx_t __ex_ctx_global; +extern ex_ctx_t __ex_ctx_global; #define __ex_ctx (&__ex_ctx_global) -#ifdef __EX_STATIC_CTX__ -static ex_ctx_t __ex_ctx_global; /* for very small environments */ -#else -extern ex_ctx_t __ex_ctx_global; /* for non-MT environments (require libex.a) */ #endif + +/* the termination handler */ +#if defined(__EX_TERMINATE_USE_NOOP__) +#define __ex_terminate(e) \ + /* noop */ +#elif defined(__EX_TERMINATE_USE_ABORT__) || !(__EX_CTX_USE_CUSTOM__) +#define __ex_terminate(e) \ + ( fprintf(stderr, \ + "**EX: UNCAUGHT EXCEPTION: " \ + "class=0x%lx object=0x%lx value=0x%lx [%s:%d@%s]\n", \ + (long)((e)->ex_class), (long)((e)->ex_object), (long)((e)->ex_value), \ + (e)->ex_file, (e)->ex_line, (e)->ex_func), \ + abort() ) #endif -/* block for trying execution */ +/* the block for trying execution */ #define ex_try \ { \ - __ex_mctx_t *__ex_mctx_prev; \ + __ex_mctx_t *__ex_mctx_en; \ __ex_mctx_t __ex_mctx_me; \ - __ex_mctx_prev = __ex_ctx->ctx_mctx_prev; \ - __ex_ctx->ctx_mctx_prev = &__ex_mctx_me; \ + __ex_mctx_en = __ex_ctx->ctx_mctx; \ + __ex_ctx->ctx_mctx = &__ex_mctx_me; \ if (__ex_mctx_save(&__ex_mctx_me)) { \ if (1) -/* block for catching an exception */ +/* the block for catching an exception */ #define ex_catch(e) \ else { \ } \ @@ -108,46 +154,55 @@ __ex_mctx_restored(&__ex_mctx_me); \ __ex_ctx->ctx_caught = 1; \ } \ - __ex_ctx->ctx_mctx_prev = __ex_mctx_prev; \ + __ex_ctx->ctx_mctx = __ex_mctx_en; \ } \ - if (!__ex_ctx->ctx_caught || ((e) = __ex_ctx->ctx_ex, 0)) { \ + if ( !(__ex_ctx->ctx_caught) \ + || ((e) = __ex_ctx->ctx_ex, 0)) { \ } \ else -/* throw a new exception */ +/* the throwing of a new exception */ #define ex_throw(c,o,v) \ - (__ex_ctx->ctx_mctx_prev == NULL ? \ - (abort(), 0) : \ - ( __ex_ctx->ctx_ex.ex_class = (void *)(c), \ - __ex_ctx->ctx_ex.ex_object = (void *)(o), \ - __ex_ctx->ctx_ex.ex_value = (void *)(v), \ - __ex_ctx->ctx_ex.ex_file = __FILE__, \ - __ex_ctx->ctx_ex.ex_line = __LINE__, \ - __ex_ctx->ctx_ex.ex_func = __EX_FUNC__, \ - __ex_mctx_restore(__ex_ctx->ctx_mctx_prev), \ - 0 \ - ) \ - ) + (__ex_ctx->ctx_disabled ? 0 : \ + (__ex_ctx->ctx_ex.ex_class = (void *)(c), \ + __ex_ctx->ctx_ex.ex_object = (void *)(o), \ + __ex_ctx->ctx_ex.ex_value = (void *)(v), \ + __ex_ctx->ctx_ex.ex_file = __FILE__, \ + __ex_ctx->ctx_ex.ex_line = __LINE__, \ + __ex_ctx->ctx_ex.ex_func = __EX_FUNC__, \ + ( __ex_ctx->ctx_mctx == NULL \ + ? (__ex_terminate(&(__ex_ctx->ctx_ex)), -1) \ + : (__ex_mctx_restore(__ex_ctx->ctx_mctx), 1) ))) -/* re-throw a caught exception */ +/* the re-throwing of an already caught exception */ #define ex_rethrow \ - (__ex_ctx->ctx_mctx_prev == NULL ? \ - (abort(), 0) : \ - (__ex_mctx_restore(__ex_ctx->ctx_mctx_prev), 0) \ - ) + (__ex_ctx->ctx_disabled ? 0 : \ + (__ex_ctx->ctx_mctx == NULL ? (__ex_terminate(&(__ex_ctx->ctx_ex)), -1) : \ + (__ex_mctx_restore(__ex_ctx->ctx_mctx), 1) )) + +/* shield an operation from exception handling */ +#define ex_shield \ + for (__ex_ctx->ctx_disabled = 1; \ + __ex_ctx->ctx_disabled == 1; \ + __ex_ctx->ctx_disabled = 0) +#define ex_shielded \ + (__ex_ctx->ctx_disabled) /* optional namespace mapping */ -#if !defined(__cplusplus) && !defined(__EX_NO_CXX_NS__) -#define try ex_try -#define catch ex_catch -#define throw ex_throw -#define rethrow ex_rethrow -#endif -#if !defined(__EX_NO_SIMPLE_NS__) +#if defined(__EX_NS_USE_UCCXX__) #define Try ex_try #define Catch ex_catch #define Throw ex_throw #define Rethrow ex_rethrow +#define Shield ex_shield +#define Shielded ex_shielded +#elif defined(__EX_NS_USE_CXX__) || (!defined(__cplusplus) && !defined(__EX_NS_USE_CUSTOM__)) +#define try ex_try +#define catch ex_catch +#define throw ex_throw +#define rethrow ex_rethrow +#define shield ex_shield +#define shielded ex_shielded #endif #endif /* __EX_H__ */ Index: ossp-pkg/ex/ex.pod RCS File: /v/ossp/cvs/ossp-pkg/ex/ex.pod,v rcsdiff -q -kk '-r1.2' '-r1.3' -u '/v/ossp/cvs/ossp-pkg/ex/ex.pod,v' 2>/dev/null --- ex.pod 2002/01/25 15:30:05 1.2 +++ ex.pod 2002/01/25 22:23:17 1.3 @@ -39,6 +39,10 @@ B; +B { ... }; + +if (B) ... + =head1 DESCRIPTION B is a small ISO-C++ style exception handling library for use @@ -173,30 +177,424 @@ 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 shielded (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 -=head1 EXAMPLE +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, 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, 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" - ex_t e; + int pth_init_ex(void); + pth_t pth_spawn_ex(pth_attr_t attr, void *(*entry)(void *), void *arg); - ex_try { + #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, ... - ex_throw (..., ..., 123); + } 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 { ... } - ex_catch (e) { - ... - if ((int)e->ex_value == 123) - ... - else if ((int)e->ex_value == 456) - ... - else - ex_rethrow; + + 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_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 +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; @@ -204,11 +602,12 @@ =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 implementation -is partly derived from B 2.0.0, a similar library written 2000 -by Adam M. Costello Eamc@cs.berkeley.eduE and Cosmin Truta + 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 Index: ossp-pkg/ex/ex_test.c RCS File: /v/ossp/cvs/ossp-pkg/ex/ex_test.c,v rcsdiff -q -kk '-r1.1' '-r1.2' -u '/v/ossp/cvs/ossp-pkg/ex/ex_test.c,v' 2>/dev/null --- ex_test.c 2002/01/25 15:25:51 1.1 +++ ex_test.c 2002/01/25 22:23:17 1.2 @@ -60,13 +60,15 @@ { srandom((unsigned int)time(NULL)); ex_t e; - + fprintf(stderr, "main-0\n"); for (;;) { fprintf(stderr, "main-1\n"); try { fprintf(stderr, "main-2\n"); - foo_func(); + ex_shield { + foo_func(); + } fprintf(stderr, "main-3\n"); for (;;) { fprintf(stderr, "main-4\n"); @@ -96,6 +98,8 @@ fprintf(stderr, " %s@%s:%d class=0x%lx object=0x%lx value=0x%lx\n", e.ex_func, e.ex_file, e.ex_line, (long)e.ex_class, (long)e.ex_object, (long)e.ex_value); fprintf(stderr, "main-12\n"); + if (e.ex_class == FOO_CLASS && (foo_rc_t)(e.ex_value) == FOO_ERR_2) + break; } } fprintf(stderr, "main-0\n");