Silently ignore recursive calls to complete -C without parameter

In e167714899 we allowed recursive calls
to complete. However, some completions use infinite recursion in their
completions and rely on `complete` to silently stop as soon as it is
called recursively twice without parameter (thus completing the
current commandline). For example:

complete -c su -s -xa "(complete -C(commandline -ct))"
su -c <TAB>

Infinite recursion happens because (commandline -ct) is an empty list,
which would print an error message.  This commmit explicitly detects
such recursive calls where `complete` has no parameter and silently
terminates.  This enables above completion (like before raising the
recursion limit) while still allowing legitimate cases with limited
recursion.

Closes #6171
This commit is contained in:
Johannes Altmanninger 2019-10-06 15:34:43 +02:00
parent cf3b24cf62
commit 2d2e15b63d
2 changed files with 11 additions and 2 deletions

View file

@ -336,11 +336,16 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
parser.libdata().transient_commandlines.push_back(do_complete_param);
cleanup_t remove_transient([&] { parser.libdata().transient_commandlines.pop_back(); });
// Allow a limited number of recursive calls to complete (#3474).
if (parser.libdata().builtin_complete_recursion_level >= 24) {
if (parser.libdata().builtin_complete_current_commandline) {
// Prevent accidental recursion (see #6171).
} else if (parser.libdata().builtin_complete_recursion_level >= 24) {
// Allow a limited number of recursive calls to complete (#3474).
streams.err.append_format(L"%ls: maximum recursion depth reached\n", cmd);
} else {
parser.libdata().builtin_complete_recursion_level++;
assert(!parser.libdata().builtin_complete_current_commandline);
if (!have_do_complete_param)
parser.libdata().builtin_complete_current_commandline = true;
std::vector<completion_t> comp;
complete(do_complete_param, &comp, completion_request_t::fuzzy_match, parser.vars(),
@ -379,6 +384,7 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
}
parser.libdata().builtin_complete_recursion_level--;
parser.libdata().builtin_complete_current_commandline = false;
}
} else if (cmd_to_complete.empty() && path.empty()) {
// No arguments specified, meaning we print the definitions of all specified completions

View file

@ -132,6 +132,9 @@ struct library_data_t {
/// Number of recursive calls to builtin_complete().
uint32_t builtin_complete_recursion_level{0};
/// Whether we called builtin_complete -C without parameter.
bool builtin_complete_current_commandline{false};
/// Whether we are currently cleaning processes.
bool is_cleaning_procs{false};