mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-03-03 22:47:39 +08:00
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:
parent
3e3f507012
commit
e717b13e75
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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: $@
|
||||
|
Loading…
x
Reference in New Issue
Block a user