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

2276 lines, 1388 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 existing, _ = self.procs.GetInvokable(node.name)
1299 if existing and 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
1304 # Note: shell functions can read vars from the file they're defined in
1305 # But they don't appear in the module itself -- rather it is __sh_funcs__
1306 # Though we could consider disallowing them though on 'import'.
1307 sh_func = value.Proc(node.name, node.name_tok, proc_sig.Open,
1308 node.body, None, True, self.mem.GlobalFrame())
1309 self.procs.DefineShellFunc(node.name, sh_func)
1310
1311 def _DoProc(self, node):
1312 # type: (Proc) -> None
1313 proc_name = lexer.TokenVal(node.name)
1314
1315 # Note: this is similar 'const x = 42' and redefine_const -- it's a
1316 # dynamic check that it doesn't already exist
1317 # Also modules make this less necessary, because there are fewer name
1318 # conflicts
1319 # We could also define procs as READ-ONLY, but that means we need
1320 # Dict[str, Cell] and not Dict[str, value_t]
1321 existing, _ = self.procs.GetInvokable(proc_name)
1322 if existing and not self.exec_opts.redefine_proc_func():
1323 e_die(
1324 "Proc %s was already defined (redefine_proc_func)" % proc_name,
1325 node.name)
1326
1327 if node.sig.tag() == proc_sig_e.Closed:
1328 sig = cast(proc_sig.Closed, node.sig)
1329 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1330 else:
1331 proc_defaults = None
1332
1333 # no dynamic scope
1334 proc = value.Proc(proc_name, node.name, node.sig, node.body,
1335 proc_defaults, False, self.mem.GlobalFrame())
1336 self.procs.DefineProc(proc_name, proc)
1337
1338 def _DoFunc(self, node):
1339 # type: (Func) -> None
1340 name = lexer.TokenVal(node.name)
1341 lval = location.LName(name)
1342
1343 # Check that we haven't already defined a function
1344 cell = self.mem.GetCell(name, scope_e.LocalOnly)
1345 if cell and cell.val.tag() == value_e.Func:
1346 if self.exec_opts.redefine_proc_func():
1347 cell.readonly = False # Ensure we can unset the value
1348 did_unset = self.mem.Unset(lval, scope_e.LocalOnly)
1349 assert did_unset, name
1350 else:
1351 e_die(
1352 "Func %s was already defined (redefine_proc_func)" % name,
1353 node.name)
1354
1355 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1356 self.expr_ev, node)
1357 func_val = value.Func(name, node, pos_defaults, named_defaults,
1358 self.mem.GlobalFrame())
1359
1360 self.mem.SetNamed(lval,
1361 func_val,
1362 scope_e.LocalOnly,
1363 flags=state.SetReadOnly)
1364
1365 def _DoIf(self, node):
1366 # type: (command.If) -> int
1367 status = -1
1368
1369 done = False
1370 for if_arm in node.arms:
1371 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1372 if b:
1373 status = self._ExecuteList(if_arm.action)
1374 done = True
1375 break
1376
1377 if not done and node.else_action is not None:
1378 status = self._ExecuteList(node.else_action)
1379
1380 assert status != -1, 'Should have been initialized'
1381 return status
1382
1383 def _DoCase(self, node):
1384 # type: (command.Case) -> int
1385
1386 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1387 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1388
1389 status = 0 # If there are no arms, it should be zero?
1390
1391 done = False # Should we try the next arm?
1392
1393 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1394 ignore_next_cond = False
1395
1396 for case_arm in node.arms:
1397 with tagswitch(case_arm.pattern) as case:
1398 if case(pat_e.Words):
1399 if to_match.tag() != value_e.Str:
1400 continue # A non-string `to_match` will never match a pat.Words
1401 to_match_str = cast(value.Str, to_match)
1402
1403 pat_words = cast(pat.Words, case_arm.pattern)
1404
1405 this_arm_matches = False
1406 if ignore_next_cond: # Special handling for ;&
1407 this_arm_matches = True
1408 ignore_next_cond = False
1409 else:
1410 for pat_word in pat_words.words:
1411 word_val = self.word_ev.EvalWordToString(
1412 pat_word, word_eval.QUOTE_FNMATCH)
1413
1414 if libc.fnmatch(word_val.s, to_match_str.s,
1415 fnmatch_flags):
1416 this_arm_matches = True
1417 break # Stop at first pattern
1418
1419 if this_arm_matches:
1420 status = self._ExecuteList(case_arm.action)
1421 done = True
1422
1423 # ;& and ;;& only apply to shell-style case
1424 if case_arm.right:
1425 id_ = case_arm.right.id
1426 if id_ == Id.Op_SemiAmp:
1427 # very weird semantic
1428 ignore_next_cond = True
1429 done = False
1430 elif id_ == Id.Op_DSemiAmp:
1431 # Keep going until next pattern
1432 done = False
1433
1434 elif case(pat_e.YshExprs):
1435 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1436
1437 for pat_expr in pat_exprs.exprs:
1438 expr_val = self.expr_ev.EvalExpr(
1439 pat_expr, case_arm.left)
1440
1441 if val_ops.ExactlyEqual(expr_val, to_match,
1442 case_arm.left):
1443 status = self._ExecuteList(case_arm.action)
1444 done = True
1445 break
1446
1447 elif case(pat_e.Eggex):
1448 eggex = cast(Eggex, case_arm.pattern)
1449 eggex_val = self.expr_ev.EvalEggex(eggex)
1450
1451 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1452 status = self._ExecuteList(case_arm.action)
1453 done = True
1454 break
1455
1456 elif case(pat_e.Else):
1457 status = self._ExecuteList(case_arm.action)
1458 done = True
1459 break
1460
1461 else:
1462 raise AssertionError()
1463
1464 if done: # first match wins
1465 break
1466
1467 return status
1468
1469 def _DoTimeBlock(self, node):
1470 # type: (command.TimeBlock) -> int
1471 # TODO:
1472 # - When do we need RUSAGE_CHILDREN?
1473 # - Respect TIMEFORMAT environment variable.
1474 # "If this variable is not set, Bash acts as if it had the value"
1475 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1476 # "A trailing newline is added when the format string is displayed."
1477
1478 s_real, s_user, s_sys = pyos.Time()
1479 status = self._Execute(node.pipeline)
1480 e_real, e_user, e_sys = pyos.Time()
1481 # note: mycpp doesn't support %.3f
1482 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1483
1484 return status
1485
1486 def _DoRedirect(self, node, cmd_st):
1487 # type: (command.Redirect, CommandStatus) -> int
1488
1489 status = 0
1490 redirects = [] # type: List[RedirValue]
1491
1492 try:
1493 for redir in node.redirects:
1494 redirects.append(self._EvalRedirect(redir))
1495 except error.RedirectEval as e:
1496 self.errfmt.PrettyPrintError(e)
1497 redirects = None
1498 except error.FailGlob as e: # e.g. echo hi > foo-*
1499 if not e.HasLocation():
1500 e.location = self.mem.GetFallbackLocation()
1501 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1502 redirects = None
1503
1504 if redirects is None:
1505 # Error evaluating redirect words
1506 status = 1
1507
1508 # Translation fix: redirect I/O errors may happen in a C++
1509 # destructor ~vm::ctx_Redirect, which means they must be signaled
1510 # by out params, not exceptions.
1511 io_errors = [] # type: List[error.IOError_OSError]
1512
1513 # If we evaluated redirects, apply/push them
1514 if status == 0:
1515 self.shell_ex.PushRedirects(redirects, io_errors)
1516 if len(io_errors):
1517 # core/process.py prints cryptic errors, so we repeat them
1518 # here. e.g. Bad File Descriptor
1519 self.errfmt.PrintMessage(
1520 'I/O error applying redirect: %s' %
1521 pyutil.strerror(io_errors[0]),
1522 self.mem.GetFallbackLocation())
1523 status = 1
1524
1525 # If we applied redirects successfully, run the command_t, and pop
1526 # them.
1527 if status == 0:
1528 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1529 status = self._Execute(node.child)
1530 if len(io_errors):
1531 # It would be better to point to the right redirect
1532 # operator, but we don't track it specifically
1533 e_die("Fatal error popping redirect: %s" %
1534 pyutil.strerror(io_errors[0]))
1535
1536 return status
1537
1538 def _LeafTick(self):
1539 # type: () -> None
1540 """Do periodic work while executing shell.
1541
1542 We may run traps, check for Ctrl-C, or garbage collect.
1543 """
1544 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1545 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1546 # and maybe throw an exception.
1547 self.RunPendingTraps()
1548 if self.signal_safe.PollUntrappedSigInt():
1549 raise KeyboardInterrupt()
1550
1551 # TODO: Does this mess up control flow analysis? If so, we can move it
1552 # back to the top of _Execute(), so there are fewer conditionals
1553 # involved. This function gets called in SOME branches of
1554 # self._Dispatch().
1555
1556 # Manual GC point before every statement
1557 mylib.MaybeCollect()
1558
1559 def _Dispatch(self, node, cmd_st):
1560 # type: (command_t, CommandStatus) -> int
1561 """Switch on the command_t variants and execute them."""
1562
1563 # If we call RunCommandSub in a recursive call to the executor, this will
1564 # be set true (if strict_errexit is false). But it only lasts for one
1565 # command.
1566 probe('cmd_eval', '_Dispatch', node.tag())
1567 self.check_command_sub_status = False
1568
1569 UP_node = node
1570 with tagswitch(node) as case:
1571 if case(command_e.Simple): # LEAF command
1572 node = cast(command.Simple, UP_node)
1573
1574 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1575 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1576 # TODO: blame_tok should always be set.
1577 if node.blame_tok is not None:
1578 self.mem.SetTokenForLine(node.blame_tok)
1579
1580 self._MaybeRunDebugTrap()
1581 cmd_st.check_errexit = True
1582 status = self._DoSimple(node, cmd_st)
1583 self._LeafTick()
1584
1585 elif case(command_e.ShAssignment): # LEAF command
1586 node = cast(command.ShAssignment, UP_node)
1587
1588 self.mem.SetTokenForLine(node.pairs[0].left)
1589 self._MaybeRunDebugTrap()
1590
1591 # Only unqualified assignment a=b
1592 status = self._DoShAssignment(node, cmd_st)
1593 self._LeafTick()
1594
1595 elif case(command_e.Sentence): # NOT leaf, but put it up front
1596 node = cast(command.Sentence, UP_node)
1597
1598 # Don't check_errexit since this isn't a leaf command
1599 if node.terminator.id == Id.Op_Semi:
1600 status = self._Execute(node.child)
1601 else:
1602 status = self.shell_ex.RunBackgroundJob(node.child)
1603
1604 elif case(command_e.DBracket): # LEAF command
1605 node = cast(command.DBracket, UP_node)
1606
1607 self.mem.SetTokenForLine(node.left)
1608 self._MaybeRunDebugTrap()
1609
1610 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1611
1612 cmd_st.check_errexit = True
1613 cmd_st.show_code = True # this is a "leaf" for errors
1614 result = self.bool_ev.EvalB(node.expr)
1615 status = 0 if result else 1
1616 self._LeafTick()
1617
1618 elif case(command_e.DParen): # LEAF command
1619 node = cast(command.DParen, UP_node)
1620
1621 self.mem.SetTokenForLine(node.left)
1622 self._MaybeRunDebugTrap()
1623
1624 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1625
1626 cmd_st.check_errexit = True
1627 cmd_st.show_code = True # this is a "leaf" for errors
1628 i = self.arith_ev.EvalToBigInt(node.child)
1629 status = 1 if mops.Equal(i, mops.ZERO) else 0
1630 self._LeafTick()
1631
1632 elif case(command_e.ControlFlow): # LEAF command
1633 node = cast(command.ControlFlow, UP_node)
1634
1635 self.mem.SetTokenForLine(node.keyword)
1636 self._MaybeRunDebugTrap()
1637
1638 status = self._DoControlFlow(node)
1639 # Omit _LeafTick() since we likely raise an exception above
1640
1641 elif case(command_e.NoOp): # LEAF
1642 status = 0 # make it true
1643
1644 elif case(command_e.VarDecl): # YSH LEAF command
1645 node = cast(command.VarDecl, UP_node)
1646
1647 # Point to var name (bare assignment has no keyword)
1648 self.mem.SetTokenForLine(node.lhs[0].left)
1649 status = self._DoVarDecl(node)
1650 self._LeafTick()
1651
1652 elif case(command_e.Mutation): # YSH LEAF command
1653 node = cast(command.Mutation, UP_node)
1654
1655 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1656 self._DoMutation(node)
1657 status = 0 # if no exception is thrown, it succeeds
1658 self._LeafTick()
1659
1660 elif case(command_e.Expr): # YSH LEAF command
1661 node = cast(command.Expr, UP_node)
1662
1663 self.mem.SetTokenForLine(node.keyword)
1664 # YSH debug trap?
1665
1666 status = self._DoExpr(node)
1667 self._LeafTick()
1668
1669 elif case(command_e.Retval): # YSH LEAF command
1670 node = cast(command.Retval, UP_node)
1671
1672 self.mem.SetTokenForLine(node.keyword)
1673 # YSH debug trap? I think we don't want the debug trap in func
1674 # dialect, for speed?
1675
1676 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1677 self._LeafTick()
1678
1679 raise vm.ValueControlFlow(node.keyword, val)
1680
1681 #
1682 # More commands that involve recursive calls
1683 #
1684
1685 elif case(command_e.ExpandedAlias):
1686 node = cast(command.ExpandedAlias, UP_node)
1687 status = self._DoExpandedAlias(node)
1688
1689 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1690 # DoGroup has 'do' and 'done' spids for translation.
1691 elif case(command_e.CommandList):
1692 node = cast(command.CommandList, UP_node)
1693 status = self._ExecuteList(node.children)
1694
1695 elif case(command_e.DoGroup):
1696 node = cast(command.DoGroup, UP_node)
1697 status = self._ExecuteList(node.children)
1698
1699 elif case(command_e.BraceGroup):
1700 node = cast(BraceGroup, UP_node)
1701 status = self._ExecuteList(node.children)
1702
1703 elif case(command_e.AndOr):
1704 node = cast(command.AndOr, UP_node)
1705 status = self._DoAndOr(node, cmd_st)
1706
1707 elif case(command_e.If):
1708 node = cast(command.If, UP_node)
1709
1710 # No SetTokenForLine() because
1711 # - $LINENO can't appear directly in 'if'
1712 # - 'if' doesn't directly cause errors
1713 # It will be taken care of by command.Simple, condition, etc.
1714 status = self._DoIf(node)
1715
1716 elif case(command_e.Case):
1717 node = cast(command.Case, UP_node)
1718
1719 # Must set location for 'case $LINENO'
1720 self.mem.SetTokenForLine(node.case_kw)
1721 self._MaybeRunDebugTrap()
1722 status = self._DoCase(node)
1723
1724 elif case(command_e.WhileUntil):
1725 node = cast(command.WhileUntil, UP_node)
1726
1727 self.mem.SetTokenForLine(node.keyword)
1728 status = self._DoWhileUntil(node)
1729
1730 elif case(command_e.ForEach):
1731 node = cast(command.ForEach, UP_node)
1732
1733 self.mem.SetTokenForLine(node.keyword)
1734 status = self._DoForEach(node)
1735
1736 elif case(command_e.ForExpr):
1737 node = cast(command.ForExpr, UP_node)
1738
1739 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1740 status = self._DoForExpr(node)
1741
1742 elif case(command_e.Redirect):
1743 node = cast(command.Redirect, UP_node)
1744
1745 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1746 # dash/ash
1747 cmd_st.check_errexit = True
1748 status = self._DoRedirect(node, cmd_st)
1749
1750 elif case(command_e.Pipeline):
1751 node = cast(command.Pipeline, UP_node)
1752 status = self._DoPipeline(node, cmd_st)
1753
1754 elif case(command_e.Subshell):
1755 node = cast(command.Subshell, UP_node)
1756
1757 # This is a leaf from the parent process POV
1758 cmd_st.check_errexit = True
1759
1760 if node.is_last_cmd:
1761 # If the subshell is the last command in the process, just
1762 # run it in this process. See _MarkLastCommands().
1763 status = self._Execute(node.child)
1764 else:
1765 status = self.shell_ex.RunSubshell(node.child)
1766
1767 elif case(command_e.ShFunction):
1768 node = cast(command.ShFunction, UP_node)
1769 self._DoShFunction(node)
1770 status = 0
1771
1772 elif case(command_e.Proc):
1773 node = cast(Proc, UP_node)
1774 self._DoProc(node)
1775 status = 0
1776
1777 elif case(command_e.Func):
1778 node = cast(Func, UP_node)
1779
1780 # Needed for error, when the func is an existing variable name
1781 self.mem.SetTokenForLine(node.name)
1782
1783 self._DoFunc(node)
1784 status = 0
1785
1786 elif case(command_e.TimeBlock):
1787 node = cast(command.TimeBlock, UP_node)
1788 status = self._DoTimeBlock(node)
1789
1790 else:
1791 raise NotImplementedError(node.tag())
1792
1793 # Return to caller. Note the only case that didn't set it was Pipeline,
1794 # which set cmd_st.pipe_status.
1795 return status
1796
1797 def RunPendingTraps(self):
1798 # type: () -> None
1799
1800 trap_nodes = self.trap_state.GetPendingTraps()
1801 if trap_nodes is not None:
1802 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1803 True):
1804 for trap_node in trap_nodes:
1805 with state.ctx_Registers(self.mem):
1806 # TODO: show trap kind in trace
1807 with dev.ctx_Tracer(self.tracer, 'trap', None):
1808 # Note: exit status is lost
1809 self._Execute(trap_node)
1810
1811 def RunPendingTrapsAndCatch(self):
1812 # type: () -> None
1813 """
1814 Like the above, but calls ExecuteAndCatch(), which may raise util.UserExit
1815 """
1816 trap_nodes = self.trap_state.GetPendingTraps()
1817 if trap_nodes is not None:
1818 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1819 True):
1820 for trap_node in trap_nodes:
1821 with state.ctx_Registers(self.mem):
1822 # TODO: show trap kind in trace
1823 with dev.ctx_Tracer(self.tracer, 'trap', None):
1824 # Note: exit status is lost
1825 try:
1826 self.ExecuteAndCatch(trap_node, 0)
1827 except util.UserExit:
1828 # If user calls 'exit', stop running traps, but
1829 # we still run the EXIT trap later.
1830 break
1831
1832 def _Execute(self, node):
1833 # type: (command_t) -> int
1834 """Call _Dispatch(), and perform the errexit check."""
1835
1836 # Optimization: These 2 records have rarely-used lists, so we don't pass
1837 # alloc_lists=True. We create them on demand.
1838 cmd_st = CommandStatus.CreateNull()
1839 if len(self.status_array_pool):
1840 # Optimized to avoid allocs
1841 process_sub_st = self.status_array_pool.pop()
1842 else:
1843 process_sub_st = StatusArray.CreateNull()
1844
1845 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1846 try:
1847 status = self._Dispatch(node, cmd_st)
1848 except error.FailGlob as e:
1849 if not e.HasLocation(): # Last resort!
1850 e.location = self.mem.GetFallbackLocation()
1851 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1852 status = 1 # another redirect word eval error
1853 cmd_st.check_errexit = True # failglob + errexit
1854
1855 # Now we've waited for process subs
1856
1857 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1858 # @_pipeline_status
1859 pipe_status = cmd_st.pipe_status
1860 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1861 # makes it annoying to check both _process_sub_status and
1862 # _pipeline_status
1863
1864 errexit_loc = loc.Missing # type: loc_t
1865 if pipe_status is not None:
1866 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1867 # for a REAL pipeline (but not singleton pipelines)
1868 assert status == -1, (
1869 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1870 status)
1871
1872 self.mem.SetPipeStatus(pipe_status)
1873
1874 if self.exec_opts.pipefail():
1875 # The status is that of the last command that is non-zero.
1876 status = 0
1877 for i, st in enumerate(pipe_status):
1878 if st != 0:
1879 status = st
1880 errexit_loc = cmd_st.pipe_locs[i]
1881 else:
1882 # The status is that of last command, period.
1883 status = pipe_status[-1]
1884
1885 if cmd_st.pipe_negated:
1886 status = 1 if status == 0 else 0
1887
1888 # Compute status from _process_sub_status
1889 if process_sub_st.codes is None:
1890 # Optimized to avoid allocs
1891 self.status_array_pool.append(process_sub_st)
1892 else:
1893 codes = process_sub_st.codes
1894 self.mem.SetProcessSubStatus(codes)
1895 if status == 0 and self.exec_opts.process_sub_fail():
1896 # Choose the LAST non-zero status, consistent with pipefail above.
1897 for i, st in enumerate(codes):
1898 if st != 0:
1899 status = st
1900 errexit_loc = process_sub_st.locs[i]
1901
1902 self.mem.SetLastStatus(status)
1903
1904 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1905 # However, any bash construct can appear in a pipeline. So it's easier
1906 # just to put it at the end, instead of after every node.
1907 #
1908 # Possible exceptions:
1909 # - function def (however this always exits 0 anyway)
1910 # - assignment - its result should be the result of the RHS?
1911 # - e.g. arith sub, command sub? I don't want arith sub.
1912 # - ControlFlow: always raises, it has no status.
1913 if cmd_st.check_errexit:
1914 #log('cmd_st %s', cmd_st)
1915 self._CheckStatus(status, cmd_st, node, errexit_loc)
1916
1917 return status
1918
1919 def _ExecuteList(self, children):
1920 # type: (List[command_t]) -> int
1921 status = 0 # for empty list
1922 for child in children:
1923 # last status wins
1924 status = self._Execute(child)
1925 return status
1926
1927 def LastStatus(self):
1928 # type: () -> int
1929 """For main_loop.py to determine the exit code of the shell itself."""
1930 return self.mem.LastStatus()
1931
1932 def _MarkLastCommands(self, node):
1933 # type: (command_t) -> None
1934
1935 if 0:
1936 log('optimizing')
1937 node.PrettyPrint(sys.stderr)
1938 log('')
1939
1940 UP_node = node
1941 with tagswitch(node) as case:
1942 if case(command_e.Simple):
1943 node = cast(command.Simple, UP_node)
1944 node.is_last_cmd = True
1945 if 0:
1946 log('Simple optimized')
1947
1948 elif case(command_e.Subshell):
1949 node = cast(command.Subshell, UP_node)
1950 # Mark ourselves as the last
1951 node.is_last_cmd = True
1952
1953 # Also mark 'date' as the last one
1954 # echo 1; (echo 2; date)
1955 self._MarkLastCommands(node.child)
1956
1957 elif case(command_e.Pipeline):
1958 node = cast(command.Pipeline, UP_node)
1959 # Bug fix: if we change the status, we can't exec the last
1960 # element!
1961 if node.negated is None and not self.exec_opts.pipefail():
1962 self._MarkLastCommands(node.children[-1])
1963
1964 elif case(command_e.Sentence):
1965 node = cast(command.Sentence, UP_node)
1966 self._MarkLastCommands(node.child)
1967
1968 elif case(command_e.Redirect):
1969 node = cast(command.Sentence, UP_node)
1970 # Don't need to restore the redirect in any of these cases:
1971
1972 # bin/osh -c 'echo hi 2>stderr'
1973 # bin/osh -c '{ echo hi; date; } 2>stderr'
1974 # echo hi 2>stderr | wc -l
1975
1976 self._MarkLastCommands(node.child)
1977
1978 elif case(command_e.CommandList):
1979 # Subshells often have a CommandList child
1980 node = cast(command.CommandList, UP_node)
1981 self._MarkLastCommands(node.children[-1])
1982
1983 elif case(command_e.BraceGroup):
1984 # TODO: What about redirects?
1985 node = cast(BraceGroup, UP_node)
1986 self._MarkLastCommands(node.children[-1])
1987
1988 def _RemoveSubshells(self, node):
1989 # type: (command_t) -> command_t
1990 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
1991
1992 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
1993 be correct otherwise.
1994 """
1995 UP_node = node
1996 with tagswitch(node) as case:
1997 if case(command_e.Subshell):
1998 node = cast(command.Subshell, UP_node)
1999 # Optimize ( ( date ) ) etc.
2000 return self._RemoveSubshells(node.child)
2001 return node
2002
2003 def ExecuteAndCatch(self, node, cmd_flags):
2004 # type: (command_t, int) -> Tuple[bool, bool]
2005 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
2006
2007 Args:
2008 node: LST subtree
2009 optimize: Whether to exec the last process rather than fork/exec
2010
2011 Returns:
2012 TODO: use enum 'why' instead of the 2 booleans
2013
2014 Used by
2015 - main_loop.py.
2016 - SubProgramThunk for pipelines, subshell, command sub, process sub
2017 - TODO: Signals besides EXIT trap
2018
2019 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
2020 finally_exit boolean. We use a different algorithm.
2021 """
2022 if cmd_flags & OptimizeSubshells:
2023 node = self._RemoveSubshells(node)
2024
2025 if cmd_flags & MarkLastCommands:
2026 # Mark the last command in each process, so we may avoid forks
2027 self._MarkLastCommands(node)
2028
2029 if 0:
2030 log('after opt:')
2031 node.PrettyPrint()
2032 log('')
2033
2034 is_return = False
2035 is_fatal = False
2036 is_errexit = False
2037
2038 err = None # type: error.FatalRuntime
2039 status = -1 # uninitialized
2040
2041 try:
2042 options = [] # type: List[int]
2043 if cmd_flags & NoDebugTrap:
2044 options.append(option_i._no_debug_trap)
2045 if cmd_flags & NoErrTrap:
2046 options.append(option_i._no_err_trap)
2047 with state.ctx_Option(self.mutable_opts, options, True):
2048 status = self._Execute(node)
2049 except vm.IntControlFlow as e:
2050 if cmd_flags & RaiseControlFlow:
2051 raise # 'eval break' and 'source return.sh', etc.
2052 else:
2053 # Return at top level is OK, unlike in bash.
2054 if e.IsReturn():
2055 is_return = True
2056 status = e.StatusCode()
2057 else:
2058 # TODO: This error message is invalid. Can also happen in eval.
2059 # We need a flag.
2060
2061 # Invalid control flow
2062 self.errfmt.Print_(
2063 "Loop and control flow can't be in different processes",
2064 blame_loc=e.token)
2065 is_fatal = True
2066 # All shells exit 0 here. It could be hidden behind
2067 # strict_control_flow if the incompatibility causes problems.
2068 status = 1
2069 except error.Parse as e:
2070 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
2071 raise
2072 except error.ErrExit as e:
2073 err = e
2074 is_errexit = True
2075 except error.FatalRuntime as e:
2076 err = e
2077
2078 if err:
2079 status = err.ExitStatus()
2080
2081 is_fatal = True
2082 # Do this before unwinding stack
2083 self.dumper.MaybeRecord(self, err)
2084
2085 if not err.HasLocation(): # Last resort!
2086 #log('Missing location')
2087 err.location = self.mem.GetFallbackLocation()
2088 #log('%s', err.location)
2089
2090 if is_errexit:
2091 if self.exec_opts.verbose_errexit():
2092 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2093 posix.getpid())
2094 else:
2095 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2096
2097 assert status >= 0, 'Should have been initialized'
2098
2099 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2100 # created a crash dump. So we get 2 or more of them.
2101 self.dumper.MaybeDump(status)
2102
2103 self.mem.SetLastStatus(status)
2104 return is_return, is_fatal
2105
2106 def EvalCommand(self, block):
2107 # type: (command_t) -> int
2108 """For builtins to evaluate command args.
2109
2110 Many exceptions are raised.
2111
2112 Examples:
2113
2114 cd /tmp (; ; mycmd)
2115
2116 And:
2117 eval (mycmd)
2118 call _io->eval(mycmd)
2119
2120 (Should those be more like eval 'mystring'?)
2121 """
2122 return self._Execute(block) # can raise FatalRuntimeError, etc.
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, self_val=None):
2216 # type: (value.Proc, cmd_value.Argv, value_t) -> 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, self_val=self_val)
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