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

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