xref: /freebsd/contrib/bmake/unit-tests/varmod-edge.mk (revision 9bc300465e48e19d794d88d0c158a2adb92c7197)
1# $NetBSD: varmod-edge.mk,v 1.29 2024/07/09 17:07:23 rillig Exp $
2#
3# Tests for edge cases in variable modifiers.
4#
5# These tests demonstrate the current implementation in small examples.
6# They may contain surprising behavior.
7#
8# Each test consists of:
9# - INP, the input to the test
10# - MOD, the expression for testing the modifier
11# - EXP, the expected output
12
13INP=	(parentheses) {braces} (opening closing) ()
14MOD=	${INP:M(*)}
15EXP=	(parentheses) ()
16.if ${MOD} != ${EXP}
17.  warning expected "${EXP}", got "${MOD}"
18.endif
19
20# The first closing brace matches the opening parenthesis.
21# The second closing brace actually ends the expression.
22#
23# XXX: This is unexpected but rarely occurs in practice.
24INP=	(paren-brace} (
25MOD=	${INP:M(*}}
26EXP=	(paren-brace}
27.if ${MOD} != ${EXP}
28.  warning expected "${EXP}", got "${MOD}"
29.endif
30
31# After the :M modifier has parsed the pattern, only the closing brace
32# and the colon are unescaped. The other characters are left as-is.
33# To actually see this effect, the backslashes in the :M modifier need
34# to be doubled since single backslashes would simply be unescaped by
35# Str_Match.
36#
37# XXX: This is unexpected. The opening brace should also be unescaped.
38INP=	({}): \(\{\}\)\: \(\{}\):
39MOD=	${INP:M\\(\\{\\}\\)\\:}
40EXP=	\(\{}\):
41.if ${MOD} != ${EXP}
42.  warning expected "${EXP}", got "${MOD}"
43.endif
44
45# When the :M and :N modifiers are parsed, the pattern finishes as soon
46# as open_parens + open_braces == closing_parens + closing_braces. This
47# means that ( and } form a matching pair.
48#
49# Nested expressions are not parsed as such. Instead, only the
50# parentheses and braces are counted. This leads to a parse error since
51# the nested expression is not "${:U*)}" but only "${:U*)", which is
52# missing the closing brace. The expression is evaluated anyway.
53# The final brace in the output comes from the end of M.nest-mix.
54#
55# XXX: This is unexpected but rarely occurs in practice.
56INP=	(parentheses)
57MOD=	${INP:M${:U*)}}
58EXP=	(parentheses)}
59# 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.if ${MOD} != ${EXP}
61.  warning expected "${EXP}", got "${MOD}"
62.endif
63
64
65# In contrast to parentheses and braces, the brackets are not counted
66# when the :M modifier is parsed since Makefile expressions only take the
67# ${VAR} or $(VAR) forms, but not $[VAR].
68#
69# The final ] in the pattern is needed to close the character class.
70INP=	[ [[ [[[
71MOD=	${INP:M${:U[[[[[]}}
72EXP=	[
73.if ${MOD} != ${EXP}
74.  warning expected "${EXP}", got "${MOD}"
75.endif
76
77
78# The pattern in the nested variable has an unclosed character class.
79#
80# Before str.c 1.104 from 2024-07-06, no error was reported.
81#
82# Before 2019-12-02, this test case triggered an out-of-bounds read
83# in Str_Match.
84INP=	[ [[ [[[
85MOD=	${INP:M${:U[[}}
86EXP=	[
87# 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.if ${MOD} != ${EXP}
89.  warning expected "${EXP}", got "${MOD}"
90.endif
91
92# The first backslash does not escape the second backslash.
93# Therefore, the second backslash escapes the parenthesis.
94# This means that the pattern ends there.
95# The final } in the output comes from the end of MOD.
96#
97# If the first backslash were to escape the second backslash, the first
98# closing brace would match the opening parenthesis (see paren-brace), and
99# the second closing brace would be needed to close the variable.
100# After that, the remaining backslash would escape the parenthesis in
101# the pattern, therefore (} would match.
102INP=	(} \( \(}
103MOD=	${INP:M\\(}}
104EXP=	\(}
105#EXP=	(}	# If the first backslash were to escape ...
106.if ${MOD} != ${EXP}
107.  warning expected "${EXP}", got "${MOD}"
108.endif
109
110# The backslash in \( does not escape the parenthesis, therefore it
111# counts for the nesting level and matches with the first closing brace.
112# The second closing brace closes the variable, and the third is copied
113# literally.
114#
115# The second :M in the pattern is nested between ( and }, therefore it
116# does not start a new modifier.
117INP=	( (:M (:M} \( \(:M \(:M}
118MOD=	${INP:M\(:M*}}}
119EXP=	(:M}}
120.if ${MOD} != ${EXP}
121.  warning expected "${EXP}", got "${MOD}"
122.endif
123
124# The double backslash is passed verbatim to the pattern matcher.
125# The Str_Match pattern is \\(:M*}, and there the backslash is unescaped.
126# Again, the ( takes place in the nesting level, and there is no way to
127# prevent this, no matter how many backslashes are used.
128INP=	( (:M (:M} \( \(:M \(:M}
129MOD=	${INP:M\\(:M*}}}
130EXP=	\(:M}}
131.if ${MOD} != ${EXP}
132.  warning expected "${EXP}", got "${MOD}"
133.endif
134
135# Before str.c 1.48 from 2020-06-15, Str_Match used a recursive algorithm for
136# matching the '*' patterns and did not optimize for multiple '*' in a row.
137# Test a pattern with 65536 asterisks.
138INP=	${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
139PAT=	${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
140MOD=	${INP:M${PAT}}
141EXP=	${INP}
142.if ${MOD} != ${EXP}
143.  warning expected "${EXP}", got "${MOD}"
144.endif
145
146# This is the normal SysV substitution. Nothing surprising here.
147INP=	file.c file.cc
148MOD=	${INP:%.c=%.o}
149EXP=	file.o file.cc
150.if ${MOD} != ${EXP}
151.  warning expected "${EXP}", got "${MOD}"
152.endif
153
154# The SysV := modifier is greedy and consumes all the modifier text
155# up until the closing brace or parenthesis. The :Q may look like a
156# modifier, but it really isn't, that's why it appears in the output.
157INP=	file.c file.cc
158MOD=	${INP:%.c=%.o:Q}
159EXP=	file.o:Q file.cc
160.if ${MOD} != ${EXP}
161.  warning expected "${EXP}", got "${MOD}"
162.endif
163
164# The = in the := modifier can be escaped.
165INP=	file.c file.c=%.o
166MOD=	${INP:%.c\=%.o=%.ext}
167EXP=	file.c file.ext
168.if ${MOD} != ${EXP}
169.  warning expected "${EXP}", got "${MOD}"
170.endif
171
172# Having only an escaped '=' results in a parse error.
173# The call to "pattern.lhs = ParseModifierPart" fails.
174INP=	file.c file...
175MOD=	${INP:a\=b}
176EXP=	# empty
177# expect+1: while evaluating variable "MOD" with value "${INP:a\=b}": while evaluating variable "INP" with value "file.c file...": Unfinished modifier ('=' missing)
178.if ${MOD} != ${EXP}
179.  warning expected "${EXP}", got "${MOD}"
180.endif
181
182INP=	value
183MOD=	${INP:}
184EXP=	value
185.if ${MOD} != ${EXP}
186.  warning expected "${EXP}", got "${MOD}"
187.endif
188
189INP=	value
190MOD=	${INP::::}
191EXP=	# empty
192# expect+2: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "value": Unknown modifier ":"
193# expect+1: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "": Unknown modifier ":"
194.if ${MOD} != ${EXP}
195.  warning expected "${EXP}", got "${MOD}"
196.endif
197
198# Even in expressions based on an unnamed variable, there may be errors.
199# XXX: The error message should mention the variable name of the expression,
200# even though that name is empty in this case.
201# expect+2: Malformed conditional (${:Z})
202# expect+1: while evaluating "${:Z}" with value "": Unknown modifier "Z"
203.if ${:Z}
204.  error
205.else
206.  error
207.endif
208
209# Even in expressions based on an unnamed variable, there may be errors.
210#
211# Before var.c 1.842 from 2021-02-23, the error message did not surround the
212# variable name with quotes, leading to the rather confusing "Unfinished
213# modifier for  (',' missing)", having two spaces in a row.
214#
215# expect+2: while evaluating "${:S,}" with value "": Unfinished modifier (',' missing)
216# expect+1: Malformed conditional (${:S,})
217.if ${:S,}
218.  error
219.else
220.  error
221.endif
222