mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-29 14:23:09 +00:00
Adopt expansion limits in wildcard expansions
This prevents e.g. `count /**` from consuming all of your memory. Fixes #7226
This commit is contained in:
parent
f11a60473a
commit
594a6a35e8
4 changed files with 69 additions and 60 deletions
|
@ -124,12 +124,13 @@ using completion_list_t = std::vector<completion_t>;
|
||||||
/// some conveniences.
|
/// some conveniences.
|
||||||
class completion_receiver_t {
|
class completion_receiver_t {
|
||||||
public:
|
public:
|
||||||
/// The default limit on expansions.
|
/// The default maximum number of items that something may expand to.
|
||||||
static constexpr size_t k_default_expansion_limit = 512 * 1024;
|
static constexpr size_t k_default_expansion_limit = 512 * 1024;
|
||||||
|
|
||||||
/// Construct with a limit.
|
/// Construct as empty, with a limit.
|
||||||
explicit completion_receiver_t(size_t limit = k_default_expansion_limit) : limit_(limit) {}
|
explicit completion_receiver_t(size_t limit = k_default_expansion_limit) : limit_(limit) {}
|
||||||
|
|
||||||
|
/// Acquire an existing list, with a limit.
|
||||||
explicit completion_receiver_t(completion_list_t &&v, size_t limit = k_default_expansion_limit)
|
explicit completion_receiver_t(completion_list_t &&v, size_t limit = k_default_expansion_limit)
|
||||||
: completions_(std::move(v)), limit_(limit) {}
|
: completions_(std::move(v)), limit_(limit) {}
|
||||||
|
|
||||||
|
|
|
@ -1037,20 +1037,18 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
|
||||||
result = expand_result_t::wildcard_no_match;
|
result = expand_result_t::wildcard_no_match;
|
||||||
completion_receiver_t expanded_recv = out->subreceiver();
|
completion_receiver_t expanded_recv = out->subreceiver();
|
||||||
for (const auto &effective_working_dir : effective_working_dirs) {
|
for (const auto &effective_working_dir : effective_working_dirs) {
|
||||||
wildcard_expand_result_t expand_res = wildcard_expand_string(
|
wildcard_result_t expand_res = wildcard_expand_string(
|
||||||
path_to_expand, effective_working_dir, flags, ctx.cancel_checker, &expanded_recv);
|
path_to_expand, effective_working_dir, flags, ctx.cancel_checker, &expanded_recv);
|
||||||
switch (expand_res) {
|
switch (expand_res) {
|
||||||
case wildcard_expand_result_t::match:
|
case wildcard_result_t::match:
|
||||||
result = expand_result_t::ok;
|
result = expand_result_t::ok;
|
||||||
break;
|
break;
|
||||||
case wildcard_expand_result_t::no_match:
|
case wildcard_result_t::no_match:
|
||||||
break;
|
|
||||||
case wildcard_expand_result_t::overflow:
|
|
||||||
result = expand_result_t::error;
|
|
||||||
break;
|
|
||||||
case wildcard_expand_result_t::cancel:
|
|
||||||
result = expand_result_t::cancel;
|
|
||||||
break;
|
break;
|
||||||
|
case wildcard_result_t::overflow:
|
||||||
|
return append_overflow_error(errors);
|
||||||
|
case wildcard_result_t::cancel:
|
||||||
|
return expand_result_t::cancel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -206,17 +206,19 @@ static bool has_prefix_match(const completion_receiver_t *comps, size_t first) {
|
||||||
///
|
///
|
||||||
/// We ignore ANY_STRING_RECURSIVE here. The consequence is that you cannot tab complete **
|
/// We ignore ANY_STRING_RECURSIVE here. The consequence is that you cannot tab complete **
|
||||||
/// wildcards. This is historic behavior.
|
/// wildcards. This is historic behavior.
|
||||||
static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
|
static wildcard_result_t wildcard_complete_internal(const wchar_t *const str, size_t str_len,
|
||||||
const wchar_t *const wc, size_t wc_len,
|
const wchar_t *const wc, size_t wc_len,
|
||||||
const wc_complete_pack_t ¶ms, complete_flags_t flags,
|
const wc_complete_pack_t ¶ms,
|
||||||
completion_receiver_t *out, bool is_first_call = false) {
|
complete_flags_t flags,
|
||||||
|
completion_receiver_t *out,
|
||||||
|
bool is_first_call = false) {
|
||||||
assert(str != nullptr);
|
assert(str != nullptr);
|
||||||
assert(wc != nullptr);
|
assert(wc != nullptr);
|
||||||
|
|
||||||
// Maybe early out for hidden files. We require that the wildcard match these exactly (i.e. a
|
// Maybe early out for hidden files. We require that the wildcard match these exactly (i.e. a
|
||||||
// dot); ANY_STRING not allowed.
|
// dot); ANY_STRING not allowed.
|
||||||
if (is_first_call && str[0] == L'.' && wc[0] != L'.') {
|
if (is_first_call && str[0] == L'.' && wc[0] != L'.') {
|
||||||
return false;
|
return wildcard_result_t::no_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locate the next wildcard character position, e.g. ANY_CHAR or ANY_STRING.
|
// Locate the next wildcard character position, e.g. ANY_CHAR or ANY_STRING.
|
||||||
|
@ -226,17 +228,17 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
|
||||||
if (next_wc_char_pos == wcstring::npos) {
|
if (next_wc_char_pos == wcstring::npos) {
|
||||||
// Try matching.
|
// Try matching.
|
||||||
maybe_t<string_fuzzy_match_t> match = string_fuzzy_match_string(wc, str);
|
maybe_t<string_fuzzy_match_t> match = string_fuzzy_match_string(wc, str);
|
||||||
if (!match) return false;
|
if (!match) return wildcard_result_t::no_match;
|
||||||
|
|
||||||
// If we're not allowing fuzzy match, then we require a prefix match.
|
// If we're not allowing fuzzy match, then we require a prefix match.
|
||||||
bool needs_prefix_match = !(params.expand_flags & expand_flag::fuzzy_match);
|
bool needs_prefix_match = !(params.expand_flags & expand_flag::fuzzy_match);
|
||||||
if (needs_prefix_match && !match->is_exact_or_prefix()) {
|
if (needs_prefix_match && !match->is_exact_or_prefix()) {
|
||||||
return false;
|
return wildcard_result_t::no_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The match was successful. If the string is not requested we're done.
|
// The match was successful. If the string is not requested we're done.
|
||||||
if (out == nullptr) {
|
if (out == nullptr) {
|
||||||
return true;
|
return wildcard_result_t::match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wildcard complete.
|
// Wildcard complete.
|
||||||
|
@ -254,14 +256,14 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
|
||||||
// 'foo' when a file 'foo' exists.
|
// 'foo' when a file 'foo' exists.
|
||||||
complete_flags_t local_flags = flags | (full_replacement ? COMPLETE_REPLACES_TOKEN : 0);
|
complete_flags_t local_flags = flags | (full_replacement ? COMPLETE_REPLACES_TOKEN : 0);
|
||||||
if (!out->add(std::move(out_completion), std::move(out_desc), local_flags, *match)) {
|
if (!out->add(std::move(out_completion), std::move(out_desc), local_flags, *match)) {
|
||||||
return false;
|
return wildcard_result_t::overflow;
|
||||||
}
|
}
|
||||||
return true;
|
return wildcard_result_t::match;
|
||||||
} else if (next_wc_char_pos > 0) {
|
} else if (next_wc_char_pos > 0) {
|
||||||
// The literal portion of a wildcard cannot be longer than the string itself,
|
// The literal portion of a wildcard cannot be longer than the string itself,
|
||||||
// e.g. `abc*` can never match a string that is only two characters long.
|
// e.g. `abc*` can never match a string that is only two characters long.
|
||||||
if (next_wc_char_pos >= str_len) {
|
if (next_wc_char_pos >= str_len) {
|
||||||
return false;
|
return wildcard_result_t::no_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we have a non-wildcard prefix. Note that we don't do fuzzy matching for stuff before
|
// Here we have a non-wildcard prefix. Note that we don't do fuzzy matching for stuff before
|
||||||
|
@ -278,7 +280,7 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
|
||||||
wc + next_wc_char_pos, wc_len - next_wc_char_pos,
|
wc + next_wc_char_pos, wc_len - next_wc_char_pos,
|
||||||
params, flags | COMPLETE_REPLACES_TOKEN, out);
|
params, flags | COMPLETE_REPLACES_TOKEN, out);
|
||||||
}
|
}
|
||||||
return false; // no match
|
return wildcard_result_t::no_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our first character is a wildcard.
|
// Our first character is a wildcard.
|
||||||
|
@ -286,7 +288,7 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
|
||||||
switch (wc[0]) {
|
switch (wc[0]) {
|
||||||
case ANY_CHAR: {
|
case ANY_CHAR: {
|
||||||
if (str[0] == L'\0') {
|
if (str[0] == L'\0') {
|
||||||
return false;
|
return wildcard_result_t::no_match;
|
||||||
}
|
}
|
||||||
return wildcard_complete_internal(str + 1, str_len - 1, wc + 1, wc_len - 1, params,
|
return wildcard_complete_internal(str + 1, str_len - 1, wc + 1, wc_len - 1, params,
|
||||||
flags, out);
|
flags, out);
|
||||||
|
@ -305,23 +307,30 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
|
||||||
bool has_match = false;
|
bool has_match = false;
|
||||||
for (size_t i = 0; str[i] != L'\0'; i++) {
|
for (size_t i = 0; str[i] != L'\0'; i++) {
|
||||||
const size_t before_count = out ? out->size() : 0;
|
const size_t before_count = out ? out->size() : 0;
|
||||||
if (wildcard_complete_internal(str + i, str_len - i, wc + 1, wc_len - 1, params,
|
auto submatch_res = wildcard_complete_internal(str + i, str_len - i, wc + 1,
|
||||||
flags, out)) {
|
wc_len - 1, params, flags, out);
|
||||||
// We found a match.
|
switch (submatch_res) {
|
||||||
has_match = true;
|
case wildcard_result_t::no_match:
|
||||||
|
|
||||||
// If out is NULL, we don't care about the actual matches. If out is not
|
|
||||||
// NULL but we have a prefix match, stop there.
|
|
||||||
if (out == nullptr || has_prefix_match(out, before_count)) {
|
|
||||||
break;
|
break;
|
||||||
}
|
case wildcard_result_t::match:
|
||||||
|
has_match = true;
|
||||||
|
// If out is NULL, we don't care about the actual matches. If out is not
|
||||||
|
// NULL but we have a prefix match, stop there.
|
||||||
|
if (out == nullptr || has_prefix_match(out, before_count)) {
|
||||||
|
return wildcard_result_t::match;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case wildcard_result_t::cancel:
|
||||||
|
case wildcard_result_t::overflow:
|
||||||
|
// Note early return.
|
||||||
|
return submatch_res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return has_match;
|
return has_match ? wildcard_result_t::match : wildcard_result_t::no_match;
|
||||||
}
|
}
|
||||||
case ANY_STRING_RECURSIVE: {
|
case ANY_STRING_RECURSIVE: {
|
||||||
// We don't even try with this one.
|
// We don't even try with this one.
|
||||||
return false;
|
return wildcard_result_t::no_match;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
DIE("unreachable code reached");
|
DIE("unreachable code reached");
|
||||||
|
@ -331,10 +340,10 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
|
||||||
DIE("unreachable code reached");
|
DIE("unreachable code reached");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wildcard_complete(const wcstring &str, const wchar_t *wc,
|
wildcard_result_t wildcard_complete(const wcstring &str, const wchar_t *wc,
|
||||||
const std::function<wcstring(const wcstring &)> &desc_func,
|
const std::function<wcstring(const wcstring &)> &desc_func,
|
||||||
completion_receiver_t *out, expand_flags_t expand_flags,
|
completion_receiver_t *out, expand_flags_t expand_flags,
|
||||||
complete_flags_t flags) {
|
complete_flags_t flags) {
|
||||||
// Note out may be NULL.
|
// Note out may be NULL.
|
||||||
assert(wc != nullptr);
|
assert(wc != nullptr);
|
||||||
wc_complete_pack_t params(str, desc_func, expand_flags);
|
wc_complete_pack_t params(str, desc_func, expand_flags);
|
||||||
|
@ -453,7 +462,7 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wc
|
||||||
const wchar_t *wc, expand_flags_t expand_flags,
|
const wchar_t *wc, expand_flags_t expand_flags,
|
||||||
completion_receiver_t *out) {
|
completion_receiver_t *out) {
|
||||||
// Check if it will match before stat().
|
// Check if it will match before stat().
|
||||||
if (!wildcard_complete(filename, wc, {}, nullptr, expand_flags, 0)) {
|
if (wildcard_complete(filename, wc, {}, nullptr, expand_flags, 0) != wildcard_result_t::match) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,9 +520,10 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wc
|
||||||
auto desc_func = const_desc(desc);
|
auto desc_func = const_desc(desc);
|
||||||
if (is_directory) {
|
if (is_directory) {
|
||||||
return wildcard_complete(filename + L'/', wc, desc_func, out, expand_flags,
|
return wildcard_complete(filename + L'/', wc, desc_func, out, expand_flags,
|
||||||
COMPLETE_NO_SPACE);
|
COMPLETE_NO_SPACE) == wildcard_result_t::match;
|
||||||
}
|
}
|
||||||
return wildcard_complete(filename, wc, desc_func, out, expand_flags, 0);
|
return wildcard_complete(filename, wc, desc_func, out, expand_flags, 0) ==
|
||||||
|
wildcard_result_t::match;
|
||||||
}
|
}
|
||||||
|
|
||||||
class wildcard_expander_t {
|
class wildcard_expander_t {
|
||||||
|
@ -712,11 +722,13 @@ class wildcard_expander_t {
|
||||||
// Do wildcard expansion. This is recursive.
|
// Do wildcard expansion. This is recursive.
|
||||||
void expand(const wcstring &base_dir, const wchar_t *wc, const wcstring &prefix);
|
void expand(const wcstring &base_dir, const wchar_t *wc, const wcstring &prefix);
|
||||||
|
|
||||||
wildcard_expand_result_t status_code() const {
|
wildcard_result_t status_code() const {
|
||||||
if (this->did_interrupt) {
|
if (this->did_interrupt) {
|
||||||
return wildcard_expand_result_t::cancel;
|
return wildcard_result_t::cancel;
|
||||||
|
} else if (this->did_overflow) {
|
||||||
|
return wildcard_result_t::overflow;
|
||||||
}
|
}
|
||||||
return this->did_add ? wildcard_expand_result_t::match : wildcard_expand_result_t::no_match;
|
return this->did_add ? wildcard_result_t::match : wildcard_result_t::no_match;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -962,11 +974,10 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wildcard_expand_result_t wildcard_expand_string(const wcstring &wc,
|
wildcard_result_t wildcard_expand_string(const wcstring &wc, const wcstring &working_directory,
|
||||||
const wcstring &working_directory,
|
expand_flags_t flags,
|
||||||
expand_flags_t flags,
|
const cancel_checker_t &cancel_checker,
|
||||||
const cancel_checker_t &cancel_checker,
|
completion_receiver_t *output) {
|
||||||
completion_receiver_t *output) {
|
|
||||||
assert(output != nullptr);
|
assert(output != nullptr);
|
||||||
// Fuzzy matching only if we're doing completions.
|
// Fuzzy matching only if we're doing completions.
|
||||||
assert(flags.get(expand_flag::for_completions) || !flags.get(expand_flag::fuzzy_match));
|
assert(flags.get(expand_flag::for_completions) || !flags.get(expand_flag::fuzzy_match));
|
||||||
|
@ -983,7 +994,7 @@ wildcard_expand_result_t wildcard_expand_string(const wcstring &wc,
|
||||||
// embedded nulls are never allowed in a filename, so we just check for them and return 0 (no
|
// embedded nulls are never allowed in a filename, so we just check for them and return 0 (no
|
||||||
// matches) if there is an embedded null.
|
// matches) if there is an embedded null.
|
||||||
if (wc.find(L'\0') != wcstring::npos) {
|
if (wc.find(L'\0') != wcstring::npos) {
|
||||||
return wildcard_expand_result_t::no_match;
|
return wildcard_result_t::no_match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the prefix and base dir. The prefix is what we prepend for filesystem operations
|
// Compute the prefix and base dir. The prefix is what we prepend for filesystem operations
|
||||||
|
|
|
@ -41,17 +41,16 @@ enum {
|
||||||
/// executables_only
|
/// executables_only
|
||||||
/// \param output The list in which to put the output
|
/// \param output The list in which to put the output
|
||||||
///
|
///
|
||||||
enum class wildcard_expand_result_t {
|
enum class wildcard_result_t {
|
||||||
no_match, /// The wildcard did not match.
|
no_match, /// The wildcard did not match.
|
||||||
match, /// The wildcard did match.
|
match, /// The wildcard did match.
|
||||||
cancel, /// Expansion was cancelled (e.g. control-C).
|
cancel, /// Expansion was cancelled (e.g. control-C).
|
||||||
overflow, /// Expansion produced too many results.
|
overflow, /// Expansion produced too many results.
|
||||||
};
|
};
|
||||||
wildcard_expand_result_t wildcard_expand_string(const wcstring &wc,
|
wildcard_result_t wildcard_expand_string(const wcstring &wc, const wcstring &working_directory,
|
||||||
const wcstring &working_directory,
|
expand_flags_t flags,
|
||||||
expand_flags_t flags,
|
const cancel_checker_t &cancel_checker,
|
||||||
const cancel_checker_t &cancel_checker,
|
completion_receiver_t *output);
|
||||||
completion_receiver_t *output);
|
|
||||||
|
|
||||||
/// Test whether the given wildcard matches the string. Does not perform any I/O.
|
/// Test whether the given wildcard matches the string. Does not perform any I/O.
|
||||||
///
|
///
|
||||||
|
@ -69,8 +68,8 @@ bool wildcard_has(const wcstring &, bool internal);
|
||||||
bool wildcard_has(const wchar_t *, bool internal);
|
bool wildcard_has(const wchar_t *, bool internal);
|
||||||
|
|
||||||
/// Test wildcard completion.
|
/// Test wildcard completion.
|
||||||
bool wildcard_complete(const wcstring &str, const wchar_t *wc, const description_func_t &desc_func,
|
wildcard_result_t wildcard_complete(const wcstring &str, const wchar_t *wc,
|
||||||
completion_receiver_t *out, expand_flags_t expand_flags,
|
const description_func_t &desc_func, completion_receiver_t *out,
|
||||||
complete_flags_t flags);
|
expand_flags_t expand_flags, complete_flags_t flags);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue