Add max-completions parameter, and implement tab-completion limiting.

This commit adds a new exception, MAX_COMPLETIONS_REACHED_ERROR, to be
thrown whenever the completer has generated too many candidates to
be useful.  A new user-settable variable, "max_completions", is added
to control this behaviour.  A top-level completion limit is added to
complete_line_internal, as the final check to ensure the user never
sees too many completions.  An additional limit is added to
default_make_symbol_completion_list_break_on, to halt time-consuming
symbol table expansions.

gdb/ChangeLog:

	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.

gdb/doc/ChangeLog:

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

gdb/testsuite/ChangeLog:

	* 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.
This commit is contained in:
Gary Benson 2015-01-31 15:07:22 -08:00 committed by Doug Evans
parent e11c72c7e4
commit ef0b411a11
10 changed files with 478 additions and 24 deletions

View File

@ -26,6 +26,13 @@ maint flush-symbol-cache
* 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.
maint set symbol-cache-size
maint show symbol-cache-size
Control the size of the symbol cache.

View File

@ -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 ());
}
}
}

View File

@ -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
};

View File

@ -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.
@ -1545,6 +1691,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];
@ -1567,5 +1720,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);
}

View File

@ -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) */

View File

@ -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 completion candidates. @value{GDBN} will
stop looking for more completions once it collects this many candidates.
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

View File

@ -60,6 +60,7 @@
#include "macroscope.h"
#include "parser-defs.h"
#include "completer.h"
/* Forward declarations for local functions. */
@ -5001,6 +5002,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. */
@ -5019,6 +5029,7 @@ completion_list_add_name (const char *symname,
{
char *new;
enum maybe_add_completion_enum add_status;
if (word == sym_text)
{
@ -5040,7 +5051,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;
}
}
}
@ -5253,11 +5279,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
@ -5275,7 +5301,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. */
{
@ -5310,7 +5336,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
{
@ -5346,8 +5372,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;
@ -5461,8 +5487,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) *

View File

@ -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"
}
}

View File

@ -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

View File

@ -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.
@ -468,6 +469,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;
@ -511,8 +513,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;