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.
This commit is contained in:
Ali Mohammad Pur 2021-10-31 14:09:11 +03:30 committed by Andreas Kling
parent 8f332ac6a3
commit d020d46846
Notes: sideshowbarker 2024-07-18 01:43:17 +09:00
2 changed files with 104 additions and 1 deletions

View file

@ -160,6 +160,9 @@ static String resolve_slices(RefPtr<Shell> 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<String> resolve_slices(RefPtr<Shell> shell, Vector<String>&& 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> shell, Function<IterationDecision(NonnullRefPtr<Value>)> 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<Value> And::run(RefPtr<Shell> shell)
{
auto commands = m_left->to_lazy_evaluated_commands(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
commands.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::And });
return make_ref_counted<CommandSequenceValue>(move(commands));
}
@ -443,11 +455,15 @@ RefPtr<Value> ListConcatenate::run(RefPtr<Shell> shell)
RefPtr<Value> result = nullptr;
for (auto& element : m_list) {
if (shell && shell->has_any_error())
break;
if (!result) {
result = make_ref_counted<ListValue>({ 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> shell, Function<IterationDeci
{
for (auto& entry : m_list) {
auto value = entry->run(shell);
if (shell && shell->has_any_error())
break;
if (!value)
continue;
if (callback(value.release_nonnull()) == IterationDecision::Break)
@ -646,6 +664,8 @@ RefPtr<Value> BraceExpansion::run(RefPtr<Shell> shell)
{
NonnullRefPtrVector<Value> 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<Value> CastToCommand::run(RefPtr<Shell> 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<ListValue>({});
if (value->is_command())
return value;
@ -771,6 +794,8 @@ RefPtr<Value> CastToList::run(RefPtr<Shell> shell)
return make_ref_counted<ListValue>({});
auto inner_value = m_inner->run(shell)->resolve_without_cast(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
if (inner_value->is_command() || inner_value->is_list())
return inner_value;
@ -972,6 +997,9 @@ void DynamicEvaluate::dump(int level) const
RefPtr<Value> DynamicEvaluate::run(RefPtr<Shell> shell)
{
auto result = m_inner->run(shell)->resolve_without_cast(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
// 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<Value> ForLoop::run(RefPtr<Shell> shell)
if (consecutive_interruptions == 2)
return IterationDecision::Break;
if (shell && shell->has_any_error())
return IterationDecision::Break;
RefPtr<Value> block_value;
{
@ -1208,6 +1239,9 @@ RefPtr<Value> ForLoop::run(RefPtr<Shell> shell)
});
} else {
for (;;) {
if (shell && shell->has_any_error())
break;
if (consecutive_interruptions == 2)
break;
@ -1328,6 +1362,9 @@ RefPtr<Value> Heredoc::run(RefPtr<Shell> shell)
// To deindent, first split to lines...
auto value = m_contents->run(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
if (!value)
return value;
auto list = value->resolve_as_list(shell);
@ -1547,7 +1584,11 @@ void Execute::for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(Non
if (m_command->would_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> shell, Function<IterationDecision(Non
RefPtr<Value> Execute::run(RefPtr<Shell> shell)
{
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
if (m_command->would_execute())
return m_command->run(shell);
@ -1798,6 +1842,9 @@ void IfCond::dump(int level) const
RefPtr<Value> IfCond::run(RefPtr<Shell> shell)
{
auto cond = m_condition->run(shell)->resolve_without_cast(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
// 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<const JobValue*>(cond.ptr());
@ -1985,7 +2032,12 @@ void Join::dump(int level) const
RefPtr<Value> Join::run(RefPtr<Shell> shell)
{
auto left = m_left->to_lazy_evaluated_commands(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
auto right = m_right->to_lazy_evaluated_commands(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
return make_ref_counted<CommandSequenceValue>(join_commands(move(left), move(right)));
}
@ -2067,6 +2119,9 @@ void MatchExpr::dump(int level) const
RefPtr<Value> MatchExpr::run(RefPtr<Shell> shell)
{
auto value = m_matched_expr->run(shell)->resolve_without_cast(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
auto list = value->resolve_as_list(shell);
auto list_matches = [&](auto&& pattern, auto& spans) {
@ -2092,6 +2147,9 @@ RefPtr<Value> MatchExpr::run(RefPtr<Shell> shell)
pattern.append(static_cast<const BarewordLiteral*>(&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<Value> Or::run(RefPtr<Shell> shell)
{
auto commands = m_left->to_lazy_evaluated_commands(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
commands.last().next_chain.empend(*m_right, NodeWithAction::Or);
return make_ref_counted<CommandSequenceValue>(move(commands));
}
@ -2260,7 +2321,12 @@ void Pipe::dump(int level) const
RefPtr<Value> Pipe::run(RefPtr<Shell> shell)
{
auto left = m_left->to_lazy_evaluated_commands(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
auto right = m_right->to_lazy_evaluated_commands(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
auto last_in_left = left.take_last();
auto first_in_right = right.take_first();
@ -2464,7 +2530,13 @@ RefPtr<Value> Range::run(RefPtr<Shell> shell)
};
auto start_value = m_start->run(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
auto end_value = m_end->run(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
if (!start_value || !end_value)
return make_ref_counted<ListValue>({});
@ -2523,6 +2595,9 @@ RefPtr<Value> ReadRedirection::run(RefPtr<Shell> shell)
{
Command command;
auto path_segments = m_path->run(shell)->resolve_as_list(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
StringBuilder builder;
builder.join(" ", path_segments);
@ -2550,6 +2625,9 @@ RefPtr<Value> ReadWriteRedirection::run(RefPtr<Shell> shell)
{
Command command;
auto path_segments = m_path->run(shell)->resolve_as_list(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
StringBuilder builder;
builder.join(" ", path_segments);
@ -2578,6 +2656,8 @@ RefPtr<Value> Sequence::run(RefPtr<Shell> shell)
Vector<Command> 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<Value> Juxtaposition::run(RefPtr<Shell> shell)
{
auto left_value = m_left->run(shell)->resolve_without_cast(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
auto right_value = m_right->run(shell)->resolve_without_cast(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
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<Value> StringPartCompose::run(RefPtr<Shell> shell)
{
auto left = m_left->run(shell)->resolve_as_list(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
auto right = m_right->run(shell)->resolve_as_list(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
StringBuilder builder;
builder.join(" ", left);
@ -3161,6 +3251,9 @@ RefPtr<Value> WriteAppendRedirection::run(RefPtr<Shell> shell)
{
Command command;
auto path_segments = m_path->run(shell)->resolve_as_list(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
StringBuilder builder;
builder.join(" ", path_segments);
@ -3188,6 +3281,9 @@ RefPtr<Value> WriteRedirection::run(RefPtr<Shell> shell)
{
Command command;
auto path_segments = m_path->run(shell)->resolve_as_list(shell);
if (shell && shell->has_any_error())
return make_ref_counted<ListValue>({});
StringBuilder builder;
builder.join(" ", path_segments);
@ -3218,9 +3314,15 @@ RefPtr<Value> VariableDeclarations::run(RefPtr<Shell> 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());
}

View file

@ -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()
{