Introduce expansion limits

This adds the ability to limit how many expansions are produced. For
example if $big contains 10 items, and is Cartesian-expanded as
$big$big$big$big... 10 times, we would naviely get 10^10 = 10 billion
results, which fish can't actually handle. Implement this in
completion_receiver_t, which now can return false to indicate an overflow.

The initial expansion limit 'k_default_expansion_limit' is set as 512k
items. There's no way for users to change this at present.
This commit is contained in:
ridiculousfish 2020-12-03 12:04:17 -08:00
parent 48567c37de
commit f11a60473a
7 changed files with 211 additions and 78 deletions

View file

@ -236,24 +236,36 @@ void completion_t::prepend_token_prefix(const wcstring &prefix) {
}
}
void completion_receiver_t::add(completion_t &&comp) {
bool completion_receiver_t::add(completion_t &&comp) {
if (this->completions_.size() >= limit_) {
return false;
}
this->completions_.push_back(std::move(comp));
return true;
}
void completion_receiver_t::add(wcstring &&comp) { this->add(std::move(comp), wcstring{}); }
bool completion_receiver_t::add(wcstring &&comp) {
return this->add(std::move(comp), wcstring{});
}
void completion_receiver_t::add(wcstring &&comp, wcstring &&desc, complete_flags_t flags,
bool completion_receiver_t::add(wcstring &&comp, wcstring &&desc, complete_flags_t flags,
string_fuzzy_match_t match) {
this->completions_.emplace_back(std::move(comp), std::move(desc), match, flags);
return this->add(completion_t(std::move(comp), std::move(desc), match, flags));
}
void completion_receiver_t::add_list(completion_list_t &&lst) {
bool completion_receiver_t::add_list(completion_list_t &&lst) {
size_t total_size = lst.size() + this->size();
if (total_size < this->size() || total_size > limit_) {
return false;
}
if (completions_.empty()) {
completions_ = std::move(lst);
} else {
completions_.reserve(completions_.size() + lst.size());
std::move(lst.begin(), lst.end(), std::back_inserter(completions_));
}
return true;
}
completion_list_t completion_receiver_t::take() {
@ -262,6 +274,11 @@ completion_list_t completion_receiver_t::take() {
return res;
}
completion_receiver_t completion_receiver_t::subreceiver() const {
size_t remaining_capacity = limit_ < size() ? 0 : limit_ - size();
return completion_receiver_t(remaining_capacity);
}
// If these functions aren't force inlined, it is actually faster to call
// stable_sort twice rather than to iterate once performing all comparisons in one go!
__attribute__((always_inline)) static inline bool compare_completions_by_duplicate_arguments(
@ -358,8 +375,8 @@ class completer_t {
bool try_complete_variable(const wcstring &str);
bool try_complete_user(const wcstring &str);
bool complete_param(const wcstring &cmd_orig, const wcstring &popt, const wcstring &str,
bool use_switches);
bool complete_param_for_command(const wcstring &cmd_orig, const wcstring &popt,
const wcstring &str, bool use_switches, bool *out_do_file);
void complete_param_expand(const wcstring &str, bool do_file,
bool handle_as_special_cd = false);
@ -910,16 +927,18 @@ static void complete_load(const wcstring &name) {
}
/// complete_param: Given a command, find completions for the argument str of command cmd_orig with
/// previous option popt.
/// previous option popt. If file completions should be disabled, then mark *out_do_file as false.
///
/// \return true if successful, false if there's an error.
///
/// Examples in format (cmd, popt, str):
///
/// echo hello world <tab> -> ("echo", "world", "")
/// echo hello world<tab> -> ("echo", "hello", "world")
///
/// Insert results into comp_out. Return true to perform file completion, false to disable it.
bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
const wcstring &str, bool use_switches) {
bool completer_t::complete_param_for_command(const wcstring &cmd_orig, const wcstring &popt,
const wcstring &str, bool use_switches,
bool *out_do_file) {
bool use_common = true, use_files = true, has_force = false;
wcstring cmd, path;
@ -1073,7 +1092,9 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
// It's a match.
wcstring desc = o.localized_desc();
// Append a short-style option
this->completions.add(wcstring{o.option}, std::move(desc), 0);
if (!this->completions.add(wcstring{o.option}, std::move(desc), 0)) {
return false;
}
}
// Check if the long style option matches.
@ -1116,15 +1137,23 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
// functions.
wcstring completion = format_string(L"%ls=", whole_opt.c_str() + offset);
// Append a long-style option with a mandatory trailing equal sign
this->completions.add(std::move(completion), C_(o.desc), flags | COMPLETE_NO_SPACE);
if (!this->completions.add(std::move(completion), C_(o.desc),
flags | COMPLETE_NO_SPACE)) {
return false;
}
}
// Append a long-style option
this->completions.add(whole_opt.substr(offset), C_(o.desc), flags);
if (!this->completions.add(whole_opt.substr(offset), C_(o.desc), flags)) {
return false;
}
}
}
return has_force || use_files;
if (!(has_force || use_files)) {
*out_do_file = false;
}
return true;
}
/// Perform generic (not command-specific) expansions on the specified string.
@ -1174,7 +1203,9 @@ void completer_t::complete_param_expand(const wcstring &str, bool do_file,
for (completion_t &comp : local_completions) {
comp.prepend_token_prefix(prefix_with_sep);
}
this->completions.add_list(std::move(local_completions));
if (!this->completions.add_list(std::move(local_completions))) {
return;
}
}
if (complete_from_start) {
@ -1189,6 +1220,7 @@ void completer_t::complete_param_expand(const wcstring &str, bool do_file,
}
/// Complete the specified string as an environment variable.
/// \return true if this was a variable, so we should stop completion.
bool completer_t::complete_variable(const wcstring &str, size_t start_offset) {
const wchar_t *const whole_var = str.c_str();
const wchar_t *var = &whole_var[start_offset];
@ -1237,7 +1269,8 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset) {
}
// Append matching environment variables
this->completions.add(std::move(comp), std::move(desc), flags, *match);
// TODO: need to propagate overflow here.
(void)this->completions.add(std::move(comp), std::move(desc), flags, *match);
res = true;
}
@ -1340,14 +1373,15 @@ bool completer_t::try_complete_user(const wcstring &str) {
const wchar_t *pw_name = pw_name_str.c_str();
if (std::wcsncmp(user_name, pw_name, name_len) == 0) {
wcstring desc = format_string(COMPLETE_USER_DESC, pw_name);
// Append a user name
this->completions.add(&pw_name[name_len], std::move(desc), COMPLETE_NO_SPACE);
// Append a user name.
// TODO: propagate overflow?
(void)this->completions.add(&pw_name[name_len], std::move(desc), COMPLETE_NO_SPACE);
result = true;
} else if (wcsncasecmp(user_name, pw_name, name_len) == 0) {
wcstring name = format_string(L"~%ls", pw_name);
wcstring desc = format_string(COMPLETE_USER_DESC, pw_name);
// Append a user name
this->completions.add(
(void)this->completions.add(
std::move(name), std::move(desc),
COMPLETE_REPLACES_TOKEN | COMPLETE_DONT_ESCAPE | COMPLETE_NO_SPACE);
result = true;
@ -1419,9 +1453,9 @@ void completer_t::complete_custom(const wcstring &cmd, const wcstring &cmdline,
cleanup_t restore_vars{apply_var_assignments(*ad->var_assignments)};
if (ctx.check_cancel()) return;
if (!complete_param(cmd, ad->previous_argument, ad->current_argument,
!ad->had_ddash)) { // Invoke any custom completions for this command.
ad->do_file = false;
if (!complete_param_for_command(
cmd, ad->previous_argument, ad->current_argument, !ad->had_ddash,
&ad->do_file)) { // Invoke any custom completions for this command.
}
}

View file

@ -124,22 +124,31 @@ using completion_list_t = std::vector<completion_t>;
/// some conveniences.
class completion_receiver_t {
public:
/// Construct, perhaps acquiring a list if necessary.
completion_receiver_t() = default;
explicit completion_receiver_t(completion_list_t &&v) : completions_(std::move(v)) {}
/// The default limit on expansions.
static constexpr size_t k_default_expansion_limit = 512 * 1024;
/// Construct with a limit.
explicit completion_receiver_t(size_t limit = k_default_expansion_limit) : limit_(limit) {}
explicit completion_receiver_t(completion_list_t &&v, size_t limit = k_default_expansion_limit)
: completions_(std::move(v)), limit_(limit) {}
/// Add a completion.
void add(completion_t &&comp);
/// \return true on success, false if this would overflow the limit.
__warn_unused bool add(completion_t &&comp);
/// Add a completion with the given string, and default other properties.
void add(wcstring &&comp);
/// \return true on success, false if this would overflow the limit.
__warn_unused bool add(wcstring &&comp);
/// Add a completion with the given string, description, flags, and fuzzy match.
void add(wcstring &&comp, wcstring &&desc, complete_flags_t flags = 0,
/// \return true on success, false if this would overflow the limit.
__warn_unused bool add(wcstring &&comp, wcstring &&desc, complete_flags_t flags = 0,
string_fuzzy_match_t match = string_fuzzy_match_t::exact_match());
/// Add a list of completions.
void add_list(completion_list_t &&lst);
/// \return true on success, false if this would overflow the limit.
__warn_unused bool add_list(completion_list_t &&lst);
/// Swap our completions with a new list.
void swap(completion_list_t &lst) { std::swap(completions_, lst); }
@ -163,11 +172,21 @@ class completion_receiver_t {
const completion_list_t &get_list() const { return completions_; }
completion_list_t &get_list() { return completions_; }
/// \return the list of completions, clearing them.
/// \return the list of completions, clearing it.
completion_list_t take();
/// \return a new, empty receiver whose limit is our remaining capacity.
/// This is useful for e.g. recursive calls when you want to act on the result before adding it.
completion_receiver_t subreceiver() const;
private:
// Our list of completions.
completion_list_t completions_;
// The maximum number of completions to add. If our list length exceeds this, then new
// completions are not added. Note 0 has no special significance here - use
// numeric_limits<size_t>::max() instead.
const size_t limit_;
};
enum complete_option_type_t {

View file

@ -119,6 +119,20 @@ static void append_cmdsub_error(parse_error_list_t *errors, size_t source_start,
errors->push_back(error);
}
/// Append an overflow error, when expansion produces too much data.
static expand_result_t append_overflow_error(parse_error_list_t *errors,
size_t source_start = SOURCE_LOCATION_UNKNOWN) {
if (errors) {
parse_error_t error;
error.source_start = source_start;
error.source_length = 0;
error.code = parse_error_generic;
error.text = _(L"Expansion produced too many results");
errors->push_back(std::move(error));
}
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
}
/// Test if the specified string does not contain character which can not be used inside a quoted
/// string.
static bool is_quotable(const wcstring &str) {
@ -273,27 +287,26 @@ static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long
/// Expand all environment variables in the string *ptr.
///
/// This function is slow, fragile and complicated. There are lots of little corner cases, like
/// $$foo should do a double expansion, $foo$bar should not double expand bar, etc. Also, it's easy
/// to accidentally leak memory on array out of bounds errors an various other situations. All in
/// all, this function should be rewritten, split out into multiple logical units and carefully
/// tested. After that, it can probably be optimized to do fewer memory allocations, fewer string
/// scans and overall just less work. But until that happens, don't edit it unless you know exactly
/// what you are doing, and do proper testing afterwards.
/// $$foo should do a double expansion, $foo$bar should not double expand bar, etc.
///
/// This function operates on strings backwards, starting at last_idx.
///
/// Note: last_idx is considered to be where it previously finished procesisng. This means it
/// actually starts operating on last_idx-1. As such, to process a string fully, pass string.size()
/// as last_idx instead of string.size()-1.
static bool expand_variables(wcstring instr, completion_receiver_t *out, size_t last_idx,
const environment_t &vars, parse_error_list_t *errors) {
///
/// \return the result of expansion.
static expand_result_t expand_variables(wcstring instr, completion_receiver_t *out, size_t last_idx,
const environment_t &vars, parse_error_list_t *errors) {
const size_t insize = instr.size();
// last_idx may be 1 past the end of the string, but no further.
assert(last_idx <= insize && "Invalid last_idx");
if (last_idx == 0) {
out->add(std::move(instr));
return true;
if (!out->add(std::move(instr))) {
return append_overflow_error(errors);
}
return expand_result_t::ok;
}
// Locate the last VARIABLE_EXPAND or VARIABLE_EXPAND_SINGLE
@ -308,8 +321,10 @@ static bool expand_variables(wcstring instr, completion_receiver_t *out, size_t
}
if (varexp_char_idx >= instr.size()) {
// No variable expand char, we're done.
out->add(std::move(instr));
return true;
if (!out->add(std::move(instr))) {
return append_overflow_error(errors);
}
return expand_result_t::ok;
}
// Get the variable name.
@ -333,7 +348,7 @@ static bool expand_variables(wcstring instr, completion_receiver_t *out, size_t
parse_util_expand_variable_error(instr, 0 /* global_token_pos */, varexp_char_idx,
errors);
}
return false;
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
}
// Get the variable name as a string, then try to get the variable from env.
@ -380,7 +395,7 @@ static bool expand_variables(wcstring instr, completion_receiver_t *out, size_t
} else {
append_syntax_error(errors, slice_start + bad_pos, L"Invalid index value");
}
return false;
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
}
var_name_and_slice_stop = (slice_end - in);
}
@ -389,7 +404,7 @@ static bool expand_variables(wcstring instr, completion_receiver_t *out, size_t
// Expanding a non-existent variable.
if (!is_single) {
// Normal expansions of missing variables successfully expand to nothing.
return true;
return expand_result_t::ok;
} else {
// Expansion to single argument.
// Replace the variable name and slice with VARIABLE_EXPAND_EMPTY.
@ -460,7 +475,9 @@ static bool expand_variables(wcstring instr, completion_receiver_t *out, size_t
// Normal cartesian-product expansion.
for (wcstring &item : var_item_list) {
if (varexp_char_idx == 0 && var_name_and_slice_stop == insize) {
out->add(std::move(item));
if (!out->add(std::move(item))) {
return append_overflow_error(errors);
}
} else {
wcstring new_in(instr, 0, varexp_char_idx);
if (!new_in.empty()) {
@ -472,13 +489,14 @@ static bool expand_variables(wcstring instr, completion_receiver_t *out, size_t
}
new_in.append(item);
new_in.append(instr, var_name_and_slice_stop, wcstring::npos);
if (!expand_variables(std::move(new_in), out, varexp_char_idx, vars, errors)) {
return false;
auto res = expand_variables(std::move(new_in), out, varexp_char_idx, vars, errors);
if (res.result != expand_result_t::ok) {
return res;
}
}
}
}
return true;
return expand_result_t::ok;
}
/// Perform brace expansion, placing the expanded strings into \p out.
@ -549,7 +567,9 @@ static expand_result_t expand_braces(wcstring &&instr, expand_flags_t flags,
}
if (brace_begin == nullptr) {
out->add(std::move(instr));
if (!out->add(std::move(instr))) {
return expand_result_t::error;
}
return expand_result_t::ok;
}
@ -608,7 +628,9 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
}
case 0: {
out->add(std::move(input));
if (!out->add(std::move(input))) {
return append_overflow_error(errors);
}
return expand_result_t::ok;
}
case 1: {
@ -671,7 +693,7 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
// Recursively call ourselves to expand any remaining command substitutions. The result of this
// recursive call using the tail of the string is inserted into the tail_expand array list
completion_receiver_t tail_expand_recv;
completion_receiver_t tail_expand_recv = out->subreceiver();
expand_cmdsubst(input.substr(tail_begin), ctx, &tail_expand_recv,
errors); // TODO: offset error locations
completion_list_t tail_expand = tail_expand_recv.take();
@ -689,7 +711,9 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
whole_item.append(sub_item2);
whole_item.push_back(INTERNAL_SEPARATOR);
whole_item.append(tail_item.completion);
out->add(std::move(whole_item));
if (!out->add(std::move(whole_item))) {
return append_overflow_error(errors);
}
}
}
@ -893,7 +917,9 @@ expand_result_t expander_t::stage_cmdsubst(wcstring input, completion_receiver_t
size_t cur = 0, start = 0, end;
switch (parse_util_locate_cmdsubst_range(input, &cur, nullptr, &start, &end, true)) {
case 0:
out->add(std::move(input));
if (!out->add(std::move(input))) {
return append_overflow_error(errors);
}
return expand_result_t::ok;
case 1:
append_cmdsub_error(errors, start, L"Command substitutions not allowed");
@ -920,14 +946,14 @@ expand_result_t expander_t::stage_variables(wcstring input, completion_receiver_
i = L'$';
}
}
out->add(std::move(next));
if (!out->add(std::move(next))) {
return append_overflow_error(errors);
}
return expand_result_t::ok;
} else {
size_t size = next.size();
if (!expand_variables(std::move(next), out, size, ctx.vars, errors)) {
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
}
return expand_variables(std::move(next), out, size, ctx.vars, errors);
}
return expand_result_t::ok;
}
expand_result_t expander_t::stage_braces(wcstring input, completion_receiver_t *out) {
@ -939,7 +965,9 @@ expand_result_t expander_t::stage_home_and_self(wcstring input, completion_recei
expand_home_directory(input, ctx.vars);
}
expand_percent_self(input);
out->add(std::move(input));
if (!out->add(std::move(input))) {
return append_overflow_error(errors);
}
return expand_result_t::ok;
}
@ -1007,7 +1035,7 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
}
result = expand_result_t::wildcard_no_match;
completion_receiver_t expanded_recv;
completion_receiver_t expanded_recv = out->subreceiver();
for (const auto &effective_working_dir : effective_working_dirs) {
wildcard_expand_result_t expand_res = wildcard_expand_string(
path_to_expand, effective_working_dir, flags, ctx.cancel_checker, &expanded_recv);
@ -1017,6 +1045,9 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
break;
case wildcard_expand_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;
@ -1028,13 +1059,17 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
[&](const completion_t &a, const completion_t &b) {
return wcsfilecmp_glob(a.completion.c_str(), b.completion.c_str()) < 0;
});
out->add_list(std::move(expanded));
if (!out->add_list(std::move(expanded))) {
result = expand_result_t::error;
}
} else {
// Can't fully justify this check. I think it's that SKIP_WILDCARDS is used when completing
// to mean don't do file expansions, so if we're not doing file expansions, just drop this
// completion on the floor.
if (!(flags & expand_flag::for_completions)) {
out->add(std::move(path_to_expand));
if (!out->add(std::move(path_to_expand))) {
return append_overflow_error(errors);
}
}
}
return result;
@ -1047,7 +1082,9 @@ expand_result_t expander_t::expand_string(wcstring input, completion_receiver_t
"Must have a parser if not skipping command substitutions");
// Early out. If we're not completing, and there's no magic in the input, we're done.
if (!(flags & expand_flag::for_completions) && expand_is_clean(input)) {
out_completions->add(std::move(input));
if (!out_completions->add(std::move(input))) {
return append_overflow_error(errors);
}
return expand_result_t::ok;
}
@ -1062,7 +1099,7 @@ expand_result_t expander_t::expand_string(wcstring input, completion_receiver_t
completion_list_t completions;
append_completion(&completions, input);
completion_receiver_t output_storage;
completion_receiver_t output_storage = out_completions->subreceiver();
expand_result_t total_result = expand_result_t::ok;
for (stage_t stage : stages) {
for (completion_t &comp : completions) {
@ -1101,7 +1138,9 @@ expand_result_t expander_t::expand_string(wcstring input, completion_receiver_t
if (!(flags & expand_flag::skip_home_directories)) {
unexpand_tildes(input, ctx.vars, &completions);
}
out_completions->add_list(std::move(completions));
if (!out_completions->add_list(std::move(completions))) {
total_result = append_overflow_error(errors);
}
}
return total_result;
}

View file

@ -2118,6 +2118,37 @@ static void test_expand() {
popd();
}
static void test_expand_overflow() {
say(L"Testing overflowing expansions");
// Ensure that we have sane limits on number of expansions - see #7497.
// Make a list of 64 elements, then expand it cartesian-style 64 times.
// This is far too large to expand.
wcstring_list_t vals;
wcstring expansion;
for (int i = 1; i <= 64; i++) {
vals.push_back(to_string(i));
expansion.append(L"$bigvar");
}
auto parser = parser_t::principal_parser().shared();
parser->vars().push(true);
int set = parser->vars().set(L"bigvar", ENV_LOCAL, std::move(vals));
do_test(set == ENV_OK);
parse_error_list_t errors;
operation_context_t ctx{parser, parser->vars(), no_cancel};
// We accept only 1024 completions.
completion_receiver_t output{1024};
auto res = expand_string(expansion, &output, expand_flags_t{}, ctx, &errors);
do_test(!errors.empty());
do_test(res == expand_result_t::error);
parser->vars().pop();
}
static void test_fuzzy_match() {
say(L"Testing fuzzy string matching");
// Check that a string fuzzy match has the expected type and case folding.
@ -6131,6 +6162,7 @@ int main(int argc, char **argv) {
if (should_test_function("pcre2_escape")) test_pcre2_escape();
if (should_test_function("lru")) test_lru();
if (should_test_function("expand")) test_expand();
if (should_test_function("expand")) test_expand_overflow();
if (should_test_function("fuzzy_match")) test_fuzzy_match();
if (should_test_function("ifind")) test_ifind();
if (should_test_function("ifind_fuzzy")) test_ifind_fuzzy();

View file

@ -226,6 +226,9 @@ enum class pipeline_position_t {
/// Error message for wildcards with no matches.
#define WILDCARD_ERR_MSG _(L"No matches for wildcard '%ls'. See `help expand`.")
/// Error message when an expansion produces too many results, e.g. `echo /**`.
#define EXPAND_OVERFLOW_ERR_MSG _(L"Too many items produced by '%ls'.")
/// Error when using break outside of loop.
#define INVALID_BREAK_ERR_MSG _(L"'break' while not inside of loop")

View file

@ -253,7 +253,9 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
// Note: out_completion may be empty if the completion really is empty, e.g. tab-completing
// 'foo' when a file 'foo' exists.
complete_flags_t local_flags = flags | (full_replacement ? COMPLETE_REPLACES_TOKEN : 0);
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 true;
} else if (next_wc_char_pos > 0) {
// The literal portion of a wildcard cannot be longer than the string itself,
@ -529,6 +531,8 @@ class wildcard_expander_t {
completion_receiver_t *resolved_completions;
// Whether we have been interrupted.
bool did_interrupt{false};
// Whether we have overflowed.
bool did_overflow{false};
// Whether we have successfully added any completions.
bool did_add{false};
// Whether some parent expansion is fuzzy, and therefore completions always prepend their prefix
@ -562,17 +566,18 @@ class wildcard_expander_t {
const wcstring &prefix);
/// Indicate whether we should cancel wildcard expansion. This latches 'interrupt'.
bool interrupted() {
bool interrupted_or_overflowed() {
did_interrupt = did_interrupt || cancel_checker();
return did_interrupt;
return did_interrupt || did_overflow;
}
void add_expansion_result(wcstring &&result) {
// This function is only for the non-completions case.
assert(!(this->flags & expand_flag::for_completions));
if (this->completion_set.insert(result).second) {
this->resolved_completions->add(std::move(result));
this->did_add = true;
if (!this->resolved_completions->add(std::move(result))) {
this->did_overflow = true;
}
}
}
@ -610,7 +615,7 @@ class wildcard_expander_t {
}
// We stop if we got two or more entries; also stop if we got zero or were interrupted
if (unique_entry.empty() || interrupted()) {
if (unique_entry.empty() || interrupted_or_overflowed()) {
stop_descent = true;
}
@ -716,7 +721,7 @@ class wildcard_expander_t {
};
void wildcard_expander_t::expand_trailing_slash(const wcstring &base_dir, const wcstring &prefix) {
if (interrupted()) {
if (interrupted_or_overflowed()) {
return;
}
@ -731,7 +736,7 @@ void wildcard_expander_t::expand_trailing_slash(const wcstring &base_dir, const
DIR *dir = open_dir(base_dir);
if (dir) {
wcstring next;
while (wreaddir(dir, next) && !interrupted()) {
while (wreaddir(dir, next) && !interrupted_or_overflowed()) {
if (!next.empty() && next.at(0) != L'.') {
this->try_add_completion_result(base_dir + next, next, L"", prefix);
}
@ -746,7 +751,7 @@ void wildcard_expander_t::expand_intermediate_segment(const wcstring &base_dir,
const wchar_t *wc_remainder,
const wcstring &prefix) {
wcstring name_str;
while (!interrupted() && wreaddir_for_dirs(base_dir_fp, &name_str)) {
while (!interrupted_or_overflowed() && wreaddir_for_dirs(base_dir_fp, &name_str)) {
// Note that it's critical we ignore leading dots here, else we may descend into . and ..
if (!wildcard_match(name_str, wc_segment, true)) {
// Doesn't match the wildcard for this segment, skip it.
@ -788,7 +793,7 @@ void wildcard_expander_t::expand_literal_intermediate_segment_with_fuzz(const wc
// Mark that we are fuzzy for the duration of this function
const scoped_push<bool> scoped_fuzzy(&this->has_fuzzy_ancestor, true);
while (!interrupted() && wreaddir_for_dirs(base_dir_fp, &name_str)) {
while (!interrupted_or_overflowed() && wreaddir_for_dirs(base_dir_fp, &name_str)) {
// Don't bother with . and ..
if (name_str == L"." || name_str == L"..") {
continue;
@ -873,7 +878,7 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc,
const wcstring &effective_prefix) {
assert(wc != nullptr);
if (interrupted()) {
if (interrupted_or_overflowed()) {
return;
}

View file

@ -45,6 +45,7 @@ enum class wildcard_expand_result_t {
no_match, /// The wildcard did not match.
match, /// The wildcard did match.
cancel, /// Expansion was cancelled (e.g. control-C).
overflow, /// Expansion produced too many results.
};
wildcard_expand_result_t wildcard_expand_string(const wcstring &wc,
const wcstring &working_directory,