tada@athena.mit.edu (Michael J Zehr) (05/08/90)
Enough people have requested this (after a posting of mine about 2 weeks ago) that i'm posting it here. I hope the comments in error.h and the samples in err_test.c give enough information for people to use it. note particularly the restrictions in the first comment in error.h. warning 1 -- this required ANSI headers. warning 2 -- i only had one machine to test this on. it's possible that something i'm doing works by coincidence only. (gosh i hope not, though! *sigh* i don't have lint, though) use with the caution you should use anything you get from the net... and feel free to respond with any suggestions. three files: error.h, error.c, and err_test.c lines of '---' separate the files (i don't know how to make a shell archive) error.h: -------------------------------------------------- /* error.h -- created 7-27-89 by michael j zehr ** ** i am indebted to Sam Itkin for the inspiration for this design. ** Sam and i currently work for Consultants for Management Decision, Inc, ** in Cambridge Mass, where he gave a luncheon seminar on error-handling ** methods. this represents an implementation of a design he sketched out. ** ** feel free to use this code, but please attribute it. ** ** error handling macros, definitions, and structures ** ** the error-handling mechanism is designed to allow error-trapping through ** non-local jumps, including deallocating resources along the way ** ** an example of use would be as follows: ** ** ... ** if (error_exit(error_code, error_class) { ** error-handling code; ** return; ** } ** ** id = on_error(cleanup, param1, param2, ...); ** do_something(); ** if (problem) error(err_code); ** no_error(id); ** ... ** clear_error_exit(); ** ** if do_something() contains a call to error(), or if the problem condition is ** true and error() is called at that level, then cleanup() will be executed ** (along with any other routines registered through on_error()) ** the cleanup will continue back to the most recent error_exit for which ** the error_class & (error function parameter) is nonzero, and control ** returns to the 'if' clause at the top of the procedure. ** (note that error(0) will not be trapped by any error_exit call, and will ** execute all functions on the error stack and eventually exit execution.) ** ** RESTRICTIONS: ** defining more than MAX_ERROR_LEVELS error_exits simlultaneously is a ** considered a fatal error, and halts execution. ** ** calling error() from withing a "cleanup()" routine registered ** with the error handler also halts execution. ** ** calling error() before checking error_exit is a bug too. ** ** because the non-local jumps depend on the state of the procedure stack, ** clear_error_exit() must be called from the same procedure as error_exit(). ** ** this code is written for ANSI C compilers and headers. if you want to run ** on something else, you need to include varargs.h, probably, and change ** the macros for variable parameter lists. you also need to change the ** void *'s to char *'s, include something other than stdlib.h, etc... ** ** this code is not intended to be a completely robust error-handler. ** it does, however, provide a way of using a non-local jump and also ** freeing allocated resources. ** ** one thing in particular that needs to be addressed is how error_exit ** handles the error-code returned. should it look for only one error ** code and propagate the rest through error()? should it look for a class ** of error codes (checking a particular bit)? ideally it should look for ** a list of error codes, but what is the best way to code that? ** ** one other area for modification would be in the error_stack structure. ** the arrays could be changed to pointers, and an error_init function ** could initialize it given values for error_depth, etc... ** */ #ifndef _ERROR_H_ #define _ERROR_H_ #include <stdlib.h> #include <setjmp.h> #include <errno.h> #define ERROR_DEPTH 15 #define ERROR_PARAMS 6 #define ERROR_STACK_SIZE 250 /* depending on what method you want to use for error codes, theses may ** or may not be useful */ #define FATAL (1<<15) #define ERROR (1<<14) #define WARNING (1<<13) #define ALL_CLASSES (~0) /* in VAXC, the last predefined error message is 65 - starting at 128 ** leaves room for predefined expansions without having to renumber */ #ifdef VAXC #define ENOMEM (128) /* out of memory */ #define EBADARGS (129) /* bad arguments */ #endif /* errno.h labelled as verion 7.1 (Berkeley) 6/4/86 has an entirely different ** set which goes up to 75 and includes ENOMEM ... ** you'll have to pick your own values to match whatever platform(s) you ** need to run on */ /* variable definitions and declarations */ typedef struct { int num_params; /* parameters for function */ void *params[ERROR_PARAMS]; /* each paramter */ void (*func)(); /* error function */ } Err_func; typedef struct { int error_fcnt; /* number of error functions on stack */ int error_code; /* error code if unwinding, zero otherwise */ int error_depth; /* number of error_exits active */ jmp_buf env[ERROR_DEPTH]; /* data for longjmp */ int jmp_index[ERROR_DEPTH]; /* function stack location of mylongjmp */ Err_func f[ERROR_STACK_SIZE]; /* cleanup functions */ } Err_stack; #pragma nostandard extern noshare Err_stack err_stack; extern noshare int _tmp_int; extern noshare void *_tmp_ptr; #pragma standard /* function declarations */ void mylongjmp(void); /* interface to longjmp() */ void error(int error_code); /* unwinds stack, returns to error_exit */ /* sets up stack to call func if an error is triggered. */ /* returns an ID used to clear error stack */ int on_error(void (*func)(), int num_params, ...); void no_error(int id); /* clears the error stack of that routine */ void clear_error_exit(void); /* clears last error trap */ void *malloc_remember(size_t size, int error_code); /* must be a macro, since it depends on setjmp */ #define error_exit(error_code, error_class) \ (((err_stack.error_depth+1 > ERROR_DEPTH) ? (abort(),0) : 0), \ (err_stack.jmp_index[err_stack.error_depth] = err_stack.error_fcnt), \ (on_error(mylongjmp, 0)), \ (((error_code) = setjmp(err_stack.env[err_stack.error_depth++])) ? \ (((error_code) & (error_class)) ? \ (error_code) : \ (error(error_code),0)) : \ (error_code)) \ ) /* sample macros for mallo */ #define safe_new(t) \ ((t*)malloc_remember(sizeof(t), ENOMEM)) #define safe_new_array(t, n) \ ((t*)malloc_remember(sizeof(t) * (n), ENOMEM)) #endif -------------------------------------------------- error.c -------------------------------------------------- /* error.c -- created 7-31-89 by michael j zehr ** ** error handling function definitions */ #include <stdio.h> #include <stdarg.h> #include <assert.h> #include "error.h" /* global error stack declaration, plus temp variables for macros */ #ifdef VAXC #pragma nostandard noshare Err_stack err_stack; #pragma standard #else Err_stack err_stack; #endif /* function declarations */ /* interface to longjmp -- clears the current error from the ** error stack, decrements error_depth, and calls longjmp with ** the env from the err_stack structure */ void mylongjmp(void) { int err_code; err_code = err_stack.error_code; err_stack.error_code = 0; err_stack.error_depth--; longjmp(err_stack.env[err_stack.error_depth], err_code); } /* triggers an error. ** sets the error_code in the err_stack structure and starts ** unwinding the error_stack, calling each function in turn until ** it hits the call to mylongjmp, which then returns control to ** the last error_exit macro */ void error(int error_code) { int findex; Err_func *errf; if (err_stack.error_depth <= 0) { fprintf(stderr, "error with no error handler in scope. code = %d\n", error_code); assert(0); } err_stack.error_code = error_code; while(1) { errf = &err_stack.f[--err_stack.error_fcnt]; switch (errf->num_params) { case 0: (*errf->func)(); break; case 1: (*errf->func)(errf->params[0]); break; case 2: (*errf->func)(errf->params[0], errf->params[1]); break; case 3: (*errf->func)(errf->params[0], errf->params[1], errf->params[2]); break; case 4: (*errf->func)(errf->params[0], errf->params[1], errf->params[2], errf->params[3]); break; case 5: (*errf->func)(errf->params[0], errf->params[1], errf->params[2], errf->params[3], errf->params[4]); break; case 6: (*errf->func)(errf->params[0], errf->params[1], errf->params[2], errf->params[3], errf->params[4], errf->params[5]); break; } /* end of switch */ } /* end of while -- execution always transfers through longjmp */ } /* puts func on the error stack, along with the parameters ** an ID is returned which is used to clear the error in no_error() */ int on_error(void (*func)(), int num_params, ...) { va_list ap; int i; err_stack.f[err_stack.error_fcnt].func = func; err_stack.f[err_stack.error_fcnt].num_params = num_params; if (num_params > ERROR_PARAMS) { fprintf(stderr, "%d too many params for error function\n", num_params); assert(0); } va_start(ap, num_params); /* set up all the parameters for the error function */ for(i=0; i<num_params; i++) err_stack.f[err_stack.error_fcnt].params[i] = va_arg(ap, void *); err_stack.error_fcnt++; va_end(ap); return err_stack.error_fcnt; } /* clears the error function associated with that ID. ** if it isn't the most recent routine pushed on the error stack, ** then it prints a diagnostic and exits */ void no_error(int id) { if (id != err_stack.error_fcnt) { fprintf(stderr, "attempt to clear error func %d out of scope\n", id); assert(0); } /* 'remove' it from the top of the stack */ err_stack.error_fcnt--; } /* clears the last error_exit macro trap */ void clear_error_exit(void) { if (err_stack.error_depth <= 0) { fprintf(stderr, "attempt to clear error trap with no error trap set\n"); assert(0); } err_stack.error_depth--; err_stack.error_fcnt = err_stack.jmp_index[err_stack.error_depth]; } /* dynamic memory allocation -- allocates memory and sets up the error stack ** to free it. because the result of on_error() is discarded, either the ** entire error stack is executed through an error() call, or it's all ** discarded through a call to clear_error_exit(). */ void *malloc_remember(size_t size, int err_code) { void *temp; void free(); temp = malloc(size); if (temp == NULL) error(err_code); on_error(free, 1, temp); return temp; } -------------------------------------------------- err_test.c -- shows how it's used a bit... can be used to test it. -------------------------------------------------- /* test program for error handling routines */ #include "error.h" #include <stdio.h> void foo(char *str) { puts("in trap1"); puts(str); } void foo2(char *str) { puts("in trap2"); puts(str); } void foo1(void) { int err_id, err_code; char *str = "second error trap function"; puts("in foo1"); if (error_exit(err_code, 2)) { puts("arrived at error_exit2"); printf("error code: %d\n", err_code); exit(1); } err_id = on_error(foo2, 1, str); clear_error_exit(); #ifdef DEBUG puts("about to generate error"); error(1); #else puts("returning without error"); #endif } int main(void) { int err_code, err_id; char *str = "test string"; if (error_exit(err_code, 1)) { puts("arrived at error_exit"); printf("error code: %d\n", err_code); exit(1); } puts("past error_exit"); err_id = on_error(foo, 1, str, NULL, NULL, NULL, NULL, NULL); foo1(); no_error(err_id); puts("about to call clear_error_exit"); clear_error_exit(); }