[3/3,v2] Implement completion limiting

Message ID 21680.36641.315766.209208@ruffy2.mtv.corp.google.com
State New, archived
Headers

Commit Message

Doug Evans Jan. 10, 2015, 2:32 a.m. UTC
  Gary Benson writes:
 > Doug Evans wrote:
 > > Doug Evans writes:
 > > > Gary Benson <gbenson@redhat.com> writes:
 > > > > Doug Evans wrote:
 > > > >> 1) IWBN if, when "Too many possibilities" is hit, the user was still
 > > > >> shown the completions thus far.  I'd rather not have to abort the
 > > > >> command I'm trying to do, increase max-completions, and then try
 > > > >> again (or anything else to try to find what I'm looking for in order
 > > > >> to complete the command).  At least not if I don't have to: the
 > > > >> completions thus far may provide a hint at what I'm looking for.
 > > > >> Plus GDB has already computed them, might as well print them.
 > > > >> Imagine if the total count is MAX+1, the user might find it annoying
 > > > >> to not be shown anything just because the count is one beyond the
 > > > >> max.
 > > > >> So instead of "Too many possibilities", how about printing the
 > > > >> completions thus far and then include a message saying the list is
 > > > >> clipped due to max-completions being reached?  [Maybe readline makes
 > > > >> this difficult, but I think it'd be really nice have. Thoughts?]
 > > > >
 > > > > It's a nice idea but I'm not volunteering to implement it :)
 > > > > I already spent too much time figuring out how to thread things
 > > > > through readline.
 > > > 
 > > > One thought I had was one could add a final completion entry
 > > > that was the message.
 > > > Would that work?
 > > 
 > > I looked into this a bit.
 > > readline provides a hook to print the completion list:
 > > rl_completion_display_matches_hook
 > > and a routine to display the matches:
 > > rl_display_match_list
 > > 
 > > The code in readline/complete.c:display_matches is
 > > pretty straightforward (though they've apparently
 > > forgotten to export a way for the hook to set
 > > rl_display_fixed - we'll want to be as equivalent
 > > as possible), so I think(!) this will be rather easy to do.
 > > 
 > > > One hope I had was that this would be enough:
 > > > 
 > > > >> > +		  rl_crlf ();
 > > > >> > +		  fputs (ex.message, rl_outstream);
 > > > >> > +		  rl_crlf ();
 > > > 
 > > > and that the efforts tui/*.c goes to to support readline would
 > > > make that work regardless of the value of tui_active.
 > > > But I confess I haven't tried it.
 > > > 
 > > > I wouldn't suggest vectorizing the tui interface.
 > > > But I do, at the least, want to understand why this is necessary
 > > > ("this" being the test for tui_active and the different code
 > > > depending on whether it is true or not),
 > > > and if it is then I would at a minimum put this code:
 > > > 
 > > > >> > +#if defined(TUI)
 > > > >> > +	      if (tui_active)
 > > > >> > +		{
 > > > >> > +		  tui_puts ("\n");
 > > > >> > +		  tui_puts (ex.message);
 > > > >> > +		  tui_puts ("\n");
 > > > >> > +		}
 > > > >> > +	      else
 > > > >> > +#endif
 > > > >> > +		{
 > > > >> > +		  rl_crlf ();
 > > > >> > +		  fputs (ex.message, rl_outstream);
 > > > >> > +		  rl_crlf ();
 > > > >> > +		}
 > > > >> > +
 > > > >> > +	      rl_on_new_line ();
 > > 
 > > So that leaves this as just the remaining thing to resolve (AFAICT).
 > > I'll look into this more next week.
 > > I'd really like to get this into 7.9.
 > 
 > If you want it in 7.9 then how about I commit it as it is then submit
 > a followup patch to remove the #ifdef, and you can make your own patch
 > to add whatever functionality you want.  The readline part of this
 > series took a good week to get right and I can guarantee you this will
 > drag past 7.9 if I touch it.

I've played with various approaches, I like some of them,
other's not so much.
This is the one I'm submitting.

It might be possible in the future to consolidate TUI's and CLI's
code for displaying matches, but there's no need to do that now.

The main change in this patch is that gdb displays the matches
it has, as well as notifying the user that the list may be truncated.
I tested this with emacs and it works there too.  To achieve that
the "list may be truncated" message needs to be included with
the text being completed.

There was no test for symbol completion limiting so I added one.

I've played with this in emacs, cli, and tui,
and couldn't find any problems.  Plus the displaying
of the truncated list instead of nothing has been nice.

This replaces part 3 of the patchset.
https://sourceware.org/ml/gdb-patches/2014-11/msg00685.html

2015-01-09  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 (gdb_rl_display_match_list): 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.
	(gdb_get_y_or_n): New function.
	(gdb_rl_display_match_list): Likewise.
	(_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.
  

Comments

Eli Zaretskii Jan. 10, 2015, 9:23 a.m. UTC | #1
> 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.)

The documentation parts are OK, thanks.
  
Doug Evans Jan. 12, 2015, 6:50 p.m. UTC | #2
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.
  
Gary Benson Jan. 15, 2015, 3:39 p.m. UTC | #3
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.

Cheers,
Gary
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 9a668c4..9954229 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -45,7 +45,7 @@ 
      compile code [-raw|-r] [--] [source code]
      compile file [-raw|-r] filename
 
-* New commands
+* New commands (for set/show, see "New options" below)
 
 queue-signal signal-name-or-number
   Queue a signal to be delivered to the thread when it is resumed.
@@ -66,6 +66,15 @@  compile file [-r|-raw] filename
   produced by compiling the source code stored in the filename
   provided.
 
+* 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.
+
 * On resume, GDB now always passes the signal the program had stopped
   for to the thread the signal was sent to, even if the user changed
   threads before resuming.  Previously GDB would often (but not
diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c
index e20d8dd..e769e62 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,13 @@  complete_command (char *arg, int from_tty)
 
       xfree (prev);
       VEC_free (char_ptr, completions);
+
+      if (size == max_completions)
+	{
+	  printf_unfiltered (_("%s%s *** List may be truncated,"
+			       " max-completions reached. ***\n"),
+			     arg_prefix, point);
+	}
     }
 }
 
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 2b6aa87..39cdcec 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,66 @@  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 (max_reached)
+    {
+      VEC_safe_push (char_ptr, result,
+		     xstrprintf (_("%s *** List may be truncated,"
+				   " max-completions reached. ***"),
+				 text));
+    }
+
+  do_cleanups (cleanups);
+
+  return result;
 }
 
 /* Complete on command names.  Used by "help".  */
@@ -1020,3 +1162,90 @@  skip_quoted (const char *str)
 {
   return skip_quoted_chars (str, NULL, NULL);
 }
+
+/* gdb version of readline/complete.c:get_y_or_n.
+   The user must press "y" or "n". Non-zero return means "y" pressed.
+   Also supported: space == 'y', RUBOUT == 'n', ctrl-g == start over.  */
+
+static int
+gdb_get_y_or_n (void)
+{
+  int c;
+
+  for (;;)
+    {
+      RL_SETSTATE (RL_STATE_MOREINPUT);
+      c = rl_read_key ();
+      RL_UNSETSTATE (RL_STATE_MOREINPUT);
+
+      if (c == 'y' || c == 'Y' || c == ' ')
+	return 1;
+      if (c == 'n' || c == 'N' || c == RUBOUT)
+	return 0;
+      if (c == ABORT_CHAR || c < 0)
+	{
+	  /* Note: The arguments to rl_abort are ignored.  */
+	  rl_abort (0, 0);
+	}
+      rl_ding ();
+    }
+}
+
+/* See completer.h.
+   This follows complete.c:display_matches as close as possible.  */
+
+void
+gdb_rl_display_match_list (char **matches, int len, int max)
+{
+  int actual_len = len;
+
+  /* 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 + 1);
+
+  /* If max_completions is reached, complete_line adds a last entry.  */
+  if (len == max_completions + 1)
+    actual_len = len - 1;
+
+  /* If there are many items, then ask the user if she really wants to
+     see them all. */
+  if (rl_completion_query_items > 0 && actual_len >= rl_completion_query_items)
+    {
+      /* We can't use *query here because they wait for <RET> which is
+	 wrong here.  This follows the readline version as closely as possible
+	 for compatibility's sake.  See readline/complete.c.  */
+      rl_crlf ();
+      fprintf (rl_outstream, _("Display all %d possibilities? (y or n)"),
+	       actual_len);
+      fflush (rl_outstream);
+      if (gdb_get_y_or_n () == 0)
+	{
+	  rl_crlf ();
+	  rl_forced_update_display ();
+	  return;
+	}
+    }
+
+  rl_display_match_list (matches, len, max);
+
+  rl_forced_update_display ();
+}
+
+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 8f925fe..dc94caa 100644
--- a/gdb/completer.h
+++ b/gdb/completer.h
@@ -59,6 +59,12 @@  extern char *gdb_completion_word_break_characters (void);
 
 extern void set_gdb_completion_word_break_characters (completer_ftype *fn);
 
+/* rl_completion_display_matches_hook for gdb.
+   We wrap readline's default routine to be able to display an extra line
+   notifying the user that there may be more matches.  */
+
+extern void gdb_rl_display_match_list (char **matches, int len, int max);
+
 /* Exported to linespec.c */
 
 extern const char *skip_quoted_chars (const char *, const char *,
@@ -66,4 +72,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/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/top.c b/gdb/top.c
index b85ea1a..0234ac8 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -1761,6 +1761,7 @@  init_main (void)
   rl_completion_entry_function = readline_line_completion_function;
   rl_completer_word_break_characters = default_word_break_characters ();
   rl_completer_quote_characters = get_gdb_completer_quote_characters ();
+  rl_completion_display_matches_hook = gdb_rl_display_match_list;
   rl_readline_name = "gdb";
   rl_terminal_name = getenv ("TERM");
 
diff --git a/gdb/tui/tui-io.c b/gdb/tui/tui-io.c
index 7e8a3bc..3348a47 100644
--- a/gdb/tui/tui-io.c
+++ b/gdb/tui/tui-io.c
@@ -37,6 +37,7 @@ 
 #include <fcntl.h>
 #include <signal.h>
 #include "filestuff.h"
+#include "completer.h"
 
 #include "gdb_curses.h"
 
@@ -132,6 +133,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.
@@ -412,18 +414,30 @@  tui_rl_display_match_list (char **matches, int len, int max)
   int count, limit, printed_len;
   int i, j, k, l;
   const char *temp;
+  int actual_len = len;
 
   /* Screen dimension correspond to the TUI command window.  */
   int screenwidth = TUI_CMD_WIN->generic.width;
 
+  /* 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 + 1);
+
+  /* If max_completions is reached, complete_line adds a last entry.  */
+  if (len == max_completions + 1)
+    actual_len = len - 1;
+
   /* If there are many items, then ask the user if she really wants to
      see them all.  */
-  if (len >= rl_completion_query_items)
+  if (actual_len >= rl_completion_query_items)
     {
       char msg[256];
 
       xsnprintf (msg, sizeof (msg),
-		 "\nDisplay all %d possibilities? (y or n)", len);
+		 "\nDisplay all %d possibilities? (y or n)", actual_len);
       tui_puts (msg);
       if (get_y_or_n () == 0)
 	{
@@ -521,6 +535,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;
@@ -564,8 +579,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;
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index f4d7132..fe4f2a5 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}
+m *** List may be truncated, max-completions reached. ***
+main
+<... the rest of the possible completions ...>
+(@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/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