| | 1 | /* $Header: d:/cvsroot/tads/tads3/vmerr.h,v 1.3 1999/05/17 02:52:29 MJRoberts Exp $ */ |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 1998, 2002 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * Please see the accompanying license file, LICENSE.TXT, for information |
| | 7 | * on using and copying this software. |
| | 8 | */ |
| | 9 | /* |
| | 10 | Name |
| | 11 | vmerr.h - VM exception handling |
| | 12 | Function |
| | 13 | Defines error macros for try/throw exception handling. |
| | 14 | |
| | 15 | To throw an exception, use err_throw(exception_object), where the |
| | 16 | exception_object is an object describing the exception. This object |
| | 17 | must be allocated with 'new'. Control will immediately transfer to |
| | 18 | the nearest enclosing err_catch() block. |
| | 19 | |
| | 20 | Protected code is defined as shown below. The blocks must occur |
| | 21 | in the order shown, but the err_catch and err_finally blocks are |
| | 22 | optional (although it would be pointless to have an err_try that |
| | 23 | omits both). |
| | 24 | |
| | 25 | err_try |
| | 26 | { |
| | 27 | // protected code |
| | 28 | } |
| | 29 | err_catch(exception_variable) // or just err_catch_disc |
| | 30 | { |
| | 31 | // Exception handler - this code is executed only if an |
| | 32 | // exception occurs in the protected block. |
| | 33 | // |
| | 34 | // If an exception is thrown here (with no nested exception |
| | 35 | // handler to catch it), the err_finally block will be executed |
| | 36 | // and then the exception will be thrown to the enclosing block. |
| | 37 | } |
| | 38 | err_finally |
| | 39 | { |
| | 40 | // Code that is executed regardless whether exception occurs |
| | 41 | // or not. If no exception occurs, this code is executed as |
| | 42 | // soon as the protected block finishes. If an exception |
| | 43 | // occurs, this code is executed, then the exception is |
| | 44 | // re-thrown. |
| | 45 | // |
| | 46 | // If an exception is thrown here, the finally block will |
| | 47 | // be aborted and the enclosing error handler will be activated. |
| | 48 | // Care should be taken to ensure that code within this block |
| | 49 | // is properly protected against exceptions if necessary. |
| | 50 | } |
| | 51 | err_end; |
| | 52 | |
| | 53 | err_catch automatically defines the given exception_variable as |
| | 54 | a local variable of type (CVmException *) in the scope of the |
| | 55 | err_catch block. The referenced CVmException object is valid |
| | 56 | *only* within the err_catch block; after the err_catch block exits, |
| | 57 | the CVmException object will no longer be present. |
| | 58 | |
| | 59 | If you don't need to access the CVmException information, use |
| | 60 | err_catch_disc instead of err_catch - this has the same effect as |
| | 61 | err_catch, but does not declare a local variable to point to the |
| | 62 | exception object. |
| | 63 | |
| | 64 | To re-throw the exception being handled from an err_catch() block, |
| | 65 | use err_rethrow(). |
| | 66 | |
| | 67 | IMPORTANT: control must NOT be transferred out of an err_try, err_catch, |
| | 68 | or err_finally block via break, goto, or return. Actual execution of |
| | 69 | the err_end is required in order to properly unwind the error stack. |
| | 70 | The easiest way to leave one of these blocks prematurely, if necessary, |
| | 71 | is with a goto: |
| | 72 | |
| | 73 | err_try |
| | 74 | { |
| | 75 | // some code |
| | 76 | |
| | 77 | if (done_with_this_block) |
| | 78 | goto done; |
| | 79 | |
| | 80 | // more code |
| | 81 | |
| | 82 | done: ; |
| | 83 | } |
| | 84 | err_catch_disc |
| | 85 | { |
| | 86 | // etc... |
| | 87 | } |
| | 88 | err_end; |
| | 89 | |
| | 90 | Notes |
| | 91 | |
| | 92 | Modified |
| | 93 | 10/20/98 MJRoberts - Creation |
| | 94 | */ |
| | 95 | |
| | 96 | #ifndef VMERR_H |
| | 97 | #define VMERR_H |
| | 98 | |
| | 99 | #include <setjmp.h> |
| | 100 | #include <stdarg.h> |
| | 101 | |
| | 102 | #include "t3std.h" |
| | 103 | #include "vmerrnum.h" |
| | 104 | |
| | 105 | /* ------------------------------------------------------------------------ */ |
| | 106 | /* |
| | 107 | * err_throw() Return Handling |
| | 108 | * |
| | 109 | * Some compilers (such as MSVC 2007) are capable of doing global |
| | 110 | * optimizations that can detect functions that never return. err_throw() |
| | 111 | * is one such function: it uses longjmp() to jump out, so it never returns |
| | 112 | * to its caller. |
| | 113 | * |
| | 114 | * Most compilers can't detect this automatically, and C++ doesn't have a |
| | 115 | * standard way to declare a function that never returns. So on most |
| | 116 | * compilers, the compiler will assume that err_throw() returns, and thus |
| | 117 | * will generate a warning if err_throw() isn't followed by some proper |
| | 118 | * control flow statement. For example, in a function with a return value, |
| | 119 | * a code branch containing an err_throw() would still need a 'return |
| | 120 | * <val>' statement - without such a statement, the compiler would generate |
| | 121 | * an error about a branch without a value return. |
| | 122 | * |
| | 123 | * This creates a porting dilemma. On compilers that can detect that |
| | 124 | * err_throw() never returns, the presence of any statement in a code |
| | 125 | * branch after an err_throw() will cause an "unreachable code" error. For |
| | 126 | * all other compilers, the *absence* of such code will often cause a |
| | 127 | * different error ("missing return", etc). |
| | 128 | * |
| | 129 | * The only way I can see to deal with this is to use a compile-time |
| | 130 | * #define to select which type of compiler we're using, and use this to |
| | 131 | * insert or delete the proper dummy control flow statement after an |
| | 132 | * err_throw(). So: |
| | 133 | * |
| | 134 | * --- INSTRUCTIONS TO BASE CODE DEVELOPERS --- |
| | 135 | * |
| | 136 | * - after each err_throw() call, if the code branch needs some kind of |
| | 137 | * explicit termination (such as a "return val;" statement), code it with |
| | 138 | * the AFTER_ERR_THROW() macro. Since err_throw() never *actually* |
| | 139 | * returns, these will be dummy statements that will never be reached, but |
| | 140 | * the compiler might require their presence anyway because it doesn't know |
| | 141 | * better. |
| | 142 | * |
| | 143 | * --- INSTRUCTIONS TO PORTERS --- |
| | 144 | * |
| | 145 | * - if your compiler CAN detect that err_throw() never returns, define |
| | 146 | * COMPILER_DETECTS_THROW_NORETURN in your compiler command-line options; |
| | 147 | * |
| | 148 | * - otherwise, leave the symbol undefined. |
| | 149 | */ |
| | 150 | #ifdef COMPILER_DETECTS_THROW_NORETURN |
| | 151 | #define AFTER_ERR_THROW(dummy) |
| | 152 | #else |
| | 153 | #define AFTER_ERR_THROW(dummy) dummy |
| | 154 | #endif |
| | 155 | |
| | 156 | |
| | 157 | /* ------------------------------------------------------------------------ */ |
| | 158 | /* |
| | 159 | * Error Message Definition structure |
| | 160 | */ |
| | 161 | struct err_msg_t |
| | 162 | { |
| | 163 | /* message number */ |
| | 164 | int msgnum; |
| | 165 | |
| | 166 | /* concise message text */ |
| | 167 | const char *short_msgtxt; |
| | 168 | |
| | 169 | /* verbose message text */ |
| | 170 | const char *long_msgtxt; |
| | 171 | |
| | 172 | #ifdef VMERR_BOOK_MSG |
| | 173 | const char *book_msgtxt; |
| | 174 | #endif |
| | 175 | }; |
| | 176 | |
| | 177 | /* VM error message array */ |
| | 178 | extern const err_msg_t *vm_messages; |
| | 179 | extern size_t vm_message_count; |
| | 180 | |
| | 181 | /* |
| | 182 | * VM error message array - English version. This version of the |
| | 183 | * messages is linked directly into the VM; at run time, we can attempt |
| | 184 | * to replace this version with another language version obtained from |
| | 185 | * an external file. We link in the English version so that we will |
| | 186 | * always have a valid set of messages even if the user doesn't have a |
| | 187 | * message file installed. |
| | 188 | */ |
| | 189 | extern const err_msg_t vm_messages_english[]; |
| | 190 | extern size_t vm_message_count_english; |
| | 191 | |
| | 192 | /* external message file signature */ |
| | 193 | #define VM_MESSAGE_FILE_SIGNATURE "TADS3.Message.0001\n\r\032" |
| | 194 | |
| | 195 | |
| | 196 | /* |
| | 197 | * load an external message file - returns zero on success, non-zero on |
| | 198 | * failure |
| | 199 | */ |
| | 200 | int err_load_message_file(osfildef *fp, |
| | 201 | const err_msg_t **arr, size_t *arr_size, |
| | 202 | const err_msg_t *default_arr, |
| | 203 | size_t default_arr_size); |
| | 204 | |
| | 205 | /* load default message file */ |
| | 206 | #define err_load_vm_message_file(fp) \ |
| | 207 | err_load_message_file((fp), &vm_messages, &vm_message_count, \ |
| | 208 | vm_messages_english, vm_message_count_english) |
| | 209 | |
| | 210 | /* |
| | 211 | * check to see if an external message file has been loaded for the |
| | 212 | * default VM message set |
| | 213 | */ |
| | 214 | int err_is_message_file_loaded(); |
| | 215 | |
| | 216 | /* |
| | 217 | * delete messages previously loaded with err_load_message_file (this is |
| | 218 | * called automatically by err_terminate, so clients generally will not |
| | 219 | * need to call this directly) |
| | 220 | */ |
| | 221 | void err_delete_message_array(const err_msg_t **arr, size_t *arr_size, |
| | 222 | const err_msg_t *default_arr, |
| | 223 | size_t default_arr_size); |
| | 224 | |
| | 225 | |
| | 226 | /* |
| | 227 | * Search an array of messages for a given message number. The array |
| | 228 | * must be sorted by message ID. |
| | 229 | */ |
| | 230 | const char *err_get_msg(const err_msg_t *msg_array, size_t msg_count, |
| | 231 | int msgnum, int verbose); |
| | 232 | |
| | 233 | /* |
| | 234 | * Format a message with the parameters contained in an exception |
| | 235 | * object. Suports the following format codes: |
| | 236 | * |
| | 237 | * %s - String. Formats an ERR_TYPE_CHAR, ERR_TYPE_TEXTCHAR, or |
| | 238 | * ERR_TYPE_TEXTCHAR_LEN value. |
| | 239 | * |
| | 240 | * %d, %u, %x - signed/unsigned decimal integer, hexadecimal integer. |
| | 241 | * Formats an ERR_TYPE_INT value or an ERR_TYPE_ULONG value. |
| | 242 | * Automatically uses the correct size for the argument. |
| | 243 | * |
| | 244 | * %% - Formats as a single percent sign. |
| | 245 | */ |
| | 246 | void err_format_msg(char *outbuf, size_t outbuflen, |
| | 247 | const char *msg, const struct CVmException *exc); |
| | 248 | |
| | 249 | /* |
| | 250 | * exception ID - this identifies an error |
| | 251 | */ |
| | 252 | typedef uint err_id_t; |
| | 253 | |
| | 254 | /* |
| | 255 | * Error parameter type codes |
| | 256 | */ |
| | 257 | enum err_param_type |
| | 258 | { |
| | 259 | /* parameter is a native 'int' value */ |
| | 260 | ERR_TYPE_INT, |
| | 261 | |
| | 262 | /* parameter is a native 'unsigned long' value */ |
| | 263 | ERR_TYPE_ULONG, |
| | 264 | |
| | 265 | /* parameter is a 'textchar_t *' value (null terminated) */ |
| | 266 | ERR_TYPE_TEXTCHAR, |
| | 267 | |
| | 268 | /* parameter is a 'char *' value (null terminated) */ |
| | 269 | ERR_TYPE_CHAR, |
| | 270 | |
| | 271 | /* |
| | 272 | * parameter is a 'textchar_t *' value followed by a 'size_t' value |
| | 273 | * giving the number of bytes in the string |
| | 274 | */ |
| | 275 | ERR_TYPE_TEXTCHAR_LEN, |
| | 276 | |
| | 277 | /* parameter is a 'char *' value with a separate length */ |
| | 278 | ERR_TYPE_CHAR_LEN |
| | 279 | }; |
| | 280 | |
| | 281 | /* |
| | 282 | * Exception parameter |
| | 283 | */ |
| | 284 | struct CVmExcParam |
| | 285 | { |
| | 286 | /* type of this parameter */ |
| | 287 | err_param_type type_; |
| | 288 | |
| | 289 | /* value of the parameter */ |
| | 290 | union |
| | 291 | { |
| | 292 | /* as an integer */ |
| | 293 | int intval_; |
| | 294 | |
| | 295 | /* as an unsigned long */ |
| | 296 | unsigned long ulong_; |
| | 297 | |
| | 298 | /* as a text string */ |
| | 299 | const textchar_t *strval_; |
| | 300 | |
| | 301 | /* as a char string */ |
| | 302 | const char *charval_; |
| | 303 | |
| | 304 | /* as char string with separate length counter */ |
| | 305 | struct |
| | 306 | { |
| | 307 | const char *str_; |
| | 308 | size_t len_; |
| | 309 | } charlenval_; |
| | 310 | } val_; |
| | 311 | }; |
| | 312 | |
| | 313 | /* |
| | 314 | * Exception object |
| | 315 | */ |
| | 316 | struct CVmException |
| | 317 | { |
| | 318 | /* get the error code */ |
| | 319 | int get_error_code() const { return error_code_; } |
| | 320 | |
| | 321 | /* get the number of parameters */ |
| | 322 | int get_param_count() const { return param_count_; } |
| | 323 | |
| | 324 | /* get the type of the nth parameter */ |
| | 325 | err_param_type get_param_type(int n) const { return params_[n].type_; } |
| | 326 | |
| | 327 | /* get the nth parameter as an integer */ |
| | 328 | int get_param_int(int n) const { return params_[n].val_.intval_; } |
| | 329 | |
| | 330 | /* get the nth parameter as an unsigned long */ |
| | 331 | unsigned long get_param_ulong(int n) const |
| | 332 | { return params_[n].val_.ulong_; } |
| | 333 | |
| | 334 | /* get the nth parameter as a string */ |
| | 335 | const textchar_t *get_param_text(int n) const |
| | 336 | { return params_[n].val_.strval_; } |
| | 337 | |
| | 338 | /* get the nth parameter as a char string */ |
| | 339 | const char *get_param_char(int n) const |
| | 340 | { return params_[n].val_.charval_; } |
| | 341 | |
| | 342 | /* get the nth parameter as a counted-length string */ |
| | 343 | const char *get_param_char_len(int n, size_t *len) const |
| | 344 | { |
| | 345 | /* set the length return */ |
| | 346 | *len = params_[n].val_.charlenval_.len_; |
| | 347 | |
| | 348 | /* return the string pointer */ |
| | 349 | return params_[n].val_.charlenval_.str_; |
| | 350 | } |
| | 351 | |
| | 352 | |
| | 353 | /* set a parameter - null-terminated string value */ |
| | 354 | void set_param_str(int n, const char *str) |
| | 355 | { |
| | 356 | params_[n].type_ = ERR_TYPE_CHAR; |
| | 357 | params_[n].val_.charval_ = str; |
| | 358 | } |
| | 359 | |
| | 360 | /* set a parameter - counted-length string value */ |
| | 361 | void set_param_str(int n, const char *str, size_t len) |
| | 362 | { |
| | 363 | params_[n].type_ = ERR_TYPE_CHAR_LEN; |
| | 364 | params_[n].val_.charlenval_.str_ = str; |
| | 365 | params_[n].val_.charlenval_.len_ = len; |
| | 366 | } |
| | 367 | |
| | 368 | /* set a parameter - integer value */ |
| | 369 | void set_param_int(int n, int val) |
| | 370 | { |
| | 371 | params_[n].type_ = ERR_TYPE_INT; |
| | 372 | params_[n].val_.intval_ = val; |
| | 373 | } |
| | 374 | |
| | 375 | /* previous exception in the exception stack */ |
| | 376 | CVmException *prv_; |
| | 377 | |
| | 378 | /* the error code */ |
| | 379 | err_id_t error_code_; |
| | 380 | |
| | 381 | /* number of parameters stored in the exception */ |
| | 382 | int param_count_; |
| | 383 | |
| | 384 | /* parameters (actual array size is given by param_cnt_) */ |
| | 385 | CVmExcParam params_[1]; |
| | 386 | }; |
| | 387 | |
| | 388 | |
| | 389 | /* error states */ |
| | 390 | enum err_state_t |
| | 391 | { |
| | 392 | /* no exception */ |
| | 393 | ERR_STATE_OKAY = 0, |
| | 394 | |
| | 395 | /* trying */ |
| | 396 | ERR_STATE_TRYING = 1, |
| | 397 | |
| | 398 | /* exception in progress, and has not been caught */ |
| | 399 | ERR_STATE_EXCEPTION = 2, |
| | 400 | |
| | 401 | /* exception has been caught */ |
| | 402 | ERR_STATE_CAUGHT = 3, |
| | 403 | |
| | 404 | /* error thrown while exception handler in progress */ |
| | 405 | ERR_STATE_RETHROWN = 4 |
| | 406 | }; |
| | 407 | |
| | 408 | /* |
| | 409 | * Error frame item - allocated by err_try. This object is not |
| | 410 | * manipulated directly by the client; this is handled automatically by |
| | 411 | * the error macros. |
| | 412 | */ |
| | 413 | struct err_frame_t |
| | 414 | { |
| | 415 | /* enclosing error frame */ |
| | 416 | err_frame_t *prv_; |
| | 417 | |
| | 418 | /* jmpbuf for this handler */ |
| | 419 | jmp_buf jmpbuf_; |
| | 420 | |
| | 421 | /* current state */ |
| | 422 | err_state_t state_; |
| | 423 | |
| | 424 | /* |
| | 425 | * Flag: processing the 'finally' clause. If an exception is thrown |
| | 426 | * while we're processing this, we will not process the 'finally' |
| | 427 | * clause again, nor will we execute the 'catch' clause. |
| | 428 | */ |
| | 429 | int in_finally_; |
| | 430 | }; |
| | 431 | |
| | 432 | /* |
| | 433 | * Global error context structure |
| | 434 | */ |
| | 435 | struct err_context_t |
| | 436 | { |
| | 437 | /* current active error frame */ |
| | 438 | err_frame_t *cur_frame_; |
| | 439 | |
| | 440 | /* current exception in the exception stack */ |
| | 441 | CVmException *cur_exc_; |
| | 442 | |
| | 443 | /* next free byte of parameter stack */ |
| | 444 | char *param_free_; |
| | 445 | |
| | 446 | /* size of parameter stack */ |
| | 447 | size_t param_stack_size_; |
| | 448 | |
| | 449 | /* parameter stack - actual size given by param_stack_size */ |
| | 450 | char param_stack_[1]; |
| | 451 | }; |
| | 452 | |
| | 453 | /* |
| | 454 | * global static error context |
| | 455 | */ |
| | 456 | extern err_context_t *G_err; |
| | 457 | |
| | 458 | /* reference count for error context */ |
| | 459 | extern int G_err_refs; |
| | 460 | |
| | 461 | /* |
| | 462 | * Initialize the global error context. Should be called at program |
| | 463 | * initialization. 'param_stack_size' is the size in bytes of the error |
| | 464 | * parameter stack; this space is used to store all error parameters in |
| | 465 | * err_throw_a() calls. Generally, only one or two errors are active at |
| | 466 | * a given time, so it should be safe to make this three or four times |
| | 467 | * sizeof(CVmException) plus sizeof(CVmExcParam) plus the total |
| | 468 | * parameter sizes of the largest parameterized exception codes; if |
| | 469 | * still in doubt, one or two kbytes should be adequate in any case. |
| | 470 | */ |
| | 471 | void err_init(size_t param_stack_size); |
| | 472 | |
| | 473 | /* |
| | 474 | * Delete the global error context. Should be called at program |
| | 475 | * termination. |
| | 476 | */ |
| | 477 | void err_terminate(); |
| | 478 | |
| | 479 | /* |
| | 480 | * Throw an exception. The first form takes no parameters except the |
| | 481 | * error code; the second form takes a parameter count followed by that |
| | 482 | * number of parameters. Each parameter requires two arguments: the |
| | 483 | * first is a type code of type err_param_type, and the second the |
| | 484 | * value, whose interpretation depends on the type code. |
| | 485 | */ |
| | 486 | void err_throw(err_id_t error_code); |
| | 487 | void err_throw_a(err_id_t error_code, int param_count, ...); |
| | 488 | |
| | 489 | /* |
| | 490 | * Rethrow the current exception. This is valid only in 'catch' blocks. |
| | 491 | */ |
| | 492 | void err_rethrow(); |
| | 493 | |
| | 494 | /* pop the top exception from the exception stack */ |
| | 495 | void err_pop_exc(); |
| | 496 | |
| | 497 | /* |
| | 498 | * Serious error - abort program |
| | 499 | */ |
| | 500 | void err_abort(const char *message); |
| | 501 | |
| | 502 | /* |
| | 503 | * determine the error stack depth |
| | 504 | */ |
| | 505 | int err_stack_depth(); |
| | 506 | |
| | 507 | #define err_try \ |
| | 508 | { \ |
| | 509 | err_frame_t err_cur__; \ |
| | 510 | err_cur__.prv_ = G_err->cur_frame_; \ |
| | 511 | err_cur__.in_finally_ = FALSE; \ |
| | 512 | G_err->cur_frame_ = &err_cur__; \ |
| | 513 | if ((err_cur__.state_ = \ |
| | 514 | (err_state_t)setjmp(err_cur__.jmpbuf_)) == 0) \ |
| | 515 | { \ |
| | 516 | err_cur__.state_ = ERR_STATE_TRYING; |
| | 517 | |
| | 518 | #define err_catch(exc) \ |
| | 519 | } \ |
| | 520 | if (!err_cur__.in_finally_ && \ |
| | 521 | err_cur__.state_ == ERR_STATE_EXCEPTION) \ |
| | 522 | { \ |
| | 523 | CVmException *exc; \ |
| | 524 | exc = G_err->cur_exc_; \ |
| | 525 | err_cur__.state_ = ERR_STATE_CAUGHT; |
| | 526 | |
| | 527 | #define err_catch_disc \ |
| | 528 | } \ |
| | 529 | if (!err_cur__.in_finally_ && \ |
| | 530 | err_cur__.state_ == ERR_STATE_EXCEPTION) \ |
| | 531 | { \ |
| | 532 | err_cur__.state_ = ERR_STATE_CAUGHT; |
| | 533 | |
| | 534 | |
| | 535 | #define err_finally \ |
| | 536 | } \ |
| | 537 | if (!err_cur__.in_finally_) \ |
| | 538 | { \ |
| | 539 | err_cur__.in_finally_ = TRUE; |
| | 540 | |
| | 541 | #define err_end \ |
| | 542 | } \ |
| | 543 | G_err->cur_frame_ = err_cur__.prv_; \ |
| | 544 | if (err_cur__.state_ == ERR_STATE_EXCEPTION \ |
| | 545 | || err_cur__.state_ == ERR_STATE_RETHROWN) \ |
| | 546 | err_rethrow(); \ |
| | 547 | if (err_cur__.state_ == ERR_STATE_CAUGHT) \ |
| | 548 | err_pop_exc(); \ |
| | 549 | } |
| | 550 | |
| | 551 | #endif /* VMERR_H */ |
| | 552 | |