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

2288 lines, 1394 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
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 is_other_special = False # TODO: There are other special builtins too!
865 if cmd_val.tag() == cmd_value_e.Assign or is_other_special:
866 # Special builtins have their temp env persisted.
867 self._EvalTempEnv(node.more_env, 0)
868 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
869 else:
870 with state.ctx_Temp(self.mem):
871 self._EvalTempEnv(node.more_env, state.SetExport)
872 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
873 else:
874 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
875
876 probe('cmd_eval', '_DoSimple_exit', status)
877 return status
878
879 def _DoExpandedAlias(self, node):
880 # type: (command.ExpandedAlias) -> int
881 # Expanded aliases need redirects and env bindings from the calling
882 # context, as well as redirects in the expansion!
883
884 # TODO: SetTokenForLine to OUTSIDE? Don't bother with stuff inside
885 # expansion, since aliases are discouraged.
886
887 if len(node.more_env):
888 with state.ctx_Temp(self.mem):
889 self._EvalTempEnv(node.more_env, state.SetExport)
890 return self._Execute(node.child)
891 else:
892 return self._Execute(node.child)
893
894 def _DoPipeline(self, node, cmd_st):
895 # type: (command.Pipeline, CommandStatus) -> int
896 cmd_st.check_errexit = True
897 for op in node.ops:
898 if op.id != Id.Op_Pipe:
899 e_die("|& isn't supported", op)
900
901 # Remove $_ before pipeline. This matches bash, and is important in
902 # pipelines than assignments because pipelines are non-deterministic.
903 self.mem.SetLastArgument('')
904
905 # Set status to INVALID value, because we MIGHT set cmd_st.pipe_status,
906 # which _Execute() boils down into a status for us.
907 status = -1
908
909 if node.negated is not None:
910 self._StrictErrExit(node)
911 with state.ctx_ErrExit(self.mutable_opts, False, node.negated):
912 # '! grep' is parsed as a pipeline, according to the grammar, but
913 # there's no pipe() call.
914 if len(node.children) == 1:
915 tmp_status = self._Execute(node.children[0])
916 status = 1 if tmp_status == 0 else 0
917 else:
918 self.shell_ex.RunPipeline(node, cmd_st)
919 cmd_st.pipe_negated = True
920
921 # errexit is disabled for !.
922 cmd_st.check_errexit = False
923 else:
924 self.shell_ex.RunPipeline(node, cmd_st)
925
926 return status
927
928 def _DoShAssignment(self, node, cmd_st):
929 # type: (command.ShAssignment, CommandStatus) -> int
930 assert len(node.pairs) >= 1, node
931
932 # x=y is 'neutered' inside 'proc'
933 which_scopes = self.mem.ScopesForWriting()
934
935 for pair in node.pairs:
936 if pair.op == assign_op_e.PlusEqual:
937 assert pair.rhs, pair.rhs # I don't think a+= is valid?
938 rhs = self.word_ev.EvalRhsWord(pair.rhs)
939
940 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
941 # do not respect set -u
942 old_val = sh_expr_eval.OldValue(lval, self.mem, None)
943
944 val = PlusEquals(old_val, rhs)
945
946 else: # plain assignment
947 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
948
949 # RHS can be a string or array.
950 if pair.rhs:
951 val = self.word_ev.EvalRhsWord(pair.rhs)
952 assert isinstance(val, value_t), val
953
954 else: # e.g. 'readonly x' or 'local x'
955 val = None
956
957 # NOTE: In bash and mksh, declare -a myarray makes an empty cell
958 # with Undef value, but the 'array' attribute.
959
960 flags = 0 # for tracing
961 self.mem.SetValue(lval, val, which_scopes, flags=flags)
962 self.tracer.OnShAssignment(lval, pair.op, val, flags, which_scopes)
963
964 # PATCH to be compatible with existing shells: If the assignment had a
965 # command sub like:
966 #
967 # s=$(echo one; false)
968 #
969 # then its status will be in mem.last_status, and we can check it here.
970 # If there was NOT a command sub in the assignment, then we don't want to
971 # check it.
972
973 # Only do this if there was a command sub? How? Look at node?
974 # Set a flag in mem? self.mem.last_status or
975 if self.check_command_sub_status:
976 last_status = self.mem.LastStatus()
977 self._CheckStatus(last_status, cmd_st, node, loc.Missing)
978 return last_status # A global assignment shouldn't clear $?.
979 else:
980 return 0
981
982 def _DoExpr(self, node):
983 # type: (command.Expr) -> int
984
985 # call f(x) or = f(x)
986 val = self.expr_ev.EvalExpr(node.e, loc.Missing)
987
988 if node.keyword.id == Id.Lit_Equals: # = f(x)
989 io_errors = [] # type: List[error.IOError_OSError]
990 with vm.ctx_FlushStdout(io_errors):
991 try:
992 ui.PrettyPrintValue('', val, mylib.Stdout())
993 except (IOError, OSError) as e:
994 self.errfmt.PrintMessage(
995 'I/O error during = keyword: %s' % pyutil.strerror(e),
996 node.keyword)
997 return 1
998
999 if len(io_errors): # e.g. disk full, ulimit
1000 self.errfmt.PrintMessage(
1001 'I/O error during = keyword: %s' %
1002 pyutil.strerror(io_errors[0]), node.keyword)
1003 return 1
1004
1005 return 0
1006
1007 def _DoControlFlow(self, node):
1008 # type: (command.ControlFlow) -> int
1009 keyword = node.keyword
1010
1011 if node.arg_word: # Evaluate the argument
1012 str_val = self.word_ev.EvalWordToString(node.arg_word)
1013
1014 # Quirk: We need 'return $empty' to be valid for libtool. This is
1015 # another meaning of strict_control_flow, which also has to do with
1016 # break/continue at top level. It has the side effect of making
1017 # 'return ""' valid, which shells other than zsh fail on.
1018 if (len(str_val.s) == 0 and
1019 not self.exec_opts.strict_control_flow()):
1020 arg = 0
1021 else:
1022 try:
1023 arg = int(str_val.s) # all control flow takes an integer
1024 except ValueError:
1025 # Either a bad argument, or integer overflow
1026 e_die(
1027 '%r expected a small integer, got %r' %
1028 (lexer.TokenVal(keyword), str_val.s),
1029 loc.Word(node.arg_word))
1030
1031 # C++ int() does range checking, but Python doesn't. So let's
1032 # simulate it here for spec tests.
1033 # TODO: could be mylib.ToMachineInt()? Problem: 'int' in C/C++
1034 # could be more than 4 bytes. We are testing INT_MAX and
1035 # INT_MIN in gc_builtins.cc - those could be hard-coded.
1036 if mylib.PYTHON:
1037 max_int = (1 << 31) - 1
1038 min_int = -(1 << 31)
1039 if not (min_int <= arg <= max_int):
1040 e_die(
1041 '%r expected a small integer, got %r' %
1042 (lexer.TokenVal(keyword), str_val.s),
1043 loc.Word(node.arg_word))
1044 else:
1045 if keyword.id in (Id.ControlFlow_Exit, Id.ControlFlow_Return):
1046 arg = self.mem.LastStatus()
1047 else:
1048 arg = 1 # break or continue 1 level by default
1049
1050 self.tracer.OnControlFlow(consts.ControlFlowName(keyword.id), arg)
1051
1052 # NOTE: A top-level 'return' is OK, unlike in bash. If you can return
1053 # from a sourced script, it makes sense to return from a main script.
1054 if (keyword.id in (Id.ControlFlow_Break, Id.ControlFlow_Continue) and
1055 self.loop_level == 0):
1056 msg = 'Invalid control flow at top level'
1057 if self.exec_opts.strict_control_flow():
1058 e_die(msg, keyword)
1059 else:
1060 # Only print warnings, never fatal.
1061 # Bash oddly only exits 1 for 'return', but no other shell does.
1062 self.errfmt.PrefixPrint(msg, 'warning: ', keyword)
1063 return 0
1064
1065 if keyword.id == Id.ControlFlow_Exit:
1066 # handled differently than other control flow
1067 raise util.UserExit(arg)
1068 else:
1069 raise vm.IntControlFlow(keyword, arg)
1070
1071 def _DoAndOr(self, node, cmd_st):
1072 # type: (command.AndOr, CommandStatus) -> int
1073 # NOTE: && and || have EQUAL precedence in command mode. See case #13
1074 # in dbracket.test.sh.
1075
1076 left = node.children[0]
1077
1078 # Suppress failure for every child except the last one.
1079 self._StrictErrExit(left)
1080 with state.ctx_ErrExit(self.mutable_opts, False, node.ops[0]):
1081 status = self._Execute(left)
1082
1083 i = 1
1084 n = len(node.children)
1085 while i < n:
1086 #log('i %d status %d', i, status)
1087 child = node.children[i]
1088 op = node.ops[i - 1]
1089 op_id = op.id
1090
1091 #log('child %s op_id %s', child, op_id)
1092
1093 if op_id == Id.Op_DPipe and status == 0:
1094 i += 1
1095 continue # short circuit
1096
1097 elif op_id == Id.Op_DAmp and status != 0:
1098 i += 1
1099 continue # short circuit
1100
1101 if i == n - 1: # errexit handled differently for last child
1102 status = self._Execute(child)
1103 else:
1104 # blame the right && or ||
1105 self._StrictErrExit(child)
1106 with state.ctx_ErrExit(self.mutable_opts, False, op):
1107 status = self._Execute(child)
1108
1109 i += 1
1110
1111 return status
1112
1113 def _DoWhileUntil(self, node):
1114 # type: (command.WhileUntil) -> int
1115 status = 0
1116 with ctx_LoopLevel(self):
1117 while True:
1118 try:
1119 # blame while/until spid
1120 b = self._EvalCondition(node.cond, node.keyword)
1121 if node.keyword.id == Id.KW_Until:
1122 b = not b
1123 if not b:
1124 break
1125 status = self._Execute(node.body) # last one wins
1126
1127 except vm.IntControlFlow as e:
1128 status = 0
1129 action = e.HandleLoop()
1130 if action == flow_e.Break:
1131 break
1132 elif action == flow_e.Raise:
1133 raise
1134
1135 return status
1136
1137 def _DoForEach(self, node):
1138 # type: (command.ForEach) -> int
1139
1140 # for the 2 kinds of shell loop
1141 iter_list = None # type: List[str]
1142
1143 # for YSH loop
1144 iter_expr = None # type: expr_t
1145 expr_blame = None # type: loc_t
1146
1147 iterable = node.iterable
1148 UP_iterable = iterable
1149
1150 with tagswitch(node.iterable) as case:
1151 if case(for_iter_e.Args):
1152 iter_list = self.mem.GetArgv()
1153
1154 elif case(for_iter_e.Words):
1155 iterable = cast(for_iter.Words, UP_iterable)
1156 words = braces.BraceExpandWords(iterable.words)
1157 iter_list = self.word_ev.EvalWordSequence(words)
1158
1159 elif case(for_iter_e.YshExpr):
1160 iterable = cast(for_iter.YshExpr, UP_iterable)
1161 iter_expr = iterable.e
1162 expr_blame = iterable.blame
1163
1164 else:
1165 raise AssertionError()
1166
1167 n = len(node.iter_names)
1168 assert n > 0
1169
1170 i_name = None # type: Optional[LeftName]
1171 # required
1172 name1 = None # type: LeftName
1173 name2 = None # type: Optional[LeftName]
1174
1175 it2 = None # type: val_ops.Iterator
1176 if iter_expr: # for_expr.YshExpr
1177 val = self.expr_ev.EvalExpr(iter_expr, expr_blame)
1178
1179 UP_val = val
1180 with tagswitch(val) as case:
1181 if case(value_e.List):
1182 val = cast(value.List, UP_val)
1183 it2 = val_ops.ListIterator(val)
1184
1185 if n == 1:
1186 name1 = location.LName(node.iter_names[0])
1187 elif n == 2:
1188 i_name = location.LName(node.iter_names[0])
1189 name1 = location.LName(node.iter_names[1])
1190 else:
1191 # This is similar to a parse error
1192 e_die_status(
1193 2,
1194 'List iteration expects at most 2 loop variables',
1195 node.keyword)
1196
1197 elif case(value_e.Dict):
1198 val = cast(value.Dict, UP_val)
1199 it2 = val_ops.DictIterator(val)
1200
1201 if n == 1:
1202 name1 = location.LName(node.iter_names[0])
1203 elif n == 2:
1204 name1 = location.LName(node.iter_names[0])
1205 name2 = location.LName(node.iter_names[1])
1206 elif n == 3:
1207 i_name = location.LName(node.iter_names[0])
1208 name1 = location.LName(node.iter_names[1])
1209 name2 = location.LName(node.iter_names[2])
1210 else:
1211 raise AssertionError()
1212
1213 elif case(value_e.Range):
1214 val = cast(value.Range, UP_val)
1215 it2 = val_ops.RangeIterator(val)
1216
1217 if n == 1:
1218 name1 = location.LName(node.iter_names[0])
1219 elif n == 2:
1220 i_name = location.LName(node.iter_names[0])
1221 name1 = location.LName(node.iter_names[1])
1222 else:
1223 e_die_status(
1224 2,
1225 'Range iteration expects at most 2 loop variables',
1226 node.keyword)
1227
1228 elif case(value_e.Stdin):
1229 # TODO: This could changed to magic iterator?
1230 it2 = val_ops.StdinIterator(expr_blame)
1231 if n == 1:
1232 name1 = location.LName(node.iter_names[0])
1233 elif n == 2:
1234 i_name = location.LName(node.iter_names[0])
1235 name1 = location.LName(node.iter_names[1])
1236 else:
1237 e_die_status(
1238 2,
1239 'Stdin iteration expects at most 2 loop variables',
1240 node.keyword)
1241 else:
1242 raise error.TypeErr(
1243 val, 'for loop expected List, Dict, Range, or Stdin',
1244 node.keyword)
1245
1246 else:
1247 assert iter_list is not None, iter_list
1248
1249 #log('iter list %s', iter_list)
1250 it2 = val_ops.ArrayIter(iter_list)
1251
1252 if n == 1:
1253 name1 = location.LName(node.iter_names[0])
1254 elif n == 2:
1255 i_name = location.LName(node.iter_names[0])
1256 name1 = location.LName(node.iter_names[1])
1257 else:
1258 # This is similar to a parse error
1259 e_die_status(
1260 2, 'Argv iteration expects at most 2 loop variables',
1261 node.keyword)
1262
1263 status = 0 # in case we loop zero times
1264 with ctx_LoopLevel(self):
1265 while True:
1266 with state.ctx_LoopFrame(self.mem, name1.name):
1267 first = it2.FirstValue()
1268 #log('first %s', first)
1269 if first is None: # for StdinIterator
1270 #log('first is None')
1271 break
1272
1273 if first.tag() == value_e.Interrupted:
1274 self.RunPendingTraps()
1275 #log('Done running traps')
1276 continue
1277
1278 self.mem.SetLocalName(name1, first)
1279 if name2:
1280 self.mem.SetLocalName(name2, it2.SecondValue())
1281 if i_name:
1282 self.mem.SetLocalName(i_name, num.ToBig(it2.Index()))
1283
1284 # increment index before handling continue, etc.
1285 it2.Next()
1286
1287 try:
1288 status = self._Execute(node.body) # last one wins
1289 except vm.IntControlFlow as e:
1290 status = 0
1291 action = e.HandleLoop()
1292 if action == flow_e.Break:
1293 break
1294 elif action == flow_e.Raise:
1295 raise
1296
1297 return status
1298
1299 def _DoForExpr(self, node):
1300 # type: (command.ForExpr) -> int
1301
1302 status = 0
1303
1304 init = node.init
1305 for_cond = node.cond
1306 body = node.body
1307 update = node.update
1308
1309 self.arith_ev.Eval(init)
1310 with ctx_LoopLevel(self):
1311 while True:
1312 # We only accept integers as conditions
1313 cond_int = self.arith_ev.EvalToBigInt(for_cond)
1314 if mops.Equal(cond_int, mops.ZERO): # false
1315 break
1316
1317 try:
1318 status = self._Execute(body)
1319 except vm.IntControlFlow as e:
1320 status = 0
1321 action = e.HandleLoop()
1322 if action == flow_e.Break:
1323 break
1324 elif action == flow_e.Raise:
1325 raise
1326
1327 self.arith_ev.Eval(update)
1328
1329 return status
1330
1331 def _DoShFunction(self, node):
1332 # type: (command.ShFunction) -> None
1333
1334 # Note: shell functions can read vars from the file they're defined in
1335 # But they don't appear in the module itself -- rather it is __sh_funcs__
1336 # Though we could consider disallowing them though on 'import'.
1337 sh_func = value.Proc(node.name, node.name_tok, proc_sig.Open,
1338 node.body, None, True, self.mem.GlobalFrame())
1339 self.procs.DefineShellFunc(node.name, sh_func)
1340
1341 def _DoProc(self, node):
1342 # type: (Proc) -> None
1343 proc_name = lexer.TokenVal(node.name)
1344
1345 if node.sig.tag() == proc_sig_e.Closed:
1346 sig = cast(proc_sig.Closed, node.sig)
1347 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1348 else:
1349 proc_defaults = None
1350
1351 # no dynamic scope
1352 proc = value.Proc(proc_name, node.name, node.sig, node.body,
1353 proc_defaults, False, self.mem.GlobalFrame())
1354 self.procs.DefineProc(proc_name, proc)
1355
1356 def _DoFunc(self, node):
1357 # type: (Func) -> None
1358 name = lexer.TokenVal(node.name)
1359 lval = location.LName(name)
1360
1361 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1362 self.expr_ev, node)
1363 func_val = value.Func(name, node, pos_defaults, named_defaults,
1364 self.mem.GlobalFrame())
1365
1366 self.mem.SetNamed(lval, func_val, scope_e.LocalOnly)
1367
1368 def _DoIf(self, node):
1369 # type: (command.If) -> int
1370 status = -1
1371
1372 done = False
1373 for if_arm in node.arms:
1374 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1375 if b:
1376 status = self._ExecuteList(if_arm.action)
1377 done = True
1378 break
1379
1380 if not done and node.else_action is not None:
1381 status = self._ExecuteList(node.else_action)
1382
1383 assert status != -1, 'Should have been initialized'
1384 return status
1385
1386 def _DoCase(self, node):
1387 # type: (command.Case) -> int
1388
1389 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1390 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1391
1392 status = 0 # If there are no arms, it should be zero?
1393
1394 done = False # Should we try the next arm?
1395
1396 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1397 ignore_next_cond = False
1398
1399 for case_arm in node.arms:
1400 with tagswitch(case_arm.pattern) as case:
1401 if case(pat_e.Words):
1402 if to_match.tag() != value_e.Str:
1403 continue # A non-string `to_match` will never match a pat.Words
1404 to_match_str = cast(value.Str, to_match)
1405
1406 pat_words = cast(pat.Words, case_arm.pattern)
1407
1408 this_arm_matches = False
1409 if ignore_next_cond: # Special handling for ;&
1410 this_arm_matches = True
1411 ignore_next_cond = False
1412 else:
1413 for pat_word in pat_words.words:
1414 word_val = self.word_ev.EvalWordToString(
1415 pat_word, word_eval.QUOTE_FNMATCH)
1416
1417 if libc.fnmatch(word_val.s, to_match_str.s,
1418 fnmatch_flags):
1419 this_arm_matches = True
1420 break # Stop at first pattern
1421
1422 if this_arm_matches:
1423 status = self._ExecuteList(case_arm.action)
1424 done = True
1425
1426 # ;& and ;;& only apply to shell-style case
1427 if case_arm.right:
1428 id_ = case_arm.right.id
1429 if id_ == Id.Op_SemiAmp:
1430 # very weird semantic
1431 ignore_next_cond = True
1432 done = False
1433 elif id_ == Id.Op_DSemiAmp:
1434 # Keep going until next pattern
1435 done = False
1436
1437 elif case(pat_e.YshExprs):
1438 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1439
1440 for pat_expr in pat_exprs.exprs:
1441 expr_val = self.expr_ev.EvalExpr(
1442 pat_expr, case_arm.left)
1443
1444 if val_ops.ExactlyEqual(expr_val, to_match,
1445 case_arm.left):
1446 status = self._ExecuteList(case_arm.action)
1447 done = True
1448 break
1449
1450 elif case(pat_e.Eggex):
1451 eggex = cast(Eggex, case_arm.pattern)
1452 eggex_val = self.expr_ev.EvalEggex(eggex)
1453
1454 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1455 status = self._ExecuteList(case_arm.action)
1456 done = True
1457 break
1458
1459 elif case(pat_e.Else):
1460 status = self._ExecuteList(case_arm.action)
1461 done = True
1462 break
1463
1464 else:
1465 raise AssertionError()
1466
1467 if done: # first match wins
1468 break
1469
1470 return status
1471
1472 def _DoTimeBlock(self, node):
1473 # type: (command.TimeBlock) -> int
1474 # TODO:
1475 # - When do we need RUSAGE_CHILDREN?
1476 # - Respect TIMEFORMAT environment variable.
1477 # "If this variable is not set, Bash acts as if it had the value"
1478 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1479 # "A trailing newline is added when the format string is displayed."
1480
1481 s_real, s_user, s_sys = pyos.Time()
1482 status = self._Execute(node.pipeline)
1483 e_real, e_user, e_sys = pyos.Time()
1484 # note: mycpp doesn't support %.3f
1485 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1486
1487 return status
1488
1489 def _DoRedirect(self, node, cmd_st):
1490 # type: (command.Redirect, CommandStatus) -> int
1491
1492 status = 0
1493 redirects = [] # type: List[RedirValue]
1494
1495 try:
1496 for redir in node.redirects:
1497 redirects.append(self._EvalRedirect(redir))
1498 except error.RedirectEval as e:
1499 self.errfmt.PrettyPrintError(e)
1500 redirects = None
1501 except error.FailGlob as e: # e.g. echo hi > foo-*
1502 if not e.HasLocation():
1503 e.location = self.mem.GetFallbackLocation()
1504 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1505 redirects = None
1506
1507 if redirects is None:
1508 # Error evaluating redirect words
1509 status = 1
1510
1511 # Translation fix: redirect I/O errors may happen in a C++
1512 # destructor ~vm::ctx_Redirect, which means they must be signaled
1513 # by out params, not exceptions.
1514 io_errors = [] # type: List[error.IOError_OSError]
1515
1516 # If we evaluated redirects, apply/push them
1517 if status == 0:
1518 self.shell_ex.PushRedirects(redirects, io_errors)
1519 if len(io_errors):
1520 # core/process.py prints cryptic errors, so we repeat them
1521 # here. e.g. Bad File Descriptor
1522 self.errfmt.PrintMessage(
1523 'I/O error applying redirect: %s' %
1524 pyutil.strerror(io_errors[0]),
1525 self.mem.GetFallbackLocation())
1526 status = 1
1527
1528 # If we applied redirects successfully, run the command_t, and pop
1529 # them.
1530 if status == 0:
1531 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1532 status = self._Execute(node.child)
1533 if len(io_errors):
1534 # It would be better to point to the right redirect
1535 # operator, but we don't track it specifically
1536 e_die("Fatal error popping redirect: %s" %
1537 pyutil.strerror(io_errors[0]))
1538
1539 return status
1540
1541 def _LeafTick(self):
1542 # type: () -> None
1543 """Do periodic work while executing shell.
1544
1545 We may run traps, check for Ctrl-C, or garbage collect.
1546 """
1547 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1548 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1549 # and maybe throw an exception.
1550 self.RunPendingTraps()
1551 if self.signal_safe.PollUntrappedSigInt():
1552 raise KeyboardInterrupt()
1553
1554 # TODO: Does this mess up control flow analysis? If so, we can move it
1555 # back to the top of _Execute(), so there are fewer conditionals
1556 # involved. This function gets called in SOME branches of
1557 # self._Dispatch().
1558
1559 # Manual GC point before every statement
1560 mylib.MaybeCollect()
1561
1562 def _Dispatch(self, node, cmd_st):
1563 # type: (command_t, CommandStatus) -> int
1564 """Switch on the command_t variants and execute them."""
1565
1566 # If we call RunCommandSub in a recursive call to the executor, this will
1567 # be set true (if strict_errexit is false). But it only lasts for one
1568 # command.
1569 probe('cmd_eval', '_Dispatch', node.tag())
1570 self.check_command_sub_status = False
1571
1572 UP_node = node
1573 with tagswitch(node) as case:
1574 if case(command_e.Simple): # LEAF command
1575 node = cast(command.Simple, UP_node)
1576
1577 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1578 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1579 # TODO: blame_tok should always be set.
1580 if node.blame_tok is not None:
1581 self.mem.SetTokenForLine(node.blame_tok)
1582
1583 self._MaybeRunDebugTrap()
1584 cmd_st.check_errexit = True
1585 status = self._DoSimple(node, cmd_st)
1586 self._LeafTick()
1587
1588 elif case(command_e.ShAssignment): # LEAF command
1589 node = cast(command.ShAssignment, UP_node)
1590
1591 self.mem.SetTokenForLine(node.pairs[0].left)
1592 self._MaybeRunDebugTrap()
1593
1594 # Only unqualified assignment a=b
1595 status = self._DoShAssignment(node, cmd_st)
1596 self._LeafTick()
1597
1598 elif case(command_e.Sentence): # NOT leaf, but put it up front
1599 node = cast(command.Sentence, UP_node)
1600
1601 # Don't check_errexit since this isn't a leaf command
1602 if node.terminator.id == Id.Op_Semi:
1603 status = self._Execute(node.child)
1604 else:
1605 status = self.shell_ex.RunBackgroundJob(node.child)
1606
1607 elif case(command_e.DBracket): # LEAF command
1608 node = cast(command.DBracket, UP_node)
1609
1610 self.mem.SetTokenForLine(node.left)
1611 self._MaybeRunDebugTrap()
1612
1613 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1614
1615 cmd_st.check_errexit = True
1616 cmd_st.show_code = True # this is a "leaf" for errors
1617 result = self.bool_ev.EvalB(node.expr)
1618 status = 0 if result else 1
1619 self._LeafTick()
1620
1621 elif case(command_e.DParen): # LEAF command
1622 node = cast(command.DParen, UP_node)
1623
1624 self.mem.SetTokenForLine(node.left)
1625 self._MaybeRunDebugTrap()
1626
1627 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1628
1629 cmd_st.check_errexit = True
1630 cmd_st.show_code = True # this is a "leaf" for errors
1631 i = self.arith_ev.EvalToBigInt(node.child)
1632 status = 1 if mops.Equal(i, mops.ZERO) else 0
1633 self._LeafTick()
1634
1635 elif case(command_e.ControlFlow): # LEAF command
1636 node = cast(command.ControlFlow, UP_node)
1637
1638 self.mem.SetTokenForLine(node.keyword)
1639 self._MaybeRunDebugTrap()
1640
1641 status = self._DoControlFlow(node)
1642 # Omit _LeafTick() since we likely raise an exception above
1643
1644 elif case(command_e.NoOp): # LEAF
1645 status = 0 # make it true
1646
1647 elif case(command_e.VarDecl): # YSH LEAF command
1648 node = cast(command.VarDecl, UP_node)
1649
1650 # Point to var name (bare assignment has no keyword)
1651 self.mem.SetTokenForLine(node.lhs[0].left)
1652 status = self._DoVarDecl(node)
1653 self._LeafTick()
1654
1655 elif case(command_e.Mutation): # YSH LEAF command
1656 node = cast(command.Mutation, UP_node)
1657
1658 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1659 self._DoMutation(node)
1660 status = 0 # if no exception is thrown, it succeeds
1661 self._LeafTick()
1662
1663 elif case(command_e.Expr): # YSH LEAF command
1664 node = cast(command.Expr, UP_node)
1665
1666 self.mem.SetTokenForLine(node.keyword)
1667 # YSH debug trap?
1668
1669 status = self._DoExpr(node)
1670 self._LeafTick()
1671
1672 elif case(command_e.Retval): # YSH LEAF command
1673 node = cast(command.Retval, UP_node)
1674
1675 self.mem.SetTokenForLine(node.keyword)
1676 # YSH debug trap? I think we don't want the debug trap in func
1677 # dialect, for speed?
1678
1679 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1680 self._LeafTick()
1681
1682 raise vm.ValueControlFlow(node.keyword, val)
1683
1684 #
1685 # More commands that involve recursive calls
1686 #
1687
1688 elif case(command_e.ExpandedAlias):
1689 node = cast(command.ExpandedAlias, UP_node)
1690 status = self._DoExpandedAlias(node)
1691
1692 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1693 # DoGroup has 'do' and 'done' spids for translation.
1694 elif case(command_e.CommandList):
1695 node = cast(command.CommandList, UP_node)
1696 status = self._ExecuteList(node.children)
1697
1698 elif case(command_e.DoGroup):
1699 node = cast(command.DoGroup, UP_node)
1700 status = self._ExecuteList(node.children)
1701
1702 elif case(command_e.BraceGroup):
1703 node = cast(BraceGroup, UP_node)
1704 status = self._ExecuteList(node.children)
1705
1706 elif case(command_e.AndOr):
1707 node = cast(command.AndOr, UP_node)
1708 status = self._DoAndOr(node, cmd_st)
1709
1710 elif case(command_e.If):
1711 node = cast(command.If, UP_node)
1712
1713 # No SetTokenForLine() because
1714 # - $LINENO can't appear directly in 'if'
1715 # - 'if' doesn't directly cause errors
1716 # It will be taken care of by command.Simple, condition, etc.
1717 status = self._DoIf(node)
1718
1719 elif case(command_e.Case):
1720 node = cast(command.Case, UP_node)
1721
1722 # Must set location for 'case $LINENO'
1723 self.mem.SetTokenForLine(node.case_kw)
1724 self._MaybeRunDebugTrap()
1725 status = self._DoCase(node)
1726
1727 elif case(command_e.WhileUntil):
1728 node = cast(command.WhileUntil, UP_node)
1729
1730 self.mem.SetTokenForLine(node.keyword)
1731 status = self._DoWhileUntil(node)
1732
1733 elif case(command_e.ForEach):
1734 node = cast(command.ForEach, UP_node)
1735
1736 self.mem.SetTokenForLine(node.keyword)
1737 status = self._DoForEach(node)
1738
1739 elif case(command_e.ForExpr):
1740 node = cast(command.ForExpr, UP_node)
1741
1742 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1743 status = self._DoForExpr(node)
1744
1745 elif case(command_e.Redirect):
1746 node = cast(command.Redirect, UP_node)
1747
1748 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1749 # dash/ash
1750 cmd_st.check_errexit = True
1751 status = self._DoRedirect(node, cmd_st)
1752
1753 elif case(command_e.Pipeline):
1754 node = cast(command.Pipeline, UP_node)
1755 status = self._DoPipeline(node, cmd_st)
1756
1757 elif case(command_e.Subshell):
1758 node = cast(command.Subshell, UP_node)
1759
1760 # This is a leaf from the parent process POV
1761 cmd_st.check_errexit = True
1762
1763 if node.is_last_cmd:
1764 # If the subshell is the last command in the process, just
1765 # run it in this process. See _MarkLastCommands().
1766 status = self._Execute(node.child)
1767 else:
1768 status = self.shell_ex.RunSubshell(node.child)
1769
1770 elif case(command_e.ShFunction):
1771 node = cast(command.ShFunction, UP_node)
1772 self._DoShFunction(node)
1773 status = 0
1774
1775 elif case(command_e.Proc):
1776 node = cast(Proc, UP_node)
1777 self._DoProc(node)
1778 status = 0
1779
1780 elif case(command_e.Func):
1781 node = cast(Func, UP_node)
1782
1783 # Needed for error, when the func is an existing variable name
1784 self.mem.SetTokenForLine(node.name)
1785
1786 self._DoFunc(node)
1787 status = 0
1788
1789 elif case(command_e.TimeBlock):
1790 node = cast(command.TimeBlock, UP_node)
1791 status = self._DoTimeBlock(node)
1792
1793 else:
1794 raise NotImplementedError(node.tag())
1795
1796 # Return to caller. Note the only case that didn't set it was Pipeline,
1797 # which set cmd_st.pipe_status.
1798 return status
1799
1800 def RunPendingTraps(self):
1801 # type: () -> None
1802
1803 trap_nodes = self.trap_state.GetPendingTraps()
1804 if trap_nodes is not None:
1805 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1806 True):
1807 for trap_node in trap_nodes:
1808 with state.ctx_Registers(self.mem):
1809 # TODO: show trap kind in trace
1810 with dev.ctx_Tracer(self.tracer, 'trap', None):
1811 # Note: exit status is lost
1812 self._Execute(trap_node)
1813
1814 def RunPendingTrapsAndCatch(self):
1815 # type: () -> None
1816 """
1817 Like the above, but calls ExecuteAndCatch(), which may raise util.UserExit
1818 """
1819 trap_nodes = self.trap_state.GetPendingTraps()
1820 if trap_nodes is not None:
1821 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1822 True):
1823 for trap_node in trap_nodes:
1824 with state.ctx_Registers(self.mem):
1825 # TODO: show trap kind in trace
1826 with dev.ctx_Tracer(self.tracer, 'trap', None):
1827 # Note: exit status is lost
1828 try:
1829 self.ExecuteAndCatch(trap_node, 0)
1830 except util.UserExit:
1831 # If user calls 'exit', stop running traps, but
1832 # we still run the EXIT trap later.
1833 break
1834
1835 def _Execute(self, node):
1836 # type: (command_t) -> int
1837 """Call _Dispatch(), and perform the errexit check."""
1838
1839 # Optimization: These 2 records have rarely-used lists, so we don't pass
1840 # alloc_lists=True. We create them on demand.
1841 cmd_st = CommandStatus.CreateNull()
1842 if len(self.status_array_pool):
1843 # Optimized to avoid allocs
1844 process_sub_st = self.status_array_pool.pop()
1845 else:
1846 process_sub_st = StatusArray.CreateNull()
1847
1848 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1849 try:
1850 status = self._Dispatch(node, cmd_st)
1851 except error.FailGlob as e:
1852 if not e.HasLocation(): # Last resort!
1853 e.location = self.mem.GetFallbackLocation()
1854 self.errfmt.PrettyPrintError(e, prefix='failglob: ')
1855 status = 1 # another redirect word eval error
1856 cmd_st.check_errexit = True # failglob + errexit
1857
1858 # Now we've waited for process subs
1859
1860 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1861 # @_pipeline_status
1862 pipe_status = cmd_st.pipe_status
1863 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1864 # makes it annoying to check both _process_sub_status and
1865 # _pipeline_status
1866
1867 errexit_loc = loc.Missing # type: loc_t
1868 if pipe_status is not None:
1869 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1870 # for a REAL pipeline (but not singleton pipelines)
1871 assert status == -1, (
1872 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1873 status)
1874
1875 self.mem.SetPipeStatus(pipe_status)
1876
1877 if self.exec_opts.pipefail():
1878 # The status is that of the last command that is non-zero.
1879 status = 0
1880 for i, st in enumerate(pipe_status):
1881 if st != 0:
1882 status = st
1883 errexit_loc = cmd_st.pipe_locs[i]
1884 else:
1885 # The status is that of last command, period.
1886 status = pipe_status[-1]
1887
1888 if cmd_st.pipe_negated:
1889 status = 1 if status == 0 else 0
1890
1891 # Compute status from _process_sub_status
1892 if process_sub_st.codes is None:
1893 # Optimized to avoid allocs
1894 self.status_array_pool.append(process_sub_st)
1895 else:
1896 codes = process_sub_st.codes
1897 self.mem.SetProcessSubStatus(codes)
1898 if status == 0 and self.exec_opts.process_sub_fail():
1899 # Choose the LAST non-zero status, consistent with pipefail above.
1900 for i, st in enumerate(codes):
1901 if st != 0:
1902 status = st
1903 errexit_loc = process_sub_st.locs[i]
1904
1905 self.mem.SetLastStatus(status)
1906
1907 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1908 # However, any bash construct can appear in a pipeline. So it's easier
1909 # just to put it at the end, instead of after every node.
1910 #
1911 # Possible exceptions:
1912 # - function def (however this always exits 0 anyway)
1913 # - assignment - its result should be the result of the RHS?
1914 # - e.g. arith sub, command sub? I don't want arith sub.
1915 # - ControlFlow: always raises, it has no status.
1916 if cmd_st.check_errexit:
1917 #log('cmd_st %s', cmd_st)
1918 self._CheckStatus(status, cmd_st, node, errexit_loc)
1919
1920 return status
1921
1922 def _ExecuteList(self, children):
1923 # type: (List[command_t]) -> int
1924 status = 0 # for empty list
1925 for child in children:
1926 # last status wins
1927 status = self._Execute(child)
1928 return status
1929
1930 def LastStatus(self):
1931 # type: () -> int
1932 """For main_loop.py to determine the exit code of the shell itself."""
1933 return self.mem.LastStatus()
1934
1935 def _MarkLastCommands(self, node):
1936 # type: (command_t) -> None
1937
1938 if 0:
1939 log('optimizing')
1940 node.PrettyPrint(sys.stderr)
1941 log('')
1942
1943 UP_node = node
1944 with tagswitch(node) as case:
1945 if case(command_e.Simple):
1946 node = cast(command.Simple, UP_node)
1947 node.is_last_cmd = True
1948 if 0:
1949 log('Simple optimized')
1950
1951 elif case(command_e.Subshell):
1952 node = cast(command.Subshell, UP_node)
1953 # Mark ourselves as the last
1954 node.is_last_cmd = True
1955
1956 # Also mark 'date' as the last one
1957 # echo 1; (echo 2; date)
1958 self._MarkLastCommands(node.child)
1959
1960 elif case(command_e.Pipeline):
1961 node = cast(command.Pipeline, UP_node)
1962 # Bug fix: if we change the status, we can't exec the last
1963 # element!
1964 if node.negated is None and not self.exec_opts.pipefail():
1965 self._MarkLastCommands(node.children[-1])
1966
1967 elif case(command_e.Sentence):
1968 node = cast(command.Sentence, UP_node)
1969 self._MarkLastCommands(node.child)
1970
1971 elif case(command_e.Redirect):
1972 node = cast(command.Sentence, UP_node)
1973 # Don't need to restore the redirect in any of these cases:
1974
1975 # bin/osh -c 'echo hi 2>stderr'
1976 # bin/osh -c '{ echo hi; date; } 2>stderr'
1977 # echo hi 2>stderr | wc -l
1978
1979 self._MarkLastCommands(node.child)
1980
1981 elif case(command_e.CommandList):
1982 # Subshells often have a CommandList child
1983 node = cast(command.CommandList, UP_node)
1984 self._MarkLastCommands(node.children[-1])
1985
1986 elif case(command_e.BraceGroup):
1987 # TODO: What about redirects?
1988 node = cast(BraceGroup, UP_node)
1989 self._MarkLastCommands(node.children[-1])
1990
1991 def _RemoveSubshells(self, node):
1992 # type: (command_t) -> command_t
1993 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
1994
1995 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
1996 be correct otherwise.
1997 """
1998 UP_node = node
1999 with tagswitch(node) as case:
2000 if case(command_e.Subshell):
2001 node = cast(command.Subshell, UP_node)
2002 # Optimize ( ( date ) ) etc.
2003 return self._RemoveSubshells(node.child)
2004 return node
2005
2006 def ExecuteAndCatch(self, node, cmd_flags):
2007 # type: (command_t, int) -> Tuple[bool, bool]
2008 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
2009
2010 Args:
2011 node: LST subtree
2012 optimize: Whether to exec the last process rather than fork/exec
2013
2014 Returns:
2015 TODO: use enum 'why' instead of the 2 booleans
2016
2017 Used by
2018 - main_loop.py.
2019 - SubProgramThunk for pipelines, subshell, command sub, process sub
2020 - TODO: Signals besides EXIT trap
2021
2022 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
2023 finally_exit boolean. We use a different algorithm.
2024 """
2025 if cmd_flags & OptimizeSubshells:
2026 node = self._RemoveSubshells(node)
2027
2028 if cmd_flags & MarkLastCommands:
2029 # Mark the last command in each process, so we may avoid forks
2030 self._MarkLastCommands(node)
2031
2032 if 0:
2033 log('after opt:')
2034 node.PrettyPrint()
2035 log('')
2036
2037 is_return = False
2038 is_fatal = False
2039 is_errexit = False
2040
2041 err = None # type: error.FatalRuntime
2042 status = -1 # uninitialized
2043
2044 try:
2045 options = [] # type: List[int]
2046 if cmd_flags & NoDebugTrap:
2047 options.append(option_i._no_debug_trap)
2048 if cmd_flags & NoErrTrap:
2049 options.append(option_i._no_err_trap)
2050 with state.ctx_Option(self.mutable_opts, options, True):
2051 status = self._Execute(node)
2052 except vm.IntControlFlow as e:
2053 if cmd_flags & RaiseControlFlow:
2054 raise # 'eval break' and 'source return.sh', etc.
2055 else:
2056 # Return at top level is OK, unlike in bash.
2057 if e.IsReturn():
2058 is_return = True
2059 status = e.StatusCode()
2060 else:
2061 # TODO: This error message is invalid. Can also happen in eval.
2062 # We need a flag.
2063
2064 # Invalid control flow
2065 self.errfmt.Print_(
2066 "Loop and control flow can't be in different processes",
2067 blame_loc=e.token)
2068 is_fatal = True
2069 # All shells exit 0 here. It could be hidden behind
2070 # strict_control_flow if the incompatibility causes problems.
2071 status = 1
2072 except error.Parse as e:
2073 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
2074 raise
2075 except error.ErrExit as e:
2076 err = e
2077 is_errexit = True
2078 except error.FatalRuntime as e:
2079 err = e
2080
2081 if err:
2082 status = err.ExitStatus()
2083
2084 is_fatal = True
2085 # Do this before unwinding stack
2086 self.dumper.MaybeRecord(self, err)
2087
2088 if not err.HasLocation(): # Last resort!
2089 #log('Missing location')
2090 err.location = self.mem.GetFallbackLocation()
2091 #log('%s', err.location)
2092
2093 if is_errexit:
2094 if self.exec_opts.verbose_errexit():
2095 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2096 posix.getpid())
2097 else:
2098 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2099
2100 assert status >= 0, 'Should have been initialized'
2101
2102 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2103 # created a crash dump. So we get 2 or more of them.
2104 self.dumper.MaybeDump(status)
2105
2106 self.mem.SetLastStatus(status)
2107 return is_return, is_fatal
2108
2109 def EvalCommandFrag(self, frag):
2110 # type: (command_t) -> int
2111 """For builtins to evaluate command args.
2112
2113 Many exceptions are raised.
2114
2115 Examples:
2116
2117 cd /tmp (; ; mycmd)
2118
2119 And:
2120 eval (mycmd)
2121 call _io->eval(mycmd)
2122
2123 (Should those be more like eval 'mystring'?)
2124 """
2125 return self._Execute(frag) # can raise FatalRuntimeError, etc.
2126
2127 if 0:
2128
2129 def EvalCommandClosure(self, cmd):
2130 # type: (value.Command) -> int
2131 frag = typed_args.GetCommandFrag(cmd)
2132 with state.ctx_EnclosedFrame(self.mem, cmd.captured_frame,
2133 cmd.module_frame):
2134 return self.EvalCommandFrag(frag)
2135
2136 def RunTrapsOnExit(self, mut_status):
2137 # type: (IntParamBox) -> None
2138 """If an EXIT trap handler exists, run it.
2139
2140 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2141 all bash/dash/mksh seem to agree on it. See cases in
2142 builtin-trap.test.sh.
2143
2144 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2145 of this awkward interface. But that's true in Python and not C!
2146
2147 Could use i & (n-1) == i & 255 because we have a power of 2.
2148 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2149 """
2150 # This does not raise, even on 'exit', etc.
2151 self.RunPendingTrapsAndCatch()
2152
2153 node = self.trap_state.GetHook('EXIT') # type: command_t
2154 if node:
2155 # NOTE: Don't set option_i._running_trap, because that's for
2156 # RunPendingTraps() in the MAIN LOOP
2157 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2158 try:
2159 is_return, is_fatal = self.ExecuteAndCatch(node, 0)
2160 except util.UserExit as e: # explicit exit
2161 mut_status.i = e.status
2162 return
2163 if is_return: # explicit 'return' in the trap handler!
2164 mut_status.i = self.LastStatus()
2165
2166 def _MaybeRunDebugTrap(self):
2167 # type: () -> None
2168 """Run user-specified DEBUG code before certain commands."""
2169 node = self.trap_state.GetHook('DEBUG') # type: command_t
2170 if node is None:
2171 return
2172
2173 # Fix lastpipe / job control / DEBUG trap interaction
2174 if self.exec_opts._no_debug_trap():
2175 return
2176
2177 # Don't run recursively run traps, etc.
2178 if not self.mem.ShouldRunDebugTrap():
2179 return
2180
2181 # NOTE: Don't set option_i._running_trap, because that's for
2182 # RunPendingTraps() in the MAIN LOOP
2183
2184 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2185 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2186 # for SetTokenForLine $LINENO
2187 with state.ctx_DebugTrap(self.mem):
2188 # Don't catch util.UserExit, etc.
2189 self._Execute(node)
2190
2191 def _MaybeRunErrTrap(self):
2192 # type: () -> None
2193 """
2194 Run user-specified ERR code after checking the status of certain
2195 commands (pipelines)
2196 """
2197 node = self.trap_state.GetHook('ERR') # type: command_t
2198 if node is None:
2199 return
2200
2201 # ERR trap is only run for a whole pipeline, not its parts
2202 if self.exec_opts._no_err_trap():
2203 return
2204
2205 # Prevent infinite recursion
2206 if self.mem.running_err_trap:
2207 return
2208
2209 # "disabled errexit" rule
2210 if self.mutable_opts.ErrExitIsDisabled():
2211 return
2212
2213 # bash rule - affected by set -o errtrace
2214 if not self.exec_opts.errtrace() and self.mem.InsideFunction():
2215 return
2216
2217 # NOTE: Don't set option_i._running_trap, because that's for
2218 # RunPendingTraps() in the MAIN LOOP
2219
2220 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2221 # In bash, the PIPESTATUS register leaks. See spec/builtin-trap-err.
2222 # So unlike other traps, we don't isolate registers.
2223 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2224 with state.ctx_ErrTrap(self.mem):
2225 self._Execute(node)
2226
2227 def RunProc(self, proc, cmd_val):
2228 # type: (value.Proc, cmd_value.Argv) -> int
2229 """Run procs aka "shell functions".
2230
2231 For SimpleCommand and registered completion hooks.
2232 """
2233 sig = proc.sig
2234 if sig.tag() == proc_sig_e.Closed:
2235 # We're binding named params. User should use @rest. No 'shift'.
2236 proc_argv = [] # type: List[str]
2237 else:
2238 proc_argv = cmd_val.argv[1:]
2239
2240 # Hm this sets "$@". TODO: Set ARGV only
2241 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2242 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2243
2244 # Redirects still valid for functions.
2245 # Here doc causes a pipe and Process(SubProgramThunk).
2246 try:
2247 status = self._Execute(proc.body)
2248 except vm.IntControlFlow as e:
2249 if e.IsReturn():
2250 status = e.StatusCode()
2251 else:
2252 # break/continue used in the wrong place.
2253 e_die(
2254 'Unexpected %r (in proc call)' %
2255 lexer.TokenVal(e.token), e.token)
2256 except error.FatalRuntime as e:
2257 # Dump the stack before unwinding it
2258 self.dumper.MaybeRecord(self, e)
2259 raise
2260
2261 return status
2262
2263 def RunFuncForCompletion(self, proc, argv):
2264 # type: (value.Proc, List[str]) -> int
2265 """
2266 Args:
2267 argv: $1 $2 $3 ... not including $0
2268 """
2269 cmd_val = MakeBuiltinArgv(argv)
2270
2271 # TODO: Change this to run YSH procs and funcs too
2272 try:
2273 status = self.RunProc(proc, cmd_val)
2274 except error.FatalRuntime as e:
2275 self.errfmt.PrettyPrintError(e)
2276 status = e.ExitStatus()
2277 except vm.IntControlFlow as e:
2278 # shouldn't be able to exit the shell from a completion hook!
2279 # TODO: Avoid overwriting the prompt!
2280 self.errfmt.Print_('Attempted to exit from completion hook.',
2281 blame_loc=e.token)
2282
2283 status = 1
2284 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2285 return status
2286
2287
2288# vim: sw=4