[3/3,v2] Implement completion limiting

Message ID m3vbjy9iqr.fsf@sspiff.org
State New, archived
Headers

Commit Message

Doug Evans Jan. 23, 2015, 5:19 a.m. UTC
  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.
  

Comments

Eli Zaretskii Jan. 23, 2015, 10:15 a.m. UTC | #1
> From: Doug Evans <xdje42@gmail.com>
> Date: Thu, 22 Jan 2015 21:19:24 -0800
> 
> This patch puts the warning at the end.

Thanks.

> +#if 0 //xyzdje
> +  if (max_reached)
> +    {
> +      VEC_safe_push (char_ptr, result,
> +		     xstrprintf (_("%s *** List may be truncated,"
> +				   " max-completions reached. ***"),
> +				 text));
> +    }
> +#endif

This part should probably go away.

> +/* 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. ***");
> +}

I'd prefer a different wording:

  (More completions follow; omitted because max-completions exceeded.)

My problem with your wording is two-fold:

  . "may be truncated" can be interpreted to the effect that GDB
    doesn't know whether truncation really happened; I think it does

  . "reached" is inaccurate; "exceeded" is more accurate

The rest of the change in wording is just to follow the style that I
frequently see in other applications in similar cases.

The documentation parts are OK (but will need an update if you accept
the above suggestion).
  
Doug Evans Jan. 23, 2015, 4:14 p.m. UTC | #2
On Fri, Jan 23, 2015 at 2:15 AM, Eli Zaretskii <eliz@gnu.org> wrote:
>> +/* 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. ***");
>> +}
>
> I'd prefer a different wording:
>
>   (More completions follow; omitted because max-completions exceeded.)
>
> My problem with your wording is two-fold:
>
>   . "may be truncated" can be interpreted to the effect that GDB
>     doesn't know whether truncation really happened; I think it does
>
>   . "reached" is inaccurate; "exceeded" is more accurate
>
> The rest of the change in wording is just to follow the style that I
> frequently see in other applications in similar cases.
>
> The documentation parts are OK (but will need an update if you accept
> the above suggestion).

Actually, the wording is correct for the current implementation.
The patch stops searching when the limit is reached.
It doesn't keep looking for at least one more to see if there are any more.
Is this absolutely critical?  Why?
  
Eli Zaretskii Jan. 23, 2015, 4:34 p.m. UTC | #3
> Date: Fri, 23 Jan 2015 08:14:15 -0800
> From: Doug Evans <xdje42@gmail.com>
> Cc: Gary Benson <gbenson@redhat.com>, 
> 	"gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> 
> The patch stops searching when the limit is reached.
> It doesn't keep looking for at least one more to see if there are any more.
> Is this absolutely critical?  Why?

Because if the user sets the maximum at N, she wants to see N
elements, and if there are exactly N, she shouldn't get any warnings,
IMO.
  
Doug Evans Jan. 23, 2015, 4:56 p.m. UTC | #4
On Fri, Jan 23, 2015 at 8:34 AM, Eli Zaretskii <eliz@gnu.org> wrote:
>> Date: Fri, 23 Jan 2015 08:14:15 -0800
>> From: Doug Evans <xdje42@gmail.com>
>> Cc: Gary Benson <gbenson@redhat.com>,
>>       "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>>
>> The patch stops searching when the limit is reached.
>> It doesn't keep looking for at least one more to see if there are any more.
>> Is this absolutely critical?  Why?
>
> Because if the user sets the maximum at N, she wants to see N
> elements, and if there are exactly N, she shouldn't get any warnings,
> IMO.

How often will there be exactly N?
And in that particular and massively rare case,
once gdb has found N, how much extra work will
be performed searching the entire executable
and all its shared libraries just to verify there are
in fact no more completions?
[because that's what has to happen if
we're to avoid printing *any* message]

The user waits 5 minutes for the entire list and
gets her 200 completions, and wonders
why it took so long.  Then she digs a bit
deeper and finds out they were found
in the first 5 seconds. Ugh.

I don't see the benefit of going to the trouble
of avoiding printing any message when there are
exactly N completions.
If there are exactly N completions and I
see the message, no big deal.
  
Eli Zaretskii Jan. 23, 2015, 8:28 p.m. UTC | #5
> Date: Fri, 23 Jan 2015 08:56:59 -0800
> From: Doug Evans <xdje42@gmail.com>
> Cc: Gary Benson <gbenson@redhat.com>, 
> 	"gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> 
> How often will there be exactly N?

About the same frequency as there will be exactly N-1, I guess.

> And in that particular and massively rare case,
> once gdb has found N, how much extra work will
> be performed searching the entire executable
> and all its shared libraries just to verify there are
> in fact no more completions?
> [because that's what has to happen if
> we're to avoid printing *any* message]
> 
> The user waits 5 minutes for the entire list and
> gets her 200 completions, and wonders
> why it took so long.  Then she digs a bit
> deeper and finds out they were found
> in the first 5 seconds. Ugh.

200 is just a random number.  It's not magic in any way.  So you
could have 199 completions found in the first 5 sec, followed by 5
minutes of waiting for the 200th which is never found.  Ugh.

There's no way around this issue.

> I don't see the benefit of going to the trouble
> of avoiding printing any message when there are
> exactly N completions.

That's fine, but then the option's name and its documentation should
be changed to reflect this.  They currently support what I thought
user will get, not what you describe above.
  
Doug Evans Jan. 24, 2015, 12:32 a.m. UTC | #6
On Fri, Jan 23, 2015 at 12:28 PM, Eli Zaretskii <eliz@gnu.org> wrote:
>> Date: Fri, 23 Jan 2015 08:56:59 -0800
>> From: Doug Evans <xdje42@gmail.com>
>> Cc: Gary Benson <gbenson@redhat.com>,
>>       "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
>>
>> How often will there be exactly N?
>
> About the same frequency as there will be exactly N-1, I guess.

My point is, given any particular value I set for
"set max-completions N", what's the probability
that exactly that N completions will be found?
Rather low I expect.

>> And in that particular and massively rare case,
>> once gdb has found N, how much extra work will
>> be performed searching the entire executable
>> and all its shared libraries just to verify there are
>> in fact no more completions?
>> [because that's what has to happen if
>> we're to avoid printing *any* message]
>>
>> The user waits 5 minutes for the entire list and
>> gets her 200 completions, and wonders
>> why it took so long.  Then she digs a bit
>> deeper and finds out they were found
>> in the first 5 seconds. Ugh.
>
> 200 is just a random number.  It's not magic in any way.  So you
> could have 199 completions found in the first 5 sec, followed by 5
> minutes of waiting for the 200th which is never found.  Ugh.
>
> There's no way around this issue.

But if I, as a user, do "set max-completions 199"
and then find out gdb is looking for 200 completions
I would be disappointed.  I might file a bug report even.

If I do "set max-completions N" I expect gdb to stop looking
when it gets to N.

>> I don't see the benefit of going to the trouble
>> of avoiding printing any message when there are
>> exactly N completions.
>
> That's fine, but then the option's name and its documentation should
> be changed to reflect this.  They currently support what I thought
> user will get, not what you describe above.

The option name and documentation is fine.
If I do "set max-completions 42" I expect
gdb to stop looking when it finds 42 completions.
It's a rather straightforward interpretation of the option's
name IMO.
  
Eli Zaretskii Jan. 24, 2015, 8:50 a.m. UTC | #7
> Date: Fri, 23 Jan 2015 16:32:29 -0800
> From: Doug Evans <xdje42@gmail.com>
> Cc: Gary Benson <gbenson@redhat.com>, 
> 	"gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> 
> On Fri, Jan 23, 2015 at 12:28 PM, Eli Zaretskii <eliz@gnu.org> wrote:
> >> Date: Fri, 23 Jan 2015 08:56:59 -0800
> >> From: Doug Evans <xdje42@gmail.com>
> >> Cc: Gary Benson <gbenson@redhat.com>,
> >>       "gdb-patches@sourceware.org" <gdb-patches@sourceware.org>
> >>
> >> How often will there be exactly N?
> >
> > About the same frequency as there will be exactly N-1, I guess.
> 
> My point is, given any particular value I set for
> "set max-completions N", what's the probability
> that exactly that N completions will be found?
> Rather low I expect.

Rather low, I agree.  But having N-1 is as low as having N.  So the
problem with wasting 99% of the time searching for a candidate that
isn't there is not solved by not searching for the N+1st candidate.

> > 200 is just a random number.  It's not magic in any way.  So you
> > could have 199 completions found in the first 5 sec, followed by 5
> > minutes of waiting for the 200th which is never found.  Ugh.
> >
> > There's no way around this issue.
> 
> But if I, as a user, do "set max-completions 199"
> and then find out gdb is looking for 200 completions
> I would be disappointed.  I might file a bug report even.

When the user sets max-completions to 199, it expects GDB not to
_show_ more than that.  The documentation says:

  @item set max-completions @var{limit}
  @itemx set max-completions unlimited
  Set the maximum number of candidates to be shown during completion.
  [...]                                ^^^^^^^^^^^
  @item show max-completions
  Show the maximum number of candidates to be shown during completion.
                                        ^^^^^^^^^^^

This usually means "find the completions, but don't show me more than
the limit".  If GDB is not going to try to see if there are more than
the limit, then we need to tell that in the docs, otherwise some user
might become disappointed and might file a bug report, claiming that
GDB told it there might be more candidates, when there were none.

> If I do "set max-completions N" I expect gdb to stop looking
> when it gets to N.

Well, at least one frequent user of GDB -- yours truly -- would expect
GDB to actually try at least one more.  So we should be explicit this
is not what GDB does (assuming we are okay with that modus operandi).

> >> I don't see the benefit of going to the trouble
> >> of avoiding printing any message when there are
> >> exactly N completions.
> >
> > That's fine, but then the option's name and its documentation should
> > be changed to reflect this.  They currently support what I thought
> > user will get, not what you describe above.
> 
> The option name and documentation is fine.

See above.
  

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..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. ***");
+}
 
 /* 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);
+	}
+    }
+}
+
+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;