diff options
-rw-r--r-- | gdb/ChangeLog | 22 | ||||
-rw-r--r-- | gdb/cli/cli-utils.c | 14 | ||||
-rw-r--r-- | gdb/cli/cli-utils.h | 14 | ||||
-rw-r--r-- | gdb/stack.c | 222 | ||||
-rw-r--r-- | gdb/stack.h | 5 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 6 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/options.exp | 97 | ||||
-rw-r--r-- | gdb/thread.c | 12 |
8 files changed, 348 insertions, 44 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 08d038b..eb0cb7c 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,27 @@ 2019-06-13 Pedro Alves <palves@redhat.com> + * cli/cli-utils.c (parse_flags_qcs): Use validate_flags_qcs. + (validate_flags_qcs): New. + * cli/cli-utils.h (struct qcs_flags): Change field types to int. + (validate_flags_qcs): Declare. + * stack.c (qcs_flag_option_def, fr_qcs_flags_option_defs): New. + (make_frame_apply_options_def_group): New. + (frame_apply_command_count): Process options with + gdb::option::process_options. + (frame_apply_completer): New. + (frame_apply_level_completer, frame_apply_all_completer) + (frame_apply_completer): New. + (_initialize_stack): Update help of "frame apply", "frame apply + level", "frame apply all" and "faas" to mention supported options + and install command completers. + * stack.h (frame_apply_all_completer): Declare. + * thread.c: Include "stack.h". + (tfaas_command): Add "--". + (_initialize_thread): Update help "tfaas" to mention supported + options and install command completer. + +2019-06-13 Pedro Alves <palves@redhat.com> + * completer.c (complete_nested_command_line): New. (gdb_completion_word_break_characters_throw): Add assertion. * completer.h (complete_nested_command_line): Declare. diff --git a/gdb/cli/cli-utils.c b/gdb/cli/cli-utils.c index 306b69e..30d4091 100644 --- a/gdb/cli/cli-utils.c +++ b/gdb/cli/cli-utils.c @@ -573,8 +573,18 @@ parse_flags_qcs (const char *which_command, const char **str, gdb_assert_not_reached ("int qcs flag out of bound"); } - if (flags->cont && flags->silent) - error (_("%s: -c and -s are mutually exclusive"), which_command); + validate_flags_qcs (which_command, flags); return true; } + +/* See documentation in cli-utils.h. */ + +void +validate_flags_qcs (const char *which_command, qcs_flags *flags) +{ + if (flags->cont && flags->silent) + error (_("%s: -c and -s are mutually exclusive"), which_command); +} + +/* See documentation in cli-utils.h. */ diff --git a/gdb/cli/cli-utils.h b/gdb/cli/cli-utils.h index 41c2356..e6b877d 100644 --- a/gdb/cli/cli-utils.h +++ b/gdb/cli/cli-utils.h @@ -229,13 +229,14 @@ check_for_argument (char **str, const char *arg) such that FLAGS [N - 1] is the valid found flag. */ extern int parse_flags (const char **str, const char *flags); -/* qcs_flags struct regroups the flags parsed by parse_flags_qcs. */ +/* qcs_flags struct groups the -q, -c, and -s flags parsed by "thread + apply" and "frame apply" commands */ struct qcs_flags { - bool quiet = false; - bool cont = false; - bool silent = false; + int quiet = false; + int cont = false; + int silent = false; }; /* A helper function that uses parse_flags to handle the flags qcs : @@ -259,4 +260,9 @@ struct qcs_flags extern bool parse_flags_qcs (const char *which_command, const char **str, qcs_flags *flags); +/* Validate FLAGS. Throws an error if both FLAGS->CONT and + FLAGS->SILENT are true. WHICH_COMMAND is included in the error + message. */ +extern void validate_flags_qcs (const char *which_command, qcs_flags *flags); + #endif /* CLI_CLI_UTILS_H */ diff --git a/gdb/stack.c b/gdb/stack.c index 1cb1fc9..547e82b 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -2823,6 +2823,42 @@ func_command (const char *arg, int from_tty) } } +/* The qcs command line flags for the "frame apply" commands. Keep + this in sync with the "thread apply" commands. */ + +using qcs_flag_option_def + = gdb::option::flag_option_def<qcs_flags>; + +static const gdb::option::option_def fr_qcs_flags_option_defs[] = { + qcs_flag_option_def { + "q", [] (qcs_flags *opt) { return &opt->quiet; }, + N_("Disables printing the frame location information."), + }, + + qcs_flag_option_def { + "c", [] (qcs_flags *opt) { return &opt->cont; }, + N_("Print any error raised by COMMAND and continue."), + }, + + qcs_flag_option_def { + "s", [] (qcs_flags *opt) { return &opt->silent; }, + N_("Silently ignore any errors or empty output produced by COMMAND."), + }, +}; + +/* Create an option_def_group array for all the "frame apply" options, + with FLAGS and SET_BT_OPTS as context. */ + +static inline std::array<gdb::option::option_def_group, 2> +make_frame_apply_options_def_group (qcs_flags *flags, + set_backtrace_options *set_bt_opts) +{ + return {{ + { {fr_qcs_flags_option_defs}, flags }, + { {set_backtrace_option_defs}, set_bt_opts }, + }}; +} + /* Apply a GDB command to all stack frames, or a set of identified frames, or innermost COUNT frames. With a negative COUNT, apply command on outermost -COUNT frames. @@ -2852,10 +2888,13 @@ frame_apply_command_count (const char *which_command, struct frame_info *trailing, int count) { qcs_flags flags; - struct frame_info *fi; + set_backtrace_options set_bt_opts = user_set_backtrace_options; - while (cmd != NULL && parse_flags_qcs (which_command, &cmd, &flags)) - ; + auto group = make_frame_apply_options_def_group (&flags, &set_bt_opts); + gdb::option::process_options + (&cmd, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group); + + validate_flags_qcs (which_command, &flags); if (cmd == NULL || *cmd == '\0') error (_("Please specify a command to apply on the selected frames")); @@ -2866,7 +2905,12 @@ frame_apply_command_count (const char *which_command, these also. */ scoped_restore_current_thread restore_thread; - for (fi = trailing; fi && count--; fi = get_prev_frame (fi)) + /* These options are handled quite deep in the unwind machinery, so + we get to pass them down by swapping globals. */ + scoped_restore restore_set_backtrace_options + = make_scoped_restore (&user_set_backtrace_options, set_bt_opts); + + for (frame_info *fi = trailing; fi && count--; fi = get_prev_frame (fi)) { QUIT; @@ -2909,6 +2953,104 @@ frame_apply_command_count (const char *which_command, } } +/* Completer for the "frame apply ..." commands. */ + +static void +frame_apply_completer (completion_tracker &tracker, const char *text) +{ + const auto group = make_frame_apply_options_def_group (nullptr, nullptr); + if (gdb::option::complete_options + (tracker, &text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, group)) + return; + + complete_nested_command_line (tracker, text); +} + +/* Completer for the "frame apply" commands. */ + +static void +frame_apply_level_cmd_completer (struct cmd_list_element *ignore, + completion_tracker &tracker, + const char *text, const char */*word*/) +{ + /* Do this explicitly because there's an early return below. */ + tracker.set_use_custom_word_point (true); + + number_or_range_parser levels (text); + + /* Skip the LEVEL list to find the options and command args. */ + try + { + while (!levels.finished ()) + { + /* Call for effect. */ + levels.get_number (); + + if (levels.in_range ()) + levels.skip_range (); + } + } + catch (const gdb_exception_error &ex) + { + /* get_number throws if it parses a negative number, for + example. But a seemingly negative number may be the start of + an option instead. */ + } + + const char *cmd = levels.cur_tok (); + + if (cmd == text) + { + /* No level list yet. */ + return; + } + + /* Check if we're past a valid LEVEL already. */ + if (levels.finished () + && cmd > text && !isspace (cmd[-1])) + return; + + /* We're past LEVELs, advance word point. */ + tracker.advance_custom_word_point_by (cmd - text); + text = cmd; + + frame_apply_completer (tracker, text); +} + +/* Completer for the "frame apply all" command. */ + +void +frame_apply_all_cmd_completer (struct cmd_list_element *ignore, + completion_tracker &tracker, + const char *text, const char */*word*/) +{ + frame_apply_completer (tracker, text); +} + +/* Completer for the "frame apply COUNT" command. */ + +static void +frame_apply_cmd_completer (struct cmd_list_element *ignore, + completion_tracker &tracker, + const char *text, const char */*word*/) +{ + const char *cmd = text; + + int count = get_number_trailer (&cmd, 0); + if (count == 0) + return; + + /* Check if we're past a valid COUNT already. */ + if (cmd > text && !isspace (cmd[-1])) + return; + + /* We're past COUNT, advance word point. */ + tracker.advance_custom_word_point_by (cmd - text); + text = cmd; + + frame_apply_completer (tracker, text); +} + /* Implementation of the "frame apply level" command. */ static void @@ -3095,44 +3237,62 @@ A single numerical argument specifies the frame to select."), add_com_alias ("f", "frame", class_stack, 1); -#define FRAME_APPLY_FLAGS_HELP "\ +#define FRAME_APPLY_OPTION_HELP "\ Prints the frame location information followed by COMMAND output.\n\ -FLAG arguments are -q (quiet), -c (continue), -s (silent).\n\ -Flag -q disables printing the frame location information.\n\ -By default, if a COMMAND raises an error, frame apply is aborted.\n\ -Flag -c indicates to print the error and continue.\n\ -Flag -s indicates to silently ignore a COMMAND that raises an error\n\ -or produces no output." - - add_prefix_cmd ("apply", class_stack, frame_apply_command, - _("Apply a command to a number of frames.\n\ -Usage: frame apply COUNT [FLAG]... COMMAND\n\ +\n\ +By default, an error raised during the execution of COMMAND\n\ +aborts \"frame apply\".\n\ +\n\ +Options:\n\ +%OPTIONS%" + + const auto frame_apply_opts + = make_frame_apply_options_def_group (nullptr, nullptr); + + static std::string frame_apply_cmd_help = gdb::option::build_help (N_("\ +Apply a command to a number of frames.\n\ +Usage: frame apply COUNT [OPTION]... COMMAND\n\ With a negative COUNT argument, applies the command on outermost -COUNT frames.\n" -FRAME_APPLY_FLAGS_HELP), - &frame_apply_cmd_list, "frame apply ", 1, &frame_cmd_list); + FRAME_APPLY_OPTION_HELP), + frame_apply_opts); - add_cmd ("all", class_stack, frame_apply_all_command, - _("\ + cmd = add_prefix_cmd ("apply", class_stack, frame_apply_command, + frame_apply_cmd_help.c_str (), + &frame_apply_cmd_list, "frame apply ", 1, + &frame_cmd_list); + set_cmd_completer_handle_brkchars (cmd, frame_apply_cmd_completer); + + static std::string frame_apply_all_cmd_help = gdb::option::build_help (N_("\ Apply a command to all frames.\n\ \n\ -Usage: frame apply all [FLAG]... COMMAND\n" -FRAME_APPLY_FLAGS_HELP), - &frame_apply_cmd_list); +Usage: frame apply all [OPTION]... COMMAND\n" + FRAME_APPLY_OPTION_HELP), + frame_apply_opts); - add_cmd ("level", class_stack, frame_apply_level_command, - _("\ + cmd = add_cmd ("all", class_stack, frame_apply_all_command, + frame_apply_all_cmd_help.c_str (), + &frame_apply_cmd_list); + set_cmd_completer_handle_brkchars (cmd, frame_apply_all_cmd_completer); + + static std::string frame_apply_level_cmd_help = gdb::option::build_help (N_("\ Apply a command to a list of frames.\n\ \n\ -Usage: frame apply level LEVEL... [FLAG]... COMMAND\n\ -ID is a space-separated list of LEVELs of frames to apply COMMAND on.\n" -FRAME_APPLY_FLAGS_HELP), +Usage: frame apply level LEVEL... [OPTION]... COMMAND\n\ +LEVEL is a space-separated list of levels of frames to apply COMMAND on.\n" + FRAME_APPLY_OPTION_HELP), + frame_apply_opts); + + cmd = add_cmd ("level", class_stack, frame_apply_level_command, + frame_apply_level_cmd_help.c_str (), &frame_apply_cmd_list); + set_cmd_completer_handle_brkchars (cmd, frame_apply_level_cmd_completer); - add_com ("faas", class_stack, faas_command, _("\ + cmd = add_com ("faas", class_stack, faas_command, _("\ Apply a command to all frames (ignoring errors and empty output).\n\ -Usage: faas COMMAND\n\ -shortcut for 'frame apply all -s COMMAND'")); - +Usage: faas [OPTION]... COMMAND\n\ +shortcut for 'frame apply all -s [OPTION]... COMMAND'\n\ +See \"help frame apply all\" for available options.")); + set_cmd_completer_handle_brkchars (cmd, frame_apply_all_cmd_completer); add_prefix_cmd ("frame", class_stack, &frame_cmd.base_command, _("\ diff --git a/gdb/stack.h b/gdb/stack.h index 6c6caa9..9ac77c0 100644 --- a/gdb/stack.h +++ b/gdb/stack.h @@ -52,4 +52,9 @@ struct symtab* get_last_displayed_symtab (void); int get_last_displayed_line (void); symtab_and_line get_last_displayed_sal (); +/* Completer for the "frame apply all" command. */ +void frame_apply_all_cmd_completer (struct cmd_list_element *ignore, + completion_tracker &tracker, + const char *text, const char */*word*/); + #endif /* #ifndef STACK_H */ diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index d550142..fbdff33 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,5 +1,11 @@ 2019-06-13 Pedro Alves <palves@redhat.com> + * gdb.base/options.exp (test-frame-apply): New. + (top level): Test print commands with different "frame apply" + prefixes. + +2019-06-13 Pedro Alves <palves@redhat.com> + * lib/completion-support.exp (test_gdb_complete_tab_multiple) (test_gdb_complete_cmd_multiple, test_gdb_complete_multiple): Add 'max_completions' parameter and handle it. diff --git a/gdb/testsuite/gdb.base/options.exp b/gdb/testsuite/gdb.base/options.exp index 5a35074..195bbb1 100644 --- a/gdb/testsuite/gdb.base/options.exp +++ b/gdb/testsuite/gdb.base/options.exp @@ -25,6 +25,9 @@ # - print # - compile print # - backtrace +# - frame apply +# - faas +# - tfaas load_lib completion-support.exp @@ -295,6 +298,79 @@ proc_with_prefix test-backtrace {} { "backtrace (1 + xxx1" } +# Basic option-machinery + "frame apply" command integration tests. +proc_with_prefix test-frame-apply {} { + test_gdb_complete_unique "frame apply all" "frame apply all" + + gdb_test "frame apply level 0-" \ + "Please specify a command to apply on the selected frames" + test_gdb_complete_none "frame apply level 0-" + + foreach cmd { + "frame apply all" + "frame apply 1" + "frame apply level 0" + "faas" + "tfaas" + } { + test_gdb_completion_offers_commands "$cmd " + + # tfaas is silent on command error by design. This procedure + # hides that aspect. EXPECTED_RE is only considered when not + # testing with "faas"/"tfaas". + proc test_error_cmd {cmd arg expected_re} { + if {$cmd == "tfaas"} { + gdb_test_no_output "$cmd$arg" + } else { + gdb_test "$cmd$arg" $expected_re + } + } + # Same, but for tests where both "faas" and "tfaas" are + # expected to be silent. + proc test_error_cmd2 {cmd arg expected_re} { + if {$cmd == "tfaas" || $cmd == "faas"} { + gdb_test_no_output "$cmd$arg" + } else { + gdb_test "$cmd$arg" $expected_re + } + } + + test_error_cmd $cmd " -" "Ambiguous option at: -" + test_gdb_complete_multiple "$cmd " "-" "" { + "-c" + "-past-entry" + "-past-main" + "-q" + "-s" + } + + with_test_prefix "no-trailing-space" { + test_error_cmd $cmd " --" \ + "Please specify a command to apply on the selected frames" + test_gdb_complete_unique "$cmd --" "$cmd --" + } + + with_test_prefix "trailing-space" { + test_error_cmd $cmd " -- " \ + "Please specify a command to apply on the selected frames" + test_gdb_completion_offers_commands "$cmd -- " + } + + # '-' is a valid TUI command. + test_error_cmd2 $cmd " -- -" \ + "Cannot enable the TUI when output is not a terminal" + test_gdb_complete_unique \ + "$cmd -- -" \ + "$cmd -- -" + + test_error_cmd2 $cmd " -foo" \ + "Undefined command: \"-foo\". Try \"help\"\\." + test_gdb_complete_none "$cmd -foo" + + test_gdb_completion_offers_commands "$cmd -s " + } +} + # Miscellaneous tests. proc_with_prefix test-misc {variant} { global all_options @@ -731,13 +807,28 @@ foreach_with_prefix cmd { test-enum $cmd } -# Run the print integration tests. -test-print "" +# Run the print integration tests, both as "standalone", and under +# "frame apply". The latter checks that the "frame apply ... COMMAND" +# commands recurse the completion machinery for COMMAND completion +# correctly. +foreach prefix { + "" + "frame apply all " + "frame apply 1 " + "frame apply level 0 " +} { + test-print $prefix +} -# Same for "compile print". +# Same for "compile print". Not really a wrapper prefix command like +# "frame apply", but similar enough that we test pretty much the same +# things. if ![skip_compile_feature_tests] { test-print "compile " } # Basic "backtrace" integration tests. test-backtrace + +# Basic "frame apply" integration tests. +test-frame-apply diff --git a/gdb/thread.c b/gdb/thread.c index a84dbf9..24906fa 100644 --- a/gdb/thread.c +++ b/gdb/thread.c @@ -46,6 +46,7 @@ #include <algorithm> #include "common/gdb_optional.h" #include "inline-frame.h" +#include "stack.h" /* Definition of struct thread_info exported to gdbthread.h. */ @@ -1653,7 +1654,7 @@ static void tfaas_command (const char *cmd, int from_tty) { std::string expanded - = std::string ("thread apply all -s frame apply all -s ") + cmd; + = std::string ("thread apply all -s -- frame apply all -s ") + cmd; execute_command (expanded.c_str (), from_tty); } @@ -1938,6 +1939,7 @@ void _initialize_thread (void) { static struct cmd_list_element *thread_apply_list = NULL; + cmd_list_element *c; add_info ("threads", info_threads_command, _("Display currently known threads.\n\ @@ -1983,10 +1985,12 @@ Apply a command to all threads (ignoring errors and empty output).\n\ Usage: taas COMMAND\n\ shortcut for 'thread apply all -s COMMAND'")); - add_com ("tfaas", class_run, tfaas_command, _("\ + c = add_com ("tfaas", class_run, tfaas_command, _("\ Apply a command to all frames of all threads (ignoring errors and empty output).\n\ -Usage: tfaas COMMAND\n\ -shortcut for 'thread apply all -s frame apply all -s COMMAND'")); +Usage: tfaas [OPTION]... COMMAND\n\ +shortcut for 'thread apply all -s -- frame apply all -s [OPTION]... COMMAND'\n\ +See \"help frame apply all\" for available options.")); + set_cmd_completer_handle_brkchars (c, frame_apply_all_cmd_completer); add_cmd ("name", class_run, thread_name_command, _("Set the current thread's name.\n\ |