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

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