From d020d468465d7edda23afc5abb7eb2bd68acf95e Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Sun, 31 Oct 2021 14:09:11 +0330 Subject: [PATCH] Shell: Unwind execution after runtime errors This commit makes the Shell check for errors after a node is run(), and prevents further execution by unwinding until the error is cleared. Fixes #10649. --- Userland/Shell/AST.cpp | 104 ++++++++++++++++++++++++++++++++++++++++- Userland/Shell/Shell.h | 1 + 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/Userland/Shell/AST.cpp b/Userland/Shell/AST.cpp index 954254828c5..5608adb3a24 100644 --- a/Userland/Shell/AST.cpp +++ b/Userland/Shell/AST.cpp @@ -160,6 +160,9 @@ static String resolve_slices(RefPtr shell, String&& input_value, NonnullR for (auto& slice : slices) { auto value = slice.run(shell); + if (shell && shell->has_any_error()) + break; + if (!value) { shell->raise_error(Shell::ShellError::InvalidSliceContentsError, "Invalid slice contents", slice.position()); return move(input_value); @@ -207,6 +210,9 @@ static Vector resolve_slices(RefPtr shell, Vector&& value for (auto& slice : slices) { auto value = slice.run(shell); + if (shell && shell->has_any_error()) + break; + if (!value) { shell->raise_error(Shell::ShellError::InvalidSliceContentsError, "Invalid slice contents", slice.position()); return move(values); @@ -270,6 +276,9 @@ bool Node::is_syntax_error() const void Node::for_each_entry(RefPtr shell, Function)> callback) { auto value = run(shell)->resolve_without_cast(shell); + if (shell && shell->has_any_error()) + return; + if (value->is_job()) { callback(value); return; @@ -389,6 +398,9 @@ void And::dump(int level) const RefPtr And::run(RefPtr shell) { auto commands = m_left->to_lazy_evaluated_commands(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + commands.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::And }); return make_ref_counted(move(commands)); } @@ -443,11 +455,15 @@ RefPtr ListConcatenate::run(RefPtr shell) RefPtr result = nullptr; for (auto& element : m_list) { + if (shell && shell->has_any_error()) + break; if (!result) { result = make_ref_counted({ element->run(shell)->resolve_without_cast(shell) }); continue; } auto element_value = element->run(shell)->resolve_without_cast(shell); + if (shell && shell->has_any_error()) + break; if (result->is_command() || element_value->is_command()) { auto joined_commands = join_commands(result->resolve_as_commands(shell), element_value->resolve_as_commands(shell)); @@ -484,6 +500,8 @@ void ListConcatenate::for_each_entry(RefPtr shell, Functionrun(shell); + if (shell && shell->has_any_error()) + break; if (!value) continue; if (callback(value.release_nonnull()) == IterationDecision::Break) @@ -646,6 +664,8 @@ RefPtr BraceExpansion::run(RefPtr shell) { NonnullRefPtrVector values; for (auto& entry : m_entries) { + if (shell && shell->has_any_error()) + break; auto value = entry.run(shell); if (value) values.append(value.release_nonnull()); @@ -704,6 +724,9 @@ RefPtr CastToCommand::run(RefPtr shell) return m_inner->run(shell); auto value = m_inner->run(shell)->resolve_without_cast(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + if (value->is_command()) return value; @@ -771,6 +794,8 @@ RefPtr CastToList::run(RefPtr shell) return make_ref_counted({}); auto inner_value = m_inner->run(shell)->resolve_without_cast(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); if (inner_value->is_command() || inner_value->is_list()) return inner_value; @@ -972,6 +997,9 @@ void DynamicEvaluate::dump(int level) const RefPtr DynamicEvaluate::run(RefPtr shell) { auto result = m_inner->run(shell)->resolve_without_cast(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + // Dynamic Evaluation behaves differently between strings and lists. // Strings are treated as variables, and Lists are treated as commands. if (result->is_string()) { @@ -1190,6 +1218,9 @@ RefPtr ForLoop::run(RefPtr shell) if (consecutive_interruptions == 2) return IterationDecision::Break; + if (shell && shell->has_any_error()) + return IterationDecision::Break; + RefPtr block_value; { @@ -1208,6 +1239,9 @@ RefPtr ForLoop::run(RefPtr shell) }); } else { for (;;) { + if (shell && shell->has_any_error()) + break; + if (consecutive_interruptions == 2) break; @@ -1328,6 +1362,9 @@ RefPtr Heredoc::run(RefPtr shell) // To deindent, first split to lines... auto value = m_contents->run(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + if (!value) return value; auto list = value->resolve_as_list(shell); @@ -1547,7 +1584,11 @@ void Execute::for_each_entry(RefPtr shell, Functionwould_execute()) return m_command->for_each_entry(shell, move(callback)); - auto commands = shell->expand_aliases(m_command->run(shell)->resolve_as_commands(shell)); + auto unexpanded_commands = m_command->run(shell)->resolve_as_commands(shell); + if (shell && shell->has_any_error()) + return; + + auto commands = shell->expand_aliases(move(unexpanded_commands)); if (m_capture_stdout) { // Make sure that we're going to be running _something_. @@ -1717,6 +1758,9 @@ void Execute::for_each_entry(RefPtr shell, Function Execute::run(RefPtr shell) { + if (shell && shell->has_any_error()) + return make_ref_counted({}); + if (m_command->would_execute()) return m_command->run(shell); @@ -1798,6 +1842,9 @@ void IfCond::dump(int level) const RefPtr IfCond::run(RefPtr shell) { auto cond = m_condition->run(shell)->resolve_without_cast(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + // The condition could be a builtin, in which case it has already run and exited. if (cond->is_job()) { auto cond_job_value = static_cast(cond.ptr()); @@ -1985,7 +2032,12 @@ void Join::dump(int level) const RefPtr Join::run(RefPtr shell) { auto left = m_left->to_lazy_evaluated_commands(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + auto right = m_right->to_lazy_evaluated_commands(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); return make_ref_counted(join_commands(move(left), move(right))); } @@ -2067,6 +2119,9 @@ void MatchExpr::dump(int level) const RefPtr MatchExpr::run(RefPtr shell) { auto value = m_matched_expr->run(shell)->resolve_without_cast(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + auto list = value->resolve_as_list(shell); auto list_matches = [&](auto&& pattern, auto& spans) { @@ -2092,6 +2147,9 @@ RefPtr MatchExpr::run(RefPtr shell) pattern.append(static_cast(&option)->text()); } else { auto list = option.run(shell); + if (shell && shell->has_any_error()) + return pattern; + option.for_each_entry(shell, [&](auto&& value) { pattern.extend(value->resolve_as_list(nullptr)); // Note: 'nullptr' incurs special behavior, // asking the node for a 'raw' value. @@ -2209,6 +2267,9 @@ void Or::dump(int level) const RefPtr Or::run(RefPtr shell) { auto commands = m_left->to_lazy_evaluated_commands(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + commands.last().next_chain.empend(*m_right, NodeWithAction::Or); return make_ref_counted(move(commands)); } @@ -2260,7 +2321,12 @@ void Pipe::dump(int level) const RefPtr Pipe::run(RefPtr shell) { auto left = m_left->to_lazy_evaluated_commands(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + auto right = m_right->to_lazy_evaluated_commands(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); auto last_in_left = left.take_last(); auto first_in_right = right.take_first(); @@ -2464,7 +2530,13 @@ RefPtr Range::run(RefPtr shell) }; auto start_value = m_start->run(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + auto end_value = m_end->run(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + if (!start_value || !end_value) return make_ref_counted({}); @@ -2523,6 +2595,9 @@ RefPtr ReadRedirection::run(RefPtr shell) { Command command; auto path_segments = m_path->run(shell)->resolve_as_list(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + StringBuilder builder; builder.join(" ", path_segments); @@ -2550,6 +2625,9 @@ RefPtr ReadWriteRedirection::run(RefPtr shell) { Command command; auto path_segments = m_path->run(shell)->resolve_as_list(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + StringBuilder builder; builder.join(" ", path_segments); @@ -2578,6 +2656,8 @@ RefPtr Sequence::run(RefPtr shell) Vector all_commands; Command* last_command_in_sequence = nullptr; for (auto& entry : m_entries) { + if (shell && shell->has_any_error()) + break; if (!last_command_in_sequence) { auto commands = entry.to_lazy_evaluated_commands(shell); all_commands.extend(move(commands)); @@ -2839,7 +2919,12 @@ void Juxtaposition::dump(int level) const RefPtr Juxtaposition::run(RefPtr shell) { auto left_value = m_left->run(shell)->resolve_without_cast(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + auto right_value = m_right->run(shell)->resolve_without_cast(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); auto left = left_value->resolve_as_list(shell); auto right = right_value->resolve_as_list(shell); @@ -2998,7 +3083,12 @@ void StringPartCompose::dump(int level) const RefPtr StringPartCompose::run(RefPtr shell) { auto left = m_left->run(shell)->resolve_as_list(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + auto right = m_right->run(shell)->resolve_as_list(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); StringBuilder builder; builder.join(" ", left); @@ -3161,6 +3251,9 @@ RefPtr WriteAppendRedirection::run(RefPtr shell) { Command command; auto path_segments = m_path->run(shell)->resolve_as_list(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + StringBuilder builder; builder.join(" ", path_segments); @@ -3188,6 +3281,9 @@ RefPtr WriteRedirection::run(RefPtr shell) { Command command; auto path_segments = m_path->run(shell)->resolve_as_list(shell); + if (shell && shell->has_any_error()) + return make_ref_counted({}); + StringBuilder builder; builder.join(" ", path_segments); @@ -3218,9 +3314,15 @@ RefPtr VariableDeclarations::run(RefPtr shell) { for (auto& var : m_variables) { auto name_value = var.name->run(shell)->resolve_as_list(shell); + if (shell && shell->has_any_error()) + break; + VERIFY(name_value.size() == 1); auto name = name_value[0]; auto value = var.value->run(shell); + if (shell && shell->has_any_error()) + break; + shell->set_local_variable(name, value.release_nonnull()); } diff --git a/Userland/Shell/Shell.h b/Userland/Shell/Shell.h index 9a19a76b86e..ec6ed6e3993 100644 --- a/Userland/Shell/Shell.h +++ b/Userland/Shell/Shell.h @@ -244,6 +244,7 @@ public: m_source_position.value().position = position.release_value(); } bool has_error(ShellError err) const { return m_error == err; } + bool has_any_error() const { return !has_error(ShellError::None); } const String& error_description() const { return m_error_description; } ShellError take_error() {