xref: /freebsd/contrib/bmake/unit-tests/varmod-edge.mk (revision 6a7405f5a6b639682cacf01e35d561411ff556aa)
1*6a7405f5SSimon J. Gerraty# $NetBSD: varmod-edge.mk,v 1.33 2025/01/11 20:54:45 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
1322619282SSimon J. GerratyINP=	(parentheses) {braces} (opening closing) ()
1422619282SSimon J. GerratyMOD=	${INP:M(*)}
1522619282SSimon J. GerratyEXP=	(parentheses) ()
1622619282SSimon J. Gerraty.if ${MOD} != ${EXP}
1722619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
1822619282SSimon 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.
2422619282SSimon J. GerratyINP=	(paren-brace} (
2522619282SSimon J. GerratyMOD=	${INP:M(*}}
2622619282SSimon J. GerratyEXP=	(paren-brace}
2722619282SSimon J. Gerraty.if ${MOD} != ${EXP}
2822619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
2922619282SSimon 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.
3822619282SSimon J. GerratyINP=	({}): \(\{\}\)\: \(\{}\):
3922619282SSimon J. GerratyMOD=	${INP:M\\(\\{\\}\\)\\:}
4022619282SSimon J. GerratyEXP=	\(\{}\):
4122619282SSimon J. Gerraty.if ${MOD} != ${EXP}
4222619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
4322619282SSimon 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.
5622619282SSimon J. GerratyINP=	(parentheses)
5722619282SSimon J. GerratyMOD=	${INP:M${:U*)}}
5822619282SSimon J. GerratyEXP=	(parentheses)}
59*6a7405f5SSimon J. Gerraty# expect+1: Unclosed expression, expecting '}' for modifier "U*)"
6022619282SSimon J. Gerraty.if ${MOD} != ${EXP}
6122619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
6222619282SSimon J. Gerraty.endif
6322619282SSimon J. Gerraty
6449caa483SSimon J. Gerraty
6549caa483SSimon J. Gerraty# In contrast to parentheses and braces, the brackets are not counted
6622619282SSimon 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.
7022619282SSimon J. GerratyINP=	[ [[ [[[
7122619282SSimon J. GerratyMOD=	${INP:M${:U[[[[[]}}
7222619282SSimon J. GerratyEXP=	[
7322619282SSimon J. Gerraty.if ${MOD} != ${EXP}
7422619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
7522619282SSimon J. Gerraty.endif
7622619282SSimon J. Gerraty
7749caa483SSimon J. Gerraty
7849caa483SSimon J. Gerraty# The pattern in the nested variable has an unclosed character class.
7949caa483SSimon J. Gerraty#
8022619282SSimon 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.
8422619282SSimon J. GerratyINP=	[ [[ [[[
8522619282SSimon J. GerratyMOD=	${INP:M${:U[[}}
8622619282SSimon J. GerratyEXP=	[
87*6a7405f5SSimon J. Gerraty# expect+1: Unfinished character list in pattern '[[' of modifier ':M'
8822619282SSimon J. Gerraty.if ${MOD} != ${EXP}
8922619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
9022619282SSimon 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.
9522619282SSimon 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
9822619282SSimon 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.
10222619282SSimon J. GerratyINP=	(} \( \(}
10322619282SSimon J. GerratyMOD=	${INP:M\\(}}
10422619282SSimon J. GerratyEXP=	\(}
10522619282SSimon J. Gerraty#EXP=	(}	# If the first backslash were to escape ...
10622619282SSimon J. Gerraty.if ${MOD} != ${EXP}
10722619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
10822619282SSimon 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.
11722619282SSimon J. GerratyINP=	( (:M (:M} \( \(:M \(:M}
11822619282SSimon J. GerratyMOD=	${INP:M\(:M*}}}
11922619282SSimon J. GerratyEXP=	(:M}}
12022619282SSimon J. Gerraty.if ${MOD} != ${EXP}
12122619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
12222619282SSimon 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.
12822619282SSimon J. GerratyINP=	( (:M (:M} \( \(:M \(:M}
12922619282SSimon J. GerratyMOD=	${INP:M\\(:M*}}}
13022619282SSimon J. GerratyEXP=	\(:M}}
13122619282SSimon J. Gerraty.if ${MOD} != ${EXP}
13222619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
13322619282SSimon J. Gerraty.endif
13449caa483SSimon J. Gerraty
13522619282SSimon J. Gerraty# Before str.c 1.48 from 2020-06-15, Str_Match used a recursive algorithm for
13622619282SSimon J. Gerraty# matching the '*' patterns and did not optimize for multiple '*' in a row.
13722619282SSimon J. Gerraty# Test a pattern with 65536 asterisks.
13822619282SSimon J. GerratyINP=	${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
13922619282SSimon J. GerratyPAT=	${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
14022619282SSimon J. GerratyMOD=	${INP:M${PAT}}
14122619282SSimon J. GerratyEXP=	${INP}
14222619282SSimon J. Gerraty.if ${MOD} != ${EXP}
14322619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
14422619282SSimon J. Gerraty.endif
14549caa483SSimon J. Gerraty
14649caa483SSimon J. Gerraty# This is the normal SysV substitution. Nothing surprising here.
14722619282SSimon J. GerratyINP=	file.c file.cc
14822619282SSimon J. GerratyMOD=	${INP:%.c=%.o}
14922619282SSimon J. GerratyEXP=	file.o file.cc
15022619282SSimon J. Gerraty.if ${MOD} != ${EXP}
15122619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
15222619282SSimon 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.
15722619282SSimon J. GerratyINP=	file.c file.cc
15822619282SSimon J. GerratyMOD=	${INP:%.c=%.o:Q}
15922619282SSimon J. GerratyEXP=	file.o:Q file.cc
16022619282SSimon J. Gerraty.if ${MOD} != ${EXP}
16122619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
16222619282SSimon J. Gerraty.endif
16349caa483SSimon J. Gerraty
16449caa483SSimon J. Gerraty# The = in the := modifier can be escaped.
16522619282SSimon J. GerratyINP=	file.c file.c=%.o
16622619282SSimon J. GerratyMOD=	${INP:%.c\=%.o=%.ext}
16722619282SSimon J. GerratyEXP=	file.c file.ext
16822619282SSimon J. Gerraty.if ${MOD} != ${EXP}
16922619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
17022619282SSimon 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.
17422619282SSimon J. GerratyINP=	file.c file...
17522619282SSimon J. GerratyMOD=	${INP:a\=b}
17622619282SSimon J. GerratyEXP=	# empty
177*6a7405f5SSimon J. Gerraty# expect+1: Unfinished modifier ('=' missing)
17822619282SSimon J. Gerraty.if ${MOD} != ${EXP}
17922619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
18049caa483SSimon J. Gerraty.endif
18122619282SSimon J. Gerraty
18222619282SSimon J. GerratyINP=	value
18322619282SSimon J. GerratyMOD=	${INP:}
18422619282SSimon J. GerratyEXP=	value
18522619282SSimon J. Gerraty.if ${MOD} != ${EXP}
18622619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
18722619282SSimon J. Gerraty.endif
18822619282SSimon J. Gerraty
18922619282SSimon J. GerratyINP=	value
19022619282SSimon J. GerratyMOD=	${INP::::}
19122619282SSimon J. GerratyEXP=	# empty
192*6a7405f5SSimon J. Gerraty# expect+2: Unknown modifier ":"
193*6a7405f5SSimon J. Gerraty# expect+1: Unknown modifier ":"
19422619282SSimon J. Gerraty.if ${MOD} != ${EXP}
19522619282SSimon J. Gerraty.  warning expected "${EXP}", got "${MOD}"
19622619282SSimon J. Gerraty.endif
1972c3632d1SSimon J. Gerraty
198b0c40a00SSimon J. Gerraty# Even in expressions based on an unnamed variable, there may be errors.
199*6a7405f5SSimon J. Gerraty# expect+1: Unknown modifier "Z"
200b0c40a00SSimon J. Gerraty.if ${:Z}
201b0c40a00SSimon J. Gerraty.  error
202b0c40a00SSimon J. Gerraty.else
203b0c40a00SSimon J. Gerraty.  error
204b0c40a00SSimon J. Gerraty.endif
205b0c40a00SSimon J. Gerraty
206b0c40a00SSimon J. Gerraty# Even in expressions based on an unnamed variable, there may be errors.
207b0c40a00SSimon J. Gerraty#
208b0c40a00SSimon J. Gerraty# Before var.c 1.842 from 2021-02-23, the error message did not surround the
209b0c40a00SSimon J. Gerraty# variable name with quotes, leading to the rather confusing "Unfinished
210b0c40a00SSimon J. Gerraty# modifier for  (',' missing)", having two spaces in a row.
211b0c40a00SSimon J. Gerraty#
212*6a7405f5SSimon J. Gerraty# expect+1: Unfinished modifier (',' missing)
213b0c40a00SSimon J. Gerraty.if ${:S,}
214b0c40a00SSimon J. Gerraty.  error
215b0c40a00SSimon J. Gerraty.else
216b0c40a00SSimon J. Gerraty.  error
217b0c40a00SSimon J. Gerraty.endif
218