1 1.37 rillig # $NetBSD: varmod-edge.mk,v 1.37 2025/06/28 22:39:29 rillig Exp $ 2 1.1 rillig # 3 1.1 rillig # Tests for edge cases in variable modifiers. 4 1.1 rillig # 5 1.1 rillig # These tests demonstrate the current implementation in small examples. 6 1.1 rillig # They may contain surprising behavior. 7 1.1 rillig # 8 1.1 rillig # Each test consists of: 9 1.1 rillig # - INP, the input to the test 10 1.1 rillig # - MOD, the expression for testing the modifier 11 1.1 rillig # - EXP, the expected output 12 1.1 rillig 13 1.26 rillig INP= (parentheses) {braces} (opening closing) () 14 1.26 rillig MOD= ${INP:M(*)} 15 1.26 rillig EXP= (parentheses) () 16 1.26 rillig .if ${MOD} != ${EXP} 17 1.26 rillig . warning expected "${EXP}", got "${MOD}" 18 1.26 rillig .endif 19 1.1 rillig 20 1.1 rillig # The first closing brace matches the opening parenthesis. 21 1.18 rillig # The second closing brace actually ends the expression. 22 1.1 rillig # 23 1.1 rillig # XXX: This is unexpected but rarely occurs in practice. 24 1.26 rillig INP= (paren-brace} ( 25 1.26 rillig MOD= ${INP:M(*}} 26 1.26 rillig EXP= (paren-brace} 27 1.26 rillig .if ${MOD} != ${EXP} 28 1.26 rillig . warning expected "${EXP}", got "${MOD}" 29 1.26 rillig .endif 30 1.1 rillig 31 1.4 rillig # After the :M modifier has parsed the pattern, only the closing brace 32 1.4 rillig # and the colon are unescaped. The other characters are left as-is. 33 1.4 rillig # To actually see this effect, the backslashes in the :M modifier need 34 1.4 rillig # to be doubled since single backslashes would simply be unescaped by 35 1.4 rillig # Str_Match. 36 1.4 rillig # 37 1.4 rillig # XXX: This is unexpected. The opening brace should also be unescaped. 38 1.26 rillig INP= ({}): \(\{\}\)\: \(\{}\): 39 1.26 rillig MOD= ${INP:M\\(\\{\\}\\)\\:} 40 1.26 rillig EXP= \(\{}\): 41 1.26 rillig .if ${MOD} != ${EXP} 42 1.26 rillig . warning expected "${EXP}", got "${MOD}" 43 1.26 rillig .endif 44 1.4 rillig 45 1.1 rillig # When the :M and :N modifiers are parsed, the pattern finishes as soon 46 1.1 rillig # as open_parens + open_braces == closing_parens + closing_braces. This 47 1.1 rillig # means that ( and } form a matching pair. 48 1.1 rillig # 49 1.18 rillig # Nested expressions are not parsed as such. Instead, only the 50 1.1 rillig # parentheses and braces are counted. This leads to a parse error since 51 1.1 rillig # the nested expression is not "${:U*)}" but only "${:U*)", which is 52 1.1 rillig # missing the closing brace. The expression is evaluated anyway. 53 1.1 rillig # The final brace in the output comes from the end of M.nest-mix. 54 1.1 rillig # 55 1.1 rillig # XXX: This is unexpected but rarely occurs in practice. 56 1.26 rillig INP= (parentheses) 57 1.26 rillig MOD= ${INP:M${:U*)}} 58 1.26 rillig EXP= (parentheses)} 59 1.37 rillig # expect+1: Unclosed expression, expecting "}" for modifier "U*)" 60 1.26 rillig .if ${MOD} != ${EXP} 61 1.26 rillig . warning expected "${EXP}", got "${MOD}" 62 1.26 rillig .endif 63 1.24 rillig 64 1.1 rillig 65 1.1 rillig # In contrast to parentheses and braces, the brackets are not counted 66 1.26 rillig # when the :M modifier is parsed since Makefile expressions only take the 67 1.1 rillig # ${VAR} or $(VAR) forms, but not $[VAR]. 68 1.1 rillig # 69 1.1 rillig # The final ] in the pattern is needed to close the character class. 70 1.26 rillig INP= [ [[ [[[ 71 1.26 rillig MOD= ${INP:M${:U[[[[[]}} 72 1.26 rillig EXP= [ 73 1.26 rillig .if ${MOD} != ${EXP} 74 1.26 rillig . warning expected "${EXP}", got "${MOD}" 75 1.26 rillig .endif 76 1.1 rillig 77 1.28 rillig 78 1.1 rillig # The pattern in the nested variable has an unclosed character class. 79 1.1 rillig # 80 1.27 rillig # Before str.c 1.104 from 2024-07-06, no error was reported. 81 1.5 rillig # 82 1.5 rillig # Before 2019-12-02, this test case triggered an out-of-bounds read 83 1.5 rillig # in Str_Match. 84 1.26 rillig INP= [ [[ [[[ 85 1.26 rillig MOD= ${INP:M${:U[[}} 86 1.26 rillig EXP= [ 87 1.37 rillig # expect+1: Unfinished character list in pattern "[[" of modifier ":M" 88 1.26 rillig .if ${MOD} != ${EXP} 89 1.26 rillig . warning expected "${EXP}", got "${MOD}" 90 1.26 rillig .endif 91 1.1 rillig 92 1.2 rillig # The first backslash does not escape the second backslash. 93 1.2 rillig # Therefore, the second backslash escapes the parenthesis. 94 1.2 rillig # This means that the pattern ends there. 95 1.26 rillig # The final } in the output comes from the end of MOD. 96 1.2 rillig # 97 1.2 rillig # If the first backslash were to escape the second backslash, the first 98 1.26 rillig # closing brace would match the opening parenthesis (see paren-brace), and 99 1.2 rillig # the second closing brace would be needed to close the variable. 100 1.3 rillig # After that, the remaining backslash would escape the parenthesis in 101 1.3 rillig # the pattern, therefore (} would match. 102 1.26 rillig INP= (} \( \(} 103 1.26 rillig MOD= ${INP:M\\(}} 104 1.26 rillig EXP= \(} 105 1.26 rillig #EXP= (} # If the first backslash were to escape ... 106 1.26 rillig .if ${MOD} != ${EXP} 107 1.26 rillig . warning expected "${EXP}", got "${MOD}" 108 1.26 rillig .endif 109 1.2 rillig 110 1.4 rillig # The backslash in \( does not escape the parenthesis, therefore it 111 1.4 rillig # counts for the nesting level and matches with the first closing brace. 112 1.4 rillig # The second closing brace closes the variable, and the third is copied 113 1.4 rillig # literally. 114 1.4 rillig # 115 1.4 rillig # The second :M in the pattern is nested between ( and }, therefore it 116 1.4 rillig # does not start a new modifier. 117 1.26 rillig INP= ( (:M (:M} \( \(:M \(:M} 118 1.26 rillig MOD= ${INP:M\(:M*}}} 119 1.26 rillig EXP= (:M}} 120 1.26 rillig .if ${MOD} != ${EXP} 121 1.26 rillig . warning expected "${EXP}", got "${MOD}" 122 1.26 rillig .endif 123 1.4 rillig 124 1.4 rillig # The double backslash is passed verbatim to the pattern matcher. 125 1.4 rillig # The Str_Match pattern is \\(:M*}, and there the backslash is unescaped. 126 1.4 rillig # Again, the ( takes place in the nesting level, and there is no way to 127 1.4 rillig # prevent this, no matter how many backslashes are used. 128 1.26 rillig INP= ( (:M (:M} \( \(:M \(:M} 129 1.26 rillig MOD= ${INP:M\\(:M*}}} 130 1.26 rillig EXP= \(:M}} 131 1.26 rillig .if ${MOD} != ${EXP} 132 1.26 rillig . warning expected "${EXP}", got "${MOD}" 133 1.26 rillig .endif 134 1.26 rillig 135 1.26 rillig # Before str.c 1.48 from 2020-06-15, Str_Match used a recursive algorithm for 136 1.26 rillig # matching the '*' patterns and did not optimize for multiple '*' in a row. 137 1.26 rillig # Test a pattern with 65536 asterisks. 138 1.26 rillig INP= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g} 139 1.26 rillig PAT= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g} 140 1.26 rillig MOD= ${INP:M${PAT}} 141 1.26 rillig EXP= ${INP} 142 1.26 rillig .if ${MOD} != ${EXP} 143 1.26 rillig . warning expected "${EXP}", got "${MOD}" 144 1.26 rillig .endif 145 1.6 rillig 146 1.6 rillig # This is the normal SysV substitution. Nothing surprising here. 147 1.26 rillig INP= file.c file.cc 148 1.26 rillig MOD= ${INP:%.c=%.o} 149 1.26 rillig EXP= file.o file.cc 150 1.26 rillig .if ${MOD} != ${EXP} 151 1.26 rillig . warning expected "${EXP}", got "${MOD}" 152 1.26 rillig .endif 153 1.6 rillig 154 1.6 rillig # The SysV := modifier is greedy and consumes all the modifier text 155 1.6 rillig # up until the closing brace or parenthesis. The :Q may look like a 156 1.6 rillig # modifier, but it really isn't, that's why it appears in the output. 157 1.26 rillig INP= file.c file.cc 158 1.26 rillig MOD= ${INP:%.c=%.o:Q} 159 1.26 rillig EXP= file.o:Q file.cc 160 1.26 rillig .if ${MOD} != ${EXP} 161 1.26 rillig . warning expected "${EXP}", got "${MOD}" 162 1.26 rillig .endif 163 1.6 rillig 164 1.6 rillig # The = in the := modifier can be escaped. 165 1.26 rillig INP= file.c file.c=%.o 166 1.26 rillig MOD= ${INP:%.c\=%.o=%.ext} 167 1.26 rillig EXP= file.c file.ext 168 1.26 rillig .if ${MOD} != ${EXP} 169 1.26 rillig . warning expected "${EXP}", got "${MOD}" 170 1.26 rillig .endif 171 1.6 rillig 172 1.8 rillig # Having only an escaped '=' results in a parse error. 173 1.8 rillig # The call to "pattern.lhs = ParseModifierPart" fails. 174 1.26 rillig INP= file.c file... 175 1.26 rillig MOD= ${INP:a\=b} 176 1.26 rillig EXP= # empty 177 1.34 rillig # expect+1: Unfinished modifier after "a\=b}", expecting "=" 178 1.26 rillig .if ${MOD} != ${EXP} 179 1.26 rillig . warning expected "${EXP}", got "${MOD}" 180 1.26 rillig .endif 181 1.26 rillig 182 1.26 rillig INP= value 183 1.26 rillig MOD= ${INP:} 184 1.26 rillig EXP= value 185 1.26 rillig .if ${MOD} != ${EXP} 186 1.26 rillig . warning expected "${EXP}", got "${MOD}" 187 1.26 rillig .endif 188 1.26 rillig 189 1.26 rillig INP= value 190 1.26 rillig MOD= ${INP::::} 191 1.35 rillig EXP= :} 192 1.36 rillig # expect+1: Unknown modifier "::" 193 1.26 rillig .if ${MOD} != ${EXP} 194 1.26 rillig . warning expected "${EXP}", got "${MOD}" 195 1.26 rillig .endif 196 1.11 rillig 197 1.14 rillig # Even in expressions based on an unnamed variable, there may be errors. 198 1.36 rillig # expect+1: Unknown modifier ":Z" 199 1.14 rillig .if ${:Z} 200 1.14 rillig . error 201 1.14 rillig .else 202 1.14 rillig . error 203 1.14 rillig .endif 204 1.14 rillig 205 1.14 rillig # Even in expressions based on an unnamed variable, there may be errors. 206 1.15 rillig # 207 1.15 rillig # Before var.c 1.842 from 2021-02-23, the error message did not surround the 208 1.15 rillig # variable name with quotes, leading to the rather confusing "Unfinished 209 1.15 rillig # modifier for (',' missing)", having two spaces in a row. 210 1.15 rillig # 211 1.34 rillig # expect+1: Unfinished modifier after "}", expecting "," 212 1.14 rillig .if ${:S,} 213 1.14 rillig . error 214 1.14 rillig .else 215 1.14 rillig . error 216 1.14 rillig .endif 217