| /* try.h -- try / catch / throw exception handling for C99 |
| Copyright (C) 2013, 2015, 2016, 2021 Mark Adler |
| Version 1.5 10 April 2021 |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the author be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| |
| Mark Adler [email protected] |
| */ |
| |
| /* |
| Version History |
| 1.0 7 Jan 2013 - First version |
| 1.1 2 Nov 2013 - Use variadic macros and functions instead of partial |
| structure assignment, allowing arbitrary arguments |
| to printf() |
| 1.2 19 Jan 2015 - Obey setjmp() invocation limits from C standard |
| 1.3 1 Mar 2015 - Add preserve to avoid use of volatile, remove retry |
| 1.4 2 Jan 2016 - Add no-return attribute to throw() |
| 1.5 10 Apr 2021 - Portability improvements |
| */ |
| |
| /* To use, include try.h in all source files that use these operations, and |
| compile and link try.c. By default, pthread threads are used to make the |
| exception handling thread-safe. If a different threads library is required, |
| then try.h and try.c must be modified to use that environment's thread-local |
| storage for the try_stack_ pointer. try.h and try.c assume that the |
| compiler and library conform to the C99 standard, at least with respect to |
| the use of variadic macro and function arguments. */ |
| |
| /* |
| try.h provides a try / catch / throw exception handler, which allows |
| catching exceptions across any number of levels of function calls. try |
| blocks can be nested as desired, with a throw going to the end of the |
| innermost enclosing try, passing the thrown information to the associated |
| catch block. A global try stack is used, to avoid having to pass exception |
| handler information through all of the functions down to the invocations of |
| throw. The try stack is thread-unique if pthread is made available. In |
| addition to the macros try, catch, and throw, the macros preserve, always, |
| punt, and drop, and the type ball_t are created. All other symbols are of |
| the form try_*_ or TRY_*_, where the final underscore should avoid conflicts |
| with application symbols. The eight exposed names can be changed easily in |
| #defines below. |
| |
| A try block encloses code that may throw an exception with the throw() |
| macro, either directly in the try block or in any function called directly |
| or indirectly from the try block. throw() must have at least one argument, |
| which is an integer. The try block is followed by a catch block whose code |
| will be executed when throw() is called with a non-zero first argument. If |
| the first argument of throw() is zero, then execution continues after the |
| catch block. If the try block completes normally, with no throw() being |
| called, then execution continues normally after the catch block. |
| |
| There can be only one catch block. catch has one argument which must be a |
| ball_t type variable declared in the current function or block containing |
| the try and catch. That variable is loaded with the information sent by the |
| throw() for use in the catch block. |
| |
| throw() may optionally include more information that is passed to the catch |
| block in the ball_t structure. throw() can have one or more arguments, |
| where the first (possibly only) argument is an integer code. The second |
| argument can be a pointer, which will be replaced by NULL in the ball_t |
| structure if not provided. The implementation of throw() in try.c assumes |
| that if the second argument is present and is not NULL, that it is a string. |
| If that string has any percent (%) signs in it, then throw() will run that |
| string through vsnprintf() with any other arguments provided after the |
| string in the throw() invocation, and save the resulting formatted string in |
| the ball_t structure. Information on whether or not the string was |
| allocated is also maintained in the ball_t structure. |
| |
| throw() in try.c can be modified to not assume that the second argument is a |
| string. For example, an application may want to assume instead that the |
| second argument is a pointer to a set of information for use in the catch |
| block. |
| |
| The catch block may conditionally do a punt(), where the argument of punt() |
| is the argument of catch. This passes the exception on to the next |
| enclosing try/catch handler. |
| |
| If a catch block does not always complete with a punt(), it should contain a |
| drop(), where the argument of drop() is the argument of catch. This frees |
| the allocated string made if vsnprintf() was used by throw() to generate the |
| string. If printf() format strings are never used, then drop() is not |
| required. |
| |
| An always block may be placed between the try and catch block. The |
| statements in that block will be executed regardless of whether or not the |
| try block completed normally. As indicated by the ordering, the always |
| block will be executed before the catch block. This block is not named |
| "finally", since it is different from the finally block in other languages |
| which is executed after the catch block. |
| |
| A naked break or continue in a try or always block will go directly to the |
| end of that block. |
| |
| try is thread-safe when compiled with pthread.h. A throw() in a thread can |
| only be caught in the same thread. If a throw() is attempted from a thread |
| without an enclosing try in that thread, even if in another thread there is |
| a try around the pthread_create() that spawned this thread, then the throw |
| will fail on an assert. Each thread has its own thread-unique try stack, |
| which starts off empty. |
| |
| If an intermediate function does not have a need for operations in a catch |
| block other than punt, and does not need an always block, then that function |
| does not need a try block. "try { block } catch (err) { punt(err); }" is |
| the same as just "block". More precisely, it's equivalent to "do { block } |
| while (0);", which replicates the behavior of a naked break or continue in a |
| block when it follows try. throw() can be used from a function that has no |
| try. All that is necessary is that there is a try somewhere up the function |
| chain that called the current function in the current thread. |
| |
| There must not be a return in any try block, nor a goto in any try block |
| that leaves that block. The always block does not catch a return from the |
| try block. There is no check or protection for an improper use of return or |
| goto. It is up to the user to assure that this doesn't happen. If it does |
| happen, then the reference to the current try block is left on the try |
| stack, and the next throw which is supposed to go to an enclosing try would |
| instead go to this try, possibly after the enclosing function has returned. |
| Mayhem will then ensue. This may be caught by the longjmp() implementation, |
| which would report "longjmp botch" and then abort. |
| |
| Any local automatic storage variables that are modified in the try block and |
| used in the catch or always block must be declared volatile. Otherwise |
| their value in the catch or always block is indeterminate. Alternatively, a |
| preserve block can be inserted after an automatic storage variable is |
| changed in a try block. The preserve block saves the state of those |
| variables at the moment preserve is executed, and effectively continues the |
| try block. The same admonition then applies to variables modified in the |
| preserve block. The only assurance is that the catch and always block will |
| have the values of the local automatic storage variables as they were at the |
| time of the last try or preserve statement, but only if they have not been |
| modified since the last try or preserve statement. As many preserve blocks |
| as desired are permitted. |
| |
| Any statements between try and always, between try and catch if there is no |
| always, or between always and catch are part of those respective try or |
| always blocks. Use of { } to enclose those blocks is optional, but { } |
| should be used anyway for clarity, style, and to inform smart source editors |
| that the enclosed code is to be indented. Enclosing the catch block with { |
| } is not optional if there is more than one statement in the block. |
| However, even if there is just one statement in the catch block, it should |
| be enclosed in { } anyway for style and editing convenience. |
| |
| The contents of the ball_t structure after the first element (int code) can |
| be customized for the application. If ball_t is customized, then the code |
| in try.c should be updated accordingly. If there is no memory allocation in |
| throw(), then drop() can be eliminated. |
| |
| |
| Summary: |
| |
| Here is the permitted structure, where [] means an optional element |
| permitted once, and []* means an optional element permitted more than once: |
| |
| try { body } [preserve { body }]* [always { clean }] catch (ball) { action } |
| |
| body must not contain a goto or return that exits body. ball must be a |
| variable of the type ball_t. "action" must always execute either a |
| drop(ball) or a punt(ball). |
| |
| A throw() may be executed in body or at any level of functions called by |
| body, and will be processed by the innermost enclosing try in the same |
| thread: |
| |
| throw(code); |
| |
| or, |
| |
| throw(code, "string"); |
| |
| or, |
| |
| throw(code, "format string", ...); |
| |
| A throw() whose first argument is not zero, and that is caught by this try, |
| will execute "clean" followed by "action". Either a throw() whose first |
| argument is zero and caught by this try, or the normal completion of body, |
| results in the execution of just "clean", with execution continuing after |
| the catch block. If "action" does not execute a punt(ball), then execution |
| continues after the catch block. If "action" does execute a punt(ball), |
| then what was caught is thrown to the next enclosing try. |
| |
| A '} preserve {' must be inserted in body after any local automatic storage |
| variable is changed in body that can be used in the always or catch block. |
| Alternatively, such variables may be declared volatile. However the use of |
| volatile can limit optimization, and has a tendency to propagate compiler |
| warnings. |
| |
| |
| Example usage: |
| |
| ball_t err; |
| char *temp = NULL; |
| try { |
| ... do something ... |
| if (ret == -1) |
| throw(1, "bad thing happened to %s\n", me); |
| temp = malloc(sizeof(me) + 1); |
| } |
| preserve { |
| if (temp == NULL) |
| throw(2, "out of memory"); |
| ... do more ... |
| if (ret == -1) |
| throw(3, "worse thing happened to %s\n", temp); |
| ... some more code ... |
| } |
| always { |
| free(temp); |
| } |
| catch (err) { |
| fputs(err.why, stderr); |
| drop(err); |
| return err.code; |
| } |
| ... end up here if nothing bad happened ... |
| |
| |
| More involved example: |
| |
| void check_part(void) |
| { |
| ball_t err; |
| |
| try { |
| ... |
| if (part == bad1) |
| throw(1); |
| ... |
| if (part == bad2) |
| throw(1); |
| ... |
| } |
| catch (err) { |
| drop(err); |
| throw(3, "part was bad"); |
| } |
| } |
| |
| void check_input(void) |
| { |
| ... |
| if (input == wrong) |
| throw(4, "input was wrong"); |
| ... |
| if (input == stupid) |
| throw(5, "input was stupid"); |
| ... |
| check_part(); |
| ... |
| } |
| |
| void *build_something(void) |
| { |
| ball_t err; |
| volatile void *thing; |
| try { |
| thing = malloc(sizeof(struct thing)); |
| ... build up thing ... |
| check_input(); |
| ... finish building it ... |
| } |
| catch (err) { |
| free(thing); |
| punt(err); |
| } |
| return thing; |
| } |
| |
| int grand_central(void) |
| { |
| ball_t err; |
| void *thing; |
| try { |
| thing = build_something(); |
| } |
| catch (err) { |
| fputs(err.why, stderr); |
| drop(err); |
| return err.code; |
| } |
| ... use thing ... |
| free(thing); |
| return 0; |
| } |
| |
| */ |
| |
| #ifndef _TRY_H |
| #define _TRY_H |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <setjmp.h> |
| |
| /* If a POSIX pthread library is not available, then compile with NOTHREAD |
| defined. */ |
| #ifndef NOTHREAD |
| # include <pthread.h> |
| #endif |
| |
| /* The exposed names can be changed here. */ |
| #define ball_t try_ball_t_ |
| #define try TRY_TRY_ |
| #define preserve TRY_PRESERVE_ |
| #define always TRY_ALWAYS_ |
| #define catch TRY_CATCH_ |
| #define throw TRY_THROW_ |
| #define punt TRY_PUNT_ |
| #define drop TRY_DROP_ |
| |
| /* Package of an integer code and any other data to be thrown and caught. Here, |
| why is a string with information to be displayed to indicate why an |
| exception was thrown. free is true if why was allocated and should be freed |
| when no longer needed. This structure can be customized as needed, but it |
| must start with an int code. If it is customized, the try_throw_() function |
| in try.c must also be updated accordingly. As an example, why could be a |
| structure with information for use in the catch block. */ |
| typedef struct { |
| int ret; /* longjmp() return value */ |
| int code; /* integer code (required) */ |
| int free; /* if true, the message string was allocated */ |
| char *why; /* informational string or NULL */ |
| } try_ball_t_; |
| |
| /* Element in the global try stack (a linked list). */ |
| typedef struct try_s_ try_t_; |
| struct try_s_ { |
| jmp_buf env; /* state information for longjmp() to jump back */ |
| try_ball_t_ ball; /* data passed from the throw() */ |
| try_t_ *next; /* link to the next enclosing try_t, or NULL */ |
| }; |
| |
| /* Global try stack. try.c must be compiled and linked to provide the stack |
| pointer. Use thread-local storage if pthread.h is included before this. |
| Note that a throw can only be caught within the same thread. A new and |
| unique try stack is created for each thread, so any attempt to throw across |
| threads will fail with an assert, by virtue of reaching the end of the |
| stack. */ |
| #ifdef PTHREAD_ONCE_INIT |
| extern pthread_key_t try_key_; |
| void try_setup_(void); |
| # define try_stack_ ((try_t_ *)pthread_getspecific(try_key_)) |
| # define try_stack_set_(next) \ |
| do { \ |
| int ret = pthread_setspecific(try_key_, next); \ |
| assert(ret == 0 && "try: pthread_setspecific() failed"); \ |
| } while (0) |
| #else /* !PTHREAD_ONCE_INIT */ |
| extern try_t_ *try_stack_; |
| # define try_setup_() |
| # define try_stack_set_(next) try_stack_ = (next) |
| #endif /* PTHREAD_ONCE_INIT */ |
| |
| /* Try a block. The block should follow the invocation of try enclosed in { }. |
| The block must be immediately followed by a preserve, always, or catch. You |
| must not goto or return out of the try block. A naked break or continue in |
| the try block will go to the end of the block. */ |
| #define TRY_TRY_ \ |
| do { \ |
| try_t_ try_this_; \ |
| volatile int try_pushed_ = 1; \ |
| try_this_.ball.ret = 0; \ |
| try_this_.ball.code = 0; \ |
| try_this_.ball.free = 0; \ |
| try_this_.ball.why = NULL; \ |
| try_setup_(); \ |
| try_this_.next = try_stack_; \ |
| try_stack_set_(&try_this_); \ |
| if (setjmp(try_this_.env) == 0) \ |
| do { \ |
| |
| /* Preserve local automatic variables that were changed in the try block by |
| reissuing the setjmp(), replacing the state for the next longjmp(). The |
| preserve block should be enclosed in { }. The block must be immediately |
| followed by a preserve, always, or catch. You must not goto or return out |
| of the preserve block. A naked break or continue in the preserve block will |
| go to the end of the block. This can only follow a try or another preserve. |
| preserve effectively saves the state of local automatic variables at threat, |
| i.e. the register state, at that point so that a subsequent throw() will |
| restore those variables to that state for the always and catch blocks. |
| Changes to those variables after the preserve statement may or may not be |
| reflected in the always and catch blocks. */ |
| #define TRY_PRESERVE_ \ |
| } while (0); \ |
| if (try_this_.ball.ret == 0) if (setjmp(try_this_.env) == 0) \ |
| do { \ |
| |
| /* Execute the code between always and catch, whether or not something was |
| thrown. An always block is optional. If present, the always block must |
| follow a try or preserve block and be followed by a catch block. The always |
| block should be enclosed in { }. A naked break or continue in the always |
| block will go to the end of the block. It is permitted to use throw in the |
| always block, which will fall up to the next enclosing try. However this |
| will result in a memory leak if the original throw() allocated space for the |
| informational string. So it's best to not throw() in an always block. Keep |
| the always block simple. |
| |
| Great care must be taken if the always block uses an automatic storage |
| variable local to the enclosing function that can be modified in the try |
| block. Such variables must be declared volatile. If such a variable is not |
| declared volatile, and if the compiler elects to keep that variable in a |
| register, then the throw will restore that variable to its state at the |
| beginning of the try block, wiping out any change that occurred in the try |
| block. This can cause very confusing bugs until you remember that you |
| didn't follow this rule. */ |
| #define TRY_ALWAYS_ \ |
| } while (0); \ |
| if (try_pushed_) { \ |
| try_stack_set_(try_this_.next); \ |
| try_pushed_ = 0; \ |
| } \ |
| if (1) \ |
| do { |
| |
| /* Catch an error thrown in the preceding try block. The catch block must |
| follow catch and its parameter, and must be enclosed in { }. The catch must |
| immediately follow a try, preserve, or always block. It is permitted to use |
| throw() in the catch block, which will fall up to the next enclosing try. |
| However the ball_t passed by throw() must be freed using drop() before doing |
| another throw, to avoid a potential memory leak. The parameter of catch must |
| be a ball_t declared in the function or block containing the catch. It is |
| set to the parameters of the throw() that jumped to the catch. The catch |
| block is not executed if the first parameter of the throw() was zero. |
| |
| A catch block should end with either a punt() or a drop(). |
| |
| Great care must be taken if the catch block uses an automatic storage |
| variable local to the enclosing function that can be modified in the try |
| block. Such variables must be declared volatile or preserve must be used to |
| save their state. If such a variable is not declared volatile, and if the |
| compiler elects to keep that variable in a register, then the throw will |
| restore that variable to its state at the beginning of the most recent try |
| or preserve block, wiping out any change that occurred after the start of |
| that block. This can cause very confusing bugs until you remember that you |
| didn't follow this rule. */ |
| #define TRY_CATCH_(try_ball_) \ |
| } while (0); \ |
| if (try_pushed_) { \ |
| try_stack_set_(try_this_.next); \ |
| try_pushed_ = 0; \ |
| } \ |
| try_ball_ = try_this_.ball; \ |
| } while (0); \ |
| if (try_ball_.code) |
| |
| /* Throw an error. This can be in the try block or in any function called from |
| the try block, at any level of nesting. This will fall back to the end of |
| the first enclosing try block in the same thread, invoking the associated |
| catch block with a ball_t set to the arguments of throw(). throw() will |
| abort the program with an assert() if there is no nesting try. Make sure |
| that there's a nesting try! |
| |
| try may have one or more arguments, where the first argument is an int, the |
| optional second argument is a string, and the remaining optional arguments |
| are referred to by printf() formatting commands in the string. If there are |
| formatting commands in the string, i.e. any percent (%) signs, then |
| vsnprintf() is used to generate the formatted string from the arguments |
| before jumping to the enclosing try block. This allows throw() to use |
| information on the stack in the scope of the throw() statement, which will |
| be lost after jumping back to the enclosing try block. That formatted |
| string will use allocated memory, which is why it is important to use drop() |
| in catch blocks to free that memory, or punt() to pass the string on to |
| another catch block. Eventually some catch block down the chain will have |
| to drop() it. |
| |
| If a memory allocation fails during the execution of a throw(), then the |
| string provided to the catch block is not the formatted string at all, but |
| rather the string: "try: out of memory", with the integer code from the |
| throw() unchanged. |
| |
| If the first argument of throw is zero, then the catch block is not |
| executed. A throw(0) from a function called in the try block is equivalent |
| to a break or continue in the try block. A throw(0) should not have any |
| other arguments, to avoid a potential memory leak. There is no opportunity |
| to make use of any arguments after the 0 anyway. |
| |
| try.c must be compiled and linked to provide the try_throw_() function. */ |
| void try_throw_(int code, char *fmt, ...) |
| #if defined(__GNUC__) || defined(__has_builtin) |
| __attribute__((noreturn)) |
| #endif |
| ; |
| |
| #define TRY_THROW_(...) try_throw_(__VA_ARGS__, NULL) |
| |
| /* Punt a caught error on to the next enclosing catcher. This is normally used |
| in a catch block with same argument as the catch. */ |
| #define TRY_PUNT_(try_ball_) \ |
| do { \ |
| try_setup_(); \ |
| assert(try_stack_ != NULL && "try: naked punt"); \ |
| try_stack_->ball = try_ball_; \ |
| longjmp(try_stack_->env, 1); \ |
| } while (0) |
| |
| /* Clean up at the end of the line in a catch (no more punts). */ |
| #define TRY_DROP_(try_ball_) \ |
| do { \ |
| if (try_ball_.free) { \ |
| free(try_ball_.why); \ |
| try_ball_.free = 0; \ |
| try_ball_.why = NULL; \ |
| } \ |
| } while (0) |
| |
| #endif /* _TRY_H */ |