diff options
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/NEWS | 7 | ||||
-rw-r--r-- | gdb/cli/cli-cmds.c | 24 | ||||
-rw-r--r-- | gdb/common/common-exceptions.h | 6 | ||||
-rw-r--r-- | gdb/completer.c | 201 | ||||
-rw-r--r-- | gdb/completer.h | 66 | ||||
-rw-r--r-- | gdb/doc/gdb.texinfo | 32 | ||||
-rw-r--r-- | gdb/symtab.c | 74 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/completion.exp | 85 | ||||
-rw-r--r-- | gdb/testsuite/gdb.linespec/ls-errs.exp | 3 | ||||
-rw-r--r-- | gdb/tui/tui-io.c | 4 |
10 files changed, 478 insertions, 24 deletions
@@ -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. 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 5937439..bfd2788 100644 --- a/gdb/completer.c +++ b/gdb/completer.c @@ -781,9 +781,93 @@ complete_line_internal (const char *text, return list; } -/* Generate completions all at once. Returns a vector of strings. - Each element is allocated with xmalloc. It can also return NULL if - there are no completions. + +/* See completer.h. */ + +int max_completions = 200; + +/* See completer.h. */ + +completion_tracker_t +new_completion_tracker (void) +{ + if (max_completions <= 0) + return NULL; + + return htab_create_alloc (max_completions, + htab_hash_string, (htab_eq) streq, + NULL, xcalloc, xfree); +} + +/* Cleanup routine to free a completion tracker and reset the pointer + to NULL. */ + +static void +free_completion_tracker (void *p) +{ + completion_tracker_t *tracker_ptr = p; + + htab_delete (*tracker_ptr); + *tracker_ptr = NULL; +} + +/* See completer.h. */ + +struct cleanup * +make_cleanup_free_completion_tracker (completion_tracker_t *tracker_ptr) +{ + if (*tracker_ptr == NULL) + return make_cleanup (null_cleanup, NULL); + + return make_cleanup (free_completion_tracker, tracker_ptr); +} + +/* See completer.h. */ + +enum maybe_add_completion_enum +maybe_add_completion (completion_tracker_t tracker, char *name) +{ + void **slot; + + if (max_completions < 0) + return MAYBE_ADD_COMPLETION_OK; + if (max_completions == 0) + return MAYBE_ADD_COMPLETION_MAX_REACHED; + + gdb_assert (tracker != NULL); + + if (htab_elements (tracker) >= max_completions) + return MAYBE_ADD_COMPLETION_MAX_REACHED; + + slot = htab_find_slot (tracker, name, INSERT); + + if (*slot != HTAB_EMPTY_ENTRY) + return MAYBE_ADD_COMPLETION_DUPLICATE; + + *slot = name; + + return (htab_elements (tracker) < max_completions + ? MAYBE_ADD_COMPLETION_OK + : MAYBE_ADD_COMPLETION_OK_MAX_REACHED); +} + +void +throw_max_completions_reached_error (void) +{ + throw_error (MAX_COMPLETIONS_REACHED_ERROR, _("Max completions reached.")); +} + +/* Generate completions all at once. Returns a vector of unique strings + allocated with xmalloc. Returns NULL if there are no completions + or if max_completions is 0. If max_completions is non-negative, this will + return at most max_completions + 1 strings. + + If max_completions strings are collected, an extra string is added which + is a text message to inform the user that the list may be truncated. + This extra string serves two purposes: + 1) Inform the user. + 2) Prevent readline from being able to find a common prefix to advance + point to, since it's working with an incomplete list. TEXT is the caller's idea of the "word" we are looking at. @@ -796,8 +880,58 @@ complete_line_internal (const char *text, VEC (char_ptr) * complete_line (const char *text, const char *line_buffer, int point) { - return complete_line_internal (text, line_buffer, - point, handle_completions); + VEC (char_ptr) *list; + VEC (char_ptr) *result = NULL; + struct cleanup *cleanups; + completion_tracker_t tracker; + char *candidate; + int ix, max_reached; + + if (max_completions == 0) + return NULL; + list = complete_line_internal (text, line_buffer, point, + handle_completions); + if (max_completions < 0) + return list; + + tracker = new_completion_tracker (); + cleanups = make_cleanup_free_completion_tracker (&tracker); + make_cleanup_free_char_ptr_vec (list); + + /* Do a final test for too many completions. Individual completers may + do some of this, but are not required to. Duplicates are also removed + here. Otherwise the user is left scratching his/her head: readline and + complete_command will remove duplicates, and if removal of duplicates + there brings the total under max_completions the user may think gdb quit + searching too early. */ + + for (ix = 0, max_reached = 0; + !max_reached && VEC_iterate (char_ptr, list, ix, candidate); + ++ix) + { + enum maybe_add_completion_enum add_status; + + add_status = maybe_add_completion (tracker, candidate); + + switch (add_status) + { + case MAYBE_ADD_COMPLETION_OK: + VEC_safe_push (char_ptr, result, xstrdup (candidate)); + break; + case MAYBE_ADD_COMPLETION_OK_MAX_REACHED: + VEC_safe_push (char_ptr, result, xstrdup (candidate)); + max_reached = 1; + break; + case MAYBE_ADD_COMPLETION_MAX_REACHED: + gdb_assert_not_reached ("more than max completions reached"); + case MAYBE_ADD_COMPLETION_DUPLICATE: + break; + } + } + + do_cleanups (cleanups); + + return result; } /* Complete on command names. Used by "help". */ @@ -1020,6 +1154,15 @@ skip_quoted (const char *str) { return skip_quoted_chars (str, NULL, NULL); } + +/* Return a message indicating that the maximum number of completions + has been reached and that there may be more. */ + +const char * +get_max_completions_reached_message (void) +{ + return _("*** List may be truncated, max-completions reached. ***"); +} /* GDB replacement for rl_display_match_list. Readline doesn't provide a clean interface for TUI(curses). @@ -1413,9 +1556,10 @@ gdb_complete_get_screenwidth (const struct match_list_displayer *displayer) } /* GDB version of readline/complete.c:rl_display_match_list. - See gdb_display_match_list for a description of MATCHES, LEN, MAX. */ + See gdb_display_match_list for a description of MATCHES, LEN, MAX. + Returns non-zero if all matches are displayed. */ -static void +static int gdb_display_match_list_1 (char **matches, int len, int max, const struct match_list_displayer *displayer) { @@ -1501,7 +1645,7 @@ gdb_display_match_list_1 (char **matches, int len, int max, { lines = gdb_display_match_list_pager (lines, displayer); if (lines < 0) - return; + return 0; } } } @@ -1523,7 +1667,7 @@ gdb_display_match_list_1 (char **matches, int len, int max, { lines = gdb_display_match_list_pager (lines, displayer); if (lines < 0) - return; + return 0; } } else @@ -1533,6 +1677,8 @@ gdb_display_match_list_1 (char **matches, int len, int max, } displayer->crlf (displayer); } + + return 1; } /* Utility for displaying completion list matches, used by both CLI and TUI. @@ -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); } 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 171c61c..aee17d3 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -1600,6 +1600,38 @@ means @kbd{@key{META} ?}. You can type this either by holding down a key designated as the @key{META} shift on your keyboard (if there is one) while typing @kbd{?}, or as @key{ESC} followed by @kbd{?}. +If the number of possible completions is large, @value{GDBN} will +print as much of the list as it has collected, as well as a message +indicating that the list may be truncated. + +@smallexample +(@value{GDBP}) b m@key{TAB}@key{TAB} +main +<... the rest of the possible completions ...> +*** List may be truncated, max-completions reached. *** +(@value{GDBP}) b m +@end smallexample + +@noindent +This behavior can be controlled with the following commands: + +@table @code +@kindex set max-completions +@item set max-completions @var{limit} +@itemx set max-completions unlimited +Set the maximum number of 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 diff --git a/gdb/symtab.c b/gdb/symtab.c index abff265..84e2680 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. */ @@ -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) * 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 831705c..2b5a166 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. @@ -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; |