[comp.lang.c++] Unwinding vs destructors

nagle@well.UUCP (John Nagle) (08/15/89)

    C++ destructors provide a means to force cleanup of an object as it
disappears upon exit from a scope.  Unfortunately, the "longjmp" mechanism
evades destructor processing.  "longjmp" is an old hack, only sort of part
of C.  But it is useful.  Should C++ have a better escape mechanism,
along the lines of "throw" and "return-from" in Common LISP or "raise" in
Ada?  With those mechanisms, appropriate cleanup processing is performed
as the scopes unwind.  But C++ lacks such a mechanism.

     One could argue that such mechanisms violate the "no hidden machinery"
concept of C.  But C++ already has destructors, functions which are invoked
by the language system, not explicitly by the user.  What do people think?

					John Nagle

ttwang@polyslo.CalPoly.EDU (Thomas Wang) (08/17/89)

nagle@well.UUCP (John Nagle) writes:

>    C++ destructors provide a means to force cleanup of an object as it
>disappears upon exit from a scope.  Unfortunately, the "longjmp" mechanism
>evades destructor processing.  "longjmp" is an old hack, only sort of part
>of C.  But it is useful.  Should C++ have a better escape mechanism,
>along the lines of "throw" and "return-from" in Common LISP or "raise" in
>Ada?  With those mechanisms, appropriate cleanup processing is performed
>as the scopes unwind.  But C++ lacks such a mechanism.

I would suggest something like the following:

extern int error_code, error_type;

real square_root(real val)
{
  real result;
  pre_condition : val > 0.0;
  post_condition: (result * result + .1 > val) && (result * result - .1 < val);

  char* tempbuf = (char*) malloc(80 * sizeof(char));
  if (tempbuf == 0)
  {
    error_code = 1;
    fail; // goto the recovery handler.  If no recovery handler exists,
          // pop stack & propogate failure upwards.
  }
  calculation_follows();
  return result;
recovery:
  switch (error_type)
  {
  case 0: // pre-condition failed
    if (val > 0.0)
    {
       val = - val;
       retry; // retry will pop all local variables, and goto top of function
    }
    break; // if we falls through, failure will propogate upward toward
           // main() function
  case 1: // post-condition failed
    cout << "incorrect result was generated.\n";
    break;
  case 2: // class-invarient failed
    break;
  case 3: // raised by the 'fail' statement
    if (error_code == 1)
      cout << "out of memory\n";
    break;
  case 4: // previous failure propogated to here
    break;
  }
}

>     One could argue that such mechanisms violate the "no hidden machinery"
>concept of C.  But C++ already has destructors, functions which are invoked
>by the language system, not explicitly by the user.  What do people think?

I think this error handling mechanism is fairly explicit, and replaces all
instances of ad-hoc error checkings.  The mechanism is borrowed from
Eiffel if you have not noticed already.

>					John Nagle

 -Thomas Wang ("This is a fantastic comedy that Ataru and his wife Lum, an
                invader from space, cause excitement involving their neighbors."
                  - from a badly translated Urusei Yatsura poster)

                                                     ttwang@polyslo.calpoly.edu

ted@nmsu.edu (Ted Dunning) (08/23/89)

In article <13635@polyslo.CalPoly.EDU> ttwang@polyslo.CalPoly.EDU (Thomas Wang) writes:


   nagle@well.UUCP (John Nagle) writes:

   >    C++ destructors provide a means to force cleanup of an object as it
   >disappears upon exit from a scope.  Unfortunately, the "longjmp" mechanism
   >evades destructor processing.  "longjmp" is an old hack, only sort of part
   >of C.  But it is useful.  Should C++ have a better escape mechanism,
   >along the lines of "throw" and "return-from" in Common LISP or "raise" in
   >Ada?  With those mechanisms, appropriate cleanup processing is performed
   >as the scopes unwind.  But C++ lacks such a mechanism.

   I would suggest something like the following:

   extern int error_code, error_type;
	... sample code ...

   I think this error handling mechanism is fairly explicit, and replaces all
   instances of ad-hoc error checkings.  The mechanism is borrowed from
   Eiffel if you have not noticed already.

i didn't actually notice.

i have implemented a unwind_protect/catch/throw mechanism for
unadorned c that would be easy to move to c++.  this mechanism is
based on the semantics of these constructs in common lisp and should
provide a robust non-local return mechanism if longjmp is not used.
it is probably desirable that an error/debug mechanism be implemented
alongside the catch/throw mechanism based on the experience in common
lisp.

if anyone is interested, i can either post/email the code (it is
really pretty small).

--
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.

ted@nmsu.edu (Ted Dunning) (08/24/89)

In article <TED.89Aug22135300@kythera.nmsu.edu> ted@nmsu.edu (Ted Dunning) writes:


   i have implemented a unwind_protect/catch/throw mechanism ...

   if anyone is interested, i can either post/email the code (it is
   really pretty small).



silly boy.  little did i realize the number of people who would
respond with requests for the code.  nor did i stop to think about the
4 hours it would take to check back through the code and minimally
saberize it.

but, here it is anyway.  send me bugs, or better yet fixes.  my
address is ted@nmsu.edu

#! /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 15:36:27 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#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#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.

ted@nmsu.edu (Ted Dunning) (08/24/89)

i recently have been flooded for requests for my unwind-protect
package, so i spent part of today cleaning it up and adding a README.
please send me bug fixes and comments at ted@nmsu.edu

here it is:

#! /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.