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

2274 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 perform the errexit check."""
1823
1824 # Optimization: These 2 records have rarely-used lists, so we don't pass
1825 # alloc_lists=True. We create them on demand.
1826 cmd_st = CommandStatus.CreateNull()
1827 if len(self.status_array_pool):
1828 # Optimized to avoid allocs
1829 process_sub_st = self.status_array_pool.pop()
1830 else:
1831 process_sub_st = StatusArray.CreateNull()
1832
1833 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1834 try:
1835 status = self._Dispatch(node, cmd_st)
1836 except error.FailGlob as e:
1837 if not e.HasLocation(): # Last resort!
1838 e.location = self.mem.GetFallbackLocation()
1839 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1840 status = 1 # another redirect word eval error
1841 cmd_st.check_errexit = True # failglob + errexit
1842
1843 # Now we've waited for process subs
1844
1845 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1846 # @_pipeline_status
1847 pipe_status = cmd_st.pipe_status
1848 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1849 # makes it annoying to check both _process_sub_status and
1850 # _pipeline_status
1851
1852 errexit_loc = loc.Missing # type: loc_t
1853 if pipe_status is not None:
1854 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1855 # for a REAL pipeline (but not singleton pipelines)
1856 assert status == -1, (
1857 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1858 status)
1859
1860 self.mem.SetPipeStatus(pipe_status)
1861
1862 if self.exec_opts.pipefail():
1863 # The status is that of the last command that is non-zero.
1864 status = 0
1865 for i, st in enumerate(pipe_status):
1866 if st != 0:
1867 status = st
1868 errexit_loc = cmd_st.pipe_locs[i]
1869 else:
1870 # The status is that of last command, period.
1871 status = pipe_status[-1]
1872
1873 if cmd_st.pipe_negated:
1874 status = 1 if status == 0 else 0
1875
1876 # Compute status from _process_sub_status
1877 if process_sub_st.codes is None:
1878 # Optimized to avoid allocs
1879 self.status_array_pool.append(process_sub_st)
1880 else:
1881 codes = process_sub_st.codes
1882 self.mem.SetProcessSubStatus(codes)
1883 if status == 0 and self.exec_opts.process_sub_fail():
1884 # Choose the LAST non-zero status, consistent with pipefail above.
1885 for i, st in enumerate(codes):
1886 if st != 0:
1887 status = st
1888 errexit_loc = process_sub_st.locs[i]
1889
1890 self.mem.SetLastStatus(status)
1891
1892 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1893 # However, any bash construct can appear in a pipeline. So it's easier
1894 # just to put it at the end, instead of after every node.
1895 #
1896 # Possible exceptions:
1897 # - function def (however this always exits 0 anyway)
1898 # - assignment - its result should be the result of the RHS?
1899 # - e.g. arith sub, command sub? I don't want arith sub.
1900 # - ControlFlow: always raises, it has no status.
1901 if cmd_st.check_errexit:
1902 #log('cmd_st %s', cmd_st)
1903 self._CheckStatus(status, cmd_st, node, errexit_loc)
1904
1905 return status
1906
1907 def _ExecuteList(self, children):
1908 # type: (List[command_t]) -> int
1909 status = 0 # for empty list
1910 for child in children:
1911 # last status wins
1912 status = self._Execute(child)
1913 return status
1914
1915 def LastStatus(self):
1916 # type: () -> int
1917 """For main_loop.py to determine the exit code of the shell itself."""
1918 return self.mem.LastStatus()
1919
1920 def _MarkLastCommands(self, node):
1921 # type: (command_t) -> None
1922
1923 if 0:
1924 log('optimizing')
1925 node.PrettyPrint(sys.stderr)
1926 log('')
1927
1928 UP_node = node
1929 with tagswitch(node) as case:
1930 if case(command_e.Simple):
1931 node = cast(command.Simple, UP_node)
1932 node.is_last_cmd = True
1933 if 0:
1934 log('Simple optimized')
1935
1936 elif case(command_e.Subshell):
1937 node = cast(command.Subshell, UP_node)
1938 # Mark ourselves as the last
1939 node.is_last_cmd = True
1940
1941 # Also mark 'date' as the last one
1942 # echo 1; (echo 2; date)
1943 self._MarkLastCommands(node.child)
1944
1945 elif case(command_e.Pipeline):
1946 node = cast(command.Pipeline, UP_node)
1947 # Bug fix: if we change the status, we can't exec the last
1948 # element!
1949 if node.negated is None and not self.exec_opts.pipefail():
1950 self._MarkLastCommands(node.children[-1])
1951
1952 elif case(command_e.Sentence):
1953 node = cast(command.Sentence, UP_node)
1954 self._MarkLastCommands(node.child)
1955
1956 elif case(command_e.Redirect):
1957 node = cast(command.Sentence, UP_node)
1958 # Don't need to restore the redirect in any of these cases:
1959
1960 # bin/osh -c 'echo hi 2>stderr'
1961 # bin/osh -c '{ echo hi; date; } 2>stderr'
1962 # echo hi 2>stderr | wc -l
1963
1964 self._MarkLastCommands(node.child)
1965
1966 elif case(command_e.CommandList):
1967 # Subshells often have a CommandList child
1968 node = cast(command.CommandList, UP_node)
1969 self._MarkLastCommands(node.children[-1])
1970
1971 elif case(command_e.BraceGroup):
1972 # TODO: What about redirects?
1973 node = cast(BraceGroup, UP_node)
1974 self._MarkLastCommands(node.children[-1])
1975
1976 def _RemoveSubshells(self, node):
1977 # type: (command_t) -> command_t
1978 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
1979
1980 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
1981 be correct otherwise.
1982 """
1983 UP_node = node
1984 with tagswitch(node) as case:
1985 if case(command_e.Subshell):
1986 node = cast(command.Subshell, UP_node)
1987 # Optimize ( ( date ) ) etc.
1988 return self._RemoveSubshells(node.child)
1989 return node
1990
1991 def ExecuteAndCatch(self, node, cmd_flags):
1992 # type: (command_t, int) -> Tuple[bool, bool]
1993 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
1994
1995 Args:
1996 node: LST subtree
1997 optimize: Whether to exec the last process rather than fork/exec
1998
1999 Returns:
2000 TODO: use enum 'why' instead of the 2 booleans
2001
2002 Used by
2003 - main_loop.py.
2004 - SubProgramThunk for pipelines, subshell, command sub, process sub
2005 - TODO: Signals besides EXIT trap
2006
2007 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
2008 finally_exit boolean. We use a different algorithm.
2009 """
2010 if cmd_flags & OptimizeSubshells:
2011 node = self._RemoveSubshells(node)
2012
2013 if cmd_flags & MarkLastCommands:
2014 # Mark the last command in each process, so we may avoid forks
2015 self._MarkLastCommands(node)
2016
2017 if 0:
2018 log('after opt:')
2019 node.PrettyPrint()
2020 log('')
2021
2022 is_return = False
2023 is_fatal = False
2024 is_errexit = False
2025
2026 err = None # type: error.FatalRuntime
2027 status = -1 # uninitialized
2028
2029 try:
2030 options = [] # type: List[int]
2031 if cmd_flags & NoDebugTrap:
2032 options.append(option_i._no_debug_trap)
2033 if cmd_flags & NoErrTrap:
2034 options.append(option_i._no_err_trap)
2035 with state.ctx_Option(self.mutable_opts, options, True):
2036 status = self._Execute(node)
2037 except vm.IntControlFlow as e:
2038 if cmd_flags & RaiseControlFlow:
2039 raise # 'eval break' and 'source return.sh', etc.
2040 else:
2041 # Return at top level is OK, unlike in bash.
2042 if e.IsReturn():
2043 is_return = True
2044 status = e.StatusCode()
2045 else:
2046 # TODO: This error message is invalid. Can also happen in eval.
2047 # We need a flag.
2048
2049 # Invalid control flow
2050 self.errfmt.Print_(
2051 "Loop and control flow can't be in different processes",
2052 blame_loc=e.token)
2053 is_fatal = True
2054 # All shells exit 0 here. It could be hidden behind
2055 # strict_control_flow if the incompatibility causes problems.
2056 status = 1
2057 except error.Parse as e:
2058 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
2059 raise
2060 except error.ErrExit as e:
2061 err = e
2062 is_errexit = True
2063 except error.FatalRuntime as e:
2064 err = e
2065
2066 if err:
2067 status = err.ExitStatus()
2068
2069 is_fatal = True
2070 # Do this before unwinding stack
2071 self.dumper.MaybeRecord(self, err)
2072
2073 if not err.HasLocation(): # Last resort!
2074 #log('Missing location')
2075 err.location = self.mem.GetFallbackLocation()
2076 #log('%s', err.location)
2077
2078 if is_errexit:
2079 if self.exec_opts.verbose_errexit():
2080 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2081 posix.getpid())
2082 else:
2083 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2084
2085 assert status >= 0, 'Should have been initialized'
2086
2087 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2088 # created a crash dump. So we get 2 or more of them.
2089 self.dumper.MaybeDump(status)
2090
2091 self.mem.SetLastStatus(status)
2092 return is_return, is_fatal
2093
2094 def EvalCommand(self, block):
2095 # type: (command_t) -> int
2096 """For builtins to evaluate command args.
2097
2098 Many exceptions are raised.
2099
2100 Examples:
2101
2102 cd /tmp (; ; mycmd)
2103
2104 And:
2105 eval (mycmd)
2106 call _io->eval(mycmd)
2107
2108 (Should those be more like eval 'mystring'?)
2109 """
2110 status = 0
2111 try:
2112 status = self._Execute(block) # can raise FatalRuntimeError, etc.
2113 except vm.IntControlFlow as e: # A block is more like a function.
2114 # return in a block
2115 if e.IsReturn():
2116 status = e.StatusCode()
2117 else:
2118 e_die('Unexpected control flow in block', e.token)
2119
2120 return status
2121
2122 def RunTrapsOnExit(self, mut_status):
2123 # type: (IntParamBox) -> None
2124 """If an EXIT trap handler exists, run it.
2125
2126 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2127 all bash/dash/mksh seem to agree on it. See cases in
2128 builtin-trap.test.sh.
2129
2130 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2131 of this awkward interface. But that's true in Python and not C!
2132
2133 Could use i & (n-1) == i & 255 because we have a power of 2.
2134 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2135 """
2136 # This does not raise, even on 'exit', etc.
2137 self.RunPendingTrapsAndCatch()
2138
2139 node = self.trap_state.GetHook('EXIT') # type: command_t
2140 if node:
2141 # NOTE: Don't set option_i._running_trap, because that's for
2142 # RunPendingTraps() in the MAIN LOOP
2143 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2144 try:
2145 is_return, is_fatal = self.ExecuteAndCatch(node, 0)
2146 except util.UserExit as e: # explicit exit
2147 mut_status.i = e.status
2148 return
2149 if is_return: # explicit 'return' in the trap handler!
2150 mut_status.i = self.LastStatus()
2151
2152 def _MaybeRunDebugTrap(self):
2153 # type: () -> None
2154 """Run user-specified DEBUG code before certain commands."""
2155 node = self.trap_state.GetHook('DEBUG') # type: command_t
2156 if node is None:
2157 return
2158
2159 # Fix lastpipe / job control / DEBUG trap interaction
2160 if self.exec_opts._no_debug_trap():
2161 return
2162
2163 # Don't run recursively run traps, etc.
2164 if not self.mem.ShouldRunDebugTrap():
2165 return
2166
2167 # NOTE: Don't set option_i._running_trap, because that's for
2168 # RunPendingTraps() in the MAIN LOOP
2169
2170 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2171 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2172 # for SetTokenForLine $LINENO
2173 with state.ctx_DebugTrap(self.mem):
2174 # Don't catch util.UserExit, etc.
2175 self._Execute(node)
2176
2177 def _MaybeRunErrTrap(self):
2178 # type: () -> None
2179 """
2180 Run user-specified ERR code after checking the status of certain
2181 commands (pipelines)
2182 """
2183 node = self.trap_state.GetHook('ERR') # type: command_t
2184 if node is None:
2185 return
2186
2187 # ERR trap is only run for a whole pipeline, not its parts
2188 if self.exec_opts._no_err_trap():
2189 return
2190
2191 # Prevent infinite recursion
2192 if self.mem.running_err_trap:
2193 return
2194
2195 # "disabled errexit" rule
2196 if self.mutable_opts.ErrExitIsDisabled():
2197 return
2198
2199 # bash rule - affected by set -o errtrace
2200 if not self.exec_opts.errtrace() and self.mem.InsideFunction():
2201 return
2202
2203 # NOTE: Don't set option_i._running_trap, because that's for
2204 # RunPendingTraps() in the MAIN LOOP
2205
2206 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2207 # In bash, the PIPESTATUS register leaks. See spec/builtin-trap-err.
2208 # So unlike other traps, we don't isolate registers.
2209 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2210 with state.ctx_ErrTrap(self.mem):
2211 self._Execute(node)
2212
2213 def RunProc(self, proc, cmd_val):
2214 # type: (value.Proc, cmd_value.Argv) -> int
2215 """Run procs aka "shell functions".
2216
2217 For SimpleCommand and registered completion hooks.
2218 """
2219 sig = proc.sig
2220 if sig.tag() == proc_sig_e.Closed:
2221 # We're binding named params. User should use @rest. No 'shift'.
2222 proc_argv = [] # type: List[str]
2223 else:
2224 proc_argv = cmd_val.argv[1:]
2225
2226 # Hm this sets "$@". TODO: Set ARGV only
2227 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2228 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2229
2230 # Redirects still valid for functions.
2231 # Here doc causes a pipe and Process(SubProgramThunk).
2232 try:
2233 status = self._Execute(proc.body)
2234 except vm.IntControlFlow as e:
2235 if e.IsReturn():
2236 status = e.StatusCode()
2237 else:
2238 # break/continue used in the wrong place.
2239 e_die(
2240 'Unexpected %r (in proc call)' %
2241 lexer.TokenVal(e.token), e.token)
2242 except error.FatalRuntime as e:
2243 # Dump the stack before unwinding it
2244 self.dumper.MaybeRecord(self, e)
2245 raise
2246
2247 return status
2248
2249 def RunFuncForCompletion(self, proc, argv):
2250 # type: (value.Proc, List[str]) -> int
2251 """
2252 Args:
2253 argv: $1 $2 $3 ... not including $0
2254 """
2255 cmd_val = MakeBuiltinArgv(argv)
2256
2257 # TODO: Change this to run YSH procs and funcs too
2258 try:
2259 status = self.RunProc(proc, cmd_val)
2260 except error.FatalRuntime as e:
2261 self.errfmt.PrettyPrintError(e)
2262 status = e.ExitStatus()
2263 except vm.IntControlFlow as e:
2264 # shouldn't be able to exit the shell from a completion hook!
2265 # TODO: Avoid overwriting the prompt!
2266 self.errfmt.Print_('Attempted to exit from completion hook.',
2267 blame_loc=e.token)
2268
2269 status = 1
2270 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2271 return status
2272
2273
2274# vim: sw=4