xref: /freebsd/contrib/bmake/unit-tests/varmod-edge.mk (revision a0409676120c1e558d0ade943019934e0f15118d)
1# $NetBSD: varmod-edge.mk,v 1.13 2020/10/24 08:46:08 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
13TESTS+=		M-paren
14INP.M-paren=	(parentheses) {braces} (opening closing) ()
15MOD.M-paren=	${INP.M-paren:M(*)}
16EXP.M-paren=	(parentheses) ()
17
18# The first closing brace matches the opening parenthesis.
19# The second closing brace actually ends the variable expression.
20#
21# XXX: This is unexpected but rarely occurs in practice.
22TESTS+=		M-mixed
23INP.M-mixed=	(paren-brace} (
24MOD.M-mixed=	${INP.M-mixed:M(*}}
25EXP.M-mixed=	(paren-brace}
26
27# After the :M modifier has parsed the pattern, only the closing brace
28# and the colon are unescaped. The other characters are left as-is.
29# To actually see this effect, the backslashes in the :M modifier need
30# to be doubled since single backslashes would simply be unescaped by
31# Str_Match.
32#
33# XXX: This is unexpected. The opening brace should also be unescaped.
34TESTS+=		M-unescape
35INP.M-unescape=	({}): \(\{\}\)\: \(\{}\):
36MOD.M-unescape=	${INP.M-unescape:M\\(\\{\\}\\)\\:}
37EXP.M-unescape=	\(\{}\):
38
39# When the :M and :N modifiers are parsed, the pattern finishes as soon
40# as open_parens + open_braces == closing_parens + closing_braces. This
41# means that ( and } form a matching pair.
42#
43# Nested variable expressions are not parsed as such. Instead, only the
44# parentheses and braces are counted. This leads to a parse error since
45# the nested expression is not "${:U*)}" but only "${:U*)", which is
46# missing the closing brace. The expression is evaluated anyway.
47# The final brace in the output comes from the end of M.nest-mix.
48#
49# XXX: This is unexpected but rarely occurs in practice.
50TESTS+=		M-nest-mix
51INP.M-nest-mix=	(parentheses)
52MOD.M-nest-mix=	${INP.M-nest-mix:M${:U*)}}
53EXP.M-nest-mix=	(parentheses)}
54# make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U
55
56# In contrast to parentheses and braces, the brackets are not counted
57# when the :M modifier is parsed since Makefile variables only take the
58# ${VAR} or $(VAR) forms, but not $[VAR].
59#
60# The final ] in the pattern is needed to close the character class.
61TESTS+=		M-nest-brk
62INP.M-nest-brk=	[ [[ [[[
63MOD.M-nest-brk=	${INP.M-nest-brk:M${:U[[[[[]}}
64EXP.M-nest-brk=	[
65
66# The pattern in the nested variable has an unclosed character class.
67# No error is reported though, and the pattern is closed implicitly.
68#
69# XXX: It is unexpected that no error is reported.
70# See str.c, function Str_Match.
71#
72# Before 2019-12-02, this test case triggered an out-of-bounds read
73# in Str_Match.
74TESTS+=		M-pat-err
75INP.M-pat-err=	[ [[ [[[
76MOD.M-pat-err=	${INP.M-pat-err:M${:U[[}}
77EXP.M-pat-err=	[
78
79# The first backslash does not escape the second backslash.
80# Therefore, the second backslash escapes the parenthesis.
81# This means that the pattern ends there.
82# The final } in the output comes from the end of MOD.M-bsbs.
83#
84# If the first backslash were to escape the second backslash, the first
85# closing brace would match the opening parenthesis (see M-mixed), and
86# the second closing brace would be needed to close the variable.
87# After that, the remaining backslash would escape the parenthesis in
88# the pattern, therefore (} would match.
89TESTS+=		M-bsbs
90INP.M-bsbs=	(} \( \(}
91MOD.M-bsbs=	${INP.M-bsbs:M\\(}}
92EXP.M-bsbs=	\(}
93#EXP.M-bsbs=	(}	# If the first backslash were to escape ...
94
95# The backslash in \( does not escape the parenthesis, therefore it
96# counts for the nesting level and matches with the first closing brace.
97# The second closing brace closes the variable, and the third is copied
98# literally.
99#
100# The second :M in the pattern is nested between ( and }, therefore it
101# does not start a new modifier.
102TESTS+=		M-bs1-par
103INP.M-bs1-par=	( (:M (:M} \( \(:M \(:M}
104MOD.M-bs1-par=	${INP.M-bs1-par:M\(:M*}}}
105EXP.M-bs1-par=	(:M}}
106
107# The double backslash is passed verbatim to the pattern matcher.
108# The Str_Match pattern is \\(:M*}, and there the backslash is unescaped.
109# Again, the ( takes place in the nesting level, and there is no way to
110# prevent this, no matter how many backslashes are used.
111TESTS+=		M-bs2-par
112INP.M-bs2-par=	( (:M (:M} \( \(:M \(:M}
113MOD.M-bs2-par=	${INP.M-bs2-par:M\\(:M*}}}
114EXP.M-bs2-par=	\(:M}}
115
116# Str_Match uses a recursive algorithm for matching the * patterns.
117# Make sure that it survives patterns with 128 asterisks.
118# That should be enough for all practical purposes.
119# To produce a stack overflow, just add more :Qs below.
120TESTS+=		M-128
121INP.M-128=	${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
122PAT.M-128=	${:U\\:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
123MOD.M-128=	${INP.M-128:M${PAT.M-128}}
124EXP.M-128=	${INP.M-128}
125
126# This is the normal SysV substitution. Nothing surprising here.
127TESTS+=		eq-ext
128INP.eq-ext=	file.c file.cc
129MOD.eq-ext=	${INP.eq-ext:%.c=%.o}
130EXP.eq-ext=	file.o file.cc
131
132# The SysV := modifier is greedy and consumes all the modifier text
133# up until the closing brace or parenthesis. The :Q may look like a
134# modifier, but it really isn't, that's why it appears in the output.
135TESTS+=		eq-q
136INP.eq-q=	file.c file.cc
137MOD.eq-q=	${INP.eq-q:%.c=%.o:Q}
138EXP.eq-q=	file.o:Q file.cc
139
140# The = in the := modifier can be escaped.
141TESTS+=		eq-bs
142INP.eq-bs=	file.c file.c=%.o
143MOD.eq-bs=	${INP.eq-bs:%.c\=%.o=%.ext}
144EXP.eq-bs=	file.c file.ext
145
146# Having only an escaped '=' results in a parse error.
147# The call to "pattern.lhs = ParseModifierPart" fails.
148TESTS+=		eq-esc
149INP.eq-esc=	file.c file...
150MOD.eq-esc=	${INP.eq-esc:a\=b}
151EXP.eq-esc=	# empty
152# make: Unfinished modifier for INP.eq-esc ('=' missing)
153
154TESTS+=		colon
155INP.colon=	value
156MOD.colon=	${INP.colon:}
157EXP.colon=	value
158
159TESTS+=		colons
160INP.colons=	value
161MOD.colons=	${INP.colons::::}
162EXP.colons=	# empty
163
164.for test in ${TESTS}
165.  if ${MOD.${test}} == ${EXP.${test}}
166.    info ok ${test}
167.  else
168.    warning error in ${test}: expected "${EXP.${test}}", got "${MOD.${test}}"
169.  endif
170.endfor
171
172all:
173	@echo ok
174