Module Name:    src
Committed By:   rillig
Date:           Tue May  9 19:43:12 UTC 2023

Modified Files:
        src/usr.bin/make: for.c
        src/usr.bin/make/unit-tests: check-expect.lua directive-for-errors.exp
            directive-for-errors.mk directive-for-escape.exp
            directive-for-escape.mk directive-for.exp directive-for.mk

Log Message:
make: skip syntactically wrong .for loops

When a .for loop cannot be interpreted correctly, for example when there
are no iteration variables or the number of words doesn't match the
iteration variables, skip the body of the .for loop instead of
interpreting it once.


To generate a diff of this commit:
cvs rdiff -u -r1.173 -r1.174 src/usr.bin/make/for.c
cvs rdiff -u -r1.3 -r1.4 src/usr.bin/make/unit-tests/check-expect.lua \
    src/usr.bin/make/unit-tests/directive-for-errors.exp
cvs rdiff -u -r1.4 -r1.5 src/usr.bin/make/unit-tests/directive-for-errors.mk
cvs rdiff -u -r1.18 -r1.19 \
    src/usr.bin/make/unit-tests/directive-for-escape.exp \
    src/usr.bin/make/unit-tests/directive-for.mk
cvs rdiff -u -r1.17 -r1.18 \
    src/usr.bin/make/unit-tests/directive-for-escape.mk
cvs rdiff -u -r1.15 -r1.16 src/usr.bin/make/unit-tests/directive-for.exp

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/usr.bin/make/for.c
diff -u src/usr.bin/make/for.c:1.173 src/usr.bin/make/for.c:1.174
--- src/usr.bin/make/for.c:1.173	Mon May  8 10:24:07 2023
+++ src/usr.bin/make/for.c	Tue May  9 19:43:12 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: for.c,v 1.173 2023/05/08 10:24:07 rillig Exp $	*/
+/*	$NetBSD: for.c,v 1.174 2023/05/09 19:43:12 rillig Exp $	*/
 
 /*
  * Copyright (c) 1992, The Regents of the University of California.
@@ -58,7 +58,7 @@
 #include "make.h"
 
 /*	"@(#)for.c	8.1 (Berkeley) 6/6/93"	*/
-MAKE_RCSID("$NetBSD: for.c,v 1.173 2023/05/08 10:24:07 rillig Exp $");
+MAKE_RCSID("$NetBSD: for.c,v 1.174 2023/05/09 19:43:12 rillig Exp $");
 
 
 typedef struct ForLoop {
@@ -145,7 +145,7 @@ IsValidInVarname(char c)
 	    c != '(' && c != '{' && c != ')' && c != '}';
 }
 
-static bool
+static void
 ForLoop_ParseVarnames(ForLoop *f, const char **pp)
 {
 	const char *p = *pp;
@@ -156,7 +156,8 @@ ForLoop_ParseVarnames(ForLoop *f, const 
 		cpp_skip_whitespace(&p);
 		if (*p == '\0') {
 			Parse_Error(PARSE_FATAL, "missing `in' in for");
-			return false;
+			f->vars.len = 0;
+			return;
 		}
 
 		for (len = 0; p[len] != '\0' && !ch_isspace(p[len]); len++) {
@@ -165,7 +166,8 @@ ForLoop_ParseVarnames(ForLoop *f, const 
 				    "invalid character '%c' "
 				    "in .for loop variable name",
 				    p[len]);
-				return false;
+				f->vars.len = 0;
+				return;
 			}
 		}
 
@@ -180,11 +182,10 @@ ForLoop_ParseVarnames(ForLoop *f, const 
 
 	if (f->vars.len == 0) {
 		Parse_Error(PARSE_FATAL, "no iteration variables in for");
-		return false;
+		return;
 	}
 
 	*pp = p;
-	return true;
 }
 
 static bool
@@ -253,11 +254,8 @@ For_Eval(const char *line)
 		p += 3;
 
 		f = ForLoop_New();
-		if (!ForLoop_ParseVarnames(f, &p)) {
-			ForLoop_Free(f);
-			return -1;
-		}
-		if (!ForLoop_ParseItems(f, p))
+		ForLoop_ParseVarnames(f, &p);
+		if (f->vars.len > 0 && !ForLoop_ParseItems(f, p))
 			f->items.len = 0;	/* don't iterate */
 
 		accumFor = f;

Index: src/usr.bin/make/unit-tests/check-expect.lua
diff -u src/usr.bin/make/unit-tests/check-expect.lua:1.3 src/usr.bin/make/unit-tests/check-expect.lua:1.4
--- src/usr.bin/make/unit-tests/check-expect.lua:1.3	Fri Apr 15 09:33:20 2022
+++ src/usr.bin/make/unit-tests/check-expect.lua	Tue May  9 19:43:12 2023
@@ -1,17 +1,28 @@
 #!  /usr/bin/lua
--- $NetBSD: check-expect.lua,v 1.3 2022/04/15 09:33:20 rillig Exp $
+-- $NetBSD: check-expect.lua,v 1.4 2023/05/09 19:43:12 rillig Exp $
 
 --[[
 
 usage: lua ./check-expect.lua *.mk
 
-Check that each text from an '# expect: ...' comment in the .mk source files
-occurs in the corresponding .exp file, in the same order as in the .mk file.
-
-Check that each text from an '# expect[+-]offset: ...' comment in the .mk
-source files occurs in the corresponding .exp file and refers back to the
-correct line in the .mk file.
+Check that the various 'expect' comments in the .mk files produce the
+expected text in the corresponding .exp file.
 
+# expect: <line>
+        All of these lines must occur in the .exp file, in the same order as
+        in the .mk file.
+
+# expect-reset
+        Search the following 'expect:' comments from the top of the .exp
+        file again.
+
+# expect[+-]offset: <message>
+        Each message must occur in the .exp file and refer back to the
+        source line in the .mk file.
+
+# expect-all
+        Each message from the .exp file that can be matched by an
+        'expect[+-]offset' comment must actually be matched.
 ]]
 
 
@@ -68,6 +79,7 @@ local function check_mk(mk_fname)
   if exp_lines == nil then return end
   local by_location = collect_lineno_diagnostics(exp_lines)
   local prev_expect_line = 0
+  local match_all = false
 
   for mk_lineno, mk_line in ipairs(mk_lines) do
     for text in mk_line:gmatch("#%s*expect:%s*(.*)") do
@@ -110,6 +122,22 @@ local function check_mk(mk_fname)
           mk_fname, mk_lineno, exp_fname, text)
       end
     end
+
+    if mk_line:match("^#%s*expect%-all$") then
+      match_all = true
+    end
+  end
+
+  if match_all then
+    -- XXX: The messages are not sorted in any meaningful way.
+    for location, messages in pairs(by_location) do
+      for _, message in ipairs(messages) do
+        if message ~= "" then
+          print_error("error: %s: missing 'expect' comment for '%s'",
+            location, message)
+        end
+      end
+    end
   end
 end
 
Index: src/usr.bin/make/unit-tests/directive-for-errors.exp
diff -u src/usr.bin/make/unit-tests/directive-for-errors.exp:1.3 src/usr.bin/make/unit-tests/directive-for-errors.exp:1.4
--- src/usr.bin/make/unit-tests/directive-for-errors.exp:1.3	Mon May  8 10:24:07 2023
+++ src/usr.bin/make/unit-tests/directive-for-errors.exp	Tue May  9 19:43:12 2023
@@ -1,23 +1,17 @@
-make: "directive-for-errors.mk" line 7: Unknown directive "fori"
-make: "directive-for-errors.mk" line 8: warning: 
-make: "directive-for-errors.mk" line 9: for-less endfor
-make: "directive-for-errors.mk" line 19: Unknown directive "for"
-make: "directive-for-errors.mk" line 20: warning: 
-make: "directive-for-errors.mk" line 21: for-less endfor
-make: "directive-for-errors.mk" line 34: invalid character '$' in .for loop variable name
-make: "directive-for-errors.mk" line 35: Dollar $   and backslash backslash backslash backslash.
-make: "directive-for-errors.mk" line 36: for-less endfor
-make: "directive-for-errors.mk" line 41: no iteration variables in for
-make: "directive-for-errors.mk" line 45: warning: Should not be reached.
-make: "directive-for-errors.mk" line 46: for-less endfor
-make: "directive-for-errors.mk" line 51: Wrong number of words (5) in .for substitution list with 3 variables
-make: "directive-for-errors.mk" line 63: missing `in' in for
-make: "directive-for-errors.mk" line 65: warning: Should not be reached.
-make: "directive-for-errors.mk" line 66: for-less endfor
-make: "directive-for-errors.mk" line 72: Unknown modifier "Z"
-make: "directive-for-errors.mk" line 73: warning: Should not be reached.
-make: "directive-for-errors.mk" line 73: warning: Should not be reached.
-make: "directive-for-errors.mk" line 73: warning: Should not be reached.
+make: "directive-for-errors.mk" line 11: Unknown directive "fori"
+make: "directive-for-errors.mk" line 12: warning: <>
+make: "directive-for-errors.mk" line 13: for-less endfor
+make: "directive-for-errors.mk" line 27: Unknown directive "for"
+make: "directive-for-errors.mk" line 28: warning: <>
+make: "directive-for-errors.mk" line 29: for-less endfor
+make: "directive-for-errors.mk" line 46: invalid character '$' in .for loop variable name
+make: "directive-for-errors.mk" line 54: no iteration variables in for
+make: "directive-for-errors.mk" line 66: Wrong number of words (5) in .for substitution list with 3 variables
+make: "directive-for-errors.mk" line 80: missing `in' in for
+make: "directive-for-errors.mk" line 91: Unknown modifier "Z"
+make: "directive-for-errors.mk" line 92: warning: Should not be reached.
+make: "directive-for-errors.mk" line 92: warning: Should not be reached.
+make: "directive-for-errors.mk" line 92: warning: Should not be reached.
 make: Fatal errors encountered -- cannot continue
 make: stopped in unit-tests
 exit status 1

Index: src/usr.bin/make/unit-tests/directive-for-errors.mk
diff -u src/usr.bin/make/unit-tests/directive-for-errors.mk:1.4 src/usr.bin/make/unit-tests/directive-for-errors.mk:1.5
--- src/usr.bin/make/unit-tests/directive-for-errors.mk:1.4	Mon May  8 10:24:07 2023
+++ src/usr.bin/make/unit-tests/directive-for-errors.mk	Tue May  9 19:43:12 2023
@@ -1,24 +1,35 @@
-# $NetBSD: directive-for-errors.mk,v 1.4 2023/05/08 10:24:07 rillig Exp $
+# $NetBSD: directive-for-errors.mk,v 1.5 2023/05/09 19:43:12 rillig Exp $
 #
 # Tests for error handling in .for loops.
 
+# expect-all
+
+
 # A .for directive must be followed by whitespace, everything else results
 # in a parse error.
+# expect+1: Unknown directive "fori"
 .fori in 1 2 3
-.  warning ${i}
+.  warning <${i}>
 .endfor
+# expect-2: <>
+# expect-2: for-less endfor
+
 
 # A slash is not whitespace, therefore this is not parsed as a .for loop.
 #
 # XXX: The error message is misleading though.  As of 2020-12-31, it says
-# "Unknown directive "for"", but that directive is actually known.  This is
+# 'Unknown directive "for"', but that directive is actually known.  This is
 # because ForEval does not detect the .for loop as such, so parsing
 # continues in ParseLine > ParseDependencyLine > ParseDependency >
 # ParseDependencyTargets > ParseErrorNoDependency, and there the directive
 # name is parsed a bit differently.
+# expect+1: Unknown directive "for"
 .for/i in 1 2 3
-.  warning ${i}
+.  warning <${i}>
 .endfor
+# expect-2: warning: <>
+# expect-2: for-less endfor
+
 
 # Before for.c 1.173 from 2023-05-08, the variable name could be an arbitrary
 # word, it only needed to be separated by whitespace.  Even '$' and '\' were
@@ -31,6 +42,7 @@
 # error everywhere outside a .for loop.
 ${:U\$}=	dollar		# see whether the "variable" '$' is local
 ${:U\\}=	backslash	# see whether the "variable" '\' is local
+# expect+1: invalid character '$' in .for loop variable name
 .for $ \ in 1 2 3 4
 .  info Dollar $$ ${$} $($) and backslash $\ ${\} $(\).
 .endfor
@@ -38,6 +50,7 @@ ${:U\\}=	backslash	# see whether the "va
 # If there are no variables, there is no point in expanding the .for loop
 # since this would end up in an endless loop, consuming 0 of the 3 values in
 # each iteration.
+# expect+1: no iteration variables in for
 .for in 1 2 3
 # XXX: This should not be reached.  It should be skipped, as already done
 # when the number of values is not a multiple of the number of variables,
@@ -45,19 +58,23 @@ ${:U\\}=	backslash	# see whether the "va
 .  warning Should not be reached.
 .endfor
 
+
 # There are 3 variables and 5 values.  These 5 values cannot be split evenly
 # among the variables, therefore the loop is not expanded at all, it is
 # skipped instead.
+# expect+1: Wrong number of words (5) in .for substitution list with 3 variables
 .for a b c in 1 2 3 4 5
 .  warning Should not be reached.
 .endfor
 
+
 # The list of values after the 'in' may be empty, no matter if this emptiness
 # comes from an empty expansion or even from a syntactically empty line.
 .for i in
 .  info Would be reached if there were items to loop over.
 .endfor
 
+
 # A missing 'in' should parse the .for loop but skip the body.
 # expect+1: missing `in' in for
 .for i over k
@@ -65,10 +82,15 @@ ${:U\\}=	backslash	# see whether the "va
 .  warning Should not be reached.
 .endfor
 
+
 # A malformed modifier should be detected and skip the body of the loop.
 #
 # XXX: As of 2020-12-31, Var_Subst doesn't report any errors, therefore
 # the loop body is expanded as if no error had happened.
+# expect+1: Unknown modifier "Z"
 .for i in 1 2 ${:U3:Z} 4
 .  warning Should not be reached.
 .endfor
+# expect-2: Should not be reached.
+# expect-3: Should not be reached.
+# expect-4: Should not be reached.

Index: src/usr.bin/make/unit-tests/directive-for-escape.exp
diff -u src/usr.bin/make/unit-tests/directive-for-escape.exp:1.18 src/usr.bin/make/unit-tests/directive-for-escape.exp:1.19
--- src/usr.bin/make/unit-tests/directive-for-escape.exp:1.18	Mon May  8 10:24:07 2023
+++ src/usr.bin/make/unit-tests/directive-for-escape.exp	Tue May  9 19:43:12 2023
@@ -2,28 +2,28 @@ For: end for 1
 For: loop body:
 .  info ${:U!"#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~}
 make: Unclosed variable expression, expecting '}' for modifier "U!"" of variable "" with value "!""
-make: "directive-for-escape.mk" line 19: !"
+make: "directive-for-escape.mk" line 21: !"
 For: end for 1
 For: loop body:
 .  info ${:U!"\\\\#$%&'()*+,-./0-9\:;<=>?@A-Z[\\]_^a-z{|\}~}
 make: Unclosed variable expression, expecting '}' for modifier "U!"\\\\" of variable "" with value "!"\\"
-make: "directive-for-escape.mk" line 29: !"\\
+make: "directive-for-escape.mk" line 32: !"\\
 For: end for 1
 For: loop body:
 .  info ${:U\$}
-make: "directive-for-escape.mk" line 43: $
+make: "directive-for-escape.mk" line 47: $
 For: loop body:
 .  info ${:U${V}}
-make: "directive-for-escape.mk" line 43: value
+make: "directive-for-escape.mk" line 47: value
 For: loop body:
 .  info ${:U${V:=-with-modifier}}
-make: "directive-for-escape.mk" line 43: value-with-modifier
+make: "directive-for-escape.mk" line 47: value-with-modifier
 For: loop body:
 .  info ${:U$(V)}
-make: "directive-for-escape.mk" line 43: value
+make: "directive-for-escape.mk" line 47: value
 For: loop body:
 .  info ${:U$(V:=-with-modifier)}
-make: "directive-for-escape.mk" line 43: value-with-modifier
+make: "directive-for-escape.mk" line 47: value-with-modifier
 For: end for 1
 For: loop body:
 # ${:U\${UNDEF\:U\\$\\$}
@@ -34,27 +34,25 @@ For: loop body:
 For: end for 1
 For: loop body:
 .  info ${:U\${UNDEF\:U\\$\\$}
-make: "directive-for-escape.mk" line 92: ${UNDEF:U\backslash$
+make: "directive-for-escape.mk" line 101: ${UNDEF:U\backslash$
 For: loop body:
 .  info ${:U{{\}\}}
-make: "directive-for-escape.mk" line 92: {{}}
+make: "directive-for-escape.mk" line 101: {{}}
 For: loop body:
 .  info ${:Uend\}}
-make: "directive-for-escape.mk" line 92: end}
+make: "directive-for-escape.mk" line 101: end}
 For: end for 1
 For: loop body:
 .  info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end}
-make: "directive-for-escape.mk" line 113: begin<fallback>end
+make: "directive-for-escape.mk" line 122: begin<fallback>end
 For: end for 1
 For: loop body:
 .  info ${:U\$}
-make: "directive-for-escape.mk" line 121: $
-make: "directive-for-escape.mk" line 129: invalid character ':' in .for loop variable name
-make: "directive-for-escape.mk" line 130: one two three one three
-make: "directive-for-escape.mk" line 131: for-less endfor
-make: "directive-for-escape.mk" line 139: invalid character '}' in .for loop variable name
-make: "directive-for-escape.mk" line 140: one.c
-make: "directive-for-escape.mk" line 141: for-less endfor
+make: "directive-for-escape.mk" line 131: $
+make: "directive-for-escape.mk" line 140: invalid character ':' in .for loop variable name
+For: end for 1
+make: "directive-for-escape.mk" line 150: invalid character '}' in .for loop variable name
+For: end for 1
 For: end for 1
 For: loop body:
 .  info .        $$i: ${:Uinner}
@@ -67,44 +65,42 @@ For: loop body:
 .  info .     $${i2}: ${i2}
 .  info .     $${i,}: ${i,}
 .  info .  adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner}
-make: "directive-for-escape.mk" line 148: .        $i: inner
-make: "directive-for-escape.mk" line 149: .      ${i}: inner
-make: "directive-for-escape.mk" line 150: .   ${i:M*}: inner
-make: "directive-for-escape.mk" line 151: .      $(i): inner
-make: "directive-for-escape.mk" line 152: .   $(i:M*): inner
-make: "directive-for-escape.mk" line 153: . ${i${:U}}: outer
-make: "directive-for-escape.mk" line 154: .    ${i\}}: inner}
-make: "directive-for-escape.mk" line 155: .     ${i2}: two
-make: "directive-for-escape.mk" line 156: .     ${i,}: comma
-make: "directive-for-escape.mk" line 157: .  adjacent: innerinnerinnerinner
-make: "directive-for-escape.mk" line 165: invalid character '$' in .for loop variable name
-make: "directive-for-escape.mk" line 166: eight $$$$ and no cents.
-make: "directive-for-escape.mk" line 167: eight  and no cents.
-make: "directive-for-escape.mk" line 168: for-less endfor
-make: "directive-for-escape.mk" line 176: eight  and no cents.
+make: "directive-for-escape.mk" line 159: .        $i: inner
+make: "directive-for-escape.mk" line 160: .      ${i}: inner
+make: "directive-for-escape.mk" line 161: .   ${i:M*}: inner
+make: "directive-for-escape.mk" line 162: .      $(i): inner
+make: "directive-for-escape.mk" line 163: .   $(i:M*): inner
+make: "directive-for-escape.mk" line 164: . ${i${:U}}: outer
+make: "directive-for-escape.mk" line 165: .    ${i\}}: inner}
+make: "directive-for-escape.mk" line 166: .     ${i2}: two
+make: "directive-for-escape.mk" line 167: .     ${i,}: comma
+make: "directive-for-escape.mk" line 168: .  adjacent: innerinnerinnerinner
+make: "directive-for-escape.mk" line 187: invalid character '$' in .for loop variable name
+For: end for 1
+make: "directive-for-escape.mk" line 199: eight  and no cents.
 For: end for 1
-make: "directive-for-escape.mk" line 183: newline in .for value
-make: "directive-for-escape.mk" line 183: newline in .for value
+make: "directive-for-escape.mk" line 212: newline in .for value
+make: "directive-for-escape.mk" line 212: newline in .for value
 For: loop body:
 .  info short: ${:U" "}
 .  info long: ${:U" "}
-make: "directive-for-escape.mk" line 184: short: " "
-make: "directive-for-escape.mk" line 185: long: " "
+make: "directive-for-escape.mk" line 213: short: " "
+make: "directive-for-escape.mk" line 214: long: " "
 For: end for 1
 For: loop body:
 For: end for 1
-Parse_PushInput: .for loop in directive-for-escape.mk, line 198
-make: "directive-for-escape.mk" line 198: newline in .for value
-	in .for loop from directive-for-escape.mk:198 with i = "
+Parse_PushInput: .for loop in directive-for-escape.mk, line 230
+make: "directive-for-escape.mk" line 230: newline in .for value
+	in .for loop from directive-for-escape.mk:230 with i = "
 "
 For: loop body:
 : ${:U" "}
 SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `directive-for-escape.mk'
-Parsing line 199: : ${:U" "}
+Parsing line 231: : ${:U" "}
 ParseDependency(: " ")
-ParseEOF: returning to file directive-for-escape.mk, line 201
+ParseEOF: returning to file directive-for-escape.mk, line 233
 SetFilenameVars: ${.PARSEDIR} = <some-dir> ${.PARSEFILE} = `directive-for-escape.mk'
-Parsing line 201: .MAKEFLAGS: -d0
+Parsing line 233: .MAKEFLAGS: -d0
 ParseDependency(.MAKEFLAGS: -d0)
 For: end for 1
 For: loop body:
Index: src/usr.bin/make/unit-tests/directive-for.mk
diff -u src/usr.bin/make/unit-tests/directive-for.mk:1.18 src/usr.bin/make/unit-tests/directive-for.mk:1.19
--- src/usr.bin/make/unit-tests/directive-for.mk:1.18	Mon May  8 10:24:07 2023
+++ src/usr.bin/make/unit-tests/directive-for.mk	Tue May  9 19:43:12 2023
@@ -1,4 +1,4 @@
-# $NetBSD: directive-for.mk,v 1.18 2023/05/08 10:24:07 rillig Exp $
+# $NetBSD: directive-for.mk,v 1.19 2023/05/09 19:43:12 rillig Exp $
 #
 # Tests for the .for directive.
 #
@@ -8,11 +8,15 @@
 #	.for _FILE_ in values
 #	.for .FILE. in values
 #	.for _f_ in values
-
-# Using the .for loop, lists of values can be produced.
-# In simple cases, the :@var@${var}@ variable modifier can be used to
-# achieve the same effects.
 #
+# See also:
+#	varmod-loop.mk		The ':@var@...@' modifier
+
+# expect-all
+
+# A typical use case for a .for loop is to populate a variable with a list of
+# values depending on other variables.  In simple cases, the same effect can
+# be achieved using the ':@var@${var}@' modifier.
 .undef NUMBERS
 .for num in 1 2 3
 NUMBERS+=	${num}
@@ -21,8 +25,9 @@ NUMBERS+=	${num}
 .  error
 .endif
 
+
 # The .for loop also works for multiple iteration variables.
-# This is something that the variable modifier :@ cannot do.
+# This is something that the modifier :@ cannot do.
 .for name value in VARNAME value NAME2 value2
 ${name}=	${value}
 .endfor
@@ -30,12 +35,12 @@ ${name}=	${value}
 .  error
 .endif
 
+
 # The .for loop splits the items at whitespace, taking quotes into account,
-# just like the :M or :S variable modifiers.
-#
-# Until 2012-06-03, it had split the items exactly at whitespace, without
-# taking the quotes into account.  This had resulted in 10 words.
+# just like the :M or :S modifiers.
 #
+# Until 2012-06-03, the .for loop had split the items exactly at whitespace,
+# without taking the quotes into account.  This had resulted in 10 words.
 .undef WORDS
 .for var in one t\ w\ o "three three" 'four four' `five six`
 WORDS+=	counted
@@ -44,16 +49,19 @@ WORDS+=	counted
 .  error
 .endif
 
+
 # In the body of the .for loop, the iteration variables can be accessed
 # like normal variables, even though they are not really variables.
 #
-# Instead, the expression ${var} is transformed into ${:U1}, ${:U2} and so
-# on, before the loop body is evaluated.
+# Instead, before interpreting the body of the .for loop, the body is
+# generated by replacing each expression ${var} with ${:U1}, ${:U2} and so
+# on.
 #
-# A notable effect of this implementation technique is that the .for
+# A noticeable effect of this implementation technique is that the .for
 # iteration variables and the normal global variables live in separate
-# namespaces and do not influence each other.
-#
+# namespaces and do not influence each other.  The "scope" of the .for loop
+# variables is restricted to the current makefile, it does not each over to
+# any included makefiles.
 var=	value before
 var2=	value before
 .for var var2 in 1 2 3 4
@@ -66,9 +74,8 @@ var2=	value before
 .endif
 
 # Everything from the paragraph above also applies if the loop body is
-# empty, even if there is no actual iteration since the loop items are
-# also empty.
-#
+# empty.  In this particular example, the items to be iterated are empty as
+# well.
 var=	value before
 var2=	value before
 .for var var2 in ${:U}
@@ -82,11 +89,13 @@ var2=	value before
 
 # Until 2008-12-21, the values of the iteration variables were simply
 # inserted as plain text and then parsed as usual, which made it possible
-# to achieve all kinds of strange effects.
+# to achieve all kinds of strange effects, such as generating '.if'
+# directives or inserting '$' characters in random places, thereby changing
+# how following '$' are interpreted.
 #
-# Before that date, the .for loop expanded to:
+# Before that date, the .for loop below expanded to:
 #	EXPANSION+= value
-# Since that date, the .for loop expands to:
+# Since that date, the .for loop below expands to:
 #	EXPANSION${:U+}= value
 #
 EXPANSION=		before
@@ -102,13 +111,16 @@ EXPANSION${plus}=	value
 .endif
 
 # When the outer .for loop is expanded, it sees the expression ${i} and
-# expands it.  The inner loop then has nothing more to expand.
+# expands it.  The inner loop then only sees the expression ${:Uouter} and
+# has nothing more to expand.
 .for i in outer
 .  for i in inner
+# expect+1: outer
 .    info ${i}
 .  endfor
 .endfor
 
+
 # From https://gnats.netbsd.org/29985.
 #
 # Until 2008-12-21, the .for loop was expanded by replacing the variable
@@ -121,17 +133,13 @@ EXPANSION${plus}=	value
 # like "a:\ a:\file.txt" that ended in a single backslash.  Since then, the
 # variable values have been replaced with expressions of the form ${:U...},
 # which are not interpreted as code anymore.
-#
-# As of 2020-09-22, a comment in for.c says that it may be possible to
-# produce an "unwanted substitution", but there is no demonstration code yet.
-#
-# The above changes prevent a backslash at the end of a word from being
-# interpreted as part of the code.  Because of this, the trailingBackslash
-# hack in Var_Subst is no longer needed and as of 2020-09-22, has been
-# removed.
 .for path in a:\ a:\file.txt d:\\ d:\\file.txt
 .  info ${path}
 .endfor
+# expect-2: a:\ a:\file.txt
+# expect-3: d:\\
+# expect-4: d:\\file.txt
+
 
 # Ensure that braces and parentheses are properly escaped by the .for loop.
 # Each line must print the same word 3 times.
@@ -139,6 +147,18 @@ EXPANSION${plus}=	value
 .for v in ( [ { ) ] } (()) [[]] {{}} )( ][ }{
 .  info $v ${v} $(v)
 .endfor
+# expect-02: ( ( (
+# expect-03: [ [ [
+# expect-04: { { {
+# expect-05: ) ) )
+# expect-06: ] ] ]
+# expect-07: } } }
+# expect-08: (()) (()) (())
+# expect-09: [[]] [[]] [[]]
+# expect-10: {{}} {{}} {{}}
+# expect-11: )( )( )(
+# expect-12: ][ ][ ][
+# expect-13: }{ }{ }{
 
 # As of 2020-10-25, the variable names may contain arbitrary characters,
 # except for whitespace.  This allows for creative side effects. Hopefully
@@ -162,17 +182,20 @@ var=	outer
 
 
 # XXX: A parse error or evaluation error in the items of the .for loop
-# should skip the whole loop.  As of 2020-12-27, the loop is expanded twice.
+# should skip the whole loop.  As of 2023-05-09, the loop is expanded as
+# usual.
+# expect+1: Unknown modifier "Z"
 .for var in word1 ${:Uword2:Z} word3
 .  info XXX: Not reached ${var}
 .endfor
+# expect-2: XXX: Not reached word1
+# expect-3: XXX: Not reached word3
 
 
 # An empty list of variables to the left of the 'in' is a parse error.
 .for in value			# expect+0: no iteration variables in for
-# XXX: The loop body is evaluated once, even with the parse error above.
-.  error			# expect+0: Missing argument for ".error"
-.endfor				# expect+0: for-less endfor
+.  error
+.endfor
 
 # An empty list of iteration values to the right of the 'in' is accepted.
 # Unlike in the shell, it is not a parse error.

Index: src/usr.bin/make/unit-tests/directive-for-escape.mk
diff -u src/usr.bin/make/unit-tests/directive-for-escape.mk:1.17 src/usr.bin/make/unit-tests/directive-for-escape.mk:1.18
--- src/usr.bin/make/unit-tests/directive-for-escape.mk:1.17	Mon May  8 10:24:07 2023
+++ src/usr.bin/make/unit-tests/directive-for-escape.mk	Tue May  9 19:43:12 2023
@@ -1,9 +1,10 @@
-# $NetBSD: directive-for-escape.mk,v 1.17 2023/05/08 10:24:07 rillig Exp $
+# $NetBSD: directive-for-escape.mk,v 1.18 2023/05/09 19:43:12 rillig Exp $
 #
 # Test escaping of special characters in the iteration values of a .for loop.
 # These values get expanded later using the :U variable modifier, and this
-# escaping and unescaping must pass all characters and strings effectively
-# unmodified.
+# escaping and unescaping must pass all characters and strings unmodified.
+
+# expect-all
 
 .MAKEFLAGS: -df
 
@@ -12,12 +13,14 @@
 # This could be considered a bug.
 ASCII=	!"\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
 
+
 # XXX: As of 2020-12-31, the '#' is not preserved in the expanded body of
 # the loop.  Not only would it need the escaping for the variable modifier
 # ':U' but also the escaping for the line-end comment.
 .for chars in ${ASCII}
 .  info ${chars}
 .endfor
+# expect-2: !"
 
 # As of 2020-12-31, using 2 backslashes before be '#' would treat the '#'
 # as comment character.  Using 3 backslashes doesn't help either since
@@ -28,6 +31,7 @@ ASCII.2020-12-31=	!"\\\#$$%&'()*+,-./0-9
 .for chars in ${ASCII.2020-12-31}
 .  info ${chars}
 .endfor
+# expect-2: !"\\
 
 # Cover the code in ExprLen.
 #
@@ -42,6 +46,11 @@ VALUES=		$$ $${V} $${V:=-with-modifier} 
 .for i in ${VALUES}
 .  info $i
 .endfor
+# expect-2: $
+# expect-3: value
+# expect-4: value-with-modifier
+# expect-5: value
+# expect-6: value-with-modifier
 
 
 # Try to cover the code for nested '{}' in ExprLen, without success.
@@ -112,6 +121,7 @@ VALUES=		begin<$${UNDEF:Ufallback:N{{{}}
 .for i in ${VALUES}
 .  info $i
 .endfor
+# expect-2: begin<fallback>end
 
 # A single trailing dollar doesn't happen in practice.
 # The dollar sign is correctly passed through to the body of the .for loop.
@@ -120,6 +130,7 @@ VALUES=		begin<$${UNDEF:Ufallback:N{{{}}
 .for i in ${:U\$}
 .  info ${i}
 .endfor
+# expect-2: $
 
 # Before for.c 1.173 from 2023-05-08, the name of the iteration variable
 # could contain colons, which affected variable expressions having this exact
@@ -156,12 +167,23 @@ i,=		comma
 .  info .     $${i,}: ${i,}
 .  info .  adjacent: $i${i}${i:M*}$i
 .endfor
+# expect-11: .        $i: inner
+# expect-11: .      ${i}: inner
+# expect-11: .   ${i:M*}: inner
+# expect-11: .      $(i): inner
+# expect-11: .   $(i:M*): inner
+# expect-11: . ${i${:U}}: outer
+# expect-11: .    ${i\}}: inner}
+# expect-11: .     ${i2}: two
+# expect-11: .     ${i,}: comma
+# expect-11: .  adjacent: innerinnerinnerinner
 
 # Before for.c 1.173 from 2023-05-08, the variable name could be a single '$'
 # since there was no check on valid variable names.  ForLoop_SubstVarShort
 # skipped "stupid" variable names though, but ForLoop_SubstVarLong naively
 # parsed the body of the loop, substituting each '${$}' with an actual
 # '${:Udollar}'.
+# expect+1: invalid character '$' in .for loop variable name
 .for $ in dollar
 .  info eight $$$$$$$$ and no cents.
 .  info eight ${$}${$}${$}${$} and no cents.
@@ -173,6 +195,7 @@ i,=		comma
 # evaluates to an empty string.
 closing-brace=		}		# guard against an
 ${closing-brace}=	<closing-brace>	# alternative interpretation
+# expect+1: eight  and no cents.
 .info eight ${$}${$}${$}${$} and no cents.
 
 # What happens if the values from the .for loop contain a literal newline?
@@ -180,10 +203,18 @@ ${closing-brace}=	<closing-brace>	# alte
 # body of the .for loop, where it was then interpreted as a literal newline,
 # leading to syntax errors such as "Unclosed variable expression" in the upper
 # line and "Invalid line type" in the lower line.
+#
+# The error message occurs in the line of the .for loop since that's the place
+# where the body of the .for loop is constructed, and at this point the
+# newline character gets replaced with a plain space.
+# expect+2: newline in .for value
+# expect+1: newline in .for value
 .for i in "${.newline}"
 .  info short: $i
 .  info long: ${i}
 .endfor
+# expect-3: short: " "
+# expect-3: long: " "
 
 # No error since the newline character is not actually used.
 .for i in "${.newline}"
@@ -195,6 +226,7 @@ ${closing-brace}=	<closing-brace>	# alte
 # loop is assembled, and at that point, ForLoop.nextItem had already been
 # advanced.
 .MAKEFLAGS: -dp
+# expect+1: newline in .for value
 .for i in "${.newline}"
 : $i
 .endfor

Index: src/usr.bin/make/unit-tests/directive-for.exp
diff -u src/usr.bin/make/unit-tests/directive-for.exp:1.15 src/usr.bin/make/unit-tests/directive-for.exp:1.16
--- src/usr.bin/make/unit-tests/directive-for.exp:1.15	Mon May  8 10:24:07 2023
+++ src/usr.bin/make/unit-tests/directive-for.exp	Tue May  9 19:43:12 2023
@@ -1,38 +1,30 @@
-make: "directive-for.mk" line 108: outer
-make: "directive-for.mk" line 133: a:\ a:\file.txt
-make: "directive-for.mk" line 133: d:\\
-make: "directive-for.mk" line 133: d:\\file.txt
-make: "directive-for.mk" line 140: ( ( (
-make: "directive-for.mk" line 140: [ [ [
-make: "directive-for.mk" line 140: { { {
-make: "directive-for.mk" line 140: ) ) )
-make: "directive-for.mk" line 140: ] ] ]
-make: "directive-for.mk" line 140: } } }
-make: "directive-for.mk" line 140: (()) (()) (())
-make: "directive-for.mk" line 140: [[]] [[]] [[]]
-make: "directive-for.mk" line 140: {{}} {{}} {{}}
-make: "directive-for.mk" line 140: )( )( )(
-make: "directive-for.mk" line 140: ][ ][ ][
-make: "directive-for.mk" line 140: }{ }{ }{
-make: "directive-for.mk" line 148: invalid character ':' in .for loop variable name
-make: "directive-for.mk" line 149: <outer> <outer> <outer>
-make: "directive-for.mk" line 150: for-less endfor
-make: "directive-for.mk" line 154: invalid character '$' in .for loop variable name
-make: "directive-for.mk" line 155: <$> <> <>
-make: "directive-for.mk" line 156: for-less endfor
-make: "directive-for.mk" line 159: invalid character '$' in .for loop variable name
-make: "directive-for.mk" line 160: <> <> <>
-make: "directive-for.mk" line 161: for-less endfor
-make: "directive-for.mk" line 166: Unknown modifier "Z"
-make: "directive-for.mk" line 167: XXX: Not reached word1
-make: "directive-for.mk" line 167: XXX: Not reached word3
-make: "directive-for.mk" line 172: no iteration variables in for
-make: "directive-for.mk" line 174: Missing argument for ".error"
-make: "directive-for.mk" line 175: for-less endfor
-make: "directive-for.mk" line 199: 1 open conditional
-make: "directive-for.mk" line 215: for-less endfor
-make: "directive-for.mk" line 216: if-less endif
-make: "directive-for.mk" line 224: if-less endif
+make: "directive-for.mk" line 119: outer
+make: "directive-for.mk" line 137: a:\ a:\file.txt
+make: "directive-for.mk" line 137: d:\\
+make: "directive-for.mk" line 137: d:\\file.txt
+make: "directive-for.mk" line 148: ( ( (
+make: "directive-for.mk" line 148: [ [ [
+make: "directive-for.mk" line 148: { { {
+make: "directive-for.mk" line 148: ) ) )
+make: "directive-for.mk" line 148: ] ] ]
+make: "directive-for.mk" line 148: } } }
+make: "directive-for.mk" line 148: (()) (()) (())
+make: "directive-for.mk" line 148: [[]] [[]] [[]]
+make: "directive-for.mk" line 148: {{}} {{}} {{}}
+make: "directive-for.mk" line 148: )( )( )(
+make: "directive-for.mk" line 148: ][ ][ ][
+make: "directive-for.mk" line 148: }{ }{ }{
+make: "directive-for.mk" line 168: invalid character ':' in .for loop variable name
+make: "directive-for.mk" line 174: invalid character '$' in .for loop variable name
+make: "directive-for.mk" line 179: invalid character '$' in .for loop variable name
+make: "directive-for.mk" line 188: Unknown modifier "Z"
+make: "directive-for.mk" line 189: XXX: Not reached word1
+make: "directive-for.mk" line 189: XXX: Not reached word3
+make: "directive-for.mk" line 196: no iteration variables in for
+make: "directive-for.mk" line 222: 1 open conditional
+make: "directive-for.mk" line 238: for-less endfor
+make: "directive-for.mk" line 239: if-less endif
+make: "directive-for.mk" line 247: if-less endif
 For: new loop 2
 For: end for 2
 For: end for 1

Reply via email to