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