[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RAM: exiting from unwind protects]



    Date: Sun, 10 May 1987  14:54 EDT
    From: "Scott E. Fahlman" <Fahlman@C.CS.CMU.EDU>

    I find the following pretty persuasive...

I don't.  Actually it seems to be mostly irrelevant to the issue at
hand.  Here's some commentary on it.  Feel free to show this to RAM if
you wish.

    Date: Saturday, 2 May 1987  11:16-EDT
    From: Rob MacLachlan <RAM>

    I don't really agree with Moon about this thing.  I certainly knew about
    about the feature of being about to write something that you can't exit
    from using any common lisp facility, but I don't see this as a
    "serious environment bug".  Our system has always has this property.
    When someone first pointed out this property of Common Lisp way back
    when, I did in fact write the pathological example and it did in fact
    behave in the pathological fashion.  After a while I got bored of
    trying to throw out of the break loop, and I quit.

    If I had really been doing anything, I could always have skipped over
    the losing frame using debug-return.  Environment problems can have
    environment solutions.  

I don't know what debug-return is (presumably something in the Spice
Lisp environment).  Unless it's something that bypasses unwind-protect
and deliberately doesn't evaluate the cleanup forms, I don't think it
solves the problem.  However, I agree that environment problems can have
environment solutions, and I think the issue is to make sure that the
language doesn't forbid the environment from solving this by trying to
enforce a particular semantics for the pathological construct, instead
of having it be an error.

			    In any case, I could have saved work I was
    doing from the break loop.  This feature certainly isn't a big deal;
    there are lots of ways a malicious Lisp user can blow the system out
    of the water.  What happened the last time you did 
    (makunbound '*terminal-io*)?

The second paragraph on p.329 appears to say that it is invalid for a
portable program to do that.  What I am arguing for is a similar
restriction on portable programs doing the similar thing with
unwind-protect and throw.  So I think this example actually supports my
position.

    In contrast, it seems to me that all the "fixes" for this problem
    result in substantial increases in the complexity of the language
    defintion for no gain.  It seems that Moon has already introduced
    three new things into the language:
     1] The concept of "throwing out of an unwind protect cleanup".  When
	are you in an unwind protect?  What does it mean to throw out of
	it?  Does this apply to lexical exits too?  Does this signal an
	error?
	  (block block
	    (unwind-protect <foo>
	      (return-from block)))
	Does this?
	  (unwind-protect <foo>
	    (block block
	      (return-from block)))

Most of this would be answered by re-reading the relevant proposal to
the cleanup committee (are these archived someplace public?), which
was not written by me.  I agree that we need a better-written version
of that proposal that is easier to understand and less ambiguous.  The
one I am looking at is
  Message-ID: <870227172152.3.KMP@RIO-DE-JANEIRO.SCRC.Symbolics.COM>
  Issue:        UNWIND-PROTECT-CLEANUP-NON-LOCAL-EXIT
  Edit history: Revision 1 by KMP 02/27/87

My only contribution to the discussion was
in the message <870423020745.9.MOON@EUPHRATES.SCRC.Symbolics.COM>.
Here are some relevant excerpts from the referenced proposal:

  If a non-local return is done while in the cleanup form of an
  UNWIND-PROTECT, the behavior is not always well-defined.
  There are three basic cases:
  ...
  1. Transfer to a point inside the point to which control 
    would have transferred.

and what I proposed in answer to this was to do one of three
things in case 1, transfer to a point inside the point to
which control would have transferred.
  1. wimp out and say it "is an error"
  2. signal an error
  3. resume the original throw, just as if the cleanup handler had
     exited normally.
I prefer signalling an error, because I firmly believe that the program
is ill-formed.

Note that I have not introduced any new concepts here.  I don't think
the UNWIND-PROTECT-CLEANUP-NON-LOCAL-EXIT proposal introduced new
concepts either; it just made explicit reference to concepts that
were already in Common Lisp.  The argument that these concepts make
the language definition too complex seems to be an argument that the
language definition should not attempt to define the semantics of throwing
precisely.

     2] The concept of errors that aren't errors, which we need so that
	users can't screw themselves with this feature no matter how hard
	they try.

Last time I read the "error proposal" it included this concept.  I don't
think I invented it, I just borrowed it from there while writing up the
discussion of throw vs. unwind-protect.  Certainly in the absence of the
error proposal this concept is not introduced into the language, since the
language currently does not contain an IGNORE-ERRORS construct, nor any
other construct that is sensitive to the issue.

     3] The implicit requirement that an implementation have some exit
	mechanism other than throw so that it can unwind out of cleanup
	forms even if the user can't.  What does the system do when you
	are running in an unwind protect and the user types an interrupt?
	In fact, it seems that Moon is being inconsistent here, since he
	has already assumed that interrupts do throw.  If the user
	interrupts when running in a cleanup do you signal an error, and
	then signal an error whenever the user tries to abort out of the
	debugger?

All of point 3 appears to be a misunderstanding of what was being
discussed and proposed.  "Transfer to a point inside the point to which
control would have transferred" is irrelevant to "the user types an
interrupt" (which I take to mean something like Maclisp's control-X
and control-G, i.e. abort the program and return to a read-eval-print
loop) since those would be transfers to a point outside of, or equal to,
any throw currently in progress.

    If I had ever been screwed by this, I would think differently.  

The inside-Symbolics component of this discussion originated with a
customer being screwed by this.

								    I'm
    sure that most of the reason that this problem doesn't happen is that
    people usually don't write code that aborts from unwind protect
    cleanups.  There is a big difference between saying that something is
    rarely needed and possibly dangerous and saying that it must signal an
    error.

    I am convinced that the simplest evaluation model is to say that the
    unwind-protect cleanup is evaluated in the lexical and dynamic
    environment of the unwind-protect form.  

Nobody ever proposed anything different as far as I am aware.

					     Any alternative must somehow
    introduce an "in an unwind protect cleanup" marker into the dynamic
    environment of the cleanup forms.

The issue is actually what happens you nest throws (throughout this
discussion "throw" has been understood to include all non-local exits,
not only the THROW function).  Thus the marker in question is "in throw",
not "in an unwind protect cleanup".

Yes, the dynamic state of a program that is throwing would need to
include an indication of where it was throwing to if we were to adopt
the proposal that misnested throws signal an error or the proposal that
they resume the outer throw.  In the "is an error" case, there are no
requirements on the implementation, and the requirement is only that
portable programs cannot assume any particular behavior.  However, I
can't imagine an implementation of throw that does -not- remember in its
dynamic state where it's going to throw to when it finishes evaluating
some unwind-protect cleanup forms.

To get back to earth after all this lofty flaming, remember that the
specific case mentioned in the UNWIND-PROTECT-CLEANUP-NON-LOCAL-EXIT
proposal was

    (CATCH 'FOO
      (CATCH 'BAR
	(UNWIND-PROTECT (THROW 'FOO 3)
	  (THROW 'BAR 4)
	  (PRINT 'XXX))))

and the question is: what does the THROW to BAR do?  One possible answer
that many people seemed to favor is it throws to BAR and the throw to
FOO never happens.  The answer I prefer is that this is not a valid
Common Lisp program.  Does this make the issue clear?