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

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