Patchwork [3/3,v2] Implement completion limiting

login
register
mail settings
Submitter Doug Evans
Date Jan. 31, 2015, 11:30 p.m.
Message ID <m3a90yle9b.fsf@sspiff.org>
Download mbox | patch
Permalink /patch/4861/
State New
Headers show

Comments

Doug Evans - Jan. 31, 2015, 11:30 p.m.
Eli Zaretskii <eliz@gnu.org> writes:
>> From: Doug Evans <xdje42@gmail.com>
>> Cc: gbenson@redhat.com,  gdb-patches@sourceware.org
>> Date: Sat, 24 Jan 2015 17:11:24 -0800
>> 
>> +@item set max-completions @var{limit}
>> +@itemx set max-completions unlimited
>> +Set the maximum number of candidates that @value{GDBN} will collect
>> +and show during completion.
>
> I'd prefer saying this more explicitly, for example:
>
>   Set the maximum number of completion candidates.  @value{GDBN} will
>   stop looking for more completions once it collects this many
>   candidates.
>
> WDYT?

Done.

Here is what I committed,
in addition the 1/3 and 2/3 patches of the series.

Thanks Gary for the core bits that made this possible.

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.

Patch

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);
 }
 
-/* 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..287e9dc 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,58 @@  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;
+	}
+    }
+
+  do_cleanups (cleanups);
+
+  return result;
 }
 
 /* Complete on command names.  Used by "help".  */
@@ -1020,6 +1154,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. ***");
+}
 
 /* GDB replacement for rl_display_match_list.
    Readline doesn't provide a clean interface for TUI(curses).
@@ -1413,9 +1556,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 +1645,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 +1667,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 +1677,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 +1696,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 +1725,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);
+	}
+    }
+}
+
+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..3a1239e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -1600,6 +1600,38 @@  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 that @value{GDBN} will collect
+and show during completion.  This is useful when completing on things
+like function names as collecting all the possible candidates can be
+time consuming.
+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 that @value{GDBN} will collect and show
+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;