diff options
-rw-r--r-- | gdb/ChangeLog | 27 | ||||
-rw-r--r-- | gdb/Makefile.in | 2 | ||||
-rw-r--r-- | gdb/cli/cli-decode.c | 5 | ||||
-rw-r--r-- | gdb/cli/cli-decode.h | 4 | ||||
-rw-r--r-- | gdb/cli/cli-option.c | 724 | ||||
-rw-r--r-- | gdb/cli/cli-option.h | 335 | ||||
-rw-r--r-- | gdb/cli/cli-setshow.c | 280 | ||||
-rw-r--r-- | gdb/cli/cli-setshow.h | 27 | ||||
-rw-r--r-- | gdb/cli/cli-utils.c | 51 | ||||
-rw-r--r-- | gdb/cli/cli-utils.h | 18 | ||||
-rw-r--r-- | gdb/maint-test-options.c | 461 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 5 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/options.c | 33 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/options.exp | 554 |
14 files changed, 2408 insertions, 118 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 73fd65e..8c9eed4 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,5 +1,32 @@ 2019-06-13 Pedro Alves <palves@redhat.com> + * Makefile.in (SUBDIR_CLI_SRCS): Add cli/cli-option.c. + (COMMON_SFILES): Add maint-test-settings.c. + * cli/cli-decode.c (boolean_enums): New global, factored out from + ... + (add_setshow_boolean_cmd): ... here. + * cli/cli-decode.h (boolean_enums): Declare. + * cli/cli-option.c: New file. + * cli/cli-option.h: New file. + * cli/cli-setshow.c (parse_cli_boolean_value(const char **)): New, + factored out from ... + (parse_cli_boolean_value(const char *)): ... this. + (is_unlimited_literal): Change parameter type to pointer to + pointer. Adjust and advance ARG pointer. + (parse_cli_var_uinteger, parse_cli_var_zuinteger_unlimited) + (parse_cli_var_enum): New, factored out from ... + (do_set_command): ... this. Adjust. + * cli/cli-setshow.h (parse_cli_boolean_value) + (parse_cli_var_uinteger, parse_cli_var_zuinteger_unlimited) + (parse_cli_var_enum): Declare. + * cli/cli-utils.c: Include "cli/cli-option.h". + (get_ulongest): New. + * cli/cli-utils.h (get_ulongest): Declare. + (check_for_argument): New overloads. + * maint-test-options.c: New file. + +2019-06-13 Pedro Alves <palves@redhat.com> + * cli/cli-utils.c (number_or_range_parser::get_number): Do not parse a range if "-" is at the end of the string. diff --git a/gdb/Makefile.in b/gdb/Makefile.in index beb69b5..03a5159 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -241,6 +241,7 @@ SUBDIR_CLI_SRCS = \ cli/cli-dump.c \ cli/cli-interp.c \ cli/cli-logging.c \ + cli/cli-option.c \ cli/cli-script.c \ cli/cli-setshow.c \ cli/cli-style.c \ @@ -1063,6 +1064,7 @@ COMMON_SFILES = \ macrotab.c \ main.c \ maint.c \ + maint-test-options.c \ maint-test-settings.c \ mdebugread.c \ mem-break.c \ diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c index 30f79b5..d739a0d 100644 --- a/gdb/cli/cli-decode.c +++ b/gdb/cli/cli-decode.c @@ -551,6 +551,7 @@ add_setshow_enum_cmd (const char *name, set_cmd_context (show, context); } +/* See cli-decode.h. */ const char * const auto_boolean_enums[] = { "on", "off", "auto", NULL }; /* Add an auto-boolean command named NAME to both the set and show @@ -578,6 +579,9 @@ add_setshow_auto_boolean_cmd (const char *name, c->enums = auto_boolean_enums; } +/* See cli-decode.h. */ +const char * const boolean_enums[] = { "on", "off", NULL }; + /* Add element named NAME to both the set and show command LISTs (the list for set/show or some sublist thereof). CLASS is as in add_cmd. VAR is address of the variable which will contain the @@ -591,7 +595,6 @@ add_setshow_boolean_cmd (const char *name, enum command_class theclass, int *var struct cmd_list_element **set_list, struct cmd_list_element **show_list) { - static const char *boolean_enums[] = { "on", "off", NULL }; struct cmd_list_element *c; add_setshow_cmd_full (name, theclass, var_boolean, var, diff --git a/gdb/cli/cli-decode.h b/gdb/cli/cli-decode.h index a9f9cbf..05280d9 100644 --- a/gdb/cli/cli-decode.h +++ b/gdb/cli/cli-decode.h @@ -261,6 +261,10 @@ extern void not_just_help_class_command (const char *arg, int from_tty); extern void print_doc_line (struct ui_file *, const char *); +/* The enums of boolean commands. */ +extern const char * const boolean_enums[]; + +/* The enums of auto-boolean commands. */ extern const char * const auto_boolean_enums[]; /* Verify whether a given cmd_list_element is a user-defined command. diff --git a/gdb/cli/cli-option.c b/gdb/cli/cli-option.c new file mode 100644 index 0000000..9a53ec0 --- /dev/null +++ b/gdb/cli/cli-option.c @@ -0,0 +1,724 @@ +/* CLI options framework, for GDB. + + Copyright (C) 2017-2019 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "cli/cli-option.h" +#include "cli/cli-decode.h" +#include "cli/cli-utils.h" +#include "cli/cli-setshow.h" +#include "command.h" +#include <vector> + +namespace gdb { +namespace option { + +/* An option's value. Which field is active depends on the option's + type. */ +union option_value +{ + /* For var_boolean options. */ + bool boolean; + + /* For var_uinteger options. */ + unsigned int uinteger; + + /* For var_zuinteger_unlimited options. */ + int integer; + + /* For var_enum options. */ + const char *enumeration; +}; + +/* Holds an options definition and its value. */ +struct option_def_and_value +{ + /* The option definition. */ + const option_def &option; + + /* A context. */ + void *ctx; + + /* The option's value, if any. */ + gdb::optional<option_value> value; +}; + +/* Info passed around when handling completion. */ +struct parse_option_completion_info +{ + /* The completion word. */ + const char *word; + + /* The tracker. */ + completion_tracker &tracker; +}; + +/* If ARGS starts with "-", look for a "--" delimiter. If one is + found, then interpret everything up until the "--" as command line + options. Otherwise, interpret unknown input as the beginning of + the command's operands. */ + +static const char * +find_end_options_delimiter (const char *args) +{ + if (args[0] == '-') + { + const char *p = args; + + p = skip_spaces (p); + while (*p) + { + if (check_for_argument (&p, "--")) + return p; + else + p = skip_to_space (p); + p = skip_spaces (p); + } + } + + return nullptr; +} + +/* Complete TEXT/WORD on all options in OPTIONS_GROUP. */ + +static void +complete_on_options (gdb::array_view<const option_def_group> options_group, + completion_tracker &tracker, + const char *text, const char *word) +{ + size_t textlen = strlen (text); + for (const auto &grp : options_group) + for (const auto &opt : grp.options) + if (strncmp (opt.name, text, textlen) == 0) + { + tracker.add_completion + (make_completion_match_str (opt.name, text, word)); + } +} + +/* See cli-option.h. */ + +void +complete_on_all_options (completion_tracker &tracker, + gdb::array_view<const option_def_group> options_group) +{ + static const char opt[] = "-"; + complete_on_options (options_group, tracker, opt + 1, opt); +} + +/* Parse ARGS, guided by OPTIONS_GROUP. HAVE_DELIMITER is true if the + whole ARGS line included the "--" options-terminator delimiter. */ + +static gdb::optional<option_def_and_value> +parse_option (gdb::array_view<const option_def_group> options_group, + process_options_mode mode, + bool have_delimiter, + const char **args, + parse_option_completion_info *completion = nullptr) +{ + if (*args == nullptr) + return {}; + else if (**args != '-') + { + if (have_delimiter) + error (_("Unrecognized option at: %s"), *args); + return {}; + } + else if (check_for_argument (args, "--")) + return {}; + + /* Skip the initial '-'. */ + const char *arg = *args + 1; + + const char *after = skip_to_space (arg); + size_t len = after - arg; + const option_def *match = nullptr; + void *match_ctx = nullptr; + + for (const auto &grp : options_group) + { + for (const auto &o : grp.options) + { + if (strncmp (o.name, arg, len) == 0) + { + if (match != nullptr) + { + if (completion != nullptr && arg[len] == '\0') + { + complete_on_options (options_group, + completion->tracker, + arg, completion->word); + return {}; + } + + error (_("Ambiguous option at: -%s"), arg); + } + + match = &o; + match_ctx = grp.ctx; + + if ((isspace (arg[len]) || arg[len] == '\0') + && strlen (o.name) == len) + break; /* Exact match. */ + } + } + } + + if (match == nullptr) + { + if (have_delimiter || mode != PROCESS_OPTIONS_UNKNOWN_IS_OPERAND) + error (_("Unrecognized option at: %s"), *args); + + return {}; + } + + if (completion != nullptr && arg[len] == '\0') + { + complete_on_options (options_group, completion->tracker, + arg, completion->word); + return {}; + } + + *args += 1 + len; + *args = skip_spaces (*args); + if (completion != nullptr) + completion->word = *args; + + switch (match->type) + { + case var_boolean: + { + if (!match->have_argument) + { + option_value val; + val.boolean = true; + return option_def_and_value {*match, match_ctx, val}; + } + + const char *val_str = *args; + int res; + + if (**args == '\0' && completion != nullptr) + { + /* Complete on both "on/off" and more options. */ + + if (mode == PROCESS_OPTIONS_REQUIRE_DELIMITER) + { + complete_on_enum (completion->tracker, + boolean_enums, val_str, val_str); + complete_on_all_options (completion->tracker, options_group); + } + return option_def_and_value {*match, match_ctx}; + } + else if (**args == '-') + { + /* Treat: + "cmd -boolean-option -another-opt..." + as: + "cmd -boolean-option on -another-opt..." + */ + res = 1; + } + else if (**args == '\0') + { + /* Treat: + (1) "cmd -boolean-option " + as: + (1) "cmd -boolean-option on" + */ + res = 1; + } + else + { + res = parse_cli_boolean_value (args); + if (res < 0) + { + const char *end = skip_to_space (*args); + if (completion != nullptr) + { + if (*end == '\0') + { + complete_on_enum (completion->tracker, + boolean_enums, val_str, val_str); + return option_def_and_value {*match, match_ctx}; + } + } + + if (have_delimiter) + error (_("Value given for `-%s' is not a boolean: %.*s"), + match->name, (int) (end - val_str), val_str); + /* The user didn't separate options from operands + using "--", so treat this unrecognized value as the + start of the operands. This makes "frame apply all + -past-main CMD" work. */ + return option_def_and_value {*match, match_ctx}; + } + else if (completion != nullptr && **args == '\0') + { + /* While "cmd -boolean [TAB]" only offers "on" and + "off", the boolean option actually accepts "1", + "yes", etc. as boolean values. We complete on all + of those instead of BOOLEAN_ENUMS here to make + these work: + + "p -object 1[TAB]" -> "p -object 1 " + "p -object ye[TAB]" -> "p -object yes " + + Etc. Note that it's important that the space is + auto-appended. Otherwise, if we only completed on + on/off here, then it might look to the user like + "1" isn't valid, like: + "p -object 1[TAB]" -> "p -object 1" (i.e., nothing happens). + */ + static const char *const all_boolean_enums[] = { + "on", "off", + "yes", "no", + "enable", "disable", + "0", "1", + nullptr, + }; + complete_on_enum (completion->tracker, all_boolean_enums, + val_str, val_str); + return {}; + } + } + + option_value val; + val.boolean = res; + return option_def_and_value {*match, match_ctx, val}; + } + case var_uinteger: + case var_zuinteger_unlimited: + { + if (completion != nullptr) + { + if (**args == '\0') + { + /* Convenience to let the user know what the option + can accept. Note there's no common prefix between + the strings on purpose, so that readline doesn't do + a partial match. */ + completion->tracker.add_completion + (make_unique_xstrdup ("NUMBER")); + completion->tracker.add_completion + (make_unique_xstrdup ("unlimited")); + return {}; + } + else if (startswith ("unlimited", *args)) + { + completion->tracker.add_completion + (make_unique_xstrdup ("unlimited")); + return {}; + } + } + + if (match->type == var_zuinteger_unlimited) + { + option_value val; + val.integer = parse_cli_var_zuinteger_unlimited (args, false); + return option_def_and_value {*match, match_ctx, val}; + } + else + { + option_value val; + val.uinteger = parse_cli_var_uinteger (match->type, args, false); + return option_def_and_value {*match, match_ctx, val}; + } + } + case var_enum: + { + if (completion != nullptr) + { + const char *after_arg = skip_to_space (*args); + if (*after_arg == '\0') + { + complete_on_enum (completion->tracker, + match->enums, *args, *args); + *args = after_arg; + + option_value val; + val.enumeration = nullptr; + return option_def_and_value {*match, match_ctx, val}; + } + } + + if (check_for_argument (args, "--")) + { + /* Treat e.g., "backtrace -entry-values --" as if there + was no argument after "-entry-values". This makes + parse_cli_var_enum throw an error with a suggestion of + what are the valid options. */ + args = nullptr; + } + + option_value val; + val.enumeration = parse_cli_var_enum (args, match->enums); + return option_def_and_value {*match, match_ctx, val}; + } + + default: + /* Not yet. */ + gdb_assert_not_reached (_("option type not supported")); + } + + return {}; +} + +/* See cli-option.h. */ + +bool +complete_options (completion_tracker &tracker, + const char **args, + process_options_mode mode, + gdb::array_view<const option_def_group> options_group) +{ + const char *text = *args; + + tracker.set_use_custom_word_point (true); + + const char *delimiter = find_end_options_delimiter (text); + bool have_delimiter = delimiter != nullptr; + + if (text[0] == '-' && (!have_delimiter || *delimiter == '\0')) + { + parse_option_completion_info completion_info {nullptr, tracker}; + + while (1) + { + *args = skip_spaces (*args); + completion_info.word = *args; + + if (strcmp (*args, "-") == 0) + { + complete_on_options (options_group, tracker, *args + 1, + completion_info.word); + } + else if (strcmp (*args, "--") == 0) + { + tracker.add_completion (make_unique_xstrdup (*args)); + } + else if (**args == '-') + { + gdb::optional<option_def_and_value> ov + = parse_option (options_group, mode, have_delimiter, + args, &completion_info); + if (!ov && !tracker.have_completions ()) + { + tracker.advance_custom_word_point_by (*args - text); + return mode == PROCESS_OPTIONS_REQUIRE_DELIMITER; + } + + if (ov + && ov->option.type == var_boolean + && !ov->value.has_value ()) + { + /* Looked like a boolean option, but we failed to + parse the value. If this command requires a + delimiter, this value can't be the start of the + operands, so return true. Otherwise, if the + command doesn't require a delimiter return false + so that the caller tries to complete on the + operand. */ + tracker.advance_custom_word_point_by (*args - text); + return mode == PROCESS_OPTIONS_REQUIRE_DELIMITER; + } + + /* If we parsed an option with an argument, and reached + the end of the input string with no trailing space, + return true, so that our callers don't try to + complete anything by themselves. E.g., this makes it + so that with: + + (gdb) frame apply all -limit 10[TAB] + + we don't try to complete on command names. */ + if (ov + && !tracker.have_completions () + && **args == '\0' + && *args > text && !isspace ((*args)[-1])) + { + tracker.advance_custom_word_point_by + (*args - text); + return true; + } + } + else + { + tracker.advance_custom_word_point_by + (completion_info.word - text); + + /* If the command requires a delimiter, but we haven't + seen one, then return true, so that the caller + doesn't try to complete on whatever follows options, + which for these commands should only be done if + there's a delimiter. */ + if (mode == PROCESS_OPTIONS_REQUIRE_DELIMITER + && !have_delimiter) + { + /* If we reached the end of the input string, then + offer all options, since that's all the user can + type (plus "--"). */ + if (completion_info.word[0] == '\0') + complete_on_all_options (tracker, options_group); + return true; + } + else + return false; + } + + if (tracker.have_completions ()) + { + tracker.advance_custom_word_point_by + (completion_info.word - text); + return true; + } + } + } + else if (delimiter != nullptr) + { + tracker.advance_custom_word_point_by (delimiter - text); + *args = delimiter; + return false; + } + + return false; +} + +/* See cli-option.h. */ + +bool +process_options (const char **args, + process_options_mode mode, + gdb::array_view<const option_def_group> options_group) +{ + if (*args == nullptr) + return false; + + /* If ARGS starts with "-", look for a "--" sequence. If one is + found, then interpret everything up until the "--" as + 'gdb::option'-style command line options. Otherwise, interpret + ARGS as possibly the command's operands. */ + bool have_delimiter = find_end_options_delimiter (*args) != nullptr; + + if (mode == PROCESS_OPTIONS_REQUIRE_DELIMITER && !have_delimiter) + return false; + + bool processed_any = false; + + while (1) + { + *args = skip_spaces (*args); + + auto ov = parse_option (options_group, mode, have_delimiter, args); + if (!ov) + { + if (processed_any) + return true; + return false; + } + + processed_any = true; + + switch (ov->option.type) + { + case var_boolean: + { + bool value = ov->value.has_value () ? ov->value->boolean : true; + *ov->option.var_address.boolean (ov->option, ov->ctx) = value; + } + break; + case var_uinteger: + *ov->option.var_address.uinteger (ov->option, ov->ctx) + = ov->value->uinteger; + break; + case var_zuinteger_unlimited: + *ov->option.var_address.integer (ov->option, ov->ctx) + = ov->value->integer; + break; + case var_enum: + *ov->option.var_address.enumeration (ov->option, ov->ctx) + = ov->value->enumeration; + break; + default: + gdb_assert_not_reached ("unhandled option type"); + } + } +} + +/* Helper for build_help. Return a fragment of a help string showing + OPT's possible values. Returns NULL if OPT doesn't take an + argument. */ + +static const char * +get_val_type_str (const option_def &opt, std::string &buffer) +{ + if (!opt.have_argument) + return nullptr; + + switch (opt.type) + { + case var_boolean: + return "[on|off]"; + case var_uinteger: + case var_zuinteger_unlimited: + return "NUMBER|unlimited"; + case var_enum: + { + buffer = ""; + for (size_t i = 0; opt.enums[i] != nullptr; i++) + { + if (i != 0) + buffer += "|"; + buffer += opt.enums[i]; + } + return buffer.c_str (); + } + default: + return nullptr; + } +} + +/* Helper for build_help. Appends an indented version of DOC into + HELP. */ + +static void +append_indented_doc (const char *doc, std::string &help) +{ + const char *p = doc; + const char *n = strchr (p, '\n'); + + while (n != nullptr) + { + help += " "; + help.append (p, n - p + 1); + p = n + 1; + n = strchr (p, '\n'); + } + help += " "; + help += p; + help += '\n'; +} + +/* Fill HELP with an auto-generated "help" string fragment for + OPTIONS. */ + +static void +build_help_option (gdb::array_view<const option_def> options, + std::string &help) +{ + std::string buffer; + + for (const auto &o : options) + { + if (o.set_doc == nullptr) + continue; + + help += " -"; + help += o.name; + + const char *val_type_str = get_val_type_str (o, buffer); + if (val_type_str != nullptr) + { + help += ' '; + help += val_type_str; + } + help += "\n"; + append_indented_doc (o.set_doc, help); + if (o.help_doc != nullptr) + append_indented_doc (o.help_doc, help); + help += '\n'; + } +} + +/* See cli-option.h. */ + +std::string +build_help (const char *help_tmpl, + gdb::array_view<const option_def_group> options_group) +{ + std::string help_str; + + const char *p = strstr (help_tmpl, "%OPTIONS%"); + help_str.assign (help_tmpl, p); + + for (const auto &grp : options_group) + for (const auto &opt : grp.options) + build_help_option (opt, help_str); + + p += strlen ("%OPTIONS%"); + help_str.append (p); + + return help_str; +} + +/* See cli-option.h. */ + +void +add_setshow_cmds_for_options (command_class cmd_class, + void *data, + gdb::array_view<const option_def> options, + struct cmd_list_element **set_list, + struct cmd_list_element **show_list) +{ + for (const auto &option : options) + { + if (option.type == var_boolean) + { + add_setshow_boolean_cmd (option.name, cmd_class, + option.var_address.boolean (option, data), + option.set_doc, option.show_doc, + option.help_doc, + nullptr, option.show_cmd_cb, + set_list, show_list); + } + else if (option.type == var_uinteger) + { + add_setshow_uinteger_cmd (option.name, cmd_class, + option.var_address.uinteger (option, data), + option.set_doc, option.show_doc, + option.help_doc, + nullptr, option.show_cmd_cb, + set_list, show_list); + } + else if (option.type == var_zuinteger_unlimited) + { + add_setshow_zuinteger_unlimited_cmd + (option.name, cmd_class, + option.var_address.integer (option, data), + option.set_doc, option.show_doc, + option.help_doc, + nullptr, option.show_cmd_cb, + set_list, show_list); + } + else if (option.type == var_enum) + { + add_setshow_enum_cmd (option.name, cmd_class, + option.enums, + option.var_address.enumeration (option, data), + option.set_doc, option.show_doc, + option.help_doc, + nullptr, option.show_cmd_cb, + set_list, show_list); + } + else + gdb_assert_not_reached (_("option type not handled")); + } +} + +} /* namespace option */ +} /* namespace gdb */ diff --git a/gdb/cli/cli-option.h b/gdb/cli/cli-option.h new file mode 100644 index 0000000..1bfbfce --- /dev/null +++ b/gdb/cli/cli-option.h @@ -0,0 +1,335 @@ +/* CLI options framework, for GDB. + + Copyright (C) 2017-2019 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef CLI_OPTION_H +#define CLI_OPTION_H 1 + +#include "common/gdb_optional.h" +#include "common/array-view.h" +#include "completer.h" +#include <string> +#include "command.h" + +namespace gdb { +namespace option { + +/* A type-erased option definition. The actual type of the option is + stored in the TYPE field. Client code cannot define objects of + this type directly (the ctor is protected). Instead, one of the + wrapper types below that extends this (boolean_option_def, + flag_option_def, uinteger_option_def, etc.) should be defined. */ +struct option_def +{ + /* The ctor is protected because you're supposed to construct using + one of bool_option_def, etc. below. */ +protected: + typedef void *(erased_get_var_address_ftype) (); + + /* Construct an option. NAME_ is the option's name. VAR_TYPE_ + defines the option's type. ERASED_GET_VAR_ADDRESS_ is a pointer + to a function that returns the option's control variable. + SHOW_CMD_CB_ is a pointer to callback for the "show" command that + is installed for this option. SET_DOC_, SHOW_DOC_, HELP_DOC_ are + used to create the option's "set/show" commands. */ + constexpr option_def (const char *name_, + var_types var_type_, + erased_get_var_address_ftype *erased_get_var_address_, + show_value_ftype *show_cmd_cb_, + const char *set_doc_, + const char *show_doc_, + const char *help_doc_) + : name (name_), type (var_type_), + erased_get_var_address (erased_get_var_address_), + var_address {}, + show_cmd_cb (show_cmd_cb_), + set_doc (set_doc_), show_doc (show_doc_), help_doc (help_doc_) + {} + +public: + /* The option's name. */ + const char *name; + + /* The option's type. */ + var_types type; + + /* A function that gets the controlling variable's address, type + erased. */ + erased_get_var_address_ftype *erased_get_var_address; + + /* Get the controlling variable's address. Each type of variable + uses a different union member. We do this instead of having a + single hook that return a "void *", for better type safety. This + way, actual instances of concrete option_def types + (boolean_option_def, etc.) fail to compile if you pass in a + function with incorrect return type. CTX here is the aggregate + object that groups the option variables from which the callback + returns the address of some member. */ + union + { + int *(*boolean) (const option_def &, void *ctx); + unsigned int *(*uinteger) (const option_def &, void *ctx); + int *(*integer) (const option_def &, void *ctx); + const char **(*enumeration) (const option_def &, void *ctx); + } + var_address; + + /* Pointer to null terminated list of enumerated values (like argv). + Only used by var_enum options. */ + const char *const *enums = nullptr; + + /* True if the option takes an argument. */ + bool have_argument = true; + + /* The "show" callback to use in the associated "show" command. + E.g., "show print elements". */ + show_value_ftype *show_cmd_cb; + + /* The set/show/help strings. These are shown in both the help of + commands that use the option group this option belongs to (e.g., + "help print"), and in the associated commands (e.g., "set/show + print elements", "help set print elements"). */ + const char *set_doc; + const char *show_doc; + const char *help_doc; + + /* Convenience method that returns THIS as an option_def. Useful + when you're putting an option_def subclass in an option_def + array_view. */ + const option_def &def () const + { + return *this; + } +}; + +namespace detail +{ + +/* Get the address of the option's value, cast to the right type. + RetType is the restored type of the variable, and Context is the + restored type of the context pointer. */ +template<typename RetType, typename Context> +static inline RetType * +get_var_address (const option_def &option, void *ctx) +{ + using unerased_ftype = RetType *(Context *); + unerased_ftype *fun = (unerased_ftype *) option.erased_get_var_address; + return fun ((Context *) ctx); +} + +/* Convenience identity helper that just returns SELF. */ + +template<typename T> +static T * +return_self (T *self) +{ + return self; +} + +} /* namespace detail */ + +/* Follows the definitions of the option types that client code should + define. Note that objects of these types are placed in option_def + arrays, by design, so they must not have data fields of their + own. */ + +/* A var_boolean command line option. */ + +template<typename Context> +struct boolean_option_def : option_def +{ + boolean_option_def (const char *long_option_, + int *(*get_var_address_cb_) (Context *), + show_value_ftype *show_cmd_cb_, + const char *set_doc_, + const char *show_doc_ = nullptr, + const char *help_doc_ = nullptr) + : option_def (long_option_, var_boolean, + (erased_get_var_address_ftype *) get_var_address_cb_, + show_cmd_cb_, + set_doc_, show_doc_, help_doc_) + { + var_address.boolean = detail::get_var_address<int, Context>; + } +}; + +/* A flag command line option. This is a var_boolean option under the + hood, but unlike boolean options, flag options don't take an on/off + argument. */ + +template<typename Context = int> +struct flag_option_def : boolean_option_def<Context> +{ + flag_option_def (const char *long_option_, + int *(*var_address_cb_) (Context *), + const char *set_doc_, + const char *help_doc_ = nullptr) + : boolean_option_def<Context> (long_option_, + var_address_cb_, + NULL, + set_doc_, NULL, help_doc_) + { + this->have_argument = false; + } + + flag_option_def (const char *long_option_, + const char *set_doc_, + const char *help_doc_ = nullptr) + : boolean_option_def<Context> (long_option_, + gdb::option::detail::return_self, + NULL, + set_doc_, nullptr, help_doc_) + { + this->have_argument = false; + } +}; + +/* A var_uinteger command line option. */ + +template<typename Context> +struct uinteger_option_def : option_def +{ + uinteger_option_def (const char *long_option_, + unsigned int *(*get_var_address_cb_) (Context *), + show_value_ftype *show_cmd_cb_, + const char *set_doc_, + const char *show_doc_ = nullptr, + const char *help_doc_ = nullptr) + : option_def (long_option_, var_uinteger, + (erased_get_var_address_ftype *) get_var_address_cb_, + show_cmd_cb_, + set_doc_, show_doc_, help_doc_) + { + var_address.uinteger = detail::get_var_address<unsigned int, Context>; + } +}; + +/* A var_zuinteger_unlimited command line option. */ + +template<typename Context> +struct zuinteger_unlimited_option_def : option_def +{ + zuinteger_unlimited_option_def (const char *long_option_, + int *(*get_var_address_cb_) (Context *), + show_value_ftype *show_cmd_cb_, + const char *set_doc_, + const char *show_doc_ = nullptr, + const char *help_doc_ = nullptr) + : option_def (long_option_, var_zuinteger_unlimited, + (erased_get_var_address_ftype *) get_var_address_cb_, + show_cmd_cb_, + set_doc_, show_doc_, help_doc_) + { + var_address.integer = detail::get_var_address<int, Context>; + } +}; + +/* An var_enum command line option. */ + +template<typename Context> +struct enum_option_def : option_def +{ + enum_option_def (const char *long_option_, + const char *const *enumlist, + const char **(*get_var_address_cb_) (Context *), + show_value_ftype *show_cmd_cb_, + const char *set_doc_, + const char *show_doc_ = nullptr, + const char *help_doc_ = nullptr) + : option_def (long_option_, var_enum, + (erased_get_var_address_ftype *) get_var_address_cb_, + show_cmd_cb_, + set_doc_, show_doc_, help_doc_) + { + var_address.enumeration = detail::get_var_address<const char *, Context>; + this->enums = enumlist; + } +}; + +/* A group of options that all share the same context pointer to pass + to the options' get-current-value callbacks. */ +struct option_def_group +{ + /* The list of options. */ + gdb::array_view<const option_def> options; + + /* The context pointer to pass to the options' get-current-value + callbacks. */ + void *ctx; +}; + +/* Modes of operation for process_options. */ +enum process_options_mode +{ + /* In this mode, options are only processed if we find a "--" + delimiter. Throws an error if unknown options are found. */ + PROCESS_OPTIONS_REQUIRE_DELIMITER, + + /* In this mode, a "--" delimiter is not required. Throws an error + if unknown options are found, regardless of whether a delimiter + is present. */ + PROCESS_OPTIONS_UNKNOWN_IS_ERROR, + + /* In this mode, a "--" delimiter is not required. If an unknown + option is found, assume it is the command's argument/operand. */ + PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, +}; + +/* Process ARGS, using OPTIONS_GROUP as valid options. Returns true + if the string has been fully parsed and there are no operands to + handle by the caller. Return false if options were parsed, and + *ARGS now points at the first operand. */ +extern bool process_options + (const char **args, + process_options_mode mode, + gdb::array_view<const option_def_group> options_group); + +/* Complete ARGS on options listed by OPTIONS_GROUP. Returns true if + the string has been fully parsed and there are no operands to + handle by the caller. Return false if options were parsed, and + *ARGS now points at the first operand. */ +extern bool complete_options + (completion_tracker &tracker, + const char **args, + process_options_mode mode, + gdb::array_view<const option_def_group> options_group); + +/* Complete on all options listed by OPTIONS_GROUP. */ +extern void + complete_on_all_options (completion_tracker &tracker, + gdb::array_view<const option_def_group> options_group); + +/* Return a string with the result of replacing %OPTIONS% in HELP_TMLP + with an auto-generated "help" string fragment for all the options + in OPTIONS_GROUP. */ +extern std::string build_help + (const char *help_tmpl, + gdb::array_view<const option_def_group> options_group); + +/* Install set/show commands for options defined in OPTIONS. DATA is + a pointer to the structure that holds the data associated with the + OPTIONS array. */ +extern void add_setshow_cmds_for_options (command_class cmd_class, void *data, + gdb::array_view<const option_def> options, + struct cmd_list_element **set_list, + struct cmd_list_element **show_list); + +} /* namespace option */ +} /* namespace gdb */ + +#endif /* CLI_OPTION_H */ diff --git a/gdb/cli/cli-setshow.c b/gdb/cli/cli-setshow.c index 9841ec1..14ea723 100644 --- a/gdb/cli/cli-setshow.c +++ b/gdb/cli/cli-setshow.c @@ -78,33 +78,48 @@ parse_auto_binary_operation (const char *arg) /* See cli-setshow.h. */ int -parse_cli_boolean_value (const char *arg) +parse_cli_boolean_value (const char **arg) { - int length; - - if (!arg || !*arg) - return 1; + const char *p = skip_to_space (*arg); + size_t length = p - *arg; - length = strlen (arg); + /* Note that "o" is ambiguous. */ - while (arg[length - 1] == ' ' || arg[length - 1] == '\t') - length--; + if ((length == 2 && strncmp (*arg, "on", length) == 0) + || strncmp (*arg, "1", length) == 0 + || strncmp (*arg, "yes", length) == 0 + || strncmp (*arg, "enable", length) == 0) + { + *arg = skip_spaces (*arg + length); + return 1; + } + else if ((length >= 2 && strncmp (*arg, "off", length) == 0) + || strncmp (*arg, "0", length) == 0 + || strncmp (*arg, "no", length) == 0 + || strncmp (*arg, "disable", length) == 0) + { + *arg = skip_spaces (*arg + length); + return 0; + } + else + return -1; +} - /* Note that "o" is ambiguous. */ +/* See cli-setshow.h. */ - if ((length == 2 && strncmp (arg, "on", length) == 0) - || strncmp (arg, "1", length) == 0 - || strncmp (arg, "yes", length) == 0 - || strncmp (arg, "enable", length) == 0) +int +parse_cli_boolean_value (const char *arg) +{ + if (!arg || !*arg) return 1; - else if ((length >= 2 && strncmp (arg, "off", length) == 0) - || strncmp (arg, "0", length) == 0 - || strncmp (arg, "no", length) == 0 - || strncmp (arg, "disable", length) == 0) - return 0; - else + + int b = parse_cli_boolean_value (&arg); + if (b >= 0 && *arg != '\0') return -1; + + return b; } + void deprecated_show_value_hack (struct ui_file *ignore_file, @@ -134,21 +149,136 @@ deprecated_show_value_hack (struct ui_file *ignore_file, /* Returns true if ARG is "unlimited". */ -static int -is_unlimited_literal (const char *arg) +static bool +is_unlimited_literal (const char **arg) { - arg = skip_spaces (arg); + *arg = skip_spaces (*arg); - const char *p = skip_to_space (arg); + const char *p = skip_to_space (*arg); - size_t len = p - arg; + size_t len = p - *arg; - if (len > 0 && strncmp ("unlimited", arg, len) == 0) - return true; + if (len > 0 && strncmp ("unlimited", *arg, len) == 0) + { + *arg += len; + return true; + } return false; } +/* See cli-setshow.h. */ + +unsigned int +parse_cli_var_uinteger (var_types var_type, const char **arg, + bool expression) +{ + LONGEST val; + + if (*arg == nullptr) + { + if (var_type == var_uinteger) + error_no_arg (_("integer to set it to, or \"unlimited\".")); + else + error_no_arg (_("integer to set it to.")); + } + + if (var_type == var_uinteger && is_unlimited_literal (arg)) + val = 0; + else if (expression) + val = parse_and_eval_long (*arg); + else + val = get_ulongest (arg); + + if (var_type == var_uinteger && val == 0) + val = UINT_MAX; + else if (val < 0 + /* For var_uinteger, don't let the user set the value + to UINT_MAX directly, as that exposes an + implementation detail to the user interface. */ + || (var_type == var_uinteger && val >= UINT_MAX) + || (var_type == var_zuinteger && val > UINT_MAX)) + error (_("integer %s out of range"), plongest (val)); + + return val; +} + +/* See cli-setshow.h. */ + +int +parse_cli_var_zuinteger_unlimited (const char **arg, bool expression) +{ + LONGEST val; + + if (*arg == nullptr) + error_no_arg (_("integer to set it to, or \"unlimited\".")); + + if (is_unlimited_literal (arg)) + val = -1; + else if (expression) + val = parse_and_eval_long (*arg); + else + val = get_ulongest (arg); + + if (val > INT_MAX) + error (_("integer %s out of range"), plongest (val)); + else if (val < -1) + error (_("only -1 is allowed to set as unlimited")); + + return val; +} + +/* See cli-setshow.h. */ + +const char * +parse_cli_var_enum (const char **args, const char *const *enums) +{ + /* If no argument was supplied, print an informative error + message. */ + if (args == NULL || *args == NULL || **args == '\0') + { + std::string msg; + + for (size_t i = 0; enums[i]; i++) + { + if (i != 0) + msg += ", "; + msg += enums[i]; + } + error (_("Requires an argument. Valid arguments are %s."), + msg.c_str ()); + } + + const char *p = skip_to_space (*args); + size_t len = p - *args; + + int nmatches = 0; + const char *match = NULL; + for (size_t i = 0; enums[i]; i++) + if (strncmp (*args, enums[i], len) == 0) + { + if (enums[i][len] == '\0') + { + match = enums[i]; + nmatches = 1; + break; /* Exact match. */ + } + else + { + match = enums[i]; + nmatches++; + } + } + + if (nmatches == 0) + error (_("Undefined item: \"%.*s\"."), (int) len, *args); + + if (nmatches > 1) + error (_("Ambiguous item \"%.*s\"."), (int) len, *args); + + *args += len; + return match; +} /* Do a "set" command. ARG is NULL if no argument, or the text of the argument, and FROM_TTY is nonzero if this command is @@ -295,30 +425,7 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c) case var_uinteger: case var_zuinteger: { - LONGEST val; - - if (arg == NULL) - { - if (c->var_type == var_uinteger) - error_no_arg (_("integer to set it to, or \"unlimited\".")); - else - error_no_arg (_("integer to set it to.")); - } - - if (c->var_type == var_uinteger && is_unlimited_literal (arg)) - val = 0; - else - val = parse_and_eval_long (arg); - - if (c->var_type == var_uinteger && val == 0) - val = UINT_MAX; - else if (val < 0 - /* For var_uinteger, don't let the user set the value - to UINT_MAX directly, as that exposes an - implementation detail to the user interface. */ - || (c->var_type == var_uinteger && val >= UINT_MAX) - || (c->var_type == var_zuinteger && val > UINT_MAX)) - error (_("integer %s out of range"), plongest (val)); + unsigned int val = parse_cli_var_uinteger (c->var_type, &arg, true); if (*(unsigned int *) c->var != val) { @@ -341,7 +448,7 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c) error_no_arg (_("integer to set it to.")); } - if (c->var_type == var_integer && is_unlimited_literal (arg)) + if (c->var_type == var_integer && is_unlimited_literal (&arg)) val = 0; else val = parse_and_eval_long (arg); @@ -366,59 +473,11 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c) } case var_enum: { - int i; - int len; - int nmatches; - const char *match = NULL; - const char *p; - - /* If no argument was supplied, print an informative error - message. */ - if (arg == NULL) - { - std::string msg; - - for (i = 0; c->enums[i]; i++) - { - if (i != 0) - msg += ", "; - msg += c->enums[i]; - } - error (_("Requires an argument. Valid arguments are %s."), - msg.c_str ()); - } - - p = strchr (arg, ' '); + const char *end_arg = arg; + const char *match = parse_cli_var_enum (&end_arg, c->enums); - if (p) - len = p - arg; - else - len = strlen (arg); - - nmatches = 0; - for (i = 0; c->enums[i]; i++) - if (strncmp (arg, c->enums[i], len) == 0) - { - if (c->enums[i][len] == '\0') - { - match = c->enums[i]; - nmatches = 1; - break; /* Exact match. */ - } - else - { - match = c->enums[i]; - nmatches++; - } - } - - if (nmatches <= 0) - error (_("Undefined item: \"%s\"."), arg); - - if (nmatches > 1) - error (_("Ambiguous item \"%s\"."), arg); - - const char *after = skip_spaces (arg + len); + int len = end_arg - arg; + const char *after = skip_spaces (end_arg); if (*after != '\0') error (_("Junk after item \"%.*s\": %s"), len, arg, after); @@ -432,20 +491,7 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c) break; case var_zuinteger_unlimited: { - LONGEST val; - - if (arg == NULL) - error_no_arg (_("integer to set it to, or \"unlimited\".")); - - if (is_unlimited_literal (arg)) - val = -1; - else - val = parse_and_eval_long (arg); - - if (val > INT_MAX) - error (_("integer %s out of range"), plongest (val)); - else if (val < -1) - error (_("only -1 is allowed to set as unlimited")); + int val = parse_cli_var_zuinteger_unlimited (&arg, true); if (*(int *) c->var != val) { diff --git a/gdb/cli/cli-setshow.h b/gdb/cli/cli-setshow.h index e4d13c3..c00a098 100644 --- a/gdb/cli/cli-setshow.h +++ b/gdb/cli/cli-setshow.h @@ -23,6 +23,33 @@ struct cmd_list_element; Returns 1 for true, 0 for false, and -1 if invalid. */ extern int parse_cli_boolean_value (const char *arg); +/* Same as above, but work with a pointer to pointer. ARG is advanced + past a successfully parsed value. */ +extern int parse_cli_boolean_value (const char **arg); + +/* Parse ARG, an option to a var_uinteger or var_zuinteger variable. + Either returns the parsed value on success or throws an error. If + EXPRESSION is true, *ARG is parsed as an expression; otherwise, it + is parsed with get_ulongest. It's not possible to parse the + integer as an expression when there may be valid input after the + integer, such as when parsing command options. E.g., "print + -elements NUMBER -obj --". In such case, parsing as an expression + would parse "-obj --" as part of the expression as well. */ +extern unsigned int parse_cli_var_uinteger (var_types var_type, + const char **arg, + bool expression); + +/* Like parse_cli_var_uinteger, for var_zuinteger_unlimited. */ +extern int parse_cli_var_zuinteger_unlimited (const char **arg, + bool expression); + +/* Parse ARG, an option to a var_enum variable. ENUM is a + null-terminated array of possible values. Either returns the parsed + value on success or throws an error. ARG is advanced past the + parsed value. */ +const char *parse_cli_var_enum (const char **args, + const char *const *enums); + extern void do_set_command (const char *arg, int from_tty, struct cmd_list_element *c); extern void do_show_command (const char *arg, int from_tty, diff --git a/gdb/cli/cli-utils.c b/gdb/cli/cli-utils.c index 23296ce..306b69e 100644 --- a/gdb/cli/cli-utils.c +++ b/gdb/cli/cli-utils.c @@ -27,6 +27,57 @@ static std::string extract_arg_maybe_quoted (const char **arg); /* See documentation in cli-utils.h. */ +ULONGEST +get_ulongest (const char **pp, int trailer) +{ + LONGEST retval = 0; /* default */ + const char *p = *pp; + + if (*p == '$') + { + value *val = value_from_history_ref (p, &p); + + if (val != NULL) /* Value history reference */ + { + if (TYPE_CODE (value_type (val)) == TYPE_CODE_INT) + retval = value_as_long (val); + else + error (_("History value must have integer type.")); + } + else /* Convenience variable */ + { + /* Internal variable. Make a copy of the name, so we can + null-terminate it to pass to lookup_internalvar(). */ + const char *start = ++p; + while (isalnum (*p) || *p == '_') + p++; + std::string varname (start, p - start); + if (!get_internalvar_integer (lookup_internalvar (varname.c_str ()), + &retval)) + error (_("Convenience variable $%s does not have integer value."), + varname.c_str ()); + } + } + else + { + retval = strtoulst (p, pp, 0); + if (p == *pp) + { + /* There is no number here. (e.g. "cond a == b"). */ + error (_("Expected integer at: %s"), p); + } + p = *pp; + } + + if (!(isspace (*p) || *p == '\0' || *p == trailer)) + error (_("Trailing junk at: %s"), p); + p = skip_spaces (p); + *pp = p; + return retval; +} + +/* See documentation in cli-utils.h. */ + int get_number_trailer (const char **pp, int trailer) { diff --git a/gdb/cli/cli-utils.h b/gdb/cli/cli-utils.h index 9425fb4..41c2356 100644 --- a/gdb/cli/cli-utils.h +++ b/gdb/cli/cli-utils.h @@ -39,6 +39,10 @@ extern int get_number (const char **); extern int get_number (char **); +/* Like get_number_trailer, but works with ULONGEST, and throws on + error instead of returning 0. */ +extern ULONGEST get_ulongest (const char **pp, int trailer = '\0'); + /* Extract from ARGS the arguments [-q] [-t TYPEREGEXP] [--] NAMEREGEXP. The caller is responsible to initialize *QUIET to false, *REGEXP @@ -193,6 +197,14 @@ extern std::string extract_arg (const char **arg); argument. */ extern int check_for_argument (const char **str, const char *arg, int arg_len); +/* Same as above, but ARG's length is computed. */ + +static inline int +check_for_argument (const char **str, const char *arg) +{ + return check_for_argument (str, arg, strlen (arg)); +} + /* Same, for non-const STR. */ static inline int @@ -202,6 +214,12 @@ check_for_argument (char **str, const char *arg, int arg_len) arg, arg_len); } +static inline int +check_for_argument (char **str, const char *arg) +{ + return check_for_argument (str, arg, strlen (arg)); +} + /* A helper function that looks for a set of flags at the start of a string. The possible flags are given as a null terminated string. A flag in STR must either be at the end of the string, diff --git a/gdb/maint-test-options.c b/gdb/maint-test-options.c new file mode 100644 index 0000000..599155c --- /dev/null +++ b/gdb/maint-test-options.c @@ -0,0 +1,461 @@ +/* Maintenance commands for testing the options framework. + + Copyright (C) 2019 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "gdbcmd.h" +#include "cli/cli-option.h" + +/* This file defines three "maintenance test-options" subcommands to + exercise TAB-completion and option processing: + + (gdb) maint test-options require-delimiter + (gdb) maint test-options unknown-is-error + (gdb) maint test-options unknown-is-operand + + And a fourth one to help with TAB-completion testing. + + (gdb) maint show test-options-completion-result + + Each of the test-options subcommands exercise + gdb::option::process_options with a different enum + process_options_mode value. Examples for commands they model: + + - "print" and "compile print", are like "require-delimiter", + because they accept random expressions as argument. + + - "backtrace" and "frame/thread apply" are like + "unknown-is-operand", because "-" is a valid command. + + - "compile file" and "compile code" are like "unknown-is-error". + + These commands allow exercising all aspects of option processing + without having to pick some existing command. That should be more + stable going forward than relying on an existing user command, + since if we picked say "print", that command or its options could + change in future, and then we'd be left with having to pick some + other command or option to exercise some non-command-specific + option processing detail. Also, actual user commands have side + effects that we're not interested in when we're focusing on unit + testing the options machinery. BTW, a maintenance command is used + as a sort of unit test driver instead of actual "maint selftest" + unit tests, since we need to go all the way via gdb including + readline, for proper testing of TAB completion. + + These maintenance commands support options of all the different + available kinds of commands (boolean, enum, flag, uinteger): + + (gdb) maint test-options require-delimiter -[TAB] + -bool -enum -flag -uinteger -xx1 -xx2 + + (gdb) maint test-options require-delimiter -bool o[TAB] + off on + (gdb) maint test-options require-delimiter -enum [TAB] + xxx yyy zzz + (gdb) maint test-options require-delimiter -uinteger [TAB] + NUMBER unlimited + + '-xx1' and '-xx2' are flag options too. They exist in order to + test ambiguous option names, like '-xx'. + + Invoking the commands makes them print out the options parsed: + + (gdb) maint test-options unknown-is-error -flag -enum yyy cmdarg + -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint 0 -zuint-unl 0 -- cmdarg + + (gdb) maint test-options require-delimiter -flag -enum yyy cmdarg + -flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -- -flag -enum yyy cmdarg + (gdb) maint test-options require-delimiter -flag -enum yyy cmdarg -- + Unrecognized option at: cmdarg -- + (gdb) maint test-options require-delimiter -flag -enum yyy -- cmdarg + -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint 0 -zuint-unl 0 -- cmdarg + + The "maint show test-options-completion-result" command exists in + order to do something similar for completion: + + (gdb) maint test-options unknown-is-error -flag -b 0 -enum yyy OPERAND[TAB] + (gdb) maint show test-options-completion-result + 0 OPERAND + + (gdb) maint test-options unknown-is-error -flag -b 0 -enum yyy[TAB] + (gdb) maint show test-options-completion-result + 1 + + (gdb) maint test-options require-dash -unknown[TAB] + (gdb) maint show test-options-completion-result + 1 + + Here, "1" means the completion function processed the whole input + line, and that the command shouldn't do anything with the arguments, + since there are no operands. While "0" indicates that there are + operands after options. The text after "0" is the operands. + + This level of detail is particularly important because getting the + completion function's entry point to return back to the caller the + right pointer into the operand is quite tricky in several + scenarios. */ + +/* Enum values for the "maintenance test-options" commands. */ +const char test_options_enum_values_xxx[] = "xxx"; +const char test_options_enum_values_yyy[] = "yyy"; +const char test_options_enum_values_zzz[] = "zzz"; +static const char *const test_options_enum_values_choices[] = +{ + test_options_enum_values_xxx, + test_options_enum_values_yyy, + test_options_enum_values_zzz, + NULL +}; + +/* Option data for the "maintenance test-options" commands. */ + +struct test_options_opts +{ + int flag_opt = 0; + int xx1_opt = 0; + int xx2_opt = 0; + int boolean_opt = 0; + const char *enum_opt = test_options_enum_values_xxx; + unsigned int uint_opt = 0; + int zuint_unl_opt = 0; +}; + +/* Option definitions for the "maintenance test-options" commands. */ + +static const gdb::option::option_def test_options_option_defs[] = { + + /* A flag option. */ + gdb::option::flag_option_def<test_options_opts> { + "flag", + [] (test_options_opts *opts) { return &opts->flag_opt; }, + N_("A flag option."), + }, + + /* A couple flags with similar names, for "ambiguous option names" + testing. */ + gdb::option::flag_option_def<test_options_opts> { + "xx1", + [] (test_options_opts *opts) { return &opts->xx1_opt; }, + N_("A flag option."), + }, + gdb::option::flag_option_def<test_options_opts> { + "xx2", + [] (test_options_opts *opts) { return &opts->xx2_opt; }, + N_("A flag option."), + }, + + /* A boolean option. */ + gdb::option::boolean_option_def<test_options_opts> { + "bool", + [] (test_options_opts *opts) { return &opts->boolean_opt; }, + nullptr, /* show_cmd_cb */ + N_("A boolean option."), + }, + + /* An enum option. */ + gdb::option::enum_option_def<test_options_opts> { + "enum", + test_options_enum_values_choices, + [] (test_options_opts *opts) { return &opts->enum_opt; }, + nullptr, /* show_cmd_cb */ + N_("An enum option."), + }, + + /* A uinteger option. */ + gdb::option::uinteger_option_def<test_options_opts> { + "uinteger", + [] (test_options_opts *opts) { return &opts->uint_opt; }, + nullptr, /* show_cmd_cb */ + N_("A uinteger option."), + nullptr, /* show_doc */ + N_("A help doc that spawns\nmultiple lines."), + }, + + /* A zuinteger_unlimited option. */ + gdb::option::zuinteger_unlimited_option_def<test_options_opts> { + "zuinteger-unlimited", + [] (test_options_opts *opts) { return &opts->zuint_unl_opt; }, + nullptr, /* show_cmd_cb */ + N_("A zuinteger-unlimited option."), + nullptr, /* show_doc */ + nullptr, /* help_doc */ + }, +}; + +/* Create an option_def_group for the test_options_opts options, with + OPTS as context. */ + +static inline gdb::option::option_def_group +make_test_options_options_def_group (test_options_opts *opts) +{ + return {{test_options_option_defs}, opts}; +} + +/* Implementation of the "maintenance test-options + require-delimiter/unknown-is-error/unknown-is-operand" commands. + Each of the commands maps to a different enum process_options_mode + enumerator. The test strategy is simply processing the options in + a number of scenarios, and printing back the parsed result. */ + +static void +maintenance_test_options_command_mode (const char *args, + gdb::option::process_options_mode mode) +{ + test_options_opts opts; + + gdb::option::process_options (&args, mode, + make_test_options_options_def_group (&opts)); + + if (args == nullptr) + args = ""; + else + args = skip_spaces (args); + + printf_unfiltered (_("-flag %d -xx1 %d -xx2 %d -bool %d " + "-enum %s -uint %s -zuint-unl %s -- %s\n"), + opts.flag_opt, + opts.xx1_opt, + opts.xx2_opt, + opts.boolean_opt, + opts.enum_opt, + (opts.uint_opt == UINT_MAX + ? "unlimited" + : pulongest (opts.uint_opt)), + (opts.zuint_unl_opt == -1 + ? "unlimited" + : plongest (opts.zuint_unl_opt)), + args); +} + +/* Variables used by the "maintenance show + test-options-completion-result" command. These variables are + stored by the completer of the "maint test-options" + subcommands. */ + +/* The result of gdb::option::complete_options. */ +static int maintenance_test_options_command_completion_result; +/* The text at the word point after gdb::option::complete_options + returns. */ +static std::string maintenance_test_options_command_completion_text; + +/* The "maintenance show test-options-completion-result" command. */ + +static void +maintenance_show_test_options_completion_result + (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + if (maintenance_test_options_command_completion_result) + fprintf_filtered (file, "1\n"); + else + fprintf_filtered + (file, _("0 %s\n"), + maintenance_test_options_command_completion_text.c_str ()); +} + +/* Implementation of completer for the "maintenance test-options + require-delimiter/unknown-is-error/unknown-is-operand" commands. + Each of the commands maps to a different enum process_options_mode + enumerator. */ + +static void +maintenance_test_options_completer_mode (completion_tracker &tracker, + const char *text, + gdb::option::process_options_mode mode) +{ + try + { + maintenance_test_options_command_completion_result + = gdb::option::complete_options + (tracker, &text, mode, + make_test_options_options_def_group (nullptr)); + maintenance_test_options_command_completion_text = text; + } + catch (const gdb_exception_error &ex) + { + maintenance_test_options_command_completion_result = 1; + throw; + } +} + +/* Implementation of the "maintenance test-options require-delimiter" + command. */ + +static void +maintenance_test_options_require_delimiter_command (const char *args, + int from_tty) +{ + maintenance_test_options_command_mode + (args, gdb::option::PROCESS_OPTIONS_REQUIRE_DELIMITER); +} + +/* Implementation of the "maintenance test-options + unknown-is-error" command. */ + +static void +maintenance_test_options_unknown_is_error_command (const char *args, + int from_tty) +{ + maintenance_test_options_command_mode + (args, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR); +} + +/* Implementation of the "maintenance test-options + unknown-is-operand" command. */ + +static void +maintenance_test_options_unknown_is_operand_command (const char *args, + int from_tty) +{ + maintenance_test_options_command_mode + (args, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND); +} + +/* Completer for the "maintenance test-options require-delimiter" + command. */ + +static void +maintenance_test_options_require_delimiter_command_completer + (cmd_list_element *ignore, completion_tracker &tracker, + const char *text, const char *word) +{ + maintenance_test_options_completer_mode + (tracker, text, gdb::option::PROCESS_OPTIONS_REQUIRE_DELIMITER); +} + +/* Completer for the "maintenance test-options unknown-is-error" + command. */ + +static void +maintenance_test_options_unknown_is_error_command_completer + (cmd_list_element *ignore, completion_tracker &tracker, + const char *text, const char *word) +{ + maintenance_test_options_completer_mode + (tracker, text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR); +} + +/* Completer for the "maintenance test-options unknown-is-operand" + command. */ + +static void +maintenance_test_options_unknown_is_operand_command_completer + (cmd_list_element *ignore, completion_tracker &tracker, + const char *text, const char *word) +{ + maintenance_test_options_completer_mode + (tracker, text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND); +} + +/* Command list for maint test-options. */ +struct cmd_list_element *maintenance_test_options_list; + +/* The "maintenance test-options" prefix command. */ + +static void +maintenance_test_options_command (const char *arg, int from_tty) +{ + printf_unfiltered + (_("\"maintenance test-options\" must be followed " + "by the name of a subcommand.\n")); + help_list (maintenance_test_options_list, "maintenance test-options ", + all_commands, gdb_stdout); +} + + +void +_initialize_maint_test_options () +{ + cmd_list_element *cmd; + + add_prefix_cmd ("test-options", no_class, maintenance_test_options_command, + _("\ +Generic command for testing the options infrastructure."), + &maintenance_test_options_list, + "maintenance test-options ", 0, + &maintenancelist); + + const auto def_group = make_test_options_options_def_group (nullptr); + + static const std::string help_require_delim_str + = gdb::option::build_help (_("\ +Command used for testing options processing.\n\ +Usage: maint test-options require-delimiter [[OPTION]... --] [OPERAND]...\n\ +\n\ +Options:\n\ +\n\ +%OPTIONS%\n\ +If you specify any command option, you must use a double dash (\"--\")\n\ +to mark the end of option processing."), + def_group); + + static const std::string help_unknown_is_error_str + = gdb::option::build_help (_("\ +Command used for testing options processing.\n\ +Usage: maint test-options unknown-is-error [OPTION]... [OPERAND]...\n\ +\n\ +Options:\n\ +\n\ +%OPTIONS%"), + def_group); + + static const std::string help_unknown_is_operand_str + = gdb::option::build_help (_("\ +Command used for testing options processing.\n\ +Usage: maint test-options unknown-is-operand [OPTION]... [OPERAND]...\n\ +\n\ +Options:\n\ +\n\ +%OPTIONS%"), + def_group); + + cmd = add_cmd ("require-delimiter", class_maintenance, + maintenance_test_options_require_delimiter_command, + help_require_delim_str.c_str (), + &maintenance_test_options_list); + set_cmd_completer_handle_brkchars + (cmd, maintenance_test_options_require_delimiter_command_completer); + + cmd = add_cmd ("unknown-is-error", class_maintenance, + maintenance_test_options_unknown_is_error_command, + help_unknown_is_error_str.c_str (), + &maintenance_test_options_list); + set_cmd_completer_handle_brkchars + (cmd, maintenance_test_options_unknown_is_error_command_completer); + + cmd = add_cmd ("unknown-is-operand", class_maintenance, + maintenance_test_options_unknown_is_operand_command, + help_unknown_is_operand_str.c_str (), + &maintenance_test_options_list); + set_cmd_completer_handle_brkchars + (cmd, maintenance_test_options_unknown_is_operand_command_completer); + + add_setshow_zinteger_cmd ("test-options-completion-result", class_maintenance, + &maintenance_test_options_command_completion_result, + _("\ +Set maintenance test-options completion result."), _("\ +Show maintenance test-options completion result."), _("\ +Show the results of completing\n\ +\"maint test-options require-delimiter\",\n\ +\"maint test-options unknown-is-error\", or\n\ +\"maint test-options unknown-is-operand\"."), + NULL, + maintenance_show_test_options_completion_result, + &maintenance_set_cmdlist, + &maintenance_show_cmdlist); +} diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 1c362f7..6e5dacb 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,5 +1,10 @@ 2019-06-13 Pedro Alves <palves@redhat.com> + * gdb.base/options.c: New file. + * gdb.base/options.exp: New file. + +2019-06-13 Pedro Alves <palves@redhat.com> + * gdb.base/settings.exp (test-boolean, test-auto-boolean): Check that "o" is ambiguous. diff --git a/gdb/testsuite/gdb.base/options.c b/gdb/testsuite/gdb.base/options.c new file mode 100644 index 0000000..fd19922 --- /dev/null +++ b/gdb/testsuite/gdb.base/options.c @@ -0,0 +1,33 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2019 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +int xxx1= 123; + +struct S +{ + int a; + int b; + int c; +}; + +struct S g_s = {1, 2, 3}; + +int +main () +{ + return 0; +} diff --git a/gdb/testsuite/gdb.base/options.exp b/gdb/testsuite/gdb.base/options.exp new file mode 100644 index 0000000..1891176 --- /dev/null +++ b/gdb/testsuite/gdb.base/options.exp @@ -0,0 +1,554 @@ +# This testcase is part of GDB, the GNU debugger. + +# Copyright 2019 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Test the gdb::option framework. + +# The test uses the "maintenance test-options" subcommands to exercise +# TAB-completion and option processing. + +load_lib completion-support.exp + +clean_restart + +if { ![readline_is_used] } { + untested "no tab completion support without readline" + return -1 +} + +# Check the completion result, as returned by the "maintenance show +# test-options-completion-result" command. TEST is used as test name. +proc check_completion_result {expected test} { + gdb_test "maintenance show test-options-completion-result" \ + "$expected" \ + "$test: res=$expected" +} + +# Like test_gdb_complete_unique, but the expected output is expected +# to be the input line. I.e., the line is already complete. We're +# just checking whether GDB recognizes the option and auto-appends a +# space. +proc test_completer_recognizes {res input_line} { + set expected_re [string_to_regexp $input_line] + test_gdb_complete_unique $input_line $expected_re + check_completion_result $res $input_line +} + +# Wrapper around test_gdb_complete_multiple that also checks the +# completion result is RES. +proc res_test_gdb_complete_multiple {res cmd_prefix completion_word args} { + test_gdb_complete_multiple $cmd_prefix $completion_word {*}$args + check_completion_result $res "$cmd_prefix$completion_word" +} + +# Wrapper around test_gdb_complete_none that also checks the +# completion result is RES. +proc res_test_gdb_complete_none { res input_line } { + test_gdb_complete_none $input_line + check_completion_result $res "$input_line" +} + +# Wrapper around test_gdb_complete_unique that also checks the +# completion result is RES. +proc res_test_gdb_complete_unique { res input_line args} { + test_gdb_complete_unique $input_line {*}$args + check_completion_result $res "$input_line" +} + +# Make a full command name from VARIANT. VARIANT is either +# "require-delimiter", "unknown-is-error" or "unknown-is-operand". +proc make_cmd {variant} { + return "maint test-options $variant" +} + +# Return a string for the expected result of running "maint +# test-options xxx", with no flag/option set. OPERAND is the expected +# operand. +proc expect_none {operand} { + return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -- $operand" +} + +# Return a string for the expected result of running "maint +# test-options xxx", with -flag set. OPERAND is the expected operand. +proc expect_flag {operand} { + return "-flag 1 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -- $operand" +} + +# Return a string for the expected result of running "maint +# test-options xxx", with -bool set. OPERAND is the expected operand. +proc expect_bool {operand} { + return "-flag 0 -xx1 0 -xx2 0 -bool 1 -enum xxx -uint 0 -zuint-unl 0 -- $operand" +} + +# Return a string for the expected result of running "maint +# test-options xxx", with one of the integer options set to $VAL. +# OPTION determines which option to expect set. OPERAND is the +# expected operand. +proc expect_integer {option val operand} { + if {$option == "uinteger"} { + return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint $val -zuint-unl 0 -- $operand" + } elseif {$option == "zuinteger-unlimited"} { + return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl $val -- $operand" + } else { + error "unsupported option: $option" + } +} + +set all_options { + "-bool" + "-enum" + "-flag" + "-uinteger" + "-xx1" + "-xx2" + "-zuinteger-unlimited" +} + +# Miscellaneous tests. +proc_with_prefix test-misc {variant} { + global all_options + + set cmd [make_cmd $variant] + + # Call test command with no arguments at all. + gdb_test "$cmd" [expect_none ""] + + # Now with a single dash. + if {$variant == "require-delimiter"} { + gdb_test "$cmd -" [expect_none "-"] + } else { + gdb_test "$cmd -" "Ambiguous option at: -" + } + + # Completing at "-" should list all options. + res_test_gdb_complete_multiple "1" "$cmd " "-" "" $all_options + + # Now with a double dash. + gdb_test "$cmd --" [expect_none ""] + + # "--" is recognized by options completer, gdb auto-appends a + # space. + test_completer_recognizes 1 "$cmd --" + + # Now with a double dash, plus a dash as operand. + gdb_test "$cmd -- -" [expect_none "-"] + res_test_gdb_complete_none "0 -" "$cmd -- -" + + # Completing an unambiguous option just appends an empty space. + test_completer_recognizes 1 "$cmd -flag" + + # Try running an ambiguous option. + if {$variant == "require-delimiter"} { + gdb_test "$cmd -xx" [expect_none "-xx"] + } else { + gdb_test "$cmd -xx" "Ambiguous option at: -xx" + } + + # Check that options are not case insensitive. + gdb_test "$cmd -flag --" [expect_flag ""] + + # Check how the different modes behave on unknown option, with a + # delimiter. + gdb_test "$cmd -FLAG --" \ + "Unrecognized option at: -FLAG --" + + # Check how the different modes behave on unknown option, without + # a delimiter. + if {$variant == "unknown-is-error"} { + gdb_test "$cmd -FLAG" \ + "Unrecognized option at: -FLAG" + } else { + gdb_test "$cmd -FLAG" [expect_none "-FLAG"] + } + + # Test parsing stops at a negative integer. + gdb_test "$cmd -1 --" \ + "Unrecognized option at: -1 --" + gdb_test "$cmd -2 --" \ + "Unrecognized option at: -2 --" +} + +# Flag option tests. +proc_with_prefix test-flag {variant} { + global all_options + + set cmd [make_cmd $variant] + + # Completing a flag just appends a space. + test_completer_recognizes 1 "$cmd -flag" + + # Add a dash, and all options should be shown. + test_gdb_complete_multiple "$cmd -flag " "-" "" $all_options + + # Basic smoke tests of accepted / not accepted values. + + # Check all the different variants a bool option may be specified. + if {$variant == "require-delimiter"} { + gdb_test "$cmd -flag 999" [expect_none "-flag 999"] + } else { + gdb_test "$cmd -flag 999" [expect_flag "999"] + } + gdb_test "$cmd -flag -- 999" [expect_flag "999"] + + # If the "--" separator is present, then GDB errors out if the + # flag option is passed some value -- check that too. + gdb_test "$cmd -flag xxx 999 --" "Unrecognized option at: xxx 999 --" + gdb_test "$cmd -flag o 999 --" "Unrecognized option at: o 999 --" + gdb_test "$cmd -flag 1 999 --" "Unrecognized option at: 1 999 --" + + # Extract twice the same flag, separated by one space. + gdb_test "$cmd -flag -flag -- non flags args" \ + [expect_flag "non flags args"] + + # Extract twice the same flag, separated by one space. + gdb_test "$cmd -xx1 -xx2 -xx1 -xx2 -xx1 -- non flags args" \ + "-flag 0 -xx1 1 -xx2 1 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -- non flags args" + + # Extract 2 known flags in front of unknown flags. + gdb_test "$cmd -xx1 -xx2 -a -b -c -xx1 --" \ + "Unrecognized option at: -a -b -c -xx1 --" + + # Check that combined flags are not recognised. + gdb_test "$cmd -xx1 -xx1xx2 -xx1 --" \ + "Unrecognized option at: -xx1xx2 -xx1 --" + + # Make sure the completer don't confuse a flag option with a + # boolean option. Specifically, "o" should not complete to + # "on/off". + + if {$variant == "require-delimiter"} { + res_test_gdb_complete_none "1" "$cmd -flag o" + + gdb_test "$cmd -flag o" [expect_none "-flag o"] + } else { + res_test_gdb_complete_none "0 o" "$cmd -flag o" + + gdb_test "$cmd -flag o" [expect_flag "o"] + } +} + +# Boolean option tests. +proc_with_prefix test-boolean {variant} { + global all_options + + set cmd [make_cmd $variant] + + # Boolean option's values are optional -- "on" is implied. Check + # that: + # + # - For require-delimiter commands, completing after a boolean + # option lists all other options, plus "on/off". This is + # because operands won't be processed until we see a "--" + # delimiter. + # + # - For !require-delimiter commands, completing after a boolean + # option completes as an operand, since that will tend to be + # more common than typing "on/off". + # E.g., "frame apply all -past-main COMMAND". + + if {$variant == "require-delimiter"} { + res_test_gdb_complete_multiple 1 "$cmd -bool " "" "" { + "-bool" + "-enum" + "-flag" + "-uinteger" + "-xx1" + "-xx2" + "-zuinteger-unlimited" + "off" + "on" + } + } else { + res_test_gdb_complete_none "0 " "$cmd -bool " + } + + # Add another dash, and "on/off" are no longer offered: + res_test_gdb_complete_multiple 1 "$cmd -bool " "-" "" $all_options + + # Basic smoke tests of accepted / not accepted values. + + # The command accepts all of "1/0/enable/disable/yes/no" too, even + # though like the "set" command, we don't offer those as + # completion candidates if you complete right after the boolean + # command's name, like: + # + # (gdb) maint test-options require-delimiter -bool [TAB] + # off on + # + # However, the completer does recognize them if you start typing + # the boolean value. + foreach value {"0" "1"} { + test_completer_recognizes 1 "$cmd -bool $value" + } + foreach value {"of" "off"} { + res_test_gdb_complete_unique 1 \ + "$cmd -bool $value" \ + "$cmd -bool off" + } + foreach value {"y" "ye" "yes"} { + res_test_gdb_complete_unique 1 \ + "$cmd -bool $value" \ + "$cmd -bool yes" + } + foreach value {"n" "no"} { + res_test_gdb_complete_unique 1 \ + "$cmd -bool $value" \ + "$cmd -bool no" + } + foreach value { + "e" + "en" + "ena" + "enab" + "enabl" + "enable" + } { + res_test_gdb_complete_unique 1 \ + "$cmd -bool $value" \ + "$cmd -bool enable" + } + foreach value { + "d" + "di" + "dis" + "disa" + "disab" + "disabl" + "disable" + } { + res_test_gdb_complete_unique 1 \ + "$cmd -bool $value" \ + "$cmd -bool disable" + } + + if {$variant == "require-delimiter"} { + res_test_gdb_complete_none "1" "$cmd -bool xxx" + } else { + res_test_gdb_complete_none "0 xxx" "$cmd -bool xxx" + } + + # The command accepts abbreviations of "enable/disable/yes/no", + # even though we don't offer those for completion. + foreach value { + "1" + "y" "ye" "yes" + "e" + "en" + "ena" + "enab" + "enabl" + "enable"} { + gdb_test "$cmd -bool $value --" [expect_bool ""] + } + foreach value { + "0" + "of" "off" + "n" "no" + "d" + "di" + "dis" + "disa" + "disab" + "disabl" + "disable"} { + gdb_test "$cmd -bool $value --" [expect_none ""] + } + + if {$variant == "require-delimiter"} { + gdb_test "$cmd -bool 999" [expect_none "-bool 999"] + } else { + gdb_test "$cmd -bool 999" [expect_bool "999"] + } + gdb_test "$cmd -bool -- 999" [expect_bool "999"] + + # Since "on" is implied after a boolean option, for + # !require-delimiter commands, anything that is not + # yes/no/1/0/on/off/enable/disable should be considered as the raw + # input after the last option. Also check "o", which might look + # like "on" or "off", but it's treated the same. + + foreach arg {"xxx" "o"} { + if {$variant == "require-delimiter"} { + gdb_test "$cmd -bool $arg" [expect_none "-bool $arg"] + } else { + gdb_test "$cmd -bool $arg" [expect_bool "$arg"] + } + } + # Also try -1. "unknown-is-error" commands error out saying that + # that's not a valid option. + if {$variant == "require-delimiter"} { + gdb_test "$cmd -bool -1" \ + [expect_none "-bool -1"] + } elseif {$variant == "unknown-is-error"} { + gdb_test "$cmd -bool -1" \ + "Unrecognized option at: -1" + } else { + gdb_test "$cmd -bool -1" [expect_bool "-1"] + } + + # OTOH, if the "--" separator is present, then GDB errors out if + # the boolean option is passed an invalid value -- check that too. + gdb_test "$cmd -bool -1 999 --" \ + "Unrecognized option at: -1 999 --" + gdb_test "$cmd -bool xxx 999 --" \ + "Value given for `-bool' is not a boolean: xxx" + gdb_test "$cmd -bool o 999 --" \ + "Value given for `-bool' is not a boolean: o" + + # Completing after a boolean option + "o" does list "on/off", + # though. + if {$variant == "require-delimiter"} { + res_test_gdb_complete_multiple 1 "$cmd -bool " "o" "" { + "off" + "on" + } + } else { + res_test_gdb_complete_multiple "0 o" "$cmd -bool " "o" "" { + "off" + "on" + } + } +} + +# Uinteger option tests. OPTION is which integer option we're +# testing. Can be "uinteger" or "zuinteger-unlimited". +proc_with_prefix test-uinteger {variant option} { + global all_options + + set cmd "[make_cmd $variant] -$option" + + # Test completing a uinteger option: + res_test_gdb_complete_multiple 1 "$cmd " "" "" { + "NUMBER" + "unlimited" + } + + # NUMBER above is just a placeholder, make sure we don't complete + # it as a valid option. + res_test_gdb_complete_none 1 "$cmd NU" + + # "unlimited" is valid though. + res_test_gdb_complete_unique 1 \ + "$cmd u" \ + "$cmd unlimited" + + # Basic smoke test of accepted / not accepted values. + gdb_test "$cmd 1 -- 999" [expect_integer $option "1" "999"] + gdb_test "$cmd unlimited -- 999" \ + [expect_integer $option "unlimited" "999"] + if {$option == "zuinteger-unlimited"} { + gdb_test "$cmd -1 --" [expect_integer $option "unlimited" ""] + gdb_test "$cmd 0 --" [expect_integer $option "0" ""] + } else { + gdb_test "$cmd -1 --" "integer -1 out of range" + gdb_test "$cmd 0 --" [expect_integer $option "unlimited" ""] + } + gdb_test "$cmd xxx --" \ + "Expected integer at: xxx --" + gdb_test "$cmd unlimitedx --" \ + "Expected integer at: unlimitedx --" + + # Don't offer completions until we're past the + # -uinteger/-zuinteger-unlimited argument. + res_test_gdb_complete_none 1 "$cmd 1" + + # A number of invalid values. + foreach value {"x" "x " "1a" "1a " "1-" "1- " "unlimitedx"} { + res_test_gdb_complete_none 1 "$cmd $value" + } + + # Try "-1". + if {$option == "uinteger"} { + # -1 is invalid uinteger. + foreach value {"-1" "-1 "} { + res_test_gdb_complete_none 1 "$cmd $value" + } + } else { + # -1 is valid for zuinteger-unlimited. + res_test_gdb_complete_none 1 "$cmd -1" + if {$variant == "require-delimiter"} { + res_test_gdb_complete_multiple 1 "$cmd -1 " "" "-" $all_options + } else { + res_test_gdb_complete_none "0 " "$cmd -1 " + } + } + + # Check that after a fully parsed option: + # + # - for require-delimiter commands, completion offers all + # options. + # + # - for !require-delimiter commands, completion offers nothing + # and returns false. + if {$variant == "require-delimiter"} { + res_test_gdb_complete_multiple 1 "$cmd 1 " "" "-" $all_options + } else { + res_test_gdb_complete_none "0 " "$cmd 1 " + } + + # Test completing non-option arguments after "-uinteger 1 ". + foreach operand {"x" "x " "1a" "1a " "1-" "1- "} { + if {$variant == "require-delimiter"} { + res_test_gdb_complete_none 1 "$cmd 1 $operand" + } else { + res_test_gdb_complete_none "0 $operand" "$cmd 1 $operand" + } + } + # These look like options, but they aren't. + foreach operand {"-1" "-1 "} { + if {$variant == "unknown-is-operand"} { + res_test_gdb_complete_none "0 $operand" "$cmd 1 $operand" + } else { + res_test_gdb_complete_none 1 "$cmd 1 $operand" + } + } +} + +# Enum option tests. +proc_with_prefix test-enum {variant} { + set cmd [make_cmd $variant] + + res_test_gdb_complete_multiple 1 "$cmd -enum " "" "" { + "xxx" + "yyy" + "zzz" + } + + # Check that "-" where a value is expected does not show the + # command's options. I.e., an enum's value is not optional. + # Check both completion and running the command. + res_test_gdb_complete_none 1 "$cmd -enum -" + gdb_test "$cmd -enum --"\ + "Requires an argument. Valid arguments are xxx, yyy, zzz\\." + + # Try passing an undefined item to an enum option. + gdb_test "$cmd -enum www --" "Undefined item: \"www\"." +} + +# Run the options framework tests first. +foreach_with_prefix cmd { + "require-delimiter" + "unknown-is-error" + "unknown-is-operand" +} { + test-misc $cmd + test-flag $cmd + test-boolean $cmd + foreach subcmd {"uinteger" "zuinteger-unlimited" } { + test-uinteger $cmd $subcmd + } + test-enum $cmd +} |