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

2579 lines, 1556 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, Id_t
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 VarDecl,
57 Mutation,
58 ExprCommand,
59)
60from _devbuild.gen.runtime_asdl import (
61 cmd_value,
62 cmd_value_e,
63 CommandStatus,
64 flow_e,
65 RedirValue,
66 redirect_arg,
67 ProcArgs,
68 scope_e,
69 StatusArray,
70)
71from _devbuild.gen.types_asdl import redir_arg_type_e
72from _devbuild.gen.value_asdl import (value, value_e, value_t, y_lvalue,
73 y_lvalue_e, y_lvalue_t, LeftName, Obj)
74
75from core import bash_impl
76from core import dev
77from core import error
78from core import executor
79from core.error import e_die, e_die_status
80from core import num
81from core import pyos # Time(). TODO: rename
82from core import pyutil
83from core import state
84from display import ui
85from core import util
86from core import vm
87from frontend import args
88from frontend import consts
89from frontend import lexer
90from frontend import location
91from frontend import typed_args
92from osh import braces
93from osh import sh_expr_eval
94from osh import word_eval
95from mycpp import iolib
96from mycpp import mops
97from mycpp import mylib
98from mycpp.mylib import log, probe, switch, tagswitch, str_switch, NewDict
99from ysh import expr_eval
100from ysh import func_proc
101from ysh import val_ops
102
103import posix_ as posix
104import libc # for fnmatch
105# Import this name directly because the C++ translation uses macros literally.
106from libc import FNM_CASEFOLD
107
108from typing import List, Dict, Tuple, Optional, Any, cast, TYPE_CHECKING
109
110if TYPE_CHECKING:
111 from _devbuild.gen.option_asdl import builtin_t
112 from _devbuild.gen.runtime_asdl import cmd_value_t
113 from _devbuild.gen.syntax_asdl import Redir, EnvPair
114 from core.alloc import Arena
115 from core import optview
116 from core.vm import _Executor, _AssignBuiltin
117 from builtin import trap_osh
118
119# flags for main_loop.Batch, ExecuteAndCatch. TODO: Should probably in
120# ExecuteAndCatch, along with SetValue() flags.
121IsMainProgram = 1 << 0 # the main shell program, not eval/source/subshell
122RaiseControlFlow = 1 << 1 # eval/source builtins
123OptimizeSubshells = 1 << 2
124MarkLastCommands = 1 << 3
125NoDebugTrap = 1 << 4
126NoErrTrap = 1 << 5
127
128_STRICT_ERREXIT_COND_MSG = "Command conditionals should only have one status, not %s (strict_errexit, OILS-ERR-300)"
129
130
131def MakeBuiltinArgv(argv1):
132 # type: (List[str]) -> cmd_value.Argv
133 argv = [''] # dummy for argv[0]
134 argv.extend(argv1)
135 missing = None # type: CompoundWord
136 return cmd_value.Argv(argv, [missing] * len(argv), False, None, None)
137
138
139class Deps(object):
140
141 def __init__(self):
142 # type: () -> None
143 self.mutable_opts = None # type: state.MutableOpts
144 self.dumper = None # type: dev.CrashDumper
145 self.debug_f = None # type: util._DebugFile
146 self.cflow_builtin = None # type: ControlFlowBuiltin
147
148
149def _HasManyStatuses(node):
150 # type: (command_t) -> Optional[command_t]
151 """Code patterns that are bad for POSIX errexit. For YSH strict_errexit.
152
153 Note: strict_errexit also uses
154 shopt --unset _allow_command_sub _allow_process_sub
155 """
156 UP_node = node
157 with tagswitch(node) as case:
158 # Atoms.
159 # TODO: Do we need YSH atoms here?
160 if case(command_e.Simple, command_e.DBracket, command_e.DParen):
161 return None
162
163 elif case(command_e.Redirect):
164 node = cast(command.Redirect, UP_node)
165 return _HasManyStatuses(node.child)
166
167 elif case(command_e.Sentence):
168 # Sentence check is for if false; versus if false
169 node = cast(command.Sentence, UP_node)
170 return _HasManyStatuses(node.child)
171
172 elif case(command_e.Pipeline):
173 node = cast(command.Pipeline, UP_node)
174 if len(node.children) == 1:
175 # '! false' is a pipeline that we want to ALLOW
176 # '! ( echo subshell )' is DISALLWOED
177 return _HasManyStatuses(node.children[0])
178 else:
179 # Multiple parts like 'ls | wc' is disallowed
180 return node
181
182 elif case(command_e.AndOr):
183 node = cast(command.AndOr, UP_node)
184 for c in node.children:
185 if _HasManyStatuses(c):
186 return c
187 return None # otherwise allow 'if true && true; ...'
188
189 # - ShAssignment could be allowed, though its exit code will always be
190 # 0 without command subs
191 # - Naively, (non-singleton) pipelines could be allowed because pipefail.
192 # BUT could be a proc executed inside a child process, which causes a
193 # problem: the strict_errexit check has to occur at runtime and there's
194 # no way to signal it ot the parent.
195 return node
196
197
198def ListInitializeTarget(old_val,
199 has_plus,
200 exec_opts,
201 blame_loc,
202 destructive=True):
203 # type: (value_t, bool, optview.Exec, loc_t, bool) -> value_t
204 UP_old_val = old_val
205 with tagswitch(old_val) as case:
206 if case(value_e.Undef):
207 return bash_impl.BashArray_New()
208 elif case(value_e.Str):
209 if has_plus:
210 if exec_opts.strict_array():
211 e_die("Can't convert Str to BashArray (strict_array)",
212 blame_loc)
213 old_val = cast(value.Str, UP_old_val)
214 return bash_impl.BashArray_FromList([old_val.s])
215 else:
216 return bash_impl.BashArray_New()
217 elif case(value_e.BashArray):
218 old_val = cast(value.BashArray, UP_old_val)
219 if not destructive:
220 if has_plus:
221 old_val = bash_impl.BashArray_Copy(old_val)
222 else:
223 old_val = bash_impl.BashArray_New()
224 return old_val
225 elif case(value_e.BashAssoc):
226 old_val = cast(value.BashAssoc, UP_old_val)
227 if not destructive:
228 if has_plus:
229 old_val = bash_impl.BashAssoc_Copy(old_val)
230 else:
231 old_val = bash_impl.BashAssoc_New()
232 return old_val
233 else:
234 e_die(
235 "Can't list-initialize a value of type %s" %
236 ui.ValType(old_val), blame_loc)
237
238
239def ListInitialize(val, initializer, has_plus, exec_opts, blame_loc, arith_ev):
240 # type: (value_t, value.InitializerList, bool, optview.Exec, loc_t, sh_expr_eval.ArithEvaluator) -> None
241 UP_val = val
242 with tagswitch(val) as case:
243 if case(value_e.BashArray):
244 val = cast(value.BashArray, UP_val)
245 bash_impl.BashArray_ListInitialize(val, initializer, has_plus,
246 blame_loc, arith_ev)
247 elif case(value_e.BashAssoc):
248 val = cast(value.BashAssoc, UP_val)
249 bash_impl.BashAssoc_ListInitialize(val, initializer, has_plus,
250 exec_opts, blame_loc)
251 else:
252 raise AssertionError(val.tag())
253
254
255def PlusEquals(old_val, val):
256 # type: (value_t, value_t) -> value_t
257 """Implement s+=val, typeset s+=val, etc."""
258
259 UP_old_val = old_val
260 UP_val = val
261
262 tag = val.tag()
263
264 with tagswitch(old_val) as case:
265 if case(value_e.Undef):
266 pass # val is RHS
267
268 elif case(value_e.Str):
269 if tag == value_e.Str:
270 old_val = cast(value.Str, UP_old_val)
271 str_to_append = cast(value.Str, UP_val)
272 val = value.Str(old_val.s + str_to_append.s)
273 else:
274 raise AssertionError() # parsing should prevent this
275
276 elif case(value_e.InternalStringArray, value_e.BashArray):
277 if tag == value_e.Str:
278 e_die("Can't append string to array")
279 else:
280 raise AssertionError() # parsing should prevent this
281
282 elif case(value_e.BashAssoc):
283 if tag == value_e.Str:
284 e_die("Can't append string to associative arrays")
285 else:
286 raise AssertionError() # parsing should prrevent this
287
288 else:
289 e_die("Can't append to value of type %s" % ui.ValType(old_val))
290
291 return val
292
293
294def _IsSpecialBuiltin(cmd_val, posix_mode):
295 # type: (cmd_value_t, bool) -> bool
296 """
297 Note: I tried calculating this in EvalWordSequence2() and
298 SimpleEvalWordSequence2(). They have special hint_str logic for assignment
299 builtins.
300
301 But the hint_str doesn't respect word splitting, so it's better done here.
302 And it's annoying to have duplication in SimpleEvalWordSequence2().
303 """
304 # set eval : etc. are always special builtins. TODO: remove this param
305 posix_mode = True
306
307 UP_cmd_val = cmd_val
308 with tagswitch(cmd_val) as case:
309 if case(cmd_value_e.Assign):
310 # assignment builtins are special
311 return True
312 elif case(cmd_value_e.Argv):
313 cmd_val = cast(cmd_value.Argv, UP_cmd_val)
314 if (posix_mode and consts.LookupSpecialBuiltin(cmd_val.argv[0])
315 != 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 _IsSpecialBuiltin(cmd_val, self.exec_opts.posix()):
1107 # Special builtins have their temp env persisted.
1108 self._EvalTempEnv(node.more_env, state.SetExport)
1109 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
1110 else:
1111 with state.ctx_Temp(self.mem):
1112 self._EvalTempEnv(node.more_env, state.SetExport)
1113 status = self._RunSimpleCommand(
1114 cmd_val, cmd_st, run_flags)
1115 else:
1116 status = self._RunSimpleCommand(cmd_val, cmd_st, run_flags)
1117
1118 probe('cmd_eval', '_DoSimple_exit', status)
1119 return status
1120
1121 def _DoExpandedAlias(self, node):
1122 # type: (command.ExpandedAlias) -> int
1123 # Expanded aliases need redirects and env bindings from the calling
1124 # context, as well as redirects in the expansion!
1125
1126 # TODO: SetTokenForLine to OUTSIDE? Don't bother with stuff inside
1127 # expansion, since aliases are discouraged.
1128
1129 if len(node.more_env):
1130 with state.ctx_Temp(self.mem):
1131 self._EvalTempEnv(node.more_env, state.SetExport)
1132 return self._Execute(node.child)
1133 else:
1134 return self._Execute(node.child)
1135
1136 def _DoPipeline(self, node, cmd_st):
1137 # type: (command.Pipeline, CommandStatus) -> int
1138 cmd_st.check_errexit = True
1139 for op in node.ops:
1140 if op.id != Id.Op_Pipe:
1141 e_die("|& isn't supported", op)
1142
1143 # Remove $_ before pipeline. This matches bash, and is important in
1144 # pipelines than assignments because pipelines are non-deterministic.
1145 self.mem.SetLastArgument('')
1146
1147 # Set status to INVALID value, because we MIGHT set cmd_st.pipe_status,
1148 # which _Execute() boils down into a status for us.
1149 status = -1
1150
1151 if node.negated is not None:
1152 self._StrictErrExit(node)
1153 with state.ctx_ErrExit(self.mutable_opts, False, node.negated):
1154 # '! grep' is parsed as a pipeline, according to the grammar, but
1155 # there's no pipe() call.
1156 if len(node.children) == 1:
1157 tmp_status = self._Execute(node.children[0])
1158 status = 1 if tmp_status == 0 else 0
1159 else:
1160 self.shell_ex.RunPipeline(node, cmd_st)
1161 cmd_st.pipe_negated = True
1162
1163 # errexit is disabled for !.
1164 cmd_st.check_errexit = False
1165 else:
1166 self.shell_ex.RunPipeline(node, cmd_st)
1167
1168 return status
1169
1170 def _DoShAssignment(self, node, cmd_st):
1171 # type: (command.ShAssignment, CommandStatus) -> int
1172 assert len(node.pairs) >= 1, node
1173
1174 # x=y is 'neutered' inside 'proc'
1175 which_scopes = self.mem.ScopesForWriting()
1176
1177 for pair in node.pairs:
1178 # The shell assignments should always have RHS. An AssignPair
1179 # stored in command_e.ShAssignment is constructed in
1180 # cmd_parse._MakeAssignPair, where rhs is explicitly constructed to
1181 # be CompoundWord or rhs_word.Empty.
1182 assert pair.rhs, pair.rhs
1183
1184 # RHS can be a string or initializer list.
1185 rhs = self.word_ev.EvalRhsWord(pair.rhs)
1186 assert isinstance(rhs, value_t), rhs
1187
1188 lval = self.arith_ev.EvalShellLhs(pair.lhs, which_scopes)
1189 has_plus = pair.op == assign_op_e.PlusEqual
1190
1191 initializer = None # type: value.InitializerList
1192 if rhs.tag() == value_e.InitializerList:
1193 initializer = cast(value.InitializerList, rhs)
1194
1195 # If rhs is an initializer list, we perform the initialization
1196 # of LHS.
1197 old_val = sh_expr_eval.OldValue(lval, self.mem, None,
1198 node.left)
1199
1200 val = ListInitializeTarget(old_val, has_plus, self.exec_opts,
1201 pair.left)
1202
1203 elif has_plus:
1204 # do not respect set -u
1205 old_val = sh_expr_eval.OldValue(lval, self.mem, None,
1206 node.left)
1207
1208 val = PlusEquals(old_val, rhs)
1209
1210 elif pair.rhs:
1211 # plain assignment
1212 val = rhs
1213
1214 # NOTE: In bash and mksh, declare -a myarray makes an empty cell
1215 # with Undef value, but the 'array' attribute.
1216
1217 flags = 0 # for tracing
1218 self.mem.SetValue(lval, val, which_scopes, flags=flags)
1219 if initializer is not None:
1220 ListInitialize(val, initializer, has_plus, self.exec_opts,
1221 pair.left, self.arith_ev)
1222
1223 self.tracer.OnShAssignment(lval, pair.op, rhs, flags, which_scopes)
1224
1225 # PATCH to be compatible with existing shells: If the assignment had a
1226 # command sub like:
1227 #
1228 # s=$(echo one; false)
1229 #
1230 # then its status will be in mem.last_status, and we can check it here.
1231 # If there was NOT a command sub in the assignment, then we don't want to
1232 # check it.
1233
1234 # Only do this if there was a command sub? How? Look at node?
1235 # Set a flag in mem? self.mem.last_status or
1236 if self.check_command_sub_status:
1237 last_status = self.mem.LastStatus()
1238 self._CheckStatus(last_status, cmd_st, node, loc.Missing)
1239 return last_status # A global assignment shouldn't clear $?.
1240 else:
1241 return 0
1242
1243 def _DoExpr(self, node):
1244 # type: (ExprCommand) -> int
1245
1246 # call f(x) or = f(x)
1247 val = self.expr_ev.EvalExpr(node.e, loc.Missing)
1248
1249 if node.keyword.id == Id.Lit_Equals: # = f(x)
1250 io_errors = [] # type: List[error.IOError_OSError]
1251 with vm.ctx_FlushStdout(io_errors):
1252 try:
1253 ui.PrettyPrintValue('', val, mylib.Stdout())
1254 except (IOError, OSError) as e:
1255 self.errfmt.PrintMessage(
1256 'I/O error during = keyword: %s' % pyutil.strerror(e),
1257 node.keyword)
1258 return 1
1259
1260 if len(io_errors): # e.g. disk full, ulimit
1261 self.errfmt.PrintMessage(
1262 'I/O error during = keyword: %s' %
1263 pyutil.strerror(io_errors[0]), node.keyword)
1264 return 1
1265
1266 return 0
1267
1268 def _DoControlFlow(self, node):
1269 # type: (command.ControlFlow) -> int
1270 """
1271 Note: YSH control flow is static, while OSH control flow may be dynamic
1272 (without shopt -s strict_control_flow)
1273
1274 One reason is to have a static control flow graph, so we could possibly
1275 compile shell to bytecode. That is MAYBE a step toward getting rid of
1276 the "C++ exceptions are slow when they throw" problem.
1277 """
1278 w = node.arg_word
1279 if w: # Evaluate the argument
1280 arg_str = self.word_ev.EvalWordToString(w).s
1281 arg_loc = w # type: loc_t
1282 else:
1283 arg_str = None
1284 arg_loc = loc.Missing
1285
1286 keyword = node.keyword
1287 # This is static, so we could also use lexer.TokenVal()
1288 keyword_str = consts.ControlFlowName(keyword.id)
1289
1290 return self.cflow_builtin.Static(keyword.id, keyword_str, keyword,
1291 arg_str, arg_loc)
1292
1293 def _DoAndOr(self, node, cmd_st):
1294 # type: (command.AndOr, CommandStatus) -> int
1295 # NOTE: && and || have EQUAL precedence in command mode. See case #13
1296 # in dbracket.test.sh.
1297
1298 left = node.children[0]
1299
1300 # Suppress failure for every child except the last one.
1301 self._StrictErrExit(left)
1302 with state.ctx_ErrExit(self.mutable_opts, False, node.ops[0]):
1303 status = self._Execute(left)
1304
1305 i = 1
1306 n = len(node.children)
1307 while i < n:
1308 #log('i %d status %d', i, status)
1309 child = node.children[i]
1310 op = node.ops[i - 1]
1311 op_id = op.id
1312
1313 #log('child %s op_id %s', child, op_id)
1314
1315 if op_id == Id.Op_DPipe and status == 0:
1316 i += 1
1317 continue # short circuit
1318
1319 elif op_id == Id.Op_DAmp and status != 0:
1320 i += 1
1321 continue # short circuit
1322
1323 if i == n - 1: # errexit handled differently for last child
1324 status = self._Execute(child)
1325 else:
1326 # blame the right && or ||
1327 self._StrictErrExit(child)
1328 with state.ctx_ErrExit(self.mutable_opts, False, op):
1329 status = self._Execute(child)
1330
1331 i += 1
1332
1333 return status
1334
1335 def _DoWhileUntil(self, node):
1336 # type: (command.WhileUntil) -> int
1337 status = 0
1338 with ctx_LoopLevel(self.cflow_builtin):
1339 while True:
1340 try:
1341 # blame while/until spid
1342 b = self._EvalCondition(node.cond, node.keyword)
1343 if node.keyword.id == Id.KW_Until:
1344 b = not b
1345 if not b:
1346 break
1347 status = self._Execute(node.body) # last one wins
1348
1349 except vm.IntControlFlow as e:
1350 status = 0
1351 action = e.HandleLoop()
1352 if action == flow_e.Break:
1353 break
1354 elif action == flow_e.Raise:
1355 raise
1356
1357 return status
1358
1359 def _DoForEach(self, node):
1360 # type: (command.ForEach) -> int
1361
1362 # for the 2 kinds of shell loop
1363 iter_list = None # type: List[str]
1364
1365 # for YSH loop
1366 iter_expr = None # type: expr_t
1367 expr_blame = None # type: loc_t
1368
1369 iterable = node.iterable
1370 UP_iterable = iterable
1371
1372 with tagswitch(node.iterable) as case:
1373 if case(for_iter_e.Args):
1374 iter_list = self.mem.GetArgv()
1375
1376 elif case(for_iter_e.Words):
1377 iterable = cast(for_iter.Words, UP_iterable)
1378 words = braces.BraceExpandWords(iterable.words)
1379 iter_list = self.word_ev.EvalWordSequence(words)
1380
1381 elif case(for_iter_e.YshExpr):
1382 iterable = cast(for_iter.YshExpr, UP_iterable)
1383 iter_expr = iterable.e
1384 expr_blame = iterable.blame
1385
1386 else:
1387 raise AssertionError()
1388
1389 n = len(node.iter_names)
1390 assert n > 0
1391
1392 i_name = None # type: Optional[LeftName]
1393 # required
1394 name1 = None # type: LeftName
1395 name2 = None # type: Optional[LeftName]
1396
1397 it2 = None # type: val_ops.Iterator
1398 if iter_expr: # for_expr.YshExpr
1399 val = self.expr_ev.EvalExpr(iter_expr, expr_blame)
1400
1401 UP_val = val
1402 with tagswitch(val) as case:
1403 if case(value_e.List):
1404 val = cast(value.List, UP_val)
1405 it2 = val_ops.ListIterator(val)
1406
1407 if n == 1:
1408 name1 = location.LName(node.iter_names[0])
1409 elif n == 2:
1410 i_name = location.LName(node.iter_names[0])
1411 name1 = location.LName(node.iter_names[1])
1412 else:
1413 # This is similar to a parse error
1414 e_die_status(
1415 2,
1416 'List iteration expects at most 2 loop variables',
1417 node.keyword)
1418
1419 elif case(value_e.Dict):
1420 val = cast(value.Dict, UP_val)
1421 it2 = val_ops.DictIterator(val)
1422
1423 if n == 1:
1424 name1 = location.LName(node.iter_names[0])
1425 elif n == 2:
1426 name1 = location.LName(node.iter_names[0])
1427 name2 = location.LName(node.iter_names[1])
1428 elif n == 3:
1429 i_name = location.LName(node.iter_names[0])
1430 name1 = location.LName(node.iter_names[1])
1431 name2 = location.LName(node.iter_names[2])
1432 else:
1433 raise AssertionError()
1434
1435 elif case(value_e.Range):
1436 val = cast(value.Range, UP_val)
1437 it2 = val_ops.RangeIterator(val)
1438
1439 if n == 1:
1440 name1 = location.LName(node.iter_names[0])
1441 elif n == 2:
1442 i_name = location.LName(node.iter_names[0])
1443 name1 = location.LName(node.iter_names[1])
1444 else:
1445 e_die_status(
1446 2,
1447 'Range iteration expects at most 2 loop variables',
1448 node.keyword)
1449
1450 elif case(value_e.Stdin):
1451 # TODO: This could changed to magic iterator?
1452 it2 = val_ops.StdinIterator(expr_blame)
1453 if n == 1:
1454 name1 = location.LName(node.iter_names[0])
1455 elif n == 2:
1456 i_name = location.LName(node.iter_names[0])
1457 name1 = location.LName(node.iter_names[1])
1458 else:
1459 e_die_status(
1460 2,
1461 'Stdin iteration expects at most 2 loop variables',
1462 node.keyword)
1463 else:
1464 raise error.TypeErr(
1465 val, 'for loop expected List, Dict, Range, or Stdin',
1466 node.keyword)
1467
1468 else:
1469 assert iter_list is not None, iter_list
1470
1471 #log('iter list %s', iter_list)
1472 it2 = val_ops.ArrayIter(iter_list)
1473
1474 if n == 1:
1475 name1 = location.LName(node.iter_names[0])
1476 elif n == 2:
1477 i_name = location.LName(node.iter_names[0])
1478 name1 = location.LName(node.iter_names[1])
1479 else:
1480 # This is similar to a parse error
1481 e_die_status(
1482 2, 'Argv iteration expects at most 2 loop variables',
1483 node.keyword)
1484
1485 status = 0 # in case we loop zero times
1486 with ctx_LoopLevel(self.cflow_builtin):
1487 while True:
1488 with state.ctx_LoopFrame(self.mem,
1489 self.exec_opts.for_loop_frames()):
1490 first = it2.FirstValue()
1491 #log('first %s', first)
1492 if first is None: # for StdinIterator
1493 #log('first is None')
1494 break
1495
1496 if first.tag() == value_e.Interrupted:
1497 self.RunPendingTraps()
1498 #log('Done running traps')
1499 continue
1500
1501 self.mem.SetLocalName(name1, first)
1502 if name2:
1503 self.mem.SetLocalName(name2, it2.SecondValue())
1504 if i_name:
1505 self.mem.SetLocalName(i_name, num.ToBig(it2.Index()))
1506
1507 # increment index before handling continue, etc.
1508 it2.Next()
1509
1510 try:
1511 status = self._Execute(node.body) # last one wins
1512 except vm.IntControlFlow as e:
1513 status = 0
1514 action = e.HandleLoop()
1515 if action == flow_e.Break:
1516 break
1517 elif action == flow_e.Raise:
1518 raise
1519
1520 return status
1521
1522 def _DoForExpr(self, node):
1523 # type: (command.ForExpr) -> int
1524
1525 status = 0
1526
1527 init = node.init
1528 for_cond = node.cond
1529 body = node.body
1530 update = node.update
1531
1532 self.arith_ev.Eval(init)
1533 with ctx_LoopLevel(self.cflow_builtin):
1534 while True:
1535 # We only accept integers as conditions
1536 cond_int = self.arith_ev.EvalToBigInt(for_cond)
1537 if mops.Equal(cond_int, mops.ZERO): # false
1538 break
1539
1540 try:
1541 status = self._Execute(body)
1542 except vm.IntControlFlow as e:
1543 status = 0
1544 action = e.HandleLoop()
1545 if action == flow_e.Break:
1546 break
1547 elif action == flow_e.Raise:
1548 raise
1549
1550 self.arith_ev.Eval(update)
1551
1552 return status
1553
1554 def _DoShFunction(self, node):
1555 # type: (command.ShFunction) -> None
1556
1557 # Note: shell functions don't have a captured frame. TODO: Get rid of
1558 # GlobalFrame as well. I think shell functions will be disallowed in
1559 # pure YSH. They appear in the __sh_funcs__, NOT in modules!
1560
1561 sh_func = value.Proc(node.name, node.name_tok,
1562 proc_sig.Open, node.body, None, True, None,
1563 self.mem.GlobalFrame())
1564 self.procs.DefineShellFunc(node.name, sh_func)
1565
1566 def _DoProc(self, node):
1567 # type: (Proc) -> None
1568 proc_name = lexer.TokenVal(node.name)
1569
1570 if node.sig.tag() == proc_sig_e.Closed:
1571 sig = cast(proc_sig.Closed, node.sig)
1572 proc_defaults = func_proc.EvalProcDefaults(self.expr_ev, sig)
1573 else:
1574 proc_defaults = None
1575
1576 # no dynamic scope
1577 proc = value.Proc(proc_name, node.name, node.sig, node.body,
1578 proc_defaults, False, self.mem.CurrentFrame(),
1579 self.mem.GlobalFrame())
1580 self.procs.DefineProc(proc_name, proc)
1581
1582 def _DoFunc(self, node):
1583 # type: (Func) -> None
1584 name = lexer.TokenVal(node.name)
1585 lval = location.LName(name)
1586
1587 pos_defaults, named_defaults = func_proc.EvalFuncDefaults(
1588 self.expr_ev, node)
1589 func_val = value.Func(name, node, pos_defaults, named_defaults,
1590 self.mem.CurrentFrame(), self.mem.GlobalFrame())
1591
1592 # TODO: I'm not observing a difference with the YshDecl flag? That
1593 # should prevent the parent scope from being modified.
1594 #self.mem.SetNamedYsh(lval, func_val, scope_e.LocalOnly, flags=state.YshDecl)
1595 self.mem.SetNamedYsh(lval, func_val, scope_e.LocalOnly)
1596
1597 def _DoIf(self, node):
1598 # type: (command.If) -> int
1599 status = -1
1600
1601 done = False
1602 for if_arm in node.arms:
1603 b = self._EvalCondition(if_arm.cond, if_arm.keyword)
1604 if b:
1605 status = self._ExecuteList(if_arm.action)
1606 done = True
1607 break
1608
1609 if not done and node.else_action is not None:
1610 status = self._ExecuteList(node.else_action)
1611
1612 assert status != -1, 'Should have been initialized'
1613 return status
1614
1615 def _DoCase(self, node):
1616 # type: (command.Case) -> int
1617
1618 to_match = self._EvalCaseArg(node.to_match, node.case_kw)
1619 fnmatch_flags = FNM_CASEFOLD if self.exec_opts.nocasematch() else 0
1620
1621 status = 0 # If there are no arms, it should be zero?
1622
1623 done = False # Should we try the next arm?
1624
1625 # For &; terminator - not just case fallthrough, but IGNORE the condition!
1626 ignore_next_cond = False
1627
1628 for case_arm in node.arms:
1629 with tagswitch(case_arm.pattern) as case:
1630 if case(pat_e.Words):
1631 if to_match.tag() != value_e.Str:
1632 continue # A non-string `to_match` will never match a pat.Words
1633 to_match_str = cast(value.Str, to_match)
1634
1635 pat_words = cast(pat.Words, case_arm.pattern)
1636
1637 this_arm_matches = False
1638 if ignore_next_cond: # Special handling for ;&
1639 this_arm_matches = True
1640 ignore_next_cond = False
1641 else:
1642 for pat_word in pat_words.words:
1643 word_val = self.word_ev.EvalWordToString(
1644 pat_word, word_eval.QUOTE_FNMATCH)
1645
1646 if libc.fnmatch(word_val.s, to_match_str.s,
1647 fnmatch_flags):
1648 this_arm_matches = True
1649 break # Stop at first pattern
1650
1651 if this_arm_matches:
1652 status = self._ExecuteList(case_arm.action)
1653 done = True
1654
1655 # ;& and ;;& only apply to shell-style case
1656 if case_arm.right:
1657 id_ = case_arm.right.id
1658 if id_ == Id.Op_SemiAmp:
1659 # very weird semantic
1660 ignore_next_cond = True
1661 done = False
1662 elif id_ == Id.Op_DSemiAmp:
1663 # Keep going until next pattern
1664 done = False
1665
1666 elif case(pat_e.YshExprs):
1667 pat_exprs = cast(pat.YshExprs, case_arm.pattern)
1668
1669 for pat_expr in pat_exprs.exprs:
1670 expr_val = self.expr_ev.EvalExpr(
1671 pat_expr, case_arm.left)
1672
1673 if val_ops.ExactlyEqual(expr_val, to_match,
1674 case_arm.left):
1675 status = self._ExecuteList(case_arm.action)
1676 done = True
1677 break
1678
1679 elif case(pat_e.Eggex):
1680 eggex = cast(Eggex, case_arm.pattern)
1681 eggex_val = self.expr_ev.EvalEggex(eggex)
1682
1683 if val_ops.MatchRegex(to_match, eggex_val, self.mem):
1684 status = self._ExecuteList(case_arm.action)
1685 done = True
1686 break
1687
1688 elif case(pat_e.Else):
1689 status = self._ExecuteList(case_arm.action)
1690 done = True
1691 break
1692
1693 else:
1694 raise AssertionError()
1695
1696 if done: # first match wins
1697 break
1698
1699 return status
1700
1701 def _DoTimeBlock(self, node):
1702 # type: (command.TimeBlock) -> int
1703 # TODO:
1704 # - When do we need RUSAGE_CHILDREN?
1705 # - Respect TIMEFORMAT environment variable.
1706 # "If this variable is not set, Bash acts as if it had the value"
1707 # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
1708 # "A trailing newline is added when the format string is displayed."
1709
1710 s_real, s_user, s_sys = pyos.Time()
1711 status = self._Execute(node.pipeline)
1712 e_real, e_user, e_sys = pyos.Time()
1713 # note: mycpp doesn't support %.3f
1714 libc.print_time(e_real - s_real, e_user - s_user, e_sys - s_sys)
1715
1716 return status
1717
1718 def _DoRedirect(self, node, cmd_st):
1719 # type: (command.Redirect, CommandStatus) -> int
1720
1721 status = 0
1722 redirects = [] # type: List[RedirValue]
1723
1724 try:
1725 for redir in node.redirects:
1726 redirects.append(self._EvalRedirect(redir))
1727 except error.RedirectEval as e:
1728 self.errfmt.PrettyPrintError(e)
1729 redirects = None
1730 except error.WordFailure as e:
1731 # This happens e.g. with the following cases:
1732 #
1733 # $ echo hi > foo-* # with failglob (FailGlob)
1734 # $ echo > ${!undef} # (VarSubFailure)
1735 #
1736 if not e.HasLocation():
1737 e.location = self.mem.GetFallbackLocation()
1738 self.errfmt.PrettyPrintError(e)
1739 redirects = None
1740
1741 if redirects is None:
1742 # Error evaluating redirect words
1743 status = 1
1744
1745 # Translation fix: redirect I/O errors may happen in a C++
1746 # destructor ~vm::ctx_Redirect, which means they must be signaled
1747 # by out params, not exceptions.
1748 io_errors = [] # type: List[error.IOError_OSError]
1749
1750 # If we evaluated redirects, apply/push them
1751 if status == 0:
1752 self.shell_ex.PushRedirects(redirects, io_errors)
1753 if len(io_errors):
1754 # core/process.py prints cryptic errors, so we repeat them
1755 # here. e.g. Bad File Descriptor
1756 self.errfmt.PrintMessage(
1757 'I/O error applying redirect: %s' %
1758 pyutil.strerror(io_errors[0]),
1759 self.mem.GetFallbackLocation())
1760 status = 1
1761
1762 # If we applied redirects successfully, run the command_t, and pop
1763 # them.
1764 if status == 0:
1765 with vm.ctx_Redirect(self.shell_ex, len(redirects), io_errors):
1766 status = self._Execute(node.child)
1767 if len(io_errors):
1768 # It would be better to point to the right redirect
1769 # operator, but we don't track it specifically
1770 e_die("Fatal error popping redirect: %s" %
1771 pyutil.strerror(io_errors[0]))
1772
1773 return status
1774
1775 def _LeafTick(self):
1776 # type: () -> None
1777 """Do periodic work while executing shell.
1778
1779 We may run traps, check for Ctrl-C, or garbage collect.
1780 """
1781 # TODO: Do this in "leaf" nodes? SimpleCommand, DBracket, DParen should
1782 # call self.DoTick()? That will RunPendingTraps and check the Ctrl-C flag,
1783 # and maybe throw an exception.
1784 self.RunPendingTraps()
1785 if self.signal_safe.PollUntrappedSigInt():
1786 raise KeyboardInterrupt()
1787
1788 # TODO: Does this mess up control flow analysis? If so, we can move it
1789 # back to the top of _Execute(), so there are fewer conditionals
1790 # involved. This function gets called in SOME branches of
1791 # self._Dispatch().
1792
1793 # Manual GC point before every statement
1794 mylib.MaybeCollect()
1795
1796 def _DispatchFast(self, node, cmd_st):
1797 # type: (command_t, CommandStatus) -> int
1798 """
1799 TODO: protoype YSH fast_cmd_t
1800
1801 var x = 5
1802 while (x > 0) {
1803 echo "x = $x"
1804 if (x === 3) {
1805 break
1806 }
1807 setvar x -= 1
1808 }
1809
1810 Nodes to compile:
1811 While, If, ControlFlow
1812 While has BraceGroup body though
1813 New nodes:
1814 Jump(int index) # index into what?
1815 JumpIfFalse( ? ) # this usually takes the top of stack?
1816
1817 Nodes that stay the same:
1818 VarDecl, Mutation, Simple
1819
1820 Do a very simple interpretation, ignoring ctx_*
1821
1822 And then introduce mem.exec_stack ? These are like the Python VMs frame.
1823
1824 - Second option: return an enum
1825 """
1826 pass
1827
1828 def _Dispatch(self, node, cmd_st):
1829 # type: (command_t, CommandStatus) -> int
1830 """Switch on the command_t variants and execute them."""
1831
1832 # If we call RunCommandSub in a recursive call to the executor, this will
1833 # be set true (if strict_errexit is false). But it only lasts for one
1834 # command.
1835 probe('cmd_eval', '_Dispatch', node.tag())
1836 self.check_command_sub_status = False
1837
1838 UP_node = node
1839 with tagswitch(node) as case:
1840 if case(command_e.Simple): # LEAF command
1841 node = cast(command.Simple, UP_node)
1842
1843 # for $LINENO, e.g. PS4='+$SOURCE_NAME:$LINENO:'
1844 # Note that for '> $LINENO' the location token is set in _EvalRedirect.
1845 # TODO: blame_tok should always be set.
1846 if node.blame_tok is not None:
1847 self.mem.SetTokenForLine(node.blame_tok)
1848
1849 self._MaybeRunDebugTrap()
1850 cmd_st.check_errexit = True
1851 status = self._DoSimple(node, cmd_st)
1852 self._LeafTick()
1853
1854 elif case(command_e.ShAssignment): # LEAF command
1855 node = cast(command.ShAssignment, UP_node)
1856
1857 self.mem.SetTokenForLine(node.pairs[0].left)
1858 self._MaybeRunDebugTrap()
1859
1860 # Only unqualified assignment a=b
1861 status = self._DoShAssignment(node, cmd_st)
1862 self._LeafTick()
1863
1864 elif case(command_e.Sentence): # NOT leaf, but put it up front
1865 node = cast(command.Sentence, UP_node)
1866
1867 # Don't check_errexit since this isn't a leaf command
1868 if node.terminator.id == Id.Op_Semi:
1869 status = self._Execute(node.child)
1870 else:
1871 status = self.shell_ex.RunBackgroundJob(node.child)
1872
1873 elif case(command_e.DBracket): # LEAF command
1874 node = cast(command.DBracket, UP_node)
1875
1876 self.mem.SetTokenForLine(node.left)
1877 self._MaybeRunDebugTrap()
1878
1879 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1880
1881 cmd_st.check_errexit = True
1882 cmd_st.show_code = True # this is a "leaf" for errors
1883 result = self.bool_ev.EvalB(node.expr)
1884 status = 0 if result else 1
1885 self._LeafTick()
1886
1887 elif case(command_e.DParen): # LEAF command
1888 node = cast(command.DParen, UP_node)
1889
1890 self.mem.SetTokenForLine(node.left)
1891 self._MaybeRunDebugTrap()
1892
1893 self.tracer.PrintSourceCode(node.left, node.right, self.arena)
1894
1895 cmd_st.check_errexit = True
1896 cmd_st.show_code = True # this is a "leaf" for errors
1897 i = self.arith_ev.EvalToBigInt(node.child)
1898 status = 1 if mops.Equal(i, mops.ZERO) else 0
1899 self._LeafTick()
1900
1901 elif case(command_e.ControlFlow): # LEAF command
1902 node = cast(command.ControlFlow, UP_node)
1903
1904 self.mem.SetTokenForLine(node.keyword)
1905 self._MaybeRunDebugTrap()
1906
1907 status = self._DoControlFlow(node)
1908 # Omit _LeafTick() since we likely raise an exception above
1909
1910 elif case(command_e.NoOp): # LEAF
1911 status = 0 # make it true
1912
1913 elif case(command_e.VarDecl): # YSH LEAF command
1914 node = cast(VarDecl, UP_node)
1915
1916 # Point to var name (bare assignment has no keyword)
1917 self.mem.SetTokenForLine(node.lhs[0].left)
1918 status = self._DoVarDecl(node)
1919 self._LeafTick()
1920
1921 elif case(command_e.Mutation): # YSH LEAF command
1922 node = cast(Mutation, UP_node)
1923
1924 self.mem.SetTokenForLine(node.keyword) # point to setvar/set
1925 self._DoMutation(node)
1926 status = 0 # if no exception is thrown, it succeeds
1927 self._LeafTick()
1928
1929 elif case(command_e.Expr): # YSH LEAF command
1930 node = cast(ExprCommand, UP_node)
1931
1932 self.mem.SetTokenForLine(node.keyword)
1933 # YSH debug trap?
1934
1935 status = self._DoExpr(node)
1936 self._LeafTick()
1937
1938 elif case(command_e.Retval): # YSH LEAF command
1939 node = cast(command.Retval, UP_node)
1940
1941 self.mem.SetTokenForLine(node.keyword)
1942 # YSH debug trap? I think we don't want the debug trap in func
1943 # dialect, for speed?
1944
1945 val = self.expr_ev.EvalExpr(node.val, node.keyword)
1946 self._LeafTick()
1947
1948 raise vm.ValueControlFlow(node.keyword, val)
1949
1950 #
1951 # More commands that involve recursive calls
1952 #
1953
1954 elif case(command_e.ExpandedAlias):
1955 node = cast(command.ExpandedAlias, UP_node)
1956 status = self._DoExpandedAlias(node)
1957
1958 # Note CommandList and DoGroup have no redirects, but BraceGroup does.
1959 # DoGroup has 'do' and 'done' spids for translation.
1960 elif case(command_e.CommandList):
1961 node = cast(command.CommandList, UP_node)
1962 status = self._ExecuteList(node.children)
1963
1964 elif case(command_e.DoGroup):
1965 node = cast(command.DoGroup, UP_node)
1966 status = self._ExecuteList(node.children)
1967
1968 elif case(command_e.BraceGroup):
1969 node = cast(BraceGroup, UP_node)
1970 status = self._ExecuteList(node.children)
1971
1972 elif case(command_e.AndOr):
1973 node = cast(command.AndOr, UP_node)
1974 status = self._DoAndOr(node, cmd_st)
1975
1976 elif case(command_e.If):
1977 node = cast(command.If, UP_node)
1978
1979 # Perf bug fix: loops might only execute 'if', but we still
1980 # need to GC
1981 self._LeafTick()
1982
1983 # No SetTokenForLine() because
1984 # - $LINENO can't appear directly in 'if'
1985 # - 'if' doesn't directly cause errors
1986 # It will be taken care of by command.Simple, condition, etc.
1987 status = self._DoIf(node)
1988
1989 elif case(command_e.Case):
1990 node = cast(command.Case, UP_node)
1991
1992 # Perf bug fix: loops might only execute 'case', but we still
1993 # need to GC
1994 self._LeafTick()
1995
1996 # Must set location for 'case $LINENO'
1997 self.mem.SetTokenForLine(node.case_kw)
1998 self._MaybeRunDebugTrap()
1999 status = self._DoCase(node)
2000
2001 elif case(command_e.WhileUntil):
2002 node = cast(command.WhileUntil, UP_node)
2003
2004 self.mem.SetTokenForLine(node.keyword)
2005 status = self._DoWhileUntil(node)
2006
2007 elif case(command_e.ForEach):
2008 node = cast(command.ForEach, UP_node)
2009
2010 self.mem.SetTokenForLine(node.keyword)
2011 status = self._DoForEach(node)
2012
2013 elif case(command_e.ForExpr):
2014 node = cast(command.ForExpr, UP_node)
2015
2016 self.mem.SetTokenForLine(node.keyword) # for x in $LINENO
2017 status = self._DoForExpr(node)
2018
2019 elif case(command_e.Redirect):
2020 node = cast(command.Redirect, UP_node)
2021
2022 # set -e affects redirect error, like mksh and bash 5.2, but unlike
2023 # dash/ash
2024 cmd_st.check_errexit = True
2025 status = self._DoRedirect(node, cmd_st)
2026
2027 elif case(command_e.Pipeline):
2028 node = cast(command.Pipeline, UP_node)
2029 status = self._DoPipeline(node, cmd_st)
2030
2031 elif case(command_e.Subshell):
2032 node = cast(command.Subshell, UP_node)
2033
2034 # This is a leaf from the parent process POV
2035 cmd_st.check_errexit = True
2036
2037 if node.is_last_cmd:
2038 # If the subshell is the last command in the process, just
2039 # run it in this process. See _MarkLastCommands().
2040 status = self._Execute(node.child)
2041 else:
2042 status = self.shell_ex.RunSubshell(node.child)
2043
2044 elif case(command_e.ShFunction):
2045 node = cast(command.ShFunction, UP_node)
2046 self._DoShFunction(node)
2047 status = 0
2048
2049 elif case(command_e.Proc):
2050 node = cast(Proc, UP_node)
2051 self._DoProc(node)
2052 status = 0
2053
2054 elif case(command_e.Func):
2055 node = cast(Func, UP_node)
2056
2057 # Needed for error, when the func is an existing variable name
2058 self.mem.SetTokenForLine(node.name)
2059
2060 self._DoFunc(node)
2061 status = 0
2062
2063 elif case(command_e.TimeBlock):
2064 node = cast(command.TimeBlock, UP_node)
2065 status = self._DoTimeBlock(node)
2066
2067 else:
2068 raise NotImplementedError(node.tag())
2069
2070 # Return to caller. Note the only case that didn't set it was Pipeline,
2071 # which set cmd_st.pipe_status.
2072 return status
2073
2074 def RunPendingTraps(self):
2075 # type: () -> None
2076
2077 trap_nodes = self.trap_state.GetPendingTraps()
2078 if trap_nodes is not None:
2079 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
2080 True):
2081 for trap_node in trap_nodes:
2082 with state.ctx_Registers(self.mem):
2083 # TODO: show trap kind in trace
2084 with dev.ctx_Tracer(self.tracer, 'trap', None):
2085 # Note: exit status is lost
2086 self._Execute(trap_node)
2087
2088 def RunPendingTrapsAndCatch(self):
2089 # type: () -> None
2090 """
2091 Like the above, but calls ExecuteAndCatch(), which may raise util.UserExit
2092 """
2093 trap_nodes = self.trap_state.GetPendingTraps()
2094 if trap_nodes is not None:
2095 with state.ctx_Option(self.mutable_opts, [option_i._running_trap],
2096 True):
2097 for trap_node in trap_nodes:
2098 with state.ctx_Registers(self.mem):
2099 # TODO: show trap kind in trace
2100 with dev.ctx_Tracer(self.tracer, 'trap', None):
2101 # Note: exit status is lost
2102 try:
2103 self.ExecuteAndCatch(trap_node, 0)
2104 except util.UserExit:
2105 # If user calls 'exit', stop running traps, but
2106 # we still run the EXIT trap later.
2107 break
2108
2109 def _Execute(self, node):
2110 # type: (command_t) -> int
2111 """Call _Dispatch(), and perform the errexit check."""
2112
2113 # Optimization: These 2 records have rarely-used lists, so we don't pass
2114 # alloc_lists=True. We create them on demand.
2115 cmd_st = CommandStatus.CreateNull()
2116 if len(self.status_array_pool):
2117 # Optimized to avoid allocs
2118 process_sub_st = self.status_array_pool.pop()
2119 else:
2120 process_sub_st = StatusArray.CreateNull()
2121
2122 with vm.ctx_ProcessSub(self.shell_ex, process_sub_st): # for wait()
2123 try:
2124 status = self._Dispatch(node, cmd_st)
2125 except error.WordFailure as e: # e.g. echo hi > ${!undef}
2126 if not e.HasLocation(): # Last resort!
2127 e.location = self.mem.GetFallbackLocation()
2128 self.errfmt.PrettyPrintError(e)
2129 status = 1 # another redirect word eval error
2130 cmd_st.check_errexit = True # errexit for e.g. a=${!undef}
2131
2132 # Now we've waited for process subs
2133
2134 # If it was a real pipeline, compute status from ${PIPESTATUS[@]} aka
2135 # @_pipeline_status
2136 pipe_status = cmd_st.pipe_status
2137 # Note: bash/mksh set PIPESTATUS set even on non-pipelines. This
2138 # makes it annoying to check both _process_sub_status and
2139 # _pipeline_status
2140
2141 errexit_loc = loc.Missing # type: loc_t
2142 if pipe_status is not None:
2143 # Tricky: _DoPipeline sets cmt_st.pipe_status and returns -1
2144 # for a REAL pipeline (but not singleton pipelines)
2145 assert status == -1, (
2146 "Shouldn't have redir errors when PIPESTATUS (status = %d)" %
2147 status)
2148
2149 self.mem.SetPipeStatus(pipe_status)
2150
2151 if self.exec_opts.pipefail():
2152 # The status is that of the last command that is non-zero.
2153 status = 0
2154 for i, st in enumerate(pipe_status):
2155 if st != 0:
2156 status = st
2157 errexit_loc = cmd_st.pipe_locs[i]
2158 else:
2159 # The status is that of last command, period.
2160 status = pipe_status[-1]
2161
2162 if cmd_st.pipe_negated:
2163 status = 1 if status == 0 else 0
2164
2165 # Compute status from _process_sub_status
2166 if process_sub_st.codes is None:
2167 # Optimized to avoid allocs
2168 self.status_array_pool.append(process_sub_st)
2169 else:
2170 codes = process_sub_st.codes
2171 self.mem.SetProcessSubStatus(codes)
2172 if status == 0 and self.exec_opts.process_sub_fail():
2173 # Choose the LAST non-zero status, consistent with pipefail above.
2174 for i, st in enumerate(codes):
2175 if st != 0:
2176 status = st
2177 errexit_loc = process_sub_st.locs[i]
2178
2179 self.mem.SetLastStatus(status)
2180
2181 # NOTE: Bash says that 'set -e' checking is done after each 'pipeline'.
2182 # However, any bash construct can appear in a pipeline. So it's easier
2183 # just to put it at the end, instead of after every node.
2184 #
2185 # Possible exceptions:
2186 # - function def (however this always exits 0 anyway)
2187 # - assignment - its result should be the result of the RHS?
2188 # - e.g. arith sub, command sub? I don't want arith sub.
2189 # - ControlFlow: always raises, it has no status.
2190 if cmd_st.check_errexit:
2191 #log('cmd_st %s', cmd_st)
2192 self._CheckStatus(status, cmd_st, node, errexit_loc)
2193
2194 return status
2195
2196 def _ExecuteList(self, children):
2197 # type: (List[command_t]) -> int
2198 status = 0 # for empty list
2199 for child in children:
2200 # last status wins
2201 status = self._Execute(child)
2202 return status
2203
2204 def LastStatus(self):
2205 # type: () -> int
2206 """For main_loop.py to determine the exit code of the shell itself."""
2207 return self.mem.LastStatus()
2208
2209 def _MarkLastCommands(self, node):
2210 # type: (command_t) -> None
2211
2212 if 0:
2213 log('optimizing')
2214 node.PrettyPrint(sys.stderr)
2215 log('')
2216
2217 UP_node = node
2218 with tagswitch(node) as case:
2219 if case(command_e.Simple):
2220 node = cast(command.Simple, UP_node)
2221 node.is_last_cmd = True
2222 if 0:
2223 log('Simple optimized')
2224
2225 elif case(command_e.Subshell):
2226 node = cast(command.Subshell, UP_node)
2227 # Mark ourselves as the last
2228 node.is_last_cmd = True
2229
2230 # Also mark 'date' as the last one
2231 # echo 1; (echo 2; date)
2232 self._MarkLastCommands(node.child)
2233
2234 elif case(command_e.Pipeline):
2235 node = cast(command.Pipeline, UP_node)
2236 # Bug fix: if we change the status, we can't exec the last
2237 # element!
2238 if node.negated is None and not self.exec_opts.pipefail():
2239 self._MarkLastCommands(node.children[-1])
2240
2241 elif case(command_e.Sentence):
2242 node = cast(command.Sentence, UP_node)
2243 self._MarkLastCommands(node.child)
2244
2245 elif case(command_e.Redirect):
2246 node = cast(command.Sentence, UP_node)
2247 # Don't need to restore the redirect in any of these cases:
2248
2249 # bin/osh -c 'echo hi 2>stderr'
2250 # bin/osh -c '{ echo hi; date; } 2>stderr'
2251 # echo hi 2>stderr | wc -l
2252
2253 self._MarkLastCommands(node.child)
2254
2255 elif case(command_e.CommandList):
2256 # Subshells often have a CommandList child
2257 node = cast(command.CommandList, UP_node)
2258 self._MarkLastCommands(node.children[-1])
2259
2260 elif case(command_e.BraceGroup):
2261 # TODO: What about redirects?
2262 node = cast(BraceGroup, UP_node)
2263 self._MarkLastCommands(node.children[-1])
2264
2265 def _RemoveSubshells(self, node):
2266 # type: (command_t) -> command_t
2267 """Eliminate redundant subshells like ( echo hi ) | wc -l etc.
2268
2269 This is ONLY called at the top level of ExecuteAndCatch() - it wouldn't
2270 be correct otherwise.
2271 """
2272 UP_node = node
2273 with tagswitch(node) as case:
2274 if case(command_e.Subshell):
2275 node = cast(command.Subshell, UP_node)
2276 # Optimize ( ( date ) ) etc.
2277 return self._RemoveSubshells(node.child)
2278 return node
2279
2280 def ExecuteAndCatch(self, node, cmd_flags):
2281 # type: (command_t, int) -> Tuple[bool, bool]
2282 """Execute a subprogram, handling vm.IntControlFlow and fatal exceptions.
2283
2284 Args:
2285 node: LST subtree
2286 optimize: Whether to exec the last process rather than fork/exec
2287
2288 Returns:
2289 TODO: use enum 'why' instead of the 2 booleans
2290
2291 Used by
2292 - main_loop.py.
2293 - SubProgramThunk for pipelines, subshell, command sub, process sub
2294 - TODO: Signals besides EXIT trap
2295
2296 Note: To do what optimize does, dash has EV_EXIT flag and yash has a
2297 finally_exit boolean. We use a different algorithm.
2298 """
2299 if cmd_flags & OptimizeSubshells:
2300 node = self._RemoveSubshells(node)
2301
2302 if cmd_flags & MarkLastCommands:
2303 # Mark the last command in each process, so we may avoid forks
2304 self._MarkLastCommands(node)
2305
2306 if 0:
2307 log('after opt:')
2308 node.PrettyPrint()
2309 log('')
2310
2311 is_return = False
2312 is_fatal = False
2313 is_errexit = False
2314
2315 err = None # type: error.FatalRuntime
2316 status = -1 # uninitialized
2317
2318 try:
2319 options = [] # type: List[int]
2320 if cmd_flags & NoDebugTrap:
2321 options.append(option_i._no_debug_trap)
2322 if cmd_flags & NoErrTrap:
2323 options.append(option_i._no_err_trap)
2324 with state.ctx_Option(self.mutable_opts, options, True):
2325 status = self._Execute(node)
2326 except vm.IntControlFlow as e:
2327 if cmd_flags & RaiseControlFlow:
2328 raise # 'eval break' and 'source return.sh', etc.
2329 else:
2330 # Return at top level is OK, unlike in bash.
2331 if e.IsReturn():
2332 is_return = True
2333 status = e.StatusCode()
2334 else:
2335 # TODO: This error message is invalid. It can also happen
2336 # in eval. Perhaps we need a flag.
2337
2338 # Invalid control flow
2339 self.errfmt.Print_(
2340 "Loop and control flow can't be in different processes",
2341 blame_loc=e.Location())
2342 is_fatal = True
2343 # All shells exit 0 here. It could be hidden behind
2344 # strict_control_flow if the incompatibility causes problems.
2345 status = 1
2346 except error.Parse as e:
2347 self.dumper.MaybeRecord(self, e) # Do this before unwinding stack
2348 raise
2349 except error.ErrExit as e:
2350 err = e
2351 is_errexit = True
2352 except error.FatalRuntime as e:
2353 err = e
2354
2355 if err:
2356 status = err.ExitStatus()
2357
2358 is_fatal = True
2359 # Do this before unwinding stack
2360 self.dumper.MaybeRecord(self, err)
2361
2362 if not err.HasLocation(): # Last resort!
2363 #log('Missing location')
2364 err.location = self.mem.GetFallbackLocation()
2365 #log('%s', err.location)
2366
2367 if is_errexit:
2368 if self.exec_opts.verbose_errexit():
2369 self.errfmt.PrintErrExit(cast(error.ErrExit, err),
2370 posix.getpid())
2371 else:
2372 self.errfmt.PrettyPrintError(err, prefix='fatal: ')
2373
2374 assert status >= 0, 'Should have been initialized'
2375
2376 # Problem: We have no idea here if a SUBSHELL (or pipeline comment) already
2377 # created a crash dump. So we get 2 or more of them.
2378 self.dumper.MaybeDump(status)
2379
2380 self.mem.SetLastStatus(status)
2381 return is_return, is_fatal
2382
2383 def EvalCommandFrag(self, frag):
2384 # type: (command_t) -> int
2385 """For builtins to evaluate command args.
2386
2387 Many exceptions are raised.
2388
2389 Examples:
2390
2391 cd /tmp (; ; mycmd)
2392
2393 And:
2394 eval (mycmd)
2395 call _io->eval(mycmd)
2396
2397 (Should those be more like eval 'mystring'?)
2398 """
2399 return self._Execute(frag) # can raise FatalRuntimeError, etc.
2400
2401 if 0:
2402
2403 def EvalCommandClosure(self, cmd):
2404 # type: (value.Command) -> int
2405 frag = typed_args.GetCommandFrag(cmd)
2406 with state.ctx_EnclosedFrame(self.mem, cmd.captured_frame,
2407 cmd.module_frame):
2408 return self.EvalCommandFrag(frag)
2409
2410 def RunTrapsOnExit(self, mut_status):
2411 # type: (IntParamBox) -> None
2412 """If an EXIT trap handler exists, run it.
2413
2414 Only mutates the status if 'return' or 'exit'. This is odd behavior, but
2415 all bash/dash/mksh seem to agree on it. See cases in
2416 builtin-trap.test.sh.
2417
2418 Note: if we could easily modulo -1 % 256 == 255 here, then we could get rid
2419 of this awkward interface. But that's true in Python and not C!
2420
2421 Could use i & (n-1) == i & 255 because we have a power of 2.
2422 https://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
2423 """
2424 # This does not raise, even on 'exit', etc.
2425 self.RunPendingTrapsAndCatch()
2426
2427 node = self.trap_state.GetHook('EXIT') # type: command_t
2428 if node:
2429 # NOTE: Don't set option_i._running_trap, because that's for
2430 # RunPendingTraps() in the MAIN LOOP
2431 with dev.ctx_Tracer(self.tracer, 'trap EXIT', None):
2432 try:
2433 is_return, is_fatal = self.ExecuteAndCatch(node, 0)
2434 except util.UserExit as e: # explicit exit
2435 mut_status.i = e.status
2436 return
2437 if is_return: # explicit 'return' in the trap handler!
2438 mut_status.i = self.LastStatus()
2439
2440 def _MaybeRunDebugTrap(self):
2441 # type: () -> None
2442 """Run user-specified DEBUG code before certain commands."""
2443 node = self.trap_state.GetHook('DEBUG') # type: command_t
2444 if node is None:
2445 return
2446
2447 # Fix lastpipe / job control / DEBUG trap interaction
2448 if self.exec_opts._no_debug_trap():
2449 return
2450
2451 # Don't run recursively run traps, etc.
2452 if not self.mem.ShouldRunDebugTrap():
2453 return
2454
2455 # NOTE: Don't set option_i._running_trap, because that's for
2456 # RunPendingTraps() in the MAIN LOOP
2457
2458 with dev.ctx_Tracer(self.tracer, 'trap DEBUG', None):
2459 with state.ctx_Registers(self.mem): # prevent setting $? etc.
2460 # for SetTokenForLine $LINENO
2461 with state.ctx_DebugTrap(self.mem):
2462 # Don't catch util.UserExit, etc.
2463 self._Execute(node)
2464
2465 def _MaybeRunErrTrap(self):
2466 # type: () -> None
2467 """
2468 Run user-specified ERR code after checking the status of certain
2469 commands (pipelines)
2470 """
2471 node = self.trap_state.GetHook('ERR') # type: command_t
2472 if node is None:
2473 return
2474
2475 # ERR trap is only run for a whole pipeline, not its parts
2476 if self.exec_opts._no_err_trap():
2477 return
2478
2479 # Prevent infinite recursion
2480 if self.mem.running_err_trap:
2481 return
2482
2483 # "disabled errexit" rule
2484 if self.mutable_opts.ErrExitIsDisabled():
2485 return
2486
2487 # bash rule - affected by set -o errtrace
2488 if not self.exec_opts.errtrace() and self.mem.InsideFunction():
2489 return
2490
2491 # NOTE: Don't set option_i._running_trap, because that's for
2492 # RunPendingTraps() in the MAIN LOOP
2493
2494 # To make a better stack trace from vm.getDebugStack(), add the last
2495 # thing that failed, even if it's not a proc/func. This can be an
2496 # external command.
2497 #
2498 # TODO: this can lead to duplicate stack frames
2499 # - We might only want this if it's an external command?
2500 # - Or maybe we need a different trap to trigger stack traces ...
2501 self.mem.debug_stack.append(
2502 debug_frame.BeforeErrTrap(self.mem.token_for_line))
2503
2504 with dev.ctx_Tracer(self.tracer, 'trap ERR', None):
2505 # TODO:
2506 # - use debug_frame.Trap
2507 # - use the original location of the 'trap' command?
2508 # - combine ctx_Tracer and debug stack? They are similar
2509 #with state.ctx_EvalDebugFrame(self.mem, self.mem.token_for_line):
2510
2511 # In bash, the PIPESTATUS register leaks. See spec/builtin-trap-err.
2512 # So unlike other traps, we don't isolate registers.
2513 #with state.ctx_Registers(self.mem): # prevent setting $? etc.
2514
2515 with state.ctx_ErrTrap(self.mem):
2516 self._Execute(node)
2517
2518 def RunProc(self, proc, cmd_val):
2519 # type: (value.Proc, cmd_value.Argv) -> int
2520 """Run procs aka "shell functions".
2521
2522 For SimpleCommand and registered completion hooks.
2523 """
2524 sig = proc.sig
2525 if sig.tag() == proc_sig_e.Closed:
2526 # We're binding named params. User should use @rest. No 'shift'.
2527 proc_argv = [] # type: List[str]
2528 else:
2529 proc_argv = cmd_val.argv[1:]
2530
2531 # Hm this sets "$@". TODO: Set ARGV only
2532 with state.ctx_ProcCall(self.mem, self.mutable_opts, proc, proc_argv,
2533 cmd_val.arg_locs[0]):
2534 func_proc.BindProcArgs(proc, cmd_val, self.mem)
2535
2536 # Redirects still valid for functions.
2537 # Here doc causes a pipe and Process(SubProgramThunk).
2538 try:
2539 status = self._Execute(proc.body)
2540 except vm.IntControlFlow as e:
2541 if e.IsReturn():
2542 status = e.StatusCode()
2543 else:
2544 # break/continue used in the wrong place.
2545 e_die('Unexpected %r (in proc call)' % e.Keyword(),
2546 e.Location())
2547 except error.FatalRuntime as e:
2548 # Dump the stack before unwinding it
2549 self.dumper.MaybeRecord(self, e)
2550 raise
2551
2552 return status
2553
2554 def RunFuncForCompletion(self, proc, argv):
2555 # type: (value.Proc, List[str]) -> int
2556 """
2557 Args:
2558 argv: $1 $2 $3 ... not including $0
2559 """
2560 cmd_val = MakeBuiltinArgv(argv)
2561
2562 # TODO: Change this to run YSH procs and funcs too
2563 try:
2564 status = self.RunProc(proc, cmd_val)
2565 except error.FatalRuntime as e:
2566 self.errfmt.PrettyPrintError(e)
2567 status = e.ExitStatus()
2568 except vm.IntControlFlow as e:
2569 # shouldn't be able to exit the shell from a completion hook!
2570 # TODO: Avoid overwriting the prompt!
2571 self.errfmt.Print_('Attempted to exit from completion hook.',
2572 blame_loc=e.Location())
2573
2574 status = 1
2575 # NOTE: (IOError, OSError) are caught in completion.py:ReadlineCallback
2576 return status
2577
2578
2579# vim: sw=4