1 /*=== 2 cexcept.h 2.0.1 (2008-Jul-19-Sat) 3 http://www.nicemice.net/cexcept/ 4 Adam M. Costello 5 http://www.nicemice.net/amc/ 6 7 An interface for exception-handling in ANSI C (C89 and subsequent ISO 8 standards), developed jointly with Cosmin Truta. 9 10 Copyright (c) 2000-2008 Adam M. Costello and Cosmin Truta. 11 This software may be modified only if its author and version 12 information is updated accurately, and may be redistributed 13 only if accompanied by this unaltered notice. Subject to those 14 restrictions, permission is granted to anyone to do anything 15 with this software. The copyright holders make no guarantees 16 regarding this software, and are not responsible for any damage 17 resulting from its use. 18 19 The cexcept interface is not compatible with and cannot interact 20 with system exceptions (like division by zero or memory segmentation 21 violation), compiler-generated exceptions (like C++ exceptions), or 22 other exception-handling interfaces. 23 24 When using this interface across multiple .c files, do not include 25 this header file directly. Instead, create a wrapper header file that 26 includes this header file and then invokes the define_exception_type 27 macro (see below). The .c files should then include that header file. 28 29 The interface consists of one type, one well-known name, and six macros. 30 31 32 define_exception_type(type_name); 33 34 This macro is used like an external declaration. It specifies 35 the type of object that gets copied from the exception thrower to 36 the exception catcher. The type_name can be any type that can be 37 assigned to, that is, a non-constant arithmetic type, struct, union, 38 or pointer. Examples: 39 40 define_exception_type(int); 41 42 enum exception { out_of_memory, bad_arguments, disk_full }; 43 define_exception_type(enum exception); 44 45 struct exception { int code; const char *msg; }; 46 define_exception_type(struct exception); 47 48 Because throwing an exception causes the object to be copied (not 49 just once, but twice), programmers may wish to consider size when 50 choosing the exception type. 51 52 53 struct exception_context; 54 55 This type may be used after the define_exception_type() macro has 56 been invoked. A struct exception_context must be known to both 57 the thrower and the catcher. It is expected that there be one 58 context for each thread that uses exceptions. It would certainly 59 be dangerous for multiple threads to access the same context. 60 One thread can use multiple contexts, but that is likely to be 61 confusing and not typically useful. The application can allocate 62 this structure in any way it pleases--automatic, static, or dynamic. 63 The application programmer should pretend not to know the structure 64 members, which are subject to change. 65 66 67 struct exception_context *the_exception_context; 68 69 The Try/Catch and Throw statements (described below) implicitly 70 refer to a context, using the name the_exception_context. It is 71 the application's responsibility to make sure that this name yields 72 the address of a mutable (non-constant) struct exception_context 73 wherever those statements are used. Subject to that constraint, the 74 application may declare a variable of this name anywhere it likes 75 (inside a function, in a parameter list, or externally), and may 76 use whatever storage class specifiers (static, extern, etc) or type 77 qualifiers (const, volatile, etc) it likes. Examples: 78 79 static struct exception_context 80 * const the_exception_context = &foo; 81 82 { struct exception_context *the_exception_context = bar; ... } 83 84 int blah(struct exception_context *the_exception_context, ...); 85 86 extern struct exception_context the_exception_context[1]; 87 88 The last example illustrates a trick that avoids creating a pointer 89 object separate from the structure object. 90 91 The name could even be a macro, for example: 92 93 struct exception_context ec_array[numthreads]; 94 #define the_exception_context (ec_array + thread_id) 95 96 Be aware that the_exception_context is used several times by the 97 Try/Catch/Throw macros, so it shouldn't be expensive or have side 98 effects. The expansion must be a drop-in replacement for an 99 identifier, so it's safest to put parentheses around it. 100 101 102 void init_exception_context(struct exception_context *ec); 103 104 For context structures allocated statically (by an external 105 definition or using the "static" keyword), the implicit 106 initialization to all zeros is sufficient, but contexts allocated 107 by other means must be initialized using this macro before they 108 are used by a Try/Catch statement. It does no harm to initialize 109 a context more than once (by using this macro on a statically 110 allocated context, or using this macro twice on the same context), 111 but a context must not be re-initialized after it has been used by a 112 Try/Catch statement. 113 114 115 Try statement 116 Catch (expression) statement 117 118 The Try/Catch/Throw macros are capitalized in order to avoid 119 confusion with the C++ keywords, which have subtly different 120 semantics. 121 122 A Try/Catch statement has a syntax similar to an if/else statement, 123 except that the parenthesized expression goes after the second 124 keyword rather than the first. As with if/else, there are two 125 clauses, each of which may be a simple statement ending with a 126 semicolon or a brace-enclosed compound statement. But whereas 127 the else clause is optional, the Catch clause is required. The 128 expression must be a modifiable lvalue (something capable of being 129 assigned to) of the same type (disregarding type qualifiers) that 130 was passed to define_exception_type(). 131 132 If a Throw that uses the same exception context as the Try/Catch is 133 executed within the Try clause (typically within a function called 134 by the Try clause), and the exception is not caught by a nested 135 Try/Catch statement, then a copy of the exception will be assigned 136 to the expression, and control will jump to the Catch clause. If no 137 such Throw is executed, then the assignment is not performed, and 138 the Catch clause is not executed. 139 140 The expression is not evaluated unless and until the exception is 141 caught, which is significant if it has side effects, for example: 142 143 Try foo(); 144 Catch (p[++i].e) { ... } 145 146 IMPORTANT: Jumping into or out of a Try clause (for example via 147 return, break, continue, goto, longjmp) is forbidden--the compiler 148 will not complain, but bad things will happen at run-time. Jumping 149 into or out of a Catch clause is okay, and so is jumping around 150 inside a Try clause. In many cases where one is tempted to return 151 from a Try clause, it will suffice to use Throw, and then return 152 from the Catch clause. Another option is to set a flag variable and 153 use goto to jump to the end of the Try clause, then check the flag 154 after the Try/Catch statement. 155 156 IMPORTANT: The values of any non-volatile automatic variables 157 changed within the Try clause are undefined after an exception is 158 caught. Therefore, variables modified inside the Try block whose 159 values are needed later outside the Try block must either use static 160 storage or be declared with the "volatile" type qualifier. 161 162 163 Throw expression; 164 165 A Throw statement is very much like a return statement, except that 166 the expression is required. Whereas return jumps back to the place 167 where the current function was called, Throw jumps back to the Catch 168 clause of the innermost enclosing Try clause. The expression must 169 be compatible with the type passed to define_exception_type(). The 170 exception must be caught, otherwise the program may crash. 171 172 Slight limitation: If the expression is a comma-expression, it must 173 be enclosed in parentheses. 174 175 176 Try statement 177 Catch_anonymous statement 178 179 When the value of the exception is not needed, a Try/Catch statement 180 can use Catch_anonymous instead of Catch (expression). 181 182 183 Everything below this point is for the benefit of the compiler. The 184 application programmer should pretend not to know any of it, because it 185 is subject to change. 186 187 ===*/ 188 189 190 #ifndef CEXCEPT_H 191 #define CEXCEPT_H 192 193 194 #include <setjmp.h> 195 196 #define define_exception_type(etype) \ 197 struct exception_context { \ 198 jmp_buf *penv; \ 199 int caught; \ 200 volatile struct { etype etmp; } v; \ 201 } 202 203 /* etmp must be volatile because the application might use automatic */ 204 /* storage for the_exception_context, and etmp is modified between */ 205 /* the calls to setjmp() and longjmp(). A wrapper struct is used to */ 206 /* avoid warnings about a duplicate volatile qualifier in case etype */ 207 /* already includes it. */ 208 209 #define init_exception_context(ec) ((void)((ec)->penv = 0)) 210 211 #define Try \ 212 { \ 213 jmp_buf *exception__prev, exception__env; \ 214 exception__prev = the_exception_context->penv; \ 215 the_exception_context->penv = &exception__env; \ 216 if (setjmp(exception__env) == 0) { \ 217 do 218 219 #define exception__catch(action) \ 220 while (the_exception_context->caught = 0, \ 221 the_exception_context->caught); \ 222 } \ 223 else { \ 224 the_exception_context->caught = 1; \ 225 } \ 226 the_exception_context->penv = exception__prev; \ 227 } \ 228 if (!the_exception_context->caught || action) { } \ 229 else 230 231 #define Catch(e) exception__catch(((e) = the_exception_context->v.etmp, 0)) 232 #define Catch_anonymous exception__catch(0) 233 234 /* Try ends with do, and Catch begins with while(0) and ends with */ 235 /* else, to ensure that Try/Catch syntax is similar to if/else */ 236 /* syntax. */ 237 /* */ 238 /* The 0 in while(0) is expressed as x=0,x in order to appease */ 239 /* compilers that warn about constant expressions inside while(). */ 240 /* Most compilers should still recognize that the condition is always */ 241 /* false and avoid generating code for it. */ 242 243 #define Throw \ 244 for (;; longjmp(*the_exception_context->penv, 1)) \ 245 the_exception_context->v.etmp = 246 247 248 #endif /* CEXCEPT_H */ 249