aboutsummaryrefslogtreecommitdiff
path: root/unit-tests
diff options
context:
space:
mode:
Diffstat (limited to 'unit-tests')
-rw-r--r--unit-tests/Makefile75
-rw-r--r--unit-tests/Makefile.config.in4
-rw-r--r--unit-tests/cond-cmp-numeric.exp4
-rw-r--r--unit-tests/cond-cmp-numeric.mk18
-rw-r--r--unit-tests/cond-cmp-string.mk4
-rw-r--r--unit-tests/cond-eof.exp3
-rw-r--r--unit-tests/cond-eof.mk12
-rw-r--r--unit-tests/cond-func-defined.mk4
-rw-r--r--unit-tests/cond-func-empty.exp4
-rw-r--r--unit-tests/cond-func-empty.mk73
-rw-r--r--unit-tests/cond-func.exp2
-rw-r--r--unit-tests/cond-op-and.exp5
-rw-r--r--unit-tests/cond-op-and.mk30
-rw-r--r--unit-tests/cond-op-or.exp5
-rw-r--r--unit-tests/cond-op-or.mk30
-rw-r--r--unit-tests/cond-op.exp34
-rw-r--r--unit-tests/cond-op.mk39
-rw-r--r--unit-tests/cond-short.mk69
-rw-r--r--unit-tests/cond-token-plain.exp27
-rw-r--r--unit-tests/cond-token-plain.mk35
-rw-r--r--unit-tests/deptgt-default.exp1
-rw-r--r--unit-tests/deptgt-default.mk17
-rw-r--r--unit-tests/deptgt-makeflags.mk29
-rw-r--r--unit-tests/directive-else.exp6
-rw-r--r--unit-tests/directive-endif.exp8
-rw-r--r--unit-tests/directive-export-impl.exp4
-rw-r--r--unit-tests/directive-for-escape.exp63
-rw-r--r--unit-tests/directive-for-escape.mk49
-rw-r--r--unit-tests/directive-for-if.exp8
-rw-r--r--unit-tests/directive-for-if.mk86
-rw-r--r--unit-tests/directive-for-null.exp2
-rwxr-xr-xunit-tests/directive-include.exp3
-rwxr-xr-xunit-tests/directive-include.mk24
-rw-r--r--unit-tests/export.mk4
-rw-r--r--unit-tests/job-output-null.exp6
-rw-r--r--unit-tests/job-output-null.mk21
-rwxr-xr-xunit-tests/lint.exp2
-rw-r--r--unit-tests/objdir-writable.exp2
-rw-r--r--unit-tests/objdir-writable.mk11
-rw-r--r--unit-tests/opt-debug-errors-jobs.exp10
-rw-r--r--unit-tests/opt-debug-errors-jobs.mk14
-rw-r--r--unit-tests/opt-debug-graph1.exp1
-rw-r--r--unit-tests/opt-debug-graph2.exp1
-rw-r--r--unit-tests/opt-debug-graph3.exp1
-rw-r--r--unit-tests/opt-file.mk12
-rw-r--r--unit-tests/opt-tracefile.exp11
-rw-r--r--unit-tests/opt-tracefile.mk18
-rw-r--r--unit-tests/suff-main-several.exp1
-rw-r--r--unit-tests/suff-transform-debug.exp1
-rw-r--r--unit-tests/var-eval-short.exp14
-rw-r--r--unit-tests/var-eval-short.mk18
-rw-r--r--unit-tests/var-op-expand.exp8
-rw-r--r--unit-tests/var-op-expand.mk105
-rw-r--r--unit-tests/vardebug.exp4
-rw-r--r--unit-tests/varmisc.mk12
-rw-r--r--unit-tests/varmod-assign.exp12
-rw-r--r--unit-tests/varmod-assign.mk107
-rw-r--r--unit-tests/varmod-defined.exp2
-rw-r--r--unit-tests/varmod-defined.mk5
-rw-r--r--unit-tests/varmod-gmtime.exp10
-rw-r--r--unit-tests/varmod-indirect.exp2
-rw-r--r--unit-tests/varmod-localtime.exp10
-rw-r--r--unit-tests/varmod-localtime.mk2
-rw-r--r--unit-tests/varmod-loop-delete.exp4
-rw-r--r--unit-tests/varmod-loop-delete.mk33
-rw-r--r--unit-tests/varmod-loop-varname.exp16
-rw-r--r--unit-tests/varmod-loop-varname.mk7
-rw-r--r--unit-tests/varmod-loop.exp6
-rw-r--r--unit-tests/varmod-loop.mk10
-rw-r--r--unit-tests/varmod-order-numeric.exp1
-rw-r--r--unit-tests/varmod-order-numeric.mk54
-rw-r--r--unit-tests/varmod-order-reverse.mk9
-rw-r--r--unit-tests/varmod-order-shuffle.mk19
-rw-r--r--unit-tests/varmod-order-string.exp1
-rw-r--r--unit-tests/varmod-order-string.mk28
-rw-r--r--unit-tests/varmod-order.exp25
-rw-r--r--unit-tests/varmod-order.mk91
-rw-r--r--unit-tests/varmod-root.exp10
-rw-r--r--unit-tests/varmod-root.mk37
-rw-r--r--unit-tests/varmod-select-words.mk5
-rw-r--r--unit-tests/varmod-subst.mk15
-rw-r--r--unit-tests/varmod-to-separator.exp4
-rw-r--r--unit-tests/varmod-unique.mk35
-rw-r--r--unit-tests/varname-dot-make-save_dollars.mk130
-rw-r--r--unit-tests/varname-dot-suffixes.exp39
-rw-r--r--unit-tests/varname-dot-suffixes.mk104
-rw-r--r--unit-tests/varname-empty.exp4
87 files changed, 1505 insertions, 386 deletions
diff --git a/unit-tests/Makefile b/unit-tests/Makefile
index 784223a56652..98ed3907cd5a 100644
--- a/unit-tests/Makefile
+++ b/unit-tests/Makefile
@@ -1,6 +1,6 @@
-# $Id: Makefile,v 1.148 2021/06/16 19:18:56 sjg Exp $
+# $Id: Makefile,v 1.164 2021/12/12 22:50:00 sjg Exp $
#
-# $NetBSD: Makefile,v 1.279 2021/06/16 09:39:48 rillig Exp $
+# $NetBSD: Makefile,v 1.288 2021/12/12 22:16:48 rillig Exp $
#
# Unit tests for make(1)
#
@@ -167,6 +167,7 @@ TESTS+= directive-for
TESTS+= directive-for-errors
TESTS+= directive-for-escape
TESTS+= directive-for-generating-endif
+TESTS+= directive-for-if
TESTS+= directive-for-lines
TESTS+= directive-for-null
TESTS+= directive-hyphen-include
@@ -351,13 +352,16 @@ TESTS+= varmod-indirect
TESTS+= varmod-l-name-to-value
TESTS+= varmod-localtime
TESTS+= varmod-loop
+TESTS+= varmod-loop-delete
TESTS+= varmod-loop-varname
TESTS+= varmod-match
TESTS+= varmod-match-escape
TESTS+= varmod-no-match
TESTS+= varmod-order
+TESTS+= varmod-order-numeric
TESTS+= varmod-order-reverse
TESTS+= varmod-order-shuffle
+TESTS+= varmod-order-string
TESTS+= varmod-path
TESTS+= varmod-quote
TESTS+= varmod-quote-dollar
@@ -415,6 +419,7 @@ TESTS+= varname-dot-parsedir
TESTS+= varname-dot-parsefile
TESTS+= varname-dot-path
TESTS+= varname-dot-shell
+TESTS+= varname-dot-suffixes
TESTS+= varname-dot-targets
TESTS+= varname-empty
TESTS+= varname-make
@@ -430,12 +435,41 @@ TESTS+= varparse-mod
TESTS+= varparse-undef-partial
TESTS+= varquote
+# for now at least
+.if ${.SHELL:T} == "ksh"
+BROKEN_TESTS+= sh-flags
+.endif
+.if ${.MAKE.OS:NDarwin} == ""
+BROKEN_TESTS+= shell-ksh
+.endif
+.if ${.MAKE.OS} == "SCO_SV"
+BROKEN_TESTS+= \
+ opt-debug-graph[23] \
+ varmod-localtime \
+ varmod-to-separator \
+
+.if ${.SHELL:T} == "bash"
+BROKEN_TESTS+= job-output-null
+.else
+BROKEN_TESTS+= \
+ cmd-interrupt \
+ job-flags \
+
+.endif
+.endif
+
+# Some tests just do not work on some platforms or environments
+# so allow for some filtering.
+.if !empty(BROKEN_TESTS)
+.warning Skipping broken tests: ${BROKEN_TESTS:O:u}
+TESTS:= ${TESTS:${BROKEN_TESTS:S,^,N,:ts:}}
+.endif
+
# Ideas for more tests:
# char-0020-space.mk
# char-005C-backslash.mk
# escape-cond-str.mk
# escape-cond-func-arg.mk
-# escape-cond-func-arg.mk
# escape-varmod.mk
# escape-varmod-define.mk
# escape-varmod-match.mk
@@ -457,7 +491,7 @@ ENV.envfirst= FROM_ENV=value-from-env
ENV.varmisc= FROM_ENV=env
ENV.varmisc+= FROM_ENV_BEFORE=env
ENV.varmisc+= FROM_ENV_AFTER=env
-ENV.varmod-localtime+= TZ=Europe/Berlin
+ENV.varmod-localtime+= TZ=${UTC_1:UEurope/Berlin}
ENV.varname-vpath+= VPATH=varname-vpath.dir:varname-vpath.dir2
# Override make flags for some of the tests; default is -k.
@@ -490,7 +524,10 @@ SED_CMDS.job-output-long-lines= \
${:D marker should always be at the beginning of the line. } \
-e '/^aa*--- job-b ---$$/d' \
-e '/^bb*--- job-a ---$$/d'
-SED_CMDS.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,'
+SED_CMDS.opt-chdir= -e 's,\(nonexistent\).[1-9][0-9]*,\1,' \
+ -e '/name/s,file,File,' \
+ -e 's,no such,No such,' \
+ -e 's,Filename,File name,'
SED_CMDS.opt-debug-graph1= ${STD_SED_CMDS.dg1}
SED_CMDS.opt-debug-graph2= ${STD_SED_CMDS.dg2}
SED_CMDS.opt-debug-graph3= ${STD_SED_CMDS.dg3}
@@ -511,11 +548,13 @@ SED_CMDS.sh-dots+= -e 's,^make: exec(\(.*\)) failed (.*)$$,<not found: \1>,'
SED_CMDS.sh-dots+= -e 's,^\(\*\*\* Error code \)[1-9][0-9]*,\1<nonzero>,'
SED_CMDS.sh-errctl= ${STD_SED_CMDS.dj}
SED_CMDS.sh-flags= ${STD_SED_CMDS.hide-from-output}
+SED_CMDS.shell-csh= ${STD_SED_CMDS.white-space}
SED_CMDS.suff-main+= ${STD_SED_CMDS.dg1}
SED_CMDS.suff-main-several+= ${STD_SED_CMDS.dg1}
SED_CMDS.suff-transform-debug+= ${STD_SED_CMDS.dg1}
SED_CMDS.var-op-shell+= ${STD_SED_CMDS.shell}
SED_CMDS.var-op-shell+= -e '/command/s,No such.*,not found,'
+SED_CMDS.var-op-shell+= ${STD_SED_CMDS.white-space}
SED_CMDS.vardebug+= -e 's,${.SHELL},</path/to/shell>,'
SED_CMDS.varmod-subst-regex+= ${STD_SED_CMDS.regex}
SED_CMDS.varname-dot-parsedir= -e '/in some cases/ s,^make: "[^"]*,make: "<normalized>,'
@@ -523,9 +562,7 @@ SED_CMDS.varname-dot-parsefile= -e '/in some cases/ s,^make: "[^"]*,make: "<norm
SED_CMDS.varname-dot-shell= -e 's, = /[^ ]*, = (details omitted),g'
SED_CMDS.varname-dot-shell+= -e 's,"/[^" ]*","(details omitted)",g'
SED_CMDS.varname-dot-shell+= -e 's,\[/[^] ]*\],[(details omitted)],g'
-SED_CMDS.varname-empty= -e 's,${.CURDIR},<curdir>,g'
-SED_CMDS.varname-empty+= -e '/\.PARSEDIR/d'
-SED_CMDS.varname-empty+= -e '/\.SHELL/d'
+SED_CMDS.varname-empty= ${.OBJDIR .PARSEDIR .PATH .SHELL:L:@v@-e '/\\$v/d'@}
# Some tests need an additional round of postprocessing.
POSTPROC.deptgt-suffixes= awk '/^\#\*\*\* Suffixes/,/^never-stop/'
@@ -541,7 +578,7 @@ unexport-env.rawout: export.mk
# Some standard sed commands, to be used in the SED_CMDS above.
# Omit details such as process IDs from the output of the -dg1 option.
-STD_SED_CMDS.dg1= -e 's,${.CURDIR}$$,<curdir>,'
+STD_SED_CMDS.dg1= -e '/\#.* \.$$/d'
STD_SED_CMDS.dg1+= -e '/\.MAKE.PATH_FILEMON/d'
STD_SED_CMDS.dg1+= -e '/^MAKE_VERSION/d;/^\#.*\/mk/d'
STD_SED_CMDS.dg1+= -e 's, ${DEFSYSPATH:U/usr/share/mk}$$, <defsyspath>,'
@@ -597,6 +634,8 @@ STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: line [0-9][0-9]*: ,,'
STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: [0-9][0-9]*: ,,'
STD_SED_CMDS.shell+= -e 's,^${.SHELL:T}: ,,'
+STD_SED_CMDS.white-space= -e 's, *, ,g' -e 's, *$$,,'
+
# The actual error messages for a failed regcomp or regexec differ between the
# implementations.
STD_SED_CMDS.regex= \
@@ -661,7 +700,8 @@ TMPDIR:= /tmp/uid${.MAKE.UID}
x!= echo; mkdir -p ${TMPDIR}
.endif
-MAKE_TEST_ENV?= MALLOC_OPTIONS="JA" # for jemalloc
+MAKE_TEST_ENV= MALLOC_OPTIONS="JA" # for jemalloc 100
+MAKE_TEST_ENV+= MALLOC_CONF="junk:true" # for jemalloc 510
MAKE_TEST_ENV+= TMPDIR=${TMPDIR}
.if ${.MAKE.OS} == "NetBSD"
@@ -697,13 +737,22 @@ _SED_CMDS+= -e 's,^usage: ${TEST_MAKE:T:S,.,\\.,g} ,usage: make ,'
# replace anything after 'stopped in' with unit-tests
_SED_CMDS+= -e '/stopped/s, /.*, unit-tests,'
_SED_CMDS+= -e 's,${TMPDIR},TMPDIR,g'
-# strip ${.CURDIR}/ from the output
-_SED_CMDS+= -e 's,${.CURDIR:S,.,\\.,g}/,,g'
+# canonicalize ${.OBJDIR} and ${.CURDIR}
+.if ${.OBJDIR} != ${.CURDIR}
+# yes this is inaccurate but none of the tests expect <objdir> anywhere
+# which we get depending on how MAKEOBJDIR is set.
+_SED_CMDS+= -e 's,${.OBJDIR},<curdir>,g'
+.endif
+_SED_CMDS+= -e 's,${.CURDIR},<curdir>,g'
+_SED_CMDS+= -e 's,<curdir>/,,g'
_SED_CMDS+= -e 's,${UNIT_TESTS:S,.,\\.,g}/,,g'
-# on AT&T derrived systems; false exits 255 not 1
+# on AT&T derived systems: false exits 255 not 1
.if ${.MAKE.OS:N*BSD} != ""
_SED_CMDS+= -e 's,\(Error code\) 255,\1 1,'
.endif
+.if ${.SHELL:T} == "ksh"
+_SED_CMDS+= -e '/^set [+-]v/d'
+.endif
.rawout.out:
@${TOOL_SED} ${_SED_CMDS} ${SED_CMDS.${.PREFIX:T}} \
diff --git a/unit-tests/Makefile.config.in b/unit-tests/Makefile.config.in
index 0fe24f08d2f9..3139a0d4d0b5 100644
--- a/unit-tests/Makefile.config.in
+++ b/unit-tests/Makefile.config.in
@@ -1,4 +1,6 @@
-# $Id: Makefile.config.in,v 1.1 2018/12/30 17:14:24 sjg Exp $
+# $Id: Makefile.config.in,v 1.3 2021/10/22 07:48:57 sjg Exp $
srcdir= @srcdir@
+TOOL_DIFF?= @diff@
DIFF_FLAGS?= @diff_u@
+UTC_1= @UTC_1@
diff --git a/unit-tests/cond-cmp-numeric.exp b/unit-tests/cond-cmp-numeric.exp
index 4a97b6879e7a..d10262aa8823 100644
--- a/unit-tests/cond-cmp-numeric.exp
+++ b/unit-tests/cond-cmp-numeric.exp
@@ -6,6 +6,10 @@ CondParser_Eval: !(${:UNaN} == NaN)
lhs = "NaN", rhs = "NaN", op = ==
CondParser_Eval: 123 ! 123
make: "cond-cmp-numeric.mk" line 34: Malformed conditional (123 ! 123)
+CondParser_Eval: ${:U 123} < 124
+lhs = 123.000000, rhs = 124.000000, op = <
+CondParser_Eval: ${:U123 } < 124
+make: "cond-cmp-numeric.mk" line 50: String comparison operator must be either == or !=
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-cmp-numeric.mk b/unit-tests/cond-cmp-numeric.mk
index b1ec3e719d47..b34e5bfc0a06 100644
--- a/unit-tests/cond-cmp-numeric.mk
+++ b/unit-tests/cond-cmp-numeric.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-numeric.mk,v 1.4 2020/11/08 22:56:16 rillig Exp $
+# $NetBSD: cond-cmp-numeric.mk,v 1.5 2021/07/29 06:31:18 rillig Exp $
#
# Tests for numeric comparisons in .if conditions.
@@ -37,5 +37,21 @@
. error
.endif
+# Leading spaces are allowed for numbers.
+# See EvalCompare and TryParseNumber.
+.if ${:U 123} < 124
+.else
+. error
+.endif
+
+# Trailing spaces are NOT allowed for numbers.
+# See EvalCompare and TryParseNumber.
+# expect+1: String comparison operator must be either == or !=
+.if ${:U123 } < 124
+. error
+.else
+. error
+.endif
+
all:
@:;
diff --git a/unit-tests/cond-cmp-string.mk b/unit-tests/cond-cmp-string.mk
index 9f3e731b2eb0..305a41099b98 100644
--- a/unit-tests/cond-cmp-string.mk
+++ b/unit-tests/cond-cmp-string.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-cmp-string.mk,v 1.14 2021/01/19 19:54:57 rillig Exp $
+# $NetBSD: cond-cmp-string.mk,v 1.15 2021/12/11 09:53:53 rillig Exp $
#
# Tests for string comparisons in .if conditions.
@@ -26,7 +26,7 @@
# starting point for variable expressions. Applying the :U modifier to such
# an undefined expression turns it into a defined expression.
#
-# See ApplyModifier_Defined and VEF_DEF.
+# See ApplyModifier_Defined and DEF_DEFINED.
.if ${:Ustr} != "str"
. error
.endif
diff --git a/unit-tests/cond-eof.exp b/unit-tests/cond-eof.exp
index 3b1e6eb1f056..3016a9b27805 100644
--- a/unit-tests/cond-eof.exp
+++ b/unit-tests/cond-eof.exp
@@ -1,8 +1,5 @@
-side effect
make: "cond-eof.mk" line 15: Malformed conditional (0 ${SIDE_EFFECT} ${SIDE_EFFECT2})
-side effect
make: "cond-eof.mk" line 17: Malformed conditional (1 ${SIDE_EFFECT} ${SIDE_EFFECT2})
-side effect
make: "cond-eof.mk" line 19: Malformed conditional ((0) ${SIDE_EFFECT} ${SIDE_EFFECT2})
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
diff --git a/unit-tests/cond-eof.mk b/unit-tests/cond-eof.mk
index 08f432bc4593..ddf4a4cd20c8 100644
--- a/unit-tests/cond-eof.mk
+++ b/unit-tests/cond-eof.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-eof.mk,v 1.2 2020/12/14 20:28:09 rillig Exp $
+# $NetBSD: cond-eof.mk,v 1.3 2021/12/10 23:12:44 rillig Exp $
#
# Tests for parsing conditions, especially the end of such conditions, which
# are represented as the token TOK_EOF.
@@ -7,11 +7,11 @@ SIDE_EFFECT= ${:!echo 'side effect' 1>&2!}
SIDE_EFFECT2= ${:!echo 'side effect 2' 1>&2!}
# In the following conditions, ${SIDE_EFFECT} is the position of the first
-# parse error. It is always fully evaluated, even if it were not necessary
-# to expand the variable expression. This is because these syntax errors are
-# an edge case that does not occur during normal operation, therefore there
-# is no need to optimize for this case, and it would slow down the common
-# case as well.
+# parse error. Before cond.c 1.286 from 2021-12-10, it was always fully
+# evaluated, even if it was not necessary to expand the variable expression.
+# These syntax errors are an edge case that does not occur during normal
+# operation. Still, it is easy to avoid evaluating these expressions, just in
+# case they have side effects.
.if 0 ${SIDE_EFFECT} ${SIDE_EFFECT2}
.endif
.if 1 ${SIDE_EFFECT} ${SIDE_EFFECT2}
diff --git a/unit-tests/cond-func-defined.mk b/unit-tests/cond-func-defined.mk
index 2aa49ccbf147..43db548a572b 100644
--- a/unit-tests/cond-func-defined.mk
+++ b/unit-tests/cond-func-defined.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-func-defined.mk,v 1.7 2020/11/15 14:07:53 rillig Exp $
+# $NetBSD: cond-func-defined.mk,v 1.8 2021/12/12 08:55:28 rillig Exp $
#
# Tests for the defined() function in .if conditions.
@@ -29,7 +29,7 @@ ${:UA B}= variable name with spaces
. error
.endif
-# Parse error: missing closing parenthesis; see ParseFuncArg.
+# Parse error: missing closing parenthesis; see ParseWord.
.if defined(DEF
. error
.else
diff --git a/unit-tests/cond-func-empty.exp b/unit-tests/cond-func-empty.exp
index 77a4edd47f49..d1dfda7c03ee 100644
--- a/unit-tests/cond-func-empty.exp
+++ b/unit-tests/cond-func-empty.exp
@@ -1,5 +1,5 @@
-make: "cond-func-empty.mk" line 152: Unclosed variable "WORD"
-make: "cond-func-empty.mk" line 152: Malformed conditional (empty(WORD)
+make: "cond-func-empty.mk" line 149: Unclosed variable "WORD"
+make: "cond-func-empty.mk" line 149: Malformed conditional (empty(WORD)
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-func-empty.mk b/unit-tests/cond-func-empty.mk
index 11a990cbbce1..25c850d23d93 100644
--- a/unit-tests/cond-func-empty.mk
+++ b/unit-tests/cond-func-empty.mk
@@ -1,10 +1,10 @@
-# $NetBSD: cond-func-empty.mk,v 1.14 2021/04/11 13:35:56 rillig Exp $
+# $NetBSD: cond-func-empty.mk,v 1.16 2021/12/11 10:41:31 rillig Exp $
#
# Tests for the empty() function in .if conditions, which tests a variable
# expression for emptiness.
#
-# Note that the argument in the parentheses is indeed a variable name,
-# optionally followed by variable modifiers.
+# Note that the argument in the parentheses is a variable name, not a variable
+# expression, optionally followed by variable modifiers.
#
.undef UNDEF
@@ -25,14 +25,10 @@ WORD= word
.endif
# The :S modifier replaces the empty value with an actual word. The
-# expression is now no longer empty, but it is still possible to see whether
-# the expression was based on an undefined variable. The expression has the
-# flag VEF_UNDEF.
-#
-# The expression does not have the flag VEF_DEF though, therefore it is still
-# considered undefined. Yes, indeed, undefined but not empty. There are a
-# few variable modifiers that turn an undefined expression into a defined
-# expression, among them :U and :D, but not :S.
+# expression is now no longer empty, but it is still based on an undefined
+# variable (DEF_UNDEF). There are a few variable modifiers that turn an
+# undefined expression into a defined expression, among them :U and :D, but
+# not :S.
#
# XXX: This is hard to explain to someone who doesn't know these
# implementation details.
@@ -41,19 +37,19 @@ WORD= word
. error
.endif
-# The :U modifier modifies expressions based on undefined variables
-# (DEF_UNDEF) by adding the DEF_DEFINED flag, which marks the expression
-# as "being interesting enough to be further processed".
+# The :U modifier changes the state of a previously undefined expression from
+# DEF_UNDEF to DEF_DEFINED. This marks the expression as "being interesting
+# enough to be further processed".
#
.if empty(UNDEF:S,^$,value,W:Ufallback)
. error
.endif
# And now to the surprising part. Applying the following :S modifier to the
-# undefined expression makes it non-empty, but the marker VEF_UNDEF is
-# preserved nevertheless. The :U modifier that follows only looks at the
-# VEF_UNDEF flag to decide whether the variable is defined or not. This kind
-# of makes sense since the :U modifier tests the _variable_, not the
+# undefined expression makes it non-empty, but the expression is still in
+# state DEF_UNDEF. The :U modifier that follows only looks at the state
+# DEF_UNDEF to decide whether the variable is defined or not. This kind of
+# makes sense since the :U modifier tests the _variable_, not the
# _expression_.
#
# But since the variable was undefined to begin with, the fallback value from
@@ -78,12 +74,13 @@ WORD= word
. error
.endif
-# The empty variable named "" gets a fallback value of " ", which counts as
-# empty.
+# The following example constructs an expression with the variable name ""
+# and the value " ". This expression counts as empty since the value contains
+# only whitespace.
#
# Contrary to the other functions in conditionals, the trailing space is not
# stripped off, as can be seen in the -dv debug log. If the space had been
-# stripped, it wouldn't make a difference in this case.
+# stripped, it wouldn't make a difference in this case, but in other cases.
#
.if !empty(:U )
. error
@@ -92,8 +89,8 @@ WORD= word
# Now the variable named " " gets a non-empty value, which demonstrates that
# neither leading nor trailing spaces are trimmed in the argument of the
# function. If the spaces were trimmed, the variable name would be "" and
-# that variable is indeed undefined. Since ParseEmptyArg calls Var_Parse
-# without VARE_UNDEFERR, the value of the undefined variable is
+# that variable is indeed undefined. Since CondParser_FuncCallEmpty calls
+# Var_Parse without VARE_UNDEFERR, the value of the undefined variable is
# returned as an empty string.
${:U }= space
.if empty( )
@@ -129,8 +126,8 @@ ${:U }= space
#
# If everything goes well, the argument expands to "WORD", and that variable
# is defined at the beginning of this file. The surrounding 'W' and 'D'
-# ensure that the parser in ParseEmptyArg has the correct position, both
-# before and after the call to Var_Parse.
+# ensure that CondParser_FuncCallEmpty keeps track of the parsing position,
+# both before and after the call to Var_Parse.
.if empty(W${:UOR}D)
. error
.endif
@@ -155,17 +152,29 @@ ${:U WORD }= variable name with spaces
. error
.endif
-# Between 2020-06-28 and var.c 1.226 from 2020-07-02, this paragraph generated
-# a wrong error message "Variable VARNAME is recursive".
+# Since cond.c 1.76 from 2020-06-28 and before var.c 1.226 from 2020-07-02,
+# the following example generated a wrong error message "Variable VARNAME is
+# recursive".
+#
+# Since at least 1993, the manual page claimed that irrelevant parts of
+# conditions were not evaluated, but that was wrong for a long time. The
+# expressions in irrelevant parts of the condition were actually evaluated,
+# they just allowed undefined variables to be used in the conditions, and the
+# result of evaluating them was not used further. These unnecessary
+# evaluations were fixed in several commits, starting with var.c 1.226 from
+# 2020-07-02.
#
-# The bug was that the !empty() condition was evaluated, even though this was
-# not necessary since the defined() condition already evaluated to false.
+# In this example, the variable "VARNAME2" is not defined, so evaluation of
+# the condition should have stopped at this point, and the rest of the
+# condition should have been processed in parse-only mode. The right-hand
+# side containing the '!empty' was evaluated though, as it had always been.
#
# When evaluating the !empty condition, the variable name was parsed as
# "VARNAME${:U2}", but without expanding any nested variable expression, in
-# this case the ${:U2}. Therefore, the variable name came out as simply
-# "VARNAME". Since this variable name should have been discarded quickly after
-# parsing it, this unrealistic variable name should have done no harm.
+# this case the ${:U2}. The expression '${:U2}' was replaced with an empty
+# string, the resulting variable name was thus "VARNAME". This conceptually
+# wrong variable name should have been discarded quickly after parsing it, to
+# prevent it from doing any harm.
#
# The variable expression was expanded though, and this was wrong. The
# expansion was done without VARE_WANTRES (called VARF_WANTRES back
diff --git a/unit-tests/cond-func.exp b/unit-tests/cond-func.exp
index 855b9e5210fd..8dc0f821a255 100644
--- a/unit-tests/cond-func.exp
+++ b/unit-tests/cond-func.exp
@@ -6,7 +6,7 @@ make: "cond-func.mk" line 102: A plain function name is parsed as !empty(...).
make: "cond-func.mk" line 109: A plain function name is parsed as !empty(...).
make: "cond-func.mk" line 119: Symbols may start with a function name.
make: "cond-func.mk" line 124: Symbols may start with a function name.
-make: "cond-func.mk" line 130: Malformed conditional (defined()
+make: "cond-func.mk" line 130: Missing closing parenthesis for defined()
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-op-and.exp b/unit-tests/cond-op-and.exp
index 173b6861a98b..cd6b03a8359e 100644
--- a/unit-tests/cond-op-and.exp
+++ b/unit-tests/cond-op-and.exp
@@ -1,4 +1,7 @@
-make: "cond-op-and.mk" line 43: Malformed conditional (0 &&& 0)
+make: "cond-op-and.mk" line 36: Malformed conditional (0 || (${DEF} && ${UNDEF}))
+make: "cond-op-and.mk" line 40: Malformed conditional (0 || (${UNDEF} && ${UNDEF}))
+make: "cond-op-and.mk" line 42: Malformed conditional (0 || (!${UNDEF} && ${UNDEF}))
+make: "cond-op-and.mk" line 71: Malformed conditional (0 &&& 0)
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-op-and.mk b/unit-tests/cond-op-and.mk
index 83c694f15723..83386ed77de4 100644
--- a/unit-tests/cond-op-and.mk
+++ b/unit-tests/cond-op-and.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op-and.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-op-and.mk,v 1.6 2021/12/10 19:14:35 rillig Exp $
#
# Tests for the && operator in .if conditions.
@@ -18,11 +18,39 @@
. error
.endif
+
# The right-hand side is not evaluated since the left-hand side is already
# false.
.if 0 && ${UNDEF}
.endif
+# When an outer condition makes the inner '&&' condition irrelevant, neither
+# of its operands must be evaluated.
+#
+.if 1 || (${UNDEF} && ${UNDEF})
+.endif
+
+# Test combinations of outer '||' with inner '&&', to ensure that the operands
+# of the inner '&&' are only evaluated if necessary.
+DEF= defined
+.if 0 || (${DEF} && ${UNDEF})
+.endif
+.if 0 || (!${DEF} && ${UNDEF})
+.endif
+.if 0 || (${UNDEF} && ${UNDEF})
+.endif
+.if 0 || (!${UNDEF} && ${UNDEF})
+.endif
+.if 1 || (${DEF} && ${UNDEF})
+.endif
+.if 1 || (!${DEF} && ${UNDEF})
+.endif
+.if 1 || (${UNDEF} && ${UNDEF})
+.endif
+.if 1 || (!${UNDEF} && ${UNDEF})
+.endif
+
+
# The && operator may be abbreviated as &. This is not widely known though
# and is also not documented in the manual page.
diff --git a/unit-tests/cond-op-or.exp b/unit-tests/cond-op-or.exp
index 7888a475e3e4..43b9a5438a31 100644
--- a/unit-tests/cond-op-or.exp
+++ b/unit-tests/cond-op-or.exp
@@ -1,4 +1,7 @@
-make: "cond-op-or.mk" line 43: Malformed conditional (0 ||| 0)
+make: "cond-op-or.mk" line 46: Malformed conditional (1 && (!${DEF} || ${UNDEF}))
+make: "cond-op-or.mk" line 48: Malformed conditional (1 && (${UNDEF} || ${UNDEF}))
+make: "cond-op-or.mk" line 50: Malformed conditional (1 && (!${UNDEF} || ${UNDEF}))
+make: "cond-op-or.mk" line 71: Malformed conditional (0 ||| 0)
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-op-or.mk b/unit-tests/cond-op-or.mk
index c6993e7c277e..0b7ac55e6c35 100644
--- a/unit-tests/cond-op-or.mk
+++ b/unit-tests/cond-op-or.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op-or.mk,v 1.6 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: cond-op-or.mk,v 1.8 2021/12/10 19:14:35 rillig Exp $
#
# Tests for the || operator in .if conditions.
@@ -18,11 +18,39 @@
. error
.endif
+
# The right-hand side is not evaluated since the left-hand side is already
# true.
.if 1 || ${UNDEF}
.endif
+# When an outer condition makes the inner '||' condition irrelevant, neither
+# of its operands must be evaluated. This had been wrong in cond.c 1.283 from
+# 2021-12-09 and was reverted in cond.c 1.284 an hour later.
+.if 0 && (!defined(UNDEF) || ${UNDEF})
+.endif
+
+# Test combinations of outer '&&' with inner '||', to ensure that the operands
+# of the inner '||' is only evaluated if necessary.
+DEF= defined
+.if 0 && (${DEF} || ${UNDEF})
+.endif
+.if 0 && (!${DEF} || ${UNDEF})
+.endif
+.if 0 && (${UNDEF} || ${UNDEF})
+.endif
+.if 0 && (!${UNDEF} || ${UNDEF})
+.endif
+.if 1 && (${DEF} || ${UNDEF})
+.endif
+.if 1 && (!${DEF} || ${UNDEF})
+.endif
+.if 1 && (${UNDEF} || ${UNDEF})
+.endif
+.if 1 && (!${UNDEF} || ${UNDEF})
+.endif
+
+
# The || operator may be abbreviated as |. This is not widely known though
# and is also not documented in the manual page.
diff --git a/unit-tests/cond-op.exp b/unit-tests/cond-op.exp
index 28e8d48e2697..b8f6a4301819 100644
--- a/unit-tests/cond-op.exp
+++ b/unit-tests/cond-op.exp
@@ -1,20 +1,22 @@
make: "cond-op.mk" line 50: Malformed conditional ("!word" == !word)
-make: "cond-op.mk" line 75: Malformed conditional (0 ${ERR::=evaluated})
-make: "cond-op.mk" line 79: After detecting a parse error, the rest is evaluated.
-make: "cond-op.mk" line 83: Parsing continues until here.
-make: "cond-op.mk" line 86: A B C => (A || B) && C A || B && C A || (B && C)
-make: "cond-op.mk" line 93: 0 0 0 => 0 0 0
-make: "cond-op.mk" line 93: 0 0 1 => 0 0 0
-make: "cond-op.mk" line 93: 0 1 0 => 0 0 0
-make: "cond-op.mk" line 93: 0 1 1 => 1 1 1
-make: "cond-op.mk" line 93: 1 0 0 => 0 1 1
-make: "cond-op.mk" line 93: 1 0 1 => 1 1 1
-make: "cond-op.mk" line 93: 1 1 0 => 0 1 1
-make: "cond-op.mk" line 93: 1 1 1 => 1 1 1
-make: "cond-op.mk" line 104: Malformed conditional (1 &&)
-make: "cond-op.mk" line 112: Malformed conditional (0 &&)
-make: "cond-op.mk" line 120: Malformed conditional (1 ||)
-make: "cond-op.mk" line 129: Malformed conditional (0 ||)
+make: "cond-op.mk" line 76: Malformed conditional (0 ${ERR::=evaluated})
+make: "cond-op.mk" line 80: A misplaced expression after 0 is not evaluated.
+make: "cond-op.mk" line 84: Malformed conditional (1 ${ERR::=evaluated})
+make: "cond-op.mk" line 88: A misplaced expression after 1 is not evaluated.
+make: "cond-op.mk" line 92: Parsing continues until here.
+make: "cond-op.mk" line 95: A B C => (A || B) && C A || B && C A || (B && C)
+make: "cond-op.mk" line 102: 0 0 0 => 0 0 0
+make: "cond-op.mk" line 102: 0 0 1 => 0 0 0
+make: "cond-op.mk" line 102: 0 1 0 => 0 0 0
+make: "cond-op.mk" line 102: 0 1 1 => 1 1 1
+make: "cond-op.mk" line 102: 1 0 0 => 0 1 1
+make: "cond-op.mk" line 102: 1 0 1 => 1 1 1
+make: "cond-op.mk" line 102: 1 1 0 => 0 1 1
+make: "cond-op.mk" line 102: 1 1 1 => 1 1 1
+make: "cond-op.mk" line 113: Malformed conditional (1 &&)
+make: "cond-op.mk" line 121: Malformed conditional (0 &&)
+make: "cond-op.mk" line 129: Malformed conditional (1 ||)
+make: "cond-op.mk" line 138: Malformed conditional (0 ||)
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-op.mk b/unit-tests/cond-op.mk
index 2ed451c90391..c3ab09f7709a 100644
--- a/unit-tests/cond-op.mk
+++ b/unit-tests/cond-op.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-op.mk,v 1.13 2021/01/19 18:20:30 rillig Exp $
+# $NetBSD: cond-op.mk,v 1.15 2021/12/10 23:12:44 rillig Exp $
#
# Tests for operators like &&, ||, ! in .if conditions.
#
@@ -58,25 +58,34 @@
. error
.endif
-# As soon as the parser sees the '$', it knows that the condition will
-# be malformed. Therefore there is no point in evaluating it.
+# In the following malformed conditions, as soon as the parser sees the '$'
+# after the '0' or the '1', it knows that the condition will be malformed.
+# Therefore there is no point in evaluating the misplaced expression.
#
-# As of 2021-01-20, that part of the condition is evaluated nevertheless,
-# since CondParser_Or just requests the next token, without restricting
-# the token to the expected tokens. If the parser were to restrict the
-# valid follow tokens for the token "0" to those that can actually produce
-# a correct condition (which in this case would be comparison operators,
-# TOK_AND, TOK_OR or TOK_RPAREN), the variable expression would not have
-# to be evaluated.
+# Before cond.c 1.286 from 2021-12-10, the extra expression was evaluated
+# nevertheless, since CondParser_Or and CondParser_And asked for the expanded
+# next token, even though in this position of the condition, only comparison
+# operators, TOK_AND, TOK_OR or TOK_RPAREN are allowed.
#
-# This would add a good deal of complexity to the code though, for almost
-# no benefit, especially since most expressions and conditions are side
-# effect free.
+#
+#
+#
+#
+#
+.undef ERR
.if 0 ${ERR::=evaluated}
. error
.endif
-.if ${ERR:Uundefined} == evaluated
-. info After detecting a parse error, the rest is evaluated.
+.if ${ERR:Uundefined} == undefined
+. info A misplaced expression after 0 is not evaluated.
+.endif
+
+.undef ERR
+.if 1 ${ERR::=evaluated}
+. error
+.endif
+.if ${ERR:Uundefined} == undefined
+. info A misplaced expression after 1 is not evaluated.
.endif
# Just in case that parsing should ever stop on the first error.
diff --git a/unit-tests/cond-short.mk b/unit-tests/cond-short.mk
index 113c3fd08fed..d41c38488fd6 100644
--- a/unit-tests/cond-short.mk
+++ b/unit-tests/cond-short.mk
@@ -1,4 +1,4 @@
-# $NetBSD: cond-short.mk,v 1.16 2021/03/14 11:49:37 rillig Exp $
+# $NetBSD: cond-short.mk,v 1.18 2021/12/12 09:49:09 rillig Exp $
#
# Demonstrates that in conditions, the right-hand side of an && or ||
# is only evaluated if it can actually influence the result.
@@ -12,7 +12,20 @@
# possible to skip evaluation of irrelevant variable expressions and only
# parse them. They were still evaluated though, the only difference to
# relevant variable expressions was that in the irrelevant variable
-# expressions, undefined variables were allowed.
+# expressions, undefined variables were allowed. This allowed for conditions
+# like 'defined(VAR) && ${VAR:S,from,to,} != ""', which no longer produced an
+# error message 'Malformed conditional', but it still evaluated the
+# expression, even though the expression was irrelevant.
+#
+# Since the initial commit on 1993-03-21, the manual page has been saying that
+# make 'will only evaluate a conditional as far as is necessary to determine',
+# but that was wrong. The code in cond.c 1.1 from 1993-03-21 looks good since
+# it calls Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree), but the
+# definition of Var_Parse does not call the third parameter 'doEval', as would
+# be expected, but instead 'err', accompanied by the comment 'TRUE if
+# undefined variables are an error'. This subtle difference between 'do not
+# evaluate at all' and 'allow undefined variables' led to the unexpected
+# evaluation.
#
# See also:
# var-eval-short.mk, for short-circuited variable modifiers
@@ -211,4 +224,56 @@ x!= echo '0 || $${iV2:U2} < $${V42}: $x' >&2; echo
. error
.endif
+
+# Ensure that irrelevant conditions do not influence the result of the whole
+# condition. As of cond.c 1.302 from 2021-12-11, an irrelevant function call
+# evaluates to true (see CondParser_FuncCall and CondParser_FuncCallEmpty), an
+# irrelevant comparison evaluates to false (see CondParser_Comparison).
+#
+# An irrelevant true bubbles up to the outermost CondParser_And, where it is
+# ignored. An irrelevant false bubbles up to the outermost CondParser_Or,
+# where it is ignored.
+#
+# If the condition parser should ever be restructured, the bubbling up of the
+# irrelevant evaluation results might show up accidentally. Prevent this.
+DEF= defined
+.undef UNDEF
+
+.if 0 && defined(DEF)
+. error
+.endif
+
+.if 1 && defined(DEF)
+.else
+. error
+.endif
+
+.if 0 && defined(UNDEF)
+. error
+.endif
+
+.if 1 && defined(UNDEF)
+. error
+.endif
+
+.if 0 || defined(DEF)
+.else
+. error
+.endif
+
+.if 1 || defined(DEF)
+.else
+. error
+.endif
+
+.if 0 || defined(UNDEF)
+. error
+.endif
+
+.if 1 || defined(UNDEF)
+.else
+. error
+.endif
+
+
all:
diff --git a/unit-tests/cond-token-plain.exp b/unit-tests/cond-token-plain.exp
index 24cfa6bcbc82..8afde2d41788 100644
--- a/unit-tests/cond-token-plain.exp
+++ b/unit-tests/cond-token-plain.exp
@@ -27,28 +27,35 @@ lhs = "var&&name", rhs = "var&&name", op = !=
CondParser_Eval: ${:Uvar}||name != "var||name"
lhs = "var||name", rhs = "var||name", op = !=
CondParser_Eval: bare
-make: "cond-token-plain.mk" line 102: A bare word is treated like defined(...), and the variable 'bare' is not defined.
+make: "cond-token-plain.mk" line 106: A bare word is treated like defined(...), and the variable 'bare' is not defined.
CondParser_Eval: VAR
-make: "cond-token-plain.mk" line 107: A bare word is treated like defined(...).
+make: "cond-token-plain.mk" line 111: A bare word is treated like defined(...).
CondParser_Eval: V${:UA}R
-make: "cond-token-plain.mk" line 114: ok
+make: "cond-token-plain.mk" line 118: ok
CondParser_Eval: V${UNDEF}AR
-make: "cond-token-plain.mk" line 122: Undefined variables in bare words expand to an empty string.
+make: "cond-token-plain.mk" line 126: Undefined variables in bare words expand to an empty string.
CondParser_Eval: 0${:Ux00}
-make: "cond-token-plain.mk" line 130: Numbers can be composed from literals and variable expressions.
-CondParser_Eval: 0${:Ux01}
make: "cond-token-plain.mk" line 134: Numbers can be composed from literals and variable expressions.
+CondParser_Eval: 0${:Ux01}
+make: "cond-token-plain.mk" line 138: Numbers can be composed from literals and variable expressions.
CondParser_Eval: "" ==
-make: "cond-token-plain.mk" line 140: Missing right-hand-side of operator '=='
+make: "cond-token-plain.mk" line 144: Missing right-hand side of operator '=='
CondParser_Eval: == ""
-make: "cond-token-plain.mk" line 148: Malformed conditional (== "")
+make: "cond-token-plain.mk" line 152: Malformed conditional (== "")
CondParser_Eval: \\
-make: "cond-token-plain.mk" line 163: The variable '\\' is not defined.
+make: "cond-token-plain.mk" line 167: The variable '\\' is not defined.
CondParser_Eval: \\
-make: "cond-token-plain.mk" line 168: Now the variable '\\' is defined.
+make: "cond-token-plain.mk" line 172: Now the variable '\\' is defined.
CondParser_Eval: "unquoted\"quoted" != unquoted"quoted
lhs = "unquoted"quoted", rhs = "unquoted"quoted", op = !=
CondParser_Eval: $$$$$$$$ != ""
+CondParser_Eval: left == right
+make: "cond-token-plain.mk" line 195: Malformed conditional (left == right)
+CondParser_Eval: ${0:?:} || left == right
+CondParser_Eval: 0
+make: "cond-token-plain.mk" line 201: Malformed conditional (${0:?:} || left == right)
+CondParser_Eval: left == right || ${0:?:}
+make: "cond-token-plain.mk" line 206: Malformed conditional (left == right || ${0:?:})
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/cond-token-plain.mk b/unit-tests/cond-token-plain.mk
index 89f2247e077c..3e59f48bc3c7 100644
--- a/unit-tests/cond-token-plain.mk
+++ b/unit-tests/cond-token-plain.mk
@@ -1,17 +1,18 @@
-# $NetBSD: cond-token-plain.mk,v 1.10 2021/01/21 14:08:09 rillig Exp $
+# $NetBSD: cond-token-plain.mk,v 1.14 2021/12/12 09:36:00 rillig Exp $
#
# Tests for plain tokens (that is, string literals without quotes)
-# in .if conditions.
+# in .if conditions. These are also called bare words.
.MAKEFLAGS: -dc
+# The word 'value' after the '!=' is a bare word.
.if ${:Uvalue} != value
. error
.endif
-# Malformed condition since comment parsing is done in an early phase
-# and removes the '#' and everything behind it long before the condition
-# parser gets to see it.
+# Using a '#' in a string literal in a condition leads to a malformed
+# condition since comment parsing is done in an early phase and removes the
+# '#' and everything after it long before the condition parser gets to see it.
#
# XXX: The error message is missing for this malformed condition.
# The right-hand side of the comparison is just a '"', before unescaping.
@@ -32,7 +33,10 @@
# in a very early parsing phase.
#
# See https://gnats.netbsd.org/19596 for example makefiles demonstrating the
-# original problems. This workaround is probably not needed anymore.
+# original problems. At that time, the parser didn't recognize the comment in
+# the line '.else # comment3'. This workaround is not needed anymore since
+# comments are stripped in an earlier phase. See "case '#'" in
+# CondParser_Token.
#
# XXX: Missing error message for the malformed condition. The right-hand
# side before unescaping is double-quotes, backslash, backslash.
@@ -152,7 +156,7 @@ VAR= defined
.endif
# The '\\' is not a line continuation. Neither is it an unquoted string
-# literal. Instead, it is parsed as a function argument (ParseFuncArg),
+# literal. Instead, it is parsed as a bare word (ParseWord),
# and in that context, the backslash is just an ordinary character. The
# function argument thus stays '\\' (2 backslashes). This string is passed
# to FuncDefined, and since there is no variable named '\\', the condition
@@ -185,6 +189,23 @@ ${:U\\\\}= backslash
. error
.endif
+# In a condition in an .if directive, the left-hand side must not be an
+# unquoted string literal.
+# expect+1: Malformed conditional (left == right)
+.if left == right
+.endif
+# Before cond.c 1.276 from 2021-09-21, a variable expression containing the
+# modifier ':?:' allowed unquoted string literals for the rest of the
+# condition. This was an unintended implementation mistake.
+# expect+1: Malformed conditional (${0:?:} || left == right)
+.if ${0:?:} || left == right
+.endif
+# This affected only the comparisons after the expression, so the following
+# was still a syntax error.
+# expect+1: Malformed conditional (left == right || ${0:?:})
+.if left == right || ${0:?:}
+.endif
+
# See cond-token-string.mk for similar tests where the condition is enclosed
# in "quotes".
diff --git a/unit-tests/deptgt-default.exp b/unit-tests/deptgt-default.exp
index 39a9383953dd..09fca899f063 100644
--- a/unit-tests/deptgt-default.exp
+++ b/unit-tests/deptgt-default.exp
@@ -1 +1,2 @@
+Default command is making 'not-a-target' from 'not-a-target'.
exit status 0
diff --git a/unit-tests/deptgt-default.mk b/unit-tests/deptgt-default.mk
index 814eaf72aed3..bf5f16536561 100644
--- a/unit-tests/deptgt-default.mk
+++ b/unit-tests/deptgt-default.mk
@@ -1,8 +1,17 @@
-# $NetBSD: deptgt-default.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: deptgt-default.mk,v 1.3 2021/12/01 23:56:29 rillig Exp $
#
-# Tests for the special target .DEFAULT in dependency declarations.
+# Tests for the special target .DEFAULT in dependency declarations, which
+# attaches its associated commands to all targets that don't specify any way
+# to create them.
-# TODO: Implementation
+all: test-default not-a-target
+
+test-default: .PHONY
+
+has-commands: .PHONY
+ @echo 'Making ${.TARGET} from ${.IMPSRC}.'
+
+.DEFAULT: dependency-is-ignored
+ @echo "Default command is making '${.TARGET}' from '${.IMPSRC}'."
all:
- @:;
diff --git a/unit-tests/deptgt-makeflags.mk b/unit-tests/deptgt-makeflags.mk
index 0a0f410e14c4..26f3f5794354 100644
--- a/unit-tests/deptgt-makeflags.mk
+++ b/unit-tests/deptgt-makeflags.mk
@@ -1,4 +1,4 @@
-# $NetBSD: deptgt-makeflags.mk,v 1.6 2020/11/15 20:20:58 rillig Exp $
+# $NetBSD: deptgt-makeflags.mk,v 1.7 2021/11/29 00:17:10 rillig Exp $
#
# Tests for the special target .MAKEFLAGS in dependency declarations,
# which adds command line options later, at parse time.
@@ -65,7 +65,7 @@
.endif
# Next try at defining another newline variable. Since whitespace around the
-# variable value is trimmed, two empty variable expressions surround the
+# variable value is trimmed, two empty variable expressions ${:U} surround the
# literal newline now. This prevents the newline from being skipped during
# parsing. The ':=' assignment operator expands the empty variable
# expressions, leaving only the newline as the variable value.
@@ -81,6 +81,31 @@
.endif
#.MAKEFLAGS: -d0
+# Now do the same for the other escape sequences; see Substring_Words.
+.MAKEFLAGS: CHAR_BS:="$${:U}\b$${:U}"
+.MAKEFLAGS: CHAR_FF:="$${:U}\f$${:U}"
+.MAKEFLAGS: CHAR_NL:="$${:U}\n$${:U}"
+.MAKEFLAGS: CHAR_CR:="$${:U}\r$${:U}"
+.MAKEFLAGS: CHAR_TAB:="$${:U}\t$${:U}"
+
+# Note: backspace is not whitespace, it is a control character.
+.if ${CHAR_BS:C,^[[:cntrl:]]$,found,W} != "found"
+. error
+.endif
+.if ${CHAR_FF:C,^[[:space:]]$,found,W} != "found"
+. error
+.endif
+.if ${CHAR_NL:C,^[[:space:]]$,found,W} != "found"
+. error
+.endif
+.if ${CHAR_CR:C,^[[:space:]]$,found,W} != "found"
+. error
+.endif
+.if ${CHAR_TAB:C,^[[:space:]]$,found,W} != "found"
+. error
+.endif
+
+
# Unbalanced quotes produce an error message. If they occur anywhere in the
# command line, the whole command line is skipped.
.MAKEFLAGS: VAR=previous
diff --git a/unit-tests/directive-else.exp b/unit-tests/directive-else.exp
index 138e893ffa88..17d5571ba74b 100644
--- a/unit-tests/directive-else.exp
+++ b/unit-tests/directive-else.exp
@@ -1,11 +1,11 @@
-make: "directive-else.mk" line 14: The .else directive does not take arguments.
+make: "directive-else.mk" line 14: The .else directive does not take arguments
make: "directive-else.mk" line 15: ok
make: "directive-else.mk" line 19: ok
-make: "directive-else.mk" line 21: The .else directive does not take arguments.
+make: "directive-else.mk" line 21: The .else directive does not take arguments
make: "directive-else.mk" line 26: if-less else
make: "directive-else.mk" line 32: ok
make: "directive-else.mk" line 33: warning: extra else
-make: "directive-else.mk" line 45: The .else directive does not take arguments.
+make: "directive-else.mk" line 45: The .else directive does not take arguments
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/directive-endif.exp b/unit-tests/directive-endif.exp
index 286d85244eae..0de1ecf0bf25 100644
--- a/unit-tests/directive-endif.exp
+++ b/unit-tests/directive-endif.exp
@@ -1,7 +1,7 @@
-make: "directive-endif.mk" line 18: The .endif directive does not take arguments.
-make: "directive-endif.mk" line 23: The .endif directive does not take arguments.
-make: "directive-endif.mk" line 33: The .endif directive does not take arguments.
-make: "directive-endif.mk" line 39: The .endif directive does not take arguments.
+make: "directive-endif.mk" line 18: The .endif directive does not take arguments
+make: "directive-endif.mk" line 23: The .endif directive does not take arguments
+make: "directive-endif.mk" line 33: The .endif directive does not take arguments
+make: "directive-endif.mk" line 39: The .endif directive does not take arguments
make: "directive-endif.mk" line 45: Unknown directive "endifx"
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
diff --git a/unit-tests/directive-export-impl.exp b/unit-tests/directive-export-impl.exp
index 740daa605129..152fc0de4063 100644
--- a/unit-tests/directive-export-impl.exp
+++ b/unit-tests/directive-export-impl.exp
@@ -7,7 +7,7 @@ Var_Parse: ${UT_VAR:N*} (eval-defined)
Var_Parse: ${REF}> (eval-defined)
Evaluating modifier ${UT_VAR:N...} on value "<>"
Pattern for ':N' is "*"
-ModifyWords: split "<>" into 1 words
+ModifyWords: split "<>" into 1 word
Result of ${UT_VAR:N*} is ""
ParseDependency(: )
CondParser_Eval: ${:!echo "\$UT_VAR"!} != "<>"
@@ -28,7 +28,7 @@ Var_Parse: ${UT_VAR:N*} (eval-defined)
Var_Parse: ${REF}> (eval-defined)
Evaluating modifier ${UT_VAR:N...} on value "<>"
Pattern for ':N' is "*"
-ModifyWords: split "<>" into 1 words
+ModifyWords: split "<>" into 1 word
Result of ${UT_VAR:N*} is ""
ParseDependency(: )
ParseReadLine (54): 'REF= defined'
diff --git a/unit-tests/directive-for-escape.exp b/unit-tests/directive-for-escape.exp
index 59d4c2324f15..492f82e16d1f 100644
--- a/unit-tests/directive-for-escape.exp
+++ b/unit-tests/directive-for-escape.exp
@@ -11,45 +11,45 @@ make: "directive-for-escape.mk" line 29: !"\\
For: end for 1
For: loop body:
. info ${:U\$}
-make: "directive-for-escape.mk" line 41: $
+make: "directive-for-escape.mk" line 43: $
For: loop body:
. info ${:U${V}}
-make: "directive-for-escape.mk" line 41: value
+make: "directive-for-escape.mk" line 43: value
For: loop body:
. info ${:U${V:=-with-modifier}}
-make: "directive-for-escape.mk" line 41: value-with-modifier
+make: "directive-for-escape.mk" line 43: value-with-modifier
For: loop body:
. info ${:U$(V)}
-make: "directive-for-escape.mk" line 41: value
+make: "directive-for-escape.mk" line 43: value
For: loop body:
. info ${:U$(V:=-with-modifier)}
-make: "directive-for-escape.mk" line 41: value-with-modifier
+make: "directive-for-escape.mk" line 43: value-with-modifier
For: end for 1
For: loop body:
. info ${:U\${UNDEF\:U\\$\\$}
-make: "directive-for-escape.mk" line 55: ${UNDEF:U\$
+make: "directive-for-escape.mk" line 57: ${UNDEF:U\$
For: loop body:
. info ${:U{{\}\}}
-make: "directive-for-escape.mk" line 55: {{}}
+make: "directive-for-escape.mk" line 57: {{}}
For: loop body:
. info ${:Uend\}}
-make: "directive-for-escape.mk" line 55: end}
+make: "directive-for-escape.mk" line 57: end}
For: end for 1
For: loop body:
. info ${:Ubegin<${UNDEF:Ufallback:N{{{}}}}>end}
-make: "directive-for-escape.mk" line 67: begin<fallback>end
+make: "directive-for-escape.mk" line 69: begin<fallback>end
For: end for 1
For: loop body:
. info ${:U\$}
-make: "directive-for-escape.mk" line 75: $
+make: "directive-for-escape.mk" line 77: $
For: end for 1
For: loop body:
. info ${NUMBERS} ${:Ureplaced}
-make: "directive-for-escape.mk" line 83: one two three replaced
+make: "directive-for-escape.mk" line 85: one two three replaced
For: end for 1
For: loop body:
. info ${:Ureplaced}
-make: "directive-for-escape.mk" line 93: replaced
+make: "directive-for-escape.mk" line 95: replaced
For: end for 1
For: loop body:
. info . $$i: ${:Uinner}
@@ -62,14 +62,31 @@ For: loop body:
. info . $${i2}: ${i2}
. info . $${i,}: ${i,}
. info . adjacent: ${:Uinner}${:Uinner}${:Uinner:M*}${:Uinner}
-make: "directive-for-escape.mk" line 101: . $i: inner
-make: "directive-for-escape.mk" line 102: . ${i}: inner
-make: "directive-for-escape.mk" line 103: . ${i:M*}: inner
-make: "directive-for-escape.mk" line 104: . $(i): inner
-make: "directive-for-escape.mk" line 105: . $(i:M*): inner
-make: "directive-for-escape.mk" line 106: . ${i${:U}}: outer
-make: "directive-for-escape.mk" line 107: . ${i\}}: inner}
-make: "directive-for-escape.mk" line 108: . ${i2}: two
-make: "directive-for-escape.mk" line 109: . ${i,}: comma
-make: "directive-for-escape.mk" line 110: . adjacent: innerinnerinnerinner
-exit status 0
+make: "directive-for-escape.mk" line 103: . $i: inner
+make: "directive-for-escape.mk" line 104: . ${i}: inner
+make: "directive-for-escape.mk" line 105: . ${i:M*}: inner
+make: "directive-for-escape.mk" line 106: . $(i): inner
+make: "directive-for-escape.mk" line 107: . $(i:M*): inner
+make: "directive-for-escape.mk" line 108: . ${i${:U}}: outer
+make: "directive-for-escape.mk" line 109: . ${i\}}: inner}
+make: "directive-for-escape.mk" line 110: . ${i2}: two
+make: "directive-for-escape.mk" line 111: . ${i,}: comma
+make: "directive-for-escape.mk" line 112: . adjacent: innerinnerinnerinner
+For: end for 1
+For: loop body:
+. info eight $$$$$$$$ and no cents.
+. info eight ${:Udollar}${:Udollar}${:Udollar}${:Udollar} and no cents.
+make: "directive-for-escape.mk" line 120: eight $$$$ and no cents.
+make: "directive-for-escape.mk" line 121: eight dollardollardollardollar and no cents.
+make: "directive-for-escape.mk" line 130: eight and no cents.
+For: end for 1
+make: "directive-for-escape.mk" line 137: newline in .for value
+make: "directive-for-escape.mk" line 137: newline in .for value
+For: loop body:
+. info short: ${:U" "}
+. info long: ${:U" "}
+make: "directive-for-escape.mk" line 138: short: " "
+make: "directive-for-escape.mk" line 139: long: " "
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-for-escape.mk b/unit-tests/directive-for-escape.mk
index babc4b8c6e88..725fa85d68c3 100644
--- a/unit-tests/directive-for-escape.mk
+++ b/unit-tests/directive-for-escape.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-for-escape.mk,v 1.7 2021/02/15 07:58:19 rillig Exp $
+# $NetBSD: directive-for-escape.mk,v 1.12 2021/12/05 11:40:03 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
@@ -13,8 +13,8 @@
ASCII= !"\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
# XXX: As of 2020-12-31, the '#' is not preserved in the expanded body of
-# the loop since it would not need only the escaping for the :U variable
-# modifier but also the escaping for the line-end comment.
+# 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
@@ -29,19 +29,21 @@ ASCII.2020-12-31= !"\\\#$$%&'()*+,-./0-9:;<=>?@A-Z[\]_^a-z{|}~
. info ${chars}
.endfor
-# Cover the code in for_var_len.
+# Cover the code in ExprLen.
#
# XXX: It is unexpected that the variable V gets expanded in the loop body.
-# The double '$$' should prevent exactly this. Probably nobody was
-# adventurous enough to use literal dollar signs in the values of a .for
-# loop.
+# The double '$$' should intuitively prevent exactly this. Probably nobody
+# was adventurous enough to use literal dollar signs in the values of a .for
+# loop, allowing this edge case to go unnoticed for years.
+#
+# See for.c, function ExprLen.
V= value
VALUES= $$ $${V} $${V:=-with-modifier} $$(V) $$(V:=-with-modifier)
.for i in ${VALUES}
. info $i
.endfor
-# Try to cover the code for nested '{}' in for_var_len, without success.
+# Try to cover the code for nested '{}' in ExprLen, without success.
#
# The value of the variable VALUES is not meant to be a variable expression.
# Instead, it is meant to represent literal text, the only escaping mechanism
@@ -55,9 +57,9 @@ VALUES= $${UNDEF:U\$$\$$ {{}} end}
. info $i
.endfor
-# Second try to cover the code for nested '{}' in for_var_len.
+# Second try to cover the code for nested '{}' in ExprLen.
#
-# XXX: It is wrong that for_var_len requires the braces to be balanced.
+# XXX: It is wrong that ExprLen requires the braces to be balanced.
# Each variable modifier has its own inconsistent way of parsing nested
# variable expressions, braces and parentheses. (Compare ':M', ':S', and
# ':D' for details.) The only sensible thing to do is therefore to let
@@ -110,4 +112,31 @@ i,= comma
. info . adjacent: $i${i}${i:M*}$i
.endfor
+# The variable name can be a single '$' since there is no check on valid
+# variable names. ForLoop_SubstVarShort skips "stupid" variable names though,
+# but ForLoop_SubstVarLong naively parses the body of the loop, substituting
+# each '${$}' with an actual 'dollar'.
+.for $ in dollar
+. info eight $$$$$$$$ and no cents.
+. info eight ${$}${$}${$}${$} and no cents.
+.endfor
+# Outside a .for loop, '${$}' is interpreted differently. The outer '$' starts
+# a variable expression. The inner '$' is followed by a '}' and is thus a
+# silent syntax error, the '$' is skipped. The variable name is thus '', and
+# since since there is never a variable named '', the whole expression '${$}'
+# evaluates to an empty string.
+closing-brace= } # guard against an
+${closing-brace}= <closing-brace> # alternative interpretation
+.info eight ${$}${$}${$}${$} and no cents.
+
+# What happens if the values from the .for loop contain a literal newline?
+# Before for.c 1.144 from 2021-06-25, the newline was passed verbatim to the
+# 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.
+.for i in "${.newline}"
+. info short: $i
+. info long: ${i}
+.endfor
+
all:
diff --git a/unit-tests/directive-for-if.exp b/unit-tests/directive-for-if.exp
new file mode 100644
index 000000000000..85bfc484856b
--- /dev/null
+++ b/unit-tests/directive-for-if.exp
@@ -0,0 +1,8 @@
+make: "directive-for-if.mk" line 48: if-less endif
+make: "directive-for-if.mk" line 48: if-less endif
+make: "directive-for-if.mk" line 48: if-less endif
+VAR1
+VAR3
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/directive-for-if.mk b/unit-tests/directive-for-if.mk
new file mode 100644
index 000000000000..8d73e8ae8c4d
--- /dev/null
+++ b/unit-tests/directive-for-if.mk
@@ -0,0 +1,86 @@
+# $NetBSD: directive-for-if.mk,v 1.1 2021/08/30 17:08:13 rillig Exp $
+#
+# Test for a .for directive that contains an .if directive.
+#
+# Before for.c 1.39 from 2008-12-21, when expanding the variables of a .for
+# loop, their values were placed verbatim in the expanded body. Since then,
+# each variable value expands to an expression of the form ${:Uvalue}.
+#
+# Before that change, the following adventurous code was possible:
+#
+# .for directive in if ifdef ifndef
+# . ${directive} "1" != "0"
+# . endif
+# .endfor
+#
+# A more practical usage of the .for loop that often led to surprises was the
+# following:
+#
+# .for var in VAR1 VAR2 VAR3
+# . if ${var} != "VAR2"
+# . endif
+# .endfor
+#
+# The .for loop body expanded to this string:
+#
+# . if VAR1 != "VAR2"
+# . endif
+#
+# Since bare words were not allowed at the left-hand side of a condition,
+# make complained about a "Malformed conditional", which was surprising since
+# the code before expanding the .for loop body looked quite well.
+#
+# In cond.c 1.48 from 2008-11-29, just a month before the expansion of .for
+# loops changed from plain textual value to using expressions of the form
+# ${:Uvalue}, this surprising behavior was documented in the code, and a
+# workaround was implemented that allowed bare words when they are followed
+# by either '!' or '=', as part of the operators '!=' or '=='.
+#
+# Since cond.c 1.68 from 2015-05-05, bare words are allowed on the left-hand
+# side of a condition, but that applies only to expression of the form
+# ${${cond} :? then : else}, it does not apply to conditions in ordinary .if
+# directives.
+
+# The following snippet worked in 2005, when the variables from the .for loop
+# expanded to their bare textual value.
+.for directive in if ifdef ifndef
+. ${directive} "1" != "0"
+. endif
+.endfor
+# In 2021, the above code does not generate an error message, even though the
+# code looks clearly malformed. This is due to the '!', which is interpreted
+# as a dependency operator, similar to ':' and '::'. The parser turns this
+# line into a dependency with the 3 targets '.', 'if', '"1"' and the 2 sources
+# '=' and '"0"'. Since that line is not interpreted as an '.if' directive,
+# the error message 'if-less endif' makes sense.
+
+# In 2005, make complained:
+#
+# .if line: Malformed conditional (VAR1 != "VAR2")
+# .endif line: if-less endif
+# .endif line: Need an operator
+#
+# 2008.11.30.22.37.55 does not complain about the left-hand side ${var}.
+.for var in VAR1 VAR2 VAR3
+. if ${var} != "VAR2"
+_!= echo "${var}" 1>&2; echo # In 2005, '.info' was not invented yet.
+. endif
+.endfor
+
+# Before for.c 1.39 from 2008-12-21, a common workaround was to surround the
+# variable expression from the .for loop with '"'. Such a string literal
+# has been allowed since cond.c 1.23 from 2004-04-13. Between that commit and
+# the one from 2008, the parser would still get confused if the value from the
+# .for loop contained '"', which was effectively a code injection.
+#
+# Surrounding ${var} with quotes disabled the check for typos though. For
+# ordinary variables, referring to an undefined variable on the left-hand side
+# of the comparison resulted in a "Malformed conditional". Since the .for
+# loop was usually close to the .if clause, this was not a problem in
+# practice.
+.for var in VAR1 VAR2 VAR3
+. if "${var}" != "VAR2"
+. endif
+.endfor
+
+all:
diff --git a/unit-tests/directive-for-null.exp b/unit-tests/directive-for-null.exp
index 37a7d68925ed..dee26de25e63 100644
--- a/unit-tests/directive-for-null.exp
+++ b/unit-tests/directive-for-null.exp
@@ -1,5 +1,5 @@
make: "(stdin)" line 2: Zero byte read from file
-make: "(stdin)" line 2: Unexpected end of file in for loop.
+make: "(stdin)" line 2: Unexpected end of file in .for loop
make: "(stdin)" line 3: Zero byte read from file
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
diff --git a/unit-tests/directive-include.exp b/unit-tests/directive-include.exp
index af56eefb2b88..b339f393e7e8 100755
--- a/unit-tests/directive-include.exp
+++ b/unit-tests/directive-include.exp
@@ -3,6 +3,9 @@ lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = !=
CondParser_Eval: ${.MAKE.MAKEFILES:T} != "${.PARSEFILE} null"
lhs = "directive-include.mk null", rhs = "directive-include.mk null", op = !=
make: "directive-include.mk" line 25: Could not find nonexistent.mk
+make: "directive-include.mk" line 47: Could not find "
+make: "directive-include.mk" line 52: Unknown modifier "Z"
+make: "directive-include.mk" line 52: Could not find nonexistent.mk
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/directive-include.mk b/unit-tests/directive-include.mk
index d36914b25a63..a6b300b3d273 100755
--- a/unit-tests/directive-include.mk
+++ b/unit-tests/directive-include.mk
@@ -1,4 +1,4 @@
-# $NetBSD: directive-include.mk,v 1.5 2020/11/21 14:59:11 rillig Exp $
+# $NetBSD: directive-include.mk,v 1.7 2021/12/03 22:48:07 rillig Exp $
#
# Tests for the .include directive, which includes another file.
@@ -30,5 +30,25 @@
# As of 2020-11-21, anything after the delimiter '"' is ignored.
.include "/dev/null" and ignore anything in the rest of the line.
+# The filename to be included can contain expressions.
+DEV= null
+.include "/dev/${DEV}"
+
+# Expressions in double quotes or angle quotes are first parsed naively, to
+# find the closing '"'. In a second step, the expressions are expanded. This
+# means that the expressions cannot include the characters '"' or '>'. This
+# restriction is not practically relevant since the expressions inside
+# '.include' directives are typically kept as simple as possible.
+#
+# If the whole line were expanded before parsing, the filename to be included
+# would be empty, and the closing '"' would be in the trailing part of the
+# line, which is ignored as of 2021-12-03.
+DQUOT= "
+.include "${DQUOT}"
+
+# When the expression in a filename cannot be evaluated, the failing
+# expression is skipped and the file is included nevertheless.
+# FIXME: Add proper error handling, no file must be included here.
+.include "nonexistent${:U123:Z}.mk"
+
all:
- @:;
diff --git a/unit-tests/export.mk b/unit-tests/export.mk
index 94e3a862dce1..bab08ee3ea23 100644
--- a/unit-tests/export.mk
+++ b/unit-tests/export.mk
@@ -1,4 +1,4 @@
-# $NetBSD: export.mk,v 1.10 2020/10/24 08:50:17 rillig Exp $
+# $NetBSD: export.mk,v 1.11 2021/12/05 14:57:36 rillig Exp $
UT_TEST= export
UT_FOO= foo${BAR}
@@ -40,7 +40,7 @@ BAR= bar is ${UT_FU}
.MAKE.EXPORTED+= UT_ZOO UT_TEST
-FILTER_CMD?= egrep -v '^(MAKEFLAGS|MALLOC_OPTIONS|PATH|PWD|SHLVL|_|&)='
+FILTER_CMD?= egrep -v '^(MAKEFLAGS|MALLOC_.*|PATH|PWD|SHLVL|_|&)='
all:
@env | ${FILTER_CMD} | sort
diff --git a/unit-tests/job-output-null.exp b/unit-tests/job-output-null.exp
index af9b4e64dba3..631d4862af44 100644
--- a/unit-tests/job-output-null.exp
+++ b/unit-tests/job-output-null.exp
@@ -1,4 +1,4 @@
-hello
-hello
-hello world without newline, hello world without newline, hello world without newline.
+1
+2a
+3a without newline, 3b without newline.
exit status 0
diff --git a/unit-tests/job-output-null.mk b/unit-tests/job-output-null.mk
index 7620bdf6a7ba..1efd9c667980 100644
--- a/unit-tests/job-output-null.mk
+++ b/unit-tests/job-output-null.mk
@@ -1,4 +1,4 @@
-# $NetBSD: job-output-null.mk,v 1.1 2021/04/15 19:02:29 rillig Exp $
+# $NetBSD: job-output-null.mk,v 1.3 2021/09/12 10:26:49 rillig Exp $
#
# Test how null bytes in the output of a command are handled. Make processes
# them using null-terminated strings, which may cut off some of the output.
@@ -7,20 +7,33 @@
# inconsistently. It's an edge case though since typically the child
# processes output text.
+# Note: The printf commands used in this test must only use a single format
+# string, without parameters. This is because it is implementation-dependent
+# how many times the command 'printf "fmt%s" "" "" ""' calls write(2).
+#
+# NetBSD /bin/sh 1 x write("fmtfmtfmt")
+# Dash 1 x write("fmtfmtfmt")
+# NetBSD /bin/ksh 3 x write("fmt") (via /bin/printf)
+# Bash 5 3 x write("fmt")
+#
+# In the latter case the output may arrive in parts, which in this test makes
+# a crucial difference since the outcome of the test depends on whether there
+# is a '\n' in each of the blocks from the output.
+
.MAKEFLAGS: -j1 # force jobs mode
all: .PHONY
# The null byte from the command output is kept as-is.
# See CollectOutput, which looks like it intended to replace these
# null bytes with simple spaces.
- @printf 'hello\0world%s\n' ''
+ @printf '1\0trailing\n'
# Give the parent process a chance to see the above output, but not
# yet the output from the next printf command.
@sleep 1
# All null bytes from the command output are kept as-is.
- @printf 'hello\0world%s\n' '' '' '' '' '' ''
+ @printf '2a\0trailing\n''2b\0trailing\n''2c\0trailing\n'
@sleep 1
@@ -29,4 +42,4 @@ all: .PHONY
#
# The three null bytes in a row test whether this output is
# compressed to a single space like in DebugFailedTarget. It isn't.
- @printf 'hello\0world\0without\0\0\0newline%s' ', ' ', ' '.'
+ @printf '3a\0without\0\0\0newline, 3b\0without\0\0\0newline.'
diff --git a/unit-tests/lint.exp b/unit-tests/lint.exp
index d7068b5e006a..db2290c040cd 100755
--- a/unit-tests/lint.exp
+++ b/unit-tests/lint.exp
@@ -1,4 +1,4 @@
-make: In the :@ modifier of "VAR", the variable name "${:Ubar:S,b,v,}" must not contain a dollar.
+make: In the :@ modifier of "VAR", the variable name "${:Ubar:S,b,v,}" must not contain a dollar
y@:Q}
xvaluey
exit status 2
diff --git a/unit-tests/objdir-writable.exp b/unit-tests/objdir-writable.exp
index 9c507f647f8c..e7298a66d369 100644
--- a/unit-tests/objdir-writable.exp
+++ b/unit-tests/objdir-writable.exp
@@ -1,5 +1,5 @@
make warning: TMPDIR/roobj: Permission denied.
-/tmp
+TMPDIR
TMPDIR/roobj
TMPDIR/roobj
exit status 0
diff --git a/unit-tests/objdir-writable.mk b/unit-tests/objdir-writable.mk
index 9fc1c69afb56..b09baa3c32b2 100644
--- a/unit-tests/objdir-writable.mk
+++ b/unit-tests/objdir-writable.mk
@@ -1,8 +1,9 @@
-# $NetBSD: objdir-writable.mk,v 1.4 2020/11/14 07:36:00 sjg Exp $
+# $NetBSD: objdir-writable.mk,v 1.5 2021/07/04 01:28:54 sjg Exp $
# test checking for writable objdir
-RO_OBJDIR?= ${TMPDIR:U/tmp}/roobj
+TMPDIR?= /tmp
+RO_OBJDIR?= ${TMPDIR}/roobj
.if make(do-objdir)
# this should succeed
@@ -20,12 +21,12 @@ rm-objdir:
@rmdir ${RO_OBJDIR}
no-objdir:
- @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR
+ @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C ${TMPDIR} -V .OBJDIR
ro-objdir:
- @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C /tmp -V .OBJDIR MAKE_OBJDIR_CHECK_WRITABLE=no
+ @MAKEOBJDIR=${RO_OBJDIR} ${.MAKE} -r -f /dev/null -C ${TMPDIR} -V .OBJDIR MAKE_OBJDIR_CHECK_WRITABLE=no
explicit-objdir:
- @MAKEOBJDIR=/tmp ${.MAKE} -r -f ${MAKEFILE:tA} -C /tmp do-objdir -V .OBJDIR
+ @MAKEOBJDIR=${TMPDIR} ${.MAKE} -r -f ${MAKEFILE:tA} -C ${TMPDIR} do-objdir -V .OBJDIR
.endif
diff --git a/unit-tests/opt-debug-errors-jobs.exp b/unit-tests/opt-debug-errors-jobs.exp
index 25eb2b470b72..c957c7736b32 100644
--- a/unit-tests/opt-debug-errors-jobs.exp
+++ b/unit-tests/opt-debug-errors-jobs.exp
@@ -24,6 +24,8 @@ line2
*** Failed target: fail-newline
*** Failed commands:
echo 'line1${.newline}line2'; false
+ => echo 'line1
+line2'; false
*** [fail-newline] Error code 1
make: stopped in unit-tests
@@ -45,4 +47,12 @@ word1 word2
*** [fail-multiline-intention] Error code 1
make: stopped in unit-tests
+
+*** Failed target: fail-vars
+*** Failed commands:
+ @${COMPILE_C} ${COMPILE_C_FLAGS}
+ => @false c-compiler flag1 -macro="several words"
+*** [fail-vars] Error code 1
+
+make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/opt-debug-errors-jobs.mk b/unit-tests/opt-debug-errors-jobs.mk
index 83b50987a752..007a5f37e08a 100644
--- a/unit-tests/opt-debug-errors-jobs.mk
+++ b/unit-tests/opt-debug-errors-jobs.mk
@@ -1,4 +1,4 @@
-# $NetBSD: opt-debug-errors-jobs.mk,v 1.1 2021/04/27 16:20:06 rillig Exp $
+# $NetBSD: opt-debug-errors-jobs.mk,v 1.2 2021/11/27 23:56:11 rillig Exp $
#
# Tests for the -de command line option, which adds debug logging for
# failed commands and targets; since 2021-04-27 also in jobs mode.
@@ -10,6 +10,7 @@ all: fail-escaped-space
all: fail-newline
all: fail-multiline
all: fail-multiline-intention
+all: fail-vars
fail-spaces:
echo '3 spaces'; false
@@ -34,3 +35,14 @@ fail-multiline:
fail-multiline-intention:
echo 'word1' \
'word2'; false
+
+# In makefiles that rely heavily on abstracted variables, it is not possible
+# to determine the actual command from the unexpanded command alone. To help
+# debugging these issues (for example in NetBSD's build.sh), output the
+# expanded command as well whenever it differs from the unexpanded command.
+# Since 2021-11-28.
+COMPILE_C= false c-compiler
+COMPILE_C_DEFS= macro="several words"
+COMPILE_C_FLAGS=flag1 ${COMPILE_C_DEFS:@def@-${def}@}
+fail-vars:
+ @${COMPILE_C} ${COMPILE_C_FLAGS}
diff --git a/unit-tests/opt-debug-graph1.exp b/unit-tests/opt-debug-graph1.exp
index 4d4aa0c3faea..4049900fee75 100644
--- a/unit-tests/opt-debug-graph1.exp
+++ b/unit-tests/opt-debug-graph1.exp
@@ -46,7 +46,6 @@ MFLAGS = -r -k -d g1
# Stats: 0 hits 2 misses 0 near misses 0 losers (0%)
# refs hits directory
# 1 0 <curdir>
-# 1 0 .
#*** Suffixes:
#*** Transformations:
diff --git a/unit-tests/opt-debug-graph2.exp b/unit-tests/opt-debug-graph2.exp
index 03f02719618e..675e5e8cac18 100644
--- a/unit-tests/opt-debug-graph2.exp
+++ b/unit-tests/opt-debug-graph2.exp
@@ -81,7 +81,6 @@ MFLAGS = -r -k -d g2
# Stats: 0 hits 4 misses 0 near misses 0 losers (0%)
# refs hits directory
# 1 0 <curdir>
-# 1 0 .
#*** Suffixes:
#*** Transformations:
diff --git a/unit-tests/opt-debug-graph3.exp b/unit-tests/opt-debug-graph3.exp
index f2966442eb26..78edb59e4e02 100644
--- a/unit-tests/opt-debug-graph3.exp
+++ b/unit-tests/opt-debug-graph3.exp
@@ -81,7 +81,6 @@ MFLAGS = -r -k -d g3
# Stats: 0 hits 4 misses 0 near misses 0 losers (0%)
# refs hits directory
# 1 0 <curdir>
-# 1 0 .
#*** Suffixes:
#*** Transformations:
diff --git a/unit-tests/opt-file.mk b/unit-tests/opt-file.mk
index b7a1c09e6d16..edeff4b9ab11 100644
--- a/unit-tests/opt-file.mk
+++ b/unit-tests/opt-file.mk
@@ -1,6 +1,7 @@
-# $NetBSD: opt-file.mk,v 1.12 2021/04/04 10:13:09 rillig Exp $
+# $NetBSD: opt-file.mk,v 1.14 2021/12/09 20:47:33 rillig Exp $
#
-# Tests for the -f command line option.
+# Tests for the -f command line option, which adds a makefile to the list of
+# files that are parsed.
# TODO: Implementation
@@ -10,7 +11,8 @@ all: file-ending-in-backslash-mmap
all: line-with-trailing-whitespace
all: file-containing-null-byte
-# Passing '-' as the filename reads from stdin. This is unusual but possible.
+# When the filename is '-', the input comes from stdin. This is unusual but
+# possible.
#
# In the unlikely case where a file ends in a backslash instead of a newline,
# that backslash is trimmed. See ParseGetLine.
@@ -19,7 +21,9 @@ all: file-containing-null-byte
# outside of the file buffer.
#
# printf '%s' 'VAR=value\' \
-# | MALLOC_OPTIONS=JA make-2014.01.01.00.00.00 -r -f - -V VAR -dA 2>&1 \
+# | MALLOC_OPTIONS="JA" \
+# MALLOC_CONF="junk:true" \
+# make-2014.01.01.00.00.00 -r -f - -V VAR -dA 2>&1 \
# | less
#
# The debug output shows how make happily uses freshly allocated memory (the
diff --git a/unit-tests/opt-tracefile.exp b/unit-tests/opt-tracefile.exp
index 39a9383953dd..0e815606d34f 100644
--- a/unit-tests/opt-tracefile.exp
+++ b/unit-tests/opt-tracefile.exp
@@ -1 +1,12 @@
+Making dependency1 from <nothing>.
+Making dependency2 from <nothing>.
+Making trace from dependency1 dependency2.
+0 BEG
+1 JOB
+1 DON
+1 JOB
+1 DON
+1 JOB
+1 DON
+0 END
exit status 0
diff --git a/unit-tests/opt-tracefile.mk b/unit-tests/opt-tracefile.mk
index b62392ca913c..291824680606 100644
--- a/unit-tests/opt-tracefile.mk
+++ b/unit-tests/opt-tracefile.mk
@@ -1,8 +1,16 @@
-# $NetBSD: opt-tracefile.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: opt-tracefile.mk,v 1.5 2021/12/06 22:35:20 rillig Exp $
#
-# Tests for the -T command line option.
+# Tests for the command line option '-T', which in jobs mode appends a trace
+# record to a trace log whenever a job is started or completed.
-# TODO: Implementation
+all: .PHONY
+ @rm -f opt-tracefile.log
+ @${MAKE} -f ${MAKEFILE} -j1 -Topt-tracefile.log trace
+ # Remove timestamps, process IDs and directory paths.
+ @awk '{ print $$2, $$3 }' opt-tracefile.log
+ @rm opt-tracefile.log
-all:
- @:;
+trace dependency1 dependency2: .PHONY
+ @echo 'Making ${.TARGET} from ${.ALLSRC:S,^$,<nothing>,W}.'
+
+trace: dependency1 dependency2
diff --git a/unit-tests/suff-main-several.exp b/unit-tests/suff-main-several.exp
index 09fa6d63bffa..b20f5ecf1143 100644
--- a/unit-tests/suff-main-several.exp
+++ b/unit-tests/suff-main-several.exp
@@ -111,7 +111,6 @@ MFLAGS = -r -k -d mps -d 0 -d g1
# Stats: 0 hits 2 misses 0 near misses 0 losers (0%)
# refs hits directory
# 1 0 <curdir>
-# 1 0 .
#*** Suffixes:
# ".4" (num 1, ref 1)
diff --git a/unit-tests/suff-transform-debug.exp b/unit-tests/suff-transform-debug.exp
index 0634ff616d0d..7fec51a1de9d 100644
--- a/unit-tests/suff-transform-debug.exp
+++ b/unit-tests/suff-transform-debug.exp
@@ -37,7 +37,6 @@ MFLAGS = -r -k -d g1
# Stats: 0 hits 2 misses 0 near misses 0 losers (0%)
# refs hits directory
# 1 0 <curdir>
-# 1 0 .
#*** Suffixes:
# ".a" (num 1, ref 2)
diff --git a/unit-tests/var-eval-short.exp b/unit-tests/var-eval-short.exp
index ae0aff7d7c2c..c476c93125e3 100644
--- a/unit-tests/var-eval-short.exp
+++ b/unit-tests/var-eval-short.exp
@@ -1,16 +1,16 @@
-make: "var-eval-short.mk" line 41: In the :@ modifier of "", the variable name "${FAIL}" must not contain a dollar.
+make: "var-eval-short.mk" line 41: In the :@ modifier of "", the variable name "${FAIL}" must not contain a dollar
make: "var-eval-short.mk" line 41: Malformed conditional (0 && ${:Uword:@${FAIL}@expr@})
-make: "var-eval-short.mk" line 79: Invalid time value: ${FAIL}}
-make: "var-eval-short.mk" line 79: Malformed conditional (0 && ${:Uword:gmtime=${FAIL}})
-make: "var-eval-short.mk" line 93: Invalid time value: ${FAIL}}
-make: "var-eval-short.mk" line 93: Malformed conditional (0 && ${:Uword:localtime=${FAIL}})
+make: "var-eval-short.mk" line 81: Invalid time value at "${FAIL}}"
+make: "var-eval-short.mk" line 81: Malformed conditional (0 && ${:Uword:gmtime=${FAIL}})
+make: "var-eval-short.mk" line 95: Invalid time value at "${FAIL}}"
+make: "var-eval-short.mk" line 95: Malformed conditional (0 && ${:Uword:localtime=${FAIL}})
CondParser_Eval: 0 && ${0:?${FAIL}then:${FAIL}else}
Var_Parse: ${0:?${FAIL}then:${FAIL}else} (parse-only)
Parsing modifier ${0:?...}
Modifier part: "${FAIL}then"
Modifier part: "${FAIL}else"
Result of ${0:?${FAIL}then:${FAIL}else} is "" (parse-only, defined)
-ParseReadLine (158): 'DEFINED= defined'
+ParseReadLine (160): 'DEFINED= defined'
Global: DEFINED = defined
CondParser_Eval: 0 && ${DEFINED:L:?${FAIL}then:${FAIL}else}
Var_Parse: ${DEFINED:L:?${FAIL}then:${FAIL}else} (parse-only)
@@ -20,7 +20,7 @@ Parsing modifier ${DEFINED:?...}
Modifier part: "${FAIL}then"
Modifier part: "${FAIL}else"
Result of ${DEFINED:?${FAIL}then:${FAIL}else} is "defined" (parse-only, regular)
-ParseReadLine (161): '.MAKEFLAGS: -d0'
+ParseReadLine (163): '.MAKEFLAGS: -d0'
ParseDependency(.MAKEFLAGS: -d0)
Global: .MAKEFLAGS = -r -k -d cpv -d
Global: .MAKEFLAGS = -r -k -d cpv -d 0
diff --git a/unit-tests/var-eval-short.mk b/unit-tests/var-eval-short.mk
index 41782f0d7823..1b9ccc714736 100644
--- a/unit-tests/var-eval-short.mk
+++ b/unit-tests/var-eval-short.mk
@@ -1,4 +1,4 @@
-# $NetBSD: var-eval-short.mk,v 1.5 2021/04/04 13:35:26 rillig Exp $
+# $NetBSD: var-eval-short.mk,v 1.7 2021/09/07 20:41:58 rillig Exp $
#
# Tests for each variable modifier to ensure that they only do the minimum
# necessary computations. If the result of the expression is not needed, they
@@ -44,13 +44,13 @@ FAIL= ${:!echo unexpected 1>&2!}
.if 0 && ${:Uword:@var@${FAIL}@}
.endif
-# Before var.c,v 1.877 from 2021-03-14, the modifier ':[...]' did not expand
+# Before var.c 1.877 from 2021-03-14, the modifier ':[...]' did not expand
# the nested expression ${FAIL} and then tried to parse the unexpanded text,
# which failed since '$' is not a valid range character.
.if 0 && ${:Uword:[${FAIL}]}
.endif
-# Before var.c,v 1.867 from 2021-03-14, the modifier ':_' defined the variable
+# Before var.c 1.867 from 2021-03-14, the modifier ':_' defined the variable
# even though the whole expression should have only been parsed, not
# evaluated.
.if 0 && ${:Uword:_=VAR}
@@ -58,11 +58,13 @@ FAIL= ${:!echo unexpected 1>&2!}
. error
.endif
-# Before var.c,v 1.856 from 2021-03-14, the modifier ':C' did not expand the
-# nested expression ${FAIL} and then tried to compile the unexpanded text as a
-# regular expression, which failed both because of the '{FAIL}', which is not
-# a valid repetition, and because of the '****', which are repeated
-# repetitions as well.
+# Before var.c 1.856 from 2021-03-14, the modifier ':C' did not expand the
+# nested expression ${FAIL}, which is correct, and then tried to compile the
+# unexpanded text as a regular expression, which is unnecessary since the
+# right-hand side of the '&&' cannot influence the outcome of the condition.
+# Compiling the regular expression then failed both because of the '{FAIL}',
+# which is not a valid repetition of the form '{1,5}', and because of the
+# '****', which are repeated repetitions as well.
# '${FAIL}'
.if 0 && ${:Uword:C,${FAIL}****,,}
.endif
diff --git a/unit-tests/var-op-expand.exp b/unit-tests/var-op-expand.exp
index 39a9383953dd..a4ba53942cf7 100644
--- a/unit-tests/var-op-expand.exp
+++ b/unit-tests/var-op-expand.exp
@@ -1 +1,7 @@
-exit status 0
+make: "var-op-expand.mk" line 265: Unknown modifier "s,value,replaced,"
+make: "var-op-expand.mk" line 268: warning: XXX Neither branch should be taken.
+make: "var-op-expand.mk" line 273: Unknown modifier "s,value,replaced,"
+make: "var-op-expand.mk" line 274: warning: XXX Neither branch should be taken.
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/var-op-expand.mk b/unit-tests/var-op-expand.mk
index ff62668a8ada..237f7baf1c62 100644
--- a/unit-tests/var-op-expand.mk
+++ b/unit-tests/var-op-expand.mk
@@ -1,8 +1,14 @@
-# $NetBSD: var-op-expand.mk,v 1.11 2021/01/01 23:07:48 sjg Exp $
+# $NetBSD: var-op-expand.mk,v 1.15 2021/11/30 23:52:19 rillig Exp $
#
# Tests for the := variable assignment operator, which expands its
# right-hand side.
+#
+# See also:
+# varname-dot-make-save_dollars.mk
+# Force the test results to be independent of the default value of this
+# setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake
+# distribution and pkgsrc/devel/bmake.
.MAKE.SAVE_DOLLARS:= yes
# If the right-hand side does not contain a dollar sign, the ':=' assignment
@@ -174,5 +180,102 @@ VAR_SUBST_${UNDEF}:= assigned by ':='
. error
.endif
+
+# The following test case demonstrates that the variable 'LATER' is preserved
+# in the ':=' assignment since the variable 'LATER' is not yet defined.
+# After the assignment to 'LATER', evaluating the variable 'INDIRECT'
+# evaluates 'LATER' as well.
+#
+.undef LATER
+INDIRECT:= ${LATER:S,value,replaced,}
+.if ${INDIRECT} != ""
+. error
+.endif
+LATER= late-value
+.if ${INDIRECT} != "late-replaced"
+. error
+.endif
+
+
+# Same as the test case above, except for the additional modifier ':tl' when
+# evaluating the variable 'INDIRECT'. Nothing surprising here.
+.undef LATER
+.undef later
+INDIRECT:= ${LATER:S,value,replaced,}
+.if ${INDIRECT:tl} != ""
+. error
+.endif
+LATER= uppercase-value
+later= lowercase-value
+.if ${INDIRECT:tl} != "uppercase-replaced"
+. error
+.endif
+
+
+# Similar to the two test cases above, the situation gets a bit more involved
+# here, due to the double indirection. The variable 'indirect' is supposed to
+# be the lowercase version of the variable 'INDIRECT'.
+#
+# The assignment operator ':=' for the variable 'INDIRECT' could be a '=' as
+# well, it wouldn't make a difference in this case. The crucial detail is the
+# assignment operator ':=' for the variable 'indirect'. During this
+# assignment, the variable modifier ':S,value,replaced,' is converted to
+# lowercase, which turns 'S' into 's', thus producing an unknown modifier.
+# In this case, make issues a warning, but in cases where the modifier
+# includes a '=', the modifier would be interpreted as a SysV-style
+# substitution like '.c=.o', and make would not issue a warning, leading to
+# silent unexpected behavior.
+#
+# As of 2021-11-20, the actual behavior is unexpected. Fixing it is not
+# trivial. When the assignment to 'indirect' takes place, the expressions
+# from the nested expression could be preserved, like this:
+#
+# Start with:
+#
+# indirect:= ${INDIRECT:tl}
+#
+# Since INDIRECT is defined, expand it, remembering that the modifier
+# ':tl' must still be applied to the final result.
+#
+# indirect:= ${LATER:S,value,replaced,} \
+# OK \
+# ${LATER:value=sysv}
+#
+# The variable 'LATER' is not defined. An idea may be to append the
+# remaining modifier ':tl' to each expression that is starting with an
+# undefined variable, resulting in:
+#
+# indirect:= ${LATER:S,value,replaced,:tl} \
+# OK \
+# ${LATER:value=sysv:tl}
+#
+# This would work for the first expression. The second expression ends
+# with the SysV modifier ':from=to', and when this modifier is parsed,
+# it consumes all characters until the end of the expression, which in
+# this case would replace the suffix 'value' with the literal 'sysv:tl',
+# ignoring that the ':tl' was intended to be an additional modifier.
+#
+# Due to all of this, this surprising behavior is not easy to fix.
+#
+.undef LATER
+.undef later
+INDIRECT:= ${LATER:S,value,replaced,} OK ${LATER:value=sysv}
+indirect:= ${INDIRECT:tl}
+# expect+1: Unknown modifier "s,value,replaced,"
+.if ${indirect} != " ok "
+. error
+.else
+. warning XXX Neither branch should be taken.
+.endif
+LATER= uppercase-value
+later= lowercase-value
+# expect+1: Unknown modifier "s,value,replaced,"
+.if ${indirect} != "uppercase-replaced ok uppercase-sysv"
+. warning XXX Neither branch should be taken.
+.else
+. error
+.endif
+
+
all:
@:;
diff --git a/unit-tests/vardebug.exp b/unit-tests/vardebug.exp
index 6d00acc977af..3519bbd0ba1b 100644
--- a/unit-tests/vardebug.exp
+++ b/unit-tests/vardebug.exp
@@ -43,11 +43,11 @@ Result of ${:Uvalue} is "value" (eval-defined, defined)
Indirect modifier "M*e" from "${:UM*e}"
Evaluating modifier ${:M...} on value "value" (eval-defined, defined)
Pattern for ':M' is "*e"
-ModifyWords: split "value" into 1 words
+ModifyWords: split "value" into 1 word
Result of ${:M*e} is "value" (eval-defined, defined)
Evaluating modifier ${:M...} on value "value" (eval-defined, defined)
Pattern for ':M' is "valu[e]"
-ModifyWords: split "value" into 1 words
+ModifyWords: split "value" into 1 word
Result of ${:Mvalu[e]} is "value" (eval-defined, defined)
Global:delete VAR
Var_Parse: ${:Uvariable:unknown} (eval-defined)
diff --git a/unit-tests/varmisc.mk b/unit-tests/varmisc.mk
index e5ab375d8f39..81818f3fb8bb 100644
--- a/unit-tests/varmisc.mk
+++ b/unit-tests/varmisc.mk
@@ -1,5 +1,5 @@
-# $Id: varmisc.mk,v 1.23 2021/02/05 20:02:30 sjg Exp $
-# $NetBSD: varmisc.mk,v 1.30 2021/02/04 21:42:47 rillig Exp $
+# $Id: varmisc.mk,v 1.25 2021/12/07 00:03:11 sjg Exp $
+# $NetBSD: varmisc.mk,v 1.32 2021/12/05 10:02:51 rillig Exp $
#
# Miscellaneous variable tests.
@@ -66,7 +66,7 @@ cmpv:
@echo Literal=3.4.5 == ${3.4.5:L:${M_cmpv}}
@echo We have ${${.TARGET:T}.only}
-# catch misshandling of nested vars in .for loop
+# catch mishandling of nested variables in .for loop
MAN=
MAN1= make.1
.for s in 1 2
@@ -78,12 +78,15 @@ MAN+= ${MAN$s}
manok:
@echo MAN=${MAN}
+# Test parsing of boolean values.
# begin .MAKE.SAVE_DOLLARS; see Var_SetWithFlags and ParseBoolean.
SD_VALUES= 0 1 2 False True false true Yes No yes no On Off ON OFF on off
SD_4_DOLLARS= $$$$
.for val in ${SD_VALUES}
-.MAKE.SAVE_DOLLARS:= ${val} # Must be := since a simple = has no effect.
+# The assignment must be done using ':=' since a simple '=' would be
+# interpreted as 'yes', due to the leading '$'; see ParseBoolean.
+.MAKE.SAVE_DOLLARS:= ${val}
SD.${val}:= ${SD_4_DOLLARS}
.endfor
.MAKE.SAVE_DOLLARS:= yes
@@ -92,6 +95,7 @@ save-dollars:
.for val in ${SD_VALUES}
@printf '%s: %-8s = %s\n' $@ ${val} ${SD.${val}:Q}
.endfor
+# end .MAKE.SAVE_DOLLARS
# Appending to an undefined variable does not add a space in front.
.undef APPENDED
diff --git a/unit-tests/varmod-assign.exp b/unit-tests/varmod-assign.exp
index 1e43714d500b..1ad388418ab5 100644
--- a/unit-tests/varmod-assign.exp
+++ b/unit-tests/varmod-assign.exp
@@ -12,18 +12,6 @@ Var_Parse: ${${VARNAME}} != "assigned-value" (eval-defined)
Var_Parse: ${VARNAME}} != "assigned-value" (eval-defined)
Global: .MAKEFLAGS = -r -k -d v -d
Global: .MAKEFLAGS = -r -k -d v -d 0
-mod-assign: first=1.
-mod-assign: last=3.
-mod-assign: appended=1 2 3.
-1
-2
-3
-mod-assign: ran:3.
-mod-assign: global: 1, 3, 1 2 3, 3.
-mod-assign-nested: then1t1
-mod-assign-nested: else2e2
-mod-assign-nested: then3t3
-mod-assign-nested: else4e4
make: Bad modifier ":" for variable ""
mod-assign-empty: value}
make: Bad modifier ":" for variable ""
diff --git a/unit-tests/varmod-assign.mk b/unit-tests/varmod-assign.mk
index f50c654f5bcf..b8559025fbfd 100644
--- a/unit-tests/varmod-assign.mk
+++ b/unit-tests/varmod-assign.mk
@@ -1,70 +1,79 @@
-# $NetBSD: varmod-assign.mk,v 1.12 2021/03/15 18:56:38 rillig Exp $
+# $NetBSD: varmod-assign.mk,v 1.14 2021/12/05 10:13:44 rillig Exp $
#
# Tests for the obscure ::= variable modifiers, which perform variable
# assignments during evaluation, just like the = operator in C.
-all: mod-assign
-all: mod-assign-nested
all: mod-assign-empty
all: mod-assign-parse
all: mod-assign-shell-error
-mod-assign:
- # The ::?= modifier applies the ?= assignment operator 3 times.
- # The ?= operator only has an effect for the first time, therefore
- # the variable FIRST ends up with the value 1.
- @echo $@: ${1 2 3:L:@i@${FIRST::?=$i}@} first=${FIRST}.
-
- # The ::= modifier applies the = assignment operator 3 times.
- # The = operator overwrites the previous value, therefore the
- # variable LAST ends up with the value 3.
- @echo $@: ${1 2 3:L:@i@${LAST::=$i}@} last=${LAST}.
-
- # The ::+= modifier applies the += assignment operator 3 times.
- # The += operator appends 3 times to the variable, therefore
- # the variable APPENDED ends up with the value "1 2 3".
- @echo $@: ${1 2 3:L:@i@${APPENDED::+=$i}@} appended=${APPENDED}.
-
- # The ::!= modifier applies the != assignment operator 3 times.
- # The side effects of the shell commands are visible in the output.
- # Just as with the ::= modifier, the last value is stored in the
- # RAN variable.
- @echo $@: ${echo.1 echo.2 echo.3:L:@i@${RAN::!=${i:C,.*,&; & 1>\&2,:S,., ,g}}@} ran:${RAN}.
-
- # The assignments happen in the global scope and thus are
- # preserved even after the shell command has been run.
- @echo $@: global: ${FIRST:Q}, ${LAST:Q}, ${APPENDED:Q}, ${RAN:Q}.
-
-mod-assign-nested:
- # The condition "1" is true, therefore THEN1 gets assigned a value,
- # and IT1 as well. Nothing surprising here.
- @echo $@: ${1:?${THEN1::=then1${IT1::=t1}}:${ELSE1::=else1${IE1::=e1}}}${THEN1}${ELSE1}${IT1}${IE1}
-
- # The condition "0" is false, therefore ELSE1 gets assigned a value,
- # and IE1 as well. Nothing surprising here as well.
- @echo $@: ${0:?${THEN2::=then2${IT2::=t2}}:${ELSE2::=else2${IE2::=e2}}}${THEN2}${ELSE2}${IT2}${IE2}
-
- # The same effects happen when the variables are defined elsewhere.
- @echo $@: ${SINK3:Q}
- @echo $@: ${SINK4:Q}
-SINK3:= ${1:?${THEN3::=then3${IT3::=t3}}:${ELSE3::=else3${IE3::=e3}}}${THEN3}${ELSE3}${IT3}${IE3}
-SINK4:= ${0:?${THEN4::=then4${IT4::=t4}}:${ELSE4::=else4${IE4::=e4}}}${THEN4}${ELSE4}${IT4}${IE4}
+# The modifier '::?=' applies the assignment operator '?=' 3 times. The
+# operator '?=' only has an effect for the first time, therefore the variable
+# FIRST ends up with the value 1.
+.if "${1 2 3:L:@i@${FIRST::?=$i}@} first=${FIRST}" != " first=1"
+. error
+.endif
+
+# The modifier '::=' applies the assignment operator '=' 3 times. The
+# operator '=' overwrites the previous value, therefore the variable LAST ends
+# up with the value 3.
+.if "${1 2 3:L:@i@${LAST::=$i}@} last=${LAST}" != " last=3"
+. error
+.endif
+
+# The modifier '::+=' applies the assignment operator '+=' 3 times. The
+# operator '+=' appends 3 times to the variable, therefore the variable
+# APPENDED ends up with the value "1 2 3".
+.if "${1 2 3:L:@i@${APPENDED::+=$i}@} appended=${APPENDED}" != " appended=1 2 3"
+. error
+.endif
+
+# The modifier '::!=' applies the assignment operator '!=' 3 times. Just as
+# with the modifier '::=', the last value is stored in the RAN variable.
+.if "${1 2 3:L:@i@${RAN::!=${i:%=echo '<%>';}}@} ran=${RAN}" != " ran=<3>"
+. error
+.endif
+
+# The assignments happen in the global scope and thus are preserved even after
+# the shell command has been run and the condition has been evaluated.
+.if "${FIRST}, ${LAST}, ${APPENDED}, ${RAN}" != "1, 3, 1 2 3, <3>"
+. error
+.endif
+
+# Tests for nested assignments, which are hard to read and therefore seldom
+# used in practice.
+
+# The condition "1" is true, therefore THEN1 gets assigned a value,
+# and the inner IT1 as well. Nothing surprising here.
+.if "${1:?${THEN1::=then1${IT1::=t1}}:${ELSE1::=else1${IE1::=e1}}} ${THEN1}${ELSE1}${IT1}${IE1}" != " then1t1"
+. error
+.endif
+
+# The condition "0" is false, therefore ELSE2 gets assigned a value,
+# and the inner IE2 as well. Nothing surprising here as well.
+.if "${0:?${THEN2::=then2${IT2::=t2}}:${ELSE2::=else2${IE2::=e2}}} ${THEN2}${ELSE2}${IT2}${IE2}" != " else2e2"
+. error
+.endif
+
+# The same effects happen when the variables are defined elsewhere.
+SINK3:= ${1:?${THEN3::=then3${IT3::=t3}}:${ELSE3::=else3${IE3::=e3}}} ${THEN3}${ELSE3}${IT3}${IE3}
+SINK4:= ${0:?${THEN4::=then4${IT4::=t4}}:${ELSE4::=else4${IE4::=e4}}} ${THEN4}${ELSE4}${IT4}${IE4}
+.if ${SINK3} != " then3t3"
+. error
+.endif
+.if ${SINK4} != " else4e4"
+. error
+.endif
mod-assign-empty:
# Assigning to the empty variable would obviously not work since that
# variable is write-protected. Therefore it is rejected early with a
# "Bad modifier" message.
- #
- # XXX: The error message is hard to read since the variable name is
- # empty. This leads to a trailing space in the error message.
@echo $@: ${::=value}
# In this variant, it is not as obvious that the name of the
# expression is empty. Assigning to it is rejected as well, with the
# same "Bad modifier" message.
- #
- # XXX: The error message is hard to read since the variable name is
- # empty. This leads to a trailing space in the error message.
@echo $@: ${:Uvalue::=overwritten}
# The :L modifier sets the value of the expression to its variable
diff --git a/unit-tests/varmod-defined.exp b/unit-tests/varmod-defined.exp
index 2f7d4dbf4baa..e2ae8d29808c 100644
--- a/unit-tests/varmod-defined.exp
+++ b/unit-tests/varmod-defined.exp
@@ -11,7 +11,7 @@ Var_Parse: ${VAR:@var@${8_DOLLARS}@} (eval-keep-dollar-and-undefined)
Evaluating modifier ${VAR:@...} on value "$$$$$$$$" (eval-keep-dollar-and-undefined, regular)
Modifier part: "var"
Modifier part: "${8_DOLLARS}"
-ModifyWords: split "$$$$$$$$" into 1 words
+ModifyWords: split "$$$$$$$$" into 1 word
Global: var = $$$$$$$$
Var_Parse: ${8_DOLLARS} (eval-keep-undefined)
ModifyWord_Loop: in "$$$$$$$$", replace "var" with "${8_DOLLARS}" to "$$$$"
diff --git a/unit-tests/varmod-defined.mk b/unit-tests/varmod-defined.mk
index a44b9f993146..ab5d708cf73f 100644
--- a/unit-tests/varmod-defined.mk
+++ b/unit-tests/varmod-defined.mk
@@ -1,8 +1,11 @@
-# $NetBSD: varmod-defined.mk,v 1.11 2021/04/11 13:35:56 rillig Exp $
+# $NetBSD: varmod-defined.mk,v 1.12 2021/11/30 23:52:19 rillig Exp $
#
# Tests for the :D variable modifier, which returns the given string
# if the variable is defined. It is closely related to the :U modifier.
+# Force the test results to be independent of the default value of this
+# setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake
+# distribution and pkgsrc/devel/bmake.
.MAKE.SAVE_DOLLARS= yes
DEF= defined
diff --git a/unit-tests/varmod-gmtime.exp b/unit-tests/varmod-gmtime.exp
index 5d5806b92d26..fdc9a2170e2f 100644
--- a/unit-tests/varmod-gmtime.exp
+++ b/unit-tests/varmod-gmtime.exp
@@ -1,12 +1,12 @@
-make: "varmod-gmtime.mk" line 57: Invalid time value: ${:U1593536400}} != "mtime=11593536400}"
+make: "varmod-gmtime.mk" line 57: Invalid time value at "${:U1593536400}} != "mtime=11593536400}""
make: "varmod-gmtime.mk" line 57: Malformed conditional (${%Y:L:gmtime=${:U1593536400}} != "mtime=11593536400}")
-make: "varmod-gmtime.mk" line 67: Invalid time value: -1} != ""
+make: "varmod-gmtime.mk" line 67: Invalid time value at "-1} != """
make: "varmod-gmtime.mk" line 67: Malformed conditional (${:L:gmtime=-1} != "")
-make: "varmod-gmtime.mk" line 76: Invalid time value: 1} != ""
+make: "varmod-gmtime.mk" line 76: Invalid time value at " 1} != """
make: "varmod-gmtime.mk" line 76: Malformed conditional (${:L:gmtime= 1} != "")
-make: "varmod-gmtime.mk" line 119: Invalid time value: 10000000000000000000000000000000} != ""
+make: "varmod-gmtime.mk" line 119: Invalid time value at "10000000000000000000000000000000} != """
make: "varmod-gmtime.mk" line 119: Malformed conditional (${:L:gmtime=10000000000000000000000000000000} != "")
-make: "varmod-gmtime.mk" line 130: Invalid time value: error} != ""
+make: "varmod-gmtime.mk" line 130: Invalid time value at "error} != """
make: "varmod-gmtime.mk" line 130: Malformed conditional (${:L:gmtime=error} != "")
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
diff --git a/unit-tests/varmod-indirect.exp b/unit-tests/varmod-indirect.exp
index 63ed988d0c0e..9cff88eb760a 100644
--- a/unit-tests/varmod-indirect.exp
+++ b/unit-tests/varmod-indirect.exp
@@ -20,7 +20,7 @@ Indirect modifier "S,a,a," from "${:US,a,a,}"
Evaluating modifier ${UNDEF:S...} on value "" (eval-keep-dollar-and-undefined, undefined)
Modifier part: "a"
Modifier part: "a"
-ModifyWords: split "" into 1 words
+ModifyWords: split "" into 1 word
Result of ${UNDEF:S,a,a,} is "" (eval-keep-dollar-and-undefined, undefined)
Global: _ = before ${UNDEF:S,a,a,} after
ParseReadLine (179): '_:= before ${UNDEF:${:U}} after'
diff --git a/unit-tests/varmod-localtime.exp b/unit-tests/varmod-localtime.exp
index ed4d4f053c61..494f160b766e 100644
--- a/unit-tests/varmod-localtime.exp
+++ b/unit-tests/varmod-localtime.exp
@@ -1,12 +1,12 @@
-make: "varmod-localtime.mk" line 57: Invalid time value: ${:U1593536400}} != "mtime=11593536400}"
+make: "varmod-localtime.mk" line 57: Invalid time value at "${:U1593536400}} != "mtime=11593536400}""
make: "varmod-localtime.mk" line 57: Malformed conditional (${%Y:L:localtime=${:U1593536400}} != "mtime=11593536400}")
-make: "varmod-localtime.mk" line 67: Invalid time value: -1} != ""
+make: "varmod-localtime.mk" line 67: Invalid time value at "-1} != """
make: "varmod-localtime.mk" line 67: Malformed conditional (${:L:localtime=-1} != "")
-make: "varmod-localtime.mk" line 76: Invalid time value: 1} != ""
+make: "varmod-localtime.mk" line 76: Invalid time value at " 1} != """
make: "varmod-localtime.mk" line 76: Malformed conditional (${:L:localtime= 1} != "")
-make: "varmod-localtime.mk" line 119: Invalid time value: 10000000000000000000000000000000} != ""
+make: "varmod-localtime.mk" line 119: Invalid time value at "10000000000000000000000000000000} != """
make: "varmod-localtime.mk" line 119: Malformed conditional (${:L:localtime=10000000000000000000000000000000} != "")
-make: "varmod-localtime.mk" line 130: Invalid time value: error} != ""
+make: "varmod-localtime.mk" line 130: Invalid time value at "error} != """
make: "varmod-localtime.mk" line 130: Malformed conditional (${:L:localtime=error} != "")
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
diff --git a/unit-tests/varmod-localtime.mk b/unit-tests/varmod-localtime.mk
index f2867b61f8e9..ffa09a0bc5fc 100644
--- a/unit-tests/varmod-localtime.mk
+++ b/unit-tests/varmod-localtime.mk
@@ -3,7 +3,7 @@
# Tests for the :localtime variable modifier, which formats a timestamp
# using strftime(3) in local time.
-.if ${TZ} != "Europe/Berlin" # see unit-tests/Makefile
+.if ${TZ:Uno:NEurope/Berlin:NUTC-1} != "" # see unit-tests/Makefile
. error
.endif
diff --git a/unit-tests/varmod-loop-delete.exp b/unit-tests/varmod-loop-delete.exp
new file mode 100644
index 000000000000..aac86ee39061
--- /dev/null
+++ b/unit-tests/varmod-loop-delete.exp
@@ -0,0 +1,4 @@
+make: "varmod-loop-delete.mk" line 19: Cannot delete variable "VAR" while it is used
+make: Fatal errors encountered -- cannot continue
+make: stopped in unit-tests
+exit status 1
diff --git a/unit-tests/varmod-loop-delete.mk b/unit-tests/varmod-loop-delete.mk
new file mode 100644
index 000000000000..ed145b59ba0f
--- /dev/null
+++ b/unit-tests/varmod-loop-delete.mk
@@ -0,0 +1,33 @@
+# $NetBSD: varmod-loop-delete.mk,v 1.2 2021/12/05 15:51:33 rillig Exp $
+#
+# Tests for the variable modifier ':@', which as a side effect allows to
+# delete an arbitrary variable.
+
+# A side effect of the modifier ':@' is that the loop variable is created as
+# an actual variable in the current evaluation scope (Command/Global/target),
+# and at the end of the loop, this variable is deleted. Since var.c 1.204
+# from 2016-02-18 and before var.c 1.963 from 2021-12-05, a variable could be
+# deleted while it was in use, leading to a use-after-free bug.
+#
+# See Var_Parse, comment 'the value of the variable must not change'.
+
+# Set up the variable that deletes itself when it is evaluated.
+VAR= ${:U:@VAR@@} rest of the value
+
+# In an assignment, the scope is 'Global'. Since the variable 'VAR' is
+# defined in the global scope, it deletes itself.
+EVAL:= ${VAR}
+.if ${EVAL} != " rest of the value"
+. error
+.endif
+
+VAR= ${:U:@VAR@@} rest of the value
+all: .PHONY
+ # In the command that is associated with a target, the scope is the
+ # one from the target. That scope only contains a few variables like
+ # '.TARGET', '.ALLSRC', '.IMPSRC'. Make does not expect that these
+ # variables get modified from the outside.
+ #
+ # There is no variable named 'VAR' in the local scope, so nothing
+ # happens.
+ : $@: '${VAR}'
diff --git a/unit-tests/varmod-loop-varname.exp b/unit-tests/varmod-loop-varname.exp
index 9170307bd2a0..4f0379d5ea0a 100644
--- a/unit-tests/varmod-loop-varname.exp
+++ b/unit-tests/varmod-loop-varname.exp
@@ -1,11 +1,11 @@
-make: "varmod-loop-varname.mk" line 13: In the :@ modifier of "", the variable name "${:Ubar:S,b,v,}" must not contain a dollar.
-make: "varmod-loop-varname.mk" line 13: Malformed conditional (${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+")
-make: "varmod-loop-varname.mk" line 80: In the :@ modifier of "1 2 3", the variable name "v$" must not contain a dollar.
-make: "varmod-loop-varname.mk" line 80: Malformed conditional (${1 2 3:L:@v$@($v)@} != "(1) (2) (3)")
-make: "varmod-loop-varname.mk" line 85: In the :@ modifier of "1 2 3", the variable name "v$$" must not contain a dollar.
-make: "varmod-loop-varname.mk" line 85: Malformed conditional (${1 2 3:L:@v$$@($v)@} != "() () ()")
-make: "varmod-loop-varname.mk" line 90: In the :@ modifier of "1 2 3", the variable name "v$$$" must not contain a dollar.
-make: "varmod-loop-varname.mk" line 90: Malformed conditional (${1 2 3:L:@v$$$@($v)@} != "() () ()")
+make: "varmod-loop-varname.mk" line 16: In the :@ modifier of "", the variable name "${:Ubar:S,b,v,}" must not contain a dollar
+make: "varmod-loop-varname.mk" line 16: Malformed conditional (${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+")
+make: "varmod-loop-varname.mk" line 85: In the :@ modifier of "1 2 3", the variable name "v$" must not contain a dollar
+make: "varmod-loop-varname.mk" line 85: Malformed conditional (${1 2 3:L:@v$@($v)@} != "(1) (2) (3)")
+make: "varmod-loop-varname.mk" line 90: In the :@ modifier of "1 2 3", the variable name "v$$" must not contain a dollar
+make: "varmod-loop-varname.mk" line 90: Malformed conditional (${1 2 3:L:@v$$@($v)@} != "() () ()")
+make: "varmod-loop-varname.mk" line 95: In the :@ modifier of "1 2 3", the variable name "v$$$" must not contain a dollar
+make: "varmod-loop-varname.mk" line 95: Malformed conditional (${1 2 3:L:@v$$$@($v)@} != "() () ()")
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/varmod-loop-varname.mk b/unit-tests/varmod-loop-varname.mk
index d51e2ba76a42..91f8a4876466 100644
--- a/unit-tests/varmod-loop-varname.mk
+++ b/unit-tests/varmod-loop-varname.mk
@@ -1,8 +1,11 @@
-# $NetBSD: varmod-loop-varname.mk,v 1.2 2021/04/04 13:35:26 rillig Exp $
+# $NetBSD: varmod-loop-varname.mk,v 1.4 2021/12/05 15:01:04 rillig Exp $
#
# Tests for the first part of the variable modifier ':@var@...@', which
# contains the variable name to use during the loop.
+# Force the test results to be independent of the default value of this
+# setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake
+# distribution and pkgsrc/devel/bmake.
.MAKE.SAVE_DOLLARS= yes
@@ -12,6 +15,8 @@
# variable name.
.if ${:Uone two three:@${:Ubar:S,b,v,}@+${var}+@} != "+one+ +two+ +three+"
. error
+.else
+. error
.endif
diff --git a/unit-tests/varmod-loop.exp b/unit-tests/varmod-loop.exp
index a4704973f6e2..d05b870d5b5e 100644
--- a/unit-tests/varmod-loop.exp
+++ b/unit-tests/varmod-loop.exp
@@ -1,10 +1,10 @@
-ParseReadLine (75): 'USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$'
+ParseReadLine (78): 'USE_8_DOLLARS= ${:U1:@var@${8_DOLLARS}@} ${8_DOLLARS} $$$$$$$$'
CondParser_Eval: ${USE_8_DOLLARS} != "\$\$\$\$ \$\$\$\$ \$\$\$\$"
lhs = "$$$$ $$$$ $$$$", rhs = "$$$$ $$$$ $$$$", op = !=
-ParseReadLine (80): 'SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}'
+ParseReadLine (83): 'SUBST_CONTAINING_LOOP:= ${USE_8_DOLLARS}'
CondParser_Eval: ${SUBST_CONTAINING_LOOP} != "\$\$ \$\$\$\$ \$\$\$\$"
lhs = "$$ $$$$ $$$$", rhs = "$$ $$$$ $$$$", op = !=
-ParseReadLine (105): '.MAKEFLAGS: -d0'
+ParseReadLine (108): '.MAKEFLAGS: -d0'
ParseDependency(.MAKEFLAGS: -d0)
:varname-overwriting-target: :x1y x2y x3y: ::
mod-loop-dollar:1:
diff --git a/unit-tests/varmod-loop.mk b/unit-tests/varmod-loop.mk
index 4fdaa3ff4e61..82046ff95d79 100644
--- a/unit-tests/varmod-loop.mk
+++ b/unit-tests/varmod-loop.mk
@@ -1,7 +1,10 @@
-# $NetBSD: varmod-loop.mk,v 1.15 2021/04/11 13:35:56 rillig Exp $
+# $NetBSD: varmod-loop.mk,v 1.18 2021/12/05 15:20:13 rillig Exp $
#
# Tests for the :@var@...${var}...@ variable modifier.
+# Force the test results to be independent of the default value of this
+# setting, which is 'yes' for NetBSD's usr.bin/make but 'no' for the bmake
+# distribution and pkgsrc/devel/bmake.
.MAKE.SAVE_DOLLARS= yes
all: varname-overwriting-target
@@ -183,7 +186,4 @@ CMDLINE= global # needed for deleting the environment
. error # 'CMDLINE' is gone now from all scopes
.endif
-
-# TODO: Actually trigger the undefined behavior (use after free) that was
-# already suspected in Var_Parse, in the comment 'the value of the variable
-# must not change'.
+all: .PHONY
diff --git a/unit-tests/varmod-order-numeric.exp b/unit-tests/varmod-order-numeric.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/unit-tests/varmod-order-numeric.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/unit-tests/varmod-order-numeric.mk b/unit-tests/varmod-order-numeric.mk
new file mode 100644
index 000000000000..40a1d408e9ae
--- /dev/null
+++ b/unit-tests/varmod-order-numeric.mk
@@ -0,0 +1,54 @@
+# $NetBSD: varmod-order-numeric.mk,v 1.5 2021/08/03 04:46:49 rillig Exp $
+#
+# Tests for the variable modifiers ':On', which returns the words, sorted in
+# ascending numeric order, and for ':Orn' and ':Onr', which additionally
+# reverse the order.
+#
+# The variable modifiers ':On', ':Onr' and ':Orn' were added in var.c 1.939
+# from 2021-07-30.
+
+# This list contains only 32-bit numbers since the make code needs to conform
+# to C90, which does not provide integer types larger than 32 bit. It uses
+# 'long long' by default, but that type is overridable if necessary to support
+# older environments.
+#
+# To get 53-bit integers even in C90, it would be possible to switch to
+# 'double' instead, but that would allow floating-point numbers as well, which
+# is out of scope for this variable modifier.
+NUMBERS= 3 5 7 1 42 -42 5K -3m 1M 1k -2G
+
+.if ${NUMBERS:On} != "-2G -3m -42 1 3 5 7 42 1k 5K 1M"
+. error ${NUMBERS:On}
+.endif
+
+.if ${NUMBERS:Orn} != "1M 5K 1k 42 7 5 3 1 -42 -3m -2G"
+. error ${NUMBERS:Orn}
+.endif
+
+# Both ':Onr' and ':Orn' have the same effect.
+.if ${NUMBERS:Onr} != "1M 5K 1k 42 7 5 3 1 -42 -3m -2G"
+. error ${NUMBERS:Onr}
+.endif
+
+# Duplicate numbers are preserved in the output. In this case the
+# equal-valued numbers are spelled the same, so they are indistinguishable in
+# the output.
+DUPLICATES= 3 1 2 2 1 1 # https://oeis.org/A034002
+.if ${DUPLICATES:On} != "1 1 1 2 2 3"
+. error ${DUPLICATES:On}
+.endif
+
+# If there are several numbers that have the same integer value, they are
+# returned in unspecified order.
+SAME_VALUE:= ${:U 79 80 0x0050 81 :On}
+.if ${SAME_VALUE} != "79 80 0x0050 81" && ${SAME_VALUE} != "79 0x0050 80 81"
+. error ${SAME_VALUE}
+.endif
+
+# Hexadecimal and octal numbers are supported as well.
+MIXED_BASE= 0 010 0x7 9
+.if ${MIXED_BASE:On} != "0 0x7 010 9"
+. error ${MIXED_BASE:On}
+.endif
+
+all:
diff --git a/unit-tests/varmod-order-reverse.mk b/unit-tests/varmod-order-reverse.mk
index 1a6d2d766f76..c3be8d0f7817 100644
--- a/unit-tests/varmod-order-reverse.mk
+++ b/unit-tests/varmod-order-reverse.mk
@@ -1,13 +1,12 @@
-# $NetBSD: varmod-order-reverse.mk,v 1.4 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varmod-order-reverse.mk,v 1.5 2021/08/03 04:46:49 rillig Exp $
#
# Tests for the :Or variable modifier, which returns the words, sorted in
# descending order.
-NUMBERS= one two three four five six seven eight nine ten
+WORDS= one two three four five six seven eight nine ten
-.if ${NUMBERS:Or} != "two three ten six seven one nine four five eight"
-. error ${NUMBERS:Or}
+.if ${WORDS:Or} != "two three ten six seven one nine four five eight"
+. error ${WORDS:Or}
.endif
all:
- @:;
diff --git a/unit-tests/varmod-order-shuffle.mk b/unit-tests/varmod-order-shuffle.mk
index 185141b6c4a5..16121d7e498f 100644
--- a/unit-tests/varmod-order-shuffle.mk
+++ b/unit-tests/varmod-order-shuffle.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-order-shuffle.mk,v 1.6 2020/11/09 20:16:33 rillig Exp $
+# $NetBSD: varmod-order-shuffle.mk,v 1.7 2021/08/03 04:46:49 rillig Exp $
#
# Tests for the :Ox variable modifier, which returns the words of the
# variable, shuffled.
@@ -11,7 +11,7 @@
#
# Tags: probabilistic
-NUMBERS= one two three four five six seven eight nine ten
+WORDS= one two three four five six seven eight nine ten
# Note that 1 in every 10! trials two independently generated
# randomized orderings will be the same. The test framework doesn't
@@ -20,24 +20,23 @@ NUMBERS= one two three four five six seven eight nine ten
# lets the whole test fail once in 1.209.600 runs, on average.
# Create two shuffles using the := assignment operator.
-shuffled1:= ${NUMBERS:Ox}
-shuffled2:= ${NUMBERS:Ox}
+shuffled1:= ${WORDS:Ox}
+shuffled2:= ${WORDS:Ox}
.if ${shuffled1} == ${shuffled2}
. error ${shuffled1} == ${shuffled2}
.endif
# Sorting the list before shuffling it has no effect.
-shuffled1:= ${NUMBERS:O:Ox}
-shuffled2:= ${NUMBERS:O:Ox}
+shuffled1:= ${WORDS:O:Ox}
+shuffled2:= ${WORDS:O:Ox}
.if ${shuffled1} == ${shuffled2}
. error ${shuffled1} == ${shuffled2}
.endif
# Sorting after shuffling must produce the original numbers.
-sorted:= ${NUMBERS:Ox:O}
-.if ${sorted} != ${NUMBERS:O}
-. error ${sorted} != ${NUMBERS:O}
+sorted:= ${WORDS:Ox:O}
+.if ${sorted} != ${WORDS:O}
+. error ${sorted} != ${WORDS:O}
.endif
all:
- @:;
diff --git a/unit-tests/varmod-order-string.exp b/unit-tests/varmod-order-string.exp
new file mode 100644
index 000000000000..39a9383953dd
--- /dev/null
+++ b/unit-tests/varmod-order-string.exp
@@ -0,0 +1 @@
+exit status 0
diff --git a/unit-tests/varmod-order-string.mk b/unit-tests/varmod-order-string.mk
new file mode 100644
index 000000000000..bb0a145ba825
--- /dev/null
+++ b/unit-tests/varmod-order-string.mk
@@ -0,0 +1,28 @@
+# $NetBSD: varmod-order-string.mk,v 1.2 2021/08/03 04:46:49 rillig Exp $
+#
+# Tests for the :O variable modifier, which returns the words, sorted in
+# ascending order.
+
+# Simple words are sorted lexicographically.
+WORDS= one two three four five six seven eight nine ten
+.if ${WORDS:O} != "eight five four nine one seven six ten three two"
+. error ${WORDS:O}
+.endif
+
+# Double quotes and single quotes delimit words, while backticks are just
+# regular characters. Therefore '`in' is a separate word from 'backticks`',
+# and the additional spaces between them are removed.
+QUOTED_WORDS= none "double quoted" 'single quoted' `in backticks`
+.if ${QUOTED_WORDS:O} != "\"double quoted\" 'single quoted' `in backticks` none"
+. error ${QUOTED_WORDS:O}
+.endif
+
+# Numbers are sorted lexicographically as well.
+# To sort the words numerically, use ':On' instead; since var.c 1.939 from
+# 2021-07-30.
+NUMBERS= -100g -50m -7k -50 -13 0 000 13 50 5k1 7k 50m 100G
+.if ${NUMBERS:O} != "-100g -13 -50 -50m -7k 0 000 100G 13 50 50m 5k1 7k"
+. error ${NUMBERS:O}
+.endif
+
+all:
diff --git a/unit-tests/varmod-order.exp b/unit-tests/varmod-order.exp
index 94c3cb694886..46dc45e9f6d6 100644
--- a/unit-tests/varmod-order.exp
+++ b/unit-tests/varmod-order.exp
@@ -1,7 +1,24 @@
-make: Bad modifier ":OX" for variable "NUMBERS"
-make: "varmod-order.mk" line 13: Undefined variable "${NUMBERS:OX"
-make: Bad modifier ":OxXX" for variable "NUMBERS"
-make: "varmod-order.mk" line 16: Undefined variable "${NUMBERS:Ox"
+make: Bad modifier ":OX" for variable "WORDS"
+make: "varmod-order.mk" line 14: Undefined variable "${WORDS:OX"
+make: Bad modifier ":OxXX" for variable "WORDS"
+make: "varmod-order.mk" line 17: Undefined variable "${WORDS:Ox"
+make: Unclosed variable expression, expecting '}' for modifier "O" of variable "WORDS" with value "eight five four nine one seven six ten three two"
+make: Unclosed variable expression, expecting '}' for modifier "On" of variable "NUMBERS" with value "1 2 3 4 5 6 7 8 9 10"
+make: Unclosed variable expression, expecting '}' for modifier "Onr" of variable "NUMBERS" with value "10 9 8 7 6 5 4 3 2 1"
+make: Bad modifier ":Oxn" for variable "NUMBERS"
+make: "varmod-order.mk" line 29: Malformed conditional (${NUMBERS:Oxn})
+make: Bad modifier ":On_typo" for variable "NUMBERS"
+make: "varmod-order.mk" line 39: Malformed conditional (${NUMBERS:On_typo})
+make: Bad modifier ":Onr_typo" for variable "NUMBERS"
+make: "varmod-order.mk" line 48: Malformed conditional (${NUMBERS:Onr_typo})
+make: Bad modifier ":Orn_typo" for variable "NUMBERS"
+make: "varmod-order.mk" line 57: Malformed conditional (${NUMBERS:Orn_typo})
+make: Bad modifier ":Onn" for variable "NUMBERS"
+make: "varmod-order.mk" line 68: Malformed conditional (${NUMBERS:Onn})
+make: Bad modifier ":Onrr" for variable "NUMBERS"
+make: "varmod-order.mk" line 77: Malformed conditional (${NUMBERS:Onrr})
+make: Bad modifier ":Orrn" for variable "NUMBERS"
+make: "varmod-order.mk" line 86: Malformed conditional (${NUMBERS:Orrn})
make: Fatal errors encountered -- cannot continue
make: stopped in unit-tests
exit status 1
diff --git a/unit-tests/varmod-order.mk b/unit-tests/varmod-order.mk
index 675b6efec5e7..7f3d485fe8e3 100644
--- a/unit-tests/varmod-order.mk
+++ b/unit-tests/varmod-order.mk
@@ -1,19 +1,92 @@
-# $NetBSD: varmod-order.mk,v 1.5 2020/10/24 08:46:08 rillig Exp $
+# $NetBSD: varmod-order.mk,v 1.7 2021/08/03 04:46:49 rillig Exp $
#
-# Tests for the :O variable modifier, which returns the words, sorted in
-# ascending order.
+# Tests for the :O variable modifier and its variants, which either sort the
+# words of the value or shuffle them.
-NUMBERS= one two three four five six seven eight nine ten
+WORDS= one two three four five six seven eight nine ten
+NUMBERS= 8 5 4 9 1 7 6 10 3 2 # in English alphabetical order
-.if ${NUMBERS:O} != "eight five four nine one seven six ten three two"
-. error ${NUMBERS:O}
+.if ${WORDS:O} != "eight five four nine one seven six ten three two"
+. error ${WORDS:O}
.endif
# Unknown modifier "OX"
-_:= ${NUMBERS:OX}
+_:= ${WORDS:OX}
# Unknown modifier "OxXX"
-_:= ${NUMBERS:OxXX}
+_:= ${WORDS:OxXX}
+
+# Missing closing brace, to cover the error handling code.
+_:= ${WORDS:O
+_:= ${NUMBERS:On
+_:= ${NUMBERS:Onr
+
+# Shuffling numerically doesn't make sense, so don't allow 'x' and 'n' to be
+# combined.
+#
+# expect-text: Bad modifier ":Oxn" for variable "NUMBERS"
+# expect+1: Malformed conditional (${NUMBERS:Oxn})
+.if ${NUMBERS:Oxn}
+. error
+.else
+. error
+.endif
+
+# Extra characters after ':On' are detected and diagnosed.
+# TODO: Add line number information to the "Bad modifier" diagnostic.
+#
+# expect-text: Bad modifier ":On_typo" for variable "NUMBERS"
+.if ${NUMBERS:On_typo}
+. error
+.else
+. error
+.endif
+
+# Extra characters after ':Onr' are detected and diagnosed.
+#
+# expect-text: Bad modifier ":Onr_typo" for variable "NUMBERS"
+.if ${NUMBERS:Onr_typo}
+. error
+.else
+. error
+.endif
+
+# Extra characters after ':Orn' are detected and diagnosed.
+#
+# expect+1: Bad modifier ":Orn_typo" for variable "NUMBERS"
+.if ${NUMBERS:Orn_typo}
+. error
+.else
+. error
+.endif
+
+# Repeating the 'n' is not supported. In the typical use cases, the sorting
+# criteria are fixed, not computed, therefore allowing this redundancy does
+# not make sense.
+#
+# expect-text: Bad modifier ":Onn" for variable "NUMBERS"
+.if ${NUMBERS:Onn}
+. error
+.else
+. error
+.endif
+
+# Repeating the 'r' is not supported as well, for the same reasons as above.
+#
+# expect-text: Bad modifier ":Onrr" for variable "NUMBERS"
+.if ${NUMBERS:Onrr}
+. error
+.else
+. error
+.endif
+
+# Repeating the 'r' is not supported as well, for the same reasons as above.
+#
+# expect-text: Bad modifier ":Orrn" for variable "NUMBERS"
+.if ${NUMBERS:Orrn}
+. error
+.else
+. error
+.endif
all:
- @:;
diff --git a/unit-tests/varmod-root.exp b/unit-tests/varmod-root.exp
index 2c99cd3ef4c7..39a9383953dd 100644
--- a/unit-tests/varmod-root.exp
+++ b/unit-tests/varmod-root.exp
@@ -1,11 +1 @@
-root of 'a/b/c' is 'a/b/c'
-root of 'def' is 'def'
-root of 'a.b.c' is 'a.b'
-root of 'a.b/c' is 'a'
-root of 'a' is 'a'
-root of 'a.a' is 'a'
-root of '.gitignore' is ''
-root of 'a' is 'a'
-root of 'a.a' is 'a'
-root of 'trailing/' is 'trailing/'
exit status 0
diff --git a/unit-tests/varmod-root.mk b/unit-tests/varmod-root.mk
index 1e3159733df0..cf88491df799 100644
--- a/unit-tests/varmod-root.mk
+++ b/unit-tests/varmod-root.mk
@@ -1,9 +1,38 @@
-# $NetBSD: varmod-root.mk,v 1.4 2020/12/20 22:57:40 rillig Exp $
+# $NetBSD: varmod-root.mk,v 1.5 2021/12/05 22:31:58 rillig Exp $
#
# Tests for the :R variable modifier, which returns the filename root
# without the extension.
+.if ${a/b/c:L:R} != "a/b/c"
+. error
+.endif
+
+.if ${def:L:R} != "def"
+. error
+.endif
+
+.if ${a.b.c:L:R} != "a.b"
+. error
+.endif
+
+.if ${a.b/c:L:R} != "a"
+. error
+.endif
+
+.if ${a:L:R} != "a"
+. error
+.endif
+
+.if ${a.a:L:R} != "a"
+. error
+.endif
+
+.if ${.gitignore:L:R} != ""
+. error
+.endif
+
+.if ${trailing/:L:R} != "trailing/"
+. error
+.endif
+
all:
-.for path in a/b/c def a.b.c a.b/c a a.a .gitignore a a.a trailing/
- @echo "root of '"${path:Q}"' is '"${path:R:Q}"'"
-.endfor
diff --git a/unit-tests/varmod-select-words.mk b/unit-tests/varmod-select-words.mk
index a9df25f9ff32..ab094bf056b0 100644
--- a/unit-tests/varmod-select-words.mk
+++ b/unit-tests/varmod-select-words.mk
@@ -1,7 +1,10 @@
-# $NetBSD: varmod-select-words.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varmod-select-words.mk,v 1.3 2021/12/05 12:06:23 rillig Exp $
#
# Tests for the :[...] variable modifier, which selects a single word
# or a range of words from a variable.
+#
+# See also:
+# modword.mk (should be migrated here)
# TODO: Implementation
diff --git a/unit-tests/varmod-subst.mk b/unit-tests/varmod-subst.mk
index 85f41e499ab7..763535ff835a 100644
--- a/unit-tests/varmod-subst.mk
+++ b/unit-tests/varmod-subst.mk
@@ -1,4 +1,4 @@
-# $NetBSD: varmod-subst.mk,v 1.8 2021/05/14 19:37:16 rillig Exp $
+# $NetBSD: varmod-subst.mk,v 1.9 2021/09/06 21:18:55 rillig Exp $
#
# Tests for the :S,from,to, variable modifier.
@@ -86,6 +86,19 @@ WORDS= sequences of letters
. error The '.' seems to be interpreted as a wildcard of some kind.
.endif
+.if ${:Uvalue:S,^val,&,} != "value"
+. error
+.endif
+.if ${:Uvalue:S,ue$,&,} != "value"
+. error
+.endif
+.if ${:Uvalue:S,^val,&-&-&,} != "val-val-value"
+. error
+.endif
+.if ${:Uvalue:S,ue$,&-&-&,} != "value-ue-ue"
+. error
+.endif
+
mod-subst:
@echo $@:
@echo :${:Ua b b c:S,a b,,:Q}:
diff --git a/unit-tests/varmod-to-separator.exp b/unit-tests/varmod-to-separator.exp
index c6e8ce98a21a..3f8f1b2a11eb 100644
--- a/unit-tests/varmod-to-separator.exp
+++ b/unit-tests/varmod-to-separator.exp
@@ -1,6 +1,6 @@
-make: "varmod-to-separator.mk" line 107: Invalid character number: 400:tu}
+make: "varmod-to-separator.mk" line 107: Invalid character number at "400:tu}"
make: "varmod-to-separator.mk" line 107: Malformed conditional (${WORDS:[1..3]:ts\400:tu})
-make: "varmod-to-separator.mk" line 121: Invalid character number: 100:tu}
+make: "varmod-to-separator.mk" line 121: Invalid character number at "100:tu}"
make: "varmod-to-separator.mk" line 121: Malformed conditional (${WORDS:[1..3]:ts\x100:tu})
make: Bad modifier ":ts\-300" for variable "WORDS"
make: "varmod-to-separator.mk" line 128: Malformed conditional (${WORDS:[1..3]:ts\-300:tu})
diff --git a/unit-tests/varmod-unique.mk b/unit-tests/varmod-unique.mk
index 04d04a575af1..7fef35b69211 100644
--- a/unit-tests/varmod-unique.mk
+++ b/unit-tests/varmod-unique.mk
@@ -1,47 +1,46 @@
-# $NetBSD: varmod-unique.mk,v 1.5 2021/05/30 20:26:41 rillig Exp $
+# $NetBSD: varmod-unique.mk,v 1.6 2021/12/05 22:37:58 rillig Exp $
#
# Tests for the :u variable modifier, which discards adjacent duplicate
# words.
-.if ${:U1 2 1:u} != "1 2 1"
-. warning The :u modifier only merges _adjacent_ duplicate words.
+.if ${1 2 1:L:u} != "1 2 1"
+. warning The modifier ':u' only merges _adjacent_ duplicate words.
.endif
-.if ${:U1 2 2 3:u} != "1 2 3"
-. warning The :u modifier must merge adjacent duplicate words.
+.if ${1 2 2 3:L:u} != "1 2 3"
+. warning The modifier ':u' must merge adjacent duplicate words.
.endif
-.if ${:U:u} != ""
-. warning The :u modifier must do nothing with an empty word list.
+.if ${:L:u} != ""
+. warning The modifier ':u' must do nothing with an empty word list.
.endif
-.if ${:U :u} != ""
+.if ${ :L:u} != ""
. warning The modifier ':u' must normalize the whitespace.
.endif
-.if ${:Uword:u} != "word"
-. warning The :u modifier must do nothing with a single-element word list.
+.if ${word:L:u} != "word"
+. warning The modifier ':u' must do nothing with a single-element word list.
.endif
-.if ${:U word :u} != "word"
+.if ${ word :L:u} != "word"
. warning The modifier ':u' must normalize the whitespace.
.endif
-.if ${:U1 1 1 1 1 1 1 1:u} != "1"
-. warning The :u modifier must merge _all_ adjacent duplicate words.
+.if ${1 1 1 1 1 1 1 1:L:u} != "1"
+. warning The modifier ':u' must merge _all_ adjacent duplicate words.
.endif
-.if ${:U 1 2 1 1 :u} != "1 2 1"
-. warning The :u modifier must normalize whitespace between the words.
+.if ${ 1 2 1 1 :L:u} != "1 2 1"
+. warning The modifier ':u' must normalize whitespace between the words.
.endif
-.if ${:U1 1 1 1 2:u} != "1 2"
+.if ${1 1 1 1 2:L:u} != "1 2"
. warning Duplicate words at the beginning must be merged.
.endif
-.if ${:U1 2 2 2 2:u} != "1 2"
+.if ${1 2 2 2 2:L:u} != "1 2"
. warning Duplicate words at the end must be merged.
.endif
all:
- @:;
diff --git a/unit-tests/varname-dot-make-save_dollars.mk b/unit-tests/varname-dot-make-save_dollars.mk
index 97f37a646d2d..31f228c220b2 100644
--- a/unit-tests/varname-dot-make-save_dollars.mk
+++ b/unit-tests/varname-dot-make-save_dollars.mk
@@ -1,8 +1,130 @@
-# $NetBSD: varname-dot-make-save_dollars.mk,v 1.2 2020/08/16 14:25:16 rillig Exp $
+# $NetBSD: varname-dot-make-save_dollars.mk,v 1.7 2021/12/03 18:43:52 rillig Exp $
#
-# Tests for the special .MAKE.SAVE_DOLLARS variable.
+# Tests for the special .MAKE.SAVE_DOLLARS variable, which controls whether
+# the assignment operator ':=' converts '$$' to a single '$' or keeps it
+# as-is.
+#
+# See also:
+# var-op-expand.mk for ':=' in general
+# varmisc.mk for parsing the boolean values
+
+# Initially, the variable .MAKE.SAVE_DOLLARS is undefined. At this point the
+# behavior of the assignment operator ':=' depends. NetBSD's usr.bin/make
+# preserves the '$$' as-is, while the bmake distribution replaces '$$' with
+# '$'.
+.if ${.MAKE.SAVE_DOLLARS:Uundefined} != "undefined"
+. error
+.endif
+
+
+# When dollars are preserved, this setting not only applies to literal
+# dollars, but also to those that come indirectly from other expressions.
+DOLLARS= $$$$$$$$
+.MAKE.SAVE_DOLLARS= yes
+VAR:= ${DOLLARS}
+# The reduction from 8 '$' to 4 '$' happens when ${VAR} is evaluated in the
+# condition; .MAKE.SAVE_DOLLARS only applies at the moment where the
+# assignment is performed using ':='.
+.if ${VAR} != "\$\$\$\$"
+. error
+.endif
+
+# When dollars are preserved, this setting not only applies to literal
+# dollars, but also to those that come indirectly from other expressions.
+DOLLARS= $$$$$$$$
+.MAKE.SAVE_DOLLARS= no
+VAR:= ${DOLLARS}
+.if ${VAR} != "\$\$"
+. error
+.endif
+
+# The 'yes' preserves the dollars from the literal.
+.MAKE.SAVE_DOLLARS= yes
+VAR:= $$$$$$$$
+.if ${VAR} != "\$\$\$\$"
+. error
+.endif
+
+# The 'no' converts each '$$' to '$'.
+.MAKE.SAVE_DOLLARS= no
+VAR:= $$$$$$$$
+.if ${VAR} != "\$\$"
+. error
+.endif
+
+# It's even possible to change the dollar interpretation in the middle of
+# evaluating an expression, but there is no practical need for it.
+.MAKE.SAVE_DOLLARS= no
+VAR:= $$$$-${.MAKE.SAVE_DOLLARS::=yes}-$$$$
+.if ${VAR} != "\$--\$\$"
+. error
+.endif
+
+# The '$' from the ':U' expressions do not appear as literal '$$' to the
+# parser (no matter whether directly or indirectly), they only appear as '$$'
+# in the value of an expression, therefore .MAKE.SAVE_DOLLARS doesn't apply
+# here.
+.MAKE.SAVE_DOLLARS= no
+VAR:= ${:U\$\$\$\$}-${.MAKE.SAVE_DOLLARS::=yes}-${:U\$\$\$\$}
+.if ${VAR} != "\$\$--\$\$"
+. error
+.endif
+
+# Undefining .MAKE.SAVE_DOLLARS does not have any effect, in particular it
+# does not restore the default behavior.
+.MAKE.SAVE_DOLLARS= no
+.undef .MAKE.SAVE_DOLLARS
+VAR:= $$$$$$$$
+.if ${VAR} != "\$\$"
+. error
+.endif
+
+# Undefining .MAKE.SAVE_DOLLARS does not have any effect, in particular it
+# does not restore the default behavior.
+.MAKE.SAVE_DOLLARS= yes
+.undef .MAKE.SAVE_DOLLARS
+VAR:= $$$$$$$$
+.if ${VAR} != "\$\$\$\$"
+. error
+.endif
+
+# The variable '.MAKE.SAVE_DOLLARS' not only affects literal '$$' on the
+# right-hand side of the assignment operator ':=', it also affects dollars
+# in indirect expressions.
+#
+# In this example, it affects the command in CMD itself, not the result of
+# running that command.
+.MAKE.SAVE_DOLLARS= no
+CMD= echo '$$$$$$$$'
+VAR:= ${CMD:sh}
+.if ${VAR} != "\$\$"
+. error
+.endif
+
+.MAKE.SAVE_DOLLARS= yes
+CMD= echo '$$$$$$$$'
+VAR:= ${CMD:sh}
+.if ${VAR} != "\$\$\$\$"
+. error
+.endif
+
+
+# In the modifier ':@var@body@', .MAKE.SAVE_DOLLARS does not affect the body.
+# In both cases, each '$$' is replaced with a single '$', no matter whether
+# directly or indirectly via another expression.
+.MAKE.SAVE_DOLLARS= no
+DOLLARS= $$$$$$$$
+VAR:= ${word:L:@word@$$$$$$$$-${DOLLARS}@}
+.if ${VAR} != "\$\$-\$\$"
+. error
+.endif
+
+.MAKE.SAVE_DOLLARS= yes
+DOLLARS= $$$$$$$$
+VAR:= ${word:L:@word@$$$$$$$$-${DOLLARS}@}
+.if ${VAR} != "\$\$-\$\$"
+. error
+.endif
-# TODO: Implementation
all:
- @:;
diff --git a/unit-tests/varname-dot-suffixes.exp b/unit-tests/varname-dot-suffixes.exp
new file mode 100644
index 000000000000..753ce20d9fa8
--- /dev/null
+++ b/unit-tests/varname-dot-suffixes.exp
@@ -0,0 +1,39 @@
+Global:delete .SUFFIXES (not found)
+Global: .MAKEFLAGS = -r -k -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0
+Global: .SUFFIXES = set ignored (read-only)
+Global: .SUFFIXES = append ignored (read-only)
+Global: _ =
+Var_Parse: ${.SUFFIXES::=assign} (eval-keep-dollar-and-undefined)
+Evaluating modifier ${.SUFFIXES::...} on value ".c .o .1 .err .tar.gz" (eval-keep-dollar-and-undefined, regular)
+Modifier part: "assign"
+Global: .SUFFIXES = assign ignored (read-only)
+Result of ${.SUFFIXES::=assign} is "" (eval-keep-dollar-and-undefined, regular)
+Global: _ =
+Var_Parse: ${preserve:L:_=.SUFFIXES} (eval-keep-dollar-and-undefined)
+Evaluating modifier ${preserve:L} on value "" (eval-keep-dollar-and-undefined, undefined)
+Result of ${preserve:L} is "preserve" (eval-keep-dollar-and-undefined, defined)
+Evaluating modifier ${preserve:_...} on value "preserve" (eval-keep-dollar-and-undefined, defined)
+Global: .SUFFIXES = preserve ignored (read-only)
+Result of ${preserve:_=.SUFFIXES} is "preserve" (eval-keep-dollar-and-undefined, defined)
+Global: _ = preserve
+Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0
+Var_Parse: ${1 2:L:@.SUFFIXES@${.SUFFIXES}@} != ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval-defined)
+Evaluating modifier ${1 2:L} on value "" (eval-defined, undefined)
+Result of ${1 2:L} is "1 2" (eval-defined, defined)
+Evaluating modifier ${1 2:@...} on value "1 2" (eval-defined, defined)
+Modifier part: ".SUFFIXES"
+Modifier part: "${.SUFFIXES}"
+ModifyWords: split "1 2" into 2 words
+Command: .SUFFIXES = 1 ignored (read-only)
+Var_Parse: ${.SUFFIXES} (eval-defined)
+ModifyWord_Loop: in "1", replace ".SUFFIXES" with "${.SUFFIXES}" to ".c .o .1 .err .tar.gz"
+Command: .SUFFIXES = 2 ignored (read-only)
+Var_Parse: ${.SUFFIXES} (eval-defined)
+ModifyWord_Loop: in "2", replace ".SUFFIXES" with "${.SUFFIXES}" to ".c .o .1 .err .tar.gz"
+Command:delete .SUFFIXES (not found)
+Result of ${1 2:@.SUFFIXES@${.SUFFIXES}@} is ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz" (eval-defined, defined)
+Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0 -d v -d
+Global: .MAKEFLAGS = -r -k -d v -d 0 -d v -d 0 -d v -d 0
+exit status 0
diff --git a/unit-tests/varname-dot-suffixes.mk b/unit-tests/varname-dot-suffixes.mk
new file mode 100644
index 000000000000..de8034a172cc
--- /dev/null
+++ b/unit-tests/varname-dot-suffixes.mk
@@ -0,0 +1,104 @@
+# $NetBSD: varname-dot-suffixes.mk,v 1.1 2021/12/12 22:16:48 rillig Exp $
+#
+# Tests for the special "variable" .SUFFIXES, which lists the suffixes that
+# have been registered for use in suffix transformation rules. Suffixes are
+# listed even if there is no actual transformation rule that uses them.
+#
+# The name '.SUFFIXES' does not refer to a real variable, instead it can be
+# used as a starting "variable name" for expressions like ${.SUFFIXES} or
+# ${.SUFFIXES:M*o}.
+
+# In the beginning, there are no suffix rules, the expression is thus empty.
+.if ${.SUFFIXES} != ""
+.endif
+
+# There is no actual variable named '.SUFFIXES', it is all made up.
+.if defined(.SUFFIXES)
+. error
+.endif
+
+# The suffixes list is still empty, and so is the "variable" '.SUFFIXES'.
+.if !empty(.SUFFIXES)
+. error
+.endif
+
+.SUFFIXES: .c .o .1 .err
+
+# The suffixes are listed in declaration order.
+.if ${.SUFFIXES} != ".c .o .1 .err"
+. error
+.endif
+
+# There is still no actual variable named '.SUFFIXES', it is all made up.
+.if defined(.SUFFIXES)
+. error
+.endif
+
+# Now the suffixes list is not empty anymore. It may seem strange that there
+# is no variable named '.SUFFIXES' but evaluating '${.SUFFIXES}' nevertheless
+# returns something. For all practical use cases, it's good enough though.
+.if empty(.SUFFIXES)
+. error
+.endif
+
+.SUFFIXES: .tar.gz
+
+# Changes to the suffixes list are reflected immediately.
+.if ${.SUFFIXES} != ".c .o .1 .err .tar.gz"
+. error
+.endif
+
+# Deleting .SUFFIXES has no effect since there is no actual variable of that
+# name.
+.MAKEFLAGS: -dv
+# expect: Global:delete .SUFFIXES (not found)
+.undef .SUFFIXES
+.MAKEFLAGS: -d0
+.if ${.SUFFIXES} != ".c .o .1 .err .tar.gz"
+. error
+.endif
+
+# The list of suffixes can only be modified using dependency declarations, any
+# attempt at setting the variable named '.SUFFIXES' is rejected.
+.MAKEFLAGS: -dv
+# expect: Global: .SUFFIXES = set ignored (read-only)
+.SUFFIXES= set
+# expect: Global: .SUFFIXES = append ignored (read-only)
+.SUFFIXES+= append
+# expect: Global: .SUFFIXES = assign ignored (read-only)
+_:= ${.SUFFIXES::=assign}
+# expect: Command: .SUFFIXES = preserve ignored (read-only)
+_:= ${preserve:L:_=.SUFFIXES}
+.MAKEFLAGS: -d0
+
+# Using the name '.SUFFIXES' in a .for loop looks strange because these
+# variable names are typically in singular form, and .for loops do not use
+# real variables either, they are made up as well, see directive-for.mk. The
+# replacement mechanism for the iteration variables takes precedence.
+.for .SUFFIXES in .c .o
+. if ${.SUFFIXES} != ".c" && ${.SUFFIXES} != ".o"
+. error
+. endif
+.endfor
+
+# After the .for loop, the expression '${.SUFFIXES}' refers to the list of
+# suffixes again.
+.if ${.SUFFIXES} != ".c .o .1 .err .tar.gz"
+. error
+.endif
+
+# Using the name '.SUFFIXES' in the modifier ':@var@body@' does not create an
+# actual variable either. Like in the .for loop, choosing the name
+# '.SUFFIXES' for the iteration variable is unusual. In ODE Make, the
+# convention for these iteration variables is to have dots at both ends, so
+# the name would be '.SUFFIXES.', furthermore the name of the iteration
+# variable is typically in singular form.
+.MAKEFLAGS: -dv
+# expect: Command: .SUFFIXES = 1 ignored (read-only)
+# expect: Command: .SUFFIXES = 2 ignored (read-only)
+.if ${1 2:L:@.SUFFIXES@${.SUFFIXES}@} != ".c .o .1 .err .tar.gz .c .o .1 .err .tar.gz"
+. error
+.endif
+.MAKEFLAGS: -d0
+
+all:
diff --git a/unit-tests/varname-empty.exp b/unit-tests/varname-empty.exp
index ec225c6973c8..75a3f4151a8c 100644
--- a/unit-tests/varname-empty.exp
+++ b/unit-tests/varname-empty.exp
@@ -2,10 +2,6 @@ Var_SetExpand: variable name "${:U}" expands to empty string, with value "cmdlin
Var_SetExpand: variable name "" expands to empty string, with value "cmdline-plain" - ignored
Global: .CURDIR = <curdir>
Var_Parse: ${MAKE_OBJDIR_CHECK_WRITABLE} (eval)
-Global: .OBJDIR = <curdir>
-Global:delete .PATH (not found)
-Global: .PATH = .
-Global: .PATH = . <curdir>
Global: .TARGETS =
Internal: MAKEFILE = varname-empty.mk
Global: .MAKE.MAKEFILES = varname-empty.mk