Sat Aug 1 15:16:15 2020 UTC ()
make(1): add test for ${VAR::::}

It's a bit unrealistic, but at least there are good diagnostics.


(rillig)
diff -r1.5 -r1.6 src/usr.bin/make/unit-tests/varmod-edge.exp
diff -r1.9 -r1.10 src/usr.bin/make/unit-tests/varmod-edge.mk

cvs diff -r1.5 -r1.6 src/usr.bin/make/unit-tests/varmod-edge.exp (switch to unified diff)

--- src/usr.bin/make/unit-tests/varmod-edge.exp 2020/08/01 15:13:45 1.5
+++ src/usr.bin/make/unit-tests/varmod-edge.exp 2020/08/01 15:16:15 1.6
@@ -1,18 +1,21 @@ @@ -1,18 +1,21 @@
1make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U 1make: Unclosed variable specification (expecting '}') for "" (value "*)") modifier U
2make: Unclosed substitution for INP.eq-esc (= missing) 2make: Unclosed substitution for INP.eq-esc (= missing)
 3make: Unknown modifier ':'
 4make: Unknown modifier ':'
3ok M-paren 5ok M-paren
4ok M-mixed 6ok M-mixed
5ok M-unescape 7ok M-unescape
6ok M-nest-mix 8ok M-nest-mix
7ok M-nest-brk 9ok M-nest-brk
8ok M-pat-err 10ok M-pat-err
9ok M-bsbs 11ok M-bsbs
10ok M-bs1-par 12ok M-bs1-par
11ok M-bs2-par 13ok M-bs2-par
12ok M-128 14ok M-128
13ok eq-ext 15ok eq-ext
14ok eq-q 16ok eq-q
15ok eq-bs 17ok eq-bs
16ok eq-esc 18ok eq-esc
17ok colon 19ok colon
 20ok colons
18exit status 0 21exit status 0

cvs diff -r1.9 -r1.10 src/usr.bin/make/unit-tests/varmod-edge.mk (switch to unified diff)

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