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

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