[gnu.g++] unwind-protect for c

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.