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

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