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

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