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

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