mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-22 14:43:55 +08:00
Fix error check for repeated quoted command substitution
Commit e40eba358
(Treat text following quoted command substitution
as quoted) made parse_util_locate_cmdsubst_range() aware of quoted
command substitutions, by skipping surrounding text via quote_end().
However, it was not quite right. We fail to properly parse
two consecutive command substitutions in the same string,
because we don't maintain the quoting context across calls to
parse_util_locate_cmdsubst_range(). Let's track that bit in a
parameter. This allows us to get rid of the quote_end() hack.
Also apply this to the other place where we call
parse_util_locate_cmdsubst_range() in a loop (highlighting).
Fixes #8500
This commit is contained in:
parent
c706b1d6cb
commit
4a575b26f5
|
@ -878,9 +878,10 @@ void highlighter_t::color_as_argument(const ast::node_t &node, bool options_allo
|
|||
// Now do command substitutions.
|
||||
size_t cmdsub_cursor = 0, cmdsub_start = 0, cmdsub_end = 0;
|
||||
wcstring cmdsub_contents;
|
||||
bool is_quoted = false;
|
||||
while (parse_util_locate_cmdsubst_range(arg_str, &cmdsub_cursor, &cmdsub_contents,
|
||||
&cmdsub_start, &cmdsub_end,
|
||||
true /* accept incomplete */) > 0) {
|
||||
true /* accept incomplete */, &is_quoted) > 0) {
|
||||
// The cmdsub_start is the open paren. cmdsub_end is either the close paren or the end of
|
||||
// the string. cmdsub_contents extends from one past cmdsub_start to cmdsub_end.
|
||||
assert(cmdsub_end > cmdsub_start);
|
||||
|
|
|
@ -88,7 +88,7 @@ size_t parse_util_get_offset(const wcstring &str, int line, long line_offset) {
|
|||
}
|
||||
|
||||
static int parse_util_locate_cmdsub(const wchar_t *in, const wchar_t **begin, const wchar_t **end,
|
||||
bool allow_incomplete, bool *is_quoted) {
|
||||
bool allow_incomplete, bool *inout_is_quoted) {
|
||||
bool escaped = false;
|
||||
bool syntax_error = false;
|
||||
int paran_count = 0;
|
||||
|
@ -97,21 +97,31 @@ static int parse_util_locate_cmdsub(const wchar_t *in, const wchar_t **begin, co
|
|||
const wchar_t *paran_begin = nullptr, *paran_end = nullptr;
|
||||
|
||||
assert(in && "null parameter");
|
||||
for (const wchar_t *pos = in; *pos; pos++) {
|
||||
|
||||
const wchar_t *pos = in;
|
||||
auto process_opening_quote = [&](wchar_t quote) -> bool /* ok */ {
|
||||
const wchar_t *q_end = quote_end(pos, quote);
|
||||
if (!q_end) return false;
|
||||
if (*q_end == L'$') {
|
||||
quoted_cmdsubs.push_back(paran_count);
|
||||
}
|
||||
// We want to report whether the outermost comand substitution between
|
||||
// paran_begin..paran_end is quoted.
|
||||
if (paran_count == 0 && inout_is_quoted) {
|
||||
*inout_is_quoted = *q_end == L'$';
|
||||
}
|
||||
pos = q_end;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (inout_is_quoted && *inout_is_quoted && *pos) {
|
||||
if (!process_opening_quote(L'"')) pos += std::wcslen(pos);
|
||||
}
|
||||
|
||||
for (; *pos; pos++) {
|
||||
if (!escaped) {
|
||||
if (*pos == L'\'' || *pos == L'"') {
|
||||
const wchar_t *q_end = quote_end(pos, *pos);
|
||||
if (q_end && *q_end) {
|
||||
if (*q_end == L'$') {
|
||||
quoted_cmdsubs.push_back(paran_count);
|
||||
// We want to report if the outermost comand substitution between
|
||||
// paran_begin..paran_end is quoted.
|
||||
if (paran_count == 0 && is_quoted) *is_quoted = true;
|
||||
}
|
||||
pos = q_end;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (!process_opening_quote(*pos)) break;
|
||||
} else {
|
||||
if (*pos == L'(') {
|
||||
if ((paran_count == 0) && (paran_begin == nullptr)) {
|
||||
|
@ -225,12 +235,11 @@ long parse_util_slice_length(const wchar_t *in) {
|
|||
|
||||
int parse_util_locate_cmdsubst_range(const wcstring &str, size_t *inout_cursor_offset,
|
||||
wcstring *out_contents, size_t *out_start, size_t *out_end,
|
||||
bool accept_incomplete, bool *out_is_quoted) {
|
||||
bool accept_incomplete, bool *inout_is_quoted) {
|
||||
// Clear the return values.
|
||||
if (out_contents != nullptr) out_contents->clear();
|
||||
*out_start = 0;
|
||||
*out_end = str.size();
|
||||
bool cmdsub_is_quoted = false;
|
||||
|
||||
// Nothing to do if the offset is at or past the end of the string.
|
||||
if (*inout_cursor_offset >= str.size()) return 0;
|
||||
|
@ -243,7 +252,7 @@ int parse_util_locate_cmdsubst_range(const wcstring &str, size_t *inout_cursor_o
|
|||
const wchar_t *bracket_range_end = nullptr;
|
||||
|
||||
int ret = parse_util_locate_cmdsub(valid_range_start, &bracket_range_begin, &bracket_range_end,
|
||||
accept_incomplete, &cmdsub_is_quoted);
|
||||
accept_incomplete, inout_is_quoted);
|
||||
if (ret <= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -264,28 +273,10 @@ int parse_util_locate_cmdsubst_range(const wcstring &str, size_t *inout_cursor_o
|
|||
// Return the start and end.
|
||||
*out_start = bracket_range_begin - buff;
|
||||
*out_end = bracket_range_end - buff;
|
||||
if (out_is_quoted) *out_is_quoted = cmdsub_is_quoted;
|
||||
|
||||
// Update the inout_cursor_offset. Note this may cause it to exceed str.size(), though
|
||||
// overflow is not likely.
|
||||
*inout_cursor_offset = 1 + *out_end;
|
||||
if (cmdsub_is_quoted && *bracket_range_end) {
|
||||
// We are usually called in a loop, to process all command substitutions in this string.
|
||||
// If we just located a quoted cmdsub like $(A) inside "$(A)B"(C), the B part is also
|
||||
// quoted but the naïve next caller wouldn't know. Since next caller only cares about
|
||||
// the next command substitution - (C) - and not about the B part, just advance the
|
||||
// cursor to the closing quote.
|
||||
if (auto *q_end = quote_end(bracket_range_end, L'"')) {
|
||||
*inout_cursor_offset = 1 + q_end - buff;
|
||||
} else {
|
||||
if (accept_incomplete) {
|
||||
// We want to skip quoted text, so if there is no closing quote, skip to the end.
|
||||
*inout_cursor_offset = bracket_range_end + std::wcslen(bracket_range_end) - buff;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -950,11 +941,12 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const ast::argumen
|
|||
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)) {
|
||||
false, &is_quoted)) {
|
||||
case -1: {
|
||||
err |= PARSER_TEST_ERROR;
|
||||
if (out_errors) {
|
||||
|
|
|
@ -29,10 +29,11 @@ long parse_util_slice_length(const wchar_t *in);
|
|||
/// \param out_end On output, the offset of the end of the command substitution (close paren), or
|
||||
/// the end of the string if it was incomplete
|
||||
/// \param accept_incomplete whether to permit missing closing parenthesis
|
||||
/// \param inout_is_quoted whether the cursor is in a double-quoted context.
|
||||
/// \return -1 on syntax error, 0 if no subshells exist and 1 on success
|
||||
int parse_util_locate_cmdsubst_range(const wcstring &str, size_t *inout_cursor_offset,
|
||||
wcstring *out_contents, size_t *out_start, size_t *out_end,
|
||||
bool accept_incomplete, bool *out_is_quoted = nullptr);
|
||||
bool accept_incomplete, bool *inout_is_quoted = nullptr);
|
||||
|
||||
/// Find the beginning and end of the command substitution under the cursor. If no subshell is
|
||||
/// found, the entire string is returned. If the current command substitution is not ended, i.e. the
|
||||
|
|
|
@ -57,3 +57,9 @@ echo "$(echo 1) ( $(echo 2)"
|
|||
|
||||
echo "$(echo A)B$(echo C)D"(echo E)
|
||||
# CHECK: ABCDE
|
||||
|
||||
echo "($(echo A)B$(echo C))"
|
||||
# CHECK: (ABC)
|
||||
|
||||
echo "quoted1""quoted2"(echo unquoted3)"$(echo quoted4)_$(echo quoted5)"
|
||||
# CHECK: quoted1quoted2unquoted3quoted4_quoted5
|
||||
|
|
Loading…
Reference in New Issue
Block a user