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

2427 lines, 1486 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 has_plus,
195 exec_opts,
196 blame_loc,
197 destructive=True):
198 # type: (value_t, bool, optview.Exec, 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 if exec_opts.strict_array():
206 e_die("Can't convert Str to BashArray (strict_array)",
207 blame_loc)
208 old_val = cast(value.Str, UP_old_val)
209 return bash_impl.BashArray_FromList([old_val.s])
210 else:
211 return bash_impl.BashArray_New()
212 elif case(value_e.BashArray):
213 old_val = cast(value.BashArray, UP_old_val)
214 if not destructive:
215 if has_plus:
216 old_val = bash_impl.BashArray_Copy(old_val)
217 else:
218 old_val = bash_impl.BashArray_New()
219 return old_val
220 elif case(value_e.BashAssoc):
221 old_val = cast(value.BashAssoc, UP_old_val)
222 if not destructive:
223 if has_plus:
224 old_val = bash_impl.BashAssoc_Copy(old_val)
225 else:
226 old_val = bash_impl.BashAssoc_New()
227 return old_val
228 else:
229 e_die(
230 "Can't list-initialize a value of type %s" %
231 ui.ValType(old_val), blame_loc)
232
233
234def ListInitialize(val, initializer, has_plus, exec_opts, blame_loc, arith_ev):
235 # type: (value_t, value.InitializerList, bool, optview.Exec, loc_t, sh_expr_eval.ArithEvaluator) -> None
236 UP_val = val
237 with tagswitch(val) as case:
238 if case(value_e.BashArray):
239 val = cast(value.BashArray, UP_val)
240 bash_impl.BashArray_ListInitialize(val, initializer, has_plus,
241 blame_loc, arith_ev)
242 elif case(value_e.BashAssoc):
243 val = cast(value.BashAssoc, UP_val)
244 bash_impl.BashAssoc_ListInitialize(val, initializer, has_plus,
245 exec_opts, blame_loc)
246 else:
247 raise AssertionError(val.tag())
248
249
250def PlusEquals(old_val, val):
251 # type: (value_t, value_t) -> value_t
252 """Implement s+=val, typeset s+=val, etc."""
253
254 UP_old_val = old_val
255 UP_val = val
256
257 tag = val.tag()
258
259 with tagswitch(old_val) as case:
260 if case(value_e.Undef):
261 pass # val is RHS
262
263 elif case(value_e.Str):
264 if tag == value_e.Str:
265 old_val = cast(value.Str, UP_old_val)
266 str_to_append = cast(value.Str, UP_val)
267 val = value.Str(old_val.s + str_to_append.s)
268 else:
269 raise AssertionError() # parsing should prevent this
270
271 elif case(value_e.InternalStringArray, value_e.BashArray):
272 if tag == value_e.Str:
273 e_die("Can't append string to array")
274 else:
275 raise AssertionError() # parsing should prevent this
276
277 elif case(value_e.BashAssoc):
278 if tag == value_e.Str:
279 e_die("Can't append string to associative arrays")
280 else:
281 raise AssertionError() # parsing should prrevent this
282
283 else:
284 e_die("Can't append to value of type %s" % ui.ValType(old_val))
285
286 return val
287
288
289class ctx_LoopLevel(object):
290 """For checking for invalid control flow."""
291
292 def __init__(self, cmd_ev):
293 # type: (CommandEvaluator) -> None
294 cmd_ev.loop_level += 1
295 self.cmd_ev = cmd_ev
296
297 def __enter__(self):
298 # type: () -> None
299 pass
300
301 def __exit__(self, type, value, traceback):
302 # type: (Any, Any, Any) -> None
303 self.cmd_ev.loop_level -= 1
304
305
306class CommandEvaluator(object):
307 """Executes the program by tree-walking.
308
309 It also does some double-dispatch by passing itself into Eval() for
310 Compound/WordPart.
311 """
312
313 def __init__(
314 self,
315 mem, # type: state.Mem
316 exec_opts, # type: optview.Exec
317 errfmt, # type: ui.ErrorFormatter
318 procs, # type: state.Procs
319 assign_builtins, # type: Dict[builtin_t, _AssignBuiltin]
320 arena, # type: Arena
321 cmd_deps, # type: Deps
322 trap_state, # type: trap_osh.TrapState
323 signal_safe, # type: iolib.SignalSafe
324 ):
325 # type: (...) -> None
326 """
327 Args:
328 mem: Mem instance for storing variables
329 procs: dict of SHELL functions or 'procs'
330 builtins: dict of builtin callables
331 TODO: This should only be for assignment builtins?
332 cmd_deps: A bundle of stateless code
333 """
334 self.shell_ex = None # type: _Executor
335 self.arith_ev = None # type: sh_expr_eval.ArithEvaluator
336 self.bool_ev = None # type: sh_expr_eval.BoolEvaluator
337 self.expr_ev = None # type: expr_eval.ExprEvaluator
338 self.word_ev = None # type: word_eval.AbstractWordEvaluator
339 self.tracer = None # type: dev.Tracer
340
341 self.mem = mem
342 # This is for shopt and set -o. They are initialized by flags.
343 self.exec_opts = exec_opts
344 self.errfmt = errfmt
345 self.procs = procs
346 self.assign_builtins = assign_builtins
347 self.arena = arena
348
349 self.mutable_opts = cmd_deps.mutable_opts
350 self.dumper = cmd_deps.dumper
351 self.debug_f = cmd_deps.debug_f # Used by ShellFuncAction too
352
353 self.trap_state = trap_state
354 self.signal_safe = signal_safe
355
356 self.loop_level = 0 # for detecting bad top-level break/continue
357 self.check_command_sub_status = False # a hack. Modified by ShellExecutor
358
359 self.status_array_pool = [] # type: List[StatusArray]
360
361 def CheckCircularDeps(self):
362 # type: () -> None
363 assert self.arith_ev is not None
364 assert self.bool_ev is not None
365 # Disabled for push OSH
366 #assert self.expr_ev is not None
367 assert self.word_ev is not None
368
369 def _RunAssignBuiltin(self, cmd_val):
370 # type: (cmd_value.Assign) -> int
371 """Run an assignment builtin.
372
373 Except blocks copied from RunBuiltin.
374 """
375 builtin_func = self.assign_builtins.get(cmd_val.builtin_id)
376 if builtin_func is None:
377 # This only happens with alternative Oils interpreters.
378 e_die("Assignment builtin %r not configured" % cmd_val.argv[0],
379 cmd_val.arg_locs[0])
380
381 io_errors = [] # type: List[error.IOError_OSError]
382 with vm.ctx_FlushStdout(io_errors):
383 with ui.ctx_Location(self.errfmt, cmd_val.arg_locs[0]):
384 try:
385 status = builtin_func.Run(cmd_val)
386 except (IOError, OSError) as e:
387 # e.g. declare -p > /dev/full
388 self.errfmt.PrintMessage(
389 '%s builtin I/O error: %s' %
390 (cmd_val.argv[0], pyutil.strerror(e)),
391 cmd_val.arg_locs[0])
392 return 1
393 except error.Usage as e: # Copied from RunBuiltin
394 arg0 = cmd_val.argv[0]
395 self.errfmt.PrefixPrint(e.msg, '%r ' % arg0, e.location)
396 return 2 # consistent error code for usage error
397
398 if len(io_errors): # e.g. declare -p > /dev/full
399 self.errfmt.PrintMessage(
400 '%s builtin I/O: %s' %
401 (cmd_val.argv[0], pyutil.strerror(io_errors[0])),
402 cmd_val.arg_locs[0])
403 return 1
404
405 return status
406
407 def _CheckStatus(self, status, cmd_st, node, default_loc):
408 # type: (int, CommandStatus, command_t, loc_t) -> None
409 """Raises error.ErrExit, maybe with location info attached."""
410
411 assert status >= 0, status
412
413 if status == 0:
414 return # Nothing to do
415
416 self._MaybeRunErrTrap()
417
418 if self.exec_opts.errexit():
419 # NOTE: Sometimes we print 2 errors
420 # - 'type -z' has a UsageError with location, then errexit
421 # - '> /nonexistent' has an I/O error, then errexit
422 # - Pipelines and subshells are compound. Commands within them fail.
423 # - however ( exit 33 ) only prints one message.
424 #
425 # But we will want something like 'false' to have location info.
426
427 UP_node = node
428 with tagswitch(node) as case:
429 if case(command_e.ShAssignment):
430 node = cast(command.ShAssignment, UP_node)
431 cmd_st.show_code = True # leaf
432 # Note: we show errors from assignments a=$(false) rarely: when
433 # errexit, inherit_errexit, verbose_errexit are on, but
434 # command_sub_errexit is off!
435
436 # Note: a subshell often doesn't fail on its own.
437 elif case(command_e.Subshell):
438 node = cast(command.Subshell, UP_node)
439 cmd_st.show_code = True # not sure about this, e.g. ( exit 42 )
440
441 elif case(command_e.Pipeline):
442 node = cast(command.Pipeline, UP_node)
443 cmd_st.show_code = True # not sure about this
444 # TODO: We should show which element of the pipeline failed!
445
446 desc = command_str(node.tag())
447
448 # Override location if explicitly passed.
449 # Note: this produces better results for process sub
450 # echo <(sort x)
451 # and different results for some pipelines:
452 # { ls; false; } | wc -l; echo hi # Point to | or first { ?
453 if default_loc.tag() != loc_e.Missing:
454 blame_loc = default_loc # type: loc_t
455 else:
456 blame_loc = location.TokenForCommand(node)
457
458 msg = '%s failed with status %d' % (desc, status)
459 raise error.ErrExit(status,
460 msg,
461 blame_loc,
462 show_code=cmd_st.show_code)
463
464 def _EvalRedirect(self, r):
465 # type: (Redir) -> RedirValue
466
467 result = RedirValue(r.op.id, r.op, r.loc, None)
468
469 arg = r.arg
470 UP_arg = arg
471 with tagswitch(arg) as case:
472 if case(redir_param_e.Word):
473 arg_word = cast(CompoundWord, UP_arg)
474
475 # Note: needed for redirect like 'echo foo > x$LINENO'
476 self.mem.SetTokenForLine(r.op)
477
478 # Could be computed at parse time?
479 redir_type = consts.RedirArgType(r.op.id)
480
481 if redir_type == redir_arg_type_e.Path:
482 # Redirects with path arguments are evaluated in a special
483 # way. bash and zsh allow globbing a path, but
484 # dash/ash/mksh don't.
485 #
486 # If there are multiple files, zsh opens BOTH, but bash
487 # makes the command fail with status 1. We mostly follow
488 # bash behavior.
489
490 # These don't match bash/zsh behavior
491 # val = self.word_ev.EvalWordToString(arg_word)
492 # val, has_extglob = self.word_ev.EvalWordToPattern(arg_word)
493 # Short-circuit with word_.StaticEval() also doesn't work
494 # with globs
495
496 # mycpp needs this explicit declaration
497 b = braces.BraceDetect(
498 arg_word) # type: Optional[word.BracedTree]
499 if b is not None:
500 raise error.RedirectEval(
501 'Brace expansion not allowed (try adding quotes)',
502 arg_word)
503
504 # Needed for globbing behavior
505 files = self.word_ev.EvalWordSequence([arg_word])
506
507 n = len(files)
508 if n == 0:
509 # happens in OSH on empty elision
510 # in YSH because simple_word_eval globs to zero
511 raise error.RedirectEval(
512 "Can't redirect to zero files", arg_word)
513 if n > 1:
514 raise error.RedirectEval(
515 "Can't redirect to more than one file", arg_word)
516
517 result.arg = redirect_arg.Path(files[0])
518 return result
519
520 elif redir_type == redir_arg_type_e.Desc: # e.g. 1>&2, 1>&-, 1>&2-
521 val = self.word_ev.EvalWordToString(arg_word)
522 t = val.s
523 if len(t) == 0:
524 raise error.RedirectEval(
525 "Redirect descriptor can't be empty", arg_word)
526 return None
527
528 try:
529 if t == '-':
530 result.arg = redirect_arg.CloseFd
531 elif t[-1] == '-':
532 target_fd = int(t[:-1])
533 result.arg = redirect_arg.MoveFd(target_fd)
534 else:
535 result.arg = redirect_arg.CopyFd(int(t))
536 except ValueError:
537 raise error.RedirectEval(
538 'Invalid descriptor %r. Expected D, -, or D- where D is an '
539 'integer' % t, arg_word)
540 return None
541
542 return result
543
544 else:
545 raise AssertionError('Unknown redirect op')
546
547 elif case(redir_param_e.HereWord):
548 arg = cast(redir_param.HereWord, UP_arg)
549
550 val = self.word_ev.EvalWordToString(arg.w)
551 assert val.tag() == value_e.Str, val
552
553 assert r.op.id == Id.Redir_TLess, r.op
554 #print(arg_word)
555
556 # NOTE: bash and mksh both add \n for
557 # read <<< 'hi'
558 #
559 # YSH doesn't do this for multi-line strings:
560 # read <<< '''
561 # read <<< u'''
562 # read <<< """
563 if arg.is_multiline:
564 s = val.s
565 else:
566 s = val.s + '\n'
567
568 result.arg = redirect_arg.HereDoc(s)
569 return result
570
571 elif case(redir_param_e.HereDoc):
572 arg = cast(redir_param.HereDoc, UP_arg)
573 w = CompoundWord(
574 arg.stdin_parts) # HACK: Wrap it in a word to eval
575 val = self.word_ev.EvalWordToString(w)
576 assert val.tag() == value_e.Str, val
577 result.arg = redirect_arg.HereDoc(val.s)
578 return result
579
580 else:
581 raise AssertionError('Unknown redirect type')
582
583 raise AssertionError('for -Wreturn-type in C++')
584
585 def _RunSimpleCommand(self, cmd_val, cmd_st, run_flags):
586 # type: (cmd_value_t, CommandStatus, int) -> int
587 """Private interface to run a simple command (including assignment)."""
588 UP_cmd_val = cmd_val
589 with tagswitch(UP_cmd_val) as case:
590 if case(cmd_value_e.Argv):
591 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
592 self.tracer.OnSimpleCommand(cmd_val.argv)
593 return self.shell_ex.RunSimpleCommand(cmd_val, cmd_st,
594 run_flags)
595
596 elif case(cmd_value_e.Assign):
597 cmd_val = cast(cmd_value.Assign, UP_cmd_val)
598 self.tracer.OnAssignBuiltin(cmd_val)
599 return self._RunAssignBuiltin(cmd_val)
600
601 else:
602 raise AssertionError()
603
604 def _EvalTempEnv(self, more_env, flags):
605 # type: (List[EnvPair], int) -> None
606 """For FOO=1 cmd."""
607 for e_pair in more_env:
608 val = self.word_ev.EvalRhsWord(e_pair.val)
609
610 has_plus = False # We currently do not support tmpenv+=()
611 initializer = None # type: value.InitializerList
612 if val.tag() == value_e.InitializerList:
613 initializer = cast(value.InitializerList, val)
614
615 lval = LeftName(e_pair.name, e_pair.left)
616 old_val = sh_expr_eval.OldValue(
617 lval,
618 self.mem,
619 None, # No nounset
620 e_pair.left)
621 val = ListInitializeTarget(old_val,
622 has_plus,
623 self.exec_opts,
624 e_pair.left,
625 destructive=False)
626
627 # Set each var so the next one can reference it. Example:
628 # FOO=1 BAR=$FOO ls /
629 self.mem.SetNamed(location.LName(e_pair.name),
630 val,
631 scope_e.LocalOnly,
632 flags=flags)
633
634 if initializer is not None:
635 ListInitialize(val, initializer, has_plus, self.exec_opts,
636 e_pair.left, self.arith_ev)
637
638 def _StrictErrExit(self, node):
639 # type: (command_t) -> None
640 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
641 return
642
643 bad_node = _HasManyStatuses(node)
644 if bad_node:
645 node_str = ui.CommandType(bad_node)
646 e_die(_STRICT_ERREXIT_COND_MSG % node_str, loc.Command(bad_node))
647
648 def _StrictErrExitList(self, node_list):
649 # type: (List[command_t]) -> None
650 """Not allowed, too confusing:
651
652 if grep foo eggs.txt; grep bar eggs.txt; then echo hi fi
653 """
654 if not (self.exec_opts.errexit() and self.exec_opts.strict_errexit()):
655 return
656
657 if len(node_list) > 1:
658 e_die(
659 "strict_errexit only allows a single command. Hint: use 'try'.",
660 loc.Command(node_list[0]))
661
662 assert len(node_list) > 0
663 node = node_list[0]
664 bad_node = _HasManyStatuses(node)
665 if bad_node:
666 node_str = ui.CommandType(bad_node)
667 e_die(_STRICT_ERREXIT_COND_MSG % node_str, loc.Command(bad_node))
668
669 def _EvalCondition(self, cond, blame_tok):
670 # type: (condition_t, Token) -> bool
671 """
672 Args:
673 spid: for OSH conditions, where errexit was disabled -- e.g. if
674 for YSH conditions, it would be nice to blame the ( instead
675 """
676 b = False
677 UP_cond = cond
678 #log('cond %r', type(cond))
679 #log('cond %r', cond)
680 with tagswitch(cond) as case:
681 if case(condition_e.Shell):
682 cond = cast(List_of_command, UP_cond)
683 self._StrictErrExitList(cond)
684 with state.ctx_ErrExit(self.mutable_opts, False, blame_tok):
685 cond_status = self._ExecuteList(cond)
686
687 b = cond_status == 0
688
689 elif case(condition_e.YshExpr):
690 cond = cast(condition.YshExpr, UP_cond)
691 obj = self.expr_ev.EvalExpr(cond.e, blame_tok)
692 b = val_ops.ToBool(obj)
693
694 return b
695
696 def _EvalCaseArg(self, arg, blame):
697 # type: (case_arg_t, loc_t) -> value_t
698 """Evaluate a `case_arg` into a `value_t` which can be matched on in a case
699 command.
700 """
701 UP_arg = arg
702 with tagswitch(arg) as case:
703 if case(case_arg_e.Word):
704 arg = cast(case_arg.Word, UP_arg)
705 return self.word_ev.EvalWordToString(arg.w)
706
707 elif case(case_arg_e.YshExpr):
708 arg = cast(case_arg.YshExpr, UP_arg)
709 return self.expr_ev.EvalExpr(arg.e, blame)
710
711 else:
712 raise NotImplementedError()
713
714 def _DoVarDecl(self, node):
715 # type: (command.VarDecl) -> int
716 # x = 'foo' in Hay blocks
717
718 flags = state.YshDecl
719
720 if node.keyword is None:
721 # Note: there's only one LHS
722 lhs0 = node.lhs[0]
723 lval = LeftName(lhs0.name, lhs0.left)
724 assert node.rhs is not None, node
725 val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
726
727 flags |= state.SetReadOnly
728 self.mem.SetNamedYsh(lval, val, scope_e.LocalOnly, flags=flags)
729
730 else: # var or const
731 flags |= (state.SetReadOnly
732 if node.keyword.id == Id.KW_Const else 0)
733
734 # var x, y does null initialization
735 if node.rhs is None:
736 for i, lhs_val in enumerate(node.lhs):
737 lval = LeftName(lhs_val.name, lhs_val.left)
738 self.mem.SetNamedYsh(lval,
739 value.Null,
740 scope_e.LocalOnly,
741 flags=flags)
742 return 0
743
744 right_val = self.expr_ev.EvalExpr(node.rhs, loc.Missing)
745 lvals = None # type: List[LeftName]
746 rhs_vals = None # type: List[value_t]
747
748 num_lhs = len(node.lhs)
749 if num_lhs == 1:
750 lhs0 = node.lhs[0]
751 lvals = [LeftName(lhs0.name, lhs0.left)]
752 rhs_vals = [right_val]
753 else:
754 items = val_ops.ToList(
755 right_val, 'Destructuring assignment expected List',
756 node.keyword)
757
758 num_rhs = len(items)
759 if num_lhs != num_rhs:
760 raise error.Expr(
761 'Got %d places on the left, but %d values on right' %
762 (num_lhs, num_rhs), node.keyword)
763
764 lvals = []
765 rhs_vals = []
766 for i, lhs_val in enumerate(node.lhs):
767 lval = LeftName(lhs_val.name, lhs_val.left)
768 lvals.append(lval)
769 rhs_vals.append(items[i])
770
771 for i, lval in enumerate(lvals):
772 rval = rhs_vals[i]
773 self.mem.SetNamedYsh(lval,
774 rval,
775 scope_e.LocalOnly,
776 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.SetNamedYsh(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, has_plus, self.exec_opts,
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, self.exec_opts,
1064 pair.left, 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 # TODO: I'm not observing a difference with the YshDecl flag? That
1472 # should prevent the parent scope from being modified.
1473 #self.mem.SetNamedYsh(lval, func_val, scope_e.LocalOnly, flags=state.YshDecl)
1474 self.mem.SetNamedYsh(lval, func_val, scope_e.LocalOnly)
1475
1476 def _DoIf(self, node):
1477 # type: (command.If) -> int
1478 status = -1
1479
1480 done = False
1481 for if_arm in node.arms:
1482 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1483 if b:
1484 status = self._ExecuteList(if_arm.action)
1485 done = True
1486 break
1487
1488 if not done and node.else_action is not None:
1489 status = self._ExecuteList(node.else_action)
1490
1491 assert status != -1, 'Should have been initialized'
1492 return status
1493
1494 def _DoCase(self, node):
1495 # type: (command.Case) -> int
1496
1497 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1498 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1499
1500 status = 0 # If there are no arms, it should be zero?
1501
1502 done = False # Should we try the next arm?
1503
1504 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1505 ignore_next_cond = False
1506
1507 for case_arm in node.arms:
1508 with tagswitch(case_arm.pattern) as case:
1509 if case(pat_e.Words):
1510 if to_match.tag() != value_e.Str:
1511 continue # A non-string `to_match` will never match a pat.Words
1512 to_match_str = cast(value.Str, to_match)
1513
1514 pat_words = cast(pat.Words, case_arm.pattern)
1515
1516 this_arm_matches = False
1517 if ignore_next_cond: # Special handling for ;&
1518 this_arm_matches = True
1519 ignore_next_cond = False
1520 else:
1521 for pat_word in pat_words.words:
1522 word_val = self.word_ev.EvalWordToString(
1523 pat_word, word_eval.QUOTE_FNMATCH)
1524
1525 if libc.fnmatch(word_val.s, to_match_str.s,
1526 fnmatch_flags):
1527 this_arm_matches = True
1528 break # Stop at first pattern
1529
1530 if this_arm_matches:
1531 status = self._ExecuteList(case_arm.action)
1532 done = True
1533
1534 # ;& and ;;& only apply to shell-style case
1535 if case_arm.right:
1536 id_ = case_arm.right.id
1537 if id_ == Id.Op_SemiAmp:
1538 # very weird semantic
1539 ignore_next_cond = True
1540 done = False
1541 elif id_ == Id.Op_DSemiAmp:
1542 # Keep going until next pattern
1543 done = False
1544
1545 elif case(pat_e.YshExprs):
1546 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1547
1548 for pat_expr in pat_exprs.exprs:
1549 expr_val = self.expr_ev.EvalExpr(
1550 pat_expr, case_arm.left)
1551
1552 if val_ops.ExactlyEqual(expr_val, to_match,
1553 case_arm.left):
1554 status = self._ExecuteList(case_arm.action)
1555 done = True
1556 break
1557
1558 elif case(pat_e.Eggex):
1559 eggex = cast(Eggex, case_arm.pattern)
1560 eggex_val = self.expr_ev.EvalEggex(eggex)
1561
1562 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1563 status = self._ExecuteList(case_arm.action)
1564 done = True
1565 break
1566
1567 elif case(pat_e.Else):
1568 status = self._ExecuteList(case_arm.action)
1569 done = True
1570 break
1571
1572 else:
1573 raise AssertionError()
1574
1575 if done: # first match wins
1576 break
1577
1578 return status
1579
1580 def _DoTimeBlock(self, node):
1581 # type: (command.TimeBlock) -> int
1582 # TODO:
1583 # - When do we need RUSAGE_CHILDREN?
1584 # - Respect TIMEFORMAT environment variable.
1585 # "If this variable is not set, Bash acts as if it had the value"
1586 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1587 # "A trailing newline is added when the format string is displayed."
1588
1589 s_real, s_user, s_sys = pyos.Time()
1590 status = self._Execute(node.pipeline)
1591 e_real, e_user, e_sys = pyos.Time()
1592 # note: mycpp doesn't support %.3f
1593 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1594
1595 return status
1596
1597 def _DoRedirect(self, node, cmd_st):
1598 # type: (command.Redirect, CommandStatus) -> int
1599
1600 status = 0
1601 redirects = [] # type: List[RedirValue]
1602
1603 try:
1604 for redir in node.redirects:
1605 redirects.append(self._EvalRedirect(redir))
1606 except error.RedirectEval as e:
1607 self.errfmt.PrettyPrintError(e)
1608 redirects = None
1609 except error.WordFailure as e:
1610 # This happens e.g. with the following cases:
1611 #
1612 # $ echo hi > foo-* # with failglob (FailGlob)
1613 # $ echo > ${!undef} # (VarSubFailure)
1614 #
1615 if not e.HasLocation():
1616 e.location = self.mem.GetFallbackLocation()
1617 self.errfmt.PrettyPrintError(e)
1618 redirects = None
1619
1620 if redirects is None:
1621 # Error evaluating redirect words
1622 status = 1
1623
1624 # Translation fix: redirect I/O errors may happen in a C++
1625 # destructor ~vm::ctx_Redirect, which means they must be signaled
1626 # by out params, not exceptions.
1627 io_errors = [] # type: List[error.IOError_OSError]
1628
1629 # If we evaluated redirects, apply/push them
1630 if status == 0:
1631 self.shell_ex.PushRedirects(redirects, io_errors)
1632 if len(io_errors):
1633 # core/process.py prints cryptic errors, so we repeat them
1634 # here. e.g. Bad File Descriptor
1635 self.errfmt.PrintMessage(
1636 'I/O error applying redirect: %s' %
1637 pyutil.strerror(io_errors[0]),
1638 self.mem.GetFallbackLocation())
1639 status = 1
1640
1641 # If we applied redirects successfully, run the command_t, and pop
1642 # them.
1643 if status == 0:
1644 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1645 status = self._Execute(node.child)
1646 if len(io_errors):
1647 # It would be better to point to the right redirect
1648 # operator, but we don't track it specifically
1649 e_die("Fatal error popping redirect: %s" %
1650 pyutil.strerror(io_errors[0]))
1651
1652 return status
1653
1654 def _LeafTick(self):
1655 # type: () -> None
1656 """Do periodic work while executing shell.
1657
1658 We may run traps, check for Ctrl-C, or garbage collect.
1659 """
1660 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1661 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1662 # and maybe throw an exception.
1663 self.RunPendingTraps()
1664 if self.signal_safe.PollUntrappedSigInt():
1665 raise KeyboardInterrupt()
1666
1667 # TODO: Does this mess up control flow analysis? If so, we can move it
1668 # back to the top of _Execute(), so there are fewer conditionals
1669 # involved. This function gets called in SOME branches of
1670 # self._Dispatch().
1671
1672 # Manual GC point before every statement
1673 mylib.MaybeCollect()
1674
1675 def _Dispatch(self, node, cmd_st):
1676 # type: (command_t, CommandStatus) -> int
1677 """Switch on the command_t variants and execute them."""
1678
1679 # If we call RunCommandSub in a recursive call to the executor, this will
1680 # be set true (if strict_errexit is false). But it only lasts for one
1681 # command.
1682 probe('cmd_eval', '_Dispatch', node.tag())
1683 self.check_command_sub_status = False
1684
1685 UP_node = node
1686 with tagswitch(node) as case:
1687 if case(command_e.Simple): # LEAF command
1688 node = cast(command.Simple, UP_node)
1689
1690 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1691 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1692 # TODO: blame_tok should always be set.
1693 if node.blame_tok is not None:
1694 self.mem.SetTokenForLine(node.blame_tok)
1695
1696 self._MaybeRunDebugTrap()
1697 cmd_st.check_errexit = True
1698 status = self._DoSimple(node, cmd_st)
1699 self._LeafTick()
1700
1701 elif case(command_e.ShAssignment): # LEAF command
1702 node = cast(command.ShAssignment, UP_node)
1703
1704 self.mem.SetTokenForLine(node.pairs[0].left)
1705 self._MaybeRunDebugTrap()
1706
1707 # Only unqualified assignment a=b
1708 status = self._DoShAssignment(node, cmd_st)
1709 self._LeafTick()
1710
1711 elif case(command_e.Sentence): # NOT leaf, but put it up front
1712 node = cast(command.Sentence, UP_node)
1713
1714 # Don't check_errexit since this isn't a leaf command
1715 if node.terminator.id == Id.Op_Semi:
1716 status = self._Execute(node.child)
1717 else:
1718 status = self.shell_ex.RunBackgroundJob(node.child)
1719
1720 elif case(command_e.DBracket): # LEAF command
1721 node = cast(command.DBracket, UP_node)
1722
1723 self.mem.SetTokenForLine(node.left)
1724 self._MaybeRunDebugTrap()
1725
1726 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1727
1728 cmd_st.check_errexit = True
1729 cmd_st.show_code = True # this is a "leaf" for errors
1730 result = self.bool_ev.EvalB(node.expr)
1731 status = 0 if result else 1
1732 self._LeafTick()
1733
1734 elif case(command_e.DParen): # LEAF command
1735 node = cast(command.DParen, UP_node)
1736
1737 self.mem.SetTokenForLine(node.left)
1738 self._MaybeRunDebugTrap()
1739
1740 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1741
1742 cmd_st.check_errexit = True
1743 cmd_st.show_code = True # this is a "leaf" for errors
1744 i = self.arith_ev.EvalToBigInt(node.child)
1745 status = 1 if mops.Equal(i, mops.ZERO) else 0
1746 self._LeafTick()
1747
1748 elif case(command_e.ControlFlow): # LEAF command
1749 node = cast(command.ControlFlow, UP_node)
1750
1751 self.mem.SetTokenForLine(node.keyword)
1752 self._MaybeRunDebugTrap()
1753
1754 status = self._DoControlFlow(node)
1755 # Omit _LeafTick() since we likely raise an exception above
1756
1757 elif case(command_e.NoOp): # LEAF
1758 status = 0 # make it true
1759
1760 elif case(command_e.VarDecl): # YSH LEAF command
1761 node = cast(command.VarDecl, UP_node)
1762
1763 # Point to var name (bare assignment has no keyword)
1764 self.mem.SetTokenForLine(node.lhs[0].left)
1765 status = self._DoVarDecl(node)
1766 self._LeafTick()
1767
1768 elif case(command_e.Mutation): # YSH LEAF command
1769 node = cast(command.Mutation, UP_node)
1770
1771 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1772 self._DoMutation(node)
1773 status = 0 # if no exception is thrown, it succeeds
1774 self._LeafTick()
1775
1776 elif case(command_e.Expr): # YSH LEAF command
1777 node = cast(command.Expr, UP_node)
1778
1779 self.mem.SetTokenForLine(node.keyword)
1780 # YSH debug trap?
1781
1782 status = self._DoExpr(node)
1783 self._LeafTick()
1784
1785 elif case(command_e.Retval): # YSH LEAF command
1786 node = cast(command.Retval, UP_node)
1787
1788 self.mem.SetTokenForLine(node.keyword)
1789 # YSH debug trap? I think we don't want the debug trap in func
1790 # dialect, for speed?
1791
1792 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1793 self._LeafTick()
1794
1795 raise vm.ValueControlFlow(node.keyword, val)
1796
1797 #
1798 # More commands that involve recursive calls
1799 #
1800
1801 elif case(command_e.ExpandedAlias):
1802 node = cast(command.ExpandedAlias, UP_node)
1803 status = self._DoExpandedAlias(node)
1804
1805 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1806 # DoGroup has 'do' and 'done' spids for translation.
1807 elif case(command_e.CommandList):
1808 node = cast(command.CommandList, UP_node)
1809 status = self._ExecuteList(node.children)
1810
1811 elif case(command_e.DoGroup):
1812 node = cast(command.DoGroup, UP_node)
1813 status = self._ExecuteList(node.children)
1814
1815 elif case(command_e.BraceGroup):
1816 node = cast(BraceGroup, UP_node)
1817 status = self._ExecuteList(node.children)
1818
1819 elif case(command_e.AndOr):
1820 node = cast(command.AndOr, UP_node)
1821 status = self._DoAndOr(node, cmd_st)
1822
1823 elif case(command_e.If):
1824 node = cast(command.If, UP_node)
1825
1826 # Perf bug fix: loops might only execute 'if', but we still
1827 # need to GC
1828 self._LeafTick()
1829
1830 # No SetTokenForLine() because
1831 # - $LINENO can't appear directly in 'if'
1832 # - 'if' doesn't directly cause errors
1833 # It will be taken care of by command.Simple, condition, etc.
1834 status = self._DoIf(node)
1835
1836 elif case(command_e.Case):
1837 node = cast(command.Case, UP_node)
1838
1839 # Perf bug fix: loops might only execute 'case', but we still
1840 # need to GC
1841 self._LeafTick()
1842
1843 # Must set location for 'case $LINENO'
1844 self.mem.SetTokenForLine(node.case_kw)
1845 self._MaybeRunDebugTrap()
1846 status = self._DoCase(node)
1847
1848 elif case(command_e.WhileUntil):
1849 node = cast(command.WhileUntil, UP_node)
1850
1851 self.mem.SetTokenForLine(node.keyword)
1852 status = self._DoWhileUntil(node)
1853
1854 elif case(command_e.ForEach):
1855 node = cast(command.ForEach, UP_node)
1856
1857 self.mem.SetTokenForLine(node.keyword)
1858 status = self._DoForEach(node)
1859
1860 elif case(command_e.ForExpr):
1861 node = cast(command.ForExpr, UP_node)
1862
1863 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
1864 status = self._DoForExpr(node)
1865
1866 elif case(command_e.Redirect):
1867 node = cast(command.Redirect, UP_node)
1868
1869 # set -e affects redirect error, like mksh and bash 5.2, but unlike
1870 # dash/ash
1871 cmd_st.check_errexit = True
1872 status = self._DoRedirect(node, cmd_st)
1873
1874 elif case(command_e.Pipeline):
1875 node = cast(command.Pipeline, UP_node)
1876 status = self._DoPipeline(node, cmd_st)
1877
1878 elif case(command_e.Subshell):
1879 node = cast(command.Subshell, UP_node)
1880
1881 # This is a leaf from the parent process POV
1882 cmd_st.check_errexit = True
1883
1884 if node.is_last_cmd:
1885 # If the subshell is the last command in the process, just
1886 # run it in this process. See _MarkLastCommands().
1887 status = self._Execute(node.child)
1888 else:
1889 status = self.shell_ex.RunSubshell(node.child)
1890
1891 elif case(command_e.ShFunction):
1892 node = cast(command.ShFunction, UP_node)
1893 self._DoShFunction(node)
1894 status = 0
1895
1896 elif case(command_e.Proc):
1897 node = cast(Proc, UP_node)
1898 self._DoProc(node)
1899 status = 0
1900
1901 elif case(command_e.Func):
1902 node = cast(Func, UP_node)
1903
1904 # Needed for error, when the func is an existing variable name
1905 self.mem.SetTokenForLine(node.name)
1906
1907 self._DoFunc(node)
1908 status = 0
1909
1910 elif case(command_e.TimeBlock):
1911 node = cast(command.TimeBlock, UP_node)
1912 status = self._DoTimeBlock(node)
1913
1914 else:
1915 raise NotImplementedError(node.tag())
1916
1917 # Return to caller. Note the only case that didn't set it was Pipeline,
1918 # which set cmd_st.pipe_status.
1919 return status
1920
1921 def RunPendingTraps(self):
1922 # type: () -> None
1923
1924 trap_nodes = self.trap_state.GetPendingTraps()
1925 if trap_nodes is not None:
1926 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1927 True):
1928 for trap_node in trap_nodes:
1929 with state.ctx_Registers(self.mem):
1930 # TODO: show trap kind in trace
1931 with dev.ctx_Tracer(self.tracer, 'trap', None):
1932 # Note: exit status is lost
1933 self._Execute(trap_node)
1934
1935 def RunPendingTrapsAndCatch(self):
1936 # type: () -> None
1937 """
1938 Like the above, but calls ExecuteAndCatch(), which may raise util.UserExit
1939 """
1940 trap_nodes = self.trap_state.GetPendingTraps()
1941 if trap_nodes is not None:
1942 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
1943 True):
1944 for trap_node in trap_nodes:
1945 with state.ctx_Registers(self.mem):
1946 # TODO: show trap kind in trace
1947 with dev.ctx_Tracer(self.tracer, 'trap', None):
1948 # Note: exit status is lost
1949 try:
1950 self.ExecuteAndCatch(trap_node, 0)
1951 except util.UserExit:
1952 # If user calls 'exit', stop running traps, but
1953 # we still run the EXIT trap later.
1954 break
1955
1956 def _Execute(self, node):
1957 # type: (command_t) -> int
1958 """Call _Dispatch(), and perform the errexit check."""
1959
1960 # Optimization: These 2 records have rarely-used lists, so we don't pass
1961 # alloc_lists=True. We create them on demand.
1962 cmd_st = CommandStatus.CreateNull()
1963 if len(self.status_array_pool):
1964 # Optimized to avoid allocs
1965 process_sub_st = self.status_array_pool.pop()
1966 else:
1967 process_sub_st = StatusArray.CreateNull()
1968
1969 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
1970 try:
1971 status = self._Dispatch(node, cmd_st)
1972 except error.WordFailure as e: # e.g. echo hi > ${!undef}
1973 if not e.HasLocation(): # Last resort!
1974 e.location = self.mem.GetFallbackLocation()
1975 self.errfmt.PrettyPrintError(e)
1976 status = 1 # another redirect word eval error
1977 cmd_st.check_errexit = True # errexit for e.g. a=${!undef}
1978
1979 # Now we've waited for process subs
1980
1981 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
1982 # @_pipeline_status
1983 pipe_status = cmd_st.pipe_status
1984 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
1985 # makes it annoying to check both _process_sub_status and
1986 # _pipeline_status
1987
1988 errexit_loc = loc.Missing # type: loc_t
1989 if pipe_status is not None:
1990 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
1991 # for a REAL pipeline (but not singleton pipelines)
1992 assert status == -1, (
1993 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
1994 status)
1995
1996 self.mem.SetPipeStatus(pipe_status)
1997
1998 if self.exec_opts.pipefail():
1999 # The status is that of the last command that is non-zero.
2000 status = 0
2001 for i, st in enumerate(pipe_status):
2002 if st != 0:
2003 status = st
2004 errexit_loc = cmd_st.pipe_locs[i]
2005 else:
2006 # The status is that of last command, period.
2007 status = pipe_status[-1]
2008
2009 if cmd_st.pipe_negated:
2010 status = 1 if status == 0 else 0
2011
2012 # Compute status from _process_sub_status
2013 if process_sub_st.codes is None:
2014 # Optimized to avoid allocs
2015 self.status_array_pool.append(process_sub_st)
2016 else:
2017 codes = process_sub_st.codes
2018 self.mem.SetProcessSubStatus(codes)
2019 if status == 0 and self.exec_opts.process_sub_fail():
2020 # Choose the LAST non-zero status, consistent with pipefail above.
2021 for i, st in enumerate(codes):
2022 if st != 0:
2023 status = st
2024 errexit_loc = process_sub_st.locs[i]
2025
2026 self.mem.SetLastStatus(status)
2027
2028 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
2029 # However, any bash construct can appear in a pipeline. So it's easier
2030 # just to put it at the end, instead of after every node.
2031 #
2032 # Possible exceptions:
2033 # - function def (however this always exits 0 anyway)
2034 # - assignment - its result should be the result of the RHS?
2035 # - e.g. arith sub, command sub? I don't want arith sub.
2036 # - ControlFlow: always raises, it has no status.
2037 if cmd_st.check_errexit:
2038 #log('cmd_st %s', cmd_st)
2039 self._CheckStatus(status, cmd_st, node, errexit_loc)
2040
2041 return status
2042
2043 def _ExecuteList(self, children):
2044 # type: (List[command_t]) -> int
2045 status = 0 # for empty list
2046 for child in children:
2047 # last status wins
2048 status = self._Execute(child)
2049 return status
2050
2051 def LastStatus(self):
2052 # type: () -> int
2053 """For main_loop.py to determine the exit code of the shell itself."""
2054 return self.mem.LastStatus()
2055
2056 def _MarkLastCommands(self, node):
2057 # type: (command_t) -> None
2058
2059 if 0:
2060 log('optimizing')
2061 node.PrettyPrint(sys.stderr)
2062 log('')
2063
2064 UP_node = node
2065 with tagswitch(node) as case:
2066 if case(command_e.Simple):
2067 node = cast(command.Simple, UP_node)
2068 node.is_last_cmd = True
2069 if 0:
2070 log('Simple optimized')
2071
2072 elif case(command_e.Subshell):
2073 node = cast(command.Subshell, UP_node)
2074 # Mark ourselves as the last
2075 node.is_last_cmd = True
2076
2077 # Also mark 'date' as the last one
2078 # echo 1; (echo 2; date)
2079 self._MarkLastCommands(node.child)
2080
2081 elif case(command_e.Pipeline):
2082 node = cast(command.Pipeline, UP_node)
2083 # Bug fix: if we change the status, we can't exec the last
2084 # element!
2085 if node.negated is None and not self.exec_opts.pipefail():
2086 self._MarkLastCommands(node.children[-1])
2087
2088 elif case(command_e.Sentence):
2089 node = cast(command.Sentence, UP_node)
2090 self._MarkLastCommands(node.child)
2091
2092 elif case(command_e.Redirect):
2093 node = cast(command.Sentence, UP_node)
2094 # Don't need to restore the redirect in any of these cases:
2095
2096 # bin/osh -c 'echo hi 2>stderr'
2097 # bin/osh -c '{ echo hi; date; } 2>stderr'
2098 # echo hi 2>stderr | wc -l
2099
2100 self._MarkLastCommands(node.child)
2101
2102 elif case(command_e.CommandList):
2103 # Subshells often have a CommandList child
2104 node = cast(command.CommandList, UP_node)
2105 self._MarkLastCommands(node.children[-1])
2106
2107 elif case(command_e.BraceGroup):
2108 # TODO: What about redirects?
2109 node = cast(BraceGroup, UP_node)
2110 self._MarkLastCommands(node.children[-1])
2111
2112 def _RemoveSubshells(self, node):
2113 # type: (command_t) -> command_t
2114 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
2115
2116 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
2117 be correct otherwise.
2118 """
2119 UP_node = node
2120 with tagswitch(node) as case:
2121 if case(command_e.Subshell):
2122 node = cast(command.Subshell, UP_node)
2123 # Optimize ( ( date ) ) etc.
2124 return self._RemoveSubshells(node.child)
2125 return node
2126
2127 def ExecuteAndCatch(self, node, cmd_flags):
2128 # type: (command_t, int) -> Tuple[bool, bool]
2129 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
2130
2131 Args:
2132 node: LST subtree
2133 optimize: Whether to exec the last process rather than fork/exec
2134
2135 Returns:
2136 TODO: use enum 'why' instead of the 2 booleans
2137
2138 Used by
2139 - main_loop.py.
2140 - SubProgramThunk for pipelines, subshell, command sub, process sub
2141 - TODO: Signals besides EXIT trap
2142
2143 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
2144 finally_exit boolean. We use a different algorithm.
2145 """
2146 if cmd_flags & OptimizeSubshells:
2147 node = self._RemoveSubshells(node)
2148
2149 if cmd_flags & MarkLastCommands:
2150 # Mark the last command in each process, so we may avoid forks
2151 self._MarkLastCommands(node)
2152
2153 if 0:
2154 log('after opt:')
2155 node.PrettyPrint()
2156 log('')
2157
2158 is_return = False
2159 is_fatal = False
2160 is_errexit = False
2161
2162 err = None # type: error.FatalRuntime
2163 status = -1 # uninitialized
2164
2165 try:
2166 options = [] # type: List[int]
2167 if cmd_flags & NoDebugTrap:
2168 options.append(option_i._no_debug_trap)
2169 if cmd_flags & NoErrTrap:
2170 options.append(option_i._no_err_trap)
2171 with state.ctx_Option(self.mutable_opts, options, True):
2172 status = self._Execute(node)
2173 except vm.IntControlFlow as e:
2174 if cmd_flags & RaiseControlFlow:
2175 raise # 'eval break' and 'source return.sh', etc.
2176 else:
2177 # Return at top level is OK, unlike in bash.
2178 if e.IsReturn():
2179 is_return = True
2180 status = e.StatusCode()
2181 else:
2182 # TODO: This error message is invalid. Can also happen in eval.
2183 # We need a flag.
2184
2185 # Invalid control flow
2186 self.errfmt.Print_(
2187 "Loop and control flow can't be in different processes",
2188 blame_loc=e.token)
2189 is_fatal = True
2190 # All shells exit 0 here. It could be hidden behind
2191 # strict_control_flow if the incompatibility causes problems.
2192 status = 1
2193 except error.Parse as e:
2194 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
2195 raise
2196 except error.ErrExit as e:
2197 err = e
2198 is_errexit = True
2199 except error.FatalRuntime as e:
2200 err = e
2201
2202 if err:
2203 status = err.ExitStatus()
2204
2205 is_fatal = True
2206 # Do this before unwinding stack
2207 self.dumper.MaybeRecord(self, err)
2208
2209 if not err.HasLocation(): # Last resort!
2210 #log('Missing location')
2211 err.location = self.mem.GetFallbackLocation()
2212 #log('%s', err.location)
2213
2214 if is_errexit:
2215 if self.exec_opts.verbose_errexit():
2216 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2217 posix.getpid())
2218 else:
2219 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2220
2221 assert status >= 0, 'Should have been initialized'
2222
2223 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2224 # created a crash dump. So we get 2 or more of them.
2225 self.dumper.MaybeDump(status)
2226
2227 self.mem.SetLastStatus(status)
2228 return is_return, is_fatal
2229
2230 def EvalCommandFrag(self, frag):
2231 # type: (command_t) -> int
2232 """For builtins to evaluate command args.
2233
2234 Many exceptions are raised.
2235
2236 Examples:
2237
2238 cd /tmp (; ; mycmd)
2239
2240 And:
2241 eval (mycmd)
2242 call _io->eval(mycmd)
2243
2244 (Should those be more like eval 'mystring'?)
2245 """
2246 return self._Execute(frag) # can raise FatalRuntimeError, etc.
2247
2248 if 0:
2249
2250 def EvalCommandClosure(self, cmd):
2251 # type: (value.Command) -> int
2252 frag = typed_args.GetCommandFrag(cmd)
2253 with state.ctx_EnclosedFrame(self.mem, cmd.captured_frame,
2254 cmd.module_frame):
2255 return self.EvalCommandFrag(frag)
2256
2257 def RunTrapsOnExit(self, mut_status):
2258 # type: (IntParamBox) -> None
2259 """If an EXIT trap handler exists, run it.
2260
2261 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2262 all bash/dash/mksh seem to agree on it. See cases in
2263 builtin-trap.test.sh.
2264
2265 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2266 of this awkward interface. But that's true in Python and not C!
2267
2268 Could use i & (n-1) == i & 255 because we have a power of 2.
2269 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2270 """
2271 # This does not raise, even on 'exit', etc.
2272 self.RunPendingTrapsAndCatch()
2273
2274 node = self.trap_state.GetHook('EXIT') # type: command_t
2275 if node:
2276 # NOTE: Don't set option_i._running_trap, because that's for
2277 # RunPendingTraps() in the MAIN LOOP
2278 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2279 try:
2280 is_return, is_fatal = self.ExecuteAndCatch(node, 0)
2281 except util.UserExit as e: # explicit exit
2282 mut_status.i = e.status
2283 return
2284 if is_return: # explicit 'return' in the trap handler!
2285 mut_status.i = self.LastStatus()
2286
2287 def _MaybeRunDebugTrap(self):
2288 # type: () -> None
2289 """Run user-specified DEBUG code before certain commands."""
2290 node = self.trap_state.GetHook('DEBUG') # type: command_t
2291 if node is None:
2292 return
2293
2294 # Fix lastpipe / job control / DEBUG trap interaction
2295 if self.exec_opts._no_debug_trap():
2296 return
2297
2298 # Don't run recursively run traps, etc.
2299 if not self.mem.ShouldRunDebugTrap():
2300 return
2301
2302 # NOTE: Don't set option_i._running_trap, because that's for
2303 # RunPendingTraps() in the MAIN LOOP
2304
2305 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2306 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2307 # for SetTokenForLine $LINENO
2308 with state.ctx_DebugTrap(self.mem):
2309 # Don't catch util.UserExit, etc.
2310 self._Execute(node)
2311
2312 def _MaybeRunErrTrap(self):
2313 # type: () -> None
2314 """
2315 Run user-specified ERR code after checking the status of certain
2316 commands (pipelines)
2317 """
2318 node = self.trap_state.GetHook('ERR') # type: command_t
2319 if node is None:
2320 return
2321
2322 # ERR trap is only run for a whole pipeline, not its parts
2323 if self.exec_opts._no_err_trap():
2324 return
2325
2326 # Prevent infinite recursion
2327 if self.mem.running_err_trap:
2328 return
2329
2330 # "disabled errexit" rule
2331 if self.mutable_opts.ErrExitIsDisabled():
2332 return
2333
2334 # bash rule - affected by set -o errtrace
2335 if not self.exec_opts.errtrace() and self.mem.InsideFunction():
2336 return
2337
2338 # NOTE: Don't set option_i._running_trap, because that's for
2339 # RunPendingTraps() in the MAIN LOOP
2340
2341 # To make a better stack trace from vm.getDebugStack(), add the last
2342 # thing that failed, even if it's not a proc/func. This can be an
2343 # external command.
2344 #
2345 # TODO: this can lead to duplicate stack frames
2346 # - We might only want this if it's an external command?
2347 # - Or maybe we need a different trap to trigger stack traces ...
2348 self.mem.debug_stack.append(
2349 debug_frame.BeforeErrTrap(self.mem.token_for_line))
2350
2351 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2352 # TODO:
2353 # - use debug_frame.Trap
2354 # - use the original location of the 'trap' command?
2355 # - combine ctx_Tracer and debug stack? They are similar
2356 #with state.ctx_EvalDebugFrame(self.mem, self.mem.token_for_line):
2357
2358 # In bash, the PIPESTATUS register leaks. See spec/builtin-trap-err.
2359 # So unlike other traps, we don't isolate registers.
2360 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2361
2362 with state.ctx_ErrTrap(self.mem):
2363 self._Execute(node)
2364
2365 def RunProc(self, proc, cmd_val):
2366 # type: (value.Proc, cmd_value.Argv) -> int
2367 """Run procs aka "shell functions".
2368
2369 For SimpleCommand and registered completion hooks.
2370 """
2371 sig = proc.sig
2372 if sig.tag() == proc_sig_e.Closed:
2373 # We're binding named params. User should use @rest. No 'shift'.
2374 proc_argv = [] # type: List[str]
2375 else:
2376 proc_argv = cmd_val.argv[1:]
2377
2378 # Hm this sets "$@". TODO: Set ARGV only
2379 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv,
2380 cmd_val.arg_locs[0]):
2381 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2382
2383 # Redirects still valid for functions.
2384 # Here doc causes a pipe and Process(SubProgramThunk).
2385 try:
2386 status = self._Execute(proc.body)
2387 except vm.IntControlFlow as e:
2388 if e.IsReturn():
2389 status = e.StatusCode()
2390 else:
2391 # break/continue used in the wrong place.
2392 e_die(
2393 'Unexpected %r (in proc call)' %
2394 lexer.TokenVal(e.token), e.token)
2395 except error.FatalRuntime as e:
2396 # Dump the stack before unwinding it
2397 self.dumper.MaybeRecord(self, e)
2398 raise
2399
2400 return status
2401
2402 def RunFuncForCompletion(self, proc, argv):
2403 # type: (value.Proc, List[str]) -> int
2404 """
2405 Args:
2406 argv: $1 $2 $3 ... not including $0
2407 """
2408 cmd_val = MakeBuiltinArgv(argv)
2409
2410 # TODO: Change this to run YSH procs and funcs too
2411 try:
2412 status = self.RunProc(proc, cmd_val)
2413 except error.FatalRuntime as e:
2414 self.errfmt.PrettyPrintError(e)
2415 status = e.ExitStatus()
2416 except vm.IntControlFlow as e:
2417 # shouldn't be able to exit the shell from a completion hook!
2418 # TODO: Avoid overwriting the prompt!
2419 self.errfmt.Print_('Attempted to exit from completion hook.',
2420 blame_loc=e.token)
2421
2422 status = 1
2423 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2424 return status
2425
2426
2427# vim: sw=4