Fix spurious syntax error on escaped $@ inside quoted command substitution

We detect use of unsupported features like $@ by scanning string tokens
as a whole. With quoted command substitution, this has false positives,
as reported in [1]. We already recursively run the same error checks on
command substitutions, so limit the remaining checks to the gaps in-between
command substitutions.

[1]: 5f94dfd094/.config/fish/README/bug.md (cannot-use-dollar-anchor-in-sed-regex-in-quoted-command-substitution)
This commit is contained in:
Johannes Altmanninger 2022-04-03 15:30:31 +02:00
parent 3e3f507012
commit e717b13e75
2 changed files with 50 additions and 32 deletions

View File

@ -955,57 +955,17 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const ast::argumen
size_t source_start = source_range->start;
parser_test_error_bits_t err = 0;
size_t cursor = 0;
wcstring subst;
bool do_loop = true;
bool is_quoted = false;
while (do_loop) {
size_t paren_begin = 0;
size_t paren_end = 0;
switch (parse_util_locate_cmdsubst_range(arg_src, &cursor, &subst, &paren_begin, &paren_end,
false, &is_quoted)) {
case -1: {
err |= PARSER_TEST_ERROR;
if (out_errors) {
append_syntax_error(out_errors, source_start, L"Mismatched parenthesis");
}
return err;
}
case 0: {
do_loop = false;
break;
}
case 1: {
assert(paren_begin < paren_end && "Parens out of order?");
parse_error_list_t subst_errors;
err |= parse_util_detect_errors(subst, &subst_errors);
// 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
// within the string, and the offset of the node.
size_t error_offset = paren_begin + 1 + source_start;
parse_error_offset_source_start(&subst_errors, error_offset);
if (out_errors != nullptr) {
out_errors->insert(out_errors->end(), subst_errors.begin(), subst_errors.end());
}
break;
}
default: {
DIE("unexpected parse_util_locate_cmdsubst() return value");
}
}
}
auto check_subtoken = [&arg_src, &out_errors, source_start](size_t begin, size_t end) -> int {
wcstring unesc;
if (!unescape_string(arg_src, &unesc, UNESCAPE_SPECIAL)) {
if (!unescape_string(arg_src.c_str() + begin, end - begin, &unesc, UNESCAPE_SPECIAL)) {
if (out_errors) {
append_syntax_error(out_errors, source_start, L"Invalid token '%ls'", arg_src.c_str());
append_syntax_error(out_errors, source_start, L"Invalid token '%ls'",
arg_src.c_str());
}
return 1;
}
parser_test_error_bits_t err = 0;
// Check for invalid variable expansions.
const size_t unesc_size = unesc.size();
for (size_t idx = 0; idx < unesc_size; idx++) {
@ -1020,7 +980,8 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const ast::argumen
if (out_errors) {
// We have something like $$$^.... Back up until we reach the first $.
size_t first_dollar = idx;
while (first_dollar > 0 && (unesc.at(first_dollar - 1) == VARIABLE_EXPAND ||
while (first_dollar > 0 &&
(unesc.at(first_dollar - 1) == VARIABLE_EXPAND ||
unesc.at(first_dollar - 1) == VARIABLE_EXPAND_SINGLE)) {
first_dollar--;
}
@ -1029,6 +990,59 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const ast::argumen
}
}
return err;
};
size_t cursor = 0;
size_t checked = 0;
wcstring subst;
bool do_loop = true;
bool is_quoted = false;
while (do_loop) {
size_t paren_begin = 0;
size_t paren_end = 0;
bool has_dollar = false;
switch (parse_util_locate_cmdsubst_range(arg_src, &cursor, &subst, &paren_begin, &paren_end,
false, &is_quoted, &has_dollar)) {
case -1: {
err |= PARSER_TEST_ERROR;
if (out_errors) {
append_syntax_error(out_errors, source_start, L"Mismatched parenthesis");
}
return err;
}
case 0: {
do_loop = false;
break;
}
case 1: {
err |= check_subtoken(checked, paren_begin - has_dollar);
assert(paren_begin < paren_end && "Parens out of order?");
parse_error_list_t subst_errors;
err |= parse_util_detect_errors(subst, &subst_errors);
// 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
// within the string, and the offset of the node.
size_t error_offset = paren_begin + 1 + source_start;
parse_error_offset_source_start(&subst_errors, error_offset);
if (out_errors != nullptr) {
out_errors->insert(out_errors->end(), subst_errors.begin(), subst_errors.end());
}
checked = paren_end + 1;
break;
}
default: {
DIE("unexpected parse_util_locate_cmdsubst() return value");
}
}
}
err |= check_subtoken(checked, arg_src.size());
return err;
}

View File

@ -74,3 +74,7 @@ echo "\$(echo 1)"
# CHECK: $(echo 1)
echo "\$$(echo 1)"
# CHECK: $1
# Make sure we don't error on an escaped $@ inside a quoted cmdsub.
echo "$(echo '$@')"
# CHECK: $@