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

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