From 2d2e15b63d005064b3a2a1c967048e0275b25c35 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sun, 6 Oct 2019 15:34:43 +0200 Subject: [PATCH] Silently ignore recursive calls to `complete -C` without parameter In e1677148999a54c7f0927fc1f37cef37835b5548 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 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 --- src/builtin_complete.cpp | 10 ++++++++-- src/parser.h | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/builtin_complete.cpp b/src/builtin_complete.cpp index 4d778b560..646c67ccd 100644 --- a/src/builtin_complete.cpp +++ b/src/builtin_complete.cpp @@ -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 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 diff --git a/src/parser.h b/src/parser.h index 09842702d..81ae57d52 100644 --- a/src/parser.h +++ b/src/parser.h @@ -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};