1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 19:07:35 +00:00

Shell: Handle (most) errors in the parsers

This turns all errors into either "OOM" or a proper shell error (if
propagation is impossible or meaningless).
Fixes `echo -en '\xfe\x4a' | $SHELL` crashing.
This commit is contained in:
Ali Mohammad Pur 2023-06-30 01:24:52 +03:30 committed by Ali Mohammad Pur
parent 3f022f4040
commit 1c1aa2c0d0
2 changed files with 200 additions and 124 deletions

View file

@ -9,6 +9,20 @@
#include <AK/StringUtils.h>
#include <Shell/PosixParser.h>
#define TRY_OR_THROW_PARSE_ERROR_AT(expr, position) ({ \
/* Ignore -Wshadow to allow nesting the macro. */ \
AK_IGNORE_DIAGNOSTIC("-Wshadow", \
auto&& _value_or_error = expr;) \
if (_value_or_error.is_error()) { \
AK_IGNORE_DIAGNOSTIC("-Wshadow", \
auto _error = _value_or_error.release_error();) \
if (_error.is_errno() && _error.code() == ENOMEM) \
return make_ref_counted<AST::SyntaxError>(position, "OOM"_short_string); \
return make_ref_counted<AST::SyntaxError>(position, MUST(String::formatted("Error: {}", _error))); \
} \
_value_or_error.release_value(); \
})
static Shell::AST::Position empty_position()
{
return { 0, 0, { 0, 0 }, { 0, 0 } };
@ -132,7 +146,8 @@ ErrorOr<void> Parser::fill_token_buffer(Optional<Reduction> starting_reduction)
RefPtr<AST::Node> Parser::parse()
{
return parse_complete_command().release_value_but_fixme_should_propagate_errors();
auto start_position = peek().position.value_or(empty_position());
return TRY_OR_THROW_PARSE_ERROR_AT(parse_complete_command(), start_position);
}
void Parser::handle_heredoc_contents()
@ -147,16 +162,26 @@ void Parser::handle_heredoc_contents()
auto& heredoc = **entry;
RefPtr<AST::Node> contents;
if (heredoc.allow_interpolation()) {
Parser parser { token.value, m_in_interactive_mode, Reduction::HeredocContents };
contents = parser.parse_word().release_value_but_fixme_should_propagate_errors();
} else {
contents = make_ref_counted<AST::StringLiteral>(token.position.value_or(empty_position()), String::from_utf8(token.value).release_value_but_fixme_should_propagate_errors(), AST::StringLiteral::EnclosureType::None);
auto contents_or_error = [&]() -> ErrorOr<RefPtr<AST::Node>> {
if (heredoc.allow_interpolation()) {
Parser parser { token.value, m_in_interactive_mode, Reduction::HeredocContents };
return parser.parse_word();
}
return make_ref_counted<AST::StringLiteral>(
token.position.value_or(empty_position()),
TRY(String::from_utf8(token.value)),
AST::StringLiteral::EnclosureType::None);
}();
if (contents_or_error.is_error()) {
warnln("Shell: Failed to parse heredoc contents: {}", contents_or_error.error());
continue;
}
if (contents)
if (auto contents = contents_or_error.release_value())
heredoc.set_contents(contents);
m_unprocessed_heredoc_entries.remove(*token.relevant_heredoc_key);
}
}
@ -639,7 +664,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_complete_command()
auto position = peek().position;
auto syntax_error = make_ref_counted<AST::SyntaxError>(
position.value_or(empty_position()),
"Extra tokens after complete command"_string.release_value_but_fixme_should_propagate_errors());
TRY("Extra tokens after complete command"_string));
if (list)
list->set_is_syntax_error(*syntax_error);
@ -836,7 +861,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_function_definition()
return make_ref_counted<AST::FunctionDeclaration>(
name.position.value_or(empty_position()).with_end(peek().position.value_or(empty_position())),
AST::NameWithPosition { String::from_utf8(name.value).release_value_but_fixme_should_propagate_errors(), name.position.value_or(empty_position()) },
AST::NameWithPosition { TRY(String::from_utf8(name.value)), name.position.value_or(empty_position()) },
Vector<AST::NameWithPosition> {},
body.release_nonnull());
}
@ -920,13 +945,13 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_while_clause()
if (!condition)
condition = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
"Expected condition after 'while'"_string.release_value_but_fixme_should_propagate_errors());
TRY("Expected condition after 'while'"_string));
auto do_group = TRY(parse_do_group());
if (!do_group)
do_group = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
"Expected 'do' after 'while'"_string.release_value_but_fixme_should_propagate_errors());
TRY("Expected 'do' after 'while'"_string));
// while foo; bar -> loop { if foo { bar } else { break } }
auto position = start_position.with_end(peek().position.value_or(empty_position()));
@ -956,13 +981,13 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_until_clause()
if (!condition)
condition = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
"Expected condition after 'until'"_string.release_value_but_fixme_should_propagate_errors());
TRY("Expected condition after 'until'"_string));
auto do_group = TRY(parse_do_group());
if (!do_group)
do_group = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
"Expected 'do' after 'until'"_string.release_value_but_fixme_should_propagate_errors());
TRY("Expected 'do' after 'until'"_string));
// until foo; bar -> loop { if foo { break } else { bar } }
auto position = start_position.with_end(peek().position.value_or(empty_position()));
@ -995,7 +1020,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_brace_group()
if (peek().type != Token::Type::CloseBrace) {
error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected '}}', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected '}}', not {}", peek().type_name())));
} else {
consume();
}
@ -1023,12 +1048,12 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_case_clause()
if (!expr)
expr = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected a word, not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected a word, not {}", peek().type_name())));
if (peek().type != Token::Type::In) {
syntax_error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected 'in', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected 'in', not {}", peek().type_name())));
} else {
skip();
}
@ -1062,7 +1087,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_case_clause()
if (!syntax_error)
syntax_error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected ')', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected ')', not {}", peek().type_name())));
break;
}
@ -1077,7 +1102,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_case_clause()
if (!syntax_error)
syntax_error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected ';;', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected ';;', not {}", peek().type_name())));
}
if (syntax_error) {
@ -1101,7 +1126,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_case_clause()
if (peek().type != Token::Type::Esac) {
syntax_error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected 'esac', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected 'esac', not {}", peek().type_name())));
} else {
skip();
}
@ -1136,7 +1161,7 @@ ErrorOr<Parser::CaseItemsResult> Parser::parse_case_list()
if (!node)
node = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected a word, not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected a word, not {}", peek().type_name())));
nodes.append(node.release_nonnull());
@ -1151,7 +1176,7 @@ ErrorOr<Parser::CaseItemsResult> Parser::parse_case_list()
if (nodes.is_empty())
nodes.append(make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected a word, not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors()));
TRY(String::formatted("Expected a word, not {}", peek().type_name()))));
return CaseItemsResult { move(pipes), move(nodes) };
}
@ -1166,20 +1191,20 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_if_clause()
skip();
auto main_condition = TRY(parse_compound_list());
if (!main_condition)
main_condition = make_ref_counted<AST::SyntaxError>(empty_position(), "Expected compound list after 'if'"_string.release_value_but_fixme_should_propagate_errors());
main_condition = make_ref_counted<AST::SyntaxError>(empty_position(), TRY("Expected compound list after 'if'"_string));
RefPtr<AST::SyntaxError> syntax_error;
if (peek().type != Token::Type::Then) {
syntax_error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected 'then', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected 'then', not {}", peek().type_name())));
} else {
skip();
}
auto main_consequence = TRY(parse_compound_list());
if (!main_consequence)
main_consequence = make_ref_counted<AST::SyntaxError>(empty_position(), "Expected compound list after 'then'"_string.release_value_but_fixme_should_propagate_errors());
main_consequence = make_ref_counted<AST::SyntaxError>(empty_position(), TRY("Expected compound list after 'then'"_string));
auto node = make_ref_counted<AST::IfCond>(start_position, Optional<AST::Position>(), main_condition.release_nonnull(), main_consequence.release_nonnull(), nullptr);
auto active_node = node;
@ -1188,20 +1213,20 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_if_clause()
skip();
auto condition = TRY(parse_compound_list());
if (!condition)
condition = make_ref_counted<AST::SyntaxError>(empty_position(), "Expected compound list after 'elif'"_string.release_value_but_fixme_should_propagate_errors());
condition = make_ref_counted<AST::SyntaxError>(empty_position(), TRY("Expected compound list after 'elif'"_string));
if (peek().type != Token::Type::Then) {
if (!syntax_error)
syntax_error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected 'then', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected 'then', not {}", peek().type_name())));
} else {
skip();
}
auto consequence = TRY(parse_compound_list());
if (!consequence)
consequence = make_ref_counted<AST::SyntaxError>(empty_position(), "Expected compound list after 'then'"_string.release_value_but_fixme_should_propagate_errors());
consequence = make_ref_counted<AST::SyntaxError>(empty_position(), TRY("Expected compound list after 'then'"_string));
auto new_node = make_ref_counted<AST::IfCond>(start_position, Optional<AST::Position>(), condition.release_nonnull(), consequence.release_nonnull(), nullptr);
@ -1215,7 +1240,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_if_clause()
skip();
active_node->false_branch() = TRY(parse_compound_list());
if (!active_node->false_branch())
active_node->false_branch() = make_ref_counted<AST::SyntaxError>(empty_position(), "Expected compound list after 'else'"_string.release_value_but_fixme_should_propagate_errors());
active_node->false_branch() = make_ref_counted<AST::SyntaxError>(empty_position(), TRY("Expected compound list after 'else'"_string));
break;
case Token::Type::Fi:
skip();
@ -1225,7 +1250,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_if_clause()
if (!syntax_error)
syntax_error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected 'else' or 'fi', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected 'else' or 'fi', not {}", peek().type_name())));
break;
}
@ -1234,7 +1259,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_if_clause()
if (!syntax_error)
syntax_error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected 'fi', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected 'fi', not {}", peek().type_name())));
} else {
skip();
}
@ -1257,10 +1282,10 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_subshell()
auto list = TRY(parse_compound_list());
if (!list)
error = make_ref_counted<AST::SyntaxError>(peek().position.value_or(empty_position()), "Expected compound list after ("_string.release_value_but_fixme_should_propagate_errors());
error = make_ref_counted<AST::SyntaxError>(peek().position.value_or(empty_position()), TRY("Expected compound list after ("_string));
if (peek().type != Token::Type::CloseParen)
error = make_ref_counted<AST::SyntaxError>(peek().position.value_or(empty_position()), "Expected ) after compound list"_string.release_value_but_fixme_should_propagate_errors());
error = make_ref_counted<AST::SyntaxError>(peek().position.value_or(empty_position()), TRY("Expected ) after compound list"_string));
else
skip();
@ -1392,7 +1417,7 @@ RefPtr<AST::Node> Parser::parse_word_list()
auto start_position = peek().position.value_or(empty_position());
for (; peek().type == Token::Type::Word;) {
auto word = parse_word().release_value_but_fixme_should_propagate_errors();
auto word = TRY_OR_THROW_PARSE_ERROR_AT(parse_word(), start_position);
nodes.append(word.release_nonnull());
}
@ -1809,7 +1834,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_do_group()
if (peek().type != Token::Type::Do) {
return make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected 'do', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected 'do', not {}", peek().type_name())));
}
consume();
@ -1820,7 +1845,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_do_group()
if (peek().type != Token::Type::Done) {
error = make_ref_counted<AST::SyntaxError>(
peek().position.value_or(empty_position()),
String::formatted("Expected 'done', not {}", peek().type_name()).release_value_but_fixme_should_propagate_errors());
TRY(String::formatted("Expected 'done', not {}", peek().type_name())));
} else {
consume();
}
@ -1986,7 +2011,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_io_here(AST::Position start_position, O
auto end_keyword = consume();
if (!is_one_of(end_keyword.type, Token::Type::Word, Token::Type::Token))
return make_ref_counted<AST::SyntaxError>(io_operator_token.position.value_or(start_position), "Expected a heredoc keyword"_string.release_value_but_fixme_should_propagate_errors(), true);
return make_ref_counted<AST::SyntaxError>(io_operator_token.position.value_or(start_position), TRY("Expected a heredoc keyword"_string), true);
auto [end_keyword_text, allow_interpolation] = Lexer::process_heredoc_key(end_keyword);
RefPtr<AST::SyntaxError> error;