OILS / osh / cmd_eval.py View on Github | oils.pub

2455 lines, 1499 significant
1#!/usr/bin/env python2
2# Copyright 2016 Andy Chu. All rights reserved.
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8"""
9cmd_eval.py -- Interpreter for the command language.
10
11Problems:
12$ < Makefile cat | < NOTES.txt head
13
14This just does head? Last one wins.
15"""
16from __future__ import print_function
17
18import sys
19
20from _devbuild.gen.id_kind_asdl import Id
21from _devbuild.gen.option_asdl import option_i
22from _devbuild.gen.syntax_asdl import (
23 IntParamBox,
24 loc,
25 loc_t,
26 loc_e,
27 Token,
28 CompoundWord,
29 command,
30 command_e,
31 command_t,
32 command_str,
33 condition,
34 condition_e,
35 condition_t,
36 case_arg,
37 case_arg_e,
38 case_arg_t,
39 BraceGroup,
40 Proc,
41 Func,
42 assign_op_e,
43 expr_t,
44 proc_sig,
45 proc_sig_e,
46 redir_param,
47 redir_param_e,
48 for_iter,
49 for_iter_e,
50 pat,
51 pat_e,
52 word,
53 Eggex,
54 List_of_command,
55 debug_frame,
56)
57from _devbuild.gen.runtime_asdl import (
58 cmd_value,
59 cmd_value_e,
60 CommandStatus,
61 flow_e,
62 RedirValue,
63 redirect_arg,
64 ProcArgs,
65 scope_e,
66 StatusArray,
67)
68from _devbuild.gen.types_asdl import redir_arg_type_e
69from _devbuild.gen.value_asdl import (value, value_e, value_t, y_lvalue,
70 y_lvalue_e, y_lvalue_t, LeftName, Obj)
71
72from core import bash_impl
73from core import dev
74from core import error
75from core import executor
76from core.error import e_die, e_die_status
77from core import num
78from core import pyos # Time(). TODO: rename
79from core import pyutil
80from core import state
81from display import ui
82from core import util
83from core import vm
84from frontend import consts
85from frontend import lexer
86from frontend import location
87from frontend import typed_args
88from osh import braces
89from osh import sh_expr_eval
90from osh import word_eval
91from mycpp import iolib
92from mycpp import mops
93from mycpp import mylib
94from mycpp.mylib import log, probe, switch, tagswitch, NewDict
95from ysh import expr_eval
96from ysh import func_proc
97from ysh import val_ops
98
99import posix_ as posix
100import libc # for fnmatch
101# Import this name directly because the C++ translation uses macros literally.
102from libc import FNM_CASEFOLD
103
104from typing import List, Dict, Tuple, Optional, Any, cast, TYPE_CHECKING
105
106if TYPE_CHECKING:
107 from _devbuild.gen.option_asdl import builtin_t
108 from _devbuild.gen.runtime_asdl import cmd_value_t
109 from _devbuild.gen.syntax_asdl import Redir, EnvPair
110 from core.alloc import Arena
111 from core import optview
112 from core.vm import _Executor, _AssignBuiltin
113 from builtin import trap_osh
114
115# flags for main_loop.Batch, ExecuteAndCatch. TODO: Should probably in
116# ExecuteAndCatch, along with SetValue() flags.
117IsMainProgram = 1 << 0 # the main shell program, not eval/source/subshell
118RaiseControlFlow = 1 << 1 # eval/source builtins
119OptimizeSubshells = 1 << 2
120MarkLastCommands = 1 << 3
121NoDebugTrap = 1 << 4
122NoErrTrap = 1 << 5
123
124_STRICT_ERREXIT_COND_MSG = "Command conditionals should only have one status, not %s (strict_errexit, OILS-ERR-300)"
125
126
127def MakeBuiltinArgv(argv1):
128 # type: (List[str]) -> cmd_value.Argv
129 argv = [''] # dummy for argv[0]
130 argv.extend(argv1)
131 missing = None # type: CompoundWord
132 return cmd_value.Argv(argv, [missing] * len(argv), False, None, None)
133
134
135class Deps(object):
136
137 def __init__(self):
138 # type: () -> None
139 self.mutable_opts = None # type: state.MutableOpts
140 self.dumper = None # type: dev.CrashDumper
141 self.debug_f = None # type: util._DebugFile
142
143
144def _HasManyStatuses(node):
145 # type: (command_t) -> Optional[command_t]
146 """Code patterns that are bad for POSIX errexit. For YSH strict_errexit.
147
148 Note: strict_errexit also uses
149 shopt --unset _allow_command_sub _allow_process_sub
150 """
151 UP_node = node
152 with tagswitch(node) as case:
153 # Atoms.
154 # TODO: Do we need YSH atoms here?
155 if case(command_e.Simple, command_e.DBracket, command_e.DParen):
156 return None
157
158 elif case(command_e.Redirect):
159 node = cast(command.Redirect, UP_node)
160 return _HasManyStatuses(node.child)
161
162 elif case(command_e.Sentence):
163 # Sentence check is for if false; versus if false
164 node = cast(command.Sentence, UP_node)
165 return _HasManyStatuses(node.child)
166
167 elif case(command_e.Pipeline):
168 node = cast(command.Pipeline, UP_node)
169 if len(node.children) == 1:
170 # '! false' is a pipeline that we want to ALLOW
171 # '! ( echo subshell )' is DISALLWOED
172 return _HasManyStatuses(node.children[0])
173 else:
174 # Multiple parts like 'ls | wc' is disallowed
175 return node
176
177 elif case(command_e.AndOr):
178 node = cast(command.AndOr, UP_node)
179 for c in node.children:
180 if _HasManyStatuses(c):
181 return c
182 return None # otherwise allow 'if true && true; ...'
183
184 # - ShAssignment could be allowed, though its exit code will always be
185 # 0 without command subs
186 # - Naively, (non-singleton) pipelines could be allowed because pipefail.
187 # BUT could be a proc executed inside a child process, which causes a
188 # problem: the strict_errexit check has to occur at runtime and there's
189 # no way to signal it ot the parent.
190 return node
191
192
193def ListInitializeTarget(old_val,
194 has_plus,
195 exec_opts,
196 blame_loc,
197 destructive=True):
198 # type: (value_t, bool, optview.Exec, loc_t, bool) -> value_t
199 UP_old_val = old_val
200 with tagswitch(old_val) as case:
201 if case(value_e.Undef):
202 return bash_impl.BashArray_New()
203 elif case(value_e.Str):
204 if has_plus:
205 if exec_opts.strict_array():
206 e_die("Can't convert Str to BashArray (strict_array)",
207 blame_loc)
208 old_val = cast(value.Str, UP_old_val)
209 return bash_impl.BashArray_FromList([old_val.s])
210 else:
211 return bash_impl.BashArray_New()
212 elif case(value_e.BashArray):
213 old_val = cast(value.BashArray, UP_old_val)
214 if not destructive:
215 if has_plus:
216 old_val = bash_impl.BashArray_Copy(old_val)
217 else:
218 old_val = bash_impl.BashArray_New()
219 return old_val
220 elif case(value_e.BashAssoc):
221 old_val = cast(value.BashAssoc, UP_old_val)
222 if not destructive:
223 if has_plus:
224 old_val = bash_impl.BashAssoc_Copy(old_val)
225 else:
226 old_val = bash_impl.BashAssoc_New()
227 return old_val
228 else:
229 e_die(
230 "Can't list-initialize a value of type %s" %
231 ui.ValType(old_val), blame_loc)
232
233
234def ListInitialize(val, initializer, has_plus, exec_opts, blame_loc, arith_ev):
235 # type: (value_t, value.InitializerList, bool, optview.Exec, loc_t, sh_expr_eval.ArithEvaluator) -> None
236 UP_val = val
237 with tagswitch(val) as case:
238 if case(value_e.BashArray):
239 val = cast(value.BashArray, UP_val)
240 bash_impl.BashArray_ListInitialize(val, initializer, has_plus,
241 blame_loc, arith_ev)
242 elif case(value_e.BashAssoc):
243 val = cast(value.BashAssoc, UP_val)
244 bash_impl.BashAssoc_ListInitialize(val, initializer, has_plus,
245 exec_opts, blame_loc)
246 else:
247 raise AssertionError(val.tag())
248
249
250def PlusEquals(old_val, val):
251 # type: (value_t, value_t) -> value_t
252 """Implement s+=val, typeset s+=val, etc."""
253
254 UP_old_val = old_val
255 UP_val = val
256
257 tag = val.tag()
258
259 with tagswitch(old_val) as case:
260 if case(value_e.Undef):
261 pass # val is RHS
262
263 elif case(value_e.Str):
264 if tag == value_e.Str:
265 old_val = cast(value.Str, UP_old_val)
266 str_to_append = cast(value.Str, UP_val)
267 val = value.Str(old_val.s + str_to_append.s)
268 else:
269 raise AssertionError() # parsing should prevent this
270
271 elif case(value_e.InternalStringArray, value_e.BashArray):
272 if tag == value_e.Str:
273 e_die("Can't append string to array")
274 else:
275 raise AssertionError() # parsing should prevent this
276
277 elif case(value_e.BashAssoc):
278 if tag == value_e.Str:
279 e_die("Can't append string to associative arrays")
280 else:
281 raise AssertionError() # parsing should prrevent this
282
283 else:
284 e_die("Can't append to value of type %s" % ui.ValType(old_val))
285
286 return val
287
288
289class ctx_LoopLevel(object):
290 """For checking for invalid control flow."""
291
292 def __init__(self, cmd_ev):
293 # type: (CommandEvaluator) -> None
294 cmd_ev.loop_level += 1
295 self.cmd_ev = cmd_ev
296
297 def __enter__(self):
298 # type: () -> None
299 pass
300
301 def __exit__(self, type, value, traceback):
302 # type: (Any, Any, Any) -> None
303 self.cmd_ev.loop_level -= 1
304
305
306def _IsSpecialBuiltin(cmd_val, posix_mode):
307 # type: (cmd_value_t, bool) -> bool
308 """
309 Note: I tried calculating this in EvalWordSequence2() and
310 SimpleEvalWordSequence2(). They have special hint_str logic for assignment
311 builtins.
312
313 But the hint_str doesn't respect word splitting, so it's better done here.
314 And it's annoying to have duplication in SimpleEvalWordSequence2().
315 """
316 # set eval : etc. are always special builtins. TODO: remove this param
317 posix_mode = True
318
319 UP_cmd_val = cmd_val
320 with tagswitch(cmd_val) as case:
321 if case(cmd_value_e.Assign):
322 # assignment builtins are special
323 return True
324 elif case(cmd_value_e.Argv):
325 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
326 if (posix_mode and consts.LookupSpecialBuiltin(cmd_val.argv[0])
327 != consts.NO_INDEX):
328 return True
329 return False
330
331
332class CommandEvaluator(object):
333 """Executes the program by tree-walking.
334
335 It also does some double-dispatch by passing itself into Eval() for
336 Compound/WordPart.
337 """
338
339 def __init__(
340 self,
341 mem, # type: state.Mem
342 exec_opts, # type: optview.Exec
343 errfmt, # type: ui.ErrorFormatter
344 procs, # type: state.Procs
345 assign_builtins, # type: Dict[builtin_t, _AssignBuiltin]
346 arena, # type: Arena
347 cmd_deps, # type: Deps
348 trap_state, # type: trap_osh.TrapState
349 signal_safe, # type: iolib.SignalSafe
350 ):
351 # type: (...) -> None
352 """
353 Args:
354 mem: Mem instance for storing variables
355 procs: dict of SHELL functions or 'procs'
356 builtins: dict of builtin callables
357 TODO: This should only be for assignment builtins?
358 cmd_deps: A bundle of stateless code
359 """
360 self.shell_ex = None # type: _Executor
361 self.arith_ev = None # type: sh_expr_eval.ArithEvaluator
362 self.bool_ev = None # type: sh_expr_eval.BoolEvaluator
363 self.expr_ev = None # type: expr_eval.ExprEvaluator
364 self.word_ev = None # type: word_eval.NormalWordEvaluator
365 self.tracer = None # type: dev.Tracer
366
367 self.mem = mem
368 # This is for shopt and set -o. They are initialized by flags.
369 self.exec_opts = exec_opts
370 self.errfmt = errfmt
371 self.procs = procs
372 self.assign_builtins = assign_builtins
373 self.arena = arena
374
375 self.mutable_opts = cmd_deps.mutable_opts
376 self.dumper = cmd_deps.dumper
377 self.debug_f = cmd_deps.debug_f # Used by ShellFuncAction too
378
379 self.trap_state = trap_state
380 self.signal_safe = signal_safe
381
382 self.loop_level = 0 # for detecting bad top-level break/continue
383 self.check_command_sub_status = False # a hack. Modified by ShellExecutor
384
385 self.status_array_pool = [] # type: List[StatusArray]
386
387 def CheckCircularDeps(self):
388 # type: () -> None
389 assert self.arith_ev is not None
390 assert self.bool_ev is not None
391 # Disabled for push OSH
392 #assert self.expr_ev is not None
393 assert self.word_ev is not None
394
395 def _RunAssignBuiltin(self, cmd_val):
396 # type: (cmd_value.Assign) -> int
397 """Run an assignment builtin.
398
399 Except blocks copied from RunBuiltin.
400 """
401 builtin_func = self.assign_builtins.get(cmd_val.builtin_id)
402 if builtin_func is None:
403 # This only happens with alternative Oils interpreters.
404 e_die("Assignment builtin %r not configured" % cmd_val.argv[0],
405 cmd_val.arg_locs[0])
406
407 io_errors = [] # type: List[error.IOError_OSError]
408 with vm.ctx_FlushStdout(io_errors):
409 with ui.ctx_Location(self.errfmt, cmd_val.arg_locs[0]):
410 try:
411 status = builtin_func.Run(cmd_val)
412 except (IOError, OSError) as e:
413 # e.g. declare -p > /dev/full
414 self.errfmt.PrintMessage(
415 '%s builtin I/O error: %s' %
416 (cmd_val.argv[0], pyutil.strerror(e)),
417 cmd_val.arg_locs[0])
418 return 1
419 except error.Usage as e: # Copied from RunBuiltin
420 arg0 = cmd_val.argv[0]
421 self.errfmt.PrefixPrint(e.msg, '%r ' % arg0, e.location)
422 return 2 # consistent error code for usage error
423
424 if len(io_errors): # e.g. declare -p > /dev/full
425 self.errfmt.PrintMessage(
426 '%s builtin I/O: %s' %
427 (cmd_val.argv[0], pyutil.strerror(io_errors[0])),
428 cmd_val.arg_locs[0])
429 return 1
430
431 return status
432
433 def _CheckStatus(self, status, cmd_st, node, default_loc):
434 # type: (int, CommandStatus, command_t, loc_t) -> None
435 """Raises error.ErrExit, maybe with location info attached."""
436
437 assert status >= 0, status
438
439 if status == 0:
440 return # Nothing to do
441
442 self._MaybeRunErrTrap()
443
444 if self.exec_opts.errexit():
445 # NOTE: Sometimes we print 2 errors
446 # - 'type -z' has a UsageError with location, then errexit
447 # - '> /nonexistent' has an I/O error, then errexit
448 # - Pipelines and subshells are compound. Commands within them fail.
449 # - however ( exit 33 ) only prints one message.
450 #
451 # But we will want something like 'false' to have location info.
452
453 UP_node = node
454 with tagswitch(node) as case:
455 if case(command_e.ShAssignment):
456 node = cast(command.ShAssignment, UP_node)
457 cmd_st.show_code = True # leaf
458 # Note: we show errors from assignments a=$(false) rarely: when
459 # errexit, inherit_errexit, verbose_errexit are on, but
460 # command_sub_errexit is off!
461
462 # Note: a subshell often doesn't fail on its own.
463 elif case(command_e.Subshell):
464 node = cast(command.Subshell, UP_node)
465 cmd_st.show_code = True # not sure about this, e.g. ( exit 42 )
466
467 elif case(command_e.Pipeline):
468 node = cast(command.Pipeline, UP_node)
469 cmd_st.show_code = True # not sure about this
470 # TODO: We should show which element of the pipeline failed!
471
472 desc = command_str(node.tag())
473
474 # Override location if explicitly passed.
475 # Note: this produces better results for process sub
476 # echo <(sort x)
477 # and different results for some pipelines:
478 # { ls; false; } | wc -l; echo hi # Point to | or first { ?
479 if default_loc.tag() != loc_e.Missing:
480 blame_loc = default_loc # type: loc_t
481 else:
482 blame_loc = location.TokenForCommand(node)
483
484 msg = '%s failed with status %d' % (desc, status)
485 raise error.ErrExit(status,
486 msg,
487 blame_loc,
488 show_code=cmd_st.show_code)
489
490 def _EvalRedirect(self, r):
491 # type: (Redir) -> RedirValue
492
493 result = RedirValue(r.op.id, r.op, r.loc, None)
494
495 arg = r.arg
496 UP_arg = arg
497 with tagswitch(arg) as case:
498 if case(redir_param_e.Word):
499 arg_word = cast(CompoundWord, UP_arg)
500
501 # Note: needed for redirect like 'echo foo > x$LINENO'
502 self.mem.SetTokenForLine(r.op)
503
504 # Could be computed at parse time?
505 redir_type = consts.RedirArgType(r.op.id)
506
507 if redir_type == redir_arg_type_e.Path:
508 # Redirects with path arguments are evaluated in a special
509 # way. bash and zsh allow globbing a path, but
510 # dash/ash/mksh don't.
511 #
512 # If there are multiple files, zsh opens BOTH, but bash
513 # makes the command fail with status 1. We mostly follow
514 # bash behavior.
515
516 # These don't match bash/zsh behavior
517 # val = self.word_ev.EvalWordToString(arg_word)
518 # val, has_extglob = self.word_ev.EvalWordToPattern(arg_word)
519 # Short-circuit with word_.StaticEval() also doesn't work
520 # with globs
521
522 # mycpp needs this explicit declaration
523 b = braces.BraceDetect(
524 arg_word) # type: Optional[word.BracedTree]
525 if b is not None:
526 raise error.RedirectEval(
527 'Brace expansion not allowed (try adding quotes)',
528 arg_word)
529
530 # Needed for globbing behavior
531 files = self.word_ev.EvalWordSequence([arg_word])
532
533 n = len(files)
534 if n == 0:
535 # happens in OSH on empty elision
536 # in YSH because simple_word_eval globs to zero
537 raise error.RedirectEval(
538 "Can't redirect to zero files", arg_word)
539 if n > 1:
540 raise error.RedirectEval(
541 "Can't redirect to more than one file", arg_word)
542
543 result.arg = redirect_arg.Path(files[0])
544 return result
545
546 elif redir_type == redir_arg_type_e.Desc: # e.g. 1>&2, 1>&-, 1>&2-
547 val = self.word_ev.EvalWordToString(arg_word)
548 t = val.s
549 if len(t) == 0:
550 raise error.RedirectEval(
551 "Redirect descriptor can't be empty", arg_word)
552 return None
553
554 try:
555 if t == '-':
556 result.arg = redirect_arg.CloseFd
557 elif t[-1] == '-':
558 target_fd = int(t[:-1])
559 result.arg = redirect_arg.MoveFd(target_fd)
560 else:
561 result.arg = redirect_arg.CopyFd(int(t))
562 except ValueError:
563 raise error.RedirectEval(
564 'Invalid descriptor %r. Expected D, -, or D- where D is an '
565 'integer' % t, arg_word)
566 return None
567
568 return result
569
570 else:
571 raise AssertionError('Unknown redirect op')
572
573 elif case(redir_param_e.HereWord):
574 arg = cast(redir_param.HereWord, UP_arg)
575
576 val = self.word_ev.EvalWordToString(arg.w)
577 assert val.tag() == value_e.Str, val
578
579 assert r.op.id == Id.Redir_TLess, r.op
580 #print(arg_word)
581
582 # NOTE: bash and mksh both add \n for
583 # read <<< 'hi'
584 #
585 # YSH doesn't do this for multi-line strings:
586 # read <<< '''
587 # read <<< u'''
588 # read <<< """
589 if arg.is_multiline:
590 s = val.s
591 else:
592 s = val.s + '\n'
593
594 result.arg = redirect_arg.HereDoc(s)
595 return result
596
597 elif case(redir_param_e.HereDoc):
598 arg = cast(redir_param.HereDoc, UP_arg)
599 w = CompoundWord(
600 arg.stdin_parts) # HACK: Wrap it in a word to eval
601 val = self.word_ev.EvalWordToString(w)
602 assert val.tag() == value_e.Str, val
603 result.arg = redirect_arg.HereDoc(val.s)
604 return result
605
606 else:
607 raise AssertionError('Unknown redirect type')
608
609 raise AssertionError('for -Wreturn-type in C++')
610
611 def _RunSimpleCommand(self, cmd_val, cmd_st, run_flags):
612 # type: (cmd_value_t, CommandStatus, int) -> int
613 """Private interface to run a simple command (including assignment)."""
614 UP_cmd_val = cmd_val
615 with tagswitch(UP_cmd_val) as case:
616 if case(cmd_value_e.Argv):
617 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
618 self.tracer.OnSimpleCommand(cmd_val.argv)
619 return self.shell_ex.RunSimpleCommand(cmd_val, cmd_st,
620 run_flags)
621
622 elif case(cmd_value_e.Assign):
623 cmd_val = cast(cmd_value.Assign, UP_cmd_val)
624 self.tracer.OnAssignBuiltin(cmd_val)
625 return self._RunAssignBuiltin(cmd_val)
626
627 else:
628 raise AssertionError()
629
630 def _EvalTempEnv(self, more_env, flags):
631 # type: (List[EnvPair], int) -> None
632 """For FOO=1 cmd."""
633 for e_pair in more_env:
634 val = self.word_ev.EvalRhsWord(e_pair.val)
635
636 has_plus = False # We currently do not support tmpenv+=()
637 initializer = None # type: value.InitializerList
638 if val.tag() == value_e.InitializerList:
639 initializer = cast(value.InitializerList, val)
640
641 lval = LeftName(e_pair.name, e_pair.left)
642 old_val = sh_expr_eval.OldValue(
643 lval,
644 self.mem,
645 None, # No nounset
646 e_pair.left)
647 val = ListInitializeTarget(old_val,
648 has_plus,
649 self.exec_opts,
650 e_pair.left,
651 destructive=False)
652
653 # Set each var so the next one can reference it. Example:
654 # FOO=1 BAR=$FOO ls /
655 self.mem.SetNamed(location.LName(e_pair.name),
656 val,
657 scope_e.LocalOnly,
658 flags=flags)
659
660 if initializer is not None:
661 ListInitialize(val, initializer, has_plus, self.exec_opts,
662 e_pair.left, self.arith_ev)
663
664 def _StrictErrExit(self, node):
665 # type: (command_t) -> None
666 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
667 return
668
669 bad_node = _HasManyStatuses(node)
670 if bad_node:
671 node_str = ui.CommandType(bad_node)
672 e_die(_STRICT_ERREXIT_COND_MSG % node_str, loc.Command(bad_node))
673
674 def _StrictErrExitList(self, node_list):
675 # type: (List[command_t]) -> None
676 """Not allowed, too confusing:
677
678 if grep foo eggs.txt; grep bar eggs.txt; then echo hi fi
679 """
680 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
681 return
682
683 if len(node_list) > 1:
684 e_die(
685 "strict_errexit only allows a single command. Hint: use 'try'.",
686 loc.Command(node_list[0]))
687
688 assert len(node_list) > 0
689 node = node_list[0]
690 bad_node = _HasManyStatuses(node)
691 if bad_node:
692 node_str = ui.CommandType(bad_node)
693 e_die(_STRICT_ERREXIT_COND_MSG % node_str, loc.Command(bad_node))
694
695 def _EvalCondition(self, cond, blame_tok):
696 # type: (condition_t, Token) -> bool
697 """
698 Args:
699 spid: for OSH conditions, where errexit was disabled -- e.g. if
700 for YSH conditions, it would be nice to blame the ( instead
701 """
702 b = False
703 UP_cond = cond
704 #log('cond %r', type(cond))
705 #log('cond %r', cond)
706 with tagswitch(cond) as case:
707 if case(condition_e.Shell):
708 cond = cast(List_of_command, UP_cond)
709 self._StrictErrExitList(cond)
710 with state.ctx_ErrExit(self.mutable_opts, False, blame_tok):
711 cond_status = self._ExecuteList(cond)
712
713 b = cond_status == 0
714
715 elif case(condition_e.YshExpr):
716 cond = cast(condition.YshExpr, UP_cond)
717 obj = self.expr_ev.EvalExpr(cond.e, blame_tok)
718 b = val_ops.ToBool(obj)
719
720 return b
721
722 def _EvalCaseArg(self, arg, blame):
723 # type: (case_arg_t, loc_t) -> value_t
724 """Evaluate a `case_arg` into a `value_t` which can be matched on in a case
725 command.
726 """
727 UP_arg = arg
728 with tagswitch(arg) as case:
729 if case(case_arg_e.Word):
730 arg = cast(case_arg.Word, UP_arg)
731 return self.word_ev.EvalWordToString(arg.w)
732
733 elif case(case_arg_e.YshExpr):
734 arg = cast(case_arg.YshExpr, UP_arg)
735 return self.expr_ev.EvalExpr(arg.e, blame)
736
737 else:
738 raise NotImplementedError()
739
740 def _DoVarDecl(self, node):
741 # type: (command.VarDecl) -> int
742 # x = 'foo' in Hay blocks
743
744 flags = state.YshDecl
745
746 if node.keyword is None:
747 # Note: there's only one LHS
748 lhs0 = node.lhs[0]
749 lval = LeftName(lhs0.name, lhs0.left)
750 assert node.rhs is not None, node
751 val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
752
753 flags |= state.SetReadOnly
754 self.mem.SetNamedYsh(lval, val, scope_e.LocalOnly, flags=flags)
755
756 else: # var or const
757 flags |= (state.SetReadOnly
758 if node.keyword.id == Id.KW_Const else 0)
759
760 # var x, y does null initialization
761 if node.rhs is None:
762 for i, lhs_val in enumerate(node.lhs):
763 lval = LeftName(lhs_val.name, lhs_val.left)
764 self.mem.SetNamedYsh(lval,
765 value.Null,
766 scope_e.LocalOnly,
767 flags=flags)
768 return 0
769
770 right_val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
771 lvals = None # type: List[LeftName]
772 rhs_vals = None # type: List[value_t]
773
774 num_lhs = len(node.lhs)
775 if num_lhs == 1:
776 lhs0 = node.lhs[0]
777 lvals = [LeftName(lhs0.name, lhs0.left)]
778 rhs_vals = [right_val]
779 else:
780 items = val_ops.ToList(
781 right_val, 'Destructuring assignment expected List',
782 node.keyword)
783
784 num_rhs = len(items)
785 if num_lhs != num_rhs:
786 raise error.Expr(
787 'Got %d places on the left, but %d values on right' %
788 (num_lhs, num_rhs), node.keyword)
789
790 lvals = []
791 rhs_vals = []
792 for i, lhs_val in enumerate(node.lhs):
793 lval = LeftName(lhs_val.name, lhs_val.left)
794 lvals.append(lval)
795 rhs_vals.append(items[i])
796
797 for i, lval in enumerate(lvals):
798 rval = rhs_vals[i]
799 self.mem.SetNamedYsh(lval,
800 rval,
801 scope_e.LocalOnly,
802 flags=flags)
803
804 return 0
805
806 def _DoMutation(self, node):
807 # type: (command.Mutation) -> None
808
809 with switch(node.keyword.id) as case2:
810 if case2(Id.KW_SetVar):
811 which_scopes = scope_e.LocalOnly
812 elif case2(Id.KW_SetGlobal):
813 which_scopes = scope_e.GlobalOnly
814 else:
815 raise AssertionError(node.keyword.id)
816
817 if node.op.id == Id.Arith_Equal:
818 right_val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
819
820 lvals = None # type: List[y_lvalue_t]
821 rhs_vals = None # type: List[value_t]
822
823 num_lhs = len(node.lhs)
824 if num_lhs == 1:
825 lvals = [self.expr_ev.EvalLhsExpr(node.lhs[0], which_scopes)]
826 rhs_vals = [right_val]
827 else:
828 items = val_ops.ToList(
829 right_val, 'Destructuring assignment expected List',
830 node.keyword)
831
832 num_rhs = len(items)
833 if num_lhs != num_rhs:
834 raise error.Expr(
835 'Got %d places on the left, but %d values on the right'
836 % (num_lhs, num_rhs), node.keyword)
837
838 lvals = []
839 rhs_vals = []
840 for i, lhs_val in enumerate(node.lhs):
841 lvals.append(
842 self.expr_ev.EvalLhsExpr(lhs_val, which_scopes))
843 rhs_vals.append(items[i])
844
845 for i, lval in enumerate(lvals):
846 rval = rhs_vals[i]
847
848 # setvar mylist[0] = 42
849 # setvar mydict['key'] = 42
850 UP_lval = lval
851
852 if lval.tag() == y_lvalue_e.Local:
853 lval = cast(LeftName, UP_lval)
854
855 self.mem.SetNamedYsh(lval, rval, which_scopes)
856
857 elif lval.tag() == y_lvalue_e.Container:
858 lval = cast(y_lvalue.Container, UP_lval)
859
860 obj = lval.obj
861 UP_obj = obj
862 with tagswitch(obj) as case:
863 if case(value_e.List):
864 obj = cast(value.List, UP_obj)
865 index = expr_eval._ConvertToInt(
866 lval.index, 'List index should be Int',
867 loc.Missing)
868 i = mops.BigTruncate(index)
869 try:
870 obj.items[i] = rval
871 except IndexError:
872 raise error.Expr('index out of range',
873 loc.Missing)
874
875 elif case(value_e.Dict):
876 obj = cast(value.Dict, UP_obj)
877 key = val_ops.ToStr(lval.index,
878 'Dict index should be Str',
879 loc.Missing)
880 obj.d[key] = rval
881
882 elif case(value_e.Obj):
883 obj = cast(Obj, UP_obj)
884 key = val_ops.ToStr(lval.index,
885 'Obj index should be Str',
886 loc.Missing)
887 obj.d[key] = rval
888
889 else:
890 raise error.TypeErr(
891 obj, "obj[index] expected List, Dict, or Obj",
892 loc.Missing)
893
894 else:
895 raise AssertionError()
896
897 else:
898 # Checked in the parser
899 assert len(node.lhs) == 1
900
901 aug_lval = self.expr_ev.EvalLhsExpr(node.lhs[0], which_scopes)
902 val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
903
904 self.expr_ev.EvalAugmented(aug_lval, val, node.op, which_scopes)
905
906 def _DoSimple(self, node, cmd_st):
907 # type: (command.Simple, CommandStatus) -> int
908 probe('cmd_eval', '_DoSimple_enter')
909
910 # PROBLEM: We want to log argv in 'xtrace' mode, but we may have already
911 # redirected here, which screws up logging. For example, 'echo hi
912 # >/dev/null 2>&1'. We want to evaluate argv and log it BEFORE applying
913 # redirects.
914
915 # Another problem:
916 # - tracing can be called concurrently from multiple processes, leading
917 # to overlap. Maybe have a mode that creates a file per process.
918 # xtrace-proc
919 # - line numbers for every command would be very nice. But then you have
920 # to print the filename too.
921
922 words = braces.BraceExpandWords(node.words)
923
924 # Note: Individual WORDS can fail
925 # - $() and <() can have failures. This can happen in DBracket,
926 # DParen, etc. too
927 # - Tracing: this can start processes for proc sub and here docs!
928 cmd_val = self.word_ev.EvalWordSequence2(words,
929 node.is_last_cmd,
930 allow_assign=True)
931
932 UP_cmd_val = cmd_val
933 if UP_cmd_val.tag() == cmd_value_e.Argv:
934 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
935
936 if len(cmd_val.argv): # it can be empty in rare cases
937 self.mem.SetLastArgument(cmd_val.argv[-1])
938 else:
939 self.mem.SetLastArgument('')
940
941 if node.typed_args or node.block: # guard to avoid allocs
942 cmd_val.proc_args = ProcArgs(node.typed_args, None, None, None)
943 func_proc.EvalTypedArgsToProc(self.expr_ev,
944 self.mem.CurrentFrame(),
945 self.mem.GlobalFrame(),
946 self.mutable_opts, node,
947 cmd_val.proc_args)
948 else:
949 if node.block:
950 e_die("ShAssignment builtins don't accept blocks",
951 node.block.brace_group.left)
952 cmd_val = cast(cmd_value.Assign, UP_cmd_val)
953
954 # Could reset $_ after assignment, but then we'd have to do it for
955 # all YSH constructs too. It's easier to let it persist. Other
956 # shells aren't consistent.
957 # self.mem.SetLastArgument('')
958
959 run_flags = executor.IS_LAST_CMD if node.is_last_cmd else 0
960
961 # NOTE: RunSimpleCommand may never return
962 if len(node.more_env): # I think this guard is necessary?
963 if self.exec_opts.env_obj(): # YSH
964 bindings = NewDict() # type: Dict[str, value_t]
965 with state.ctx_EnclosedFrame(self.mem, self.mem.CurrentFrame(),
966 self.mem.GlobalFrame(), bindings):
967 self._EvalTempEnv(node.more_env, 0)
968
969 # Push this on the prototype chain
970 with state.ctx_EnvObj(self.mem, bindings):
971 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
972
973 else: # OSH
974 if _IsSpecialBuiltin(cmd_val, self.exec_opts.posix()):
975 # Special builtins have their temp env persisted.
976 self._EvalTempEnv(node.more_env, state.SetExport)
977 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
978 else:
979 with state.ctx_Temp(self.mem):
980 self._EvalTempEnv(node.more_env, state.SetExport)
981 status = self._RunSimpleCommand(
982 cmd_val, cmd_st, run_flags)
983 else:
984 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
985
986 probe('cmd_eval', '_DoSimple_exit', status)
987 return status
988
989 def _DoExpandedAlias(self, node):
990 # type: (command.ExpandedAlias) -> int
991 # Expanded aliases need redirects and env bindings from the calling
992 # context, as well as redirects in the expansion!
993
994 # TODO: SetTokenForLine to OUTSIDE? Don't bother with stuff inside
995 # expansion, since aliases are discouraged.
996
997 if len(node.more_env):
998 with state.ctx_Temp(self.mem):
999 self._EvalTempEnv(node.more_env, state.SetExport)
1000 return self._Execute(node.child)
1001 else:
1002 return self._Execute(node.child)
1003
1004 def _DoPipeline(self, node, cmd_st):
1005 # type: (command.Pipeline, CommandStatus) -> int
1006 cmd_st.check_errexit = True
1007 for op in node.ops:
1008 if op.id != Id.Op_Pipe:
1009 e_die("|& isn't supported", op)
1010
1011 # Remove $_ before pipeline. This matches bash, and is important in
1012 # pipelines than assignments because pipelines are non-deterministic.
1013 self.mem.SetLastArgument('')
1014
1015 # Set status to INVALID value, because we MIGHT set cmd_st.pipe_status,
1016 # which _Execute() boils down into a status for us.
1017 status = -1
1018
1019 if node.negated is not None:
1020 self._StrictErrExit(node)
1021 with state.ctx_ErrExit(self.mutable_opts, False, node.negated):
1022 # '! grep' is parsed as a pipeline, according to the grammar, but
1023 # there's no pipe() call.
1024 if len(node.children) == 1:
1025 tmp_status = self._Execute(node.children[0])
1026 status = 1 if tmp_status == 0 else 0
1027 else:
1028 self.shell_ex.RunPipeline(node, cmd_st)
1029 cmd_st.pipe_negated = True
1030
1031 # errexit is disabled for !.
1032 cmd_st.check_errexit = False
1033 else:
1034 self.shell_ex.RunPipeline(node, cmd_st)
1035
1036 return status
1037
1038 def _DoShAssignment(self, node, cmd_st):
1039 # type: (command.ShAssignment, CommandStatus) -> int
1040 assert len(node.pairs) >= 1, node
1041
1042 # x=y is 'neutered' inside 'proc'
1043 which_scopes = self.mem.ScopesForWriting()
1044
1045 for pair in node.pairs:
1046 # The shell assignments should always have RHS. An AssignPair
1047 # stored in command_e.ShAssignment is constructed in
1048 # cmd_parse._MakeAssignPair, where rhs is explicitly constructed to
1049 # be CompoundWord or rhs_word.Empty.
1050 assert pair.rhs, pair.rhs
1051
1052 # RHS can be a string or initializer list.
1053 rhs = self.word_ev.EvalRhsWord(pair.rhs)
1054 assert isinstance(rhs, value_t), rhs
1055
1056 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
1057 has_plus = pair.op == assign_op_e.PlusEqual
1058
1059 initializer = None # type: value.InitializerList
1060 if rhs.tag() == value_e.InitializerList:
1061 initializer = cast(value.InitializerList, rhs)
1062
1063 # If rhs is an initializer list, we perform the initialization
1064 # of LHS.
1065 old_val = sh_expr_eval.OldValue(lval, self.mem, None,
1066 node.left)
1067
1068 val = ListInitializeTarget(old_val, has_plus, self.exec_opts,
1069 pair.left)
1070
1071 elif has_plus:
1072 # do not respect set -u
1073 old_val = sh_expr_eval.OldValue(lval, self.mem, None,
1074 node.left)
1075
1076 val = PlusEquals(old_val, rhs)
1077
1078 elif pair.rhs:
1079 # plain assignment
1080 val = rhs
1081
1082 # NOTE: In bash and mksh, declare -a myarray makes an empty cell
1083 # with Undef value, but the 'array' attribute.
1084
1085 flags = 0 # for tracing
1086 self.mem.SetValue(lval, val, which_scopes, flags=flags)
1087 if initializer is not None:
1088 ListInitialize(val, initializer, has_plus, self.exec_opts,
1089 pair.left, self.arith_ev)
1090
1091 self.tracer.OnShAssignment(lval, pair.op, rhs, flags, which_scopes)
1092
1093 # PATCH to be compatible with existing shells: If the assignment had a
1094 # command sub like:
1095 #
1096 # s=$(echo one; false)
1097 #
1098 # then its status will be in mem.last_status, and we can check it here.
1099 # If there was NOT a command sub in the assignment, then we don't want to
1100 # check it.
1101
1102 # Only do this if there was a command sub? How? Look at node?
1103 # Set a flag in mem? self.mem.last_status or
1104 if self.check_command_sub_status:
1105 last_status = self.mem.LastStatus()
1106 self._CheckStatus(last_status, cmd_st, node, loc.Missing)
1107 return last_status # A global assignment shouldn't clear $?.
1108 else:
1109 return 0
1110
1111 def _DoExpr(self, node):
1112 # type: (command.Expr) -> int
1113
1114 # call f(x) or = f(x)
1115 val = self.expr_ev.EvalExpr(node.e, loc.Missing)
1116
1117 if node.keyword.id == Id.Lit_Equals: # = f(x)
1118 io_errors = [] # type: List[error.IOError_OSError]
1119 with vm.ctx_FlushStdout(io_errors):
1120 try:
1121 ui.PrettyPrintValue('', val, mylib.Stdout())
1122 except (IOError, OSError) as e:
1123 self.errfmt.PrintMessage(
1124 'I/O error during = keyword: %s' % pyutil.strerror(e),
1125 node.keyword)
1126 return 1
1127
1128 if len(io_errors): # e.g. disk full, ulimit
1129 self.errfmt.PrintMessage(
1130 'I/O error during = keyword: %s' %
1131 pyutil.strerror(io_errors[0]), node.keyword)
1132 return 1
1133
1134 return 0
1135
1136 def _DoControlFlow(self, node):
1137 # type: (command.ControlFlow) -> int
1138 keyword = node.keyword
1139
1140 if node.arg_word: # Evaluate the argument
1141 str_val = self.word_ev.EvalWordToString(node.arg_word)
1142
1143 # Quirk: We need 'return $empty' to be valid for libtool. This is
1144 # another meaning of strict_control_flow, which also has to do with
1145 # break/continue at top level. It has the side effect of making
1146 # 'return ""' valid, which shells other than zsh fail on.
1147 if (len(str_val.s) == 0 and
1148 not self.exec_opts.strict_control_flow()):
1149 arg = 0
1150 else:
1151 try:
1152 arg = int(str_val.s) # all control flow takes an integer
1153 except ValueError:
1154 # Either a bad argument, or integer overflow
1155 e_die(
1156 '%r expected a small integer, got %r' %
1157 (lexer.TokenVal(keyword), str_val.s),
1158 loc.Word(node.arg_word))
1159
1160 # C++ int() does range checking, but Python doesn't. So let's
1161 # simulate it here for spec tests.
1162 # TODO: could be mylib.ToMachineInt()? Problem: 'int' in C/C++
1163 # could be more than 4 bytes. We are testing INT_MAX and
1164 # INT_MIN in gc_builtins.cc - those could be hard-coded.
1165 if mylib.PYTHON:
1166 max_int = (1 << 31) - 1
1167 min_int = -(1 << 31)
1168 if not (min_int <= arg <= max_int):
1169 e_die(
1170 '%r expected a small integer, got %r' %
1171 (lexer.TokenVal(keyword), str_val.s),
1172 loc.Word(node.arg_word))
1173 else:
1174 if keyword.id in (Id.ControlFlow_Exit, Id.ControlFlow_Return):
1175 arg = self.mem.LastStatus()
1176 else:
1177 arg = 1 # break or continue 1 level by default
1178
1179 self.tracer.OnControlFlow(consts.ControlFlowName(keyword.id), arg)
1180
1181 # NOTE: A top-level 'return' is OK, unlike in bash. If you can return
1182 # from a sourced script, it makes sense to return from a main script.
1183 if (keyword.id in (Id.ControlFlow_Break, Id.ControlFlow_Continue) and
1184 self.loop_level == 0):
1185 msg = 'Invalid control flow at top level'
1186 if self.exec_opts.strict_control_flow():
1187 e_die(msg, keyword)
1188 else:
1189 # Only print warnings, never fatal.
1190 # Bash oddly only exits 1 for 'return', but no other shell does.
1191 self.errfmt.PrefixPrint(msg, 'warning: ', keyword)
1192 return 0
1193
1194 if keyword.id == Id.ControlFlow_Exit:
1195 # handled differently than other control flow
1196 raise util.UserExit(arg)
1197 else:
1198 raise vm.IntControlFlow(keyword, arg)
1199
1200 def _DoAndOr(self, node, cmd_st):
1201 # type: (command.AndOr, CommandStatus) -> int
1202 # NOTE: && and || have EQUAL precedence in command mode. See case #13
1203 # in dbracket.test.sh.
1204
1205 left = node.children[0]
1206
1207 # Suppress failure for every child except the last one.
1208 self._StrictErrExit(left)
1209 with state.ctx_ErrExit(self.mutable_opts, False, node.ops[0]):
1210 status = self._Execute(left)
1211
1212 i = 1
1213 n = len(node.children)
1214 while i < n:
1215 #log('i %d status %d', i, status)
1216 child = node.children[i]
1217 op = node.ops[i - 1]
1218 op_id = op.id
1219
1220 #log('child %s op_id %s', child, op_id)
1221
1222 if op_id == Id.Op_DPipe and status == 0:
1223 i += 1
1224 continue # short circuit
1225
1226 elif op_id == Id.Op_DAmp and status != 0:
1227 i += 1
1228 continue # short circuit
1229
1230 if i == n - 1: # errexit handled differently for last child
1231 status = self._Execute(child)
1232 else:
1233 # blame the right && or ||
1234 self._StrictErrExit(child)
1235 with state.ctx_ErrExit(self.mutable_opts, False, op):
1236 status = self._Execute(child)
1237
1238 i += 1
1239
1240 return status
1241
1242 def _DoWhileUntil(self, node):
1243 # type: (command.WhileUntil) -> int
1244 status = 0
1245 with ctx_LoopLevel(self):
1246 while True:
1247 try:
1248 # blame while/until spid
1249 b = self._EvalCondition(node.cond, node.keyword)
1250 if node.keyword.id == Id.KW_Until:
1251 b = not b
1252 if not b:
1253 break
1254 status = self._Execute(node.body) # last one wins
1255
1256 except vm.IntControlFlow as e:
1257 status = 0
1258 action = e.HandleLoop()
1259 if action == flow_e.Break:
1260 break
1261 elif action == flow_e.Raise:
1262 raise
1263
1264 return status
1265
1266 def _DoForEach(self, node):
1267 # type: (command.ForEach) -> int
1268
1269 # for the 2 kinds of shell loop
1270 iter_list = None # type: List[str]
1271
1272 # for YSH loop
1273 iter_expr = None # type: expr_t
1274 expr_blame = None # type: loc_t
1275
1276 iterable = node.iterable
1277 UP_iterable = iterable
1278
1279 with tagswitch(node.iterable) as case:
1280 if case(for_iter_e.Args):
1281 iter_list = self.mem.GetArgv()
1282
1283 elif case(for_iter_e.Words):
1284 iterable = cast(for_iter.Words, UP_iterable)
1285 words = braces.BraceExpandWords(iterable.words)
1286 iter_list = self.word_ev.EvalWordSequence(words)
1287
1288 elif case(for_iter_e.YshExpr):
1289 iterable = cast(for_iter.YshExpr, UP_iterable)
1290 iter_expr = iterable.e
1291 expr_blame = iterable.blame
1292
1293 else:
1294 raise AssertionError()
1295
1296 n = len(node.iter_names)
1297 assert n > 0
1298
1299 i_name = None # type: Optional[LeftName]
1300 # required
1301 name1 = None # type: LeftName
1302 name2 = None # type: Optional[LeftName]
1303
1304 it2 = None # type: val_ops.Iterator
1305 if iter_expr: # for_expr.YshExpr
1306 val = self.expr_ev.EvalExpr(iter_expr, expr_blame)
1307
1308 UP_val = val
1309 with tagswitch(val) as case:
1310 if case(value_e.List):
1311 val = cast(value.List, UP_val)
1312 it2 = val_ops.ListIterator(val)
1313
1314 if n == 1:
1315 name1 = location.LName(node.iter_names[0])
1316 elif n == 2:
1317 i_name = location.LName(node.iter_names[0])
1318 name1 = location.LName(node.iter_names[1])
1319 else:
1320 # This is similar to a parse error
1321 e_die_status(
1322 2,
1323 'List iteration expects at most 2 loop variables',
1324 node.keyword)
1325
1326 elif case(value_e.Dict):
1327 val = cast(value.Dict, UP_val)
1328 it2 = val_ops.DictIterator(val)
1329
1330 if n == 1:
1331 name1 = location.LName(node.iter_names[0])
1332 elif n == 2:
1333 name1 = location.LName(node.iter_names[0])
1334 name2 = location.LName(node.iter_names[1])
1335 elif n == 3:
1336 i_name = location.LName(node.iter_names[0])
1337 name1 = location.LName(node.iter_names[1])
1338 name2 = location.LName(node.iter_names[2])
1339 else:
1340 raise AssertionError()
1341
1342 elif case(value_e.Range):
1343 val = cast(value.Range, UP_val)
1344 it2 = val_ops.RangeIterator(val)
1345
1346 if n == 1:
1347 name1 = location.LName(node.iter_names[0])
1348 elif n == 2:
1349 i_name = location.LName(node.iter_names[0])
1350 name1 = location.LName(node.iter_names[1])
1351 else:
1352 e_die_status(
1353 2,
1354 'Range iteration expects at most 2 loop variables',
1355 node.keyword)
1356
1357 elif case(value_e.Stdin):
1358 # TODO: This could changed to magic iterator?
1359 it2 = val_ops.StdinIterator(expr_blame)
1360 if n == 1:
1361 name1 = location.LName(node.iter_names[0])
1362 elif n == 2:
1363 i_name = location.LName(node.iter_names[0])
1364 name1 = location.LName(node.iter_names[1])
1365 else:
1366 e_die_status(
1367 2,
1368 'Stdin iteration expects at most 2 loop variables',
1369 node.keyword)
1370 else:
1371 raise error.TypeErr(
1372 val, 'for loop expected List, Dict, Range, or Stdin',
1373 node.keyword)
1374
1375 else:
1376 assert iter_list is not None, iter_list
1377
1378 #log('iter list %s', iter_list)
1379 it2 = val_ops.ArrayIter(iter_list)
1380
1381 if n == 1:
1382 name1 = location.LName(node.iter_names[0])
1383 elif n == 2:
1384 i_name = location.LName(node.iter_names[0])
1385 name1 = location.LName(node.iter_names[1])
1386 else:
1387 # This is similar to a parse error
1388 e_die_status(
1389 2, 'Argv iteration expects at most 2 loop variables',
1390 node.keyword)
1391
1392 status = 0 # in case we loop zero times
1393 with ctx_LoopLevel(self):
1394 while True:
1395 with state.ctx_LoopFrame(self.mem,
1396 self.exec_opts.for_loop_frames()):
1397 first = it2.FirstValue()
1398 #log('first %s', first)
1399 if first is None: # for StdinIterator
1400 #log('first is None')
1401 break
1402
1403 if first.tag() == value_e.Interrupted:
1404 self.RunPendingTraps()
1405 #log('Done running traps')
1406 continue
1407
1408 self.mem.SetLocalName(name1, first)
1409 if name2:
1410 self.mem.SetLocalName(name2, it2.SecondValue())
1411 if i_name:
1412 self.mem.SetLocalName(i_name, num.ToBig(it2.Index()))
1413
1414 # increment index before handling continue, etc.
1415 it2.Next()
1416
1417 try:
1418 status = self._Execute(node.body) # last one wins
1419 except vm.IntControlFlow as e:
1420 status = 0
1421 action = e.HandleLoop()
1422 if action == flow_e.Break:
1423 break
1424 elif action == flow_e.Raise:
1425 raise
1426
1427 return status
1428
1429 def _DoForExpr(self, node):
1430 # type: (command.ForExpr) -> int
1431
1432 status = 0
1433
1434 init = node.init
1435 for_cond = node.cond
1436 body = node.body
1437 update = node.update
1438
1439 self.arith_ev.Eval(init)
1440 with ctx_LoopLevel(self):
1441 while True:
1442 # We only accept integers as conditions
1443 cond_int = self.arith_ev.EvalToBigInt(for_cond)
1444 if mops.Equal(cond_int, mops.ZERO): # false
1445 break
1446
1447 try:
1448 status = self._Execute(body)
1449 except vm.IntControlFlow as e:
1450 status = 0
1451 action = e.HandleLoop()
1452 if action == flow_e.Break:
1453 break
1454 elif action == flow_e.Raise:
1455 raise
1456
1457 self.arith_ev.Eval(update)
1458
1459 return status
1460
1461 def _DoShFunction(self, node):
1462 # type: (command.ShFunction) -> None
1463
1464 # Note: shell functions don't have a captured frame. TODO: Get rid of
1465 # GlobalFrame as well. I think shell functions will be disallowed in
1466 # pure YSH. They appear in the __sh_funcs__, NOT in modules!
1467
1468 sh_func = value.Proc(node.name, node.name_tok,
1469 proc_sig.Open, node.body, None, True, None,
1470 self.mem.GlobalFrame())
1471 self.procs.DefineShellFunc(node.name, sh_func)
1472
1473 def _DoProc(self, node):
1474 # type: (Proc) -> None
1475 proc_name = lexer.TokenVal(node.name)
1476
1477 if node.sig.tag() == proc_sig_e.Closed:
1478 sig = cast(proc_sig.Closed, node.sig)
1479 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1480 else:
1481 proc_defaults = None
1482
1483 # no dynamic scope
1484 proc = value.Proc(proc_name, node.name, node.sig, node.body,
1485 proc_defaults, False, self.mem.CurrentFrame(),
1486 self.mem.GlobalFrame())
1487 self.procs.DefineProc(proc_name, proc)
1488
1489 def _DoFunc(self, node):
1490 # type: (Func) -> None
1491 name = lexer.TokenVal(node.name)
1492 lval = location.LName(name)
1493
1494 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1495 self.expr_ev, node)
1496 func_val = value.Func(name, node, pos_defaults, named_defaults,
1497 self.mem.CurrentFrame(), self.mem.GlobalFrame())
1498
1499 # TODO: I'm not observing a difference with the YshDecl flag? That
1500 # should prevent the parent scope from being modified.
1501 #self.mem.SetNamedYsh(lval, func_val, scope_e.LocalOnly, flags=state.YshDecl)
1502 self.mem.SetNamedYsh(lval, func_val, scope_e.LocalOnly)
1503
1504 def _DoIf(self, node):
1505 # type: (command.If) -> int
1506 status = -1
1507
1508 done = False
1509 for if_arm in node.arms:
1510 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1511 if b:
1512 status = self._ExecuteList(if_arm.action)
1513 done = True
1514 break
1515
1516 if not done and node.else_action is not None:
1517 status = self._ExecuteList(node.else_action)
1518
1519 assert status != -1, 'Should have been initialized'
1520 return status
1521
1522 def _DoCase(self, node):
1523 # type: (command.Case) -> int
1524
1525 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1526 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1527
1528 status = 0 # If there are no arms, it should be zero?
1529
1530 done = False # Should we try the next arm?
1531
1532 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1533 ignore_next_cond = False
1534
1535 for case_arm in node.arms:
1536 with tagswitch(case_arm.pattern) as case:
1537 if case(pat_e.Words):
1538 if to_match.tag() != value_e.Str:
1539 continue # A non-string `to_match` will never match a pat.Words
1540 to_match_str = cast(value.Str, to_match)
1541
1542 pat_words = cast(pat.Words, case_arm.pattern)
1543
1544 this_arm_matches = False
1545 if ignore_next_cond: # Special handling for ;&
1546 this_arm_matches = True
1547 ignore_next_cond = False
1548 else:
1549 for pat_word in pat_words.words:
1550 word_val = self.word_ev.EvalWordToString(
1551 pat_word, word_eval.QUOTE_FNMATCH)
1552
1553 if libc.fnmatch(word_val.s, to_match_str.s,
1554 fnmatch_flags):
1555 this_arm_matches = True
1556 break # Stop at first pattern
1557
1558 if this_arm_matches:
1559 status = self._ExecuteList(case_arm.action)
1560 done = True
1561
1562 # ;& and ;;& only apply to shell-style case
1563 if case_arm.right:
1564 id_ = case_arm.right.id
1565 if id_ == Id.Op_SemiAmp:
1566 # very weird semantic
1567 ignore_next_cond = True
1568 done = False
1569 elif id_ == Id.Op_DSemiAmp:
1570 # Keep going until next pattern
1571 done = False
1572
1573 elif case(pat_e.YshExprs):
1574 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1575
1576 for pat_expr in pat_exprs.exprs:
1577 expr_val = self.expr_ev.EvalExpr(
1578 pat_expr, case_arm.left)
1579
1580 if val_ops.ExactlyEqual(expr_val, to_match,
1581 case_arm.left):
1582 status = self._ExecuteList(case_arm.action)
1583 done = True
1584 break
1585
1586 elif case(pat_e.Eggex):
1587 eggex = cast(Eggex, case_arm.pattern)
1588 eggex_val = self.expr_ev.EvalEggex(eggex)
1589
1590 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1591 status = self._ExecuteList(case_arm.action)
1592 done = True
1593 break
1594
1595 elif case(pat_e.Else):
1596 status = self._ExecuteList(case_arm.action)
1597 done = True
1598 break
1599
1600 else:
1601 raise AssertionError()
1602
1603 if done: # first match wins
1604 break
1605
1606 return status
1607
1608 def _DoTimeBlock(self, node):
1609 # type: (command.TimeBlock) -> int
1610 # TODO:
1611 # - When do we need RUSAGE_CHILDREN?
1612 # - Respect TIMEFORMAT environment variable.
1613 # "If this variable is not set, Bash acts as if it had the value"
1614 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1615 # "A trailing newline is added when the format string is displayed."
1616
1617 s_real, s_user, s_sys = pyos.Time()
1618 status = self._Execute(node.pipeline)
1619 e_real, e_user, e_sys = pyos.Time()
1620 # note: mycpp doesn't support %.3f
1621 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1622
1623 return status
1624
1625 def _DoRedirect(self, node, cmd_st):
1626 # type: (command.Redirect, CommandStatus) -> int
1627
1628 status = 0
1629 redirects = [] # type: List[RedirValue]
1630
1631 try:
1632 for redir in node.redirects:
1633 redirects.append(self._EvalRedirect(redir))
1634 except error.RedirectEval as e:
1635 self.errfmt.PrettyPrintError(e)
1636 redirects = None
1637 except error.WordFailure as e:
1638 # This happens e.g. with the following cases:
1639 #
1640 # $ echo hi > foo-* # with failglob (FailGlob)
1641 # $ echo > ${!undef} # (VarSubFailure)
1642 #
1643 if not e.HasLocation():
1644 e.location = self.mem.GetFallbackLocation()
1645 self.errfmt.PrettyPrintError(e)
1646 redirects = None
1647
1648 if redirects is None:
1649 # Error evaluating redirect words
1650 status = 1
1651
1652 # Translation fix: redirect I/O errors may happen in a C++
1653 # destructor ~vm::ctx_Redirect, which means they must be signaled
1654 # by out params, not exceptions.
1655 io_errors = [] # type: List[error.IOError_OSError]
1656
1657 # If we evaluated redirects, apply/push them
1658 if status == 0:
1659 self.shell_ex.PushRedirects(redirects, io_errors)
1660 if len(io_errors):
1661 # core/process.py prints cryptic errors, so we repeat them
1662 # here. e.g. Bad File Descriptor
1663 self.errfmt.PrintMessage(
1664 'I/O error applying redirect: %s' %
1665 pyutil.strerror(io_errors[0]),
1666 self.mem.GetFallbackLocation())
1667 status = 1
1668
1669 # If we applied redirects successfully, run the command_t, and pop
1670 # them.
1671 if status == 0:
1672 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1673 status = self._Execute(node.child)
1674 if len(io_errors):
1675 # It would be better to point to the right redirect
1676 # operator, but we don't track it specifically
1677 e_die("Fatal error popping redirect: %s" %
1678 pyutil.strerror(io_errors[0]))
1679
1680 return status
1681
1682 def _LeafTick(self):
1683 # type: () -> None
1684 """Do periodic work while executing shell.
1685
1686 We may run traps, check for Ctrl-C, or garbage collect.
1687 """
1688 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1689 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1690 # and maybe throw an exception.
1691 self.RunPendingTraps()
1692 if self.signal_safe.PollUntrappedSigInt():
1693 raise KeyboardInterrupt()
1694
1695 # TODO: Does this mess up control flow analysis? If so, we can move it
1696 # back to the top of _Execute(), so there are fewer conditionals
1697 # involved. This function gets called in SOME branches of
1698 # self._Dispatch().
1699
1700 # Manual GC point before every statement
1701 mylib.MaybeCollect()
1702
1703 def _Dispatch(self, node, cmd_st):
1704 # type: (command_t, CommandStatus) -> int
1705 """Switch on the command_t variants and execute them."""
1706
1707 # If we call RunCommandSub in a recursive call to the executor, this will
1708 # be set true (if strict_errexit is false). But it only lasts for one
1709 # command.
1710 probe('cmd_eval', '_Dispatch', node.tag())
1711 self.check_command_sub_status = False
1712
1713 UP_node = node
1714 with tagswitch(node) as case:
1715 if case(command_e.Simple): # LEAF command
1716 node = cast(command.Simple, UP_node)
1717
1718 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1719 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1720 # TODO: blame_tok should always be set.
1721 if node.blame_tok is not None:
1722 self.mem.SetTokenForLine(node.blame_tok)
1723
1724 self._MaybeRunDebugTrap()
1725 cmd_st.check_errexit = True
1726 status = self._DoSimple(node, cmd_st)
1727 self._LeafTick()
1728
1729 elif case(command_e.ShAssignment): # LEAF command
1730 node = cast(command.ShAssignment, UP_node)
1731
1732 self.mem.SetTokenForLine(node.pairs[0].left)
1733 self._MaybeRunDebugTrap()
1734
1735 # Only unqualified assignment a=b
1736 status = self._DoShAssignment(node, cmd_st)
1737 self._LeafTick()
1738
1739 elif case(command_e.Sentence): # NOT leaf, but put it up front
1740 node = cast(command.Sentence, UP_node)
1741
1742 # Don't check_errexit since this isn't a leaf command
1743 if node.terminator.id == Id.Op_Semi:
1744 status = self._Execute(node.child)
1745 else:
1746 status = self.shell_ex.RunBackgroundJob(node.child)
1747
1748 elif case(command_e.DBracket): # LEAF command
1749 node = cast(command.DBracket, UP_node)
1750
1751 self.mem.SetTokenForLine(node.left)
1752 self._MaybeRunDebugTrap()
1753
1754 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1755
1756 cmd_st.check_errexit = True
1757 cmd_st.show_code = True # this is a "leaf" for errors
1758 result = self.bool_ev.EvalB(node.expr)
1759 status = 0 if result else 1
1760 self._LeafTick()
1761
1762 elif case(command_e.DParen): # LEAF command
1763 node = cast(command.DParen, UP_node)
1764
1765 self.mem.SetTokenForLine(node.left)
1766 self._MaybeRunDebugTrap()
1767
1768 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1769
1770 cmd_st.check_errexit = True
1771 cmd_st.show_code = True # this is a "leaf" for errors
1772 i = self.arith_ev.EvalToBigInt(node.child)
1773 status = 1 if mops.Equal(i, mops.ZERO) else 0
1774 self._LeafTick()
1775
1776 elif case(command_e.ControlFlow): # LEAF command
1777 node = cast(command.ControlFlow, UP_node)
1778
1779 self.mem.SetTokenForLine(node.keyword)
1780 self._MaybeRunDebugTrap()
1781
1782 status = self._DoControlFlow(node)
1783 # Omit _LeafTick() since we likely raise an exception above
1784
1785 elif case(command_e.NoOp): # LEAF
1786 status = 0 # make it true
1787
1788 elif case(command_e.VarDecl): # YSH LEAF command
1789 node = cast(command.VarDecl, UP_node)
1790
1791 # Point to var name (bare assignment has no keyword)
1792 self.mem.SetTokenForLine(node.lhs[0].left)
1793 status = self._DoVarDecl(node)
1794 self._LeafTick()
1795
1796 elif case(command_e.Mutation): # YSH LEAF command
1797 node = cast(command.Mutation, UP_node)
1798
1799 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1800 self._DoMutation(node)
1801 status = 0 # if no exception is thrown, it succeeds
1802 self._LeafTick()
1803
1804 elif case(command_e.Expr): # YSH LEAF command
1805 node = cast(command.Expr, UP_node)
1806
1807 self.mem.SetTokenForLine(node.keyword)
1808 # YSH debug trap?
1809
1810 status = self._DoExpr(node)
1811 self._LeafTick()
1812
1813 elif case(command_e.Retval): # YSH LEAF command
1814 node = cast(command.Retval, UP_node)
1815
1816 self.mem.SetTokenForLine(node.keyword)
1817 # YSH debug trap? I think we don't want the debug trap in func
1818 # dialect, for speed?
1819
1820 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1821 self._LeafTick()
1822
1823 raise vm.ValueControlFlow(node.keyword, val)
1824
1825 #
1826 # More commands that involve recursive calls
1827 #
1828
1829 elif case(command_e.ExpandedAlias):
1830 node = cast(command.ExpandedAlias, UP_node)
1831 status = self._DoExpandedAlias(node)
1832
1833 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1834 # DoGroup has 'do' and 'done' spids for translation.
1835 elif case(command_e.CommandList):
1836 node = cast(command.CommandList, UP_node)
1837 status = self._ExecuteList(node.children)
1838
1839 elif case(command_e.DoGroup):
1840 node = cast(command.DoGroup, UP_node)
1841 status = self._ExecuteList(node.children)
1842
1843 elif case(command_e.BraceGroup):
1844 node = cast(BraceGroup, UP_node)
1845 status = self._ExecuteList(node.children)
1846
1847 elif case(command_e.AndOr):
1848 node = cast(command.AndOr, UP_node)
1849 status = self._DoAndOr(node, cmd_st)
1850
1851 elif case(command_e.If):
1852 node = cast(command.If, UP_node)
1853
1854 # Perf bug fix: loops might only execute 'if', but we still
1855 # need to GC
1856 self._LeafTick()
1857
1858 # No SetTokenForLine() because
1859 # - $LINENO can't appear directly in 'if'
1860 # - 'if' doesn't directly cause errors
1861 # It will be taken care of by command.Simple, condition, etc.
1862 status = self._DoIf(node)
1863
1864 elif case(command_e.Case):
1865 node = cast(command.Case, UP_node)
1866
1867 # Perf bug fix: loops might only execute 'case', but we still
1868 # need to GC
1869 self._LeafTick()
1870
1871 # Must set location for 'case $LINENO'
1872 self.mem.SetTokenForLine(node.case_kw)
1873 self._MaybeRunDebugTrap()
1874 status = self._DoCase(node)
1875
1876 elif case(command_e.WhileUntil):
1877 node = cast(command.WhileUntil, UP_node)
1878
1879 self.mem.SetTokenForLine(node.keyword)
1880 status = self._DoWhileUntil(node)
1881
1882 elif case(command_e.ForEach):
1883 node = cast(command.ForEach, UP_node)
1884
1885 self.mem.SetTokenForLine(node.keyword)
1886 status = self._DoForEach(node)
1887
1888 elif case(command_e.ForExpr):
1889 node = cast(command.ForExpr, UP_node)
1890
1891 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1892 status = self._DoForExpr(node)
1893
1894 elif case(command_e.Redirect):
1895 node = cast(command.Redirect, UP_node)
1896
1897 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1898 # dash/ash
1899 cmd_st.check_errexit = True
1900 status = self._DoRedirect(node, cmd_st)
1901
1902 elif case(command_e.Pipeline):
1903 node = cast(command.Pipeline, UP_node)
1904 status = self._DoPipeline(node, cmd_st)
1905
1906 elif case(command_e.Subshell):
1907 node = cast(command.Subshell, UP_node)
1908
1909 # This is a leaf from the parent process POV
1910 cmd_st.check_errexit = True
1911
1912 if node.is_last_cmd:
1913 # If the subshell is the last command in the process, just
1914 # run it in this process. See _MarkLastCommands().
1915 status = self._Execute(node.child)
1916 else:
1917 status = self.shell_ex.RunSubshell(node.child)
1918
1919 elif case(command_e.ShFunction):
1920 node = cast(command.ShFunction, UP_node)
1921 self._DoShFunction(node)
1922 status = 0
1923
1924 elif case(command_e.Proc):
1925 node = cast(Proc, UP_node)
1926 self._DoProc(node)
1927 status = 0
1928
1929 elif case(command_e.Func):
1930 node = cast(Func, UP_node)
1931
1932 # Needed for error, when the func is an existing variable name
1933 self.mem.SetTokenForLine(node.name)
1934
1935 self._DoFunc(node)
1936 status = 0
1937
1938 elif case(command_e.TimeBlock):
1939 node = cast(command.TimeBlock, UP_node)
1940 status = self._DoTimeBlock(node)
1941
1942 else:
1943 raise NotImplementedError(node.tag())
1944
1945 # Return to caller. Note the only case that didn't set it was Pipeline,
1946 # which set cmd_st.pipe_status.
1947 return status
1948
1949 def RunPendingTraps(self):
1950 # type: () -> None
1951
1952 trap_nodes = self.trap_state.GetPendingTraps()
1953 if trap_nodes is not None:
1954 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1955 True):
1956 for trap_node in trap_nodes:
1957 with state.ctx_Registers(self.mem):
1958 # TODO: show trap kind in trace
1959 with dev.ctx_Tracer(self.tracer, 'trap', None):
1960 # Note: exit status is lost
1961 self._Execute(trap_node)
1962
1963 def RunPendingTrapsAndCatch(self):
1964 # type: () -> None
1965 """
1966 Like the above, but calls ExecuteAndCatch(), which may raise util.UserExit
1967 """
1968 trap_nodes = self.trap_state.GetPendingTraps()
1969 if trap_nodes is not None:
1970 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1971 True):
1972 for trap_node in trap_nodes:
1973 with state.ctx_Registers(self.mem):
1974 # TODO: show trap kind in trace
1975 with dev.ctx_Tracer(self.tracer, 'trap', None):
1976 # Note: exit status is lost
1977 try:
1978 self.ExecuteAndCatch(trap_node, 0)
1979 except util.UserExit:
1980 # If user calls 'exit', stop running traps, but
1981 # we still run the EXIT trap later.
1982 break
1983
1984 def _Execute(self, node):
1985 # type: (command_t) -> int
1986 """Call _Dispatch(), and perform the errexit check."""
1987
1988 # Optimization: These 2 records have rarely-used lists, so we don't pass
1989 # alloc_lists=True. We create them on demand.
1990 cmd_st = CommandStatus.CreateNull()
1991 if len(self.status_array_pool):
1992 # Optimized to avoid allocs
1993 process_sub_st = self.status_array_pool.pop()
1994 else:
1995 process_sub_st = StatusArray.CreateNull()
1996
1997 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1998 try:
1999 status = self._Dispatch(node, cmd_st)
2000 except error.WordFailure as e: # e.g. echo hi > ${!undef}
2001 if not e.HasLocation(): # Last resort!
2002 e.location = self.mem.GetFallbackLocation()
2003 self.errfmt.PrettyPrintError(e)
2004 status = 1 # another redirect word eval error
2005 cmd_st.check_errexit = True # errexit for e.g. a=${!undef}
2006
2007 # Now we've waited for process subs
2008
2009 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
2010 # @_pipeline_status
2011 pipe_status = cmd_st.pipe_status
2012 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
2013 # makes it annoying to check both _process_sub_status and
2014 # _pipeline_status
2015
2016 errexit_loc = loc.Missing # type: loc_t
2017 if pipe_status is not None:
2018 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
2019 # for a REAL pipeline (but not singleton pipelines)
2020 assert status == -1, (
2021 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
2022 status)
2023
2024 self.mem.SetPipeStatus(pipe_status)
2025
2026 if self.exec_opts.pipefail():
2027 # The status is that of the last command that is non-zero.
2028 status = 0
2029 for i, st in enumerate(pipe_status):
2030 if st != 0:
2031 status = st
2032 errexit_loc = cmd_st.pipe_locs[i]
2033 else:
2034 # The status is that of last command, period.
2035 status = pipe_status[-1]
2036
2037 if cmd_st.pipe_negated:
2038 status = 1 if status == 0 else 0
2039
2040 # Compute status from _process_sub_status
2041 if process_sub_st.codes is None:
2042 # Optimized to avoid allocs
2043 self.status_array_pool.append(process_sub_st)
2044 else:
2045 codes = process_sub_st.codes
2046 self.mem.SetProcessSubStatus(codes)
2047 if status == 0 and self.exec_opts.process_sub_fail():
2048 # Choose the LAST non-zero status, consistent with pipefail above.
2049 for i, st in enumerate(codes):
2050 if st != 0:
2051 status = st
2052 errexit_loc = process_sub_st.locs[i]
2053
2054 self.mem.SetLastStatus(status)
2055
2056 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
2057 # However, any bash construct can appear in a pipeline. So it's easier
2058 # just to put it at the end, instead of after every node.
2059 #
2060 # Possible exceptions:
2061 # - function def (however this always exits 0 anyway)
2062 # - assignment - its result should be the result of the RHS?
2063 # - e.g. arith sub, command sub? I don't want arith sub.
2064 # - ControlFlow: always raises, it has no status.
2065 if cmd_st.check_errexit:
2066 #log('cmd_st %s', cmd_st)
2067 self._CheckStatus(status, cmd_st, node, errexit_loc)
2068
2069 return status
2070
2071 def _ExecuteList(self, children):
2072 # type: (List[command_t]) -> int
2073 status = 0 # for empty list
2074 for child in children:
2075 # last status wins
2076 status = self._Execute(child)
2077 return status
2078
2079 def LastStatus(self):
2080 # type: () -> int
2081 """For main_loop.py to determine the exit code of the shell itself."""
2082 return self.mem.LastStatus()
2083
2084 def _MarkLastCommands(self, node):
2085 # type: (command_t) -> None
2086
2087 if 0:
2088 log('optimizing')
2089 node.PrettyPrint(sys.stderr)
2090 log('')
2091
2092 UP_node = node
2093 with tagswitch(node) as case:
2094 if case(command_e.Simple):
2095 node = cast(command.Simple, UP_node)
2096 node.is_last_cmd = True
2097 if 0:
2098 log('Simple optimized')
2099
2100 elif case(command_e.Subshell):
2101 node = cast(command.Subshell, UP_node)
2102 # Mark ourselves as the last
2103 node.is_last_cmd = True
2104
2105 # Also mark 'date' as the last one
2106 # echo 1; (echo 2; date)
2107 self._MarkLastCommands(node.child)
2108
2109 elif case(command_e.Pipeline):
2110 node = cast(command.Pipeline, UP_node)
2111 # Bug fix: if we change the status, we can't exec the last
2112 # element!
2113 if node.negated is None and not self.exec_opts.pipefail():
2114 self._MarkLastCommands(node.children[-1])
2115
2116 elif case(command_e.Sentence):
2117 node = cast(command.Sentence, UP_node)
2118 self._MarkLastCommands(node.child)
2119
2120 elif case(command_e.Redirect):
2121 node = cast(command.Sentence, UP_node)
2122 # Don't need to restore the redirect in any of these cases:
2123
2124 # bin/osh -c 'echo hi 2>stderr'
2125 # bin/osh -c '{ echo hi; date; } 2>stderr'
2126 # echo hi 2>stderr | wc -l
2127
2128 self._MarkLastCommands(node.child)
2129
2130 elif case(command_e.CommandList):
2131 # Subshells often have a CommandList child
2132 node = cast(command.CommandList, UP_node)
2133 self._MarkLastCommands(node.children[-1])
2134
2135 elif case(command_e.BraceGroup):
2136 # TODO: What about redirects?
2137 node = cast(BraceGroup, UP_node)
2138 self._MarkLastCommands(node.children[-1])
2139
2140 def _RemoveSubshells(self, node):
2141 # type: (command_t) -> command_t
2142 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
2143
2144 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
2145 be correct otherwise.
2146 """
2147 UP_node = node
2148 with tagswitch(node) as case:
2149 if case(command_e.Subshell):
2150 node = cast(command.Subshell, UP_node)
2151 # Optimize ( ( date ) ) etc.
2152 return self._RemoveSubshells(node.child)
2153 return node
2154
2155 def ExecuteAndCatch(self, node, cmd_flags):
2156 # type: (command_t, int) -> Tuple[bool, bool]
2157 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
2158
2159 Args:
2160 node: LST subtree
2161 optimize: Whether to exec the last process rather than fork/exec
2162
2163 Returns:
2164 TODO: use enum 'why' instead of the 2 booleans
2165
2166 Used by
2167 - main_loop.py.
2168 - SubProgramThunk for pipelines, subshell, command sub, process sub
2169 - TODO: Signals besides EXIT trap
2170
2171 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
2172 finally_exit boolean. We use a different algorithm.
2173 """
2174 if cmd_flags & OptimizeSubshells:
2175 node = self._RemoveSubshells(node)
2176
2177 if cmd_flags & MarkLastCommands:
2178 # Mark the last command in each process, so we may avoid forks
2179 self._MarkLastCommands(node)
2180
2181 if 0:
2182 log('after opt:')
2183 node.PrettyPrint()
2184 log('')
2185
2186 is_return = False
2187 is_fatal = False
2188 is_errexit = False
2189
2190 err = None # type: error.FatalRuntime
2191 status = -1 # uninitialized
2192
2193 try:
2194 options = [] # type: List[int]
2195 if cmd_flags & NoDebugTrap:
2196 options.append(option_i._no_debug_trap)
2197 if cmd_flags & NoErrTrap:
2198 options.append(option_i._no_err_trap)
2199 with state.ctx_Option(self.mutable_opts, options, True):
2200 status = self._Execute(node)
2201 except vm.IntControlFlow as e:
2202 if cmd_flags & RaiseControlFlow:
2203 raise # 'eval break' and 'source return.sh', etc.
2204 else:
2205 # Return at top level is OK, unlike in bash.
2206 if e.IsReturn():
2207 is_return = True
2208 status = e.StatusCode()
2209 else:
2210 # TODO: This error message is invalid. Can also happen in eval.
2211 # We need a flag.
2212
2213 # Invalid control flow
2214 self.errfmt.Print_(
2215 "Loop and control flow can't be in different processes",
2216 blame_loc=e.token)
2217 is_fatal = True
2218 # All shells exit 0 here. It could be hidden behind
2219 # strict_control_flow if the incompatibility causes problems.
2220 status = 1
2221 except error.Parse as e:
2222 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
2223 raise
2224 except error.ErrExit as e:
2225 err = e
2226 is_errexit = True
2227 except error.FatalRuntime as e:
2228 err = e
2229
2230 if err:
2231 status = err.ExitStatus()
2232
2233 is_fatal = True
2234 # Do this before unwinding stack
2235 self.dumper.MaybeRecord(self, err)
2236
2237 if not err.HasLocation(): # Last resort!
2238 #log('Missing location')
2239 err.location = self.mem.GetFallbackLocation()
2240 #log('%s', err.location)
2241
2242 if is_errexit:
2243 if self.exec_opts.verbose_errexit():
2244 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2245 posix.getpid())
2246 else:
2247 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2248
2249 assert status >= 0, 'Should have been initialized'
2250
2251 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2252 # created a crash dump. So we get 2 or more of them.
2253 self.dumper.MaybeDump(status)
2254
2255 self.mem.SetLastStatus(status)
2256 return is_return, is_fatal
2257
2258 def EvalCommandFrag(self, frag):
2259 # type: (command_t) -> int
2260 """For builtins to evaluate command args.
2261
2262 Many exceptions are raised.
2263
2264 Examples:
2265
2266 cd /tmp (; ; mycmd)
2267
2268 And:
2269 eval (mycmd)
2270 call _io->eval(mycmd)
2271
2272 (Should those be more like eval 'mystring'?)
2273 """
2274 return self._Execute(frag) # can raise FatalRuntimeError, etc.
2275
2276 if 0:
2277
2278 def EvalCommandClosure(self, cmd):
2279 # type: (value.Command) -> int
2280 frag = typed_args.GetCommandFrag(cmd)
2281 with state.ctx_EnclosedFrame(self.mem, cmd.captured_frame,
2282 cmd.module_frame):
2283 return self.EvalCommandFrag(frag)
2284
2285 def RunTrapsOnExit(self, mut_status):
2286 # type: (IntParamBox) -> None
2287 """If an EXIT trap handler exists, run it.
2288
2289 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2290 all bash/dash/mksh seem to agree on it. See cases in
2291 builtin-trap.test.sh.
2292
2293 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2294 of this awkward interface. But that's true in Python and not C!
2295
2296 Could use i & (n-1) == i & 255 because we have a power of 2.
2297 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2298 """
2299 # This does not raise, even on 'exit', etc.
2300 self.RunPendingTrapsAndCatch()
2301
2302 node = self.trap_state.GetHook('EXIT') # type: command_t
2303 if node:
2304 # NOTE: Don't set option_i._running_trap, because that's for
2305 # RunPendingTraps() in the MAIN LOOP
2306 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2307 try:
2308 is_return, is_fatal = self.ExecuteAndCatch(node, 0)
2309 except util.UserExit as e: # explicit exit
2310 mut_status.i = e.status
2311 return
2312 if is_return: # explicit 'return' in the trap handler!
2313 mut_status.i = self.LastStatus()
2314
2315 def _MaybeRunDebugTrap(self):
2316 # type: () -> None
2317 """Run user-specified DEBUG code before certain commands."""
2318 node = self.trap_state.GetHook('DEBUG') # type: command_t
2319 if node is None:
2320 return
2321
2322 # Fix lastpipe / job control / DEBUG trap interaction
2323 if self.exec_opts._no_debug_trap():
2324 return
2325
2326 # Don't run recursively run traps, etc.
2327 if not self.mem.ShouldRunDebugTrap():
2328 return
2329
2330 # NOTE: Don't set option_i._running_trap, because that's for
2331 # RunPendingTraps() in the MAIN LOOP
2332
2333 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2334 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2335 # for SetTokenForLine $LINENO
2336 with state.ctx_DebugTrap(self.mem):
2337 # Don't catch util.UserExit, etc.
2338 self._Execute(node)
2339
2340 def _MaybeRunErrTrap(self):
2341 # type: () -> None
2342 """
2343 Run user-specified ERR code after checking the status of certain
2344 commands (pipelines)
2345 """
2346 node = self.trap_state.GetHook('ERR') # type: command_t
2347 if node is None:
2348 return
2349
2350 # ERR trap is only run for a whole pipeline, not its parts
2351 if self.exec_opts._no_err_trap():
2352 return
2353
2354 # Prevent infinite recursion
2355 if self.mem.running_err_trap:
2356 return
2357
2358 # "disabled errexit" rule
2359 if self.mutable_opts.ErrExitIsDisabled():
2360 return
2361
2362 # bash rule - affected by set -o errtrace
2363 if not self.exec_opts.errtrace() and self.mem.InsideFunction():
2364 return
2365
2366 # NOTE: Don't set option_i._running_trap, because that's for
2367 # RunPendingTraps() in the MAIN LOOP
2368
2369 # To make a better stack trace from vm.getDebugStack(), add the last
2370 # thing that failed, even if it's not a proc/func. This can be an
2371 # external command.
2372 #
2373 # TODO: this can lead to duplicate stack frames
2374 # - We might only want this if it's an external command?
2375 # - Or maybe we need a different trap to trigger stack traces ...
2376 self.mem.debug_stack.append(
2377 debug_frame.BeforeErrTrap(self.mem.token_for_line))
2378
2379 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2380 # TODO:
2381 # - use debug_frame.Trap
2382 # - use the original location of the 'trap' command?
2383 # - combine ctx_Tracer and debug stack? They are similar
2384 #with state.ctx_EvalDebugFrame(self.mem, self.mem.token_for_line):
2385
2386 # In bash, the PIPESTATUS register leaks. See spec/builtin-trap-err.
2387 # So unlike other traps, we don't isolate registers.
2388 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2389
2390 with state.ctx_ErrTrap(self.mem):
2391 self._Execute(node)
2392
2393 def RunProc(self, proc, cmd_val):
2394 # type: (value.Proc, cmd_value.Argv) -> int
2395 """Run procs aka "shell functions".
2396
2397 For SimpleCommand and registered completion hooks.
2398 """
2399 sig = proc.sig
2400 if sig.tag() == proc_sig_e.Closed:
2401 # We're binding named params. User should use @rest. No 'shift'.
2402 proc_argv = [] # type: List[str]
2403 else:
2404 proc_argv = cmd_val.argv[1:]
2405
2406 # Hm this sets "$@". TODO: Set ARGV only
2407 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv,
2408 cmd_val.arg_locs[0]):
2409 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2410
2411 # Redirects still valid for functions.
2412 # Here doc causes a pipe and Process(SubProgramThunk).
2413 try:
2414 status = self._Execute(proc.body)
2415 except vm.IntControlFlow as e:
2416 if e.IsReturn():
2417 status = e.StatusCode()
2418 else:
2419 # break/continue used in the wrong place.
2420 e_die(
2421 'Unexpected %r (in proc call)' %
2422 lexer.TokenVal(e.token), e.token)
2423 except error.FatalRuntime as e:
2424 # Dump the stack before unwinding it
2425 self.dumper.MaybeRecord(self, e)
2426 raise
2427
2428 return status
2429
2430 def RunFuncForCompletion(self, proc, argv):
2431 # type: (value.Proc, List[str]) -> int
2432 """
2433 Args:
2434 argv: $1 $2 $3 ... not including $0
2435 """
2436 cmd_val = MakeBuiltinArgv(argv)
2437
2438 # TODO: Change this to run YSH procs and funcs too
2439 try:
2440 status = self.RunProc(proc, cmd_val)
2441 except error.FatalRuntime as e:
2442 self.errfmt.PrettyPrintError(e)
2443 status = e.ExitStatus()
2444 except vm.IntControlFlow as e:
2445 # shouldn't be able to exit the shell from a completion hook!
2446 # TODO: Avoid overwriting the prompt!
2447 self.errfmt.Print_('Attempted to exit from completion hook.',
2448 blame_loc=e.token)
2449
2450 status = 1
2451 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2452 return status
2453
2454
2455# vim: sw=4