zsh-workers
 help / color / mirror / code / Atom feed
* Re: Exception handling and "trap" vs. TRAPNAL()
       [not found]   ` <20051001153756.GA12183@DervishD>
@ 2005-10-01 18:38     ` Bart Schaefer
  2005-10-01 19:10       ` Peter Stephenson
  2005-10-01 20:28       ` DervishD
  0 siblings, 2 replies; 25+ messages in thread
From: Bart Schaefer @ 2005-10-01 18:38 UTC (permalink / raw)
  To: Peter Stephenson; +Cc: DervishD, zsh-workers

On Oct 1,  5:37pm, DervishD wrote:
} Subject: Re: Exception handling and "trap" vs. TRAPNAL()
}
}     Hi Peter :)
}  * Peter Stephenson <pws@csr.com> dixit:
} > Add some "print"s to the trap to see when it's triggering.
} 
}     The trap is triggering just in the "false" statement, and the
} TRAPZERR works perfectly, it's the "trap" kind which is failing.

I ran this with "set -x" (much better than inserting print statements)
and it's clear that the "trap" command version of the trap is not
tripping the "always" block:

+./script:34> false
+./script:34> throw DEFAULT
+throw:34> typeset -g 'EXCEPTION=DEFAULT'
+throw:34> readonly 'THROW=0'
+throw:34> ((  TRY_BLOCK_ERROR == 0  ))
throw:34: read-only variable: THROW
+throw:34> THROW='' 
+./script:35> throw EXCEPTION

The problem is this snippet in signals.c:

    } else if (errflag)
	trapret = 1;
    execrestore();
    lexrestore();

    if (trapret > 0) {
	if (isfunc) {
	    breaks = loops;
	    errflag = 1;
	} else {
	    lastval = trapret-1;
	}

We start that section with errflag == 1 and set trapret = 1, but then
at lexrestore(), errflag is reset to zero, and because isfunc is false,
it's not set back to 1 again.  After unwinding out of the trap handler,
execution of the try block finds errflag == 0, and proceeds rather than
skipping ahead to the always block.

So the question is:  Why ignore errors that occur in inline traps?

} Can I do anything to make it work (except patching zsh)?

I don't think so.

} Is there any other way of throwing exceptions automagically when a
} command returns a non-zero status

No.


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-01 18:38     ` Exception handling and "trap" vs. TRAPNAL() Bart Schaefer
@ 2005-10-01 19:10       ` Peter Stephenson
  2005-10-01 20:41         ` DervishD
  2005-10-01 22:44         ` Bart Schaefer
  2005-10-01 20:28       ` DervishD
  1 sibling, 2 replies; 25+ messages in thread
From: Peter Stephenson @ 2005-10-01 19:10 UTC (permalink / raw)
  To: zsh-workers

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 1908 bytes --]

Bart Schaefer wrote:
> So the question is:  Why ignore errors that occur in inline traps?

I'm glad you looked at this rather than me...

Correct me if I'm wrong, but I don't think that's quite the question,
which is in two parts: (1) why ignore errors in traps (the save/restore
behaviour seen regardless of the trap style) (2) why flag an error only
when returning a non-zero status from a function-style trap (the
explicit setting of "errflag" when returning with non-zero status from
such a trap).

The answer to (1) is that traps are present as error handlers (of some
sort and for some debatable value of "error") and to avoid confusing
matters the error is taken from the code surrounding the trap, not the
trap itself.  Traps are supposed to operate in a sort of semi-permeable
sandbox.  (Yes, I know that's meaningless.)

The answer to (2) is this, from the manual regarding function-style traps:

  The return status from the function is handled specially.  If it
  is  zero, the signal is assumed to have been handled, and execu‐
  tion continues normally.  Otherwise, the normal  effect  of  the
  signal  is  produced; if this causes execution to terminate, the
  status returned to the shell is the  status  returned  from  the
  function.

So the whole behaviour that DervishD is seeing is a side affect of the
fact that this:

  TRAPZERR() { throw DEFAULT; }

finishes by executing a command with non-zero status, the throw, causing
the special behaviour described above.

You can certainly argue that the "normal effect" of a ZERR trap is not
to cause an error, and it's certainly possible to argue that yet more
hacked-up special cases and additional bug-prone complexity in the trap
code are warranted.  However, I'm still not convinced this is going
anywhere I want to go.

-- 
Peter Stephenson <pws@pwstephenson.fsnet.co.uk>
Work: pws@csr.com
Web: http://www.pwstephenson.fsnet.co.uk


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-01 18:38     ` Exception handling and "trap" vs. TRAPNAL() Bart Schaefer
  2005-10-01 19:10       ` Peter Stephenson
@ 2005-10-01 20:28       ` DervishD
  2005-10-02  4:40         ` Bart Schaefer
  1 sibling, 1 reply; 25+ messages in thread
From: DervishD @ 2005-10-01 20:28 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: Peter Stephenson, zsh-workers

    Hi Bart :)

 * Bart Schaefer <schaefer@brasslantern.com> dixit:
> On Oct 1,  5:37pm, DervishD wrote:
> } Subject: Re: Exception handling and "trap" vs. TRAPNAL()
> }
> }     Hi Peter :)
> }  * Peter Stephenson <pws@csr.com> dixit:
> } > Add some "print"s to the trap to see when it's triggering.
> } 
> }     The trap is triggering just in the "false" statement, and the
> } TRAPZERR works perfectly, it's the "trap" kind which is failing.
> I ran this with "set -x" (much better than inserting print statements)

    I did exactly the same, and the "trap" didn't caused the always
block to run but the TRAPZERR bit did.

> } Can I do anything to make it work (except patching zsh)?
> I don't think so.

    Well, then I'll use explicit throw commands instead of ZERR. The
code will be a bit messier but I'll try to use TRAPZERR whenever
possible until the ¿bug? is fixed. I am still not sure whether this
is a bug or a feature:

    trap 'false' DEBUG

    true
    [[ $? -eq 1 ]] && print "What?"

    This snippet doesn't print anything, and won't do even if using
TRAPDEBUG and "return 1" instead of "false". So ignoring errors from
inline traps make those traps and the TRAPNAL ones semantically
identical (TRAPNAL is a function, but its return value seems to be
ignored). Apart from this, I cannot think of a reason :?

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-01 19:10       ` Peter Stephenson
@ 2005-10-01 20:41         ` DervishD
  2005-10-01 22:44         ` Bart Schaefer
  1 sibling, 0 replies; 25+ messages in thread
From: DervishD @ 2005-10-01 20:41 UTC (permalink / raw)
  To: Peter Stephenson; +Cc: zsh-workers

    Hi Peter :)

 * Peter Stephenson <pws@pwstephenson.fsnet.co.uk> dixit:
> The answer to (2) is this, from the manual regarding function-style traps:
> 
>   The return status from the function is handled specially.  If it
>   is  zero, the signal is assumed to have been handled, and execu‐
>   tion continues normally.  Otherwise, the normal  effect  of  the
>   signal  is  produced; if this causes execution to terminate, the
>   status returned to the shell is the  status  returned  from  the
>   function.
> 
> So the whole behaviour that DervishD is seeing is a side affect of the
> fact that this:
> 
>   TRAPZERR() { throw DEFAULT; }
> 
> finishes by executing a command with non-zero status, the throw, causing
> the special behaviour described above.

    But I didn't see that as a side effect, since the trap is not
returning. I assumed that the "throw" broke execution flow, and
jumped to the start of the "always" block, that is, no return value
from TRAPZERR was involved.

    I know, the code I was proposing is weird, and ZERR is intended
to *handle* errors, not to throwing exceptions, but if inline traps
won't cause an "always" block to be run, it should be stated in the
manual (IMHO). Probably ZERR is a bad example, but as soon as I
started to mess with exceptions and "always" blocks I thought about a
trap for SIGINT to do cleanup, using an exception and some "always"
blocks. Probably people is not using exception handling in shell
script, but if the feature exits, it should work seamlessly with
traps or at least document the problem.

    On the other hand, I don't see any problem in traps not causing
exceptions to be thrown, but the behaviour should be consistent and
both kinds of traps should behave the same. Otherwise you can write
code using function traps and throwing exceptions from the trap, and
as soon as you need the trap to be inline (for using $LINENO or any
other similar variable) your code will no longer work :(

    Anyway, thanks a lot for explaining the problem and the decisions
behind the behaviour. As I suspected and told to Bart, I wasn't sure
this was a bug.

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-01 19:10       ` Peter Stephenson
  2005-10-01 20:41         ` DervishD
@ 2005-10-01 22:44         ` Bart Schaefer
  2005-10-02  8:06           ` DervishD
  1 sibling, 1 reply; 25+ messages in thread
From: Bart Schaefer @ 2005-10-01 22:44 UTC (permalink / raw)
  To: Peter Stephenson, zsh-workers

On Oct 1,  8:10pm, Peter Stephenson wrote:
}
} Bart Schaefer wrote:
} > So the question is:  Why ignore errors that occur in inline traps?
} 
} The answer to (1) is that traps are present as error handlers (of some
} sort and for some debatable value of "error") and to avoid confusing
} matters the error is taken from the code surrounding the trap, not the
} trap itself.

Yes, but the "error" of a non-zero command status is not the same as a
shell-level error (syntax or what have you).

} So the whole behaviour that DervishD is seeing is a side affect of the
} fact that this:
} 
}   TRAPZERR() { throw DEFAULT; }
} 
} finishes by executing a command with non-zero status, the throw

It could be argued that, in the presence of an "always" block, the
function should not be considered to have "finished" at all.  The
shell-level error is supposed to have stopped execution; the implict
"return $?" never happened, the return status of the function is not
defined, and we're in no-man's land.

The trouble, of course, is that shell-level errors are not really
exceptions, an "always" block is not really an exception handler, and
nothing is actually "thrown" by "throw", so none of the assumptions
are valid that Raúl is making based on the names "throw" and "catch".
And the implementation simply can't stop execution and unwind to the
"always" block the way a real exception system would.
 
} You can certainly argue that the "normal effect" of a ZERR trap is not
} to cause an error, and it's certainly possible to argue that yet more
} hacked-up special cases and additional bug-prone complexity in the trap
} code are warranted.

Well, that's just it.  An even stronger argument than the no-man's land
proposition, is that errflag should *not* be propagated out of a ZERR
trap.  This would not accomplish what Raúl wants, because it would mean
that *neither* TRAPZERR nor "trap ... ZERR" would be capable of "raising
an exception" -- in effect we'd be removing a special-case, not adding
one.

    } else if (trapreturn >= 0 && !isfunc) {
	/*
	 * Context was an inline trap.  If an explicit return value
	 * was used, we need to set `lastval'.  Otherwise we use the
	 * value restored by execrestore.  In this case, all return
	 * values indicate an explicit return from the current function,
	 * so always handle specially.  trapreturn is always restored by
	 * execrestore.
	 */
	trapret = trapreturn + 1;
    } else if (errflag)
	trapret = 1;

I'm not sure, though, that there isn't some other kind of real signal
that depends on that snippet.

} However, I'm still not convinced this is going anywhere I want to go.

The behavior should be consistent, is all.


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-01 20:28       ` DervishD
@ 2005-10-02  4:40         ` Bart Schaefer
  2005-10-02  8:13           ` DervishD
  2005-10-02 19:09           ` Peter Stephenson
  0 siblings, 2 replies; 25+ messages in thread
From: Bart Schaefer @ 2005-10-02  4:40 UTC (permalink / raw)
  To: DervishD; +Cc: Peter Stephenson, zsh-workers

On Oct 1, 10:28pm, DervishD wrote:
}
} I am still not sure whether this is a bug or a feature:
} 
}     trap 'false' DEBUG
} 
}     true
}     [[ $? -eq 1 ]] && print "What?"
} 
}     This snippet doesn't print anything, and won't do even if using
} TRAPDEBUG and "return 1" instead of "false".

Guess what.

The bit of documentation that PWS quoted earlier is significantly at
odds with the actual implementation.

Here's that paragraph again:

  The return status from the function is handled specially.  If it is
  zero, the signal is assumed to have been handled, and execution
  continues normally.

So far we're right on the mark.

  Otherwise, the normal effect of the signal is produced;

This is clearly inaccurate.  If it were accurate, a non-zero return
from TRAPHUP, TRAPABRT, or TRAPALRM (among others) ought to cause the
shell to exit.  This demonstrably does not happen.  What *does* happen
is that all levels of nested loop are broken (as if by the "break N"
command with a sufficiently large N) and the shell is forced to behave
as if a shell-level error occurred.

(I'll note in passing that it actually has to be a GREATER THAN zero
return value, not just non-zero; "return -1" doesn't cause any of the
effects discussed here.  But this bit of doc is already so far wrong
that signedness makes little difference.)

However, this effect is only achieved in two cases: (1) An explicit
"return" command is used; it's a side-effect of the "return" builtin
that the value of $? is recorded as the return value of the trap.
(2) The TRAPNAL function itself exits with a shell-level error, such
as a syntax error or an assignment to a read-only variable.  Merely
falling off the end of the function with $? > 0 is not sufficient.

It's my contention that case (2) above is an oddball.  It ought to
behave the same way for "TRAPNAL() { ... }" and "trap ... NAL".  That
it does not is because the same local variable is overloaded to mean
both "$? > 0 was true" and "error condition was true", not because of
some strange special-case.

  if this causes execution to terminate, the status returned to the
  shell is the status returned from the function.

This is also demonstrably wrong.  Both cases above cause the return
status to be set via the "behave as if a shell-level error occurred"
reaction, so usually $? == 1.  The side-effect of "return" on $? lasts
only long enough to set up the forced error, then is erased as part
of unwinding the trap context.

In fact, ONLY in the case of "trap 'return X' NAL" is $? = X caused
to happen, and that's because the trap executes in the context of the
caller and forces the caller itself to return.  This behavior of the
return builtin in an "inline trap" is not documented anywhere as far
as I can tell, but you can watch it in action (and badly cripple
your shell) by writing --

    trap return DEBUG

-- and then attempting to execute any function that has more than one
command in the body.

A much more accurate description of the behavior of TRAPNAL can be
found under the "return" command entry:

  If return was executed from a trap in a TRAPNAL function, the
  effect is different for zero and non-zero return status.  With zero
  status (or after an implicit return at the end of the trap), the
  shell will return to whatever it was previously processing; with a
  non-zero status, the shell will behave as interrupted except that
  the return status of the trap is retained.

Aha!  "Behave as if interrupted" NOT "the normal effect of the signal."
The doc under "TRAPNAL" needs to be repaired.  (Here's another case of
zsh code overloading a variable; "interrupt" and "shell-level error"
are indistinguishable after a certain point.)


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-01 22:44         ` Bart Schaefer
@ 2005-10-02  8:06           ` DervishD
  0 siblings, 0 replies; 25+ messages in thread
From: DervishD @ 2005-10-02  8:06 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: Peter Stephenson, zsh-workers

    Hi Bart :)

 * Bart Schaefer <schaefer@brasslantern.com> dixit:
> } You can certainly argue that the "normal effect" of a ZERR trap is not
> } to cause an error, and it's certainly possible to argue that yet more
> } hacked-up special cases and additional bug-prone complexity in the trap
> } code are warranted.
> Well, that's just it.  An even stronger argument than the no-man's
> land proposition, is that errflag should *not* be propagated out of
> a ZERR trap.  This would not accomplish what Raúl wants, because it
> would mean that *neither* TRAPZERR nor "trap ... ZERR" would be
> capable of "raising an exception" -- in effect we'd be removing a
> special-case, not adding one.

    But as I told in one of my last messages, that's ok too. My
problem is not really about not being able to throw an exception from
a trap, but having one kind of trap that works and another that
doesn't with no special reason. I will be equally happy if this is
shown in the documentation or the special-case removed.

    This said, being able to throw an exception from a trap is IMHO
desirable. In the end, both traps and exceptions are tools for doing
cleanup or recovering from errors, so common usage is not a bad idea.

    Of course, even in the case of not being able to throw exceptions
from traps, is more or less easy (assuming that shell scripts are
short snippets of code in the common case) to implement a way of
throwing exceptions if some trap is invoked, by setting a variable
and doing a busy loop or something like that, so... Not the best
solution but it's a suitable one.

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-02  4:40         ` Bart Schaefer
@ 2005-10-02  8:13           ` DervishD
  2005-10-02 19:09           ` Peter Stephenson
  1 sibling, 0 replies; 25+ messages in thread
From: DervishD @ 2005-10-02  8:13 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: Peter Stephenson, zsh-workers

    Hi Bart :)

 * Bart Schaefer <schaefer@brasslantern.com> dixit:
>   Otherwise, the normal effect of the signal is produced;
> 
> This is clearly inaccurate.  If it were accurate, a non-zero return
> from TRAPHUP, TRAPABRT, or TRAPALRM (among others) ought to cause the
> shell to exit.  This demonstrably does not happen.  What *does* happen
> is that all levels of nested loop are broken (as if by the "break N"
> command with a sufficiently large N) and the shell is forced to behave
> as if a shell-level error occurred.

    I haven't tested that. But I see your point.

    I'm starting to think the same: it's a documentation problem, not
a code problem (although eliminating the special case for TRAPNAL
doesn't sound bad...).

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-02  4:40         ` Bart Schaefer
  2005-10-02  8:13           ` DervishD
@ 2005-10-02 19:09           ` Peter Stephenson
  2005-10-02 19:55             ` Bart Schaefer
  1 sibling, 1 reply; 25+ messages in thread
From: Peter Stephenson @ 2005-10-02 19:09 UTC (permalink / raw)
  To: zsh-workers

Bart Schaefer wrote:
> The doc under "TRAPNAL" needs to be repaired.

OK, I've just copied the other chunk.

I'm not sure there's any code change I would feel comfortable with.  It
took quite a lot of effort in numerous different discussions working out
how traps should behave and I don't see anything that's important enough
to risk breaking something else, nor anything that's obviously
inconsistent provided the manual is written correctly... except for the
odd case of the error in the TRAP function, which I'm currently too
confused even to think about straight.

-- 
Peter Stephenson <pws@pwstephenson.fsnet.co.uk>
Work: pws@csr.com
Web: http://www.pwstephenson.fsnet.co.uk


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-02 19:09           ` Peter Stephenson
@ 2005-10-02 19:55             ` Bart Schaefer
  2005-10-02 23:00               ` DervishD
  0 siblings, 1 reply; 25+ messages in thread
From: Bart Schaefer @ 2005-10-02 19:55 UTC (permalink / raw)
  To: zsh-workers

On Oct 2,  8:09pm, Peter Stephenson wrote:
}
} I'm not sure there's any code change I would feel comfortable with.
} [...] I don't see anything that's [...] obviously inconsistent
} provided the manual is written correctly... except for the
} odd case of the error in the TRAP function, which I'm currently too
} confused even to think about straight.

It's really pretty simple.  The whole problem is the "else if (errflag)"
branch in dotrapargs().  It's a no-op for inline traps because of later
tests of (isfunc).

If we want to suppress errors/interrupts from trap functions, so they
behave the same as inline traps, we simply delete that branch:

-------------------------------------------------------------------
Index: Src/signals.c
===================================================================
RCS file: /extra/cvsroot/zsh/zsh-4.0/Src/signals.c,v
retrieving revision 1.13
diff -c -r1.13 signals.c
--- Src/signals.c	28 May 2005 04:32:49 -0000	1.13
+++ Src/signals.c	2 Oct 2005 19:31:00 -0000
@@ -1097,8 +1097,7 @@
 	 * execrestore.
 	 */
 	trapret = trapreturn + 1;
-    } else if (errflag)
-	trapret = 1;
+    }
     execrestore();
     lexrestore();
 
-------------------------------------------------------------------

Note that an effect of suppressing errors in this way is that it
becomes difficult to send a keyboard interrupt to the shell, because
interrupts that arrive during execution of a trap will be ignored.

If we want to propagate errors that occur inside traps, and thereby
make it possible for inline traps to "throw exceptions" in "always"
blocks, we do this instead (more discussion below patch):

-------------------------------------------------------------------
Index: Src/signals.c
===================================================================
diff -c -r1.13 signals.c
--- Src/signals.c	28 May 2005 04:32:49 -0000	1.13
+++ Src/signals.c	2 Oct 2005 19:22:57 -0000
@@ -1003,6 +1003,7 @@
     int trapret = 0;
     int obreaks = breaks;
     int isfunc;
+    int traperr;
 
     /* if signal is being ignored or the trap function		      *
      * is NULL, then return					      *
@@ -1097,8 +1098,8 @@
 	 * execrestore.
 	 */
 	trapret = trapreturn + 1;
-    } else if (errflag)
-	trapret = 1;
+    }
+    traperr = errflag;
     execrestore();
     lexrestore();
 
@@ -1110,6 +1111,7 @@
 	    lastval = trapret-1;
 	}
     } else {
+	errflag |= traperr;
 	breaks += obreaks;
 	if (breaks > loops)
 	    breaks = loops;
-------------------------------------------------------------------

The |= there is to retain any true value of errflag from the context
restored by lexrestore().  That may be impossible in the first place,
but I'm not certain.

Note that the "if" for the "else" in that third hunk is:

    if (trapret > 0) {

With the change in the second hunk, (trapret > 0) is true if and only
if an explicit "return" statement was executed within the trap (either
inline or function).  When (traperr) is true an error/interrupt must
have occurred in the trap, and it therefore did not call "return",
and therefore (trapret > 0) must be false.


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-02 19:55             ` Bart Schaefer
@ 2005-10-02 23:00               ` DervishD
  2005-10-03  1:37                 ` Bart Schaefer
  0 siblings, 1 reply; 25+ messages in thread
From: DervishD @ 2005-10-02 23:00 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: zsh-workers

    Hi Bart :)

 * Bart Schaefer <schaefer@brasslantern.com> dixit:
> If we want to propagate errors that occur inside traps, and thereby
> make it possible for inline traps to "throw exceptions" in "always"
> blocks, we do this instead (more discussion below patch):
[...]

    I insist: while ZERR may not be the place for throwing exceptions
(I think it's a perfect place, but that's another matter), signals
like SIGTERM, SIGINT, etc. or even SIGALRM, are very good candidates
to do exception handling. The best point about EH is that it allows
to do better error handling and cleanup, and traps are mostly used
exactly for that, so using the two things together is not as weird as
it may look.

    Propagating "errflag" may break current code only if that code is
using an inline trap which "returns" a value and that error value is
ignored on purpose. Weird behaviour, to return a value you are going
to ignore as if it never existed. Moreover, simulating a "return"
from an inline trap is something difficult to do accidentally. It
would require messing with "$?" directly or something like that.

    Of course, there are other code that may break: inline traps
which generate syntax errors (due to "eval" or the like, so the error
cannot be picked up always because it is produced only when the
"eval" parameters are ill formed or something like that) that
previously didn't cause any problem except maybe print an error
message and now will jump into the "always" block. But I don't think
there is much code out there using "always" blocks that can break.

    I cannot think about any other code that may break due to the
last change you're proposing, but anyway your first patch seems
reasonable too: if inline traps cannot throw exceptions, do not let
function traps do it and document the problem (not about exceptions,
but about return values and syntax errors).

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-02 23:00               ` DervishD
@ 2005-10-03  1:37                 ` Bart Schaefer
  2005-10-03  8:57                   ` Peter Stephenson
  2005-10-03  9:01                   ` DervishD
  0 siblings, 2 replies; 25+ messages in thread
From: Bart Schaefer @ 2005-10-03  1:37 UTC (permalink / raw)
  To: DervishD; +Cc: zsh-workers

Having written most of what follows, I just discovered something that
may make a lot of it moot:

In zsh prior to 4.1.something, an error condition in an inline trap
*WAS* passed through to the calling context.  That it now is not, is
apparently either a broken behavior or an undocumented change, and in
any case no code written for older zsh could have relied upon errors
in traps being inconsequential.

Compare zsh-4.0.6:

schaefer[505] trap 'readonly BAR; BAR= ; print ok' ZERR
schaefer[506] foo() { false; print more }
schaefer[507] foo
foo: read-only variable: BAR
schaefer[508] print $?
1

Now here's zsh-4.2.5:

schaefer[501] trap 'readonly BAR; BAR= ; print ok' ZERR
schaefer[502] foo() { false; print more }
schaefer[503] foo
foo: read-only variable: BAR
more
schaefer[504] print $?
0

This gets even stranger if you compare to bash2.  With an ERR trap,
bash2 behaves like zsh-4.2.5 (except that the ERR trap is reset on
function entry, so you have to put the trap command inside the body).
So maybe this is a standards-compliance thing?

[ASIDE:  The bash2 behavior implies that traps set globally outside
of a function are not supposed to apply within the function context.
E.g., in bash2 a global INT trap does not prevent the function from
being interrupted by a SIGINT; instead it handles the signal in the
context where the trap command was run.  Similarly an ERR trap stops
being tripped during the body of the function, but remains in effect
so that it is tripped if/when the function itself returns nonzero.
This is, almost, the inverse of zsh's "setopt localtraps" behavior.]

Nevertheless ...

On Oct 3,  1:00am, DervishD wrote:
}
}     I insist: while ZERR may not be the place for throwing exceptions
} (I think it's a perfect place, but that's another matter), signals
} like SIGTERM, SIGINT, etc. or even SIGALRM, are very good candidates
} to do exception handling.

Raúl ... the important point that you're missing is that zsh doesn't
really have or handle exceptions.  Peter's throw/catch functions are
simulations of exception behavior using a mechanism that is far less
powerful than true exception handling.

The zsh "always" syntax could -- and perhaps even should -- have been
implemented equally well as a new syntax for the "eval" builtin, much
like Perl's "eval" can be followed by a curly-bracketed block instead
of a string.  Zsh's TRY_BLOCK_ERROR is a crude approximation of Perl's
$@, in that $TRY_BLOCK_ERROR captures only the presence of an error
and not the error message.  (In fact, a very useful extension would
be to add an EVAL_ERROR variable that is 1 if an "eval" stopped with
an error, and 0 if "eval" finished normally, so that an error could be
distinguished from "eval false".)  The important distinction, though,
is that zsh lacks even an approximation of Perl's "die" builtin.

}   Propagating "errflag" may break current code only if that code is
} using an inline trap which "returns" a value and that error value is
} ignored on purpose.

That's not quite correct.  Remember, an inline trap using the "return"
builtin actually causes the surrounding/calling context to return, so
it's imposible to provide a value to the calling context that way;
"return" in an inline trap is roughly analogous to using "break 2" in
a nested loop.  When "return" is not used, the value of $? from the
calling context is always restored when the trap finishes, so it never
makes sense for the calling context to expect to get a value from an
inline trap.

}     Of course, there are other code that may break: inline traps
} which generate syntax errors [...] that
} previously didn't cause any problem except maybe print an error
} message and now will jump into the "always" block. But I don't think
} there is much code out there using "always" blocks that can break.

The trouble is not with code that is using an "always" block, it's with
code that is NOT using an "always" block.  Which would be the majority
of existing code, because "always" is a very new feature.

So propagating errflag may break current code if that code is using an
inline trap, that code does not have an "always" block, and that code
expects to keep going no matter what happens in the trap.

}     I cannot think about any other code that may break due to the
} last change you're proposing, but anyway your first patch seems
} reasonable too: if inline traps cannot throw exceptions, do not let
} function traps do it and document the problem (not about exceptions,
} but about return values and syntax errors).

Here's another way to think about it:

Presently (that is, without either of my patches), a TRAPNAL function
handles error conditions like a function call, whereas an inline trap
handles error conditions like an "eval" statement.  That's defensible,
in a way, because a "trap" command *looks* like an "eval" statement;
it contains a string that is evaluated as commands.  It could even be
explained that way in the docs, so that you would have understood why
your sample script didn't work.

Given all of this plus the bash2 behavior, I'm inclined to add a few
more words to the documentation and apply *neither* of the patches
from workers/21804.  Further, *IF* we were going to choose one of
those patches to apply, I'd say it should be the first one, to make
TRAPNAL ignore errors too.

Here's how to use throw/catch from a TRAPNAL function in the event
that 21804 part 1 is applied:

    TRAPZERR() { eval 'throw DEFAULT'; return 1 }

That is, call throw in the eval to set the variables, and then return
a positive $? to interrupt into the always block.


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-03  1:37                 ` Bart Schaefer
@ 2005-10-03  8:57                   ` Peter Stephenson
  2005-10-03 14:51                     ` Bart Schaefer
  2005-10-03  9:01                   ` DervishD
  1 sibling, 1 reply; 25+ messages in thread
From: Peter Stephenson @ 2005-10-03  8:57 UTC (permalink / raw)
  To: zsh-workers

Bart Schaefer <schaefer@brasslantern.com> wrote:
> In zsh prior to 4.1.something, an error condition in an inline trap
> *WAS* passed through to the calling context.  That it now is not, is
> apparently either a broken behavior or an undocumented change, and in
> any case no code written for older zsh could have relied upon errors
> in traps being inconsequential.

I think this is 19575: reading the "-" path in the diff the old behaviour
of setting trapret if errflag is set was there, and a subsequent "errflag =
1" depended solely on that, not being in a function.  Given the description
it doesn't look like the change was intentional, even though arguably it
should be the way it now is.

> [ASIDE:  The bash2 behavior implies that traps set globally outside
> of a function are not supposed to apply within the function context.
> E.g., in bash2 a global INT trap does not prevent the function from
> being interrupted by a SIGINT; instead it handles the signal in the
> context where the trap command was run.  Similarly an ERR trap stops
> being tripped during the body of the function, but remains in effect
> so that it is tripped if/when the function itself returns nonzero.
> This is, almost, the inverse of zsh's "setopt localtraps" behavior.]

This is presumably implementable without too much horror, since we
can already save and restore traps.  It would probably now need to be an
option, though.

> Raúl ... the important point that you're missing is that zsh doesn't
> really have or handle exceptions.  Peter's throw/catch functions are
> simulations of exception behavior using a mechanism that is far less
> powerful than true exception handling.

Yes, it's all just based on testing a single variable and relying on the
fact that zsh has to unwind the state during an error anyway.  Maybe the
zshcontrib entry needs more cautionary wording.

> The zsh "always" syntax could -- and perhaps even should -- have been
> implemented equally well as a new syntax for the "eval" builtin, much
> like Perl's "eval" can be followed by a curly-bracketed block instead
> of a string.

That's not particularly natural in zsh.

  eval { ... }

would mean that "eval" could no longer be a true builtin but would have to
be treated as a reserved word early in parsing.  We can second-guess the
later stages in processing (is eval disabled?), so probably it can be done
without major grief, but the zsh syntax isn't really comparable to Perl.

> (In fact, a very useful extension would
> be to add an EVAL_ERROR variable that is 1 if an "eval" stopped with
> an error, and 0 if "eval" finished normally, so that an error could be
> distinguished from "eval false".)

This, presumably, is simple.

> Presently (that is, without either of my patches), a TRAPNAL function
> handles error conditions like a function call, whereas an inline trap
> handles error conditions like an "eval" statement.  That's defensible,
> in a way, because a "trap" command *looks* like an "eval" statement;
> it contains a string that is evaluated as commands.  It could even be
> explained that way in the docs, so that you would have understood why
> your sample script didn't work.

Yes, this all sounds rational.

> Given all of this plus the bash2 behavior, I'm inclined to add a few
> more words to the documentation and apply *neither* of the patches
> from workers/21804.  Further, *IF* we were going to choose one of
> those patches to apply, I'd say it should be the first one, to make
> TRAPNAL ignore errors too.

It looks like the reason for the error to be propagated is receding into
history and I'd be perfectly happy with that patch.

-- 
Peter Stephenson <pws@csr.com>                  Software Engineer
CSR PLC, Churchill House, Cambridge Business Park, Cowley Road
Cambridge, CB4 0WZ, UK                          Tel: +44 (0)1223 692070


This message has been scanned for viruses by BlackSpider MailControl - www.blackspider.com


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-03  1:37                 ` Bart Schaefer
  2005-10-03  8:57                   ` Peter Stephenson
@ 2005-10-03  9:01                   ` DervishD
  2005-10-03 16:21                     ` Bart Schaefer
  1 sibling, 1 reply; 25+ messages in thread
From: DervishD @ 2005-10-03  9:01 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: zsh-workers

    HI Bart :)

 * Bart Schaefer <schaefer@brasslantern.com> dixit:
> In zsh prior to 4.1.something, an error condition in an inline trap
> *WAS* passed through to the calling context.  That it now is not, is
> apparently either a broken behavior or an undocumented change, and in
> any case no code written for older zsh could have relied upon errors
> in traps being inconsequential.

    :((((

> This gets even stranger if you compare to bash2.  With an ERR trap,
> bash2 behaves like zsh-4.2.5 (except that the ERR trap is reset on
> function entry, so you have to put the trap command inside the
> body). So maybe this is a standards-compliance thing?

    "Traps shall remain in place for a given shell until explictly
changed with another trap command", says SUSv3. But it also says that
"Shell Functions" form another shell execution environment...

    I haven't found anything in SUSv3 saying that traps must be reset
when entering a shell function, but it says they are reset when
spawning a subshell (which is very logical anyway).

    This looks more like a bashism.

> On Oct 3,  1:00am, DervishD wrote:
> }     I insist: while ZERR may not be the place for throwing exceptions
> } (I think it's a perfect place, but that's another matter), signals
> } like SIGTERM, SIGINT, etc. or even SIGALRM, are very good candidates
> } to do exception handling.
> Raúl ... the important point that you're missing is that zsh doesn't
> really have or handle exceptions.  Peter's throw/catch functions are
> simulations of exception behavior using a mechanism that is far less
> powerful than true exception handling.

    I know, I know, but AFAIK the "always" block works more or less
the same as exception handling does in other languages. It's far less
powerful, that's ok, but it's powerful enough to do what I was
proposing and using traps together with always blocks.
 
> }   Propagating "errflag" may break current code only if that code is
> } using an inline trap which "returns" a value and that error value is
> } ignored on purpose.
> That's not quite correct.  Remember, an inline trap using the "return"
> builtin actually causes the surrounding/calling context to return, so
> it's imposible to provide a value to the calling context that way;

    That's why I was quoting "return": I didn't mean a literal
"return" command, but a way of returning a value to the current code.
If $? is restored, then there is not easy way of returning the error,
but that's good because then propagatin "errflag" shouldn't break
current code, am I wrong?

> }     Of course, there are other code that may break: inline traps
> } which generate syntax errors [...] that
> } previously didn't cause any problem except maybe print an error
> } message and now will jump into the "always" block. But I don't think
> } there is much code out there using "always" blocks that can break.
> The trouble is not with code that is using an "always" block, it's with
> code that is NOT using an "always" block.  Which would be the majority
> of existing code, because "always" is a very new feature.
> 
> So propagating errflag may break current code if that code is using an
> inline trap, that code does not have an "always" block, and that code
> expects to keep going no matter what happens in the trap.

    But if you propagate the error, you won't break such code. I
mean, even with your patch, this code...

    trap 'readonly VAR; VAR=0' ZERR

    print "HERE"
    false
    print "HERE AGAIN"

    ...will print both strings. I must admit I may be misinterpreting
your patch, and assuming that errflag doesn't do what I think it does
O:)) As far as I understan, "errflag" just signals the error and
propagate it to the current execution environment (for inline traps,
I mean), but doesn't make the shell abort :?

> Presently (that is, without either of my patches), a TRAPNAL
> function handles error conditions like a function call, whereas an
> inline trap handles error conditions like an "eval" statement. 
> That's defensible, in a way, because a "trap" command *looks* like
> an "eval" statement; it contains a string that is evaluated as
> commands.  It could even be explained that way in the docs, so that
> you would have understood why your sample script didn't work.

    It sounds quite sensible, yes. In fact SUS says that inline traps
(the only ones supported by SUS) do an implicit "eval" on the string
you pass to it.

    Moreover this is, obviously, the less intrusive change and won't
break existing code ;)

> Here's how to use throw/catch from a TRAPNAL function in the event
> that 21804 part 1 is applied:
> 
>     TRAPZERR() { eval 'throw DEFAULT'; return 1 }
> 
> That is, call throw in the eval to set the variables, and then return
> a positive $? to interrupt into the always block.

    But this is useless to me, since I already have that behaviour
without patching. My problem are inline traps. I need to use some
variables that the shell sets automatically, like LINENO, so I must
go for an inline trap and it won't make the try block to stop and
jump into the always block. This is not a big deal if using ZERR,
because you can get rid of the trap and substitute each "command" by
"command && throw whatever", but it's a problem with, for example,
SIGQUIT. I would like to handle SIGQUIT specially on always blocks,
but I need "throw" to work inside all kind of traps for that.

    You're proposing a quite intelligent way of have consequent
behaviour (that is, no trap can throw exceptions) for TRAPNAL()
traps, but is there any similar solution for inline traps?

    After reading your message I'm starting to think that zsh should
stick to current behaviour but reflect that behaviour in the docs.
Any patch will need redocumenting, too, and can break existing code,
just to fix a problem that can be seen as proper behaviour.

    If no patch is applied I can still use TRAPNAL's for throwing
exceptions, but I must think a way of doing that with inline traps,
which I think it's impossible because the value of TRY_BLOCK_ERROR is
reset. I've tried a partial solution, adding a variable to "throw"
and "catch" which is set up independently of the value of
TRY_BLOCK_ERROR, but the problem is jumping out the try block into
the always block :((( With the variable, the exception is caught as
soon as the always block is run, but execution of the try block
doesn't stop...

    Thanks a lot, Bart, for your explanation and your valuable
insights. The problem is much more complex than I thought.

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-03  8:57                   ` Peter Stephenson
@ 2005-10-03 14:51                     ` Bart Schaefer
  2005-10-03 15:10                       ` Peter Stephenson
  0 siblings, 1 reply; 25+ messages in thread
From: Bart Schaefer @ 2005-10-03 14:51 UTC (permalink / raw)
  To: zsh-workers

On Oct 3,  9:57am, Peter Stephenson wrote:
} Subject: Re: Exception handling and "trap" vs. TRAPNAL()
}
} Bart Schaefer <schaefer@brasslantern.com> wrote:
} > In zsh prior to 4.1.something, an error condition in an inline trap
} > *WAS* passed through to the calling context.
} 
} it doesn't look like the change was intentional, even though arguably it
} should be the way it now is.

It could also be argued that, if the trap should behave like an "eval",
it ought to set $? = 1 when an error occurs inside the trap (but still
not cause an interrupt condition).  The example of bash2 contradicts
that position.  Can anyone who is reading this try ksh?
 
} > E.g., in bash2 a global INT trap does not prevent the function from
} > being interrupted by a SIGINT; instead it handles the signal in the
} > context where the trap command was run.  Similarly an ERR trap stops
} > being tripped during the body of the function, but remains in effect
} 
} This is presumably implementable without too much horror, since we
} can already save and restore traps.

It's worse than just saving and restoring traps.  It requires treating a
function call like a subshell, in that both the function and the caller
get the SIGINT, the function is interrupted, and the caller's trap is
trigged.

} It would probably now need to be an option, though.

Yes.

} > The zsh "always" syntax could -- and perhaps even should -- have been
} > implemented equally well as a new syntax for the "eval" builtin, much
} > like Perl's "eval" can be followed by a curly-bracketed block instead
} > of a string.
} 
} That's not particularly natural in zsh.

I didn't really mean that the syntax would be exactly like Perl's, just
that the behavior would be.  If you wanted to implement something as
close as possible to "always" in an older version of zsh, you'd do
something like:

    eval "print this is the try block"
    TRY_ERR=$?
    print "this is the always block"
    if (( TRY_ERR == 0 ))
    then
      print "this is what comes after the always block"
    else
      # This is a stupid way to reset $? without exiting from the
      # parent shell script when we aren't in a function body.
      ( return TRY_ERR )
    fi

} > Given all of this plus the bash2 behavior, I'm inclined to add a few
} > more words to the documentation and apply *neither* of the patches
} > from workers/21804.  Further, *IF* we were going to choose one of
} > those patches to apply, I'd say it should be the first one, to make
} > TRAPNAL ignore errors too.
} 
} It looks like the reason for the error to be propagated is receding into
} history and I'd be perfectly happy with that patch.

As I said, I'm actually leaning towards no patch at all.  However, the
reason for propagating the error isn't actually receding, because Raúl
says he needs that behavior, and given "always" it really is useful.

Here's a possible compromise:  Use my second patch, but propagate the
error if and only if we're in the try-block of an always-construct.
That's guaranteed not to break old code, and continues to behave like
bash2 in the absence of "always".  Can dotrapargs() determine that it
is in "always" context without too much trouble?


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-03 14:51                     ` Bart Schaefer
@ 2005-10-03 15:10                       ` Peter Stephenson
  2005-10-03 16:50                         ` Bart Schaefer
  0 siblings, 1 reply; 25+ messages in thread
From: Peter Stephenson @ 2005-10-03 15:10 UTC (permalink / raw)
  To: zsh-workers

Bart Schaefer wrote:
> It could also be argued that, if the trap should behave like an "eval",
> it ought to set $? = 1 when an error occurs inside the trap (but still
> not cause an interrupt condition).  The example of bash2 contradicts
> that position.  Can anyone who is reading this try ksh?

ksh appears to set the status to 1.

> Here's a possible compromise:  Use my second patch, but propagate the
> error if and only if we're in the try-block of an always-construct.
> That's guaranteed not to break old code, and continues to behave like
> bash2 in the absence of "always".  Can dotrapargs() determine that it
> is in "always" context without too much trouble?

It can test try_errflag, which is usually -1 when not in an always
block and >= 0 otherwise.  However that's the value of TRY_BLOCK_ERROR
which can be set by the user, so isn't actually guaranteed to be -1
outside.  Either adding another flag not tied to a variable or rejecting
user attempts to set TRY_BLOCK_ERROR when it's current value is less than
zero (and therefore presumably only let it be set to such a number)
would work around this.

But do you mean how can it test if it's in the try block part?  There's
no explicit test for that at the moment, but it's easy to add a variable.

pws


This message has been scanned for viruses by BlackSpider MailControl - www.blackspider.com


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-03  9:01                   ` DervishD
@ 2005-10-03 16:21                     ` Bart Schaefer
  2005-10-03 17:59                       ` DervishD
  0 siblings, 1 reply; 25+ messages in thread
From: Bart Schaefer @ 2005-10-03 16:21 UTC (permalink / raw)
  To: DervishD; +Cc: zsh-workers

On Oct 3, 11:01am, DervishD wrote:
}
}     I know, I know, but AFAIK the "always" block works more or less
} the same as exception handling does in other languages.

Less, not more.  The problem is that in a language with real exceptions,
the difference between an error and an exception is well-defined, and it
is usually possible to have errors without exceptions and vice-versa.
Zsh's "always" block is an error handler, not an exception handler, and
PWS's functions are overloading the meaning of "error".

} > }   Propagating "errflag" may break current code only if that code is
} > } using an inline trap which "returns" a value and that error value is
} > } ignored on purpose.
} > That's not quite correct.  Remember, an inline trap using the "return"
} > builtin actually causes the surrounding/calling context to return
} 
}     That's why I was quoting "return": I didn't mean a literal
} "return" command, but a way of returning a value to the current code.

Yes, but there *isn't* any way of returning a value to the surrounding
code -- unless you mean something like setting $REPLY.  So your basis
for the statement is a practical impossibility.

} If $? is restored, then there is not easy way of returning the error,
} but that's good because then propagating "errflag" shouldn't break
} current code, am I wrong?

Yes, unfortunately, you're wrong.

}     But if you propagate the error, you won't break such code. I
} mean, even with your patch, this code...
} 
}     trap 'readonly VAR; VAR=0' ZERR
} 
}     print "HERE"
}     false
}     print "HERE AGAIN"
} 
}     ...will print both strings.
   
In 4.0.6, or with my second potential patch, that prints only "HERE"
(plus the error message about the readonly assignment).  In 4.2.5, it
prints both strings.  In bash2, it also prints both strings.  Anything
that relies on the 4.2.5 or bash2 behavior will break if we go back to
propagating the error as was done in 4.0.6.

} O:)) As far as I understan, "errflag" just signals the error and
} propagate it to the current execution environment (for inline traps,
} I mean), but doesn't make the shell abort :?

It doesn't make the shell *exit*, but it does stop execution in the
way that would jump into an "always" block.  (If it did not, how
would it solve your problem?)  In the absence of the "always", it
simply bails out of the current context.  The "always" block is a way
to intercept the "bail out" action, but the bail-out happens whether
"always" is there or not.
 
}     If no patch is applied I can still use TRAPNAL's for throwing
} exceptions, but I must think a way of doing that with inline traps,
} which I think it's impossible because the value of TRY_BLOCK_ERROR is
} reset.

You're right, it's impossible.  You'll have to think about how you
would write the code if you never had "always" in the first place,
and write it that way.


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-03 15:10                       ` Peter Stephenson
@ 2005-10-03 16:50                         ` Bart Schaefer
  0 siblings, 0 replies; 25+ messages in thread
From: Bart Schaefer @ 2005-10-03 16:50 UTC (permalink / raw)
  To: zsh-workers

On Oct 3,  4:10pm, Peter Stephenson wrote:
}
} Bart Schaefer wrote:
} > It could also be argued that, if the trap should behave like an "eval",
} > it ought to set $? = 1 when an error occurs inside the trap (but still
} > not cause an interrupt condition).  The example of bash2 contradicts
} > that position.  Can anyone who is reading this try ksh?
} 
} ksh appears to set the status to 1.

Oh, goody.

} > Here's a possible compromise:  Use my second patch, but propagate the
} > error if and only if we're in the try-block of an always-construct.
} 
} But do you mean how can it test if it's in the try block part?  There's
} no explicit test for that at the moment, but it's easy to add a variable.

Yes, that is what I meant.

That gives us something like this (except I'm only half serious about the
emulation part):

Index: Src/loop.c
===================================================================
diff -c -r1.10 loop.c
--- Src/loop.c	18 Feb 2005 17:05:17 -0000	1.10
+++ Src/loop.c	3 Oct 2005 16:31:57 -0000
@@ -627,13 +627,17 @@
 try_errflag = -1;
 
 /**/
+zlong
+try_tryflag = 0;
+
+/**/
 int
 exectry(Estate state, int do_exec)
 {
     Wordcode end, always;
     int endval;
     int save_retflag, save_breaks, save_loops, save_contflag;
-    zlong save_try_errflag;
+    zlong save_try_errflag, save_try_tryflag;
 
     end = state->pc + WC_TRY_SKIP(state->pc[-1]);
     always = state->pc + 1 + WC_TRY_SKIP(*state->pc);
@@ -642,7 +646,12 @@
     cmdpush(CS_CURSH);
 
     /* The :try clause */
+    save_try_tryflag = try_tryflag;
+    try_tryflag = 1;
+
     execlist(state, 1, do_exec);
+
+    try_tryflag = save_try_tryflag;
 
     /* Don't record errflag here, may be reset. */
     endval = lastval;
Index: Src/signals.c
===================================================================
RCS file: /extra/cvsroot/zsh/zsh-4.0/Src/signals.c,v
retrieving revision 1.13
diff -c -r1.13 signals.c
--- Src/signals.c	28 May 2005 04:32:49 -0000	1.13
+++ Src/signals.c	3 Oct 2005 16:48:34 -0000
@@ -1003,6 +1003,7 @@
     int trapret = 0;
     int obreaks = breaks;
     int isfunc;
+    int traperr;
 
     /* if signal is being ignored or the trap function		      *
      * is NULL, then return					      *
@@ -1097,8 +1098,8 @@
 	 * execrestore.
 	 */
 	trapret = trapreturn + 1;
-    } else if (errflag)
-	trapret = 1;
+    }
+    traperr = errflag;
     execrestore();
     lexrestore();
 
@@ -1110,6 +1111,10 @@
 	    lastval = trapret-1;
 	}
     } else {
+	if (traperr && emulation != EMULATE_SH)
+	    lastval = 1;
+	if (try_tryflag)
+	    errflag = traperr;
 	breaks += obreaks;
 	if (breaks > loops)
 	    breaks = loops;


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-03 16:21                     ` Bart Schaefer
@ 2005-10-03 17:59                       ` DervishD
  2005-10-04 16:31                         ` Bart Schaefer
  0 siblings, 1 reply; 25+ messages in thread
From: DervishD @ 2005-10-03 17:59 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: zsh-workers

    Hi Bart :)

 * Bart Schaefer <schaefer@brasslantern.com> dixit:
> On Oct 3, 11:01am, DervishD wrote:
> }     I know, I know, but AFAIK the "always" block works more or less
> } the same as exception handling does in other languages.
> Less, not more.  The problem is that in a language with real
> exceptions, the difference between an error and an exception is
> well-defined, and it is usually possible to have errors without
> exceptions and vice-versa. Zsh's "always" block is an error
> handler, not an exception handler, and PWS's functions are
> overloading the meaning of "error".

    But always blocks handle errors that would cause the shell to
abort execution. In a certain way, they are exceptions, they're
different from errors (with "error" I mean the usual "non-zero exit
code").
 
> } > }   Propagating "errflag" may break current code only if that code is
> } > } using an inline trap which "returns" a value and that error value is
> } > } ignored on purpose.
> } > That's not quite correct.  Remember, an inline trap using the "return"
> } > builtin actually causes the surrounding/calling context to return
> } 
> }     That's why I was quoting "return": I didn't mean a literal
> } "return" command, but a way of returning a value to the current code.
> 
> Yes, but there *isn't* any way of returning a value to the surrounding
> code -- unless you mean something like setting $REPLY.  So your basis
> for the statement is a practical impossibility.

    I was thinking exactly that, using $REPLY or something similar. I
just wasn't sure if the trap restored all the environment or not.

> }     But if you propagate the error, you won't break such code. I
> } mean, even with your patch, this code...
> } 
> }     trap 'readonly VAR; VAR=0' ZERR
> } 
> }     print "HERE"
> }     false
> }     print "HERE AGAIN"
> } 
> }     ...will print both strings.
>    
> In 4.0.6, or with my second potential patch, that prints only "HERE"
> (plus the error message about the readonly assignment).  In 4.2.5, it
> prints both strings.  In bash2, it also prints both strings.  Anything
> that relies on the 4.2.5 or bash2 behavior will break if we go back to
> propagating the error as was done in 4.0.6.

    It doesn't seem to be a good idea, then...
 
> } O:)) As far as I understan, "errflag" just signals the error and
> } propagate it to the current execution environment (for inline traps,
> } I mean), but doesn't make the shell abort :?
> It doesn't make the shell *exit*, but it does stop execution in the
> way that would jump into an "always" block.  (If it did not, how
> would it solve your problem?)  In the absence of the "always", it
> simply bails out of the current context.  The "always" block is a way
> to intercept the "bail out" action, but the bail-out happens whether
> "always" is there or not.

    OK, I just was assuming that without the always block the
execution would go on undisturbed, which is false. I don't know why I
got that false impression, because I really know that code like this:

    readonly VAR
    VAR=

    will stop execution. Sorry, my fault.
  
> }     If no patch is applied I can still use TRAPNAL's for throwing
> } exceptions, but I must think a way of doing that with inline traps,
> } which I think it's impossible because the value of TRY_BLOCK_ERROR is
> } reset.
> You're right, it's impossible.  You'll have to think about how you
> would write the code if you never had "always" in the first place,
> and write it that way.

    The problem is that I would like to use "always" blocks with the
"nonzeroexitcode" kind of errors (that's why I use ZERR, it helps me
to convert the normal errors into errors as defined in a try block
context, a condition which causes the shell to stop execution).
Obviously this doesn't need nor exceptions nor always blocks, a
simple "goto" will suffice because what I want is a way of avoiding
handling similar errors in a per-command basis. I mean, I want to
replace this code:

    command1 && {# handle here some possible error probably exiting}
    command2 && {# handle here exactly the same error as before}
    ...
    commandn && {# incredible, here we must handle a similar error}

    with this one:

    trap 'throw commonerror$LINENO' ZERR
    {
        command1
        command2
        command3
    } always {
        # Here we catch and handle the common error
        #   In the exception name we have the line number,
        # just in case we want to fine tune error handling
    }

    I face this kind of code frequently: some tightly coupled
commands that cause the same kind of errors, or whose failures can be
seen as a single error, and execution *must* stop at that point.

    That's the whole point. I maybe am using the wrong shell
construct for solving the problem, and obviously I can always use the
first syntax and handle errors one-by-one (and not, something like
this: { command1 || command 2 || command 3 } && {#handle error here}
won't always work for me), but I find the always block construct much
better for this task. If I cannot use the ZERR trap and I must add an
explicit throw call after each command, it still is far better than
handling the error in place, better even than using a function to
handle the error instead of the throw call, because of rethrowing.

    With all the information you've given to me, I think now that
it's not a good idea to patch zsh to alter current behaviour, because
I see that current behaviour makes sense, but nonetheless being able
to cause 'alwaysable' errors from inline traps looks very good to me
O:))

    Thanks again for all, Bart.

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-03 17:59                       ` DervishD
@ 2005-10-04 16:31                         ` Bart Schaefer
  2005-10-04 17:29                           ` DervishD
  0 siblings, 1 reply; 25+ messages in thread
From: Bart Schaefer @ 2005-10-04 16:31 UTC (permalink / raw)
  To: DervishD; +Cc: zsh-workers

On Oct 3,  7:59pm, DervishD wrote:
:
: I want to replace this code:
: 
:     command1 && {# handle here some possible error probably exiting}
:     command2 && {# handle here exactly the same error as before}
:     ...
:     commandn && {# incredible, here we must handle a similar error}
: 
:     with this one:
: 
:     trap 'throw commonerror$LINENO' ZERR
:     {
:         command1
:         command2
:         command3
:     } always {
:         # Here we catch and handle the common error
:         #   In the exception name we have the line number,
:         # just in case we want to fine tune error handling
:     }

First of all, note that unless command3 is return, the always block
is going to execute regardless of whether there has been an error.

How about:

    function common_error() {
	# Here we catch and handle the common error
	# In the ERROR_LINE variable we have the line number,
	# and in the ERROR variable we have the $? status,
	# just in case we want to fine tune error handling
    }

    trap 'ERROR=$?; ERROR_LINE=$LINENO; return $ERROR' ZERR
    trap common_error EXIT

    command1
    command2
    command3

    trap - EXIT ZERR

Note also for reference that
    trap 'return $?' ZERR
is roughly equivalent to
    setopt ERR_RETURN


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-04 16:31                         ` Bart Schaefer
@ 2005-10-04 17:29                           ` DervishD
  2005-10-04 17:34                             ` Peter Stephenson
  0 siblings, 1 reply; 25+ messages in thread
From: DervishD @ 2005-10-04 17:29 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: zsh-workers

    Hi Bart :)

 * Bart Schaefer <schaefer@brasslantern.com> dixit:
> : I want to replace this code:
> : 
> :     command1 && {# handle here some possible error probably exiting}
> :     command2 && {# handle here exactly the same error as before}
> :     ...
> :     commandn && {# incredible, here we must handle a similar error}
> : 
> :     with this one:
> : 
> :     trap 'throw commonerror$LINENO' ZERR
> :     {
> :         command1
> :         command2
> :         command3
> :     } always {
> :         # Here we catch and handle the common error
> :         #   In the exception name we have the line number,
> :         # just in case we want to fine tune error handling
> :     }
> First of all, note that unless command3 is return, the always block
> is going to execute regardless of whether there has been an error.

    Well, I was going to use a test in the always block to see if an
exception was thrown, using catch or testing for "EXCEPTION" and
"TRY_BLOCK_ERROR" manually. That's not an issue.

> How about:
> 
>     function common_error() {
> 	# Here we catch and handle the common error
> 	# In the ERROR_LINE variable we have the line number,
> 	# and in the ERROR variable we have the $? status,
> 	# just in case we want to fine tune error handling
>     }
> 
>     trap 'ERROR=$?; ERROR_LINE=$LINENO; return $ERROR' ZERR
>     trap common_error EXIT
> 
>     command1
>     command2
>     command3
> 
>     trap - EXIT ZERR
> 
> Note also for reference that
>     trap 'return $?' ZERR
> is roughly equivalent to
>     setopt ERR_RETURN

    Man, you're the f. boss. I NEVER thought about this, which
doesn't need throw nor an always block. Thanks a lot for the help,
Bart, this is a very good solution, much better, even, than using
always blocks.

    Just one note: why should be always blocks needed at all if you
can use this kind of solutions? As long as you don't cause ZERR to be
raised in "common_error()", this solution is much better because you
can fine tune (using the "trap" builtin) when do you want to do
common handling and when you don't. The only difference I can see is
that code in an always block is executed in the current environment
and code in "common_error()" is not.

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-04 17:29                           ` DervishD
@ 2005-10-04 17:34                             ` Peter Stephenson
  2005-10-04 17:46                               ` DervishD
  0 siblings, 1 reply; 25+ messages in thread
From: Peter Stephenson @ 2005-10-04 17:34 UTC (permalink / raw)
  To: zsh-workers

DervishD wrote:
>     Just one note: why should be always blocks needed at all if you
> can use this kind of solutions? As long as you don't cause ZERR to be
> raised in "common_error()", this solution is much better because you
> can fine tune (using the "trap" builtin) when do you want to do
> common handling and when you don't. The only difference I can see is
> that code in an always block is executed in the current environment
> and code in "common_error()" is not.

TRAP_RETURN triggers on any non-zero status, while an "always" block
is used to stop actual errors which would usually cause everything in
sight(*) to abort.  If you're really concerned only with non-zero status
and don't care about errors there's every reason to go with something
like TRAP_RETURN.

(*) for some values of "everything"

pws


This message has been scanned for viruses by BlackSpider MailControl - www.blackspider.com


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: Exception handling and "trap" vs. TRAPNAL()
  2005-10-04 17:34                             ` Peter Stephenson
@ 2005-10-04 17:46                               ` DervishD
  0 siblings, 0 replies; 25+ messages in thread
From: DervishD @ 2005-10-04 17:46 UTC (permalink / raw)
  To: Peter Stephenson; +Cc: zsh-workers

    Hi Peter :)

 * Peter Stephenson <pws@csr.com> dixit:
> DervishD wrote:
> >     Just one note: why should be always blocks needed at all if you
> > can use this kind of solutions? As long as you don't cause ZERR to be
> > raised in "common_error()", this solution is much better because you
> > can fine tune (using the "trap" builtin) when do you want to do
> > common handling and when you don't. The only difference I can see is
> > that code in an always block is executed in the current environment
> > and code in "common_error()" is not.
> TRAP_RETURN triggers on any non-zero status, while an "always" block
> is used to stop actual errors which would usually cause everything in
> sight(*) to abort.  If you're really concerned only with non-zero status
> and don't care about errors there's every reason to go with something
> like TRAP_RETURN.

    But you can check for those kind of errors like this:

    ( #whatever would have caused an abortive-error )

    OK, you must run them in a subshell, but the fact is that this is
doable. But I see, an always block here is far more readable and easy
to use (apart from being more optimal if the 'abortive-error' can
happen in a loop).

    OTOH, code like this is very annoying when trapping ZERR:

    # If the file doesn't exist, this will raise ZERR
    # We probably will want ZERR raised if "do_something" fails,
    # but certainly not if the test fails. We must use "if"...
    [[ -r "$filename" ]] && do_something

    Thanks for the explanation :)

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Exception handling and "trap" vs. TRAPNAL()
@ 2005-10-01  8:01 DervishD
  0 siblings, 0 replies; 25+ messages in thread
From: DervishD @ 2005-10-01  8:01 UTC (permalink / raw)
  To: Zsh Workers

    Hi all :)

    I've tried twice to forward this message from zsh-users to
zsh-workers, with no success. Has this list any kind of limitation
for forwarded material? Well, anyway, here is the message again.

    Let's say we have this script, using exception handling:

--- cut here ---
#!/bin/zsh

emulate -L zsh

[[ "$1" = "trap" ]] && trap 'throw DEFAULT' ZERR
[[ "$1" = "TRAPZERR" ]] && function TRAPZERR() { throw DEFAULT ; }

function throw() {
  typeset -g EXCEPTION="$1"
  readonly THROW=0
  if (( TRY_BLOCK_ERROR == 0 )); then
    (( TRY_BLOCK_ERROR = 1 ))
  fi
  THROW= 2>/dev/null
}

function catch() {
    typeset -g CAUGHT
    if [[ $TRY_BLOCK_ERROR -gt 0 && $EXCEPTION = ${~1} ]]; then
        (( TRY_BLOCK_ERROR = 0 ))
        CAUGHT="$EXCEPTION"
        unset EXCEPTION
        return 0
    fi
    return 1
}

alias catch="noglob catch"


{
    print "Before throwing"
    # This should throw "DEFAULT" exception
    false
    throw EXCEPTION
    # This shouldn't be shown
    print "After throwing"
} always {
    catch * && print "Caught exception $CAUGHT"
    return 0
}

--- cut here ---

    Sorry for the size, but I want to make sure it is as
self-contained as possible. Well, I do the following:

    $ ./script trap
    Before throwing
    Caught exception EXCEPTION

    $ ./script TRAPZERR
    Before throwing
    Caught exception DEFAULT

    $ ./script
    Before throwing
    Caught exception EXCEPTION

    I'm puzzled. The "trap" trap is executed in the current
environment, so I assume it would throw "DEFAULT", as intended, as
soon as we hit the "false". It doesn't and I don't know why. OTOH,
the TRAPZERR function, which runs "throw" in its own environment,
works ok :??? WHY?

    AFAIK the "trap" builtin runs its arguments in the current
environment, so to say it's like an alias, like cutting and pasting
the code, but anyway that shouldn't be the problem, since "throw" is
using globals and so it affects running in any environment.

    The only thing that could be happening is that the ZERR trap
using "trap" is not setting "TRY_BLOCK_ERROR" to "1", or it sets the
variable but the variable gets reset upon exiting the trap :???

    Can anybody (and I really mean anybody, not Bart XD) explain this
to me? I would like to use something similar and I need to use "trap"
traps and not "TRAPNAL()" traps because I want to use LINENO and
probably a couple of variables which are local to the functions where
the ZERR trap can be raised. I'm not sure if this is an intended
behaviour or a zsh bug, since there have been bugs related to traps
when saving/restoring shell state in the past, and this may be one of
those bugs.

    Thanks a lot in advance :)

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Exception handling and "trap" vs. TRAPNAL()
@ 2005-10-01  7:45 DervishD
  0 siblings, 0 replies; 25+ messages in thread
From: DervishD @ 2005-10-01  7:45 UTC (permalink / raw)
  To: Zsh Workers

    Hi all :))

    I forwarded this message yesterday, but it hasn't made its way
into the list, don't know why :? so I'm posting it here again.

    Let's say we have this script, using exception handling:

--- cut here ---
#!/bin/zsh

emulate -L zsh

[[ "$1" = "trap" ]] && trap 'throw DEFAULT' ZERR
[[ "$1" = "TRAPZERR" ]] && function TRAPZERR() { throw DEFAULT ; }

function throw() {
  typeset -g EXCEPTION="$1"
  readonly THROW=0
  if (( TRY_BLOCK_ERROR == 0 )); then
    (( TRY_BLOCK_ERROR = 1 ))
  fi
  THROW= 2>/dev/null
}

function catch() {
    typeset -g CAUGHT
    if [[ $TRY_BLOCK_ERROR -gt 0 && $EXCEPTION = ${~1} ]]; then
        (( TRY_BLOCK_ERROR = 0 ))
        CAUGHT="$EXCEPTION"
        unset EXCEPTION
        return 0
    fi
    return 1
}

alias catch="noglob catch"


{
    print "Before throwing"
    # This should throw "DEFAULT" exception
    false
    throw EXCEPTION
    # This shouldn't be shown
    print "After throwing"
} always {
    catch * && print "Caught exception $CAUGHT"
    return 0
}

--- cut here ---

    Sorry for the size, but I want to make sure it is as
self-contained as possible. Well, I do the following:

    $ ./script trap
    Before throwing
    Caught exception EXCEPTION

    $ ./script TRAPZERR
    Before throwing
    Caught exception DEFAULT

    $ ./script
    Before throwing
    Caught exception EXCEPTION

    I'm puzzled. The "trap" trap is executed in the current
environment, so I assume it would throw "DEFAULT", as intended, as
soon as we hit the "false". It doesn't and I don't know why. OTOH,
the TRAPZERR function, which runs "throw" in its own environment,
works ok :??? WHY?

    AFAIK the "trap" builtin runs its arguments in the current
environment, so to say it's like an alias, like cutting and pasting
the code, but anyway that shouldn't be the problem, since "throw" is
using globals and so it affects running in any environment.

    The only thing that could be happening is that the ZERR trap
using "trap" is not setting "TRY_BLOCK_ERROR" to "1", or it sets the
variable but the variable gets reset upon exiting the trap :???

    Can anybody (and I really mean anybody, not Bart XD) explain this
to me? I would like to use something similar and I need to use "trap"
traps and not "TRAPNAL()" traps because I want to use LINENO and
probably a couple of variables which are local to the functions where
the ZERR trap can be raised.

    Thanks a lot in advance :)

    Raúl Núñez de Arenas Coronado

-- 
Linux Registered User 88736 | http://www.dervishd.net
http://www.pleyades.net & http://www.gotesdelluna.net
It's my PC and I'll cry if I want to...


^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2005-10-04 17:45 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20050929200741.GA1156@DervishD>
     [not found] ` <20050930124130.45eb0463.pws@csr.com>
     [not found]   ` <20051001153756.GA12183@DervishD>
2005-10-01 18:38     ` Exception handling and "trap" vs. TRAPNAL() Bart Schaefer
2005-10-01 19:10       ` Peter Stephenson
2005-10-01 20:41         ` DervishD
2005-10-01 22:44         ` Bart Schaefer
2005-10-02  8:06           ` DervishD
2005-10-01 20:28       ` DervishD
2005-10-02  4:40         ` Bart Schaefer
2005-10-02  8:13           ` DervishD
2005-10-02 19:09           ` Peter Stephenson
2005-10-02 19:55             ` Bart Schaefer
2005-10-02 23:00               ` DervishD
2005-10-03  1:37                 ` Bart Schaefer
2005-10-03  8:57                   ` Peter Stephenson
2005-10-03 14:51                     ` Bart Schaefer
2005-10-03 15:10                       ` Peter Stephenson
2005-10-03 16:50                         ` Bart Schaefer
2005-10-03  9:01                   ` DervishD
2005-10-03 16:21                     ` Bart Schaefer
2005-10-03 17:59                       ` DervishD
2005-10-04 16:31                         ` Bart Schaefer
2005-10-04 17:29                           ` DervishD
2005-10-04 17:34                             ` Peter Stephenson
2005-10-04 17:46                               ` DervishD
2005-10-01  8:01 DervishD
  -- strict thread matches above, loose matches on Subject: below --
2005-10-01  7:45 DervishD

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).