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

1276 lines, 670 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
303 # More chains
304 _strict-errexit-case '{ echo 1; echo 2; } && true && true'
305 _strict-errexit-case 'true && { echo 1; echo 2; } || true || true'
306 _strict-errexit-case 'true && true && { echo 1; echo 2; } || true || true'
307
308 _strict-errexit-case 'if { echo 1; echo 2; }; then echo IF; fi'
309 _strict-errexit-case 'while { echo 1; echo 2; }; do echo WHILE; done'
310 _strict-errexit-case 'until { echo 1; echo 2; }; do echo UNTIL; done'
311
312 # Must be separate lines for parsing options to take effect
313 _strict-errexit-case 'shopt -s ysh:upgrade
314 proc p { echo p }
315 if p { echo hi }'
316}
317
318test-strict_errexit_conditionals() {
319 # this works, even though this is a subshell
320 _strict-errexit-case '
321myfunc() { return 1; }
322
323while ( myfunc )
324do
325 echo yes
326done
327'
328
329 # Conditional - Proc - Child Interpreter Problem (command sub)
330 # Same problem here. A proc run in a command sub LOSES the exit code.
331 _strict-errexit-case '
332myfunc() { return 1; }
333
334while test "$(myfunc)" != ""
335do
336 echo yes
337done
338'
339
340 # Process Sub is be disallowed; it could invoke a proc!
341 _strict-errexit-case '
342myfunc() { return 1; }
343
344if cat <(ls)
345then
346 echo yes
347fi
348'
349
350 # Conditional - Proc - Child Interpreter Problem (pipeline)
351 _strict-errexit-case '
352myfunc() {
353 return 1
354}
355
356set -o pipefail
357while myfunc | cat
358do
359 echo yes
360done
361'
362
363 # regression for issue #1107 bad error message
364 # Also revealed #1113: the strict_errexit check was handled inside the
365 # command sub process!
366 _strict-errexit-case '
367myfunc() {
368 return 1
369}
370
371foo=$(true)
372
373# test assignment without proc
374while bar=$(false)
375do
376 echo yes
377done
378
379# issue 1007 was caused using command.ShAssignment, rather than the more common
380# command.Sentence with ;
381while spam=$(myfunc)
382do
383 echo yes
384done
385'
386}
387
388# OLD WAY OF BLAMING
389# Note: most of these don't fail
390test-strict-errexit-old() {
391 # Test out all the location info
392
393 # command.Pipeline.
394 _strict-errexit-case 'if ls | wc -l; then echo Pipeline; fi'
395 _strict-errexit-case 'if ! ls | wc -l; then echo Pipeline; fi'
396
397 # This one is ALLOWED
398 #_strict-errexit-case 'if ! ls; then echo Pipeline; fi'
399
400 # command.AndOr
401 #_strict-errexit-case 'if echo a && echo b; then echo AndOr; fi'
402
403 # command.DoGroup
404 _strict-errexit-case '! for x in a; do echo $x; done'
405
406 # command.BraceGroup
407 _strict-errexit-case '_func() { echo; }; ! _func'
408 _strict-errexit-case '! { echo brace; }; echo "should not get here"'
409
410 # command.Subshell
411 _strict-errexit-case '! ( echo subshell ); echo "should not get here"'
412
413 # command.WhileUntil
414 _strict-errexit-case '! while false; do echo while; done; echo "should not get here"'
415
416 # command.If
417 _strict-errexit-case '! if true; then false; fi; echo "should not get here"'
418
419 # command.Case
420 _strict-errexit-case '! case x in x) echo x;; esac; echo "should not get here"'
421
422 # command.TimeBlock
423 _strict-errexit-case '! time echo hi; echo "should not get here"'
424
425 # Command Sub
426 _strict-errexit-case '! echo $(echo hi); echo "should not get here"'
427}
428
429unquoted-pipefail() {
430 false | wc -l
431
432 set -o errexit
433 set -o pipefail
434 false | wc -l
435
436 echo 'SHOULD NOT GET HERE'
437}
438
439unquoted-pipefail_no_words() {
440 set -o errexit
441 set -o pipefail
442
443 # Make sure we can blame this
444 seq 3 | wc -l | > /nonexistent
445
446 echo done
447}
448
449unquoted-pipefail_func() {
450 set -o errexit -o pipefail
451 f42() {
452 cat
453 # NOTE: If you call 'exit 42', there is no error message displayed!
454 #exit 42
455 return 42
456 }
457
458 # TODO: blame the right location
459 echo hi | cat | f42 | wc
460
461 echo 'SHOULD NOT GET HERE'
462}
463
464# TODO: point to {. It's the same sas a subshell so you don't know exactly
465# which command failed.
466unquoted-pipefail_group() {
467 set -o errexit -o pipefail
468 echo hi | { cat; sh -c 'exit 42'; } | wc
469
470 echo 'SHOULD NOT GET HERE'
471}
472
473# TODO: point to (
474unquoted-pipefail_subshell() {
475 set -o errexit -o pipefail
476 echo hi | (cat; sh -c 'exit 42') | wc
477
478 echo 'SHOULD NOT GET HERE'
479}
480
481# TODO: point to 'while'
482unquoted-pipefail_while() {
483 set -o errexit -o pipefail
484 seq 3 | while true; do
485 read line
486 echo X $line X
487 if test "$line" = 2; then
488 sh -c 'exit 42'
489 fi
490 done | wc
491
492 echo 'SHOULD NOT GET HERE'
493}
494
495# Multiple errors from multiple processes
496# TODO: These errors get interleaved and messed up. Maybe we should always
497# print a single line from pipeline processes? We should set their
498# ErrorFormatter?
499unquoted-pipefail_multiple() {
500 set -o errexit -o pipefail
501 { echo 'four'; sh -c 'exit 4'; } |
502 { echo 'five'; sh -c 'exit 5'; } |
503 { echo 'six'; sh -c 'exit 6'; }
504}
505
506test-control_flow() {
507 # This prints a WARNING in bash. Not fatal in any shell except zsh.
508 _osh-error-X 0 '
509break
510continue
511echo UNREACHABLE
512'
513
514 _osh-error-X 1 '
515shopt -s strict_control_flow
516break
517continue
518echo UNREACHABLE
519'
520}
521
522# Errors from core/process.py
523test-core_process() {
524 _osh-error-1 '
525 echo foo > not/a/file
526 echo foo > /etc/no-perms-for-this
527 '
528
529 # DISABLED! This messes up the toil log file!
530 # echo hi 1>&3
531}
532
533# Errors from core/state.py
534test-core-state() {
535
536 _osh-should-run 'HOME=(a b)'
537
538 # $HOME is an exported string, so it shouldn't be changed to an array
539 _osh-error-1 'shopt --set strict_array; HOME=(a b)'
540}
541
542unquoted-ambiguous_redirect() {
543 echo foo > "$@"
544 echo 'ambiguous redirect not fatal unless errexit'
545
546 set -o errexit
547 echo foo > "$@"
548 echo 'should not get here'
549}
550
551# bash semantics.
552unquoted-ambiguous_redirect_context() {
553 # Problem: A WORD cannot fail. Only a COMMAND can fail.
554
555 # http://stackoverflow.com/questions/29532904/bash-subshell-errexit-semantics
556 # https://groups.google.com/forum/?fromgroups=#!topic/gnu.bash.bug/NCK_0GmIv2M
557
558 # http://unix.stackexchange.com/questions/23026/how-can-i-get-bash-to-exit-on-backtick-failure-in-a-similar-way-to-pipefail
559
560 echo $(echo hi > "$@")
561 echo 'ambiguous is NOT FATAL in command sub'
562 echo
563
564 foo=$(echo hi > "$@")
565 echo $foo
566 echo 'ambiguous is NOT FATAL in assignment in command sub'
567 echo
568
569 set -o errexit
570
571 # This is the issue addressed by command_sub_errexit!
572 echo $(echo hi > "$@")
573 echo 'ambiguous is NOT FATAL in command sub, even if errexit'
574 echo
575
576 # OK this one works. Because the exit code of the assignment is the exit
577 # code of the RHS?
578 echo 'But when the command sub is in an assignment, it is fatal'
579 foo=$(echo hi > "$@")
580 echo $foo
581 echo 'SHOULD NOT REACH HERE'
582}
583
584unquoted-bad_file_descriptor() {
585 : 1>&7
586}
587
588unquoted-command_sub_errexit() {
589 #set -o errexit
590 shopt -s command_sub_errexit || true
591 shopt -s inherit_errexit || true
592
593 echo t=$(true) f=$(false) 3=$(exit 3)
594 echo 'SHOULD NOT GET HERE'
595}
596
597unquoted-process_sub_fail() {
598 shopt -s process_sub_fail || true
599 shopt -s inherit_errexit || true
600 set -o errexit
601
602 cat <(echo a; exit 2) <(echo b; exit 3)
603 echo 'SHOULD NOT GET HERE'
604}
605
606myproc() {
607 echo ---
608 grep pat BAD # exits with code 2
609 #grep pat file.txt
610 echo ---
611}
612
613unquoted-bool_status() {
614 set -o errexit
615
616 if try --allow-status-01 -- myproc; then
617 echo 'match'
618 else
619 echo 'no match'
620 fi
621}
622
623unquoted-bool_status_simple() {
624 set -o errexit
625
626 if try --allow-status-01 -- grep pat BAD; then
627 echo 'match'
628 else
629 echo 'no match'
630 fi
631}
632
633#
634# WORD ERRORS
635#
636
637unquoted-nounset() {
638 set -o nounset
639 echo $x
640
641 echo 'SHOULD NOT GET HERE'
642}
643
644unquoted-bad_var_ref() {
645 name='bad var name'
646 echo ${!name}
647}
648
649#
650# ARITHMETIC ERRORS
651#
652
653unquoted-nounset_arith() {
654 set -o nounset
655 echo $(( x ))
656
657 echo 'SHOULD NOT GET HERE'
658}
659
660test-divzero() {
661 _osh-error-1 'echo $(( 1 / 0 ))'
662 # Better location
663 _osh-error-1 'echo $(( 1 / (3 -3 ) ))'
664 _osh-error-1 'echo $(( 1 % 0 ))'
665
666 _osh-error-1 'zero=0; echo $(( 1 / zero ))'
667 _osh-error-1 'zero=0; echo $(( 1 % zero ))'
668
669 _osh-error-1 '(( a = 1 / 0 )); echo non-fatal; exit 1'
670 _osh-error-1 '(( a = 1 % 0 )); echo non-fatal; exit 1'
671
672 # fatal!
673 _osh-error-1 'set -e; (( a = 1 / 0 ));'
674 _osh-error-1 'set -e; (( a = 1 % 0 ));'
675}
676
677test-unsafe_arith_eval() {
678 _osh-error-1 '
679 local e1=1+
680 local e2="e1 + 5"
681 echo $(( e2 )) # recursively references e1
682 '
683}
684
685test-unset_expr() {
686 _osh-error-1 'unset -v 1[1]'
687 _osh-error-2 'unset -v 1+2'
688}
689
690test-strict-arith() {
691 _osh-error-1 'shopt -s strict_arith; echo $(( undef[0] ))'
692 _osh-error-1 'shopt -s strict_arith; s=abc; echo $(( s[0] ))'
693 _osh-error-1 'shopt -s strict_arith; var i = 42; echo $(( i[0] ))'
694}
695
696# Only dash flags this as an error.
697unquoted-string_to_int_arith() {
698 local x='ZZZ'
699 echo $(( x + 5 ))
700
701 shopt -s strict_arith
702
703 echo $(( x + 5 ))
704
705 echo 'SHOULD NOT GET HERE'
706}
707
708# Hm bash treats this as a fatal error
709unquoted-string_to_hex() {
710 echo $(( 0xGG + 1 ))
711
712 echo 'SHOULD NOT GET HERE'
713}
714
715# Hm bash treats this as a fatal error
716unquoted-string_to_octal() {
717 echo $(( 018 + 1 ))
718
719 echo 'SHOULD NOT GET HERE'
720}
721
722# Hm bash treats this as a fatal error
723unquoted-string_to_intbase() {
724 echo $(( 16#GG ))
725
726 echo 'SHOULD NOT GET HERE'
727}
728
729unquoted-undef_arith() {
730 (( undef++ )) # doesn't make sense
731
732 # Can't assign to characters of string? Is that strong?
733 (( undef[42]++ ))
734}
735
736unquoted-undef_arith2() {
737 a=()
738
739 # undefined cell: This is kind of what happens in awk / "wok"
740 (( a[42]++ ))
741 (( a[42]++ ))
742 spec/bin/argv.py "${a[@]}"
743}
744
745unquoted-array_arith() {
746 a=(1 2)
747 (( a++ )) # doesn't make sense
748 echo "${a[@]}"
749}
750
751unquoted-undef-assoc-array() {
752 declare -A A
753 A['foo']=bar
754 echo "${A['foo']}"
755
756 A['spam']+=1
757 A['spam']+=1
758
759 spec/bin/argv.py "${A[@]}"
760
761 (( A['spam']+=5 ))
762
763 spec/bin/argv.py "${A[@]}"
764}
765
766test-assoc-array() {
767 _osh-error-1 'declare -A assoc; assoc[x]=1'
768 _osh-should-run 'declare -A assoc; assoc[$key]=1'
769
770 # double quotes
771 _osh-should-run 'declare -A assoc; assoc["x"]=1'
772
773 # single quotes
774 _osh-should-run-here <<'EOF'
775declare -A assoc; assoc['x']=1
776EOF
777
778 _osh-error-1 'declare -A assoc; echo ${assoc[x]}'
779 _osh-should-run 'declare -A assoc; echo ${assoc["x"]}'
780 _osh-should-run 'declare -A assoc; echo ${assoc[$key]}'
781
782 _osh-error-1 'declare -A assoc; key=k; unset assoc[$key]'
783 # quotes removed
784 _osh-error-1 'declare -A assoc; key=k; unset "assoc[$key]"'
785
786 # Like Samuel's Nix error
787 # unset -v "hardeningEnableMap[$flag]"
788 _osh-error-here-X 1 <<'EOF'
789declare -A assoc; key=k; unset "assoc[$key]"
790EOF
791
792 # SINGLE quotes fixes it
793 _osh-should-run-here <<'EOF'
794declare -A assoc; key=k; unset 'assoc[$key]'
795EOF
796
797 # Wrap in eval to see how it composes
798 _osh-error-here-X 1 <<'EOF'
799eval 'declare -A assoc; assoc[x]=1'
800EOF
801
802 _osh-error-here-X 1 <<'EOF'
803eval 'declare -A assoc; unset "assoc[x]"'
804EOF
805
806}
807
808unquoted-patsub_bad_glob() {
809 local x='abc'
810 # inspired by git-completion.bash
811 echo ${x//[^]}
812}
813
814
815#
816# BOOLEAN ERRORS
817#
818
819# Only osh cares about this.
820unquoted-string_to_int_bool() {
821 [[ a -eq 0 ]]
822
823 shopt -s strict_arith
824
825 [[ a -eq 0 ]]
826 echo 'SHOULD NOT GET HERE'
827}
828
829unquoted-strict_array() {
830 set -- 1 2
831 echo foo > _tmp/"$@"
832 shopt -s strict_array
833 echo foo > _tmp/"$@"
834}
835
836unquoted-strict_array_2() {
837 local foo="$@"
838 shopt -s strict_array
839 local foo="$@"
840}
841
842unquoted-strict_array_3() {
843 local foo=${1:- "[$@]" }
844 shopt -s strict_array
845 local foo=${1:- "[$@]" }
846}
847
848unquoted-strict_array_4() {
849 local -a x
850 x[42]=99
851 echo "x[42] = ${x[42]}"
852
853 # Not implemented yet
854 shopt -s strict_array
855 local -a y
856 y[42]=99
857}
858
859unquoted-array_assign_1() {
860 s=1
861 s[0]=x # can't assign value
862}
863
864unquoted-array_assign_2() {
865 _osh-error-1 'readonly -a array=(1 2 3); array[0]=x'
866
867 _osh-error-1 'readonly -a array=(1 2 3); export array'
868}
869
870unquoted-readonly_assign() {
871 _osh-error-1 'readonly x=1; x=2'
872
873 _osh-error-1 'readonly x=2; y=3 x=99'
874
875 _osh-error-1 'readonly x=2; declare x=99'
876 _osh-error-1 'readonly x=2; export x=99'
877}
878
879unquoted-multiple_assign() {
880 readonly x=1
881 # It blames x, not a!
882 a=1 b=2 x=42
883}
884
885unquoted-multiple_assign_2() {
886 readonly y
887 local x=1 y=$(( x ))
888 echo $y
889}
890
891unquoted-string_as_array() {
892 local str='foo'
893 echo $str
894 echo "${str[@]}"
895}
896
897#
898# BUILTINS
899#
900
901unquoted-builtin_bracket() {
902 set +o errexit
903
904 # xxx is not a valid file descriptor
905 [ -t xxx ]
906 [ -t '' ]
907
908 [ zz -eq 0 ]
909
910 # This is from a different evaluator
911 #[ $((a/0)) -eq 0 ]
912}
913
914unquoted-builtin_builtin() {
915 set +o errexit
916 builtin ls
917}
918
919unquoted-builtin_source() {
920 source
921
922 bad=/nonexistent/path
923 source $bad
924}
925
926unquoted-builtin_cd() {
927 ( unset HOME
928 cd
929 )
930
931 # TODO: Hm this gives a different useful error without location info
932 ( unset HOME
933 HOME=(a b)
934 cd
935 )
936
937 # TODO: Hm this gives a different useful error without location info
938 ( unset OLDPWD
939 cd -
940 )
941
942 ( cd /nonexistent
943 )
944}
945
946unquoted-builtin_pushd() {
947 pushd /nonexistent
948}
949
950unquoted-builtin_popd() {
951 popd # empty dir stack
952
953 (
954 local dir=$PWD/_tmp/runtime-error-popd
955 mkdir -p $dir
956 pushd $dir
957 pushd /
958 rmdir $dir
959 popd
960 )
961}
962
963unquoted-builtin_unset() {
964 local x=x
965 readonly a
966
967 unset x a
968 unset -v x a
969}
970
971unquoted-builtin_alias_unalias() {
972 alias zzz
973 unalias zzz
974}
975
976unquoted-builtin_help() {
977 help zzz
978}
979
980unquoted-builtin_trap() {
981 trap
982 trap EXIT
983
984 trap zzz yyy
985}
986
987unquoted-builtin_getopts() {
988 getopts
989 getopts 'a:'
990
991 # TODO: It would be nice to put this in a loop and use it properly
992 set -- -a
993 getopts 'a:' varname
994}
995
996builtin_printf() {
997 printf '%s %d\n' foo not_a_number
998 echo status=$?
999
1000 # bad arg recycling. This is really a runtime error.
1001 printf '%s %d\n' foo 3 bar
1002 echo status=$?
1003
1004 # invalid width
1005 printf '%*d\n' foo foo
1006 echo status=$?
1007
1008 # precision can't be specified
1009 printf '%.*d\n' foo foo
1010 echo status=$?
1011
1012 # precision can't be specified
1013 printf '%.*s\n' foo foo
1014 echo status=$?
1015
1016 # invalid time
1017 printf '%(%Y)T\n' z
1018 echo status=$?
1019
1020 # invalid time with no SPID
1021 printf '%(%Y)T\n'
1022 echo status=$?
1023
1024 # invalid integer with no SPID
1025 printf '%d %d %d\n' 1 2
1026 echo status=$?
1027}
1028
1029
1030unquoted-builtin_wait() {
1031 wait 1234578
1032}
1033
1034unquoted-builtin_exec() {
1035 exec nonexistent-command 1 2 3
1036 echo $?
1037}
1038
1039#
1040# Strict options (see spec/strict_options.sh)
1041#
1042
1043unquoted-strict_word_eval_warnings() {
1044 # Warnings when 'set +o strict_word_eval' is OFF
1045
1046 echo slice start negative
1047 s='abc'
1048 echo -${s: -2}-
1049
1050 echo slice length negative
1051 s='abc'
1052 echo -${s: 1: -2}-
1053
1054 # TODO: These need span IDs.
1055 # - invalid utf-8 and also invalid backslash escape
1056
1057 echo slice bad utf-8
1058 s=$(echo -e "\xFF")bcdef
1059 echo -${s:1:3}-
1060
1061 echo length bad utf-8
1062 echo ${#s}
1063}
1064
1065unquoted-strict_arith_warnings() {
1066 local x='xx'
1067 echo $(( x + 1 ))
1068
1069 # TODO: OSH is more lenient here actually
1070 local y='-yy-'
1071 echo $(( y + 1 ))
1072
1073 [[ $y -eq 0 ]]
1074
1075 echo 'done'
1076}
1077
1078test-control_flow_subshell() {
1079 _osh-error-1 '
1080 set -o errexit
1081 for i in $(seq 2); do
1082 echo $i
1083 ( break; echo oops)
1084 done
1085 '
1086}
1087
1088test-fallback-locations() {
1089 # Redirect
1090 _osh-error-1 'echo hi > /'
1091
1092 _osh-error-1 's=x; (( s[0] ))'
1093
1094 _osh-error-1 's=x; (( s[0] = 42 ))'
1095
1096 _osh-error-1 'set -u; (( undef ))'
1097
1098 _osh-error-1 '(( 3 ** -2 ))'
1099 echo
1100
1101 # DBracket
1102 _osh-error-1 'set -u; [[ $undef =~ . ]]'
1103
1104 # No good fallback info here, we need it
1105 _osh-error-1 '[[ $x =~ $(( 3 ** -2 )) ]]'
1106
1107 _osh-error-2 'type -x' # correctly points to -x
1108 _osh-error-2 'use'
1109
1110 # Assign builtin
1111 _osh-error-2 'export -f'
1112
1113 _osh-error-1 's=$(true) y=$(( 3 ** -2 ))'
1114
1115 _osh-error-1 'if s=$(true) y=$(( 3 ** -2 )); then echo hi; fi'
1116
1117 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( x ))'
1118 _osh-error-1 'shopt -s strict_arith; x=a; echo $(( $x ))'
1119 _osh-error-1 'shopt -s strict_arith; x=a; [[ $x -gt 3 ]]'
1120 _osh-error-1 'shopt -s strict_arith; shopt -u eval_unsafe_arith; x=a; [[ $x -gt 3 ]]'
1121
1122 _osh-error-1 'shopt -s strict_arith; x=0xgg; echo $(( x ))'
1123
1124 echo done
1125}
1126
1127test-var-op-qmark() {
1128 _osh-error-1 'echo ${zz?}'
1129 _osh-error-1 'echo ${zz:?}'
1130
1131 _osh-should-run 'zz=""; echo ${zz?}'
1132 _osh-error-1 'zz=""; echo ${zz:?}'
1133
1134 _osh-error-1 'echo ${zz?Required}'
1135 _osh-error-1 'echo ${zz:?Required}'
1136}
1137
1138test-external_cmd_typed_args() {
1139 _ysh-error-X 1 'cat ("myfile")'
1140}
1141
1142test-arith_ops_str() {
1143 _ysh-error-X 3 '= "100" + "10a"'
1144 _ysh-error-X 3 '= "100" - "10a"'
1145 _ysh-error-X 3 '= "100" * "10a"'
1146 _ysh-error-X 3 '= "100" / "10a"'
1147 _ysh-error-X 3 'var a = "100"; setvar a += "10a"'
1148 _ysh-error-X 3 'var a = "100"; setvar a -= "10a"'
1149 _ysh-error-X 3 'var a = "100"; setvar a *= "10a"'
1150 _ysh-error-X 3 'var a = "100"; setvar a /= "10a"'
1151 _ysh-error-X 3 '= "age: " + "100"'
1152 _ysh-error-X 3 'var myvar = "a string"
1153= 100 + myvar'
1154}
1155
1156assert-test-v-error() {
1157 local code=$1
1158
1159 # note: the test builtin fails with status 2, but the shell doesn't fail
1160 _osh-error-2 "shopt -s strict_word_eval; a=(1 2 3); $code"
1161}
1162
1163test-test-v-expr() {
1164 assert-test-v-error 'test -v ""'
1165 assert-test-v-error 'test -v "a[foo"'
1166 assert-test-v-error 'test -v "a[not-int]"'
1167 assert-test-v-error 'test -v "a[-42]"'
1168
1169 _osh-error-2 'shopt -s strict_word_eval; s=""; test -v s[0]'
1170}
1171
1172test-long-shell-line() {
1173 # Example from https://github.com/oilshell/oil/issues/1973
1174
1175 _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'
1176 echo
1177}
1178
1179test-int-overflow() {
1180 local pos='18446744073709551616'
1181 local neg='-18446744073709551616'
1182
1183 # TODO
1184 return
1185
1186if false; then
1187 # frontend/args.py
1188 _osh-error-1 "read -n $pos"
1189 _osh-error-1 "read -n $neg"
1190
1191 # osh/sh_expr_eval.py
1192 _osh-error-1 "s=$pos;"' echo $(( $s ))'
1193 _osh-error-1 "s=$neg;"' echo $(( $s ))'
1194fi
1195
1196 # builtins
1197 _osh-error-1 'printf %d'" $pos"
1198 _osh-error-1 'printf %d'" $neg"
1199
1200 _osh-error-1 "trap $pos ERR"
1201 _osh-error-1 "trap $neg ERR"
1202
1203 _osh-error-1 "ulimit $pos"
1204 _osh-error-1 "ulimit $neg"
1205}
1206
1207#
1208# TEST DRIVER
1209#
1210
1211list-unquoted-funcs() {
1212 ### These tests need assertions, with quoting
1213
1214 compgen -A function | egrep '^unquoted-'
1215}
1216
1217run-unquoted-funcs() {
1218 local i=0
1219 list-unquoted-funcs | ( while read -r test_func; do
1220 _run-test-func $test_func '' # don't assert status
1221 i=$((i + 1))
1222 done
1223
1224 # Hacky subshell for $i
1225 echo
1226 echo "$0: $i unquoted functions run. TODO: migrate to test-* to assert status"
1227 )
1228}
1229
1230all-tests() {
1231
1232 section-banner 'Runtime errors - Unquoted test functions'
1233 # Legacy functions that don't check status
1234 run-unquoted-funcs
1235
1236 section-banner 'Runtime errors - test functions'
1237
1238 # Run with strict mode
1239 set -o nounset
1240 set -o pipefail
1241 set -o errexit
1242
1243 run-test-funcs
1244}
1245
1246# TODO: could show these as separate text files in the CI
1247
1248with-bash() {
1249 SH_ASSERT_DISABLE=1 OSH=bash YSH=bash run-test-funcs
1250}
1251
1252with-dash() {
1253 SH_ASSERT_DISABLE=1 OSH=dash YSH=dash run-test-funcs
1254}
1255
1256soil-run-py() {
1257 all-tests
1258}
1259
1260soil-run-cpp() {
1261 # TODO: There are some UBSAN errors, like downcasting mylib::LineReader.
1262 # Is that a real problem? Could be due to mylib::File.
1263
1264 #local osh=_bin/cxx-ubsan/osh
1265
1266 local osh=_bin/cxx-asan/osh
1267
1268 ninja $osh
1269 OSH=$osh all-tests
1270}
1271
1272run-for-release() {
1273 run-other-suite-for-release runtime-errors all-tests
1274}
1275
1276"$@"