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

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