error handling and freeing resources using setjmp/longjmp
Michael J Zehr
tada at athena.mit.edu
Wed May 9 02:11:00 AEST 1990
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();
}
More information about the Comp.lang.c
mailing list