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

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