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

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