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

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