Re: [PATCH 3/3 v2] Implement completion limiting

Doug Evans
2015-01-23T05:21:16Z

Gary Benson <gbenson@redhat.com> writes:
> Doug Evans wrote:
>> On Sat, Jan 10, 2015 at 1:23 AM, Eli Zaretskii <eliz@gnu.org> wrote:
>> > > From: Doug Evans <dje@google.com>
>> > > Date: Fri, 9 Jan 2015 18:32:01 -0800
>> > > Cc: gdb-patches@sourceware.org
>> > >
>> > > +If the number of possible completions is large, @value{GDBN} will
>> > > +print as much of the list as it has collected, as well as a message
>> > > +indicating that the list may be truncated.
>> > > +
>> > > +@smallexample
>> > > +(@value{GDBP}) b m@key{TAB}@key{TAB}
>> > > +m *** List may be truncated, max-completions reached. ***
>> > > +main
>> > > +<... the rest of the possible completions ...>
>> > > +(@value{GDBP}) b m
>> >
>> > Doesn't the "list may be truncated" message scrolls off the
>> > screen, if the list is long enough? If so, wouldn't it be better
>> > to display that message at the end instead, saying something like
>> >
>> > (More completions follow; max-completions exceeded.)
>>
>> If the user sets a bad height, or if pagination is turned off,
>> possibly.
>>
>> I can put it at the end.
>
> I'd prefer it at the end.

This patch puts the warning at the end.

It replaces 3/3 of this series
(still requires 1/3 and 2/3 of course):

https://sourceware.org/ml/gdb-patches/2014-11/msg00685.html

and requires this patch:

https://sourceware.org/ml/gdb-patches/2015-01/msg00573.html

2015-01-22 Gary Benson <gbenson@redhat.com>
Doug Evans <dje@google.com>

PR cli/9007
PR cli/11920
PR cli/15548
* cli/cli-cmds.c (complete_command): Notify user if max-completions
reached.
* common/common-exceptions.h (enum errors)
<MAX_COMPLETIONS_REACHED_ERROR>: New value.
* completer.h (get_max_completions_reached_message): New declaration.
(max_completions): Likewise.
(completion_tracker_t): New typedef.
(new_completion_tracker): New declaration.
(make_cleanup_free_completion_tracker): Likewise.
(maybe_add_completion_enum): New enum.
(maybe_add_completion): New declaration.
(throw_max_completions_reached_error): Likewise.
* completer.c (max_completions): New global variable.
(new_completion_tracker): New function.
(free_completion_tracker): Likewise.
(make_cleanup_free_completion_tracker): Likewise.
(maybe_add_completions): Likewise.
(throw_max_completions_reached_error): Likewise.
(complete_line): Remove duplicates and limit result to max_completions
entries.
(get_max_completions_reached_message): New function.
(gdb_display_match_list): Handle max_completions.
(_initialize_completer): New declaration and function.
* symtab.c: Include completer.h.
(completion_tracker): New static variable.
(completion_list_add_name): Call maybe_add_completion.
(default_make_symbol_completion_list_break_on_1): Renamed from
default_make_symbol_completion_list_break_on. Maintain
completion_tracker across calls to completion_list_add_name.
(default_make_symbol_completion_list_break_on): New function.
* top.c (init_main): Set rl_completion_display_matches_hook.
* tui/tui-io.c: Include completer.h.
(tui_old_rl_display_matches_hook): New static global.
(tui_rl_display_match_list): Notify user if max-completions reached.
(tui_setup_io): Save/restore rl_completion_display_matches_hook.
* NEWS (New Options): Mention set/show max-completions.

doc/
* gdb.texinfo (Command Completion): Document new
"set/show max-completions" option.

testsuite/
* gdb.base/completion.exp: Disable completion limiting for
existing tests. Add new tests to check completion limiting.
* gdb.linespec/ls-errs.exp: Disable completion limiting.

diff --git a/gdb/NEWS b/gdb/NEWS
index 2d2c941..09a5742 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,15 @@

*** Changes since GDB 7.9

+* New options
+
+set max-completions
+show max-completions
+ Set the maximum number of candidates to be considered during
+ completion. The default value is 200. This limit allows GDB
+ to avoid generating large completion lists, the computation of
+ which can cause the debugger to become temporarily unresponsive.
+
*** Changes in GDB 7.9

* GDB now supports hardware watchpoints on x86 GNU Hurd.
diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c
index e20d8dd..e46f036 100644
--- a/gdb/cli/cli-cmds.c
+++ b/gdb/cli/cli-cmds.c
@@ -236,7 +236,8 @@ help_command (char *command, int from_tty)
help_cmd (command, gdb_stdout);
}
\f
-/* The "complete" command is used by Emacs to implement completion. */
+/* Note: The "complete" command is used by Emacs to implement completion.
+ [Is that why this function writes output with *_unfiltered?] */

static void
complete_command (char *arg, int from_tty)
@@ -247,6 +248,18 @@ complete_command (char *arg, int from_tty)

dont_repeat ();

+ if (max_completions == 0)
+ {
+ /* Only print this for non-mi frontends. An MI frontend may not
+ be able to handle this. */
+ if (!ui_out_is_mi_like_p (current_uiout))
+ {
+ printf_unfiltered (_("max-completions is zero,"
+ " completion is disabled.\n"));
+ }
+ return;
+ }
+
if (arg == NULL)
arg = "";
argpoint = strlen (arg);
@@ -293,6 +306,15 @@ complete_command (char *arg, int from_tty)

xfree (prev);
VEC_free (char_ptr, completions);
+
+ if (size == max_completions)
+ {
+ /* ARG_PREFIX and POINT are included in the output so that emacs
+ will include the message in the output. */
+ printf_unfiltered (_("%s%s %s\n"),
+ arg_prefix, point,
+ get_max_completions_reached_message ());
+ }
}
}

diff --git a/gdb/common/common-exceptions.h b/gdb/common/common-exceptions.h
index 4f60ad8..e349ed0 100644
--- a/gdb/common/common-exceptions.h
+++ b/gdb/common/common-exceptions.h
@@ -99,6 +99,12 @@ enum errors {
/* Requested feature, method, mechanism, etc. is not supported. */
NOT_SUPPORTED_ERROR,

+ /* The number of candidates generated during line completion has
+ reached the user's specified limit. This isn't an error, this exception
+ is used to halt searching for more completions, but for consistency
+ "_ERROR" is appended to the name. */
+ MAX_COMPLETIONS_REACHED_ERROR,
+
/* Add more errors here. */
NR_ERRORS
};
diff --git a/gdb/completer.c b/gdb/completer.c
index 88c8e16..8d27a8b 100644
--- a/gdb/completer.c
+++ b/gdb/completer.c
@@ -781,9 +781,93 @@ complete_line_internal (const char *text,

return list;
}
-/* Generate completions all at once. Returns a vector of strings.
- Each element is allocated with xmalloc. It can also return NULL if
- there are no completions.
+
+/* See completer.h. */
+
+int max_completions = 200;
+
+/* See completer.h. */
+
+completion_tracker_t
+new_completion_tracker (void)
+{
+ if (max_completions <= 0)
+ return NULL;
+
+ return htab_create_alloc (max_completions,
+ htab_hash_string, (htab_eq) streq,
+ NULL, xcalloc, xfree);
+}
+
+/* Cleanup routine to free a completion tracker and reset the pointer
+ to NULL. */
+
+static void
+free_completion_tracker (void *p)
+{
+ completion_tracker_t *tracker_ptr = p;
+
+ htab_delete (*tracker_ptr);
+ *tracker_ptr = NULL;
+}
+
+/* See completer.h. */
+
+struct cleanup *
+make_cleanup_free_completion_tracker (completion_tracker_t *tracker_ptr)
+{
+ if (*tracker_ptr == NULL)
+ return make_cleanup (null_cleanup, NULL);
+
+ return make_cleanup (free_completion_tracker, tracker_ptr);
+}
+
+/* See completer.h. */
+
+enum maybe_add_completion_enum
+maybe_add_completion (completion_tracker_t tracker, char *name)
+{
+ void **slot;
+
+ if (max_completions < 0)
+ return MAYBE_ADD_COMPLETION_OK;
+ if (max_completions == 0)
+ return MAYBE_ADD_COMPLETION_MAX_REACHED;
+
+ gdb_assert (tracker != NULL);
+
+ if (htab_elements (tracker) >= max_completions)
+ return MAYBE_ADD_COMPLETION_MAX_REACHED;
+
+ slot = htab_find_slot (tracker, name, INSERT);
+
+ if (*slot != HTAB_EMPTY_ENTRY)
+ return MAYBE_ADD_COMPLETION_DUPLICATE;
+
+ *slot = name;
+
+ return (htab_elements (tracker) < max_completions
+ ? MAYBE_ADD_COMPLETION_OK
+ : MAYBE_ADD_COMPLETION_OK_MAX_REACHED);
+}
+
+void
+throw_max_completions_reached_error (void)
+{
+ throw_error (MAX_COMPLETIONS_REACHED_ERROR, _("Max completions reached."));
+}
+
+/* Generate completions all at once. Returns a vector of unique strings
+ allocated with xmalloc. Returns NULL if there are no completions
+ or if max_completions is 0. If max_completions is non-negative, this will
+ return at most max_completions + 1 strings.
+
+ If max_completions strings are collected, an extra string is added which
+ is a text message to inform the user that the list may be truncated.
+ This extra string serves two purposes:
+ 1) Inform the user.
+ 2) Prevent readline from being able to find a common prefix to advance
+ point to, since it's working with an incomplete list.

TEXT is the caller's idea of the "word" we are looking at.

@@ -796,8 +880,68 @@ complete_line_internal (const char *text,
VEC (char_ptr) *
complete_line (const char *text, const char *line_buffer, int point)
{
- return complete_line_internal (text, line_buffer,
- point, handle_completions);
+ VEC (char_ptr) *list;
+ VEC (char_ptr) *result = NULL;
+ struct cleanup *cleanups;
+ completion_tracker_t tracker;
+ char *candidate;
+ int ix, max_reached;
+
+ if (max_completions == 0)
+ return NULL;
+ list = complete_line_internal (text, line_buffer, point,
+ handle_completions);
+ if (max_completions < 0)
+ return list;
+
+ tracker = new_completion_tracker ();
+ cleanups = make_cleanup_free_completion_tracker (&tracker);
+ make_cleanup_free_char_ptr_vec (list);
+
+ /* Do a final test for too many completions. Individual completers may
+ do some of this, but are not required to. Duplicates are also removed
+ here. Otherwise the user is left scratching his/her head: readline and
+ complete_command will remove duplicates, and if removal of duplicates
+ there brings the total under max_completions the user may think gdb quit
+ searching too early. */
+
+ for (ix = 0, max_reached = 0;
+ !max_reached && VEC_iterate (char_ptr, list, ix, candidate);
+ ++ix)
+ {
+ enum maybe_add_completion_enum add_status;
+
+ add_status = maybe_add_completion (tracker, candidate);
+
+ switch (add_status)
+ {
+ case MAYBE_ADD_COMPLETION_OK:
+ VEC_safe_push (char_ptr, result, xstrdup (candidate));
+ break;
+ case MAYBE_ADD_COMPLETION_OK_MAX_REACHED:
+ VEC_safe_push (char_ptr, result, xstrdup (candidate));
+ max_reached = 1;
+ break;
+ case MAYBE_ADD_COMPLETION_MAX_REACHED:
+ gdb_assert_not_reached ("more than max completions reached");
+ case MAYBE_ADD_COMPLETION_DUPLICATE:
+ break;
+ }
+ }
+
+#if 0 //xyzdje
+ if (max_reached)
+ {
+ VEC_safe_push (char_ptr, result,
+ xstrprintf (_("%s *** List may be truncated,"
+ " max-completions reached. ***"),
+ text));
+ }
+#endif
+
+ do_cleanups (cleanups);
+
+ return result;
}

/* Complete on command names. Used by "help". */
@@ -1020,6 +1164,15 @@ skip_quoted (const char *str)
{
return skip_quoted_chars (str, NULL, NULL);
}
+
+/* Return a message indicating that the maximum number of completions
+ has been reached and that there may be more. */
+
+const char *
+get_max_completions_reached_message (void)
+{
+ return _("*** List may be truncated, max-completions reached. ***");
+}
\f
/* GDB replacement for rl_display_match_list.
Readline doesn't provide a clean interface for TUI(curses).
@@ -1413,9 +1566,10 @@ gdb_complete_get_screenwidth (const struct match_list_displayer *displayer)
}

/* GDB version of readline/complete.c:rl_display_match_list.
- See gdb_display_match_list for a description of MATCHES, LEN, MAX. */
+ See gdb_display_match_list for a description of MATCHES, LEN, MAX.
+ Returns non-zero if all matches are displayed. */

-static void
+static int
gdb_display_match_list_1 (char **matches, int len, int max,
const struct match_list_displayer *displayer)
{
@@ -1501,7 +1655,7 @@ gdb_display_match_list_1 (char **matches, int len, int max,
{
lines = gdb_display_match_list_pager (lines, displayer);
if (lines < 0)
- return;
+ return 0;
}
}
}
@@ -1523,7 +1677,7 @@ gdb_display_match_list_1 (char **matches, int len, int max,
{
lines = gdb_display_match_list_pager (lines, displayer);
if (lines < 0)
- return;
+ return 0;
}
}
else
@@ -1533,6 +1687,8 @@ gdb_display_match_list_1 (char **matches, int len, int max,
}
displayer->crlf (displayer);
}
+
+ return 1;
}

/* Utility for displaying completion list matches, used by both CLI and TUI.
@@ -1550,6 +1706,13 @@ void
gdb_display_match_list (char **matches, int len, int max,
const struct match_list_displayer *displayer)
{
+ /* Readline will never call this if complete_line returned NULL. */
+ gdb_assert (max_completions != 0);
+
+ /* complete_line will never return more than this. */
+ if (max_completions > 0)
+ gdb_assert (len <= max_completions);
+
if (rl_completion_query_items > 0 && len >= rl_completion_query_items)
{
char msg[100];
@@ -1572,5 +1735,33 @@ gdb_display_match_list (char **matches, int len, int max,
}
}

- gdb_display_match_list_1 (matches, len, max, displayer);
+ if (gdb_display_match_list_1 (matches, len, max, displayer))
+ {
+ /* Note: MAX_COMPLETIONS may be -1 or zero, but LEN is always > 0. */
+ if (len == max_completions)
+ {
+ /* The maximum number of completions has been reached. Warn the user
+ that there may be more. */
+ const char *message = get_max_completions_reached_message ();
+
+ displayer->puts (displayer, message);
+ displayer->crlf (displayer);
+ }
+ }
+}
+\f
+extern initialize_file_ftype _initialize_completer; /* -Wmissing-prototypes */
+
+void
+_initialize_completer (void)
+{
+ add_setshow_zuinteger_unlimited_cmd ("max-completions", no_class,
+ &max_completions, _("\
+Set maximum number of completion candidates."), _("\
+Show maximum number of completion candidates."), _("\
+Use this to limit the number of candidates considered\n\
+during completion. Specifying \"unlimited\" or -1\n\
+disables limiting. Note that setting either no limit or\n\
+a very large limit can make completion slow."),
+ NULL, NULL, &setlist, &showlist);
}
diff --git a/gdb/completer.h b/gdb/completer.h
index dbb1cfb..56e1a2b 100644
--- a/gdb/completer.h
+++ b/gdb/completer.h
@@ -66,6 +66,8 @@ struct match_list_displayer
extern void gdb_display_match_list (char **matches, int len, int max,
const struct match_list_displayer *);

+extern const char *get_max_completions_reached_message (void);
+
extern VEC (char_ptr) *complete_line (const char *text,
const char *line_buffer,
int point);
@@ -112,4 +114,68 @@ extern const char *skip_quoted_chars (const char *, const char *,

extern const char *skip_quoted (const char *);

+/* Maximum number of candidates to consider before the completer
+ bails by throwing MAX_COMPLETIONS_REACHED_ERROR. Negative values
+ disable limiting. */
+
+extern int max_completions;
+
+/* Object to track how many unique completions have been generated.
+ Used to limit the size of generated completion lists. */
+
+typedef htab_t completion_tracker_t;
+
+/* Create a new completion tracker.
+ The result is a hash table to track added completions, or NULL
+ if max_completions <= 0. If max_completions < 0, tracking is disabled.
+ If max_completions == 0, the max is indeed zero. */
+
+extern completion_tracker_t new_completion_tracker (void);
+
+/* Make a cleanup to free a completion tracker, and reset its pointer
+ to NULL. */
+
+extern struct cleanup *make_cleanup_free_completion_tracker
+ (completion_tracker_t *tracker_ptr);
+
+/* Return values for maybe_add_completion. */
+
+enum maybe_add_completion_enum
+{
+ /* NAME has been recorded and max_completions has not been reached,
+ or completion tracking is disabled (max_completions < 0). */
+ MAYBE_ADD_COMPLETION_OK,
+
+ /* NAME has been recorded and max_completions has been reached
+ (thus the caller can stop searching). */
+ MAYBE_ADD_COMPLETION_OK_MAX_REACHED,
+
+ /* max-completions entries has been reached.
+ Whether NAME is a duplicate or not is not determined. */
+ MAYBE_ADD_COMPLETION_MAX_REACHED,
+
+ /* NAME has already been recorded.
+ Note that this is never returned if completion tracking is disabled
+ (max_completions < 0). */
+ MAYBE_ADD_COMPLETION_DUPLICATE
+};
+
+/* Add the completion NAME to the list of generated completions if
+ it is not there already.
+ If max_completions is negative, nothing is done, not even watching
+ for duplicates, and MAYBE_ADD_COMPLETION_OK is always returned.
+
+ If MAYBE_ADD_COMPLETION_MAX_REACHED is returned, callers are required to
+ record at least one more completion. The final list will be pruned to
+ max_completions, but recording at least one more than max_completions is
+ the signal to the completion machinery that too many completions were
+ found. */
+
+extern enum maybe_add_completion_enum
+ maybe_add_completion (completion_tracker_t tracker, char *name);
+
+/* Wrapper to throw MAX_COMPLETIONS_REACHED_ERROR. */
+
+extern void throw_max_completions_reached_error (void);
+
#endif /* defined (COMPLETER_H) */
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index f413e23..90e4a61 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -1600,6 +1600,34 @@ means @kbd{@key{META} ?}. You can type this either by holding down a
key designated as the @key{META} shift on your keyboard (if there is
one) while typing @kbd{?}, or as @key{ESC} followed by @kbd{?}.

+If the number of possible completions is large, @value{GDBN} will
+print as much of the list as it has collected, as well as a message
+indicating that the list may be truncated.
+
+@smallexample
+(@value{GDBP}) b m@key{TAB}@key{TAB}
+main
+<... the rest of the possible completions ...>
+*** List may be truncated, max-completions reached. ***
+(@value{GDBP}) b m
+@end smallexample
+
+@noindent
+This behavior can be controlled with the following commands:
+
+@table @code
+@kindex set max-completions
+@item set max-completions @var{limit}
+@itemx set max-completions unlimited
+Set the maximum number of candidates to be shown during completion.
+The default value is 200. A value of zero disables tab-completion.
+Note that setting either no limit or a very large limit can make
+completion slow.
+@kindex show max-completions
+@item show max-completions
+Show the maximum number of candidates to be shown during completion.
+@end table
+
@cindex quotes in commands
@cindex completion of quoted strings
Sometimes the string you need, while logically a ``word'', may contain
diff --git a/gdb/symtab.c b/gdb/symtab.c
index 94b04fb..1d3d8ee 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -60,6 +60,7 @@
#include "macroscope.h"

#include "parser-defs.h"
+#include "completer.h"

/* Forward declarations for local functions. */

@@ -4309,6 +4310,15 @@ static VEC (char_ptr) *return_val;
completion_list_add_name \
(MSYMBOL_NATURAL_NAME (symbol), (sym_text), (len), (text), (word))

+/* Tracker for how many unique completions have been generated. Used
+ to terminate completion list generation early if the list has grown
+ to a size so large as to be useless. This helps avoid GDB seeming
+ to lock up in the event the user requests to complete on something
+ vague that necessitates the time consuming expansion of many symbol
+ tables. */
+
+static completion_tracker_t completion_tracker;
+
/* Test to see if the symbol specified by SYMNAME (which is already
demangled for C++ symbols) matches SYM_TEXT in the first SYM_TEXT_LEN
characters. If so, add it to the current completion list. */
@@ -4327,6 +4337,7 @@ completion_list_add_name (const char *symname,

{
char *new;
+ enum maybe_add_completion_enum add_status;

if (word == sym_text)
{
@@ -4348,7 +4359,22 @@ completion_list_add_name (const char *symname,
strcat (new, symname);
}

- VEC_safe_push (char_ptr, return_val, new);
+ add_status = maybe_add_completion (completion_tracker, new);
+
+ switch (add_status)
+ {
+ case MAYBE_ADD_COMPLETION_OK:
+ VEC_safe_push (char_ptr, return_val, new);
+ break;
+ case MAYBE_ADD_COMPLETION_OK_MAX_REACHED:
+ VEC_safe_push (char_ptr, return_val, new);
+ throw_max_completions_reached_error ();
+ case MAYBE_ADD_COMPLETION_MAX_REACHED:
+ throw_max_completions_reached_error ();
+ case MAYBE_ADD_COMPLETION_DUPLICATE:
+ xfree (new);
+ break;
+ }
}
}

@@ -4561,11 +4587,11 @@ symtab_expansion_callback (struct compunit_symtab *symtab,
datum->code);
}

-VEC (char_ptr) *
-default_make_symbol_completion_list_break_on (const char *text,
- const char *word,
- const char *break_on,
- enum type_code code)
+static void
+default_make_symbol_completion_list_break_on_1 (const char *text,
+ const char *word,
+ const char *break_on,
+ enum type_code code)
{
/* Problem: All of the symbols have to be copied because readline
frees them. I'm not going to worry about this; hopefully there
@@ -4583,7 +4609,7 @@ default_make_symbol_completion_list_break_on (const char *text,
/* Length of sym_text. */
int sym_text_len;
struct add_name_data datum;
- struct cleanup *back_to;
+ struct cleanup *cleanups;

/* Now look for the symbol we are supposed to complete on. */
{
@@ -4618,7 +4644,7 @@ default_make_symbol_completion_list_break_on (const char *text,
/* A double-quoted string is never a symbol, nor does it make sense
to complete it any other way. */
{
- return NULL;
+ return;
}
else
{
@@ -4654,8 +4680,8 @@ default_make_symbol_completion_list_break_on (const char *text,
}
gdb_assert (sym_text[sym_text_len] == '\0' || sym_text[sym_text_len] == '(');

- return_val = NULL;
- back_to = make_cleanup (do_free_completion_list, &return_val);
+ completion_tracker = new_completion_tracker ();
+ cleanups = make_cleanup_free_completion_tracker (&completion_tracker);

datum.sym_text = sym_text;
datum.sym_text_len = sym_text_len;
@@ -4769,8 +4795,34 @@ default_make_symbol_completion_list_break_on (const char *text,
macro_for_each (macro_user_macros, add_macro_name, &datum);
}

+ do_cleanups (cleanups);
+}
+
+VEC (char_ptr) *
+default_make_symbol_completion_list_break_on (const char *text,
+ const char *word,
+ const char *break_on,
+ enum type_code code)
+{
+ struct cleanup *back_to;
+ volatile struct gdb_exception except;
+
+ return_val = NULL;
+ back_to = make_cleanup (do_free_completion_list, &return_val);
+
+ TRY_CATCH (except, RETURN_MASK_ERROR)
+ {
+ default_make_symbol_completion_list_break_on_1 (text, word,
+ break_on, code);
+ }
+ if (except.reason < 0)
+ {
+ if (except.error != MAX_COMPLETIONS_REACHED_ERROR)
+ throw_exception (except);
+ }
+
discard_cleanups (back_to);
- return (return_val);
+ return return_val;
}

VEC (char_ptr) *
diff --git a/gdb/testsuite/gdb.base/completion.exp b/gdb/testsuite/gdb.base/completion.exp
index 1123b99..f77bfe2 100644
--- a/gdb/testsuite/gdb.base/completion.exp
+++ b/gdb/testsuite/gdb.base/completion.exp
@@ -67,6 +67,7 @@ if ![runto_main] then {
}

set timeout 30
+gdb_test_no_output "set max-completions unlimited"

gdb_test_no_output "complete print values\[0\].x." \
"field completion with invalid field"
@@ -775,4 +776,86 @@ gdb_test_multiple "" "$test" {
}
}

-return 0
+#
+# Completion limiting.
+#
+
+gdb_test_no_output "set max-completions 5"
+
+set test "command-name completion limiting using tab character"
+send_gdb "p\t"
+gdb_test_multiple "" "$test" {
+ -re "^p\\\x07$" {
+ send_gdb "\t"
+ gdb_test_multiple "" "$test" {
+ -re "List may be truncated, max-completions reached.*\r\n$gdb_prompt p$" {
+ # Complete the command and ignore the output to resync
+ # gdb for the next test.
+ send_gdb "\n"
+ gdb_test_multiple "" "$test" {
+ -re "$gdb_prompt $" {
+ pass "$test"
+ }
+ }
+ }
+ -re "$gdb_prompt p$" {
+ # Complete the command and ignore the output to resync
+ # gdb for the next test.
+ send_gdb "\n"
+ gdb_test_multiple "" "$test" {
+ -re "$gdb_prompt $" {
+ fail "$test"
+ }
+ }
+ }
+ }
+ }
+}
+
+set test "command-name completion limiting using complete command"
+send_gdb "complete p\n"
+gdb_test_multiple "" "$test" {
+ -re "List may be truncated, max-completions reached.*\r\n$gdb_prompt $" {
+ pass "$test"
+ }
+}
+
+gdb_test_no_output "set max-completions 3"
+
+set test "symbol-name completion limiting using tab character"
+send_gdb "p marker\t"
+gdb_test_multiple "" "$test" {
+ -re "^p marker\\\x07$" {
+ send_gdb "\t"
+ gdb_test_multiple "" "$test" {
+ -re "List may be truncated, max-completions reached.*\r\n$gdb_prompt p marker$" {
+ # Complete the command and ignore the output to resync
+ # gdb for the next test.
+ send_gdb "\n"
+ gdb_test_multiple "" "$test" {
+ -re "$gdb_prompt $" {
+ pass "$test"
+ }
+ }
+ }
+ -re "$gdb_prompt p marker$" {
+ # Complete the command and ignore the output to resync
+ # gdb for the next test.
+ send_gdb "\n"
+ gdb_test_multiple "" "$test" {
+ -re "$gdb_prompt $" {
+ fail "$test"
+ }
+ }
+ }
+ }
+ }
+}
+
+set test "symbol-name completion limiting using complete command"
+send_gdb "complete p mark\n"
+gdb_test_multiple "" "$test" {
+ -re "List may be truncated, max-completions reached.*\r\n$gdb_prompt $" {
+ pass "$test"
+ }
+}
diff --git a/gdb/testsuite/gdb.linespec/ls-errs.exp b/gdb/testsuite/gdb.linespec/ls-errs.exp
index 322598e..019312c 100644
--- a/gdb/testsuite/gdb.linespec/ls-errs.exp
+++ b/gdb/testsuite/gdb.linespec/ls-errs.exp
@@ -26,6 +26,9 @@ if {[prepare_for_testing $testfile $exefile $srcfile \
# Turn off the pending breakpoint queries.
gdb_test_no_output "set breakpoint pending off"

+# Turn off completion limiting
+gdb_test_no_output "set max-completions unlimited"
+
# We intentionally do not use gdb_breakpoint for these tests.

# Break at 'linespec' and expect the message in ::error_messages indexed by
diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c
index bac76c3..e5cab45 100644
--- a/gdb/tui/tui-io.c
+++ b/gdb/tui/tui-io.c
@@ -132,6 +132,7 @@ static rl_getc_func_t *tui_old_rl_getc_function;
static rl_voidfunc_t *tui_old_rl_redisplay_function;
static rl_vintfunc_t *tui_old_rl_prep_terminal;
static rl_voidfunc_t *tui_old_rl_deprep_terminal;
+static rl_compdisp_func_t *tui_old_rl_display_matches_hook;
static int tui_old_rl_echoing_p;

/* Readline output stream.
@@ -442,6 +443,7 @@ tui_setup_io (int mode)
tui_old_rl_deprep_terminal = rl_deprep_term_function;
tui_old_rl_prep_terminal = rl_prep_term_function;
tui_old_rl_getc_function = rl_getc_function;
+ tui_old_rl_display_matches_hook = rl_completion_display_matches_hook;
tui_old_rl_outstream = rl_outstream;
tui_old_rl_echoing_p = _rl_echoing_p;
rl_redisplay_function = tui_redisplay_readline;
@@ -485,8 +487,8 @@ tui_setup_io (int mode)
rl_deprep_term_function = tui_old_rl_deprep_terminal;
rl_prep_term_function = tui_old_rl_prep_terminal;
rl_getc_function = tui_old_rl_getc_function;
+ rl_completion_display_matches_hook = tui_old_rl_display_matches_hook;
rl_outstream = tui_old_rl_outstream;
- rl_completion_display_matches_hook = 0;
_rl_echoing_p = tui_old_rl_echoing_p;
rl_already_prompted = 0;