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