varmod-edge.mk revision 1.35 1 # $NetBSD: varmod-edge.mk,v 1.35 2025/03/29 16:44:14 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
13 INP= (parentheses) {braces} (opening closing) ()
14 MOD= ${INP:M(*)}
15 EXP= (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.
24 INP= (paren-brace} (
25 MOD= ${INP:M(*}}
26 EXP= (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.
38 INP= ({}): \(\{\}\)\: \(\{}\):
39 MOD= ${INP:M\\(\\{\\}\\)\\:}
40 EXP= \(\{}\):
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.
56 INP= (parentheses)
57 MOD= ${INP:M${:U*)}}
58 EXP= (parentheses)}
59 # expect+1: 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.
70 INP= [ [[ [[[
71 MOD= ${INP:M${:U[[[[[]}}
72 EXP= [
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.
84 INP= [ [[ [[[
85 MOD= ${INP:M${:U[[}}
86 EXP= [
87 # expect+1: 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.
102 INP= (} \( \(}
103 MOD= ${INP:M\\(}}
104 EXP= \(}
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.
117 INP= ( (:M (:M} \( \(:M \(:M}
118 MOD= ${INP:M\(:M*}}}
119 EXP= (: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.
128 INP= ( (:M (:M} \( \(:M \(:M}
129 MOD= ${INP:M\\(:M*}}}
130 EXP= \(: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.
138 INP= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
139 PAT= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
140 MOD= ${INP:M${PAT}}
141 EXP= ${INP}
142 .if ${MOD} != ${EXP}
143 . warning expected "${EXP}", got "${MOD}"
144 .endif
145
146 # This is the normal SysV substitution. Nothing surprising here.
147 INP= file.c file.cc
148 MOD= ${INP:%.c=%.o}
149 EXP= 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.
157 INP= file.c file.cc
158 MOD= ${INP:%.c=%.o:Q}
159 EXP= 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.
165 INP= file.c file.c=%.o
166 MOD= ${INP:%.c\=%.o=%.ext}
167 EXP= 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.
174 INP= file.c file...
175 MOD= ${INP:a\=b}
176 EXP= # empty
177 # expect+1: Unfinished modifier after "a\=b}", expecting "="
178 .if ${MOD} != ${EXP}
179 . warning expected "${EXP}", got "${MOD}"
180 .endif
181
182 INP= value
183 MOD= ${INP:}
184 EXP= value
185 .if ${MOD} != ${EXP}
186 . warning expected "${EXP}", got "${MOD}"
187 .endif
188
189 INP= value
190 MOD= ${INP::::}
191 EXP= :}
192 # expect+1: Unknown modifier ":"
193 .if ${MOD} != ${EXP}
194 . warning expected "${EXP}", got "${MOD}"
195 .endif
196
197 # Even in expressions based on an unnamed variable, there may be errors.
198 # expect+1: Unknown modifier "Z"
199 .if ${:Z}
200 . error
201 .else
202 . error
203 .endif
204
205 # Even in expressions based on an unnamed variable, there may be errors.
206 #
207 # Before var.c 1.842 from 2021-02-23, the error message did not surround the
208 # variable name with quotes, leading to the rather confusing "Unfinished
209 # modifier for (',' missing)", having two spaces in a row.
210 #
211 # expect+1: Unfinished modifier after "}", expecting ","
212 .if ${:S,}
213 . error
214 .else
215 . error
216 .endif
217