zsh-workers
 help / color / mirror / code / Atom feed
From: Bart Schaefer <schaefer@brasslantern.com>
To: zsh-workers@zsh.org
Subject: [PATCH] ERR_EXIT with "for" loops and shell functions (Re: Bug report)
Date: Sat, 27 Dec 2014 16:32:20 -0800	[thread overview]
Message-ID: <141227163220.ZM11821@torch.brasslantern.com> (raw)
In-Reply-To: <141226183516.ZM18384@torch.brasslantern.com>

On Dec 26,  6:35pm, Bart Schaefer wrote:
}
} This is very tricky because we have to propagate the value of $? from
} before the "for" keyword (including before the start of the function)
} into the "do" body, but must *not* *use* the value of $? for deciding
} whether to exit-on-error, because the nonzero value came from an "if"
} test.  There is a "noerrexit" flag that is supposed to cover this case,
} but it's not set when the very first "sublist" in a function body is
} a for-loop (there are likely to be other similar cases).

There's a further complication here, which is this:

    zsh -fc 'false; for x; do :; done; echo $?'
    1

A "for" loop which executes zero times is supposed to set $? == 0, or
at least it does so in bash.  Zsh 5.0.7 passes through $? unchanged when
the "for" does not loop.

Oddly, the current version from git behaves differently:

    Src/zsh -fc 'false; for x in; do false; done; echo $?' 
    0

But:

    Src/zsh -o null_glob -fc 'false; for x in _*; do false; done; echo $?'   
    1

I haven't traced through why a missing "in" list resets $? to zero,
whereas an empty list created by globbing passes through the outer $?.

Here's another difference from bash:

    bash -c 'false; select x in; do false; done; echo $?'
    bash: -c: line 0: syntax error near unexpected token `;'
    bash: -c: line 0: `false; select x in; do false; done; echo $?'

whereas zsh accepts a variety of syntax with inconsistent return: 

    zsh -fc 'true; select x; do :; done; echo $?'
    1
    zsh -fc 'true; select x in; do :; done; echo $?'
    0
    zsh -fc 'true; select x in $empty; do :; done; echo $?'
    1

The first and last of those three also pass syntax in bash and give a
loop that executes zero times and sets $? to zero rather than one.

The following patch attempts to fix all of this weirdness.  I'm a bit
concerned that there may still be cases where prefork substitutions
are performed when they actually should not be because the errexit
[not noerrexit] condition has not been tested soon enough -- it seems
as if zsh is checking errexit at the start of the next command rather
than immediately following the command that returned nonzero, in at
least some circumstances -- but I can't come up with a test case.

Possibly this whole thing needs to be rethought.

Anyway, the most controversial thing below is probably the change in the
return status of "select", which is now always zero unless something in
the loop body returns nonzero.  If we want to disallow empty-"in" syntax
we should do it elsewhere.


diff --git a/Src/exec.c b/Src/exec.c
index 6a7dbb1..eaf73df 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2632,6 +2632,10 @@ execcmd(Estate state, int input, int output, int how, int last1)
 	}
     }
 
+    /* if we get this far, it is OK to pay attention to lastval again */
+    if (noerrexit == 2 && !is_shfunc)
+	noerrexit = 0;
+
     /* Do prefork substitutions */
     esprefork = (assign || isset(MAGICEQUALSUBST)) ? PREFORK_TYPESET : 0;
     if (args && htok)
diff --git a/Src/loop.c b/Src/loop.c
index 8bb1ec9..7b3bdd2 100644
--- a/Src/loop.c
+++ b/Src/loop.c
@@ -102,7 +102,10 @@ execfor(Estate state, int do_exec)
 		addlinknode(args, dupstring(*x));
 	}
     }
-    /* lastval = 0; */
+
+    if (!args || empty(args))
+	lastval = 0;
+
     loops++;
     pushheap();
     cmdpush(CS_FOR);
@@ -238,10 +241,10 @@ execselect(Estate state, UNUSED(int do_exec))
     }
     if (!args || empty(args)) {
 	state->pc = end;
-	return 1;
+	return 0;
     }
     loops++;
-    /* lastval = 0; */
+
     pushheap();
     cmdpush(CS_SELECT);
     usezle = interact && SHTTY != -1 && isset(USEZLE);
@@ -519,14 +522,17 @@ execif(Estate state, int do_exec)
 	s = 1;
 	state->pc = next;
     }
-    noerrexit = olderrexit;
 
     if (run) {
+	/* we need to ignore lastval until we reach execcmd() */
+	noerrexit = olderrexit ? olderrexit : lastval ? 2 : 0;
 	cmdpush(run == 2 ? CS_ELSE : (s ? CS_ELIFTHEN : CS_IFTHEN));
 	execlist(state, 1, do_exec);
 	cmdpop();
-    } else
+    } else {
+	noerrexit = olderrexit;
 	lastval = 0;
+    }
     state->pc = end;
 
     return lastval;

-- 
Barton E. Schaefer


  reply	other threads:[~2014-12-28  0:32 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-12-26 16:53 Bug report mvxxc
2014-12-27  2:35 ` Bart Schaefer
2014-12-28  0:32   ` Bart Schaefer [this message]
2015-01-02 16:56     ` [PATCH] ERR_EXIT with "for" loops and shell functions (Re: Bug report) Peter Stephenson
2015-01-02 18:56       ` Bart Schaefer
2015-01-02 20:15         ` Peter Stephenson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=141227163220.ZM11821@torch.brasslantern.com \
    --to=schaefer@brasslantern.com \
    --cc=zsh-workers@zsh.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).