OILS / test / runtime-errors.sh View on Github | oilshell.org

1295 lines, 683 significant
1#!/usr/bin/env bash
2#
3# A file that tickles many runtime errors, to see the error message.
4# Sometimes spec tests are better - 'test/spec.sh smoke -v -d' can also show
5# errors clearly.
6#
7# Usage:
8# test/runtime-errors.sh <function name>
9#
10# TODO: Migrate the unquoted-* functions below to test-* functions, which means
11# adding assertions.
12
13source test/common.sh
14source test/sh-assert.sh # banner, _assert-sh-status
15
16# Note: should run in bash/dash mode, where we don't check errors
17OSH=${OSH:-bin/osh}
18YSH=${YSH:-bin/ysh}
19
20_run-test-func() {
21 ### Run a function, and optionally assert status
22
23 local test_func=$1
24 local expected_status=${2:-}
25
26 echo
27 echo "===== TEST function: $test_func ====="
28 echo
29
30 $OSH $0 $test_func
31
32 status=$?
33 if test -n "$expected_status"; then
34 if test $status != $expected_status; then
35 die "*** Test $test_func -> status $status, expected $expected_status"
36 fi
37 fi
38
39 echo "----- STATUS: $?"
40 echo
41}
42
43test-FAIL() {
44 ### Make sure the assertions work
45
46 # Error string
47 _osh-error-1 'echo hi > /zzz'
48
49 return
50
51 # doesn't fail
52 _osh-error-2 'echo hi'
53
54 echo nope
55}
56
57#
58# PARSE ERRORS
59#
60
61test-source_bad_syntax() {
62 cat >_tmp/bad-syntax.sh <<EOF
63if foo; echo ls; fi
64EOF
65 _osh-error-2 '. _tmp/bad-syntax.sh'
66}
67
68# NOTE:
69# - bash correctly reports line 25 (24 would be better)
70# - mksh: no line number
71# - zsh: line 2 of eval, which doesn't really help.
72# - dash: ditto, line 2 of eval
73test-eval_bad_syntax() {
74 _osh-error-2 '
75code="if foo; echo ls; fi"
76eval "echo --
77 $code"
78'
79}
80
81#
82# COMMAND ERRORS
83#
84
85test-no_such_command() {
86 _osh-error-X 127 'set -o errexit; ZZZZZ; echo UNREACHABLE'
87}
88
89test-no_such_command_commandsub() {
90 _osh-should-run 'set -o errexit; echo $(ZZZZZ); echo UNREACHABLE'
91 _osh-error-X 127 'set -o errexit; shopt -s command_sub_errexit; echo $(ZZZZZ); echo UNREACHABLE'
92}
93
94unquoted-no_such_command_heredoc() {
95 set -o errexit
96
97 # Note: bash gives the line of the beginning of the here doc! Not the actual
98 # line.
99 cat <<EOF
100one
101$(ZZZZZ)
102three
103EOF
104 echo 'SHOULD NOT GET HERE'
105}
106
107test-failed_command() {
108 _osh-error-1 'set -o errexit; false; echo UNREACHABLE'
109}
110
111# This quotes the same line of code twice, but maybe that's OK. At least there
112# is different column information.
113test-errexit_usage_error() {
114 _osh-error-2 'set -o errexit; type -z'
115}
116
117test-errexit_subshell() {
118 # Note: for loops, while loops don't trigger errexit; their components do
119 _osh-error-X 42 'set -o errexit; ( echo subshell; exit 42; )'
120}
121
122TODO-BUG-test-errexit_pipeline() {
123 # We don't blame the right location here
124
125 # BUG: what happnened here? Is there a race?
126 local code='set -o errexit; set -o pipefail; echo subshell | cat | exit 42 | wc -l'
127
128 #local code='set -o errexit; set -o pipefail; echo subshell | cat | exit 42'
129
130 bash -c "$code"
131 echo status=$?
132
133 _osh-error-X 42 "$code"
134}
135
136test-errexit_dbracket() {
137 _osh-error-1 'set -o errexit; [[ -n "" ]]; echo UNREACHABLE'
138}
139
140shopt -s expand_aliases
141# Why can't this be in the function?
142alias foo='echo hi; ls '
143
144test-errexit_alias() {
145 _osh-error-1 'set -o errexit; type foo; foo /nonexistent'
146}
147
148_sep() {
149 echo
150 echo '---------------------------'
151}
152
153test-command-not-found() {
154 _osh-error-X 127 'findz'
155}
156
157test-errexit-one-process() {
158 # two quotations of same location: not found then errexit
159 _ysh-error-X 127 'zz'
160
161 _sep
162
163 # two quotations, different location
164 _ysh-error-1 'echo hi > ""'
165
166 _sep
167
168 _ysh-error-1 'shopt -s failglob; echo *.ZZZZ'
169
170 _sep
171
172 _ysh-error-1 'cd /x'
173
174 _sep
175
176 # TODO: remove duplicate snippet
177 _ysh-error-X 126 './README.md; echo hi'
178
179 # one location
180 _ysh-error-2 'ls /x; echo $?'
181
182 _sep
183
184 _ysh-error-2 'declare cmd=ls; $cmd /x; echo $?'
185
186 _sep
187
188 # one location
189 _ysh-error-1 'echo $undef'
190
191 _sep
192
193 # Show multiple "nested" errors, and then errexit
194 _osh-error-1 '
195eval "("
196echo status=$?
197
198eval ")"
199echo status=$?
200
201set -e; shopt -s verbose_errexit
202false
203echo DONE
204'
205
206 _sep
207 _osh-error-1 'shopt --set ysh:upgrade; [[ 0 -eq 1 ]]'
208
209 # not parsed
210 _ysh-error-2 '[[ 0 -eq 1 ]]'
211
212 _sep
213
214 _osh-error-1 'shopt --set ysh:upgrade; (( 0 ))'
215
216 # not parsed
217 _ysh-error-2 '(( 0 ))'
218}
219
220test-errexit-multiple-processes() {
221 ### A representative set of errors. For consolidating code quotations
222
223 # command_sub_errexit. Hm this gives 2 errors as well, because of inherit_errexit
224 _ysh-error-1 'echo t=$(true) f=$(false; true)'
225 #return
226
227 _sep
228
229 # BUG introduced by shopt -s no_last_fork: Even though set -o pipefail is on
230 # in YSH, the entire shell does NOT exit!
231 #
232 # This is because 'wc -l' does exec. And then there is nothing to "modify"
233 # the exit status based on pipefail.
234 #
235 # So it's actually unsound to do this optmization when set -o pipefail is on.
236 # Combined with shopt -s lastpipe
237
238 #_ysh-should-run 'ls | false | wc -l'
239 _ysh-error-1 'ls | false | wc -l'
240
241 _sep
242
243 # note: need trailing echo to prevent pipeline optimization
244 _ysh-error-X 42 'ls | { echo hi; ( exit 42 ); } | wc -l; echo'
245
246 _sep
247
248 # Showing errors for THREE PIDs here! That is technically correct, but
249 # noisy.
250 _ysh-error-1 '{ echo one; ( exit 42 ); } |\
251{ false; wc -l; }'
252
253 _sep
254
255 # realistic example
256 _ysh-error-1 '{ ls; false; } \
257| wc -l
258'
259
260 _sep
261
262 # Three errors!
263 _ysh-error-1 '{ ls; ( false; true ); } | wc -l; echo hi'
264
265 _sep
266
267 _ysh-error-X 127 'ls <(sort YY) <(zz); echo hi'
268
269 # 2 kinds of errors
270 _sep
271 _ysh-error-X 127 'zz <(sort YY) <(sort ZZ); echo hi'
272
273 # This one has badly interleaved errors!
274 _sep
275 _ysh-error-X 127 'yy | zz'
276
277 _sep
278 _osh-error-1 'shopt -s ysh:upgrade; echo $([[ 0 -eq 1 ]])'
279
280 _sep
281 _osh-error-1 'shopt -s ysh:upgrade; var y = $([[ 0 -eq 1 ]])'
282}
283
284
285_strict-errexit-case() {
286 local code=$1
287
288 #case-banner "[strict_errexit] $code"
289
290 _osh-error-1 \
291 "set -o errexit; shopt -s strict_errexit; $code"
292 echo
293}
294
295test-strict-errexit-1() {
296 # Test out all the location info
297
298 _strict-errexit-case '! { echo 1; echo 2; }'
299
300 _strict-errexit-case '{ echo 1; echo 2; } && true'
301 _strict-errexit-case '{ echo 1; echo 2; } || true'
302 _strict-errexit-case '{ echo 1; echo 2; } >/dev/null || true'
303
304 # More chains
305 _strict-errexit-case '{ echo 1; echo 2; } && true && true'
306 _strict-errexit-case 'true && { echo 1; echo 2; } || true || true'
307 _strict-errexit-case 'true && true && { echo 1; echo 2; } || true || true'
308
309 _strict-errexit-case 'if { echo 1; echo 2; }; then echo IF; fi'
310 _strict-errexit-case 'if { echo 1; echo 2; } >/dev/null; then echo IF; fi'
311
312 _strict-errexit-case 'while { echo 1; echo 2; }; do echo WHILE; done'
313 _strict-errexit-case 'until { echo 1; echo 2; }; do echo UNTIL; done'
314
315 # Must be separate lines for parsing options to take effect
316 _strict-errexit-case 'shopt -s ysh:upgrade
317 proc p { echo p }
318 if p { echo hi }'
319}
320
321test-strict-errexit-conditionals() {
322 # this works, even though this is a subshell
323 _strict-errexit-case '
324myfunc() { return 1; }
325
326while ( myfunc )
327do
328 echo yes
329done
330'
331
332 # Conditional - Proc - Child Interpreter Problem (command sub)
333 # Same problem here. A proc run in a command sub LOSES the exit code.
334 _strict-errexit-case '
335myfunc() { return 1; }
336
337while test "$(myfunc)" != ""
338do
339 echo yes
340done
341'
342
343 # Process Sub is be disallowed; it could invoke a proc!
344 _strict-errexit-case '
345myfunc() { return 1; }
346
347if cat <(ls)
348then
349 echo yes
350fi
351'
352
353 # Conditional - Proc - Child Interpreter Problem (pipeline)
354 _strict-errexit-case '
355myfunc() {
356 return 1
357}
358
359set -o pipefail
360while myfunc | cat
361do
362 echo yes
363done
364'
365
366 # regression for issue #1107 bad error message
367 # Also revealed #1113: the strict_errexit check was handled inside the
368 # command sub process!
369 _strict-errexit-case '
370myfunc() {
371 return 1
372}
373
374foo=$(true)
375
376# test assignment without proc
377while bar=$(false)
378do
379 echo yes
380done
381
382# issue 1007 was caused using command.ShAssignment, rather than the more common
383# command.Sentence with ;
384while spam=$(myfunc)
385do
386 echo yes
387done
388'
389}
390
391# OLD WAY OF BLAMING
392# Note: most of these don't fail
393test-strict-errexit-old() {
394 # Test out all the location info
395
396 # command.Pipeline.
397 _strict-errexit-case 'if ls | wc -l; then echo Pipeline; fi'
398 _strict-errexit-case 'if ! ls | wc -l; then echo failed; fi'
399
400 # This one is ALLOWED
401 #_strict-errexit-case 'if ! ls; then echo Pipeline; fi'
402
403 # command.AndOr
404 #_strict-errexit-case 'if echo a && echo b; then echo AndOr; fi'
405
406 # command.DoGroup
407 _strict-errexit-case '! for x in a; do echo $x; done'
408
409 # command.BraceGroup
410 _strict-errexit-case '_func() { echo; }; ! _func'
411 _strict-errexit-case '! { echo brace; }; echo "should not get here"'
412
413 # command.Subshell
414 _strict-errexit-case '! ( echo subshell ); echo "should not get here"'
415
416 # command.WhileUntil
417 _strict-errexit-case '! while false; do echo while; done; echo "should not get here"'
418
419 # command.If
420 _strict-errexit-case '! if true; then false; fi; echo "should not get here"'
421
422 # command.Case
423 _strict-errexit-case '! case x in x) echo x;; esac; echo "should not get here"'
424
425 # command.TimeBlock
426 _strict-errexit-case '! time echo hi; echo "should not get here"'
427
428 # Command Sub
429 _strict-errexit-case '! echo $(echo hi); echo "should not get here"'
430}
431
432unquoted-pipefail() {
433 false | wc -l
434
435 set -o errexit
436 set -o pipefail
437 false | wc -l
438
439 echo 'SHOULD NOT GET HERE'
440}
441
442unquoted-pipefail_no_words() {
443 set -o errexit
444 set -o pipefail
445
446 # Make sure we can blame this
447 seq 3 | wc -l | > /nonexistent
448
449 echo done
450}
451
452unquoted-pipefail_func() {
453 set -o errexit -o pipefail
454 f42() {
455 cat
456 # NOTE: If you call 'exit 42', there is no error message displayed!
457 #exit 42
458 return 42
459 }
460
461 # TODO: blame the right location
462 echo hi | cat | f42 | wc
463
464 echo 'SHOULD NOT GET HERE'
465}
466
467# TODO: point to {. It's the same sas a subshell so you don't know exactly
468# which command failed.
469unquoted-pipefail_group() {
470 set -o errexit -o pipefail
471 echo hi | { cat; sh -c 'exit 42'; } | wc
472
473 echo 'SHOULD NOT GET HERE'
474}
475
476# TODO: point to (
477unquoted-pipefail_subshell() {
478 set -o errexit -o pipefail
479 echo hi | (cat; sh -c 'exit 42') | wc
480
481 echo 'SHOULD NOT GET HERE'
482}
483
484# TODO: point to 'while'
485unquoted-pipefail_while() {
486 set -o errexit -o pipefail
487 seq 3 | while true; do
488 read line
489 echo X $line X
490 if test "$line" = 2; then
491 sh -c 'exit 42'
492 fi
493 done | wc
494
495 echo 'SHOULD NOT GET HERE'
496}
497
498# Multiple errors from multiple processes
499# TODO: These errors get interleaved and messed up. Maybe we should always
500# print a single line from pipeline processes? We should set their
501# ErrorFormatter?
502unquoted-pipefail_multiple() {
503 set -o errexit -o pipefail
504 { echo 'four'; sh -c 'exit 4'; } |
505 { echo 'five'; sh -c 'exit 5'; } |
506 { echo 'six'; sh -c 'exit 6'; }
507}
508
509test-control_flow() {
510 # This prints a WARNING in bash. Not fatal in any shell except zsh.
511 _osh-error-X 0 '
512break
513continue
514echo UNREACHABLE
515'
516
517 _osh-error-X 1 '
518shopt -s strict_control_flow
519break
520continue
521echo UNREACHABLE
522'
523}
524
525# Errors from core/process.py
526test-core_process() {
527 _osh-error-1 '
528 echo foo > not/a/file
529 echo foo > /etc/no-perms-for-this
530 '
531
532 # DISABLED! This messes up the toil log file!
533 # echo hi 1>&3
534}
535
536# Errors from core/state.py
537test-core-state() {
538
539 _osh-should-run 'HOME=(a b)'
540
541 # $HOME is an exported string, so it shouldn't be changed to an array
542 _osh-error-1 'shopt --set strict_array; HOME=(a b)'
543}
544
545unquoted-ambiguous_redirect() {
546 echo foo > "$@"
547 echo 'ambiguous redirect not fatal unless errexit'
548
549 set -o errexit
550 echo foo > "$@"
551 echo 'should not get here'
552}
553
554# bash semantics.
555unquoted-ambiguous_redirect_context() {
556 # Problem: A WORD cannot fail. Only a COMMAND can fail.
557
558 # http://stackoverflow.com/questions/29532904/bash-subshell-errexit-semantics
559 # https://groups.google.com/forum/?fromgroups=#!topic/gnu.bash.bug/NCK_0GmIv2M
560
561 # http://unix.stackexchange.com/questions/23026/how-can-i-get-bash-to-exit-on-backtick-failure-in-a-similar-way-to-pipefail
562
563 echo $(echo hi > "$@")
564 echo 'ambiguous is NOT FATAL in command sub'
565 echo
566
567 foo=$(echo hi > "$@")
568 echo $foo
569 echo 'ambiguous is NOT FATAL in assignment in command sub'
570 echo
571
572 set -o errexit
573
574 # This is the issue addressed by command_sub_errexit!
575 echo $(echo hi > "$@")
576 echo 'ambiguous is NOT FATAL in command sub, even if errexit'
577 echo
578
579 # OK this one works. Because the exit code of the assignment is the exit
580 # code of the RHS?
581 echo 'But when the command sub is in an assignment, it is fatal'
582 foo=$(echo hi > "$@")
583 echo $foo
584 echo 'SHOULD NOT REACH HERE'
585}
586
587unquoted-bad_file_descriptor() {
588 : 1>&7
589}
590
591unquoted-command_sub_errexit() {
592 #set -o errexit
593 shopt -s command_sub_errexit || true
594 shopt -s inherit_errexit || true
595
596 echo t=$(true) f=$(false) 3=$(exit 3)
597 echo 'SHOULD NOT GET HERE'
598}
599
600unquoted-process_sub_fail() {
601 shopt -s process_sub_fail || true
602 shopt -s inherit_errexit || true
603 set -o errexit
604
605 cat <(echo a; exit 2) <(echo b; exit 3)
606 echo 'SHOULD NOT GET HERE'
607}
608
609myproc() {
610 echo ---
611 grep pat BAD # exits with code 2
612 #grep pat file.txt
613 echo ---
614}
615
616unquoted-bool_status() {
617 set -o errexit
618
619 if try --allow-status-01 -- myproc; then
620 echo 'match'
621 else
622 echo 'no match'
623 fi
624}
625
626unquoted-bool_status_simple() {
627 set -o errexit
628
629 if try --allow-status-01 -- grep pat BAD; then
630 echo 'match'
631 else
632 echo 'no match'
633 fi
634}
635
636#
637# WORD ERRORS
638#
639
640unquoted-nounset() {
641 set -o nounset
642 echo $x
643
644 echo 'SHOULD NOT GET HERE'
645}
646
647unquoted-bad_var_ref() {
648 name='bad var name'
649 echo ${!name}
650}
651
652#
653# ARITHMETIC ERRORS
654#
655
656unquoted-nounset_arith() {
657 set -o nounset
658 echo $(( x ))
659
660 echo 'SHOULD NOT GET HERE'
661}
662
663test-divzero() {
664 _osh-error-1 'echo $(( 1 / 0 ))'
665 # Better location
666 _osh-error-1 'echo $(( 1 / (3 -3 ) ))'
667 _osh-error-1 'echo $(( 1 % 0 ))'
668
669 _osh-error-1 'zero=0; echo $(( 1 / zero ))'
670 _osh-error-1 'zero=0; echo $(( 1 % zero ))'
671
672 _osh-error-1 '(( a = 1 / 0 )); echo non-fatal; exit 1'
673 _osh-error-1 '(( a = 1 % 0 )); echo non-fatal; exit 1'
674
675 # fatal!
676 _osh-error-1 'set -e; (( a = 1 / 0 ));'
677 _osh-error-1 'set -e; (( a = 1 % 0 ));'
678}
679
680test-unsafe_arith_eval() {
681 _osh-error-1 '
682 local e1=1+
683 local e2="e1 + 5"
684 echo $(( e2 )) # recursively references e1
685 '
686}
687
688test-unset_expr() {
689 _osh-error-1 'unset -v 1[1]'
690 _osh-error-2 'unset -v 1+2'
691}
692
693test-strict-arith() {
694 _osh-error-1 'shopt -s strict_arith; echo $(( undef[0] ))'
695 _osh-error-1 'shopt -s strict_arith; s=abc; echo $(( s[0] ))'
696 _osh-error-1 'shopt -s strict_arith; var i = 42; echo $(( i[0] ))'
697}
698
699# Only dash flags this as an error.
700unquoted-string_to_int_arith() {
701 local x='ZZZ'
702 echo $(( x + 5 ))
703
704 shopt -s strict_arith
705
706 echo $(( x + 5 ))
707
708 echo 'SHOULD NOT GET HERE'
709}
710
711# Hm bash treats this as a fatal error
712unquoted-string_to_hex() {
713 echo $(( 0xGG + 1 ))
714
715 echo 'SHOULD NOT GET HERE'
716}
717
718# Hm bash treats this as a fatal error
719unquoted-string_to_octal() {
720 echo $(( 018 + 1 ))
721
722 echo 'SHOULD NOT GET HERE'
723}
724
725# Hm bash treats this as a fatal error
726unquoted-string_to_intbase() {
727 echo $(( 16#GG ))
728
729 echo 'SHOULD NOT GET HERE'
730}
731
732unquoted-undef_arith() {
733 (( undef++ )) # doesn't make sense
734
735 # Can't assign to characters of string? Is that strong?
736 (( undef[42]++ ))
737}
738
739unquoted-undef_arith2() {
740 a=()
741
742 # undefined cell: This is kind of what happens in awk / "wok"
743 (( a[42]++ ))
744 (( a[42]++ ))
745 spec/bin/argv.py "${a[@]}"
746}
747
748unquoted-array_arith() {
749 a=(1 2)
750 (( a++ )) # doesn't make sense
751 echo "${a[@]}"
752}
753
754unquoted-undef-assoc-array() {
755 declare -A A
756 A['foo']=bar
757 echo "${A['foo']}"
758
759 A['spam']+=1
760 A['spam']+=1
761
762 spec/bin/argv.py "${A[@]}"
763
764 (( A['spam']+=5 ))
765
766 spec/bin/argv.py "${A[@]}"
767}
768
769test-assoc-array() {
770 _osh-error-1 'declare -A assoc; assoc[x]=1'
771 _osh-should-run 'declare -A assoc; assoc[$key]=1'
772
773 # double quotes
774 _osh-should-run 'declare -A assoc; assoc["x"]=1'
775
776 # single quotes
777 _osh-should-run-here <<'EOF'
778declare -A assoc; assoc['x']=1
779EOF
780
781 _osh-error-1 'declare -A assoc; echo ${assoc[x]}'
782 _osh-should-run 'declare -A assoc; echo ${assoc["x"]}'
783 _osh-should-run 'declare -A assoc; echo ${assoc[$key]}'
784
785 _osh-error-1 'declare -A assoc; key=k; unset assoc[$key]'
786 # quotes removed
787 _osh-error-1 'declare -A assoc; key=k; unset "assoc[$key]"'
788
789 # Like Samuel's Nix error
790 # unset -v "hardeningEnableMap[$flag]"
791 _osh-error-here-X 1 <<'EOF'
792declare -A assoc; key=k; unset "assoc[$key]"
793EOF
794
795 # SINGLE quotes fixes it
796 _osh-should-run-here <<'EOF'
797declare -A assoc; key=k; unset 'assoc[$key]'
798EOF
799
800 # Wrap in eval to see how it composes
801 _osh-error-here-X 1 <<'EOF'
802eval 'declare -A assoc; assoc[x]=1'
803EOF
804
805 _osh-error-here-X 1 <<'EOF'
806eval 'declare -A assoc; unset "assoc[x]"'
807EOF
808
809}
810
811unquoted-patsub_bad_glob() {
812 local x='abc'
813 # inspired by git-completion.bash
814 echo ${x//[^]}
815}
816
817
818#
819# BOOLEAN ERRORS
820#
821
822# Only osh cares about this.
823unquoted-string_to_int_bool() {
824 [[ a -eq 0 ]]
825
826 shopt -s strict_arith
827
828 [[ a -eq 0 ]]
829 echo 'SHOULD NOT GET HERE'
830}
831
832unquoted-strict_array() {
833 set -- 1 2
834 echo foo > _tmp/"$@"
835 shopt -s strict_array
836 echo foo > _tmp/"$@"
837}
838
839unquoted-strict_array_2() {
840 local foo="$@"
841 shopt -s strict_array
842 local foo="$@"
843}
844
845unquoted-strict_array_3() {
846 local foo=${1:- "[$@]" }
847 shopt -s strict_array
848 local foo=${1:- "[$@]" }
849}
850
851unquoted-strict_array_4() {
852 local -a x
853 x[42]=99
854 echo "x[42] = ${x[42]}"
855
856 # Not implemented yet
857 shopt -s strict_array
858 local -a y
859 y[42]=99
860}
861
862unquoted-array_assign_1() {
863 s=1
864 s[0]=x # can't assign value
865}
866
867unquoted-array_assign_2() {
868 _osh-error-1 'readonly -a array=(1 2 3); array[0]=x'
869
870 _osh-error-1 'readonly -a array=(1 2 3); export array'
871}
872
873unquoted-readonly_assign() {
874 _osh-error-1 'readonly x=1; x=2'
875
876 _osh-error-1 'readonly x=2; y=3 x=99'
877
878 _osh-error-1 'readonly x=2; declare x=99'
879 _osh-error-1 'readonly x=2; export x=99'
880}
881
882unquoted-multiple_assign() {
883 readonly x=1
884 # It blames x, not a!
885 a=1 b=2 x=42
886}
887
888unquoted-multiple_assign_2() {
889 readonly y
890 local x=1 y=$(( x ))
891 echo $y
892}
893
894unquoted-string_as_array() {
895 local str='foo'
896 echo $str
897 echo "${str[@]}"
898}
899
900#
901# BUILTINS
902#
903
904unquoted-builtin_bracket() {
905 set +o errexit
906
907 # xxx is not a valid file descriptor
908 [ -t xxx ]
909 [ -t '' ]
910
911 [ zz -eq 0 ]
912
913 # This is from a different evaluator
914 #[ $((a/0)) -eq 0 ]
915}
916
917unquoted-builtin_builtin() {
918 set +o errexit
919 builtin ls
920}
921
922unquoted-builtin_source() {
923 source
924
925 bad=/nonexistent/path
926 source $bad
927}
928
929unquoted-builtin_cd() {
930 ( unset HOME
931 cd
932 )
933
934 # TODO: Hm this gives a different useful error without location info
935 ( unset HOME
936 HOME=(a b)
937 cd
938 )
939
940 # TODO: Hm this gives a different useful error without location info
941 ( unset OLDPWD
942 cd -
943 )
944
945 ( cd /nonexistent
946 )
947}
948
949unquoted-builtin_pushd() {
950 pushd /nonexistent
951}
952
953unquoted-builtin_popd() {
954 popd # empty dir stack
955
956 (
957 local dir=$PWD/_tmp/runtime-error-popd
958 mkdir -p $dir
959 pushd $dir
960 pushd /
961 rmdir $dir
962 popd
963 )
964}
965
966unquoted-builtin_unset() {
967 local x=x
968 readonly a
969
970 unset x a
971 unset -v x a
972}
973
974unquoted-builtin_alias_unalias() {
975 alias zzz
976 unalias zzz
977}
978
979unquoted-builtin_help() {
980 help zzz
981}
982
983unquoted-builtin_trap() {
984 trap
985 trap EXIT
986
987 trap zzz yyy
988}
989
990unquoted-builtin_getopts() {
991 getopts
992 getopts 'a:'
993
994 # TODO: It would be nice to put this in a loop and use it properly
995 set -- -a
996 getopts 'a:' varname
997}
998
999builtin_printf() {
1000 printf '%s %d\n' foo not_a_number
1001 echo status=$?
1002
1003 # bad arg recycling. This is really a runtime error.
1004 printf '%s %d\n' foo 3 bar
1005 echo status=$?
1006
1007 # invalid width
1008 printf '%*d\n' foo foo
1009 echo status=$?
1010
1011 # precision can't be specified
1012 printf '%.*d\n' foo foo
1013 echo status=$?
1014
1015 # precision can't be specified
1016 printf '%.*s\n' foo foo
1017 echo status=$?
1018
1019 # invalid time
1020 printf '%(%Y)T\n' z
1021 echo status=$?
1022
1023 # invalid time with no SPID
1024 printf '%(%Y)T\n'
1025 echo status=$?
1026
1027 # invalid integer with no SPID
1028 printf '%d %d %d\n' 1 2
1029 echo status=$?
1030}
1031
1032
1033unquoted-builtin_wait() {
1034 wait 1234578
1035}
1036
1037unquoted-builtin_exec() {
1038 exec nonexistent-command 1 2 3
1039 echo $?
1040}
1041
1042#
1043# Strict options (see spec/strict_options.sh)
1044#
1045
1046unquoted-strict_word_eval_warnings() {
1047 # Warnings when 'set +o strict_word_eval' is OFF
1048
1049 echo slice start negative
1050 s='abc'
1051 echo -${s: -2}-
1052
1053 echo slice length negative
1054 s='abc'
1055 echo -${s: 1: -2}-
1056
1057 # TODO: These need span IDs.
1058 # - invalid utf-8 and also invalid backslash escape
1059
1060 echo slice bad utf-8
1061 s=$(echo -e "\xFF")bcdef
1062 echo -${s:1:3}-
1063
1064 echo length bad utf-8
1065 echo ${#s}
1066}
1067
1068unquoted-strict_arith_warnings() {
1069 local x='xx'
1070 echo $(( x + 1 ))
1071
1072 # TODO: OSH is more lenient here actually
1073 local y='-yy-'
1074 echo $(( y + 1 ))
1075
1076 [[ $y -eq 0 ]]
1077
1078 echo 'done'
1079}
1080
1081test-control_flow_subshell() {
1082 _osh-error-1 '
1083 set -o errexit
1084 for i in $(seq 2); do
1085 echo $i
1086 ( break; echo oops)
1087 done
1088 '
1089}
1090
1091test-fallback-locations() {
1092 # Redirect
1093 _osh-error-1 'echo hi > /'
1094
1095 _osh-error-1 's=x; (( s[0] ))'
1096
1097 _osh-error-1 's=x; (( s[0] = 42 ))'
1098
1099 _osh-error-1 'set -u; (( undef ))'
1100
1101 _osh-error-1 '(( 3 ** -2 ))'
1102 echo
1103
1104 # DBracket
1105 _osh-error-1 'set -u; [[ $undef =~ . ]]'
1106
1107 # No good fallback info here, we need it
1108 _osh-error-1 '[[ $x =~ $(( 3 ** -2 )) ]]'
1109
1110 _osh-error-2 'type -x' # correctly points to -x
1111 _osh-error-2 'use'
1112
1113 # Assign builtin
1114 _osh-error-2 'export -f'
1115
1116 _osh-error-1 's=$(true) y=$(( 3 ** -2 ))'
1117
1118 _osh-error-1 'if s=$(true) y=$(( 3 ** -2 )); then echo hi; fi'
1119
1120 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( x ))'
1121 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( $x ))'
1122 _osh-error-1 'shopt -s strict_arith; x=a; [[ $x -gt 3 ]]'
1123 _osh-error-1 'shopt -s strict_arith; shopt -u eval_unsafe_arith; x=a; [[ $x -gt 3 ]]'
1124
1125 _osh-error-1 'shopt -s strict_arith; x=0xgg; echo $(( x ))'
1126
1127 echo done
1128}
1129
1130test-var-op-qmark() {
1131 _osh-error-1 'echo ${zz?}'
1132 _osh-error-1 'echo ${zz:?}'
1133
1134 _osh-should-run 'zz=""; echo ${zz?}'
1135 _osh-error-1 'zz=""; echo ${zz:?}'
1136
1137 _osh-error-1 'echo ${zz?Required}'
1138 _osh-error-1 'echo ${zz:?Required}'
1139}
1140
1141test-external_cmd_typed_args() {
1142 _ysh-error-X 1 'cat ("myfile")'
1143}
1144
1145test-arith-ops-str() {
1146 _ysh-error-X 3 '= "100" + "10a"'
1147 _ysh-error-X 3 '= "100" - "10a"'
1148 _ysh-error-X 3 '= "100" * "10a"'
1149 _ysh-error-X 3 '= "100" / "10a"'
1150 _ysh-error-X 3 'var a = "100"; setvar a += "10a"'
1151 _ysh-error-X 3 'var a = "100"; setvar a -= "10a"'
1152 _ysh-error-X 3 'var a = "100"; setvar a *= "10a"'
1153 _ysh-error-X 3 'var a = "100"; setvar a /= "10a"'
1154 _ysh-error-X 3 '= "age: " + "100"'
1155 _ysh-error-X 3 'var myvar = "a string"
1156= 100 + myvar'
1157}
1158
1159assert-test-v-error() {
1160 local code=$1
1161
1162 # note: the test builtin fails with status 2, but the shell doesn't fail
1163 _osh-error-2 "shopt -s strict_word_eval; a=(1 2 3); $code"
1164}
1165
1166test-test-v-expr() {
1167 assert-test-v-error 'test -v ""'
1168 assert-test-v-error 'test -v "a[foo"'
1169 assert-test-v-error 'test -v "a[not-int]"'
1170 assert-test-v-error 'test -v "a[-42]"'
1171
1172 _osh-error-2 'shopt -s strict_word_eval; s=""; test -v s[0]'
1173}
1174
1175test-long-shell-line() {
1176 # Example from https://github.com/oilshell/oil/issues/1973
1177
1178 _ysh-error-1 'myvar=$(printf "what a very long string that we have here, which forces the command line to wrap around the terminal width. long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long") && echo $myvar'
1179 echo
1180}
1181
1182test-int-overflow() {
1183 local pos='18446744073709551616'
1184 local neg='-18446744073709551616'
1185
1186 # frontend/args.py
1187 _osh-error-2 "echo hi | read -n $pos"
1188 _osh-error-2 "echo hi | read -n $neg"
1189
1190 # osh/sh_expr_eval.py
1191 _osh-error-1 "s=$pos;"' echo $(( $s ))'
1192 _osh-error-1 "s=$neg;"' echo $(( $s ))'
1193
1194 # octal
1195 local oct_pos='01234567012345670123456701234567'
1196 local oct_neg="-$oct_pos"
1197 _osh-error-1 "s=$oct_pos;"' echo $(( $s ))'
1198 _osh-error-1 "s=$oct_neg;"' echo $(( $s ))' # treated as negation
1199
1200 # hex
1201 local hex_pos='0x123456789abcdef0123456789'
1202 local hex_neg="-$hex_pos"
1203 _osh-error-1 "s=$hex_pos;"' echo $(( $s ))'
1204 _osh-error-1 "s=$hex_neg;"' echo $(( $s ))' # treated as negation
1205
1206 # builtins
1207 _osh-error-1 'printf %d'" $pos"
1208 _osh-error-1 'printf %d'" $neg"
1209
1210 _osh-error-2 "trap $pos ERR"
1211 _osh-error-2 "trap -- $neg ERR"
1212
1213 _osh-error-2 "ulimit $pos"
1214 _osh-error-2 "ulimit -- $neg"
1215}
1216
1217test-bind() {
1218 _osh-error-2 'bind -x'
1219 _osh-error-1 'bind -x foo'
1220
1221 _osh-error-1 'bind "a" "b"'
1222 _osh-error-1 'bind "\eQ: yank-pop" "b"'
1223 _osh-error-1 'bind "\eQ: yank-pop" "\eQ: yank-pop" "c"'
1224}
1225
1226#
1227# TEST DRIVER
1228#
1229
1230list-unquoted-funcs() {
1231 ### These tests need assertions, with quoting
1232
1233 compgen -A function | egrep '^unquoted-'
1234}
1235
1236run-unquoted-funcs() {
1237 local i=0
1238 list-unquoted-funcs | ( while read -r test_func; do
1239 _run-test-func $test_func '' # don't assert status
1240 i=$((i + 1))
1241 done
1242
1243 # Hacky subshell for $i
1244 echo
1245 echo "$0: $i unquoted functions run. TODO: migrate to test-* to assert status"
1246 )
1247}
1248
1249all-tests() {
1250
1251 section-banner 'Runtime errors - Unquoted test functions'
1252 # Legacy functions that don't check status
1253 run-unquoted-funcs
1254
1255 section-banner 'Runtime errors - test functions'
1256
1257 # Run with strict mode
1258 set -o nounset
1259 set -o pipefail
1260 set -o errexit
1261
1262 run-test-funcs
1263}
1264
1265# TODO: could show these as separate text files in the CI
1266
1267with-bash() {
1268 SH_ASSERT_DISABLE=1 OSH=bash YSH=bash run-test-funcs
1269}
1270
1271with-dash() {
1272 SH_ASSERT_DISABLE=1 OSH=dash YSH=dash run-test-funcs
1273}
1274
1275soil-run-py() {
1276 all-tests
1277}
1278
1279soil-run-cpp() {
1280 # TODO: There are some UBSAN errors, like downcasting mylib::LineReader.
1281 # Is that a real problem? Could be due to mylib::File.
1282
1283 #local osh=_bin/cxx-ubsan/osh
1284
1285 local osh=_bin/cxx-asan/osh
1286
1287 ninja $osh
1288 OSH=$osh all-tests
1289}
1290
1291run-for-release() {
1292 run-other-suite-for-release runtime-errors all-tests
1293}
1294
1295"$@"