ted@nmsu.edu (Ted Dunning) (08/24/89)
we have had some disk full problems with getting news posted, so this posting has been delayed slightly. i have reviewed my unwind-protect package, added a Makefile and README and slightly saberized the code. i tested it only on a sun-3. since this code involves relatively bizarre (ab)use of the preprocessor, it may well not run on your favorite machine. please mail me comments or bug !fixes! at ted@nmsu.edu so here it is, by popular demand, #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create: # README # Makefile # unwind.h # test_unwind.c # unwind.c # test_output # correct_output # This archive created: Wed Aug 23 17:42:23 MDT 1989 if test -f 'README' then echo shar: will not over-write existing file 'README' else sed -e '1,$s/^XX//' << 'SHAR_EOF' X README XXThis unwind/package package implements a facility similar to that XXprovided by Common Lisp's unwind-protect, catch and throw. XX XXThe utility of this package is dependent on code being a valid XXargument for a preprocessor macro. To test this assumption, you can XXuse `make test_output' and compare the resulting contents of XXtest_output with the contents of correct_output. XX XXThe file test_unwind.c should contain sufficient examples and XXdocumentation to allow someone familiar with what unwind-protect does XXin lisp to unravel how to use the package. Macros are used to XXobscurity and so it will not be surprising if the code (especially in XXunwind.h) is impenetrable. You have my apologies. XX XXThe basic purpose of unwind protection is to allow the execution of a XXpiece of code with the guarantee that a piece of cleanup code will be XXexecuted even if some form of non-local exit is used. For example, if XXwe have a numerically controlled drill, we might want to be sure that XXafter some action, the bit is raised and the motor is turned off. To XXassure this, we might write code of the form: XX XX protect(do_some_work(), XX raise_bit();turn_motor(OFF)); XX XXThe use of the protect macro here guarantees (see reservations below) XXthat raise_bit() and turn_motor() will be called, even if XXdo_some_work() is exited via a non-local exit. Of course, this XXguarantee is voided if you don't use throw() to perform this non-local XXexit. XX XXCatch() and throw() are used in conjunction with protect() to allow XXnon-local exits, and to allow values to be returned via these XXnon-local mechanisms. For example, if in the previous code, we wanted XXsome completion code to be set to a non-zero value if do_some_work() XXterminates abnormally, we could use the following code: XX XX work_code = 0; XX catch(DO_SOME_WORK_TAG, XX protect(do_some_work(), XX raise_bit();turn_motor(OFF)), XX work_code); XX XXThen if do_some_work() needs to exit non-locally, it can use XXthrow(DO_SOME_WORK_TAG, error_code) where error_code is the value to XXbe stored in work_code. XX XXIf you need to use one of the builtin C mechanisms for non-local exits XXsuch as break, next, or return, then unwind() provides a mechanism for XXyou to unwind the protection stack manually. In order to do this, you XXmust know how many rings of protection/catchers you want to unwind. XX XXThe function unwind_and_exit() can used to unwind all protections and XXexit. The following procedure is an fanciful example of the use of XXunwind() and unwind_and_exit(), XX XXfoo(n) XX{ XX int i; XX catch(2, XX protect( XX if (n>3) { XX unwind_and_exit(1); XX } XX else if (n == 0) { XX unwind(2); XX return 42; XX } XX ), XX i); XX n = i; XX return i; /* not executed if n == 3 */ XX} XX XX XXUnwind() should only unwind protections that are lexically visible. XXIt should not be used to simulate the operation of unwind_and_exit(). XX XX XX------- XXCAVEATS XX------- XX XX XXThe code for the macros is probably unreadable. XX XX XXUnwind() can cause problems since it munges on the nice lexical XXordering of the unwind stack. It should only be used to unwind XXlexically enclosing protections and catchers (as opposed to XXnon-visible dynamically enclosing protections). In particular, XXunwind_and_exit() should be used to terminate the program. XX XX XXThis code has only been tested on a sun-3. Good Luck. XX XX XXIf you find and correct a bug in this code, please mail the update to XXme at ted@nmsu.edu so that i can incorporate the fix. XX XX XXPlease keep this code free. Incorporate it into proprietary code if XXyou want, but distribute the source code to this part freely. XX XX SHAR_EOF echo recreated README chmod u=rw README chmod g=rw README chmod o=r README fi if test -f 'Makefile' then echo shar: will not over-write existing file 'Makefile' else sed -e '1,$s/^XX//' << 'SHAR_EOF' X Makefile XXCFLAGS = -g XX XXtest_unwind: test_unwind.o unwind.o XX cc $(CFLAGS) test_unwind.o unwind.o -o test_unwind XX XXtest_unwind.o : unwind.h XX XXtest_output : test_unwind XX -test_unwind 1> test_output 2>&1 XX SHAR_EOF echo recreated Makefile chmod u=rw Makefile chmod g=rw Makefile chmod o=r Makefile fi if test -f 'unwind.h' then echo shar: will not over-write existing file 'unwind.h' else sed -e '1,$s/^XX//' << 'SHAR_EOF' X unwind.h XX/* macro based implementation of unwind-protect/catch/throw a la common lisp XX XX Copyright 1989, Ted Dunning (ted@nmsu.edu) XX Permission to copy and distribute this code is granted on the condition XX that this source code be freely distributed to others even if incorporated XX in a proprietary product. XX */ XX XX/* XX XXprotect(Protected, Cleanup) executes Protected with the guarantee that XXCleanup will be executed even if throw is used to non-locally exit XXProtected. Both Protected and Cleanup are C code and may each be a XXseries of statements separated by semi-colons (depending on XXpreprocessor quality XX XXcatch(Tag, Statements, Catch_Variable) establishes a catch ring around XXStatements. any values throw'n in the course of executing Statements XXwith a matching Tag will be assigned to Catch_Variable. any XXunwind-protection established within Statements will be respected. XX XXthrow(Tag, Value) Value will be thrown to the innermost catch variable XXwhich has a matching tag. XX XXunwind(N) cleans up N catch or protect rings. this is used to allow XXforms of goto such as return, break, and next to be used with the XXunwind-protect package. XX XX XX---------------- XX XX XXthese primitives will work as long as throw is the only mechanism used XXto cross catch and protect boundaries. examples of other methods XXinclude return, break, next, exit() and goto. if atexit() is XXsupported, then exit() can be fixed. XX XXbreak, goto, next and return can all be handled by using unwind. XXto do this, the programmer must explicitly provide the number of XXprotect and catch rings to be unwound. a trivial preprocessor could XXbe written to handle everything except goto in exactly this way. XX XXthe major beef i still have with this code is that it malloc's and XXthen throws away each unwind stack frame separately. if you have an XXexpensive malloc (like everybody else in the world, then you might XXwant to hack up a frame wholesaler that calloc's a bunch of frames and XXwarehouses them until needed. XX XX*/ XX XX#include <setjmp.h> XX XXvoid *setup_newframe(); XXvoid throw(); XX XX/* ineffable use of macros follows, caveat lector */ XX#define protect(S1,S2) if (setup_unwind(0,0)) {S1;cleanup_unwind();S2;} else {S2; continue_unwind();} XX#define catch(Tag,S1,Var) if (setup_unwind(1,Tag)) {S1;cleanup_unwind();} else {Var=thrown_value;cleanup_unwind();} XX#define unwind(n) {int i;catch(i=safe_tag(),move_top_frame(n);throw(i,0),i)} XX XX/* this macro can't be a procedure because setjmp is used */ XX#define setup_unwind(type,tag) (!setjmp(setup_newframe(type,tag))) XX XX XX XX/* these are only here so they are in scope for the macros... not to XX be used directly */ XX XXtypedef struct UWS { XX short type; /* 0 => unwind, 1 => catch */ XX int tag; /* a tag if a catch */ XX struct UWS *next; /* the rest of the stack */ XX jmp_buf context; /* where to go */ XX} *unwind_stack; XX XXunwind_stack XX uw_stack, /* the unwind stack */ XX new_frame; /* must be a global, since */ XX /* setup_unwind because must be a macro */ XX XXlong thrown_value; /* the value that was last thrown */ SHAR_EOF echo recreated unwind.h chmod u=rw unwind.h chmod g=rw unwind.h chmod o=r unwind.h fi if test -f 'test_unwind.c' then echo shar: will not over-write existing file 'test_unwind.c' else sed -e '1,$s/^XX//' << 'SHAR_EOF' X test_unwind.c XX/* XX Copyright 1989, Ted Dunning (ted@nmsu.edu) XX Permission to copy and distribute this code is granted on the condition XX that this source code be freely distributed to others even if incorporated XX in a proprietary product. XX*/ XX#include <stdio.h> XX#include "unwind.h" XX XXfoo(i,j) XXint i,j; XX{ XX if (j) throw(i,j+33); XX else return; XX} XX XXmain() XX{ XX int i; XX XX catch(1, XX protect( XX foo(1,32), XX printf("protect foo\n")), XX i); XX printf("i=%d\n",i); XX if (i == 65) printf("threw the correct value\n"); XX else printf("lost throw error\n"); XX XX XX catch(2, XX i = 0; XX protect( XX foo(2,0), XX printf("protect foo\n")), XX i); XX printf("i=%d\n",i); XX if (i == 0) printf("did not disturb the correct value\n"); XX else printf("excess throw error\n"); XX XX catch(3, XX i = -1; XX protect( XX printf("starting "), XX printf("protected\n")); XX unwind(1);goto EXIT;, XX i); XX printf("should skip this\n"); XX EXIT: XX printf("i=%d\n",i); XX XX catch(4, XX protect( XX foo(3,3), XX printf("protect foo\n")), XX i); XX printf("i=%d\n",i); XX} SHAR_EOF echo recreated test_unwind.c chmod u=rw test_unwind.c chmod g=rw test_unwind.c chmod o=r test_unwind.c fi if test -f 'unwind.c' then echo shar: will not over-write existing file 'unwind.c' else sed -e '1,$s/^XX//' << 'SHAR_EOF' X unwind.c XX/* XX Copyright 1989, Ted Dunning (ted@nmsu.edu) XX Permission to copy and distribute this code is granted on the condition XX that this source code be freely distributed to others even if incorporated XX in a proprietary product. XX*/ XX#include <stdio.h> XX#include "unwind.h" XX XXunwind_stack XX uw_stack=NULL, /* the unwind stack */ XX cleanup_limit = NULL; /* how far back do we cleanup? */ XXlong current_thrown_tag; /* what tag was last thrown */ XXint exit_value=0; /* stash for unwind_and_exit() */ XX XX/* cleanup one level of unwind-protection or catch ring */ XXcleanup_unwind() XX{ XX unwind_stack p; XX if (uw_stack) { XX p = uw_stack; XX uw_stack = uw_stack->next; XX free(p); XX } XX} XX XX/* build an unwind frame and push it on the stack */ XXvoid *setup_newframe(type,tag) XXint tag; XX{ XX new_frame = (unwind_stack) malloc(sizeof(*new_frame)); XX XX new_frame->type = type; XX new_frame->tag = tag; XX new_frame->next = uw_stack; XX uw_stack = new_frame; XX XX return (void *) (new_frame->context); /* we return the context to */ XX /* fit into the macro that */ XX /* calls us */ XX} XX XX/* do a throw. this involves searching back through the unwind stack */ XX/* and finding a matching catch. we do the search first in case we */ XX/* later want to add the capacity to trap an unmatched throw without */ XX/* doing any clean-ups. without a complete error handler, though, */ XX/* this doesn't make sense */ XXvoid throw(tag,value) XXint tag; XXlong value; XX{ XX unwind_stack p; XX XX p = uw_stack; /* look for our catcher */ XX while (p && (p->type != 1 || p->tag != tag)) { XX p = p->next; XX } XX XX if (!p) { XX fprintf(stderr,"no catch with tag %d\n",tag); XX } XX current_thrown_tag = tag; XX thrown_value = value; /* stash the value away */ XX cleanup_limit = NULL; XX XX continue_unwind(); XX} XX XX/* this is called when a throw is done, or at the tail end of an */ XX/* unwind-protection statement. we deallocate any unmatched catchers */ XX/* and execute the first protect that we find. this procedure will be */ XX/* called again to continue the process. since the frames on the */ XX/* unwind stack can't be made into procedures, this pseudo-return is */ XX/* the only hope */ XX XXint continue_unwind() XX{ XX jmp_buf j; XX XX /* throw away the wrong catchers */ XX while (uw_stack && XX uw_stack->type == 1 && XX uw_stack != cleanup_limit && XX uw_stack->tag != current_thrown_tag) { XX cleanup_unwind(); XX } XX XX if (!uw_stack) { /* should only be as a result of */ XX exit(exit_value); /* unwind and exit */ XX } XX else if (uw_stack != cleanup_limit) { XX /* did we find our catcher or a protect? */ XX if (uw_stack) { XX bcopy(uw_stack->context,j,sizeof(j)); XX cleanup_unwind(); XX longjmp(j,1); XX } XX } XX XX /* the only way out here is if we hit a non-NULL cleanup_limit */ XX cleanup_limit = NULL; XX} XX XX/* find a novel tag for use in explicit unwinding */ XXint safe_tag() XX{ XX unwind_stack p; XX int max_tag; XX XX XX max_tag = 0; XX for (p=uw_stack;p;p=p->next) { XX if (p->tag > max_tag) max_tag = p->tag; XX } XX return max_tag+1; XX} XX XX/* move the first frame on the stack down to the n positions */ XXmove_top_frame(n) XX{ XX int i; XX unwind_stack p,q,t; XX XX p = uw_stack; XX uw_stack = p->next; XX q = p->next; XX XX for (i=1;i<n && q;i++) { XX q = q->next; XX } XX XX t = q->next; XX q->next = p; XX p->next = t; XX} XX XX/* can't use unwind safely here if longjmp does stack unwinds */ XXunwind_and_exit(n) XXint n; XX{ XX exit_value = n; XX throw(safe_tag()); XX} SHAR_EOF echo recreated unwind.c chmod u=rw unwind.c chmod g=rw unwind.c chmod o=r unwind.c fi if test -f 'test_output' then echo shar: will not over-write existing file 'test_output' else sed -e '1,$s/^XX//' << 'SHAR_EOF' X test_output XXprotect foo XXi=65 XXthrew the correct value XXprotect foo XXi=0 XXdid not disturb the correct value XXstarting protected XXran off the end of the unwind stack SHAR_EOF echo recreated test_output chmod u=rw test_output chmod g=rw test_output chmod o=r test_output fi if test -f 'correct_output' then echo shar: will not over-write existing file 'correct_output' else sed -e '1,$s/^XX//' << 'SHAR_EOF' X correct_output XXprotect foo XXi=65 XXthrew the correct value XXprotect foo XXi=0 XXdid not disturb the correct value XXstarting protected XXran off the end of the unwind stack SHAR_EOF echo recreated correct_output chmod u=rw correct_output chmod g=rw correct_output chmod o=r correct_output fi # End of shell archive -- ted@nmsu.edu Most of all, he loved the fall when the cottonwoods leaves turned gold and floated down the trout streams under the clear blue windswept skies.