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

2342 lines, 1431 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, name1.name):
1308 first = it2.FirstValue()
1309 #log('first %s', first)
1310 if first is None: # for StdinIterator
1311 #log('first is None')
1312 break
1313
1314 if first.tag() == value_e.Interrupted:
1315 self.RunPendingTraps()
1316 #log('Done running traps')
1317 continue
1318
1319 self.mem.SetLocalName(name1, first)
1320 if name2:
1321 self.mem.SetLocalName(name2, it2.SecondValue())
1322 if i_name:
1323 self.mem.SetLocalName(i_name, num.ToBig(it2.Index()))
1324
1325 # increment index before handling continue, etc.
1326 it2.Next()
1327
1328 try:
1329 status = self._Execute(node.body) # last one wins
1330 except vm.IntControlFlow as e:
1331 status = 0
1332 action = e.HandleLoop()
1333 if action == flow_e.Break:
1334 break
1335 elif action == flow_e.Raise:
1336 raise
1337
1338 return status
1339
1340 def _DoForExpr(self, node):
1341 # type: (command.ForExpr) -> int
1342
1343 status = 0
1344
1345 init = node.init
1346 for_cond = node.cond
1347 body = node.body
1348 update = node.update
1349
1350 self.arith_ev.Eval(init)
1351 with ctx_LoopLevel(self):
1352 while True:
1353 # We only accept integers as conditions
1354 cond_int = self.arith_ev.EvalToBigInt(for_cond)
1355 if mops.Equal(cond_int, mops.ZERO): # false
1356 break
1357
1358 try:
1359 status = self._Execute(body)
1360 except vm.IntControlFlow as e:
1361 status = 0
1362 action = e.HandleLoop()
1363 if action == flow_e.Break:
1364 break
1365 elif action == flow_e.Raise:
1366 raise
1367
1368 self.arith_ev.Eval(update)
1369
1370 return status
1371
1372 def _DoShFunction(self, node):
1373 # type: (command.ShFunction) -> None
1374
1375 # Note: shell functions can read vars from the file they're defined in
1376 # But they don't appear in the module itself -- rather it is __sh_funcs__
1377 # Though we could consider disallowing them though on 'import'.
1378 sh_func = value.Proc(node.name, node.name_tok, proc_sig.Open,
1379 node.body, None, True, self.mem.GlobalFrame())
1380 self.procs.DefineShellFunc(node.name, sh_func)
1381
1382 def _DoProc(self, node):
1383 # type: (Proc) -> None
1384 proc_name = lexer.TokenVal(node.name)
1385
1386 if node.sig.tag() == proc_sig_e.Closed:
1387 sig = cast(proc_sig.Closed, node.sig)
1388 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1389 else:
1390 proc_defaults = None
1391
1392 # no dynamic scope
1393 proc = value.Proc(proc_name, node.name, node.sig, node.body,
1394 proc_defaults, False, self.mem.GlobalFrame())
1395 self.procs.DefineProc(proc_name, proc)
1396
1397 def _DoFunc(self, node):
1398 # type: (Func) -> None
1399 name = lexer.TokenVal(node.name)
1400 lval = location.LName(name)
1401
1402 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1403 self.expr_ev, node)
1404 func_val = value.Func(name, node, pos_defaults, named_defaults,
1405 self.mem.GlobalFrame())
1406
1407 self.mem.SetNamed(lval, func_val, scope_e.LocalOnly)
1408
1409 def _DoIf(self, node):
1410 # type: (command.If) -> int
1411 status = -1
1412
1413 done = False
1414 for if_arm in node.arms:
1415 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1416 if b:
1417 status = self._ExecuteList(if_arm.action)
1418 done = True
1419 break
1420
1421 if not done and node.else_action is not None:
1422 status = self._ExecuteList(node.else_action)
1423
1424 assert status != -1, 'Should have been initialized'
1425 return status
1426
1427 def _DoCase(self, node):
1428 # type: (command.Case) -> int
1429
1430 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1431 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1432
1433 status = 0 # If there are no arms, it should be zero?
1434
1435 done = False # Should we try the next arm?
1436
1437 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1438 ignore_next_cond = False
1439
1440 for case_arm in node.arms:
1441 with tagswitch(case_arm.pattern) as case:
1442 if case(pat_e.Words):
1443 if to_match.tag() != value_e.Str:
1444 continue # A non-string `to_match` will never match a pat.Words
1445 to_match_str = cast(value.Str, to_match)
1446
1447 pat_words = cast(pat.Words, case_arm.pattern)
1448
1449 this_arm_matches = False
1450 if ignore_next_cond: # Special handling for ;&
1451 this_arm_matches = True
1452 ignore_next_cond = False
1453 else:
1454 for pat_word in pat_words.words:
1455 word_val = self.word_ev.EvalWordToString(
1456 pat_word, word_eval.QUOTE_FNMATCH)
1457
1458 if libc.fnmatch(word_val.s, to_match_str.s,
1459 fnmatch_flags):
1460 this_arm_matches = True
1461 break # Stop at first pattern
1462
1463 if this_arm_matches:
1464 status = self._ExecuteList(case_arm.action)
1465 done = True
1466
1467 # ;& and ;;& only apply to shell-style case
1468 if case_arm.right:
1469 id_ = case_arm.right.id
1470 if id_ == Id.Op_SemiAmp:
1471 # very weird semantic
1472 ignore_next_cond = True
1473 done = False
1474 elif id_ == Id.Op_DSemiAmp:
1475 # Keep going until next pattern
1476 done = False
1477
1478 elif case(pat_e.YshExprs):
1479 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1480
1481 for pat_expr in pat_exprs.exprs:
1482 expr_val = self.expr_ev.EvalExpr(
1483 pat_expr, case_arm.left)
1484
1485 if val_ops.ExactlyEqual(expr_val, to_match,
1486 case_arm.left):
1487 status = self._ExecuteList(case_arm.action)
1488 done = True
1489 break
1490
1491 elif case(pat_e.Eggex):
1492 eggex = cast(Eggex, case_arm.pattern)
1493 eggex_val = self.expr_ev.EvalEggex(eggex)
1494
1495 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1496 status = self._ExecuteList(case_arm.action)
1497 done = True
1498 break
1499
1500 elif case(pat_e.Else):
1501 status = self._ExecuteList(case_arm.action)
1502 done = True
1503 break
1504
1505 else:
1506 raise AssertionError()
1507
1508 if done: # first match wins
1509 break
1510
1511 return status
1512
1513 def _DoTimeBlock(self, node):
1514 # type: (command.TimeBlock) -> int
1515 # TODO:
1516 # - When do we need RUSAGE_CHILDREN?
1517 # - Respect TIMEFORMAT environment variable.
1518 # "If this variable is not set, Bash acts as if it had the value"
1519 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1520 # "A trailing newline is added when the format string is displayed."
1521
1522 s_real, s_user, s_sys = pyos.Time()
1523 status = self._Execute(node.pipeline)
1524 e_real, e_user, e_sys = pyos.Time()
1525 # note: mycpp doesn't support %.3f
1526 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1527
1528 return status
1529
1530 def _DoRedirect(self, node, cmd_st):
1531 # type: (command.Redirect, CommandStatus) -> int
1532
1533 status = 0
1534 redirects = [] # type: List[RedirValue]
1535
1536 try:
1537 for redir in node.redirects:
1538 redirects.append(self._EvalRedirect(redir))
1539 except error.RedirectEval as e:
1540 self.errfmt.PrettyPrintError(e)
1541 redirects = None
1542 except error.WordFailure as e:
1543 # This happens e.g. with the following cases:
1544 #
1545 # $ echo hi > foo-* # with failglob (FailGlob)
1546 # $ echo > ${!undef} # (VarSubFailure)
1547 #
1548 if not e.HasLocation():
1549 e.location = self.mem.GetFallbackLocation()
1550 self.errfmt.PrettyPrintError(e)
1551 redirects = None
1552
1553 if redirects is None:
1554 # Error evaluating redirect words
1555 status = 1
1556
1557 # Translation fix: redirect I/O errors may happen in a C++
1558 # destructor ~vm::ctx_Redirect, which means they must be signaled
1559 # by out params, not exceptions.
1560 io_errors = [] # type: List[error.IOError_OSError]
1561
1562 # If we evaluated redirects, apply/push them
1563 if status == 0:
1564 self.shell_ex.PushRedirects(redirects, io_errors)
1565 if len(io_errors):
1566 # core/process.py prints cryptic errors, so we repeat them
1567 # here. e.g. Bad File Descriptor
1568 self.errfmt.PrintMessage(
1569 'I/O error applying redirect: %s' %
1570 pyutil.strerror(io_errors[0]),
1571 self.mem.GetFallbackLocation())
1572 status = 1
1573
1574 # If we applied redirects successfully, run the command_t, and pop
1575 # them.
1576 if status == 0:
1577 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1578 status = self._Execute(node.child)
1579 if len(io_errors):
1580 # It would be better to point to the right redirect
1581 # operator, but we don't track it specifically
1582 e_die("Fatal error popping redirect: %s" %
1583 pyutil.strerror(io_errors[0]))
1584
1585 return status
1586
1587 def _LeafTick(self):
1588 # type: () -> None
1589 """Do periodic work while executing shell.
1590
1591 We may run traps, check for Ctrl-C, or garbage collect.
1592 """
1593 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1594 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1595 # and maybe throw an exception.
1596 self.RunPendingTraps()
1597 if self.signal_safe.PollUntrappedSigInt():
1598 raise KeyboardInterrupt()
1599
1600 # TODO: Does this mess up control flow analysis? If so, we can move it
1601 # back to the top of _Execute(), so there are fewer conditionals
1602 # involved. This function gets called in SOME branches of
1603 # self._Dispatch().
1604
1605 # Manual GC point before every statement
1606 mylib.MaybeCollect()
1607
1608 def _Dispatch(self, node, cmd_st):
1609 # type: (command_t, CommandStatus) -> int
1610 """Switch on the command_t variants and execute them."""
1611
1612 # If we call RunCommandSub in a recursive call to the executor, this will
1613 # be set true (if strict_errexit is false). But it only lasts for one
1614 # command.
1615 probe('cmd_eval', '_Dispatch', node.tag())
1616 self.check_command_sub_status = False
1617
1618 UP_node = node
1619 with tagswitch(node) as case:
1620 if case(command_e.Simple): # LEAF command
1621 node = cast(command.Simple, UP_node)
1622
1623 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1624 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1625 # TODO: blame_tok should always be set.
1626 if node.blame_tok is not None:
1627 self.mem.SetTokenForLine(node.blame_tok)
1628
1629 self._MaybeRunDebugTrap()
1630 cmd_st.check_errexit = True
1631 status = self._DoSimple(node, cmd_st)
1632 self._LeafTick()
1633
1634 elif case(command_e.ShAssignment): # LEAF command
1635 node = cast(command.ShAssignment, UP_node)
1636
1637 self.mem.SetTokenForLine(node.pairs[0].left)
1638 self._MaybeRunDebugTrap()
1639
1640 # Only unqualified assignment a=b
1641 status = self._DoShAssignment(node, cmd_st)
1642 self._LeafTick()
1643
1644 elif case(command_e.Sentence): # NOT leaf, but put it up front
1645 node = cast(command.Sentence, UP_node)
1646
1647 # Don't check_errexit since this isn't a leaf command
1648 if node.terminator.id == Id.Op_Semi:
1649 status = self._Execute(node.child)
1650 else:
1651 status = self.shell_ex.RunBackgroundJob(node.child)
1652
1653 elif case(command_e.DBracket): # LEAF command
1654 node = cast(command.DBracket, UP_node)
1655
1656 self.mem.SetTokenForLine(node.left)
1657 self._MaybeRunDebugTrap()
1658
1659 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1660
1661 cmd_st.check_errexit = True
1662 cmd_st.show_code = True # this is a "leaf" for errors
1663 result = self.bool_ev.EvalB(node.expr)
1664 status = 0 if result else 1
1665 self._LeafTick()
1666
1667 elif case(command_e.DParen): # LEAF command
1668 node = cast(command.DParen, UP_node)
1669
1670 self.mem.SetTokenForLine(node.left)
1671 self._MaybeRunDebugTrap()
1672
1673 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1674
1675 cmd_st.check_errexit = True
1676 cmd_st.show_code = True # this is a "leaf" for errors
1677 i = self.arith_ev.EvalToBigInt(node.child)
1678 status = 1 if mops.Equal(i, mops.ZERO) else 0
1679 self._LeafTick()
1680
1681 elif case(command_e.ControlFlow): # LEAF command
1682 node = cast(command.ControlFlow, UP_node)
1683
1684 self.mem.SetTokenForLine(node.keyword)
1685 self._MaybeRunDebugTrap()
1686
1687 status = self._DoControlFlow(node)
1688 # Omit _LeafTick() since we likely raise an exception above
1689
1690 elif case(command_e.NoOp): # LEAF
1691 status = 0 # make it true
1692
1693 elif case(command_e.VarDecl): # YSH LEAF command
1694 node = cast(command.VarDecl, UP_node)
1695
1696 # Point to var name (bare assignment has no keyword)
1697 self.mem.SetTokenForLine(node.lhs[0].left)
1698 status = self._DoVarDecl(node)
1699 self._LeafTick()
1700
1701 elif case(command_e.Mutation): # YSH LEAF command
1702 node = cast(command.Mutation, UP_node)
1703
1704 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1705 self._DoMutation(node)
1706 status = 0 # if no exception is thrown, it succeeds
1707 self._LeafTick()
1708
1709 elif case(command_e.Expr): # YSH LEAF command
1710 node = cast(command.Expr, UP_node)
1711
1712 self.mem.SetTokenForLine(node.keyword)
1713 # YSH debug trap?
1714
1715 status = self._DoExpr(node)
1716 self._LeafTick()
1717
1718 elif case(command_e.Retval): # YSH LEAF command
1719 node = cast(command.Retval, UP_node)
1720
1721 self.mem.SetTokenForLine(node.keyword)
1722 # YSH debug trap? I think we don't want the debug trap in func
1723 # dialect, for speed?
1724
1725 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1726 self._LeafTick()
1727
1728 raise vm.ValueControlFlow(node.keyword, val)
1729
1730 #
1731 # More commands that involve recursive calls
1732 #
1733
1734 elif case(command_e.ExpandedAlias):
1735 node = cast(command.ExpandedAlias, UP_node)
1736 status = self._DoExpandedAlias(node)
1737
1738 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1739 # DoGroup has 'do' and 'done' spids for translation.
1740 elif case(command_e.CommandList):
1741 node = cast(command.CommandList, UP_node)
1742 status = self._ExecuteList(node.children)
1743
1744 elif case(command_e.DoGroup):
1745 node = cast(command.DoGroup, UP_node)
1746 status = self._ExecuteList(node.children)
1747
1748 elif case(command_e.BraceGroup):
1749 node = cast(BraceGroup, UP_node)
1750 status = self._ExecuteList(node.children)
1751
1752 elif case(command_e.AndOr):
1753 node = cast(command.AndOr, UP_node)
1754 status = self._DoAndOr(node, cmd_st)
1755
1756 elif case(command_e.If):
1757 node = cast(command.If, UP_node)
1758
1759 # Perf bug fix: loops might only execute 'if', but we still
1760 # need to GC
1761 self._LeafTick()
1762
1763 # No SetTokenForLine() because
1764 # - $LINENO can't appear directly in 'if'
1765 # - 'if' doesn't directly cause errors
1766 # It will be taken care of by command.Simple, condition, etc.
1767 status = self._DoIf(node)
1768
1769 elif case(command_e.Case):
1770 node = cast(command.Case, UP_node)
1771
1772 # Perf bug fix: loops might only execute 'case', but we still
1773 # need to GC
1774 self._LeafTick()
1775
1776 # Must set location for 'case $LINENO'
1777 self.mem.SetTokenForLine(node.case_kw)
1778 self._MaybeRunDebugTrap()
1779 status = self._DoCase(node)
1780
1781 elif case(command_e.WhileUntil):
1782 node = cast(command.WhileUntil, UP_node)
1783
1784 self.mem.SetTokenForLine(node.keyword)
1785 status = self._DoWhileUntil(node)
1786
1787 elif case(command_e.ForEach):
1788 node = cast(command.ForEach, UP_node)
1789
1790 self.mem.SetTokenForLine(node.keyword)
1791 status = self._DoForEach(node)
1792
1793 elif case(command_e.ForExpr):
1794 node = cast(command.ForExpr, UP_node)
1795
1796 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1797 status = self._DoForExpr(node)
1798
1799 elif case(command_e.Redirect):
1800 node = cast(command.Redirect, UP_node)
1801
1802 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1803 # dash/ash
1804 cmd_st.check_errexit = True
1805 status = self._DoRedirect(node, cmd_st)
1806
1807 elif case(command_e.Pipeline):
1808 node = cast(command.Pipeline, UP_node)
1809 status = self._DoPipeline(node, cmd_st)
1810
1811 elif case(command_e.Subshell):
1812 node = cast(command.Subshell, UP_node)
1813
1814 # This is a leaf from the parent process POV
1815 cmd_st.check_errexit = True
1816
1817 if node.is_last_cmd:
1818 # If the subshell is the last command in the process, just
1819 # run it in this process. See _MarkLastCommands().
1820 status = self._Execute(node.child)
1821 else:
1822 status = self.shell_ex.RunSubshell(node.child)
1823
1824 elif case(command_e.ShFunction):
1825 node = cast(command.ShFunction, UP_node)
1826 self._DoShFunction(node)
1827 status = 0
1828
1829 elif case(command_e.Proc):
1830 node = cast(Proc, UP_node)
1831 self._DoProc(node)
1832 status = 0
1833
1834 elif case(command_e.Func):
1835 node = cast(Func, UP_node)
1836
1837 # Needed for error, when the func is an existing variable name
1838 self.mem.SetTokenForLine(node.name)
1839
1840 self._DoFunc(node)
1841 status = 0
1842
1843 elif case(command_e.TimeBlock):
1844 node = cast(command.TimeBlock, UP_node)
1845 status = self._DoTimeBlock(node)
1846
1847 else:
1848 raise NotImplementedError(node.tag())
1849
1850 # Return to caller. Note the only case that didn't set it was Pipeline,
1851 # which set cmd_st.pipe_status.
1852 return status
1853
1854 def RunPendingTraps(self):
1855 # type: () -> None
1856
1857 trap_nodes = self.trap_state.GetPendingTraps()
1858 if trap_nodes is not None:
1859 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1860 True):
1861 for trap_node in trap_nodes:
1862 with state.ctx_Registers(self.mem):
1863 # TODO: show trap kind in trace
1864 with dev.ctx_Tracer(self.tracer, 'trap', None):
1865 # Note: exit status is lost
1866 self._Execute(trap_node)
1867
1868 def RunPendingTrapsAndCatch(self):
1869 # type: () -> None
1870 """
1871 Like the above, but calls ExecuteAndCatch(), which may raise util.UserExit
1872 """
1873 trap_nodes = self.trap_state.GetPendingTraps()
1874 if trap_nodes is not None:
1875 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1876 True):
1877 for trap_node in trap_nodes:
1878 with state.ctx_Registers(self.mem):
1879 # TODO: show trap kind in trace
1880 with dev.ctx_Tracer(self.tracer, 'trap', None):
1881 # Note: exit status is lost
1882 try:
1883 self.ExecuteAndCatch(trap_node, 0)
1884 except util.UserExit:
1885 # If user calls 'exit', stop running traps, but
1886 # we still run the EXIT trap later.
1887 break
1888
1889 def _Execute(self, node):
1890 # type: (command_t) -> int
1891 """Call _Dispatch(), and perform the errexit check."""
1892
1893 # Optimization: These 2 records have rarely-used lists, so we don't pass
1894 # alloc_lists=True. We create them on demand.
1895 cmd_st = CommandStatus.CreateNull()
1896 if len(self.status_array_pool):
1897 # Optimized to avoid allocs
1898 process_sub_st = self.status_array_pool.pop()
1899 else:
1900 process_sub_st = StatusArray.CreateNull()
1901
1902 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1903 try:
1904 status = self._Dispatch(node, cmd_st)
1905 except error.WordFailure as e: # e.g. echo hi > ${!undef}
1906 if not e.HasLocation(): # Last resort!
1907 e.location = self.mem.GetFallbackLocation()
1908 self.errfmt.PrettyPrintError(e)
1909 status = 1 # another redirect word eval error
1910 cmd_st.check_errexit = True # errexit for e.g. a=${!undef}
1911
1912 # Now we've waited for process subs
1913
1914 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1915 # @_pipeline_status
1916 pipe_status = cmd_st.pipe_status
1917 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1918 # makes it annoying to check both _process_sub_status and
1919 # _pipeline_status
1920
1921 errexit_loc = loc.Missing # type: loc_t
1922 if pipe_status is not None:
1923 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1924 # for a REAL pipeline (but not singleton pipelines)
1925 assert status == -1, (
1926 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1927 status)
1928
1929 self.mem.SetPipeStatus(pipe_status)
1930
1931 if self.exec_opts.pipefail():
1932 # The status is that of the last command that is non-zero.
1933 status = 0
1934 for i, st in enumerate(pipe_status):
1935 if st != 0:
1936 status = st
1937 errexit_loc = cmd_st.pipe_locs[i]
1938 else:
1939 # The status is that of last command, period.
1940 status = pipe_status[-1]
1941
1942 if cmd_st.pipe_negated:
1943 status = 1 if status == 0 else 0
1944
1945 # Compute status from _process_sub_status
1946 if process_sub_st.codes is None:
1947 # Optimized to avoid allocs
1948 self.status_array_pool.append(process_sub_st)
1949 else:
1950 codes = process_sub_st.codes
1951 self.mem.SetProcessSubStatus(codes)
1952 if status == 0 and self.exec_opts.process_sub_fail():
1953 # Choose the LAST non-zero status, consistent with pipefail above.
1954 for i, st in enumerate(codes):
1955 if st != 0:
1956 status = st
1957 errexit_loc = process_sub_st.locs[i]
1958
1959 self.mem.SetLastStatus(status)
1960
1961 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
1962 # However, any bash construct can appear in a pipeline. So it's easier
1963 # just to put it at the end, instead of after every node.
1964 #
1965 # Possible exceptions:
1966 # - function def (however this always exits 0 anyway)
1967 # - assignment - its result should be the result of the RHS?
1968 # - e.g. arith sub, command sub? I don't want arith sub.
1969 # - ControlFlow: always raises, it has no status.
1970 if cmd_st.check_errexit:
1971 #log('cmd_st %s', cmd_st)
1972 self._CheckStatus(status, cmd_st, node, errexit_loc)
1973
1974 return status
1975
1976 def _ExecuteList(self, children):
1977 # type: (List[command_t]) -> int
1978 status = 0 # for empty list
1979 for child in children:
1980 # last status wins
1981 status = self._Execute(child)
1982 return status
1983
1984 def LastStatus(self):
1985 # type: () -> int
1986 """For main_loop.py to determine the exit code of the shell itself."""
1987 return self.mem.LastStatus()
1988
1989 def _MarkLastCommands(self, node):
1990 # type: (command_t) -> None
1991
1992 if 0:
1993 log('optimizing')
1994 node.PrettyPrint(sys.stderr)
1995 log('')
1996
1997 UP_node = node
1998 with tagswitch(node) as case:
1999 if case(command_e.Simple):
2000 node = cast(command.Simple, UP_node)
2001 node.is_last_cmd = True
2002 if 0:
2003 log('Simple optimized')
2004
2005 elif case(command_e.Subshell):
2006 node = cast(command.Subshell, UP_node)
2007 # Mark ourselves as the last
2008 node.is_last_cmd = True
2009
2010 # Also mark 'date' as the last one
2011 # echo 1; (echo 2; date)
2012 self._MarkLastCommands(node.child)
2013
2014 elif case(command_e.Pipeline):
2015 node = cast(command.Pipeline, UP_node)
2016 # Bug fix: if we change the status, we can't exec the last
2017 # element!
2018 if node.negated is None and not self.exec_opts.pipefail():
2019 self._MarkLastCommands(node.children[-1])
2020
2021 elif case(command_e.Sentence):
2022 node = cast(command.Sentence, UP_node)
2023 self._MarkLastCommands(node.child)
2024
2025 elif case(command_e.Redirect):
2026 node = cast(command.Sentence, UP_node)
2027 # Don't need to restore the redirect in any of these cases:
2028
2029 # bin/osh -c 'echo hi 2>stderr'
2030 # bin/osh -c '{ echo hi; date; } 2>stderr'
2031 # echo hi 2>stderr | wc -l
2032
2033 self._MarkLastCommands(node.child)
2034
2035 elif case(command_e.CommandList):
2036 # Subshells often have a CommandList child
2037 node = cast(command.CommandList, UP_node)
2038 self._MarkLastCommands(node.children[-1])
2039
2040 elif case(command_e.BraceGroup):
2041 # TODO: What about redirects?
2042 node = cast(BraceGroup, UP_node)
2043 self._MarkLastCommands(node.children[-1])
2044
2045 def _RemoveSubshells(self, node):
2046 # type: (command_t) -> command_t
2047 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
2048
2049 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
2050 be correct otherwise.
2051 """
2052 UP_node = node
2053 with tagswitch(node) as case:
2054 if case(command_e.Subshell):
2055 node = cast(command.Subshell, UP_node)
2056 # Optimize ( ( date ) ) etc.
2057 return self._RemoveSubshells(node.child)
2058 return node
2059
2060 def ExecuteAndCatch(self, node, cmd_flags):
2061 # type: (command_t, int) -> Tuple[bool, bool]
2062 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
2063
2064 Args:
2065 node: LST subtree
2066 optimize: Whether to exec the last process rather than fork/exec
2067
2068 Returns:
2069 TODO: use enum 'why' instead of the 2 booleans
2070
2071 Used by
2072 - main_loop.py.
2073 - SubProgramThunk for pipelines, subshell, command sub, process sub
2074 - TODO: Signals besides EXIT trap
2075
2076 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
2077 finally_exit boolean. We use a different algorithm.
2078 """
2079 if cmd_flags & OptimizeSubshells:
2080 node = self._RemoveSubshells(node)
2081
2082 if cmd_flags & MarkLastCommands:
2083 # Mark the last command in each process, so we may avoid forks
2084 self._MarkLastCommands(node)
2085
2086 if 0:
2087 log('after opt:')
2088 node.PrettyPrint()
2089 log('')
2090
2091 is_return = False
2092 is_fatal = False
2093 is_errexit = False
2094
2095 err = None # type: error.FatalRuntime
2096 status = -1 # uninitialized
2097
2098 try:
2099 options = [] # type: List[int]
2100 if cmd_flags & NoDebugTrap:
2101 options.append(option_i._no_debug_trap)
2102 if cmd_flags & NoErrTrap:
2103 options.append(option_i._no_err_trap)
2104 with state.ctx_Option(self.mutable_opts, options, True):
2105 status = self._Execute(node)
2106 except vm.IntControlFlow as e:
2107 if cmd_flags & RaiseControlFlow:
2108 raise # 'eval break' and 'source return.sh', etc.
2109 else:
2110 # Return at top level is OK, unlike in bash.
2111 if e.IsReturn():
2112 is_return = True
2113 status = e.StatusCode()
2114 else:
2115 # TODO: This error message is invalid. Can also happen in eval.
2116 # We need a flag.
2117
2118 # Invalid control flow
2119 self.errfmt.Print_(
2120 "Loop and control flow can't be in different processes",
2121 blame_loc=e.token)
2122 is_fatal = True
2123 # All shells exit 0 here. It could be hidden behind
2124 # strict_control_flow if the incompatibility causes problems.
2125 status = 1
2126 except error.Parse as e:
2127 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
2128 raise
2129 except error.ErrExit as e:
2130 err = e
2131 is_errexit = True
2132 except error.FatalRuntime as e:
2133 err = e
2134
2135 if err:
2136 status = err.ExitStatus()
2137
2138 is_fatal = True
2139 # Do this before unwinding stack
2140 self.dumper.MaybeRecord(self, err)
2141
2142 if not err.HasLocation(): # Last resort!
2143 #log('Missing location')
2144 err.location = self.mem.GetFallbackLocation()
2145 #log('%s', err.location)
2146
2147 if is_errexit:
2148 if self.exec_opts.verbose_errexit():
2149 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2150 posix.getpid())
2151 else:
2152 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2153
2154 assert status >= 0, 'Should have been initialized'
2155
2156 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2157 # created a crash dump. So we get 2 or more of them.
2158 self.dumper.MaybeDump(status)
2159
2160 self.mem.SetLastStatus(status)
2161 return is_return, is_fatal
2162
2163 def EvalCommandFrag(self, frag):
2164 # type: (command_t) -> int
2165 """For builtins to evaluate command args.
2166
2167 Many exceptions are raised.
2168
2169 Examples:
2170
2171 cd /tmp (; ; mycmd)
2172
2173 And:
2174 eval (mycmd)
2175 call _io->eval(mycmd)
2176
2177 (Should those be more like eval 'mystring'?)
2178 """
2179 return self._Execute(frag) # can raise FatalRuntimeError, etc.
2180
2181 if 0:
2182
2183 def EvalCommandClosure(self, cmd):
2184 # type: (value.Command) -> int
2185 frag = typed_args.GetCommandFrag(cmd)
2186 with state.ctx_EnclosedFrame(self.mem, cmd.captured_frame,
2187 cmd.module_frame):
2188 return self.EvalCommandFrag(frag)
2189
2190 def RunTrapsOnExit(self, mut_status):
2191 # type: (IntParamBox) -> None
2192 """If an EXIT trap handler exists, run it.
2193
2194 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2195 all bash/dash/mksh seem to agree on it. See cases in
2196 builtin-trap.test.sh.
2197
2198 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2199 of this awkward interface. But that's true in Python and not C!
2200
2201 Could use i & (n-1) == i & 255 because we have a power of 2.
2202 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2203 """
2204 # This does not raise, even on 'exit', etc.
2205 self.RunPendingTrapsAndCatch()
2206
2207 node = self.trap_state.GetHook('EXIT') # type: command_t
2208 if node:
2209 # NOTE: Don't set option_i._running_trap, because that's for
2210 # RunPendingTraps() in the MAIN LOOP
2211 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2212 try:
2213 is_return, is_fatal = self.ExecuteAndCatch(node, 0)
2214 except util.UserExit as e: # explicit exit
2215 mut_status.i = e.status
2216 return
2217 if is_return: # explicit 'return' in the trap handler!
2218 mut_status.i = self.LastStatus()
2219
2220 def _MaybeRunDebugTrap(self):
2221 # type: () -> None
2222 """Run user-specified DEBUG code before certain commands."""
2223 node = self.trap_state.GetHook('DEBUG') # type: command_t
2224 if node is None:
2225 return
2226
2227 # Fix lastpipe / job control / DEBUG trap interaction
2228 if self.exec_opts._no_debug_trap():
2229 return
2230
2231 # Don't run recursively run traps, etc.
2232 if not self.mem.ShouldRunDebugTrap():
2233 return
2234
2235 # NOTE: Don't set option_i._running_trap, because that's for
2236 # RunPendingTraps() in the MAIN LOOP
2237
2238 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2239 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2240 # for SetTokenForLine $LINENO
2241 with state.ctx_DebugTrap(self.mem):
2242 # Don't catch util.UserExit, etc.
2243 self._Execute(node)
2244
2245 def _MaybeRunErrTrap(self):
2246 # type: () -> None
2247 """
2248 Run user-specified ERR code after checking the status of certain
2249 commands (pipelines)
2250 """
2251 node = self.trap_state.GetHook('ERR') # type: command_t
2252 if node is None:
2253 return
2254
2255 # ERR trap is only run for a whole pipeline, not its parts
2256 if self.exec_opts._no_err_trap():
2257 return
2258
2259 # Prevent infinite recursion
2260 if self.mem.running_err_trap:
2261 return
2262
2263 # "disabled errexit" rule
2264 if self.mutable_opts.ErrExitIsDisabled():
2265 return
2266
2267 # bash rule - affected by set -o errtrace
2268 if not self.exec_opts.errtrace() and self.mem.InsideFunction():
2269 return
2270
2271 # NOTE: Don't set option_i._running_trap, because that's for
2272 # RunPendingTraps() in the MAIN LOOP
2273
2274 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2275 # In bash, the PIPESTATUS register leaks. See spec/builtin-trap-err.
2276 # So unlike other traps, we don't isolate registers.
2277 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2278 with state.ctx_ErrTrap(self.mem):
2279 self._Execute(node)
2280
2281 def RunProc(self, proc, cmd_val):
2282 # type: (value.Proc, cmd_value.Argv) -> int
2283 """Run procs aka "shell functions".
2284
2285 For SimpleCommand and registered completion hooks.
2286 """
2287 sig = proc.sig
2288 if sig.tag() == proc_sig_e.Closed:
2289 # We're binding named params. User should use @rest. No 'shift'.
2290 proc_argv = [] # type: List[str]
2291 else:
2292 proc_argv = cmd_val.argv[1:]
2293
2294 # Hm this sets "$@". TODO: Set ARGV only
2295 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv):
2296 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2297
2298 # Redirects still valid for functions.
2299 # Here doc causes a pipe and Process(SubProgramThunk).
2300 try:
2301 status = self._Execute(proc.body)
2302 except vm.IntControlFlow as e:
2303 if e.IsReturn():
2304 status = e.StatusCode()
2305 else:
2306 # break/continue used in the wrong place.
2307 e_die(
2308 'Unexpected %r (in proc call)' %
2309 lexer.TokenVal(e.token), e.token)
2310 except error.FatalRuntime as e:
2311 # Dump the stack before unwinding it
2312 self.dumper.MaybeRecord(self, e)
2313 raise
2314
2315 return status
2316
2317 def RunFuncForCompletion(self, proc, argv):
2318 # type: (value.Proc, List[str]) -> int
2319 """
2320 Args:
2321 argv: $1 $2 $3 ... not including $0
2322 """
2323 cmd_val = MakeBuiltinArgv(argv)
2324
2325 # TODO: Change this to run YSH procs and funcs too
2326 try:
2327 status = self.RunProc(proc, cmd_val)
2328 except error.FatalRuntime as e:
2329 self.errfmt.PrettyPrintError(e)
2330 status = e.ExitStatus()
2331 except vm.IntControlFlow as e:
2332 # shouldn't be able to exit the shell from a completion hook!
2333 # TODO: Avoid overwriting the prompt!
2334 self.errfmt.Print_('Attempted to exit from completion hook.',
2335 blame_loc=e.token)
2336
2337 status = 1
2338 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2339 return status
2340
2341
2342# vim: sw=4