mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-23 02:27:28 +08:00
Reduce copying in parse_util_detect_errors
Allow parse_util_detect_errors to accept an already-parsed ast. This eliminates a copy of the source, which is helpful when executing large scripts.
This commit is contained in:
parent
dfeec433d8
commit
487de1e6c3
|
@ -287,8 +287,7 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||||
if (condition && std::wcslen(condition)) {
|
if (condition && std::wcslen(condition)) {
|
||||||
const wcstring condition_string = condition;
|
const wcstring condition_string = condition;
|
||||||
parse_error_list_t errors;
|
parse_error_list_t errors;
|
||||||
if (parse_util_detect_errors(condition_string, &errors,
|
if (parse_util_detect_errors(condition_string, &errors)) {
|
||||||
false /* do not accept incomplete */)) {
|
|
||||||
streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", cmd,
|
streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", cmd,
|
||||||
condition);
|
condition);
|
||||||
for (const auto &error : errors) {
|
for (const auto &error : errors) {
|
||||||
|
|
|
@ -1001,117 +1001,119 @@ static parser_test_error_bits_t detect_argument_errors(const wcstring &src) {
|
||||||
static void test_parser() {
|
static void test_parser() {
|
||||||
say(L"Testing parser");
|
say(L"Testing parser");
|
||||||
|
|
||||||
auto parser = parser_t::principal_parser().shared();
|
auto detect_errors = [](const wcstring &s) {
|
||||||
|
return parse_util_detect_errors(s, nullptr, true /* accept incomplete */);
|
||||||
|
};
|
||||||
|
|
||||||
say(L"Testing block nesting");
|
say(L"Testing block nesting");
|
||||||
if (!parse_util_detect_errors(L"if; end")) {
|
if (!detect_errors(L"if; end")) {
|
||||||
err(L"Incomplete if statement undetected");
|
err(L"Incomplete if statement undetected");
|
||||||
}
|
}
|
||||||
if (!parse_util_detect_errors(L"if test; echo")) {
|
if (!detect_errors(L"if test; echo")) {
|
||||||
err(L"Missing end undetected");
|
err(L"Missing end undetected");
|
||||||
}
|
}
|
||||||
if (!parse_util_detect_errors(L"if test; end; end")) {
|
if (!detect_errors(L"if test; end; end")) {
|
||||||
err(L"Unbalanced end undetected");
|
err(L"Unbalanced end undetected");
|
||||||
}
|
}
|
||||||
|
|
||||||
say(L"Testing detection of invalid use of builtin commands");
|
say(L"Testing detection of invalid use of builtin commands");
|
||||||
if (!parse_util_detect_errors(L"case foo")) {
|
if (!detect_errors(L"case foo")) {
|
||||||
err(L"'case' command outside of block context undetected");
|
err(L"'case' command outside of block context undetected");
|
||||||
}
|
}
|
||||||
if (!parse_util_detect_errors(L"switch ggg; if true; case foo;end;end")) {
|
if (!detect_errors(L"switch ggg; if true; case foo;end;end")) {
|
||||||
err(L"'case' command outside of switch block context undetected");
|
err(L"'case' command outside of switch block context undetected");
|
||||||
}
|
}
|
||||||
if (!parse_util_detect_errors(L"else")) {
|
if (!detect_errors(L"else")) {
|
||||||
err(L"'else' command outside of conditional block context undetected");
|
err(L"'else' command outside of conditional block context undetected");
|
||||||
}
|
}
|
||||||
if (!parse_util_detect_errors(L"else if")) {
|
if (!detect_errors(L"else if")) {
|
||||||
err(L"'else if' command outside of conditional block context undetected");
|
err(L"'else if' command outside of conditional block context undetected");
|
||||||
}
|
}
|
||||||
if (!parse_util_detect_errors(L"if false; else if; end")) {
|
if (!detect_errors(L"if false; else if; end")) {
|
||||||
err(L"'else if' missing command undetected");
|
err(L"'else if' missing command undetected");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"break")) {
|
if (!detect_errors(L"break")) {
|
||||||
err(L"'break' command outside of loop block context undetected");
|
err(L"'break' command outside of loop block context undetected");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"break --help")) {
|
if (detect_errors(L"break --help")) {
|
||||||
err(L"'break --help' incorrectly marked as error");
|
err(L"'break --help' incorrectly marked as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"while false ; function foo ; break ; end ; end ")) {
|
if (!detect_errors(L"while false ; function foo ; break ; end ; end ")) {
|
||||||
err(L"'break' command inside function allowed to break from loop outside it");
|
err(L"'break' command inside function allowed to break from loop outside it");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"exec ls|less") || !parse_util_detect_errors(L"echo|return")) {
|
if (!detect_errors(L"exec ls|less") || !detect_errors(L"echo|return")) {
|
||||||
err(L"Invalid pipe command undetected");
|
err(L"Invalid pipe command undetected");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"for i in foo ; switch $i ; case blah ; break; end; end ")) {
|
if (detect_errors(L"for i in foo ; switch $i ; case blah ; break; end; end ")) {
|
||||||
err(L"'break' command inside switch falsely reported as error");
|
err(L"'break' command inside switch falsely reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"or cat | cat") || parse_util_detect_errors(L"and cat | cat")) {
|
if (detect_errors(L"or cat | cat") || detect_errors(L"and cat | cat")) {
|
||||||
err(L"boolean command at beginning of pipeline falsely reported as error");
|
err(L"boolean command at beginning of pipeline falsely reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"cat | and cat")) {
|
if (!detect_errors(L"cat | and cat")) {
|
||||||
err(L"'and' command in pipeline not reported as error");
|
err(L"'and' command in pipeline not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"cat | or cat")) {
|
if (!detect_errors(L"cat | or cat")) {
|
||||||
err(L"'or' command in pipeline not reported as error");
|
err(L"'or' command in pipeline not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"cat | exec") || !parse_util_detect_errors(L"exec | cat")) {
|
if (!detect_errors(L"cat | exec") || !detect_errors(L"exec | cat")) {
|
||||||
err(L"'exec' command in pipeline not reported as error");
|
err(L"'exec' command in pipeline not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"begin ; end arg")) {
|
if (!detect_errors(L"begin ; end arg")) {
|
||||||
err(L"argument to 'end' not reported as error");
|
err(L"argument to 'end' not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"switch foo ; end arg")) {
|
if (!detect_errors(L"switch foo ; end arg")) {
|
||||||
err(L"argument to 'end' not reported as error");
|
err(L"argument to 'end' not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"if true; else if false ; end arg")) {
|
if (!detect_errors(L"if true; else if false ; end arg")) {
|
||||||
err(L"argument to 'end' not reported as error");
|
err(L"argument to 'end' not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"if true; else ; end arg")) {
|
if (!detect_errors(L"if true; else ; end arg")) {
|
||||||
err(L"argument to 'end' not reported as error");
|
err(L"argument to 'end' not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"begin ; end 2> /dev/null")) {
|
if (detect_errors(L"begin ; end 2> /dev/null")) {
|
||||||
err(L"redirection after 'end' wrongly reported as error");
|
err(L"redirection after 'end' wrongly reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"true | ") != PARSER_TEST_INCOMPLETE) {
|
if (detect_errors(L"true | ") != PARSER_TEST_INCOMPLETE) {
|
||||||
err(L"unterminated pipe not reported properly");
|
err(L"unterminated pipe not reported properly");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"echo (\nfoo\n bar") != PARSER_TEST_INCOMPLETE) {
|
if (detect_errors(L"echo (\nfoo\n bar") != PARSER_TEST_INCOMPLETE) {
|
||||||
err(L"unterminated multiline subshell not reported properly");
|
err(L"unterminated multiline subshell not reported properly");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"begin ; true ; end | ") != PARSER_TEST_INCOMPLETE) {
|
if (detect_errors(L"begin ; true ; end | ") != PARSER_TEST_INCOMPLETE) {
|
||||||
err(L"unterminated pipe not reported properly");
|
err(L"unterminated pipe not reported properly");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L" | true ") != PARSER_TEST_ERROR) {
|
if (detect_errors(L" | true ") != PARSER_TEST_ERROR) {
|
||||||
err(L"leading pipe not reported properly");
|
err(L"leading pipe not reported properly");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"true | # comment") != PARSER_TEST_INCOMPLETE) {
|
if (detect_errors(L"true | # comment") != PARSER_TEST_INCOMPLETE) {
|
||||||
err(L"comment after pipe not reported as incomplete");
|
err(L"comment after pipe not reported as incomplete");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"true | # comment \n false ")) {
|
if (detect_errors(L"true | # comment \n false ")) {
|
||||||
err(L"comment and newline after pipe wrongly reported as error");
|
err(L"comment and newline after pipe wrongly reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"true | ; false ") != PARSER_TEST_ERROR) {
|
if (detect_errors(L"true | ; false ") != PARSER_TEST_ERROR) {
|
||||||
err(L"semicolon after pipe not detected as error");
|
err(L"semicolon after pipe not detected as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1149,59 +1151,59 @@ static void test_parser() {
|
||||||
err(L"Bad escape in nested command substitution not reported as error");
|
err(L"Bad escape in nested command substitution not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"false & ; and cat")) {
|
if (!detect_errors(L"false & ; and cat")) {
|
||||||
err(L"'and' command after background not reported as error");
|
err(L"'and' command after background not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"true & ; or cat")) {
|
if (!detect_errors(L"true & ; or cat")) {
|
||||||
err(L"'or' command after background not reported as error");
|
err(L"'or' command after background not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse_util_detect_errors(L"true & ; not cat")) {
|
if (detect_errors(L"true & ; not cat")) {
|
||||||
err(L"'not' command after background falsely reported as error");
|
err(L"'not' command after background falsely reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"if true & ; end")) {
|
if (!detect_errors(L"if true & ; end")) {
|
||||||
err(L"backgrounded 'if' conditional not reported as error");
|
err(L"backgrounded 'if' conditional not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"if false; else if true & ; end")) {
|
if (!detect_errors(L"if false; else if true & ; end")) {
|
||||||
err(L"backgrounded 'else if' conditional not reported as error");
|
err(L"backgrounded 'else if' conditional not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"while true & ; end")) {
|
if (!detect_errors(L"while true & ; end")) {
|
||||||
err(L"backgrounded 'while' conditional not reported as error");
|
err(L"backgrounded 'while' conditional not reported as error");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"true | || false")) {
|
if (!detect_errors(L"true | || false")) {
|
||||||
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"|| false")) {
|
if (!detect_errors(L"|| false")) {
|
||||||
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"&& false")) {
|
if (!detect_errors(L"&& false")) {
|
||||||
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"true ; && false")) {
|
if (!detect_errors(L"true ; && false")) {
|
||||||
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"true ; || false")) {
|
if (!detect_errors(L"true ; || false")) {
|
||||||
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"true || && false")) {
|
if (!detect_errors(L"true || && false")) {
|
||||||
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"true && || false")) {
|
if (!detect_errors(L"true && || false")) {
|
||||||
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parse_util_detect_errors(L"true && && false")) {
|
if (!detect_errors(L"true && && false")) {
|
||||||
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1209,6 +1211,7 @@ static void test_parser() {
|
||||||
|
|
||||||
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
|
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
|
||||||
// the principal parser because we cannot yet execute jobs on other parsers.
|
// the principal parser because we cannot yet execute jobs on other parsers.
|
||||||
|
auto parser = parser_t::principal_parser().shared();
|
||||||
say(L"Testing recursion detection");
|
say(L"Testing recursion detection");
|
||||||
parser->eval(L"function recursive ; recursive ; end ; recursive; ", io_chain_t());
|
parser->eval(L"function recursive ; recursive ; end ; recursive; ", io_chain_t());
|
||||||
#if 0
|
#if 0
|
||||||
|
@ -4708,7 +4711,7 @@ static void test_error_messages() {
|
||||||
parse_error_list_t errors;
|
parse_error_list_t errors;
|
||||||
for (const auto &test : error_tests) {
|
for (const auto &test : error_tests) {
|
||||||
errors.clear();
|
errors.clear();
|
||||||
parse_util_detect_errors(test.src, &errors, false /* allow_incomplete */);
|
parse_util_detect_errors(test.src, &errors);
|
||||||
do_test(!errors.empty());
|
do_test(!errors.empty());
|
||||||
if (!errors.empty()) {
|
if (!errors.empty()) {
|
||||||
do_test1(string_matches_format(errors.at(0).text, test.error_text_format), test.src);
|
do_test1(string_matches_format(errors.at(0).text, test.error_text_format), test.src);
|
||||||
|
|
|
@ -1100,7 +1100,7 @@ static bool should_import_bash_history_line(const wcstring &line) {
|
||||||
|
|
||||||
// In doing this test do not allow incomplete strings. Hence the "false" argument.
|
// In doing this test do not allow incomplete strings. Hence the "false" argument.
|
||||||
parse_error_list_t errors;
|
parse_error_list_t errors;
|
||||||
parse_util_detect_errors(line, &errors, false);
|
parse_util_detect_errors(line, &errors);
|
||||||
if (!errors.empty()) return false;
|
if (!errors.empty()) return false;
|
||||||
|
|
||||||
// The following are Very naive tests!
|
// The following are Very naive tests!
|
||||||
|
|
|
@ -202,12 +202,12 @@ wcstring parse_token_t::user_presentable_description() const {
|
||||||
return token_type_user_presentable_description(type, keyword);
|
return token_type_user_presentable_description(type, keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed_source_t::parsed_source_t(wcstring s, ast::ast_t &&ast)
|
parsed_source_t::parsed_source_t(wcstring &&s, ast::ast_t &&ast)
|
||||||
: src(std::move(s)), ast(std::move(ast)) {}
|
: src(std::move(s)), ast(std::move(ast)) {}
|
||||||
|
|
||||||
parsed_source_t::~parsed_source_t() = default;
|
parsed_source_t::~parsed_source_t() = default;
|
||||||
|
|
||||||
parsed_source_ref_t parse_source(wcstring src, parse_tree_flags_t flags,
|
parsed_source_ref_t parse_source(wcstring &&src, parse_tree_flags_t flags,
|
||||||
parse_error_list_t *errors) {
|
parse_error_list_t *errors) {
|
||||||
using namespace ast;
|
using namespace ast;
|
||||||
ast_t ast = ast_t::parse(src, flags, errors);
|
ast_t ast = ast_t::parse(src, flags, errors);
|
||||||
|
|
|
@ -64,7 +64,7 @@ struct parsed_source_t {
|
||||||
wcstring src;
|
wcstring src;
|
||||||
ast::ast_t ast;
|
ast::ast_t ast;
|
||||||
|
|
||||||
parsed_source_t(wcstring s, ast::ast_t &&ast);
|
parsed_source_t(wcstring &&s, ast::ast_t &&ast);
|
||||||
~parsed_source_t();
|
~parsed_source_t();
|
||||||
|
|
||||||
parsed_source_t(const parsed_source_t &) = delete;
|
parsed_source_t(const parsed_source_t &) = delete;
|
||||||
|
@ -76,7 +76,7 @@ struct parsed_source_t {
|
||||||
/// Return a shared pointer to parsed_source_t, or null on failure.
|
/// Return a shared pointer to parsed_source_t, or null on failure.
|
||||||
/// If parse_flag_continue_after_error is not set, this will return null on any error.
|
/// If parse_flag_continue_after_error is not set, this will return null on any error.
|
||||||
using parsed_source_ref_t = std::shared_ptr<const parsed_source_t>;
|
using parsed_source_ref_t = std::shared_ptr<const parsed_source_t>;
|
||||||
parsed_source_ref_t parse_source(wcstring src, parse_tree_flags_t flags,
|
parsed_source_ref_t parse_source(wcstring &&src, parse_tree_flags_t flags,
|
||||||
parse_error_list_t *errors);
|
parse_error_list_t *errors);
|
||||||
|
|
||||||
/// Error message for improper use of the exec builtin.
|
/// Error message for improper use of the exec builtin.
|
||||||
|
|
|
@ -722,6 +722,7 @@ std::vector<int> parse_util_compute_indents(const wcstring &src) {
|
||||||
/// Append a syntax error to the given error list.
|
/// Append a syntax error to the given error list.
|
||||||
static bool append_syntax_error(parse_error_list_t *errors, size_t source_location,
|
static bool append_syntax_error(parse_error_list_t *errors, size_t source_location,
|
||||||
const wchar_t *fmt, ...) {
|
const wchar_t *fmt, ...) {
|
||||||
|
if (!errors) return true;
|
||||||
parse_error_t error;
|
parse_error_t error;
|
||||||
error.source_start = source_location;
|
error.source_start = source_location;
|
||||||
error.source_length = 0;
|
error.source_length = 0;
|
||||||
|
@ -732,7 +733,7 @@ static bool append_syntax_error(parse_error_list_t *errors, size_t source_locati
|
||||||
error.text = vformat_string(fmt, va);
|
error.text = vformat_string(fmt, va);
|
||||||
va_end(va);
|
va_end(va);
|
||||||
|
|
||||||
errors->push_back(error);
|
errors->push_back(std::move(error));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -944,8 +945,7 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const ast::argumen
|
||||||
working_copy.replace(cmd_sub_start, cmd_sub_len, wcstring(1, INTERNAL_SEPARATOR));
|
working_copy.replace(cmd_sub_start, cmd_sub_len, wcstring(1, INTERNAL_SEPARATOR));
|
||||||
|
|
||||||
parse_error_list_t subst_errors;
|
parse_error_list_t subst_errors;
|
||||||
err |= parse_util_detect_errors(subst, &subst_errors,
|
err |= parse_util_detect_errors(subst, &subst_errors);
|
||||||
false /* do not accept incomplete */);
|
|
||||||
|
|
||||||
// Our command substitution produced error offsets relative to its source. Tweak the
|
// Our command substitution produced error offsets relative to its source. Tweak the
|
||||||
// offsets of the errors in the command substitution to account for both its offset
|
// offsets of the errors in the command substitution to account for both its offset
|
||||||
|
@ -1203,12 +1203,9 @@ static bool detect_errors_in_block_redirection_list(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
parser_test_error_bits_t parse_util_detect_errors(const ast::ast_t &ast, const wcstring &buff_src,
|
||||||
parse_error_list_t *out_errors,
|
parse_error_list_t *out_errors) {
|
||||||
bool allow_incomplete,
|
using namespace ast;
|
||||||
parsed_source_ref_t *out_pstree) {
|
|
||||||
parse_error_list_t parse_errors;
|
|
||||||
|
|
||||||
parser_test_error_bits_t res = 0;
|
parser_test_error_bits_t res = 0;
|
||||||
|
|
||||||
// Whether we encountered a parse error.
|
// Whether we encountered a parse error.
|
||||||
|
@ -1222,6 +1219,65 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
||||||
// detecting job_continuations that have source for pipes but not the statement.
|
// detecting job_continuations that have source for pipes but not the statement.
|
||||||
bool has_unclosed_pipe = false;
|
bool has_unclosed_pipe = false;
|
||||||
|
|
||||||
|
// Expand all commands.
|
||||||
|
// Verify 'or' and 'and' not used inside pipelines.
|
||||||
|
// Verify pipes via parser_is_pipe_forbidden.
|
||||||
|
// Verify return only within a function.
|
||||||
|
// Verify no variable expansions.
|
||||||
|
wcstring storage;
|
||||||
|
|
||||||
|
for (const node_t &node : ast) {
|
||||||
|
if (const job_continuation_t *jc = node.try_as<job_continuation_t>()) {
|
||||||
|
// Somewhat clumsy way of checking for a statement without source in a pipeline.
|
||||||
|
// See if our pipe has source but our statement does not.
|
||||||
|
if (!jc->pipe.unsourced && !jc->statement.try_source_range().has_value()) {
|
||||||
|
has_unclosed_pipe = true;
|
||||||
|
}
|
||||||
|
} else if (const argument_t *arg = node.try_as<argument_t>()) {
|
||||||
|
const wcstring &arg_src = arg->source(buff_src, &storage);
|
||||||
|
res |= parse_util_detect_errors_in_argument(*arg, arg_src, out_errors);
|
||||||
|
} else if (const ast::job_t *job = node.try_as<ast::job_t>()) {
|
||||||
|
// Disallow background in the following cases:
|
||||||
|
//
|
||||||
|
// foo & ; and bar
|
||||||
|
// foo & ; or bar
|
||||||
|
// if foo & ; end
|
||||||
|
// while foo & ; end
|
||||||
|
// If it's not a background job, nothing to do.
|
||||||
|
if (job->bg) {
|
||||||
|
errored |= detect_errors_in_backgrounded_job(*job, out_errors);
|
||||||
|
}
|
||||||
|
} else if (const ast::decorated_statement_t *stmt =
|
||||||
|
node.try_as<decorated_statement_t>()) {
|
||||||
|
errored |=
|
||||||
|
detect_errors_in_decorated_statement(buff_src, *stmt, &storage, out_errors);
|
||||||
|
} else if (const auto *block = node.try_as<block_statement_t>()) {
|
||||||
|
// If our 'end' had no source, we are unsourced.
|
||||||
|
if (block->end.unsourced) has_unclosed_block = true;
|
||||||
|
errored |=
|
||||||
|
detect_errors_in_block_redirection_list(block->args_or_redirs, out_errors);
|
||||||
|
} else if (const auto *ifs = node.try_as<if_statement_t>()) {
|
||||||
|
// If our 'end' had no source, we are unsourced.
|
||||||
|
if (ifs->end.unsourced) has_unclosed_block = true;
|
||||||
|
errored |= detect_errors_in_block_redirection_list(ifs->args_or_redirs, out_errors);
|
||||||
|
} else if (const auto *switchs = node.try_as<switch_statement_t>()) {
|
||||||
|
// If our 'end' had no source, we are unsourced.
|
||||||
|
if (switchs->end.unsourced) has_unclosed_block = true;
|
||||||
|
errored |=
|
||||||
|
detect_errors_in_block_redirection_list(switchs->args_or_redirs, out_errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errored) res |= PARSER_TEST_ERROR;
|
||||||
|
|
||||||
|
if (has_unclosed_block || has_unclosed_pipe) res |= PARSER_TEST_INCOMPLETE;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
||||||
|
parse_error_list_t *out_errors,
|
||||||
|
bool allow_incomplete) {
|
||||||
// Whether there's an unclosed quote or subshell, and therefore unfinished. This is only set if
|
// Whether there's an unclosed quote or subshell, and therefore unfinished. This is only set if
|
||||||
// allow_incomplete is set.
|
// allow_incomplete is set.
|
||||||
bool has_unclosed_quote_or_subshell = false;
|
bool has_unclosed_quote_or_subshell = false;
|
||||||
|
@ -1231,6 +1287,7 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
||||||
|
|
||||||
// Parse the input string into an ast. Some errors are detected here.
|
// Parse the input string into an ast. Some errors are detected here.
|
||||||
using namespace ast;
|
using namespace ast;
|
||||||
|
parse_error_list_t parse_errors;
|
||||||
auto ast = ast_t::parse(buff_src, parse_flags, &parse_errors);
|
auto ast = ast_t::parse(buff_src, parse_flags, &parse_errors);
|
||||||
if (allow_incomplete) {
|
if (allow_incomplete) {
|
||||||
// Issue #1238: If the only error was unterminated quote, then consider this to have parsed
|
// Issue #1238: If the only error was unterminated quote, then consider this to have parsed
|
||||||
|
@ -1253,75 +1310,14 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
||||||
return PARSER_TEST_INCOMPLETE;
|
return PARSER_TEST_INCOMPLETE;
|
||||||
}
|
}
|
||||||
|
|
||||||
errored = !parse_errors.empty();
|
// Early parse error, stop here.
|
||||||
|
if (!parse_errors.empty()) {
|
||||||
// Expand all commands.
|
if (out_errors) vec_append(*out_errors, std::move(parse_errors));
|
||||||
// Verify 'or' and 'and' not used inside pipelines.
|
return PARSER_TEST_ERROR;
|
||||||
// Verify pipes via parser_is_pipe_forbidden.
|
|
||||||
// Verify return only within a function.
|
|
||||||
// Verify no variable expansions.
|
|
||||||
wcstring storage;
|
|
||||||
|
|
||||||
if (!errored) {
|
|
||||||
for (const node_t &node : ast) {
|
|
||||||
if (const job_continuation_t *jc = node.try_as<job_continuation_t>()) {
|
|
||||||
// Somewhat clumsy way of checking for a statement without source in a pipeline.
|
|
||||||
// See if our pipe has source but our statement does not.
|
|
||||||
if (!jc->pipe.unsourced && !jc->statement.try_source_range().has_value()) {
|
|
||||||
has_unclosed_pipe = true;
|
|
||||||
}
|
|
||||||
} else if (const argument_t *arg = node.try_as<argument_t>()) {
|
|
||||||
const wcstring &arg_src = arg->source(buff_src, &storage);
|
|
||||||
res |= parse_util_detect_errors_in_argument(*arg, arg_src, &parse_errors);
|
|
||||||
} else if (const ast::job_t *job = node.try_as<ast::job_t>()) {
|
|
||||||
// Disallow background in the following cases:
|
|
||||||
//
|
|
||||||
// foo & ; and bar
|
|
||||||
// foo & ; or bar
|
|
||||||
// if foo & ; end
|
|
||||||
// while foo & ; end
|
|
||||||
// If it's not a background job, nothing to do.
|
|
||||||
if (job->bg) {
|
|
||||||
errored |= detect_errors_in_backgrounded_job(*job, &parse_errors);
|
|
||||||
}
|
|
||||||
} else if (const ast::decorated_statement_t *stmt =
|
|
||||||
node.try_as<decorated_statement_t>()) {
|
|
||||||
errored |=
|
|
||||||
detect_errors_in_decorated_statement(buff_src, *stmt, &storage, &parse_errors);
|
|
||||||
} else if (const auto *block = node.try_as<block_statement_t>()) {
|
|
||||||
// If our 'end' had no source, we are unsourced.
|
|
||||||
if (block->end.unsourced) has_unclosed_block = true;
|
|
||||||
errored |=
|
|
||||||
detect_errors_in_block_redirection_list(block->args_or_redirs, &parse_errors);
|
|
||||||
} else if (const auto *ifs = node.try_as<if_statement_t>()) {
|
|
||||||
// If our 'end' had no source, we are unsourced.
|
|
||||||
if (ifs->end.unsourced) has_unclosed_block = true;
|
|
||||||
errored |=
|
|
||||||
detect_errors_in_block_redirection_list(ifs->args_or_redirs, &parse_errors);
|
|
||||||
} else if (const auto *switchs = node.try_as<switch_statement_t>()) {
|
|
||||||
// If our 'end' had no source, we are unsourced.
|
|
||||||
if (switchs->end.unsourced) has_unclosed_block = true;
|
|
||||||
errored |=
|
|
||||||
detect_errors_in_block_redirection_list(switchs->args_or_redirs, &parse_errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errored) res |= PARSER_TEST_ERROR;
|
// Defer to the tree-walking version.
|
||||||
|
return parse_util_detect_errors(ast, buff_src, out_errors);
|
||||||
if (has_unclosed_block || has_unclosed_quote_or_subshell || has_unclosed_pipe)
|
|
||||||
res |= PARSER_TEST_INCOMPLETE;
|
|
||||||
|
|
||||||
if (out_errors != nullptr) {
|
|
||||||
*out_errors = std::move(parse_errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
// \return the ast to our caller if requested.
|
|
||||||
if (out_pstree != nullptr) {
|
|
||||||
*out_pstree = std::make_shared<parsed_source_t>(buff_src, std::move(ast));
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maybe_t<wcstring> parse_util_detect_errors_in_argument_list(const wcstring &arg_list_src,
|
maybe_t<wcstring> parse_util_detect_errors_in_argument_list(const wcstring &arg_list_src,
|
||||||
|
|
|
@ -133,8 +133,12 @@ std::vector<int> parse_util_compute_indents(const wcstring &src);
|
||||||
/// error. If out_pstree is not NULL, the resulting tree is returned by reference.
|
/// error. If out_pstree is not NULL, the resulting tree is returned by reference.
|
||||||
parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
||||||
parse_error_list_t *out_errors = nullptr,
|
parse_error_list_t *out_errors = nullptr,
|
||||||
bool allow_incomplete = true,
|
bool allow_incomplete = false);
|
||||||
parsed_source_ref_t *out_pstree = nullptr);
|
|
||||||
|
/// Like parse_util_detect_errors but accepts an already-parsed ast.
|
||||||
|
/// The top of the ast is assumed to be a job list.
|
||||||
|
parser_test_error_bits_t parse_util_detect_errors(const ast::ast_t &ast, const wcstring &buff_src,
|
||||||
|
parse_error_list_t *out_errors);
|
||||||
|
|
||||||
/// Detect errors in the specified string when parsed as an argument list. Returns the text of an
|
/// Detect errors in the specified string when parsed as an argument list. Returns the text of an
|
||||||
/// error, or none if no error occurred.
|
/// error, or none if no error occurred.
|
||||||
|
|
|
@ -631,7 +631,7 @@ eval_res_t parser_t::eval(const wcstring &cmd, const io_chain_t &io,
|
||||||
const job_group_ref_t &job_group, enum block_type_t block_type) {
|
const job_group_ref_t &job_group, enum block_type_t block_type) {
|
||||||
// Parse the source into a tree, if we can.
|
// Parse the source into a tree, if we can.
|
||||||
parse_error_list_t error_list;
|
parse_error_list_t error_list;
|
||||||
if (parsed_source_ref_t ps = parse_source(cmd, parse_flag_none, &error_list)) {
|
if (parsed_source_ref_t ps = parse_source(wcstring{cmd}, parse_flag_none, &error_list)) {
|
||||||
return this->eval(ps, io, job_group, block_type);
|
return this->eval(ps, io, job_group, block_type);
|
||||||
} else {
|
} else {
|
||||||
// Get a backtrace. This includes the message.
|
// Get a backtrace. This includes the message.
|
||||||
|
|
|
@ -3887,10 +3887,18 @@ static int read_ni(parser_t &parser, int fd, const io_chain_t &io) {
|
||||||
str.erase(0, 1);
|
str.erase(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse into an ast and detect errors.
|
||||||
parse_error_list_t errors;
|
parse_error_list_t errors;
|
||||||
parsed_source_ref_t pstree;
|
auto ast = ast::ast_t::parse(str, parse_flag_none, &errors);
|
||||||
if (!parse_util_detect_errors(str, &errors, false /* do not accept incomplete */, &pstree)) {
|
bool errored = ast.errored();
|
||||||
parser.eval(pstree, io);
|
if (!errored) {
|
||||||
|
errored = parse_util_detect_errors(ast, str, &errors);
|
||||||
|
}
|
||||||
|
if (!errored) {
|
||||||
|
// Construct a parsed source ref.
|
||||||
|
// Be careful to transfer ownership, this could be a very large string.
|
||||||
|
parsed_source_ref_t ps = std::make_shared<parsed_source_t>(std::move(str), std::move(ast));
|
||||||
|
parser.eval(ps, io);
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
wcstring sb;
|
wcstring sb;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user