xref: /freebsd/contrib/bmake/unit-tests/varmod-edge.mk (revision 226192822cddc30cacecd55bccb48f39c653058c)
1*22619282SSimon J. Gerraty# $NetBSD: varmod-edge.mk,v 1.29 2024/07/09 17:07:23 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
13*22619282SSimon J. GerratyINP=	(parentheses) {braces} (opening closing) ()
14*22619282SSimon J. GerratyMOD=	${INP:M(*)}
15*22619282SSimon J. GerratyEXP=	(parentheses) ()
16*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
17*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
18*22619282SSimon J. Gerraty.endif
1949caa483SSimon J. Gerraty
2049caa483SSimon J. Gerraty# The first closing brace matches the opening parenthesis.
21d5e0a182SSimon J. Gerraty# The second closing brace actually ends the expression.
2249caa483SSimon J. Gerraty#
2349caa483SSimon J. Gerraty# XXX: This is unexpected but rarely occurs in practice.
24*22619282SSimon J. GerratyINP=	(paren-brace} (
25*22619282SSimon J. GerratyMOD=	${INP:M(*}}
26*22619282SSimon J. GerratyEXP=	(paren-brace}
27*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
28*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
29*22619282SSimon J. Gerraty.endif
3049caa483SSimon J. Gerraty
3149caa483SSimon J. Gerraty# After the :M modifier has parsed the pattern, only the closing brace
3249caa483SSimon J. Gerraty# and the colon are unescaped. The other characters are left as-is.
3349caa483SSimon J. Gerraty# To actually see this effect, the backslashes in the :M modifier need
3449caa483SSimon J. Gerraty# to be doubled since single backslashes would simply be unescaped by
3549caa483SSimon J. Gerraty# Str_Match.
3649caa483SSimon J. Gerraty#
3749caa483SSimon J. Gerraty# XXX: This is unexpected. The opening brace should also be unescaped.
38*22619282SSimon J. GerratyINP=	({}): \(\{\}\)\: \(\{}\):
39*22619282SSimon J. GerratyMOD=	${INP:M\\(\\{\\}\\)\\:}
40*22619282SSimon J. GerratyEXP=	\(\{}\):
41*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
42*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
43*22619282SSimon J. Gerraty.endif
4449caa483SSimon J. Gerraty
4549caa483SSimon J. Gerraty# When the :M and :N modifiers are parsed, the pattern finishes as soon
4649caa483SSimon J. Gerraty# as open_parens + open_braces == closing_parens + closing_braces. This
4749caa483SSimon J. Gerraty# means that ( and } form a matching pair.
4849caa483SSimon J. Gerraty#
49d5e0a182SSimon J. Gerraty# Nested expressions are not parsed as such. Instead, only the
5049caa483SSimon J. Gerraty# parentheses and braces are counted. This leads to a parse error since
5149caa483SSimon J. Gerraty# the nested expression is not "${:U*)}" but only "${:U*)", which is
5249caa483SSimon J. Gerraty# missing the closing brace. The expression is evaluated anyway.
5349caa483SSimon J. Gerraty# The final brace in the output comes from the end of M.nest-mix.
5449caa483SSimon J. Gerraty#
5549caa483SSimon J. Gerraty# XXX: This is unexpected but rarely occurs in practice.
56*22619282SSimon J. GerratyINP=	(parentheses)
57*22619282SSimon J. GerratyMOD=	${INP:M${:U*)}}
58*22619282SSimon J. GerratyEXP=	(parentheses)}
59*22619282SSimon J. Gerraty# expect+1: while evaluating variable "MOD" with value "${INP:M${:U*)}}": while evaluating variable "INP" with value "(parentheses)": while evaluating "${:U*)" with value "*)": Unclosed expression, expecting '}' for modifier "U*)"
60*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
61*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
62*22619282SSimon J. Gerraty.endif
63*22619282SSimon J. Gerraty
6449caa483SSimon J. Gerraty
6549caa483SSimon J. Gerraty# In contrast to parentheses and braces, the brackets are not counted
66*22619282SSimon J. Gerraty# when the :M modifier is parsed since Makefile expressions only take the
6749caa483SSimon J. Gerraty# ${VAR} or $(VAR) forms, but not $[VAR].
6849caa483SSimon J. Gerraty#
6949caa483SSimon J. Gerraty# The final ] in the pattern is needed to close the character class.
70*22619282SSimon J. GerratyINP=	[ [[ [[[
71*22619282SSimon J. GerratyMOD=	${INP:M${:U[[[[[]}}
72*22619282SSimon J. GerratyEXP=	[
73*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
74*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
75*22619282SSimon J. Gerraty.endif
76*22619282SSimon J. Gerraty
7749caa483SSimon J. Gerraty
7849caa483SSimon J. Gerraty# The pattern in the nested variable has an unclosed character class.
7949caa483SSimon J. Gerraty#
80*22619282SSimon J. Gerraty# Before str.c 1.104 from 2024-07-06, no error was reported.
8149caa483SSimon J. Gerraty#
8249caa483SSimon J. Gerraty# Before 2019-12-02, this test case triggered an out-of-bounds read
8349caa483SSimon J. Gerraty# in Str_Match.
84*22619282SSimon J. GerratyINP=	[ [[ [[[
85*22619282SSimon J. GerratyMOD=	${INP:M${:U[[}}
86*22619282SSimon J. GerratyEXP=	[
87*22619282SSimon J. Gerraty# expect+1: while evaluating variable "MOD" with value "${INP:M${:U[[}}": while evaluating variable "INP" with value "[ [[ [[[": Unfinished character list in pattern '[[' of modifier ':M'
88*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
89*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
90*22619282SSimon J. Gerraty.endif
9149caa483SSimon J. Gerraty
9249caa483SSimon J. Gerraty# The first backslash does not escape the second backslash.
9349caa483SSimon J. Gerraty# Therefore, the second backslash escapes the parenthesis.
9449caa483SSimon J. Gerraty# This means that the pattern ends there.
95*22619282SSimon J. Gerraty# The final } in the output comes from the end of MOD.
9649caa483SSimon J. Gerraty#
9749caa483SSimon J. Gerraty# If the first backslash were to escape the second backslash, the first
98*22619282SSimon J. Gerraty# closing brace would match the opening parenthesis (see paren-brace), and
9949caa483SSimon J. Gerraty# the second closing brace would be needed to close the variable.
10049caa483SSimon J. Gerraty# After that, the remaining backslash would escape the parenthesis in
10149caa483SSimon J. Gerraty# the pattern, therefore (} would match.
102*22619282SSimon J. GerratyINP=	(} \( \(}
103*22619282SSimon J. GerratyMOD=	${INP:M\\(}}
104*22619282SSimon J. GerratyEXP=	\(}
105*22619282SSimon J. Gerraty#EXP=	(}	# If the first backslash were to escape ...
106*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
107*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
108*22619282SSimon J. Gerraty.endif
10949caa483SSimon J. Gerraty
11049caa483SSimon J. Gerraty# The backslash in \( does not escape the parenthesis, therefore it
11149caa483SSimon J. Gerraty# counts for the nesting level and matches with the first closing brace.
11249caa483SSimon J. Gerraty# The second closing brace closes the variable, and the third is copied
11349caa483SSimon J. Gerraty# literally.
11449caa483SSimon J. Gerraty#
11549caa483SSimon J. Gerraty# The second :M in the pattern is nested between ( and }, therefore it
11649caa483SSimon J. Gerraty# does not start a new modifier.
117*22619282SSimon J. GerratyINP=	( (:M (:M} \( \(:M \(:M}
118*22619282SSimon J. GerratyMOD=	${INP:M\(:M*}}}
119*22619282SSimon J. GerratyEXP=	(:M}}
120*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
121*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
122*22619282SSimon J. Gerraty.endif
12349caa483SSimon J. Gerraty
12449caa483SSimon J. Gerraty# The double backslash is passed verbatim to the pattern matcher.
12549caa483SSimon J. Gerraty# The Str_Match pattern is \\(:M*}, and there the backslash is unescaped.
12649caa483SSimon J. Gerraty# Again, the ( takes place in the nesting level, and there is no way to
12749caa483SSimon J. Gerraty# prevent this, no matter how many backslashes are used.
128*22619282SSimon J. GerratyINP=	( (:M (:M} \( \(:M \(:M}
129*22619282SSimon J. GerratyMOD=	${INP:M\\(:M*}}}
130*22619282SSimon J. GerratyEXP=	\(:M}}
131*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
132*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
133*22619282SSimon J. Gerraty.endif
13449caa483SSimon J. Gerraty
135*22619282SSimon J. Gerraty# Before str.c 1.48 from 2020-06-15, Str_Match used a recursive algorithm for
136*22619282SSimon J. Gerraty# matching the '*' patterns and did not optimize for multiple '*' in a row.
137*22619282SSimon J. Gerraty# Test a pattern with 65536 asterisks.
138*22619282SSimon J. GerratyINP=	${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
139*22619282SSimon J. GerratyPAT=	${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
140*22619282SSimon J. GerratyMOD=	${INP:M${PAT}}
141*22619282SSimon J. GerratyEXP=	${INP}
142*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
143*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
144*22619282SSimon J. Gerraty.endif
14549caa483SSimon J. Gerraty
14649caa483SSimon J. Gerraty# This is the normal SysV substitution. Nothing surprising here.
147*22619282SSimon J. GerratyINP=	file.c file.cc
148*22619282SSimon J. GerratyMOD=	${INP:%.c=%.o}
149*22619282SSimon J. GerratyEXP=	file.o file.cc
150*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
151*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
152*22619282SSimon J. Gerraty.endif
15349caa483SSimon J. Gerraty
15449caa483SSimon J. Gerraty# The SysV := modifier is greedy and consumes all the modifier text
15549caa483SSimon J. Gerraty# up until the closing brace or parenthesis. The :Q may look like a
15649caa483SSimon J. Gerraty# modifier, but it really isn't, that's why it appears in the output.
157*22619282SSimon J. GerratyINP=	file.c file.cc
158*22619282SSimon J. GerratyMOD=	${INP:%.c=%.o:Q}
159*22619282SSimon J. GerratyEXP=	file.o:Q file.cc
160*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
161*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
162*22619282SSimon J. Gerraty.endif
16349caa483SSimon J. Gerraty
16449caa483SSimon J. Gerraty# The = in the := modifier can be escaped.
165*22619282SSimon J. GerratyINP=	file.c file.c=%.o
166*22619282SSimon J. GerratyMOD=	${INP:%.c\=%.o=%.ext}
167*22619282SSimon J. GerratyEXP=	file.c file.ext
168*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
169*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
170*22619282SSimon J. Gerraty.endif
17149caa483SSimon J. Gerraty
1722c3632d1SSimon J. Gerraty# Having only an escaped '=' results in a parse error.
1732c3632d1SSimon J. Gerraty# The call to "pattern.lhs = ParseModifierPart" fails.
174*22619282SSimon J. GerratyINP=	file.c file...
175*22619282SSimon J. GerratyMOD=	${INP:a\=b}
176*22619282SSimon J. GerratyEXP=	# empty
177*22619282SSimon J. Gerraty# expect+1: while evaluating variable "MOD" with value "${INP:a\=b}": while evaluating variable "INP" with value "file.c file...": Unfinished modifier ('=' missing)
178*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
179*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
18049caa483SSimon J. Gerraty.endif
181*22619282SSimon J. Gerraty
182*22619282SSimon J. GerratyINP=	value
183*22619282SSimon J. GerratyMOD=	${INP:}
184*22619282SSimon J. GerratyEXP=	value
185*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
186*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
187*22619282SSimon J. Gerraty.endif
188*22619282SSimon J. Gerraty
189*22619282SSimon J. GerratyINP=	value
190*22619282SSimon J. GerratyMOD=	${INP::::}
191*22619282SSimon J. GerratyEXP=	# empty
192*22619282SSimon J. Gerraty# expect+2: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "value": Unknown modifier ":"
193*22619282SSimon J. Gerraty# expect+1: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "": Unknown modifier ":"
194*22619282SSimon J. Gerraty.if ${MOD} != ${EXP}
195*22619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
196*22619282SSimon J. Gerraty.endif
1972c3632d1SSimon J. Gerraty
198b0c40a00SSimon J. Gerraty# Even in expressions based on an unnamed variable, there may be errors.
199b0c40a00SSimon J. Gerraty# XXX: The error message should mention the variable name of the expression,
200b0c40a00SSimon J. Gerraty# even though that name is empty in this case.
201148ee845SSimon J. Gerraty# expect+2: Malformed conditional (${:Z})
202*22619282SSimon J. Gerraty# expect+1: while evaluating "${:Z}" with value "": Unknown modifier "Z"
203b0c40a00SSimon J. Gerraty.if ${:Z}
204b0c40a00SSimon J. Gerraty.  error
205b0c40a00SSimon J. Gerraty.else
206b0c40a00SSimon J. Gerraty.  error
207b0c40a00SSimon J. Gerraty.endif
208b0c40a00SSimon J. Gerraty
209b0c40a00SSimon J. Gerraty# Even in expressions based on an unnamed variable, there may be errors.
210b0c40a00SSimon J. Gerraty#
211b0c40a00SSimon J. Gerraty# Before var.c 1.842 from 2021-02-23, the error message did not surround the
212b0c40a00SSimon J. Gerraty# variable name with quotes, leading to the rather confusing "Unfinished
213b0c40a00SSimon J. Gerraty# modifier for  (',' missing)", having two spaces in a row.
214b0c40a00SSimon J. Gerraty#
215*22619282SSimon J. Gerraty# expect+2: while evaluating "${:S,}" with value "": Unfinished modifier (',' missing)
216148ee845SSimon J. Gerraty# expect+1: Malformed conditional (${:S,})
217b0c40a00SSimon J. Gerraty.if ${:S,}
218b0c40a00SSimon J. Gerraty.  error
219b0c40a00SSimon J. Gerraty.else
220b0c40a00SSimon J. Gerraty.  error
221b0c40a00SSimon J. Gerraty.endif
222