Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: Loops and pipes



Making it an error is quite simple:

diff --git a/Src/exec.c b/Src/exec.c
index 5f849db63..2e6684c96 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1258,6 +1258,7 @@ entersubsh(int flags, struct entersubsh_ret *retp)
        clearjobtab(monitor);
     get_usage();
     forklevel = locallevel;
+    loops = 0;
 }

 /* execute a string */

That works but for break/continue statements that are in a subshell of their first enclosing loop it produces rather confusing error messages stating that there are no loops. Here is a patch that avoids that:

- Forbid break/continue in subshells

And here is a further patch that also checks that the break/continue statements don't escape past the available loops:

- Forbid escaping past all enclosing loops in the same subshell

The description of "continue n" states that it breaks out of n-1 loops and resumes the nth loop but if there are m<n loops then it behaves as "continue m", which is rather unexpected. The second patch avoids that by making "continue n" (as well as "break n") illegal if n is greater than the number of enclosing loops (started in the same subshell as the break/continue statement).

Philippe

diff --git a/Src/builtin.c b/Src/builtin.c
index e35d7fe2c..10c64d3c8 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -5814,24 +5814,22 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func)
 	nump = 1;
     }
 
-    if (nump > 0 && (func == BIN_CONTINUE || func == BIN_BREAK) && num <= 0) {
-	zerrnam(name, "argument is not positive: %d", num);
-	return 1;
-    }
-
     switch (func) {
     case BIN_CONTINUE:
-	if (!loops) {   /* continue is only permitted in loops */
-	    zerrnam(name, "not in while, until, select, or repeat loop");
+    case BIN_BREAK:
+	num = nump ? num : 1;
+	if (num <= 0) {
+	    zerrnam(name, "argument is not positive: %d", num);
 	    return 1;
 	}
-	contflag = 1; /* FALLTHROUGH */
-    case BIN_BREAK:
-	if (!loops) {   /* break is only permitted in loops */
-	    zerrnam(name, "not in while, until, select, or repeat loop");
+	if (!loops) {   /* break/continue only permitted in loops */
+	    zerrnam(name, ancestor_loops
+		    ? "not in same subshell as first enclosing loop"
+		    : "not in for, while, until, select, or repeat loop");
 	    return 1;
 	}
-	breaks = nump ? minimum(num,loops) : 1;
+	contflag = func == BIN_CONTINUE;
+	breaks = minimum(num, loops);
 	break;
     case BIN_RETURN:
 	if ((isset(INTERACTIVE) && isset(SHINSTDIN))
diff --git a/Src/exec.c b/Src/exec.c
index 5f849db63..94f1b735c 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -1258,6 +1258,8 @@ entersubsh(int flags, struct entersubsh_ret *retp)
 	clearjobtab(monitor);
     get_usage();
     forklevel = locallevel;
+    ancestor_loops += loops;
+    loops = 0;
 }
 
 /* execute a string */
diff --git a/Src/init.c b/Src/init.c
index d2b97daff..c5e915bb1 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -1325,7 +1325,7 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
     }
 #endif
 
-    breaks = loops = 0;
+    breaks = contflag = loops = ancestor_loops = 0;
     lastmailcheck = zmonotime(NULL);
     locallevel = sourcelevel = 0;
     sfcontext = SFC_NONE;
diff --git a/Src/loop.c b/Src/loop.c
index 351edde89..66dad05f8 100644
--- a/Src/loop.c
+++ b/Src/loop.c
@@ -30,18 +30,23 @@
 #include "zsh.mdh"
 #include "loop.pro"
 
-/* # of nested loops we are in */
- 
+/* # of nested loops we are in started in ancestor subshells */
+
+/**/
+int ancestor_loops;
+
+/* # of nested loops we are in started in the current subshell */
+
 /**/
 int loops;
- 
-/* # of continue levels */
- 
+
+/* whether to continue instead of break on the last break level */
+
 /**/
 mod_export int contflag;
- 
+
 /* # of break levels */
- 
+
 /**/
 mod_export volatile int breaks;
 
diff --git a/Test/A07control.ztst b/Test/A07control.ztst
index b1a248732..cf70d46fe 100644
--- a/Test/A07control.ztst
+++ b/Test/A07control.ztst
@@ -28,7 +28,18 @@
   }
   fn'
 1:continue outside loop
-?fn:continue:1: not in while, until, select, or repeat loop
+?fn:continue:1: not in for, while, until, select, or repeat loop
+
+  for x in a b c; do
+    ( echo $x:before; continue 2>&1; echo $x:after )
+  done
+1:continue in subshell
+>a:before
+>(eval):continue:2: not in same subshell as first enclosing loop
+>b:before
+>(eval):continue:2: not in same subshell as first enclosing loop
+>c:before
+>(eval):continue:2: not in same subshell as first enclosing loop
 
   for outer in 0 1 2 3; do
     print outer $outer
diff --git a/Src/builtin.c b/Src/builtin.c
index 10c64d3c8..49effb22b 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -5822,10 +5822,20 @@ bin_break(char *name, char **argv, UNUSED(Options ops), int func)
 	    zerrnam(name, "argument is not positive: %d", num);
 	    return 1;
 	}
-	if (!loops) {   /* break/continue only permitted in loops */
-	    zerrnam(name, ancestor_loops
-		    ? "not in same subshell as first enclosing loop"
-		    : "not in for, while, until, select, or repeat loop");
+	if (num > loops) {   /* break/continue only permitted in loops */
+	    if (!loops && !ancestor_loops)
+		zerrnam(name,
+			"not in for, while, until, select, or repeat loop");
+	    else if (num > loops + ancestor_loops)
+		zerrnam(name,
+			"not in %d for, while, until, select, or repeat loops",
+			num);
+	    else if (!loops)
+		zerrnam(name, "not in same subshell as first enclosing loop");
+	    else
+		zerrnam(name,
+			"not in same subshell as first %d enclosing loops",
+			num);
 	    return 1;
 	}
 	contflag = func == BIN_CONTINUE;
diff --git a/Test/A07control.ztst b/Test/A07control.ztst
index cf70d46fe..adb676f23 100644
--- a/Test/A07control.ztst
+++ b/Test/A07control.ztst
@@ -30,6 +30,21 @@
 1:continue outside loop
 ?fn:continue:1: not in for, while, until, select, or repeat loop
 
+  $ZTST_testdir/../Src/zsh -fc '
+    for x in a b c; do
+      for y in d e f; do
+        echo "$x$y:before"
+        continue 3
+        echo "$x$y:after"
+      done
+      echo "$x-:after"
+    done
+    echo "--:after"
+  '
+1:continue beyond all enclosing loops
+>ad:before
+?zsh:continue:5: not in 3 for, while, until, select, or repeat loops
+
   for x in a b c; do
     ( echo $x:before; continue 2>&1; echo $x:after )
   done
@@ -41,6 +56,31 @@
 >c:before
 >(eval):continue:2: not in same subshell as first enclosing loop
 
+  for x in a b c; do
+    (
+      for y in d e f; do
+        echo "$x$y:before"
+        continue 2 2>&1
+        echo "$x$y:after"
+      done
+      echo "$x-:after1"
+    )
+    echo "$x-:after2"
+  done
+  echo "--:after"
+0:continue beyond all enclosing loops
+>ad:before
+>(eval):continue:5: not in same subshell as first 2 enclosing loops
+>a-:after2
+>bd:before
+>(eval):continue:5: not in same subshell as first 2 enclosing loops
+>b-:after2
+>cd:before
+>(eval):continue:5: not in same subshell as first 2 enclosing loops
+>c-:after2
+>--:after
+
+
   for outer in 0 1 2 3; do
     print outer $outer
     for inner in 0 1 2 3; do


Messages sorted by: Reverse Date, Date, Thread, Author