OILS / osh / cmd_eval.py View on Github | oilshell.org

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