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