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

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