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

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