1*22619282SSimon J. Gerraty# $NetBSD: varmod-edge.mk,v 1.29 2024/07/09 17:07:23 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 13*22619282SSimon J. GerratyINP= (parentheses) {braces} (opening closing) () 14*22619282SSimon J. GerratyMOD= ${INP:M(*)} 15*22619282SSimon J. GerratyEXP= (parentheses) () 16*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 17*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 18*22619282SSimon 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. 24*22619282SSimon J. GerratyINP= (paren-brace} ( 25*22619282SSimon J. GerratyMOD= ${INP:M(*}} 26*22619282SSimon J. GerratyEXP= (paren-brace} 27*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 28*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 29*22619282SSimon 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. 38*22619282SSimon J. GerratyINP= ({}): \(\{\}\)\: \(\{}\): 39*22619282SSimon J. GerratyMOD= ${INP:M\\(\\{\\}\\)\\:} 40*22619282SSimon J. GerratyEXP= \(\{}\): 41*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 42*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 43*22619282SSimon 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. 56*22619282SSimon J. GerratyINP= (parentheses) 57*22619282SSimon J. GerratyMOD= ${INP:M${:U*)}} 58*22619282SSimon J. GerratyEXP= (parentheses)} 59*22619282SSimon J. Gerraty# 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*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 61*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 62*22619282SSimon J. Gerraty.endif 63*22619282SSimon J. Gerraty 6449caa483SSimon J. Gerraty 6549caa483SSimon J. Gerraty# In contrast to parentheses and braces, the brackets are not counted 66*22619282SSimon 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. 70*22619282SSimon J. GerratyINP= [ [[ [[[ 71*22619282SSimon J. GerratyMOD= ${INP:M${:U[[[[[]}} 72*22619282SSimon J. GerratyEXP= [ 73*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 74*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 75*22619282SSimon J. Gerraty.endif 76*22619282SSimon J. Gerraty 7749caa483SSimon J. Gerraty 7849caa483SSimon J. Gerraty# The pattern in the nested variable has an unclosed character class. 7949caa483SSimon J. Gerraty# 80*22619282SSimon 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. 84*22619282SSimon J. GerratyINP= [ [[ [[[ 85*22619282SSimon J. GerratyMOD= ${INP:M${:U[[}} 86*22619282SSimon J. GerratyEXP= [ 87*22619282SSimon J. Gerraty# 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*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 89*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 90*22619282SSimon 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. 95*22619282SSimon 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 98*22619282SSimon 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. 102*22619282SSimon J. GerratyINP= (} \( \(} 103*22619282SSimon J. GerratyMOD= ${INP:M\\(}} 104*22619282SSimon J. GerratyEXP= \(} 105*22619282SSimon J. Gerraty#EXP= (} # If the first backslash were to escape ... 106*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 107*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 108*22619282SSimon 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. 117*22619282SSimon J. GerratyINP= ( (:M (:M} \( \(:M \(:M} 118*22619282SSimon J. GerratyMOD= ${INP:M\(:M*}}} 119*22619282SSimon J. GerratyEXP= (:M}} 120*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 121*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 122*22619282SSimon 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. 128*22619282SSimon J. GerratyINP= ( (:M (:M} \( \(:M \(:M} 129*22619282SSimon J. GerratyMOD= ${INP:M\\(:M*}}} 130*22619282SSimon J. GerratyEXP= \(:M}} 131*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 132*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 133*22619282SSimon J. Gerraty.endif 13449caa483SSimon J. Gerraty 135*22619282SSimon J. Gerraty# Before str.c 1.48 from 2020-06-15, Str_Match used a recursive algorithm for 136*22619282SSimon J. Gerraty# matching the '*' patterns and did not optimize for multiple '*' in a row. 137*22619282SSimon J. Gerraty# Test a pattern with 65536 asterisks. 138*22619282SSimon J. GerratyINP= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g} 139*22619282SSimon J. GerratyPAT= ${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g} 140*22619282SSimon J. GerratyMOD= ${INP:M${PAT}} 141*22619282SSimon J. GerratyEXP= ${INP} 142*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 143*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 144*22619282SSimon J. Gerraty.endif 14549caa483SSimon J. Gerraty 14649caa483SSimon J. Gerraty# This is the normal SysV substitution. Nothing surprising here. 147*22619282SSimon J. GerratyINP= file.c file.cc 148*22619282SSimon J. GerratyMOD= ${INP:%.c=%.o} 149*22619282SSimon J. GerratyEXP= file.o file.cc 150*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 151*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 152*22619282SSimon 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. 157*22619282SSimon J. GerratyINP= file.c file.cc 158*22619282SSimon J. GerratyMOD= ${INP:%.c=%.o:Q} 159*22619282SSimon J. GerratyEXP= file.o:Q file.cc 160*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 161*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 162*22619282SSimon J. Gerraty.endif 16349caa483SSimon J. Gerraty 16449caa483SSimon J. Gerraty# The = in the := modifier can be escaped. 165*22619282SSimon J. GerratyINP= file.c file.c=%.o 166*22619282SSimon J. GerratyMOD= ${INP:%.c\=%.o=%.ext} 167*22619282SSimon J. GerratyEXP= file.c file.ext 168*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 169*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 170*22619282SSimon 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. 174*22619282SSimon J. GerratyINP= file.c file... 175*22619282SSimon J. GerratyMOD= ${INP:a\=b} 176*22619282SSimon J. GerratyEXP= # empty 177*22619282SSimon J. Gerraty# expect+1: while evaluating variable "MOD" with value "${INP:a\=b}": while evaluating variable "INP" with value "file.c file...": Unfinished modifier ('=' missing) 178*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 179*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 18049caa483SSimon J. Gerraty.endif 181*22619282SSimon J. Gerraty 182*22619282SSimon J. GerratyINP= value 183*22619282SSimon J. GerratyMOD= ${INP:} 184*22619282SSimon J. GerratyEXP= value 185*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 186*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 187*22619282SSimon J. Gerraty.endif 188*22619282SSimon J. Gerraty 189*22619282SSimon J. GerratyINP= value 190*22619282SSimon J. GerratyMOD= ${INP::::} 191*22619282SSimon J. GerratyEXP= # empty 192*22619282SSimon J. Gerraty# expect+2: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "value": Unknown modifier ":" 193*22619282SSimon J. Gerraty# expect+1: while evaluating variable "MOD" with value "${INP::::}": while evaluating variable "INP" with value "": Unknown modifier ":" 194*22619282SSimon J. Gerraty.if ${MOD} != ${EXP} 195*22619282SSimon J. Gerraty. warning expected "${EXP}", got "${MOD}" 196*22619282SSimon J. Gerraty.endif 1972c3632d1SSimon J. Gerraty 198b0c40a00SSimon J. Gerraty# Even in expressions based on an unnamed variable, there may be errors. 199b0c40a00SSimon J. Gerraty# XXX: The error message should mention the variable name of the expression, 200b0c40a00SSimon J. Gerraty# even though that name is empty in this case. 201148ee845SSimon J. Gerraty# expect+2: Malformed conditional (${:Z}) 202*22619282SSimon J. Gerraty# expect+1: while evaluating "${:Z}" with value "": Unknown modifier "Z" 203b0c40a00SSimon J. Gerraty.if ${:Z} 204b0c40a00SSimon J. Gerraty. error 205b0c40a00SSimon J. Gerraty.else 206b0c40a00SSimon J. Gerraty. error 207b0c40a00SSimon J. Gerraty.endif 208b0c40a00SSimon J. Gerraty 209b0c40a00SSimon J. Gerraty# Even in expressions based on an unnamed variable, there may be errors. 210b0c40a00SSimon J. Gerraty# 211b0c40a00SSimon J. Gerraty# Before var.c 1.842 from 2021-02-23, the error message did not surround the 212b0c40a00SSimon J. Gerraty# variable name with quotes, leading to the rather confusing "Unfinished 213b0c40a00SSimon J. Gerraty# modifier for (',' missing)", having two spaces in a row. 214b0c40a00SSimon J. Gerraty# 215*22619282SSimon J. Gerraty# expect+2: while evaluating "${:S,}" with value "": Unfinished modifier (',' missing) 216148ee845SSimon J. Gerraty# expect+1: Malformed conditional (${:S,}) 217b0c40a00SSimon J. Gerraty.if ${:S,} 218b0c40a00SSimon J. Gerraty. error 219b0c40a00SSimon J. Gerraty.else 220b0c40a00SSimon J. Gerraty. error 221b0c40a00SSimon J. Gerraty.endif 222