OILS / osh / cmd_eval.py View on Github | oils.pub

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