From f11a60473a05e2a50424056fa574c2092bd41fea Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Thu, 3 Dec 2020 12:04:17 -0800 Subject: [PATCH] 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. --- src/complete.cpp | 80 +++++++++++++++++++++--------- src/complete.h | 35 ++++++++++--- src/expand.cpp | 111 ++++++++++++++++++++++++++++-------------- src/fish_tests.cpp | 32 ++++++++++++ src/parse_constants.h | 3 ++ src/wildcard.cpp | 27 +++++----- src/wildcard.h | 1 + 7 files changed, 211 insertions(+), 78 deletions(-) diff --git a/src/complete.cpp b/src/complete.cpp index 6ff4d864a..2869191a8 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -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 -> ("echo", "world", "") /// echo hello world -> ("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. } } diff --git a/src/complete.h b/src/complete.h index 6ec605f53..5593ee1e8 100644 --- a/src/complete.h +++ b/src/complete.h @@ -124,22 +124,31 @@ using completion_list_t = std::vector; /// 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::max() instead. + const size_t limit_; }; enum complete_option_type_t { diff --git a/src/expand.cpp b/src/expand.cpp index d14915511..6f3c94021 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -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::vectoradd(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; } diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 5978112f3..8e8b29f05 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -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(); diff --git a/src/parse_constants.h b/src/parse_constants.h index b8a03989f..04656dea1 100644 --- a/src/parse_constants.h +++ b/src/parse_constants.h @@ -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") diff --git a/src/wildcard.cpp b/src/wildcard.cpp index 306cf6ab3..4ab4a9f9f 100644 --- a/src/wildcard.cpp +++ b/src/wildcard.cpp @@ -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 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; } diff --git a/src/wildcard.h b/src/wildcard.h index 268953e88..17dc21983 100644 --- a/src/wildcard.h +++ b/src/wildcard.h @@ -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,