| | 1 | #ifdef RCSID |
| | 2 | static char RCSid[] = |
| | 3 | "$Header: d:/cvsroot/tads/tads3/vmerr.cpp,v 1.4 1999/07/11 00:46:59 MJRoberts Exp $"; |
| | 4 | #endif |
| | 5 | |
| | 6 | /* |
| | 7 | * Copyright (c) 1998, 2002 Michael J. Roberts. All Rights Reserved. |
| | 8 | * |
| | 9 | * Please see the accompanying license file, LICENSE.TXT, for information |
| | 10 | * on using and copying this software. |
| | 11 | */ |
| | 12 | /* |
| | 13 | Name |
| | 14 | vmerr.cpp - VM error handling |
| | 15 | Function |
| | 16 | This module contains global variables required for the error handler. |
| | 17 | Notes |
| | 18 | |
| | 19 | Modified |
| | 20 | 10/20/98 MJRoberts - Creation |
| | 21 | */ |
| | 22 | |
| | 23 | #include <stdarg.h> |
| | 24 | #include <stdlib.h> |
| | 25 | #include <assert.h> |
| | 26 | |
| | 27 | #include "t3std.h" |
| | 28 | #include "vmtype.h" |
| | 29 | #include "vmerr.h" |
| | 30 | |
| | 31 | |
| | 32 | /* |
| | 33 | * Global error context pointer, and reference count for the error |
| | 34 | * subsystem |
| | 35 | */ |
| | 36 | err_context_t *G_err = 0; |
| | 37 | int G_err_refs = 0; |
| | 38 | |
| | 39 | /* |
| | 40 | * Initialize the global error context |
| | 41 | */ |
| | 42 | void err_init(size_t param_stack_size) |
| | 43 | { |
| | 44 | /* count the reference */ |
| | 45 | ++G_err_refs; |
| | 46 | |
| | 47 | /* ignore this if we're already initialized */ |
| | 48 | if (G_err_refs > 1) |
| | 49 | return; |
| | 50 | |
| | 51 | /* |
| | 52 | * allocate the error context, adding in room for the parameter |
| | 53 | * stack |
| | 54 | */ |
| | 55 | G_err = (err_context_t *)t3malloc(sizeof(err_context_t) |
| | 56 | + param_stack_size); |
| | 57 | |
| | 58 | /* initialize the parameter stack pointers */ |
| | 59 | G_err->param_free_ = G_err->param_stack_; |
| | 60 | G_err->param_stack_size_ = param_stack_size; |
| | 61 | G_err->cur_exc_ = 0; |
| | 62 | |
| | 63 | /* we have no active frame yet */ |
| | 64 | G_err->cur_frame_ = 0; |
| | 65 | } |
| | 66 | |
| | 67 | /* |
| | 68 | * Delete the global error context |
| | 69 | */ |
| | 70 | void err_terminate() |
| | 71 | { |
| | 72 | /* decrease the error system reference counter */ |
| | 73 | --G_err_refs; |
| | 74 | |
| | 75 | /* if that leaves no references, delete the error context */ |
| | 76 | if (G_err_refs == 0) |
| | 77 | { |
| | 78 | /* free the global error structure */ |
| | 79 | t3free(G_err); |
| | 80 | G_err = 0; |
| | 81 | |
| | 82 | /* delete external messages, if we loaded them */ |
| | 83 | err_delete_message_array(&vm_messages, &vm_message_count, |
| | 84 | vm_messages_english, |
| | 85 | vm_message_count_english); |
| | 86 | } |
| | 87 | } |
| | 88 | |
| | 89 | /* |
| | 90 | * Throw the error currently on the stack |
| | 91 | */ |
| | 92 | static void err_throw_current() |
| | 93 | { |
| | 94 | err_state_t new_state; |
| | 95 | |
| | 96 | /* |
| | 97 | * Figure out what state the enclosing frame will be in after this |
| | 98 | * jump. If we're currently in a 'trying' block, we'll now be in an |
| | 99 | * 'exception' state. Otherwise, we must have been in a 'catch' or |
| | 100 | * 'finally' already - in these cases, the new state is 'rethrown', |
| | 101 | * because we have an exception within an exception handler. |
| | 102 | */ |
| | 103 | if (G_err->cur_frame_->state_ == ERR_STATE_TRYING) |
| | 104 | new_state = ERR_STATE_EXCEPTION; |
| | 105 | else |
| | 106 | new_state = ERR_STATE_RETHROWN; |
| | 107 | |
| | 108 | /* jump to the enclosing frame's exception handler */ |
| | 109 | longjmp(G_err->cur_frame_->jmpbuf_, new_state); |
| | 110 | } |
| | 111 | |
| | 112 | /* |
| | 113 | * Throw an exception |
| | 114 | */ |
| | 115 | void err_throw(err_id_t error_code) |
| | 116 | { |
| | 117 | /* throw the error, with no parameters */ |
| | 118 | err_throw_a(error_code, 0); |
| | 119 | } |
| | 120 | |
| | 121 | /* |
| | 122 | * Allocate space in the exception parameter stack. |
| | 123 | */ |
| | 124 | static void *err_stack_alloc(size_t siz) |
| | 125 | { |
| | 126 | void *ret; |
| | 127 | |
| | 128 | /* round the size up to the OS allocation alignment boundary */ |
| | 129 | siz = osrndsz(siz); |
| | 130 | |
| | 131 | /* if we don't have room in the parameter stack, it's a fatal error */ |
| | 132 | if ((G_err->param_stack_size_ |
| | 133 | - (G_err->param_free_ - G_err->param_stack_)) < siz) |
| | 134 | err_abort("no space for exception parameters"); |
| | 135 | |
| | 136 | /* |
| | 137 | * get the current free pointer - this is where we'll allocate the |
| | 138 | * requested space |
| | 139 | */ |
| | 140 | ret = G_err->param_free_; |
| | 141 | |
| | 142 | /* advance the free pointer to consume the requested space */ |
| | 143 | G_err->param_free_ += siz; |
| | 144 | |
| | 145 | /* return a poiner to the allocated space */ |
| | 146 | return ret; |
| | 147 | } |
| | 148 | |
| | 149 | /* |
| | 150 | * Store an exception parameter string. Allocates space for the string in |
| | 151 | * the exception parameter stack, stores a null-terminated copy, and |
| | 152 | * returns a pointer to the copy of the string. |
| | 153 | */ |
| | 154 | static char *err_store_param_str(const char *str, size_t len) |
| | 155 | { |
| | 156 | char *new_str; |
| | 157 | |
| | 158 | /* allocate space for the string and a null terminator */ |
| | 159 | new_str = (char *)err_stack_alloc(len + 1); |
| | 160 | |
| | 161 | /* store a copy of the string */ |
| | 162 | memcpy(new_str, str, len); |
| | 163 | |
| | 164 | /* null-terminate the copy */ |
| | 165 | new_str[len] = '\0'; |
| | 166 | |
| | 167 | /* return a pointer to the newly-allocated copy */ |
| | 168 | return new_str; |
| | 169 | } |
| | 170 | |
| | 171 | /* |
| | 172 | * Throw an exception with parameters in va_list format |
| | 173 | */ |
| | 174 | static void err_throw_v(err_id_t error_code, int param_count, va_list va) |
| | 175 | { |
| | 176 | size_t siz; |
| | 177 | int i; |
| | 178 | CVmException *exc; |
| | 179 | CVmExcParam *param; |
| | 180 | |
| | 181 | /* |
| | 182 | * Assert that the size of the err_param_type enum is no larger than |
| | 183 | * the size of the native 'int' type. Since this routine is called |
| | 184 | * with a varargs list, and since ANSI requires compilers to promote |
| | 185 | * any enum type smaller than int to int when passing to a varargs |
| | 186 | * function, we must retrieve our err_param_type arguments (via the |
| | 187 | * va_arg() macro) with type 'int' rather than type 'enum |
| | 188 | * err_param_type'. |
| | 189 | * |
| | 190 | * The promotion to int is mandatory, so retrieving our values as |
| | 191 | * 'int' values is the correct, portable thing to do; the only |
| | 192 | * potential problem is that our enum type could be defined to be |
| | 193 | * *larger* than int, in which case the compiler would pass it to us |
| | 194 | * as the larger type, not int, and our retrieval would be incorrect. |
| | 195 | * The only way a compiler would be allowed to do this is if our enum |
| | 196 | * type actually required a type larger than unsigned int (in other |
| | 197 | * words, we had enum values higher than the largest unsigned int |
| | 198 | * value for the local platform). This is extremely unlikely, but |
| | 199 | * we'll assert our assumption here to ensure that the problem is |
| | 200 | * readily apparent should it ever occur. |
| | 201 | */ |
| | 202 | assert(sizeof(err_param_type) <= sizeof(int)); |
| | 203 | |
| | 204 | /* |
| | 205 | * If the current stack frame is already handling an error, pop the |
| | 206 | * current error, since the current error will no longer be |
| | 207 | * accessible after this throw. |
| | 208 | */ |
| | 209 | if (G_err->cur_frame_->state_ == ERR_STATE_EXCEPTION |
| | 210 | || G_err->cur_frame_->state_ == ERR_STATE_CAUGHT |
| | 211 | || G_err->cur_frame_->state_ == ERR_STATE_RETHROWN) |
| | 212 | err_pop_exc(); |
| | 213 | |
| | 214 | /* |
| | 215 | * Figure out how much space we need. Start with the size of the base |
| | 216 | * CVmException class, since we always need a CVmException structure. |
| | 217 | * This structure has space for one argument descriptor built in, so |
| | 218 | * subtract that off from our base size, since we'll add in space for |
| | 219 | * the argument descriptors separately. |
| | 220 | */ |
| | 221 | siz = sizeof(CVmException) - sizeof(CVmExcParam); |
| | 222 | |
| | 223 | /* |
| | 224 | * Add in space the parameter descriptors. We need one CVmExcParam |
| | 225 | * structure to describe each parameter. |
| | 226 | */ |
| | 227 | siz += param_count * sizeof(CVmExcParam); |
| | 228 | |
| | 229 | /* |
| | 230 | * allocate space for the base exception structure in the exception |
| | 231 | * parameter stack |
| | 232 | */ |
| | 233 | exc = (CVmException *)err_stack_alloc(siz); |
| | 234 | |
| | 235 | /* fill in our base structure */ |
| | 236 | exc->error_code_ = error_code; |
| | 237 | exc->param_count_ = param_count; |
| | 238 | |
| | 239 | /* |
| | 240 | * link to the previous exception on the stack, so that we can pop |
| | 241 | * this exception when we're done with it |
| | 242 | */ |
| | 243 | exc->prv_ = G_err->cur_exc_; |
| | 244 | |
| | 245 | /* this is now the current exception */ |
| | 246 | G_err->cur_exc_ = exc; |
| | 247 | |
| | 248 | /* |
| | 249 | * We now have our base exception structure, with enough space for the |
| | 250 | * parameter descriptors, but we still need to store the parameter |
| | 251 | * values themselves. |
| | 252 | */ |
| | 253 | for (i = 0, param = exc->params_ ; i < param_count ; ++i, ++param) |
| | 254 | { |
| | 255 | err_param_type typ; |
| | 256 | const char *sptr; |
| | 257 | size_t slen; |
| | 258 | |
| | 259 | /* get the type indicator, and store it in the descriptor */ |
| | 260 | typ = (err_param_type)va_arg(va, int); |
| | 261 | |
| | 262 | /* store the type */ |
| | 263 | param->type_ = typ; |
| | 264 | |
| | 265 | /* store the argument's value */ |
| | 266 | switch(typ) |
| | 267 | { |
| | 268 | case ERR_TYPE_INT: |
| | 269 | /* store the integer */ |
| | 270 | param->val_.intval_ = va_arg(va, int); |
| | 271 | break; |
| | 272 | |
| | 273 | case ERR_TYPE_ULONG: |
| | 274 | /* store the unsigned long */ |
| | 275 | param->val_.ulong_ = va_arg(va, unsigned long); |
| | 276 | break; |
| | 277 | |
| | 278 | case ERR_TYPE_TEXTCHAR: |
| | 279 | /* |
| | 280 | * It's a (textchar_t *) string, null-terminated. Get the |
| | 281 | * string pointer and calculate its length. |
| | 282 | */ |
| | 283 | sptr = va_arg(va, textchar_t *); |
| | 284 | slen = get_strlen(sptr); |
| | 285 | |
| | 286 | /* store it in parameter memory */ |
| | 287 | param->val_.strval_ = err_store_param_str(sptr, slen); |
| | 288 | break; |
| | 289 | |
| | 290 | case ERR_TYPE_TEXTCHAR_LEN: |
| | 291 | /* |
| | 292 | * It's a (textchar_t *) string with an explicit length given |
| | 293 | * as a separate size_t parameter. |
| | 294 | */ |
| | 295 | sptr = va_arg(va, textchar_t *); |
| | 296 | slen = va_arg(va, size_t); |
| | 297 | |
| | 298 | /* store it in parameter memory */ |
| | 299 | param->val_.strval_ = err_store_param_str(sptr, slen); |
| | 300 | |
| | 301 | /* |
| | 302 | * change the type to a regular textchar string now, since |
| | 303 | * we've converted the value to a null-terminated string |
| | 304 | */ |
| | 305 | param->type_ = ERR_TYPE_TEXTCHAR; |
| | 306 | break; |
| | 307 | |
| | 308 | case ERR_TYPE_CHAR: |
| | 309 | /* it's a (char *) string, null-terminated */ |
| | 310 | sptr = va_arg(va, char *); |
| | 311 | slen = strlen(sptr); |
| | 312 | |
| | 313 | /* store it */ |
| | 314 | param->val_.charval_ = err_store_param_str(sptr, slen); |
| | 315 | break; |
| | 316 | |
| | 317 | case ERR_TYPE_CHAR_LEN: |
| | 318 | /* it's a (char *) string with an explicit size_t size */ |
| | 319 | sptr = va_arg(va, char *); |
| | 320 | slen = va_arg(va, size_t); |
| | 321 | |
| | 322 | /* store it */ |
| | 323 | param->val_.charval_ = err_store_param_str(sptr, slen); |
| | 324 | |
| | 325 | /* skip the string */ |
| | 326 | va_arg(va, char *); |
| | 327 | |
| | 328 | /* |
| | 329 | * change the type to a regular char string, since we've added |
| | 330 | * null termination |
| | 331 | */ |
| | 332 | param->type_ = ERR_TYPE_CHAR; |
| | 333 | break; |
| | 334 | } |
| | 335 | } |
| | 336 | |
| | 337 | /* throw the error that we just pushed */ |
| | 338 | err_throw_current(); |
| | 339 | } |
| | 340 | |
| | 341 | #ifdef MICROSOFT |
| | 342 | /* |
| | 343 | * Microsoft Visual C++ optimizer workaround - not applicable to other |
| | 344 | * systems. |
| | 345 | * |
| | 346 | * For MSVC, we need to turn off warning 4702 ("unreachable code"). MSVC |
| | 347 | * is too clever by half for our implementation here of err_throw_a(), and |
| | 348 | * the only recourse (empirically) seems to be to turn off this warning for |
| | 349 | * the duration of this function. |
| | 350 | * |
| | 351 | * The issue is that err_throw_a() makes a call to err_throw_v() within a |
| | 352 | * va_start()...va_end() pair. The MSVC optimizer recognizes that |
| | 353 | * err_throw_v() never returns, so it marks any code following a call to |
| | 354 | * same as unreachable. But we *have to* include the va_end() call after |
| | 355 | * the err_throw_v() call. The va_end() is *required* for portability - |
| | 356 | * some compilers expand va_end() to structural C code (for example, to |
| | 357 | * insert a close brace to balance an open brace inserted by va_start()). |
| | 358 | * So we can't omit the va_end() call regardless of its run-time |
| | 359 | * reachability. |
| | 360 | * |
| | 361 | * So, we have code that's both unreachable and required. The only |
| | 362 | * apparent solution is to disable the unreachable-code warning for the |
| | 363 | * duration of this function. |
| | 364 | * |
| | 365 | * (Conceivably, we could trick the optimizer by moving the err_throw_a() |
| | 366 | * implementation to a separate module; the optimizer probably couldn't |
| | 367 | * track the does-not-return status of err_throw_v() across modules. But |
| | 368 | * that wouldn't be any cleaner in any sense - it would just trick the |
| | 369 | * compiler in a different way. Better to explicitly turn off the warning; |
| | 370 | * at least that way the workaround is plain and explicit.) |
| | 371 | */ |
| | 372 | #pragma warning(push) |
| | 373 | #pragma warning(disable: 4702) |
| | 374 | #endif |
| | 375 | |
| | 376 | /* |
| | 377 | * Throw an exception with parameters |
| | 378 | */ |
| | 379 | void err_throw_a(err_id_t error_code, int param_count, ...) |
| | 380 | { |
| | 381 | va_list marker; |
| | 382 | |
| | 383 | /* build the argument list and throw the error */ |
| | 384 | va_start(marker, param_count); |
| | 385 | err_throw_v(error_code, param_count, marker); |
| | 386 | va_end(marker); |
| | 387 | } |
| | 388 | |
| | 389 | /* MSVC - restore previous warning state (see above) */ |
| | 390 | #ifdef MICROSOFT |
| | 391 | #pragma warning(pop) |
| | 392 | #endif |
| | 393 | |
| | 394 | /* |
| | 395 | * Re-throw the current exception. This is valid only from 'catch' |
| | 396 | * blocks. |
| | 397 | */ |
| | 398 | void err_rethrow() |
| | 399 | { |
| | 400 | /* throw the error currently on the stack */ |
| | 401 | err_throw_current(); |
| | 402 | } |
| | 403 | |
| | 404 | /* |
| | 405 | * Pop the current exception from the exception stack. |
| | 406 | */ |
| | 407 | void err_pop_exc() |
| | 408 | { |
| | 409 | /* |
| | 410 | * if there's anything on the stack, remove it and replace it with |
| | 411 | * the previous item on the stack |
| | 412 | */ |
| | 413 | if (G_err->cur_exc_ != 0) |
| | 414 | { |
| | 415 | /* |
| | 416 | * since we're removing this element, its space can now be |
| | 417 | * re-used for the next exception allocation (since exceptions |
| | 418 | * are always stacked, the space we use to consume them can be |
| | 419 | * treated as a stack as well) |
| | 420 | */ |
| | 421 | G_err->param_free_ = (char *)G_err->cur_exc_; |
| | 422 | |
| | 423 | /* remove the top element of the stack */ |
| | 424 | G_err->cur_exc_ = G_err->cur_exc_->prv_; |
| | 425 | } |
| | 426 | else |
| | 427 | { |
| | 428 | /* no errors are on the stack, so the parameter space is all free */ |
| | 429 | G_err->param_free_ = G_err->param_stack_; |
| | 430 | } |
| | 431 | } |
| | 432 | |
| | 433 | /* |
| | 434 | * Abort the program with a serious, unrecoverable error |
| | 435 | */ |
| | 436 | void err_abort(const char *message) |
| | 437 | { |
| | 438 | printf("%s\n", message); |
| | 439 | exit(2); |
| | 440 | } |
| | 441 | |
| | 442 | /* |
| | 443 | * Determine the current error stack depth |
| | 444 | */ |
| | 445 | int err_stack_depth() |
| | 446 | { |
| | 447 | int cnt; |
| | 448 | CVmException *exc; |
| | 449 | |
| | 450 | /* count exceptions in the stack */ |
| | 451 | for (cnt = 0, exc = G_err->cur_exc_ ; exc != 0 ; exc = exc->prv_) |
| | 452 | { |
| | 453 | /* count this exception */ |
| | 454 | ++cnt; |
| | 455 | } |
| | 456 | |
| | 457 | /* return the number of exceptions in the stack */ |
| | 458 | return cnt; |
| | 459 | } |
| | 460 | |
| | 461 | /* ------------------------------------------------------------------------ */ |
| | 462 | /* |
| | 463 | * Try loading a message file. Returns zero on success, non-zero if an |
| | 464 | * error occurred. |
| | 465 | */ |
| | 466 | int err_load_message_file(osfildef *fp, |
| | 467 | const err_msg_t **arr, size_t *arr_size, |
| | 468 | const err_msg_t *default_arr, |
| | 469 | size_t default_arr_size) |
| | 470 | { |
| | 471 | char buf[128]; |
| | 472 | size_t i; |
| | 473 | err_msg_t *msg; |
| | 474 | |
| | 475 | /* read the file signature */ |
| | 476 | if (osfrb(fp, buf, sizeof(VM_MESSAGE_FILE_SIGNATURE)) |
| | 477 | || memcmp(buf, VM_MESSAGE_FILE_SIGNATURE, |
| | 478 | sizeof(VM_MESSAGE_FILE_SIGNATURE)) != 0) |
| | 479 | goto fail; |
| | 480 | |
| | 481 | /* delete any previously-loaded message array */ |
| | 482 | err_delete_message_array(arr, arr_size, default_arr, default_arr_size); |
| | 483 | |
| | 484 | /* read the message count */ |
| | 485 | if (osfrb(fp, buf, 2)) |
| | 486 | goto fail; |
| | 487 | |
| | 488 | /* set the new message count */ |
| | 489 | *arr_size = osrp2(buf); |
| | 490 | |
| | 491 | /* allocate the message array */ |
| | 492 | *arr = (err_msg_t *)t3malloc(*arr_size * sizeof(err_msg_t)); |
| | 493 | if (*arr == 0) |
| | 494 | goto fail; |
| | 495 | |
| | 496 | /* clear the memory */ |
| | 497 | memset((err_msg_t *)*arr, 0, *arr_size * sizeof(err_msg_t)); |
| | 498 | |
| | 499 | /* read the individual messages */ |
| | 500 | for (i = 0, msg = (err_msg_t *)*arr ; i < *arr_size ; ++i, ++msg) |
| | 501 | { |
| | 502 | size_t len1, len2; |
| | 503 | |
| | 504 | /* read the current message ID and the length of the two messages */ |
| | 505 | if (osfrb(fp, buf, 8)) |
| | 506 | goto fail; |
| | 507 | |
| | 508 | /* set the message ID */ |
| | 509 | msg->msgnum = (int)t3rp4u(buf); |
| | 510 | |
| | 511 | /* get the short and long mesage lengths */ |
| | 512 | len1 = osrp2(buf + 4); |
| | 513 | len2 = osrp2(buf + 6); |
| | 514 | |
| | 515 | /* allocate buffers */ |
| | 516 | msg->short_msgtxt = (char *)t3malloc(len1 + 1); |
| | 517 | msg->long_msgtxt = (char *)t3malloc(len2 + 1); |
| | 518 | |
| | 519 | /* if either one failed, give up */ |
| | 520 | if (msg->short_msgtxt == 0 || msg->long_msgtxt == 0) |
| | 521 | goto fail; |
| | 522 | |
| | 523 | /* read the two messages */ |
| | 524 | if (osfrb(fp, (char *)msg->short_msgtxt, len1) |
| | 525 | || osfrb(fp, (char *)msg->long_msgtxt, len2)) |
| | 526 | goto fail; |
| | 527 | |
| | 528 | /* null-terminate the strings */ |
| | 529 | *(char *)(msg->short_msgtxt + len1) = '\0'; |
| | 530 | *(char *)(msg->long_msgtxt + len2) = '\0'; |
| | 531 | } |
| | 532 | |
| | 533 | /* success */ |
| | 534 | return 0; |
| | 535 | |
| | 536 | fail: |
| | 537 | /* revert back to the built-in array */ |
| | 538 | err_delete_message_array(arr, arr_size, |
| | 539 | default_arr, default_arr_size); |
| | 540 | |
| | 541 | /* indicate failure */ |
| | 542 | return 1; |
| | 543 | } |
| | 544 | |
| | 545 | /* |
| | 546 | * Determine if an external message file has been loaded for the default |
| | 547 | * VM message set |
| | 548 | */ |
| | 549 | int err_is_message_file_loaded() |
| | 550 | { |
| | 551 | /* |
| | 552 | * if we're not using the compiled-in message array, we must have |
| | 553 | * loaded an external message set |
| | 554 | */ |
| | 555 | return vm_messages != &vm_messages_english[0]; |
| | 556 | } |
| | 557 | |
| | 558 | /* |
| | 559 | * Delete the message array, if one is loaded |
| | 560 | */ |
| | 561 | void err_delete_message_array(const err_msg_t **arr, size_t *arr_size, |
| | 562 | const err_msg_t *default_arr, |
| | 563 | size_t default_arr_size) |
| | 564 | { |
| | 565 | /* |
| | 566 | * If the message array is valid, and it's not set to point to the |
| | 567 | * built-in array of English messages, we must have allocated it, so |
| | 568 | * we must now free it. We don't need to free it if it points to |
| | 569 | * the English array, because that's static data linked into the VM |
| | 570 | * executable. |
| | 571 | */ |
| | 572 | if (*arr != 0 && *arr != default_arr) |
| | 573 | { |
| | 574 | size_t i; |
| | 575 | err_msg_t *msg; |
| | 576 | |
| | 577 | /* delete each message in the array */ |
| | 578 | for (i = 0, msg = (err_msg_t *)*arr ; i < *arr_size ; ++i, ++msg) |
| | 579 | { |
| | 580 | /* delete the strings for this entry */ |
| | 581 | if (msg->short_msgtxt != 0) |
| | 582 | t3free((char *)msg->short_msgtxt); |
| | 583 | if (msg->long_msgtxt != 0) |
| | 584 | t3free((char *)msg->long_msgtxt); |
| | 585 | } |
| | 586 | |
| | 587 | /* delete the message array itself */ |
| | 588 | t3free((err_msg_t *)*arr); |
| | 589 | } |
| | 590 | |
| | 591 | /* set the messages array back to the built-in english messages */ |
| | 592 | *arr = default_arr; |
| | 593 | *arr_size = default_arr_size; |
| | 594 | } |
| | 595 | |
| | 596 | /* ------------------------------------------------------------------------ */ |
| | 597 | /* |
| | 598 | * Find an error message |
| | 599 | */ |
| | 600 | const char *err_get_msg(const err_msg_t *msg_array, size_t msg_count, |
| | 601 | int msgnum, int verbose) |
| | 602 | { |
| | 603 | int hi, lo, cur; |
| | 604 | |
| | 605 | /* perform a binary search of the message list */ |
| | 606 | lo = 0; |
| | 607 | hi = msg_count - 1; |
| | 608 | while (lo <= hi) |
| | 609 | { |
| | 610 | /* split the difference */ |
| | 611 | cur = lo + (hi - lo)/2; |
| | 612 | |
| | 613 | /* is it a match? */ |
| | 614 | if (msg_array[cur].msgnum == msgnum) |
| | 615 | { |
| | 616 | /* it's the one - return the text */ |
| | 617 | return (verbose |
| | 618 | ? msg_array[cur].long_msgtxt |
| | 619 | : msg_array[cur].short_msgtxt); |
| | 620 | } |
| | 621 | else if (msgnum > msg_array[cur].msgnum) |
| | 622 | { |
| | 623 | /* we need to go higher */ |
| | 624 | lo = (cur == lo ? cur + 1 : cur); |
| | 625 | } |
| | 626 | else |
| | 627 | { |
| | 628 | /* we need to go lower */ |
| | 629 | hi = (cur == hi ? cur - 1 : cur); |
| | 630 | } |
| | 631 | } |
| | 632 | |
| | 633 | /* no such message */ |
| | 634 | return 0; |
| | 635 | } |
| | 636 | |
| | 637 | /* ------------------------------------------------------------------------ */ |
| | 638 | /* |
| | 639 | * Format a message with the parameters contained in an exception object. |
| | 640 | * Suports the following format codes: |
| | 641 | * |
| | 642 | * %s - String. Formats an ERR_TYPE_CHAR or ERR_TYPE_TEXTCHAR value, |
| | 643 | * including the counted-length versions. |
| | 644 | * |
| | 645 | * %d, %u, %x - signed/unsigned decimal integer, hexadecimal integer. |
| | 646 | * Formats an ERR_TYPE_INT value or an ERR_TYPE_ULONG value. |
| | 647 | * Automatically uses the correct size for the argument. |
| | 648 | * |
| | 649 | * %% - Formats as a single percent sign. |
| | 650 | */ |
| | 651 | void err_format_msg(char *outbuf, size_t outbuflen, |
| | 652 | const char *msg, const CVmException *exc) |
| | 653 | { |
| | 654 | int curarg; |
| | 655 | const char *p; |
| | 656 | char *dst; |
| | 657 | int exc_argc; |
| | 658 | |
| | 659 | /* if there's no space at all, ignore the request */ |
| | 660 | if (outbuf == 0 || outbuflen == 0) |
| | 661 | return; |
| | 662 | |
| | 663 | /* get the number of parameters in the exception object */ |
| | 664 | exc_argc = (exc == 0 ? 0 : exc->get_param_count()); |
| | 665 | |
| | 666 | /* start with the first parameter */ |
| | 667 | curarg = 0; |
| | 668 | |
| | 669 | /* start at the beginning of the buffer */ |
| | 670 | dst = outbuf; |
| | 671 | |
| | 672 | /* if there's no message, there's nothing to return */ |
| | 673 | if (msg == 0) |
| | 674 | { |
| | 675 | *dst = '\0'; |
| | 676 | return; |
| | 677 | } |
| | 678 | |
| | 679 | /* scan the format string for formatting codes */ |
| | 680 | for (p = msg ; *p != '\0' ; ++p) |
| | 681 | { |
| | 682 | /* |
| | 683 | * If we're out of space, stop now. Make sure we leave room for |
| | 684 | * the terminating null byte. |
| | 685 | */ |
| | 686 | if (dst + 1 >= outbuf + outbuflen) |
| | 687 | break; |
| | 688 | |
| | 689 | /* if it's a format specifier, translate it */ |
| | 690 | if (*p == '%') |
| | 691 | { |
| | 692 | const char *src; |
| | 693 | char srcbuf[30]; |
| | 694 | err_param_type typ; |
| | 695 | size_t len; |
| | 696 | int use_strlen; |
| | 697 | |
| | 698 | /* |
| | 699 | * if no more parameters are available, ignore the |
| | 700 | * formatting code entirely, and leave it in the string as |
| | 701 | * it is |
| | 702 | */ |
| | 703 | if (curarg >= exc_argc) |
| | 704 | { |
| | 705 | *dst++ = *p; |
| | 706 | continue; |
| | 707 | } |
| | 708 | |
| | 709 | /* get the type of the current parameter */ |
| | 710 | typ = exc->get_param_type(curarg); |
| | 711 | |
| | 712 | /* |
| | 713 | * presume we'll want to use strlen to get the length of the |
| | 714 | * source value |
| | 715 | */ |
| | 716 | use_strlen = TRUE; |
| | 717 | |
| | 718 | /* skip the '%' and determine what follows */ |
| | 719 | ++p; |
| | 720 | switch (*p) |
| | 721 | { |
| | 722 | case 's': |
| | 723 | /* get the string value using the appropriate type */ |
| | 724 | if (typ == ERR_TYPE_TEXTCHAR) |
| | 725 | src = exc->get_param_text(curarg); |
| | 726 | else if (typ == ERR_TYPE_CHAR) |
| | 727 | src = exc->get_param_char(curarg); |
| | 728 | else if (typ == ERR_TYPE_CHAR_LEN) |
| | 729 | { |
| | 730 | /* get the string value and its length */ |
| | 731 | src = exc->get_param_char_len(curarg, &len); |
| | 732 | |
| | 733 | /* |
| | 734 | * src isn't null terminated, so don't use strlen to |
| | 735 | * get its length - we already have it from the |
| | 736 | * parameter data |
| | 737 | */ |
| | 738 | use_strlen = FALSE; |
| | 739 | } |
| | 740 | else |
| | 741 | src = "s"; |
| | 742 | break; |
| | 743 | |
| | 744 | case 'd': |
| | 745 | src = srcbuf; |
| | 746 | if (typ == ERR_TYPE_INT) |
| | 747 | sprintf(srcbuf, "%d", exc->get_param_int(curarg)); |
| | 748 | else if (typ == ERR_TYPE_ULONG) |
| | 749 | sprintf(srcbuf, "%ld", exc->get_param_ulong(curarg)); |
| | 750 | else |
| | 751 | src = "d"; |
| | 752 | break; |
| | 753 | |
| | 754 | case 'u': |
| | 755 | src = srcbuf; |
| | 756 | if (typ == ERR_TYPE_INT) |
| | 757 | sprintf(srcbuf, "%u", exc->get_param_int(curarg)); |
| | 758 | else if (typ == ERR_TYPE_ULONG) |
| | 759 | sprintf(srcbuf, "%lu", exc->get_param_ulong(curarg)); |
| | 760 | else |
| | 761 | src = "u"; |
| | 762 | break; |
| | 763 | |
| | 764 | case 'x': |
| | 765 | src = srcbuf; |
| | 766 | if (typ == ERR_TYPE_INT) |
| | 767 | sprintf(srcbuf, "%x", exc->get_param_int(curarg)); |
| | 768 | else if (typ == ERR_TYPE_ULONG) |
| | 769 | sprintf(srcbuf, "%lx", exc->get_param_ulong(curarg)); |
| | 770 | else |
| | 771 | src = "x"; |
| | 772 | break; |
| | 773 | |
| | 774 | case '%': |
| | 775 | /* add a single percent sign */ |
| | 776 | src = "%"; |
| | 777 | break; |
| | 778 | |
| | 779 | default: |
| | 780 | /* invalid format character; leave the whole thing intact */ |
| | 781 | src = srcbuf; |
| | 782 | srcbuf[0] = '%'; |
| | 783 | srcbuf[1] = *p; |
| | 784 | srcbuf[2] = '\0'; |
| | 785 | break; |
| | 786 | } |
| | 787 | |
| | 788 | /* get the length, if it's null-terminated */ |
| | 789 | if (use_strlen) |
| | 790 | len = strlen(src); |
| | 791 | |
| | 792 | /* figure out how much of the value we can copy */ |
| | 793 | if (len > outbuflen - (dst - outbuf) - 1) |
| | 794 | len = outbuflen - (dst - outbuf) - 1; |
| | 795 | |
| | 796 | /* copy the value and advance past it in the output buffer */ |
| | 797 | memcpy(dst, src, len); |
| | 798 | dst += len; |
| | 799 | |
| | 800 | /* consume the argument */ |
| | 801 | ++curarg; |
| | 802 | } |
| | 803 | else |
| | 804 | { |
| | 805 | /* just copy the current character as it is */ |
| | 806 | *dst++ = *p; |
| | 807 | } |
| | 808 | } |
| | 809 | |
| | 810 | /* add the trailing null */ |
| | 811 | *dst++ = '\0'; |
| | 812 | } |
| | 813 | |