| 1 | #!/usr/bin/env bash
 | 
| 2 | #
 | 
| 3 | # Usage:
 | 
| 4 | #   test/parse-errors.sh <function name>
 | 
| 5 | 
 | 
| 6 | set -o nounset
 | 
| 7 | set -o pipefail
 | 
| 8 | set -o errexit
 | 
| 9 | 
 | 
| 10 | source test/common.sh
 | 
| 11 | source test/sh-assert.sh  # _assert-sh-status
 | 
| 12 | 
 | 
| 13 | # We can't really run with OSH=bash, because the exit status is often different
 | 
| 14 | 
 | 
| 15 | # Although it would be nice to IGNORE errors and them some how preview errors.
 | 
| 16 | 
 | 
| 17 | OSH=${OSH:-bin/osh}
 | 
| 18 | YSH=${YSH:-bin/ysh}
 | 
| 19 | 
 | 
| 20 | # More detailed assertions - TODO: remove these?
 | 
| 21 | 
 | 
| 22 | _assert-status-2() {
 | 
| 23 |   ### An interface where you can pass flags like -O test-parse_backslash
 | 
| 24 | 
 | 
| 25 |   local message=$0
 | 
| 26 |   _assert-sh-status 2 $OSH $message "$@"
 | 
| 27 | }
 | 
| 28 | 
 | 
| 29 | _assert-status-2-here() {
 | 
| 30 |   _assert-status-2 "$@" -c "$(cat)"
 | 
| 31 | }
 | 
| 32 | 
 | 
| 33 | _runtime-parse-error() {
 | 
| 34 |   ### Assert that a parse error happens at runtime, e.g. for [ z z ]
 | 
| 35 | 
 | 
| 36 |   _osh-error-X 2 "$@"
 | 
| 37 | }
 | 
| 38 | 
 | 
| 39 | #
 | 
| 40 | # Cases
 | 
| 41 | #
 | 
| 42 | 
 | 
| 43 | # All in osh/word_parse.py
 | 
| 44 | test-patsub() {
 | 
| 45 |   _osh-should-parse 'echo ${x/}'
 | 
| 46 |   _osh-should-parse 'echo ${x//}'
 | 
| 47 | 
 | 
| 48 |   _osh-should-parse 'echo ${x/foo}'  # pat 'foo', no mode, replace empty
 | 
| 49 | 
 | 
| 50 |   _osh-should-parse 'echo ${x//foo}'  # pat 'foo', replace mode '/', replace empty
 | 
| 51 |   _osh-should-parse 'echo ${x/%foo}'  # same as above
 | 
| 52 | 
 | 
| 53 |   _osh-should-parse 'echo ${x///foo}'
 | 
| 54 | 
 | 
| 55 |   _osh-should-parse 'echo ${x///}'   # found and fixed bug
 | 
| 56 |   _osh-should-parse 'echo ${x/%/}'   # pat '', replace mode '%', replace ''
 | 
| 57 | 
 | 
| 58 |   _osh-should-parse 'echo ${x////}'  # pat '/', replace mode '/', replace empty
 | 
| 59 |   _osh-should-parse 'echo ${x/%//}'  # pat '', replace mode '%', replace '/'
 | 
| 60 | 
 | 
| 61 |   # Newline in replacement pattern
 | 
| 62 |   _osh-should-parse 'echo ${x//foo/replace
 | 
| 63 | }'
 | 
| 64 |   _osh-should-parse 'echo ${x//foo/replace$foo}'
 | 
| 65 | }
 | 
| 66 | 
 | 
| 67 | test-slice() {
 | 
| 68 |   _osh-should-parse '${foo:42}'
 | 
| 69 |   _osh-should-parse '${foo:42+1}'
 | 
| 70 | 
 | 
| 71 |   # Slicing
 | 
| 72 |   _osh-parse-error 'echo ${a:1;}'
 | 
| 73 |   _osh-parse-error 'echo ${a:1:2;}'
 | 
| 74 | }
 | 
| 75 | 
 | 
| 76 | # osh/word_parse.py
 | 
| 77 | test-word-parse() {
 | 
| 78 |   _osh-parse-error 'echo ${'
 | 
| 79 | 
 | 
| 80 |   _osh-parse-error 'echo ${a[@Z'
 | 
| 81 | 
 | 
| 82 |   _osh-parse-error 'echo ${x.}'
 | 
| 83 |   _osh-parse-error 'echo ${!x.}'
 | 
| 84 | 
 | 
| 85 |   # I don't seem to be able to tickle errors here
 | 
| 86 |   #_osh-parse-error 'echo ${a:-}'
 | 
| 87 |   #_osh-parse-error 'echo ${a#}'
 | 
| 88 | 
 | 
| 89 |   _osh-parse-error 'echo ${#a.'
 | 
| 90 | 
 | 
| 91 |   # for (( ))
 | 
| 92 |   _osh-parse-error 'for (( i = 0; i < 10; i++ ;'
 | 
| 93 |   # Hm not sure about this
 | 
| 94 |   _osh-parse-error 'for (( i = 0; i < 10; i++ /'
 | 
| 95 | 
 | 
| 96 |   _osh-parse-error 'echo @(extglob|foo'
 | 
| 97 | 
 | 
| 98 |   # Copied from osh/word_parse_test.py.  Bugs were found while writing
 | 
| 99 |   # core/completion_test.py.
 | 
| 100 | 
 | 
| 101 |   _osh-parse-error '${undef:-'
 | 
| 102 |   _osh-parse-error '${undef:-$'
 | 
| 103 |   _osh-parse-error '${undef:-$F'
 | 
| 104 | 
 | 
| 105 |   _osh-parse-error '${x@'
 | 
| 106 |   _osh-parse-error '${x@Q'
 | 
| 107 | 
 | 
| 108 |   _osh-parse-error '${x%'
 | 
| 109 | 
 | 
| 110 |   _osh-parse-error '${x/'
 | 
| 111 |   _osh-parse-error '${x/a/'
 | 
| 112 |   _osh-parse-error '${x/a/b'
 | 
| 113 |   _osh-parse-error '${x:'
 | 
| 114 | }
 | 
| 115 | 
 | 
| 116 | test-dparen() {
 | 
| 117 |   # (( ))
 | 
| 118 | 
 | 
| 119 |   _osh-should-parse '(())'
 | 
| 120 |   _osh-should-parse '(( ))'
 | 
| 121 |   _osh-parse-error '(( )'
 | 
| 122 |   _osh-parse-error '(( )x'
 | 
| 123 |   #_osh-should-parse '$(echo $(( 1 + 2 )) )'
 | 
| 124 | 
 | 
| 125 |   # Hard case
 | 
| 126 |   _osh-should-parse '$(echo $(( 1 + 2 )))'
 | 
| 127 |   _osh-should-parse '$( (()))'
 | 
| 128 | 
 | 
| 129 |   # More
 | 
| 130 |   _osh-parse-error '(( 1 + 2 /'
 | 
| 131 |   _osh-parse-error '(( 1 + 2 )/'
 | 
| 132 |   _osh-parse-error '(( 1'
 | 
| 133 |   _osh-parse-error '(('
 | 
| 134 | }
 | 
| 135 | 
 | 
| 136 | test-arith-sub() {
 | 
| 137 |   # $(( ))
 | 
| 138 | 
 | 
| 139 |   _osh-should-parse 'echo $(( ))'
 | 
| 140 |   _osh-should-parse 'echo $(())'
 | 
| 141 |   _osh-parse-error 'echo $(()x'
 | 
| 142 | 
 | 
| 143 |   _osh-parse-error 'echo $(()'
 | 
| 144 | 
 | 
| 145 |   _osh-parse-error 'echo $(( 1 + 2 ;'
 | 
| 146 |   _osh-parse-error 'echo $(( 1 + 2 );'
 | 
| 147 |   _osh-parse-error 'echo $(( '
 | 
| 148 |   _osh-parse-error 'echo $(( 1'
 | 
| 149 | }
 | 
| 150 | 
 | 
| 151 | 
 | 
| 152 | test-array-literal() {
 | 
| 153 |   # Array literal with invalid TokenWord.
 | 
| 154 |   _osh-parse-error 'a=(1 & 2)'
 | 
| 155 |   _osh-parse-error 'a= (1 2)'
 | 
| 156 |   _osh-parse-error 'a=(1 2'
 | 
| 157 |   _osh-parse-error 'a=(1 ${2@} )'  # error in word inside array literal
 | 
| 158 | }
 | 
| 159 | 
 | 
| 160 | test-arith-context() {
 | 
| 161 |   # Disable Oil stuff for osh_{parse,eval}.asan
 | 
| 162 |   if false; then
 | 
| 163 |     # Non-standard arith sub $[1 + 2]
 | 
| 164 |     _osh-parse-error 'echo $[ 1 + 2 ;'
 | 
| 165 | 
 | 
| 166 |     # What's going on here?   No location info?
 | 
| 167 |     _osh-parse-error 'echo $[ 1 + 2 /'
 | 
| 168 | 
 | 
| 169 |     _osh-parse-error 'echo $[ 1 + 2 / 3'
 | 
| 170 |     _osh-parse-error 'echo $['
 | 
| 171 |   fi
 | 
| 172 | 
 | 
| 173 |   # Should be an error
 | 
| 174 |   _osh-parse-error 'a[x+]=1'
 | 
| 175 | 
 | 
| 176 |   # Check what happens when you wrap
 | 
| 177 |   # This could use more detail - it needs the eval location
 | 
| 178 |   _osh-error-2 'eval a[x+]=1'
 | 
| 179 | 
 | 
| 180 |   _osh-parse-error 'a[]=1'
 | 
| 181 | 
 | 
| 182 |   _osh-parse-error 'a[*]=1'
 | 
| 183 | 
 | 
| 184 |   # These errors are different because the arithmetic lexer mode has } but not
 | 
| 185 |   # {.  May be changed later.
 | 
| 186 |   _osh-parse-error '(( a + { ))'
 | 
| 187 |   _osh-parse-error '(( a + } ))'
 | 
| 188 | 
 | 
| 189 | }
 | 
| 190 | 
 | 
| 191 | test-arith-integration() {
 | 
| 192 |   # Regression: these were not parse errors, but should be!
 | 
| 193 |   _osh-parse-error 'echo $((a b))'
 | 
| 194 |   _osh-parse-error '((a b))'
 | 
| 195 | 
 | 
| 196 |   # Empty arithmetic expressions
 | 
| 197 |   _osh-should-parse 'for ((x=0; x<5; x++)); do echo $x; done'
 | 
| 198 |   _osh-should-parse 'for ((; x<5; x++)); do echo $x; done'
 | 
| 199 |   _osh-should-parse 'for ((; ; x++)); do echo $x; done'
 | 
| 200 |   _osh-should-parse 'for ((; ;)); do echo $x; done'
 | 
| 201 | 
 | 
| 202 |   # Extra tokens on the end of each expression
 | 
| 203 |   _osh-parse-error 'for ((x=0; x<5; x++ b)); do echo $x; done'
 | 
| 204 | 
 | 
| 205 |   _osh-parse-error 'for ((x=0 b; x<5; x++)); do echo $x; done'
 | 
| 206 |   _osh-parse-error 'for ((x=0; x<5 b; x++)); do echo $x; done'
 | 
| 207 | 
 | 
| 208 |   _osh-parse-error '${a:1+2 b}'
 | 
| 209 |   _osh-parse-error '${a:1+2:3+4 b}'
 | 
| 210 | 
 | 
| 211 |   _osh-parse-error '${a[1+2 b]}'
 | 
| 212 | }
 | 
| 213 | 
 | 
| 214 | test-arith-expr() {
 | 
| 215 |   # BUG: the token is off here
 | 
| 216 |   _osh-parse-error '$(( 1 + + ))'
 | 
| 217 | 
 | 
| 218 |   # BUG: not a great error either
 | 
| 219 |   _osh-parse-error '$(( 1 2 ))'
 | 
| 220 | 
 | 
| 221 |   # Triggered a crash!
 | 
| 222 |   _osh-parse-error '$(( - ; ))'
 | 
| 223 | 
 | 
| 224 |   # NOTE: This is confusing, should point to ` for command context?
 | 
| 225 |   _osh-parse-error '$(( ` ))'
 | 
| 226 | 
 | 
| 227 |   _osh-parse-error '$(( $ ))'
 | 
| 228 | 
 | 
| 229 |   # Invalid assignments
 | 
| 230 |   _osh-parse-error '$(( x+1 = 42 ))'
 | 
| 231 |   _osh-parse-error '$(( (x+42)++ ))'
 | 
| 232 |   _osh-parse-error '$(( ++(x+42) ))'
 | 
| 233 | 
 | 
| 234 |   # Note these aren't caught because '1' is an ArithWord like 0x$x
 | 
| 235 |   #_osh-parse-error '$(( 1 = foo ))'
 | 
| 236 |   #_osh-parse-error '$(( 1++ ))'
 | 
| 237 |   #_osh-parse-error '$(( ++1 ))'
 | 
| 238 | }
 | 
| 239 | 
 | 
| 240 | test-command-sub() {
 | 
| 241 |   _osh-parse-error ' 
 | 
| 242 |     echo line 2
 | 
| 243 |     echo $( echo '
 | 
| 244 |   _osh-parse-error ' 
 | 
| 245 |     echo line 2
 | 
| 246 |     echo ` echo '
 | 
| 247 | 
 | 
| 248 |   # This is source.Reparsed('backticks', ...)
 | 
| 249 | 
 | 
| 250 |   # Both unclosed
 | 
| 251 |   _osh-parse-error '
 | 
| 252 |     echo line 2
 | 
| 253 |     echo ` echo \` '
 | 
| 254 | 
 | 
| 255 |   # Only the inner one is unclosed
 | 
| 256 |   _osh-parse-error '
 | 
| 257 |     echo line 2
 | 
| 258 |     echo ` echo \`unclosed ` '
 | 
| 259 | 
 | 
| 260 |   _osh-parse-error 'echo `for x in`'
 | 
| 261 | }
 | 
| 262 | 
 | 
| 263 | test-bool-expr() {
 | 
| 264 |   # Extra word
 | 
| 265 |   _osh-parse-error '[[ a b ]]'
 | 
| 266 |   _osh-parse-error '[[ a "a"$(echo hi)"b" ]]'
 | 
| 267 | 
 | 
| 268 |   # Wrong error message
 | 
| 269 |   _osh-parse-error '[[ a == ]]'
 | 
| 270 | 
 | 
| 271 |   if false; then
 | 
| 272 |     # Invalid regex
 | 
| 273 |     # These are currently only detected at runtime.
 | 
| 274 |     _osh-parse-error '[[ $var =~ * ]]'
 | 
| 275 |     _osh-parse-error '[[ $var =~ + ]]'
 | 
| 276 |   fi
 | 
| 277 | 
 | 
| 278 |   # Unbalanced parens
 | 
| 279 |   _osh-parse-error '[[ ( 1 == 2 - ]]'
 | 
| 280 | 
 | 
| 281 |   _osh-parse-error '[[ == ]]'
 | 
| 282 |   _osh-parse-error '[[ ) ]]'
 | 
| 283 |   _osh-parse-error '[[ ( ]]'
 | 
| 284 | 
 | 
| 285 |   _osh-parse-error '[[ ;;; ]]'
 | 
| 286 |   _osh-parse-error '[['
 | 
| 287 | 
 | 
| 288 |   # Expected right )
 | 
| 289 |   _osh-parse-error '[[ ( a == b foo${var} ]]'
 | 
| 290 | }
 | 
| 291 | 
 | 
| 292 | test-regex-nix() {
 | 
| 293 |   ### Based on Nix bug
 | 
| 294 | 
 | 
| 295 |   # Nix idiom - added space
 | 
| 296 |   _osh-should-parse '
 | 
| 297 | if [[ ! (" ${params[*]} " =~ " -shared " || " ${params[*]} " =~ " -static " ) ]]; then
 | 
| 298 |   echo hi
 | 
| 299 | fi
 | 
| 300 | '
 | 
| 301 | 
 | 
| 302 |   # (x) is part of the regex
 | 
| 303 |   _osh-should-parse '
 | 
| 304 | if [[ (foo =~ (x) ) ]]; then
 | 
| 305 |   echo hi
 | 
| 306 | fi
 | 
| 307 | '
 | 
| 308 |   # Nix idiom - reduced
 | 
| 309 |   _osh-should-parse '
 | 
| 310 | if [[ (foo =~ x) ]]; then
 | 
| 311 |   echo hi
 | 
| 312 | fi
 | 
| 313 | '
 | 
| 314 | 
 | 
| 315 |   # Nix idiom - original
 | 
| 316 |   _osh-should-parse '
 | 
| 317 | if [[ ! (" ${params[*]} " =~ " -shared " || " ${params[*]} " =~ " -static ") ]]; then
 | 
| 318 |   echo hi
 | 
| 319 | fi
 | 
| 320 | '
 | 
| 321 | }
 | 
| 322 | 
 | 
| 323 | test-regex-pipe() {
 | 
| 324 |   # Pipe in outer expression - it becomes Lit_Other, which is fine
 | 
| 325 | 
 | 
| 326 |   # Well we need a special rule for this probably
 | 
| 327 |   local s='[[ a =~ b|c ]]'
 | 
| 328 |   bash -n -c "$s"
 | 
| 329 |   _osh-should-parse "$s"
 | 
| 330 | }
 | 
| 331 | 
 | 
| 332 | test-regex-space() {
 | 
| 333 |   # initial space
 | 
| 334 |   _osh-should-parse '[[ a =~ ( ) ]]'
 | 
| 335 |   _osh-should-parse '[[ a =~ (b c) ]]'
 | 
| 336 |   _osh-should-parse '[[ a =~ (a b)(c d) ]]'
 | 
| 337 | 
 | 
| 338 |   # Hm bash allows newline inside (), but not outside
 | 
| 339 |   # I feel like we don't need to duplicate this
 | 
| 340 | 
 | 
| 341 |   local s='[[ a =~ (b
 | 
| 342 | c) ]]'
 | 
| 343 |   bash -n -c "$s"
 | 
| 344 |   echo bash=$?
 | 
| 345 | 
 | 
| 346 |   _osh-should-parse "$s"
 | 
| 347 | }
 | 
| 348 | 
 | 
| 349 | test-regex-right-paren() {
 | 
| 350 |   # BashRegex lexer mode
 | 
| 351 |   _osh-should-parse '[[ a =~ b ]]'
 | 
| 352 |   _osh-should-parse '[[ a =~ (b) ]]'  # this is a regex
 | 
| 353 |   _osh-should-parse '[[ (a =~ b) ]]'  # this is grouping
 | 
| 354 |   _osh-should-parse '[[ (a =~ (b)) ]]'  # regex and grouping!
 | 
| 355 | 
 | 
| 356 |   _osh-parse-error '[[ (a =~ ('  # EOF
 | 
| 357 |   _osh-parse-error '[[ (a =~ (b'  # EOF
 | 
| 358 | 
 | 
| 359 |   return
 | 
| 360 |   # Similar thing for extglob
 | 
| 361 |   _osh-should-parse '[[ a == b ]]'
 | 
| 362 |   _osh-should-parse '[[ a == @(b) ]]'  # this is a regex
 | 
| 363 |   _osh-should-parse '[[ (a == b) ]]'  # this is grouping
 | 
| 364 |   _osh-should-parse '[[ (a == @(b)) ]]'  # regex and grouping!
 | 
| 365 | }
 | 
| 366 | 
 | 
| 367 | 
 | 
| 368 | # These don't have any location information.
 | 
| 369 | test-test-builtin() {
 | 
| 370 |   # Some of these come from osh/bool_parse.py, and some from
 | 
| 371 |   # osh/builtin_bracket.py.
 | 
| 372 | 
 | 
| 373 |   # Extra token
 | 
| 374 |   _runtime-parse-error '[ x -a y f ]'
 | 
| 375 |   _runtime-parse-error 'test x -a y f'
 | 
| 376 | 
 | 
| 377 |   # Missing closing ]
 | 
| 378 |   _runtime-parse-error '[ x '
 | 
| 379 | 
 | 
| 380 |   # Hm some of these errors are wonky.  Need positions.
 | 
| 381 |   _runtime-parse-error '[ x x ]'
 | 
| 382 | 
 | 
| 383 |   _runtime-parse-error '[ x x "a b" ]'
 | 
| 384 | 
 | 
| 385 |   # This is a runtime error but is handled similarly
 | 
| 386 |   _runtime-parse-error '[ -t xxx ]'
 | 
| 387 | 
 | 
| 388 |   _runtime-parse-error '[ \( x -a -y -a z ]'
 | 
| 389 | 
 | 
| 390 |   # -o tests if an option is enabled.
 | 
| 391 |   #_osh-parse-error '[ -o x ]'
 | 
| 392 | }
 | 
| 393 | 
 | 
| 394 | test-printf-builtin() {
 | 
| 395 |   _runtime-parse-error 'printf %'
 | 
| 396 |   _runtime-parse-error 'printf [%Z]'
 | 
| 397 | 
 | 
| 398 |   _runtime-parse-error 'printf -v "-invalid-" %s foo'
 | 
| 399 | }
 | 
| 400 | 
 | 
| 401 | test-other-builtins() {
 | 
| 402 |   _runtime-parse-error 'shift 1 2'
 | 
| 403 |   _runtime-parse-error 'shift zzz'
 | 
| 404 | 
 | 
| 405 |   _runtime-parse-error 'pushd x y'
 | 
| 406 |   _runtime-parse-error 'pwd -x'
 | 
| 407 | 
 | 
| 408 |   _runtime-parse-error 'pp x foo a-x'
 | 
| 409 | 
 | 
| 410 |   _runtime-parse-error 'wait zzz'
 | 
| 411 |   _runtime-parse-error 'wait %jobspec-not-supported'
 | 
| 412 | 
 | 
| 413 |   _runtime-parse-error 'unset invalid-var-name'
 | 
| 414 |   _runtime-parse-error 'getopts 'hc:' invalid-var-name'
 | 
| 415 | }
 | 
| 416 | 
 | 
| 417 | test-quoted-strings() {
 | 
| 418 |   _osh-parse-error '"unterminated double'
 | 
| 419 | 
 | 
| 420 |   _osh-parse-error "'unterminated single"
 | 
| 421 | 
 | 
| 422 |   _osh-parse-error '
 | 
| 423 |   "unterminated double multiline
 | 
| 424 |   line 1
 | 
| 425 |   line 2'
 | 
| 426 | 
 | 
| 427 |   _osh-parse-error "
 | 
| 428 |   'unterminated single multiline
 | 
| 429 |   line 1
 | 
| 430 |   line 2"
 | 
| 431 | }
 | 
| 432 | 
 | 
| 433 | test-braced-var-sub() {
 | 
| 434 |   # These should have ! for a prefix query
 | 
| 435 |   _osh-parse-error 'echo ${x*}'
 | 
| 436 |   _osh-parse-error 'echo ${x@}'
 | 
| 437 | 
 | 
| 438 |   _osh-parse-error 'echo ${x.}'
 | 
| 439 | }
 | 
| 440 | 
 | 
| 441 | test-cmd-parse() {
 | 
| 442 |   _osh-parse-error 'FOO=1 break'
 | 
| 443 |   _osh-parse-error 'break 1 2'
 | 
| 444 | 
 | 
| 445 |   _osh-parse-error 'x"y"() { echo hi; }'
 | 
| 446 | 
 | 
| 447 |   _osh-parse-error 'function x"y" { echo hi; }'
 | 
| 448 | 
 | 
| 449 |   _osh-parse-error '}'
 | 
| 450 | 
 | 
| 451 |   _osh-parse-error 'case foo in *) echo '
 | 
| 452 |   _osh-parse-error 'case foo in x|) echo '
 | 
| 453 | 
 | 
| 454 |   _osh-parse-error 'ls foo|'
 | 
| 455 |   _osh-parse-error 'ls foo&&'
 | 
| 456 | 
 | 
| 457 |   _osh-parse-error 'foo()'
 | 
| 458 | 
 | 
| 459 |   # parse_ignored
 | 
| 460 |   _osh-should-parse 'break >out'
 | 
| 461 |   _ysh-parse-error 'break >out'
 | 
| 462 | 
 | 
| 463 |   # Unquoted (
 | 
| 464 |   _osh-parse-error '[ ( x ]'
 | 
| 465 | }
 | 
| 466 | 
 | 
| 467 | test-append() {
 | 
| 468 |   # from spec/test-append.test.sh.  bash treats this as a runtime error, but it's a
 | 
| 469 |   # parse error in OSH.
 | 
| 470 |   _osh-parse-error 'a[-1]+=(4 5)'
 | 
| 471 | }
 | 
| 472 | 
 | 
| 473 | test-redirect() {
 | 
| 474 |   _osh-parse-error 'echo < <<'
 | 
| 475 |   _osh-parse-error 'echo $( echo > >>  )'
 | 
| 476 | }
 | 
| 477 | 
 | 
| 478 | test-simple-command() {
 | 
| 479 |   _osh-parse-error 'PYTHONPATH=. FOO=(1 2) python'
 | 
| 480 |   # not statically detected after dynamic assignment
 | 
| 481 |   #_osh-parse-error 'echo foo FOO=(1 2)'
 | 
| 482 | 
 | 
| 483 |   _osh-parse-error 'PYTHONPATH+=1 python'
 | 
| 484 | 
 | 
| 485 |   # Space sensitivity: disallow =
 | 
| 486 |   _osh-parse-error '=var'
 | 
| 487 |   _osh-parse-error '=f(x)'
 | 
| 488 | 
 | 
| 489 |   _ysh-parse-error '=var'
 | 
| 490 |   _ysh-parse-error '=f(x)'
 | 
| 491 | }
 | 
| 492 | 
 | 
| 493 | # Old code?  All these pass
 | 
| 494 | DISABLED-assign() {
 | 
| 495 |   _osh-parse-error 'local name$x'
 | 
| 496 |   _osh-parse-error 'local "ab"'
 | 
| 497 |   _osh-parse-error 'local a.b'
 | 
| 498 | 
 | 
| 499 |   _osh-parse-error 'FOO=1 local foo=1'
 | 
| 500 | }
 | 
| 501 | 
 | 
| 502 | # I can't think of any other here doc error conditions except arith/var/command
 | 
| 503 | # substitution, and unterminated.
 | 
| 504 | test-here-doc() {
 | 
| 505 |   # Arith in here doc
 | 
| 506 |   _osh-parse-error 'cat <<EOF
 | 
| 507 | $(( 1 * ))  
 | 
| 508 | EOF
 | 
| 509 | '
 | 
| 510 | 
 | 
| 511 |   # Varsub in here doc
 | 
| 512 |   _osh-parse-error 'cat <<EOF
 | 
| 513 | invalid: ${a!}
 | 
| 514 | EOF
 | 
| 515 | '
 | 
| 516 | 
 | 
| 517 |   _osh-parse-error 'cat <<EOF
 | 
| 518 | $(for x in )
 | 
| 519 | EOF
 | 
| 520 | '
 | 
| 521 | }
 | 
| 522 | 
 | 
| 523 | test-here-doc-delimiter() {
 | 
| 524 |   # NOTE: This is more like the case where.
 | 
| 525 |   _osh-parse-error 'cat << $(invalid here end)'
 | 
| 526 | 
 | 
| 527 |   # TODO: Arith parser doesn't have location information
 | 
| 528 |   _osh-parse-error 'cat << $((1+2))'
 | 
| 529 |   _osh-parse-error 'cat << a=(1 2 3)'
 | 
| 530 |   _osh-parse-error 'cat << \a$(invalid)'
 | 
| 531 | 
 | 
| 532 |   # Actually the $invalid part should be highlighted... yeah an individual
 | 
| 533 |   # part is the problem.
 | 
| 534 |   #"cat << 'single'$(invalid)"
 | 
| 535 |   _osh-parse-error 'cat << "double"$(invalid)'
 | 
| 536 |   _osh-parse-error 'cat << ~foo/$(invalid)'
 | 
| 537 |   _osh-parse-error 'cat << $var/$(invalid)'
 | 
| 538 | }
 | 
| 539 | 
 | 
| 540 | test-args-parse-builtin() {
 | 
| 541 |   _runtime-parse-error 'read -x'  # invalid
 | 
| 542 |   _runtime-parse-error 'builtin read -x'  # ditto
 | 
| 543 | 
 | 
| 544 |   _runtime-parse-error 'read -n'  # expected argument for -n
 | 
| 545 |   _runtime-parse-error 'read -n x'  # expected integer
 | 
| 546 | 
 | 
| 547 |   _runtime-parse-error 'set -o errexit +o oops'
 | 
| 548 | 
 | 
| 549 |   # not implemented yet
 | 
| 550 |   #_osh-parse-error 'read -t x'  # expected floating point number
 | 
| 551 | 
 | 
| 552 |   # TODO:
 | 
| 553 |   # - invalid choice
 | 
| 554 |   # - Oil flags: invalid long flag, boolean argument, etc.
 | 
| 555 | }
 | 
| 556 | 
 | 
| 557 | test-args-parse-more() {
 | 
| 558 |   _runtime-parse-error 'set -z'
 | 
| 559 |   _runtime-parse-error 'shopt -s foo'
 | 
| 560 |   _runtime-parse-error 'shopt -z'
 | 
| 561 | }
 | 
| 562 | 
 | 
| 563 | DISABLED-args-parse-main() {
 | 
| 564 |   $OSH --ast-format x
 | 
| 565 | 
 | 
| 566 |   $OSH -o errexit +o oops
 | 
| 567 | }
 | 
| 568 | 
 | 
| 569 | test-invalid-brace-ranges() {
 | 
| 570 |   _osh-parse-error 'echo {1..3..-1}'
 | 
| 571 |   _osh-parse-error 'echo {1..3..0}'
 | 
| 572 |   _osh-parse-error 'echo {3..1..1}'
 | 
| 573 |   _osh-parse-error 'echo {3..1..0}'
 | 
| 574 |   _osh-parse-error 'echo {a..Z}'
 | 
| 575 |   _osh-parse-error 'echo {a..z..0}'
 | 
| 576 |   _osh-parse-error 'echo {a..z..-1}'
 | 
| 577 |   _osh-parse-error 'echo {z..a..1}'
 | 
| 578 | }
 | 
| 579 | 
 | 
| 580 | test-extra-newlines() {
 | 
| 581 |   _osh-parse-error '
 | 
| 582 |   for
 | 
| 583 |   do
 | 
| 584 |   done
 | 
| 585 |   '
 | 
| 586 | 
 | 
| 587 |   _osh-parse-error '
 | 
| 588 |   case
 | 
| 589 |   in esac
 | 
| 590 |   '
 | 
| 591 | 
 | 
| 592 |   _osh-parse-error '
 | 
| 593 |   while
 | 
| 594 |   do
 | 
| 595 |   done
 | 
| 596 |   '
 | 
| 597 | 
 | 
| 598 |   _osh-parse-error '
 | 
| 599 |   if
 | 
| 600 |   then
 | 
| 601 |   fi
 | 
| 602 |   '
 | 
| 603 | 
 | 
| 604 |   _osh-parse-error '
 | 
| 605 |   if true
 | 
| 606 |   then
 | 
| 607 |   elif
 | 
| 608 |   then
 | 
| 609 |   fi
 | 
| 610 |   '
 | 
| 611 | 
 | 
| 612 |   _osh-parse-error '
 | 
| 613 |   case |
 | 
| 614 |   in
 | 
| 615 |   esac
 | 
| 616 |   '
 | 
| 617 | 
 | 
| 618 |   _osh-parse-error '
 | 
| 619 |   case ;
 | 
| 620 |   in
 | 
| 621 |   esac
 | 
| 622 |   '
 | 
| 623 | 
 | 
| 624 |   _osh-should-parse '
 | 
| 625 |   if
 | 
| 626 |   true
 | 
| 627 |   then
 | 
| 628 |   fi
 | 
| 629 |   '
 | 
| 630 | 
 | 
| 631 |   _osh-should-parse '
 | 
| 632 |   while
 | 
| 633 |   false
 | 
| 634 |   do
 | 
| 635 |   done
 | 
| 636 |   '
 | 
| 637 | 
 | 
| 638 |   _osh-should-parse '
 | 
| 639 |   while
 | 
| 640 |   true;
 | 
| 641 |   false
 | 
| 642 |   do
 | 
| 643 |   done
 | 
| 644 |   '
 | 
| 645 | 
 | 
| 646 |   _osh-should-parse '
 | 
| 647 |   if true
 | 
| 648 |   then
 | 
| 649 |   fi
 | 
| 650 |   '
 | 
| 651 | 
 | 
| 652 |   _osh-should-parse '
 | 
| 653 |   while true;
 | 
| 654 |         false
 | 
| 655 |   do
 | 
| 656 |   done
 | 
| 657 |   '
 | 
| 658 | }
 | 
| 659 | 
 | 
| 660 | test-parse_backticks() {
 | 
| 661 | 
 | 
| 662 |   # These are allowed
 | 
| 663 |   _osh-should-parse 'echo `echo hi`'
 | 
| 664 |   _osh-should-parse 'echo "foo = `echo hi`"'
 | 
| 665 | 
 | 
| 666 |   _assert-status-2 +O test-parse_backticks -n -c 'echo `echo hi`'
 | 
| 667 |   _assert-status-2 +O test-parse_backticks -n -c 'echo "foo = `echo hi`"'
 | 
| 668 | }
 | 
| 669 | 
 | 
| 670 | test-shell_for() {
 | 
| 671 | 
 | 
| 672 |   _osh-parse-error 'for x in &'
 | 
| 673 | 
 | 
| 674 |   _osh-parse-error 'for (( i=0; i<10; i++ )) ls'
 | 
| 675 | 
 | 
| 676 |   # ( is invalid
 | 
| 677 |   _osh-parse-error 'for ( i=0; i<10; i++ )'
 | 
| 678 | 
 | 
| 679 |   _osh-parse-error 'for $x in 1 2 3; do echo $i; done'
 | 
| 680 |   _osh-parse-error 'for x.y in 1 2 3; do echo $i; done'
 | 
| 681 |   _osh-parse-error 'for x in 1 2 3; &'
 | 
| 682 |   _osh-parse-error 'for foo BAD'
 | 
| 683 | 
 | 
| 684 |   # BUG fix: var is a valid name
 | 
| 685 |   _osh-should-parse 'for var in x; do echo $var; done'
 | 
| 686 | }
 | 
| 687 | 
 | 
| 688 | #
 | 
| 689 | # Different source_t variants
 | 
| 690 | #
 | 
| 691 | 
 | 
| 692 | test-nested_source_argvword() {
 | 
| 693 |   # source.ArgvWord
 | 
| 694 |   _runtime-parse-error '
 | 
| 695 |   code="printf % x"
 | 
| 696 |   eval $code
 | 
| 697 |   '
 | 
| 698 | }
 | 
| 699 | 
 | 
| 700 | test-eval_parse_error() {
 | 
| 701 |   _runtime-parse-error '
 | 
| 702 |   x="echo )"
 | 
| 703 |   eval $x
 | 
| 704 |   '
 | 
| 705 | }
 | 
| 706 | 
 | 
| 707 | trap_parse_error() {
 | 
| 708 |   _runtime-parse-error '
 | 
| 709 |   trap "echo )" EXIT
 | 
| 710 |   '
 | 
| 711 | }
 | 
| 712 | 
 | 
| 713 | test-proc_func_reserved() {
 | 
| 714 |   ### Prevents confusion
 | 
| 715 | 
 | 
| 716 |   _osh-parse-error 'proc p (x) { echo hi }'
 | 
| 717 |   _osh-parse-error 'func f (x) { return (x) }'
 | 
| 718 | }
 | 
| 719 | 
 | 
| 720 | # Cases in their own file
 | 
| 721 | cases-in-files() {
 | 
| 722 |   for test_file in test/parse-errors/*.sh; do
 | 
| 723 |     case-banner "FILE $test_file"
 | 
| 724 | 
 | 
| 725 |     set +o errexit
 | 
| 726 |     $OSH $test_file
 | 
| 727 |     local status=$?
 | 
| 728 |     set -o errexit
 | 
| 729 | 
 | 
| 730 |     if test -z "${SH_ASSERT_DISABLE:-}"; then
 | 
| 731 |       if test $status != 2; then
 | 
| 732 |         die "Expected status 2 from parse error file, got $status"
 | 
| 733 |       fi
 | 
| 734 |     fi
 | 
| 735 |   done
 | 
| 736 | }
 | 
| 737 | 
 | 
| 738 | test-case() {
 | 
| 739 |   readonly -a YES=(
 | 
| 740 |     # Right is optional
 | 
| 741 |     'case $x in foo) echo
 | 
| 742 | esac'
 | 
| 743 |     'case $x in foo) echo ;; esac'
 | 
| 744 |     'case $x in foo) echo ;& esac'
 | 
| 745 |     'case $x in foo) echo ;;& esac'
 | 
| 746 |   )
 | 
| 747 | 
 | 
| 748 |   readonly -a NO=(
 | 
| 749 |     ';&'
 | 
| 750 |     'echo ;&'
 | 
| 751 |     'echo ;;&'
 | 
| 752 |   )
 | 
| 753 | 
 | 
| 754 |   for c in "${YES[@]}"; do
 | 
| 755 |     echo "--- test-case YES $c"
 | 
| 756 | 
 | 
| 757 |     _osh-should-parse "$c"
 | 
| 758 |     echo
 | 
| 759 | 
 | 
| 760 |     bash -n -c "$c"
 | 
| 761 |     echo bash=$?
 | 
| 762 |   done
 | 
| 763 | 
 | 
| 764 |   for c in "${NO[@]}"; do
 | 
| 765 |     echo "--- test-case NO $c"
 | 
| 766 | 
 | 
| 767 |     _osh-parse-error "$c"
 | 
| 768 | 
 | 
| 769 |     set +o errexit
 | 
| 770 |     bash -n -c "$c"
 | 
| 771 |     echo bash=$?
 | 
| 772 |     set -o errexit
 | 
| 773 |   done
 | 
| 774 | }
 | 
| 775 | 
 | 
| 776 | all() {
 | 
| 777 |   section-banner 'Cases in Files'
 | 
| 778 | 
 | 
| 779 |   cases-in-files
 | 
| 780 | 
 | 
| 781 |   section-banner 'Cases in Functions, with strings'
 | 
| 782 | 
 | 
| 783 |   run-test-funcs
 | 
| 784 | }
 | 
| 785 | 
 | 
| 786 | # TODO: Something like test/parse-err-compare.sh
 | 
| 787 | 
 | 
| 788 | all-with-bash() {
 | 
| 789 |   # override OSH and YSH
 | 
| 790 |   SH_ASSERT_DISABLE=1 OSH=bash YSH=bash all
 | 
| 791 | }
 | 
| 792 | 
 | 
| 793 | all-with-dash() {
 | 
| 794 |   # override OSH and YSH
 | 
| 795 |   SH_ASSERT_DISABLE=1 OSH=dash YSH=dash all
 | 
| 796 | }
 | 
| 797 | 
 | 
| 798 | soil-run-py() {
 | 
| 799 |   ### Run in CI with Python
 | 
| 800 |   
 | 
| 801 |   # output _tmp/other/parse-errors.txt
 | 
| 802 | 
 | 
| 803 |   all
 | 
| 804 | }
 | 
| 805 | 
 | 
| 806 | soil-run-cpp() {
 | 
| 807 |   ninja _bin/cxx-asan/osh
 | 
| 808 |   OSH=_bin/cxx-asan/osh all
 | 
| 809 | }
 | 
| 810 | 
 | 
| 811 | release-oils-for-unix() {
 | 
| 812 |   readonly OIL_VERSION=$(head -n 1 oil-version.txt)
 | 
| 813 |   local dir="../benchmark-data/src/oils-for-unix-$OIL_VERSION"
 | 
| 814 | 
 | 
| 815 |   # Maybe rebuild it
 | 
| 816 |   pushd $dir
 | 
| 817 |   _build/oils.sh '' '' SKIP_REBUILD
 | 
| 818 |   popd
 | 
| 819 | 
 | 
| 820 |   local suite_name=parse-errors-osh-cpp
 | 
| 821 |   OSH=$dir/_bin/cxx-opt-sh/osh \
 | 
| 822 |     run-other-suite-for-release $suite_name all
 | 
| 823 | }
 | 
| 824 | 
 | 
| 825 | run-for-release() {
 | 
| 826 |   ### Test with bin/osh and the ASAN binary.
 | 
| 827 | 
 | 
| 828 |   run-other-suite-for-release parse-errors all
 | 
| 829 | 
 | 
| 830 |   release-oils-for-unix
 | 
| 831 | }
 | 
| 832 | 
 | 
| 833 | "$@"
 |