Home | History | Annotate | Line # | Download | only in unit-tests
      1 # $NetBSD: varmod-edge.mk,v 1.37 2025/06/28 22:39:29 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 
     13 INP=	(parentheses) {braces} (opening closing) ()
     14 MOD=	${INP:M(*)}
     15 EXP=	(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.
     24 INP=	(paren-brace} (
     25 MOD=	${INP:M(*}}
     26 EXP=	(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.
     38 INP=	({}): \(\{\}\)\: \(\{}\):
     39 MOD=	${INP:M\\(\\{\\}\\)\\:}
     40 EXP=	\(\{}\):
     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.
     56 INP=	(parentheses)
     57 MOD=	${INP:M${:U*)}}
     58 EXP=	(parentheses)}
     59 # expect+1: 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.
     70 INP=	[ [[ [[[
     71 MOD=	${INP:M${:U[[[[[]}}
     72 EXP=	[
     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.
     84 INP=	[ [[ [[[
     85 MOD=	${INP:M${:U[[}}
     86 EXP=	[
     87 # expect+1: 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.
    102 INP=	(} \( \(}
    103 MOD=	${INP:M\\(}}
    104 EXP=	\(}
    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.
    117 INP=	( (:M (:M} \( \(:M \(:M}
    118 MOD=	${INP:M\(:M*}}}
    119 EXP=	(: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.
    128 INP=	( (:M (:M} \( \(:M \(:M}
    129 MOD=	${INP:M\\(:M*}}}
    130 EXP=	\(: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.
    138 INP=	${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,x,g}
    139 PAT=	${:U\\:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:Q:S,\\,*,g}
    140 MOD=	${INP:M${PAT}}
    141 EXP=	${INP}
    142 .if ${MOD} != ${EXP}
    143 .  warning expected "${EXP}", got "${MOD}"
    144 .endif
    145 
    146 # This is the normal SysV substitution. Nothing surprising here.
    147 INP=	file.c file.cc
    148 MOD=	${INP:%.c=%.o}
    149 EXP=	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.
    157 INP=	file.c file.cc
    158 MOD=	${INP:%.c=%.o:Q}
    159 EXP=	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.
    165 INP=	file.c file.c=%.o
    166 MOD=	${INP:%.c\=%.o=%.ext}
    167 EXP=	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.
    174 INP=	file.c file...
    175 MOD=	${INP:a\=b}
    176 EXP=	# empty
    177 # expect+1: Unfinished modifier after "a\=b}", expecting "="
    178 .if ${MOD} != ${EXP}
    179 .  warning expected "${EXP}", got "${MOD}"
    180 .endif
    181 
    182 INP=	value
    183 MOD=	${INP:}
    184 EXP=	value
    185 .if ${MOD} != ${EXP}
    186 .  warning expected "${EXP}", got "${MOD}"
    187 .endif
    188 
    189 INP=	value
    190 MOD=	${INP::::}
    191 EXP=	:}
    192 # expect+1: Unknown modifier "::"
    193 .if ${MOD} != ${EXP}
    194 .  warning expected "${EXP}", got "${MOD}"
    195 .endif
    196 
    197 # Even in expressions based on an unnamed variable, there may be errors.
    198 # expect+1: Unknown modifier ":Z"
    199 .if ${:Z}
    200 .  error
    201 .else
    202 .  error
    203 .endif
    204 
    205 # Even in expressions based on an unnamed variable, there may be errors.
    206 #
    207 # Before var.c 1.842 from 2021-02-23, the error message did not surround the
    208 # variable name with quotes, leading to the rather confusing "Unfinished
    209 # modifier for  (',' missing)", having two spaces in a row.
    210 #
    211 # expect+1: Unfinished modifier after "}", expecting ","
    212 .if ${:S,}
    213 .  error
    214 .else
    215 .  error
    216 .endif
    217