xref: /freebsd/contrib/bmake/unit-tests/varmod-edge.mk (revision 148ee84570001f46b7b667c86573d378101c3801)
1*148ee845SSimon J. Gerraty# $NetBSD: varmod-edge.mk,v 1.17 2023/06/01 20:56:35 rillig Exp $
249caa483SSimon J. Gerraty#
349caa483SSimon J. Gerraty# Tests for edge cases in variable modifiers.
449caa483SSimon J. Gerraty#
549caa483SSimon J. Gerraty# These tests demonstrate the current implementation in small examples.
649caa483SSimon J. Gerraty# They may contain surprising behavior.
749caa483SSimon J. Gerraty#
849caa483SSimon J. Gerraty# Each test consists of:
949caa483SSimon J. Gerraty# - INP, the input to the test
1049caa483SSimon J. Gerraty# - MOD, the expression for testing the modifier
1149caa483SSimon J. Gerraty# - EXP, the expected output
1249caa483SSimon J. Gerraty
1349caa483SSimon J. GerratyTESTS+=		M-paren
1449caa483SSimon J. GerratyINP.M-paren=	(parentheses) {braces} (opening closing) ()
1549caa483SSimon J. GerratyMOD.M-paren=	${INP.M-paren:M(*)}
1649caa483SSimon J. GerratyEXP.M-paren=	(parentheses) ()
1749caa483SSimon J. Gerraty
1849caa483SSimon J. Gerraty# The first closing brace matches the opening parenthesis.
1949caa483SSimon J. Gerraty# The second closing brace actually ends the variable expression.
2049caa483SSimon J. Gerraty#
2149caa483SSimon J. Gerraty# XXX: This is unexpected but rarely occurs in practice.
2249caa483SSimon J. GerratyTESTS+=		M-mixed
2349caa483SSimon J. GerratyINP.M-mixed=	(paren-brace} (
2449caa483SSimon J. GerratyMOD.M-mixed=	${INP.M-mixed:M(*}}
2549caa483SSimon J. GerratyEXP.M-mixed=	(paren-brace}
2649caa483SSimon J. Gerraty
2749caa483SSimon J. Gerraty# After the :M modifier has parsed the pattern, only the closing brace
2849caa483SSimon J. Gerraty# and the colon are unescaped. The other characters are left as-is.
2949caa483SSimon J. Gerraty# To actually see this effect, the backslashes in the :M modifier need
3049caa483SSimon J. Gerraty# to be doubled since single backslashes would simply be unescaped by
3149caa483SSimon J. Gerraty# Str_Match.
3249caa483SSimon J. Gerraty#
3349caa483SSimon J. Gerraty# XXX: This is unexpected. The opening brace should also be unescaped.
3449caa483SSimon J. GerratyTESTS+=		M-unescape
3549caa483SSimon J. GerratyINP.M-unescape=	({}): \(\{\}\)\: \(\{}\):
3649caa483SSimon J. GerratyMOD.M-unescape=	${INP.M-unescape:M\\(\\{\\}\\)\\:}
3749caa483SSimon J. GerratyEXP.M-unescape=	\(\{}\):
3849caa483SSimon J. Gerraty
3949caa483SSimon J. Gerraty# When the :M and :N modifiers are parsed, the pattern finishes as soon
4049caa483SSimon J. Gerraty# as open_parens + open_braces == closing_parens + closing_braces. This
4149caa483SSimon J. Gerraty# means that ( and } form a matching pair.
4249caa483SSimon J. Gerraty#
4349caa483SSimon J. Gerraty# Nested variable expressions are not parsed as such. Instead, only the
4449caa483SSimon J. Gerraty# parentheses and braces are counted. This leads to a parse error since
4549caa483SSimon J. Gerraty# the nested expression is not "${:U*)}" but only "${:U*)", which is
4649caa483SSimon J. Gerraty# missing the closing brace. The expression is evaluated anyway.
4749caa483SSimon J. Gerraty# The final brace in the output comes from the end of M.nest-mix.
4849caa483SSimon J. Gerraty#
4949caa483SSimon J. Gerraty# XXX: This is unexpected but rarely occurs in practice.
5049caa483SSimon J. GerratyTESTS+=		M-nest-mix
5149caa483SSimon J. GerratyINP.M-nest-mix=	(parentheses)
5249caa483SSimon J. GerratyMOD.M-nest-mix=	${INP.M-nest-mix:M${:U*)}}
5349caa483SSimon J. GerratyEXP.M-nest-mix=	(parentheses)}
54b0c40a00SSimon J. Gerraty# make: Unclosed variable expression, expecting '}' for modifier "U*)" of variable "" with value "*)"
5549caa483SSimon J. Gerraty
5649caa483SSimon J. Gerraty# In contrast to parentheses and braces, the brackets are not counted
5749caa483SSimon J. Gerraty# when the :M modifier is parsed since Makefile variables only take the
5849caa483SSimon J. Gerraty# ${VAR} or $(VAR) forms, but not $[VAR].
5949caa483SSimon J. Gerraty#
6049caa483SSimon J. Gerraty# The final ] in the pattern is needed to close the character class.
6149caa483SSimon J. GerratyTESTS+=		M-nest-brk
6249caa483SSimon J. GerratyINP.M-nest-brk=	[ [[ [[[
6349caa483SSimon J. GerratyMOD.M-nest-brk=	${INP.M-nest-brk:M${:U[[[[[]}}
6449caa483SSimon J. GerratyEXP.M-nest-brk=	[
6549caa483SSimon J. Gerraty
6649caa483SSimon J. Gerraty# The pattern in the nested variable has an unclosed character class.
6749caa483SSimon J. Gerraty# No error is reported though, and the pattern is closed implicitly.
6849caa483SSimon J. Gerraty#
6949caa483SSimon J. Gerraty# XXX: It is unexpected that no error is reported.
7049caa483SSimon J. Gerraty# See str.c, function Str_Match.
7149caa483SSimon J. Gerraty#
7249caa483SSimon J. Gerraty# Before 2019-12-02, this test case triggered an out-of-bounds read
7349caa483SSimon J. Gerraty# in Str_Match.
7449caa483SSimon J. GerratyTESTS+=		M-pat-err
7549caa483SSimon J. GerratyINP.M-pat-err=	[ [[ [[[
7649caa483SSimon J. GerratyMOD.M-pat-err=	${INP.M-pat-err:M${:U[[}}
7749caa483SSimon J. GerratyEXP.M-pat-err=	[
7849caa483SSimon J. Gerraty
7949caa483SSimon J. Gerraty# The first backslash does not escape the second backslash.
8049caa483SSimon J. Gerraty# Therefore, the second backslash escapes the parenthesis.
8149caa483SSimon J. Gerraty# This means that the pattern ends there.
8249caa483SSimon J. Gerraty# The final } in the output comes from the end of MOD.M-bsbs.
8349caa483SSimon J. Gerraty#
8449caa483SSimon J. Gerraty# If the first backslash were to escape the second backslash, the first
8549caa483SSimon J. Gerraty# closing brace would match the opening parenthesis (see M-mixed), and
8649caa483SSimon J. Gerraty# the second closing brace would be needed to close the variable.
8749caa483SSimon J. Gerraty# After that, the remaining backslash would escape the parenthesis in
8849caa483SSimon J. Gerraty# the pattern, therefore (} would match.
8949caa483SSimon J. GerratyTESTS+=		M-bsbs
9049caa483SSimon J. GerratyINP.M-bsbs=	(} \( \(}
9149caa483SSimon J. GerratyMOD.M-bsbs=	${INP.M-bsbs:M\\(}}
9249caa483SSimon J. GerratyEXP.M-bsbs=	\(}
9349caa483SSimon J. Gerraty#EXP.M-bsbs=	(}	# If the first backslash were to escape ...
9449caa483SSimon J. Gerraty
9549caa483SSimon J. Gerraty# The backslash in \( does not escape the parenthesis, therefore it
9649caa483SSimon J. Gerraty# counts for the nesting level and matches with the first closing brace.
9749caa483SSimon J. Gerraty# The second closing brace closes the variable, and the third is copied
9849caa483SSimon J. Gerraty# literally.
9949caa483SSimon J. Gerraty#
10049caa483SSimon J. Gerraty# The second :M in the pattern is nested between ( and }, therefore it
10149caa483SSimon J. Gerraty# does not start a new modifier.
10249caa483SSimon J. GerratyTESTS+=		M-bs1-par
10349caa483SSimon J. GerratyINP.M-bs1-par=	( (:M (:M} \( \(:M \(:M}
10449caa483SSimon J. GerratyMOD.M-bs1-par=	${INP.M-bs1-par:M\(:M*}}}
10549caa483SSimon J. GerratyEXP.M-bs1-par=	(:M}}
10649caa483SSimon J. Gerraty
10749caa483SSimon J. Gerraty# The double backslash is passed verbatim to the pattern matcher.
10849caa483SSimon J. Gerraty# The Str_Match pattern is \\(:M*}, and there the backslash is unescaped.
10949caa483SSimon J. Gerraty# Again, the ( takes place in the nesting level, and there is no way to
11049caa483SSimon J. Gerraty# prevent this, no matter how many backslashes are used.
11149caa483SSimon J. GerratyTESTS+=		M-bs2-par
11249caa483SSimon J. GerratyINP.M-bs2-par=	( (:M (:M} \( \(:M \(:M}
11349caa483SSimon J. GerratyMOD.M-bs2-par=	${INP.M-bs2-par:M\\(:M*}}}
11449caa483SSimon J. GerratyEXP.M-bs2-par=	\(:M}}
11549caa483SSimon J. Gerraty
11649caa483SSimon J. Gerraty# Str_Match uses a recursive algorithm for matching the * patterns.
11749caa483SSimon J. Gerraty# Make sure that it survives patterns with 128 asterisks.
11849caa483SSimon J. Gerraty# That should be enough for all practical purposes.
11949caa483SSimon J. Gerraty# To produce a stack overflow, just add more :Qs below.
12049caa483SSimon J. GerratyTESTS+=		M-128
12149caa483SSimon J. GerratyINP.M-128=	${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
12249caa483SSimon J. GerratyPAT.M-128=	${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
12349caa483SSimon J. GerratyMOD.M-128=	${INP.M-128:M${PAT.M-128}}
12449caa483SSimon J. GerratyEXP.M-128=	${INP.M-128}
12549caa483SSimon J. Gerraty
12649caa483SSimon J. Gerraty# This is the normal SysV substitution. Nothing surprising here.
12749caa483SSimon J. GerratyTESTS+=		eq-ext
12849caa483SSimon J. GerratyINP.eq-ext=	file.c file.cc
12949caa483SSimon J. GerratyMOD.eq-ext=	${INP.eq-ext:%.c=%.o}
13049caa483SSimon J. GerratyEXP.eq-ext=	file.o file.cc
13149caa483SSimon J. Gerraty
13249caa483SSimon J. Gerraty# The SysV := modifier is greedy and consumes all the modifier text
13349caa483SSimon J. Gerraty# up until the closing brace or parenthesis. The :Q may look like a
13449caa483SSimon J. Gerraty# modifier, but it really isn't, that's why it appears in the output.
13549caa483SSimon J. GerratyTESTS+=		eq-q
13649caa483SSimon J. GerratyINP.eq-q=	file.c file.cc
13749caa483SSimon J. GerratyMOD.eq-q=	${INP.eq-q:%.c=%.o:Q}
13849caa483SSimon J. GerratyEXP.eq-q=	file.o:Q file.cc
13949caa483SSimon J. Gerraty
14049caa483SSimon J. Gerraty# The = in the := modifier can be escaped.
14149caa483SSimon J. GerratyTESTS+=		eq-bs
14249caa483SSimon J. GerratyINP.eq-bs=	file.c file.c=%.o
14349caa483SSimon J. GerratyMOD.eq-bs=	${INP.eq-bs:%.c\=%.o=%.ext}
14449caa483SSimon J. GerratyEXP.eq-bs=	file.c file.ext
14549caa483SSimon J. Gerraty
1462c3632d1SSimon J. Gerraty# Having only an escaped '=' results in a parse error.
1472c3632d1SSimon J. Gerraty# The call to "pattern.lhs = ParseModifierPart" fails.
14849caa483SSimon J. GerratyTESTS+=		eq-esc
14949caa483SSimon J. GerratyINP.eq-esc=	file.c file...
15049caa483SSimon J. GerratyMOD.eq-esc=	${INP.eq-esc:a\=b}
15149caa483SSimon J. GerratyEXP.eq-esc=	# empty
1522c3632d1SSimon J. Gerraty# make: Unfinished modifier for INP.eq-esc ('=' missing)
15349caa483SSimon J. Gerraty
1542c3632d1SSimon J. GerratyTESTS+=		colon
1552c3632d1SSimon J. GerratyINP.colon=	value
1562c3632d1SSimon J. GerratyMOD.colon=	${INP.colon:}
1572c3632d1SSimon J. GerratyEXP.colon=	value
1582c3632d1SSimon J. Gerraty
1592c3632d1SSimon J. GerratyTESTS+=		colons
1602c3632d1SSimon J. GerratyINP.colons=	value
1612c3632d1SSimon J. GerratyMOD.colons=	${INP.colons::::}
1622c3632d1SSimon J. GerratyEXP.colons=	# empty
1632c3632d1SSimon J. Gerraty
16449caa483SSimon J. Gerraty.for test in ${TESTS}
165*148ee845SSimon J. Gerraty# expect+2: Unknown modifier ":"
166*148ee845SSimon J. Gerraty# expect+1: Unknown modifier ":"
16749caa483SSimon J. Gerraty.  if ${MOD.${test}} == ${EXP.${test}}
168*148ee845SSimon J. Gerraty# expect+16: ok M-paren
169*148ee845SSimon J. Gerraty# expect+15: ok M-mixed
170*148ee845SSimon J. Gerraty# expect+14: ok M-unescape
171*148ee845SSimon J. Gerraty# expect+13: ok M-nest-mix
172*148ee845SSimon J. Gerraty# expect+12: ok M-nest-brk
173*148ee845SSimon J. Gerraty# expect+11: ok M-pat-err
174*148ee845SSimon J. Gerraty# expect+10: ok M-bsbs
175*148ee845SSimon J. Gerraty# expect+09: ok M-bs1-par
176*148ee845SSimon J. Gerraty# expect+08: ok M-bs2-par
177*148ee845SSimon J. Gerraty# expect+07: ok M-128
178*148ee845SSimon J. Gerraty# expect+06: ok eq-ext
179*148ee845SSimon J. Gerraty# expect+05: ok eq-q
180*148ee845SSimon J. Gerraty# expect+04: ok eq-bs
181*148ee845SSimon J. Gerraty# expect+03: ok eq-esc
182*148ee845SSimon J. Gerraty# expect+02: ok colon
183*148ee845SSimon J. Gerraty# expect+01: ok colons
1842c3632d1SSimon J. Gerraty.    info ok ${test}
18549caa483SSimon J. Gerraty.  else
1862c3632d1SSimon J. Gerraty.    warning error in ${test}: expected "${EXP.${test}}", got "${MOD.${test}}"
18749caa483SSimon J. Gerraty.  endif
18849caa483SSimon J. Gerraty.endfor
1892c3632d1SSimon J. Gerraty
190b0c40a00SSimon J. Gerraty# Even in expressions based on an unnamed variable, there may be errors.
191b0c40a00SSimon J. Gerraty# XXX: The error message should mention the variable name of the expression,
192b0c40a00SSimon J. Gerraty# even though that name is empty in this case.
193*148ee845SSimon J. Gerraty# expect+2: Malformed conditional (${:Z})
194*148ee845SSimon J. Gerraty# expect+1: Unknown modifier "Z"
195b0c40a00SSimon J. Gerraty.if ${:Z}
196b0c40a00SSimon J. Gerraty.  error
197b0c40a00SSimon J. Gerraty.else
198b0c40a00SSimon J. Gerraty.  error
199b0c40a00SSimon J. Gerraty.endif
200b0c40a00SSimon J. Gerraty
201b0c40a00SSimon J. Gerraty# Even in expressions based on an unnamed variable, there may be errors.
202b0c40a00SSimon J. Gerraty#
203b0c40a00SSimon J. Gerraty# Before var.c 1.842 from 2021-02-23, the error message did not surround the
204b0c40a00SSimon J. Gerraty# variable name with quotes, leading to the rather confusing "Unfinished
205b0c40a00SSimon J. Gerraty# modifier for  (',' missing)", having two spaces in a row.
206b0c40a00SSimon J. Gerraty#
207b0c40a00SSimon J. Gerraty# XXX: The error message should report the filename:lineno.
208*148ee845SSimon J. Gerraty# expect+1: Malformed conditional (${:S,})
209b0c40a00SSimon J. Gerraty.if ${:S,}
210b0c40a00SSimon J. Gerraty.  error
211b0c40a00SSimon J. Gerraty.else
212b0c40a00SSimon J. Gerraty.  error
213b0c40a00SSimon J. Gerraty.endif
214b0c40a00SSimon J. Gerraty
2152c3632d1SSimon J. Gerratyall:
2162c3632d1SSimon J. Gerraty	@echo ok
217