diff --git a/gdb/cli/cli-option.c b/gdb/cli/cli-option.c index 05539285c80..9eb9ff81154 100644 --- a/gdb/cli/cli-option.c +++ b/gdb/cli/cli-option.c @@ -43,7 +43,7 @@ union option_value /* For var_enum options. */ const char *enumeration; - /* For var_string options. This is malloc-allocated. */ + /* For var_string and var_filename options. This is allocated with new. */ std::string *string; }; @@ -85,7 +85,7 @@ struct option_def_and_value { if (value.has_value ()) { - if (option.type == var_string) + if (option.type == var_string || option.type == var_filename) delete value->string; } } @@ -102,7 +102,7 @@ struct option_def_and_value { if (value.has_value ()) { - if (option.type == var_string) + if (option.type == var_string || option.type == var_filename) value->string = nullptr; } } @@ -452,6 +452,78 @@ parse_option (gdb::array_view options_group, return option_def_and_value {*match, match_ctx, val}; } + case var_filename: + { + if (check_for_argument (args, "--")) + { + /* Treat e.g., "maint test-options -filename --" as if there + was no argument after "-filename". */ + error (_("-%s requires an argument"), match->name); + } + + const char *arg_start = *args; + std::string str = extract_string_maybe_quoted (args); + + /* If we are performing completion, and extracting STR moved ARGS + to the end of the line, then the user is trying to complete the + filename value. + + If ARGS didn't make it to the end of the line then the filename + value is already complete and the user is trying to complete + something later on the line. */ + if (completion != nullptr && **args == '\0') + { + /* Preserve the current custom word point. If the call to + advance_to_filename_maybe_quoted_complete_word_point below + skips to the end of the command line then the custom word + point will have been updated even though we generate no + completions. + + However, *ARGS will also have been updated, and the general + option completion code (which we will return too) also + updates the custom word point based on the adjustment made + to *ARGS. + + And so, if we don't find any completions, we should restore + the custom word point value, this leaves the generic option + completion code free to make its own adjustments. */ + int prev_word_pt = completion->tracker.custom_word_point (); + + /* From ARG_START move forward to the start of the completion + word, this will skip over any opening quote if there is + one. + + If the word to complete is fully quoted, i.e. has an + opening and closing quote, then this will skip over the + word entirely and leave WORD pointing to the end of the + input string. */ + const char *word + = advance_to_filename_maybe_quoted_complete_word_point + (completion->tracker, arg_start); + + if (word == arg_start || *word != '\0') + { + filename_maybe_quoted_completer (nullptr, completion->tracker, + arg_start, word); + + if (completion->tracker.have_completions ()) + return {}; + } + + /* No completions. Restore the custom word point. See the + comment above for why this is needed. */ + completion->tracker.set_custom_word_point (prev_word_pt); + } + + /* Check we did manage to extract something. */ + if (*args == arg_start) + error (_("-%s requires an argument"), match->name); + + option_value val; + val.string = new std::string (std::move (str)); + return option_def_and_value {*match, match_ctx, val}; + } + default: /* Not yet. */ gdb_assert_not_reached ("option type not supported"); @@ -612,6 +684,7 @@ save_option_value_in_ctx (std::optional &ov) = ov->value->enumeration; break; case var_string: + case var_filename: *ov->option.var_address.string (ov->option, ov->ctx) = std::move (*ov->value->string); break; @@ -701,6 +774,8 @@ get_val_type_str (const option_def &opt, std::string &buffer) } case var_string: return "STRING"; + case var_filename: + return "FILENAME"; default: return nullptr; } @@ -856,6 +931,15 @@ add_setshow_cmds_for_options (command_class cmd_class, nullptr, option.show_cmd_cb, set_list, show_list); } + else if (option.type == var_filename) + { + add_setshow_filename_cmd (option.name, cmd_class, + option.var_address.string (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"); } diff --git a/gdb/cli/cli-option.h b/gdb/cli/cli-option.h index bbe281d9721..26307a5d1e9 100644 --- a/gdb/cli/cli-option.h +++ b/gdb/cli/cli-option.h @@ -308,6 +308,26 @@ struct string_option_def : option_def } }; +/* A var_filename command line option. */ + +template +struct filename_option_def : option_def +{ + filename_option_def (const char *long_option_, + std::string *(*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_filename, nullptr, + (erased_get_var_address_ftype *) get_var_address_cb_, + show_cmd_cb_, + set_doc_, show_doc_, help_doc_) + { + var_address.string = detail::get_var_address; + } +}; + /* A group of options that all share the same context pointer to pass to the options' get-current-value callbacks. */ struct option_def_group diff --git a/gdb/maint-test-options.c b/gdb/maint-test-options.c index 48b68f91084..9d768177798 100644 --- a/gdb/maint-test-options.c +++ b/gdb/maint-test-options.c @@ -57,12 +57,13 @@ readline, for proper testing of TAB completion. These maintenance commands support options of all the different - available kinds of commands (boolean, enum, flag, string, uinteger): + available kinds of commands (boolean, enum, flag, string, filename, + uinteger): (gdb) maint test-options require-delimiter -[TAB] - -bool -pinteger-unlimited -xx1 - -enum -string -xx2 - -flag -uinteger-unlimited + -bool -flag -uinteger-unlimited + -enum -pinteger-unlimited -xx1 + -filename -string -xx2 (gdb) maint test-options require-delimiter -bool o[TAB] off on @@ -77,14 +78,14 @@ 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-unl 0 -pint-unl 0 -string '' -- cmdarg + -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint-unl 0 -pint-unl 0 -string '' -filename '' -- cmdarg (gdb) maint test-options require-delimiter -flag -enum yyy cmdarg - -flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0 -string '' -- -flag -enum yyy cmdarg + -flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0 -string '' -filename '' -- -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-unl 0 -pint-unl 0 -string '' -- cmdarg + -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint-unl 0 -pint-unl 0 -string '' -filename '' -- cmdarg The "maint show test-options-completion-result" command exists in order to do something similar for completion: @@ -135,6 +136,7 @@ struct test_options_opts unsigned int uint_unl_opt = 0; int pint_unl_opt = 0; std::string string_opt; + std::string filename_opt; test_options_opts () = default; @@ -146,7 +148,8 @@ struct test_options_opts { gdb_printf (file, _("-flag %d -xx1 %d -xx2 %d -bool %d " - "-enum %s -uint-unl %s -pint-unl %s -string '%s' -- %s\n"), + "-enum %s -uint-unl %s -pint-unl %s -string '%s' " + "-filename '%s' -- %s\n"), flag_opt, xx1_opt, xx2_opt, @@ -159,6 +162,7 @@ struct test_options_opts ? "unlimited" : plongest (pint_unl_opt)), string_opt.c_str (), + filename_opt.c_str (), args); } }; @@ -233,6 +237,14 @@ static const gdb::option::option_def test_options_option_defs[] = { nullptr, /* show_cmd_cb */ N_("A string option."), }, + + /* A filename option. */ + gdb::option::filename_option_def { + "filename", + [] (test_options_opts *opts) { return &opts->filename_opt; }, + nullptr, /* show_cmd_cb */ + N_("A filename option."), + }, }; /* Create an option_def_group for the test_options_opts options, with diff --git a/gdb/testsuite/gdb.base/filename-completion.exp b/gdb/testsuite/gdb.base/filename-completion.exp index 389e2d736c5..6de312bc6a3 100644 --- a/gdb/testsuite/gdb.base/filename-completion.exp +++ b/gdb/testsuite/gdb.base/filename-completion.exp @@ -412,6 +412,13 @@ proc run_quoting_and_escaping_tests { root } { run_mid_line_completion_tests $root $cmd } + + foreach sub_cmd { require-delimiter unknown-is-error unknown-is-operand } { + set cmd "maintenance test-options $sub_cmd -filename" + with_test_prefix "cmd=$cmd" { + run_quoting_and_escaping_tests_1 $root $cmd + } + } } # Helper for run_unquoted_tests. ROOT is the root directory as setup diff --git a/gdb/testsuite/gdb.base/options.exp b/gdb/testsuite/gdb.base/options.exp index 841e603764c..e1ad61e6470 100644 --- a/gdb/testsuite/gdb.base/options.exp +++ b/gdb/testsuite/gdb.base/options.exp @@ -99,21 +99,21 @@ proc make_cmd {variant} { # operand. proc expect_none {operand} { return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0\ - -string '' -- $operand" + -string '' -filename '' -- $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-unl 0 -pint-unl 0\ - -string '' -- $operand" + -string '' -filename '' -- $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-unl 0 -pint-unl 0\ - -string '' -- $operand" + -string '' -filename '' -- $operand" } # Return a string for the expected result of running "maint @@ -123,10 +123,10 @@ proc expect_bool {operand} { proc expect_integer {option val operand} { if {$option == "uinteger-unlimited"} { return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl $val\ - -pint-unl 0 -string '' -- $operand" + -pint-unl 0 -string '' -filename '' -- $operand" } elseif {$option == "pinteger-unlimited"} { return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0\ - -pint-unl $val -string '' -- $operand" + -pint-unl $val -string '' -filename '' -- $operand" } else { error "unsupported option: $option" } @@ -144,12 +144,28 @@ proc expect_string {str operand} { set str [string range $str 1 end-1] } return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0\ - -string '$str' -- $operand" + -string '$str' -filename '' -- $operand" +} + +# Return a string for the expected result of running "maint +# test-options xxx", with -filename set to $STR. OPERAND is the +# expected operand. +proc expect_filename {str operand} { + # Dequote the string in the expected output. + if { ( [string range $str 0 0] == "\"" + && [string range $str end end] == "\"") + || ([string range $str 0 0] == "'" + && [string range $str end end] == "'")} { + set str [string range $str 1 end-1] + } + return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint-unl 0 -pint-unl 0\ + -string '' -filename '$str' -- $operand" } set all_options { "-bool" "-enum" + "-filename" "-flag" "-pinteger-unlimited" "-string" @@ -612,7 +628,7 @@ proc_with_prefix test-flag {variant} { # 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-unl 0 -pint-unl 0\ - -string '' -- non flags args" + -string '' -filename '' -- non flags args" # Extract 2 known flags in front of unknown flags. gdb_test "$cmd -xx1 -xx2 -a -b -c -xx1 --" \ @@ -1031,6 +1047,70 @@ proc_with_prefix test-string {variant} { } } +# Filename option tests. These tests only focus on how GDB parses the +# filename option, and ensures that GDB can complete things after the +# filename value. The actual strings passed as filenames in this proc +# are not actual files that exist on disk. +# +# Filename options do also support completion. For testing of this +# aspect see the gdb.base/filename-completion.exp script. +proc_with_prefix test-filename {variant} { + global all_options + + set cmd [make_cmd $variant] + + # Check that "-" where a value is expected does not show the + # command's options. I.e., a filename's value is not optional. + # Check both completion and running the command. + res_test_gdb_complete_none \ + "1 [expect_none ""]" \ + "$cmd -filename -" + gdb_test "$cmd -filename --" \ + "-filename requires an argument" + if {$variant == "require-delimiter"} { + gdb_test "$cmd -filename" [expect_none "-filename"] + } else { + gdb_test "$cmd -filename" \ + "-filename requires an argument" + } + + foreach_with_prefix str { + "STR" + "\"STR\"" + "\\\"STR" + "'STR'" + "\\'STR" + "\"STR AAA\"" + "'STR BBB'" + "\"STR 'CCC' DDD\"" + "'STR \"EEE\" FFF'" + "\"STR \\\"GGG\\\" HHH\"" + "'STR \\\'III\\\' JJJ'" + } { + res_test_gdb_complete_none \ + "1 [expect_none ""]" \ + "$cmd -filename ${str}" + gdb_test "$cmd -filename ${str} --" [expect_filename "${str}" ""] + + # Completing at "-" after parsing STR should list all options. + res_test_gdb_complete_multiple \ + "1 [expect_filename "${str}" "-"]" \ + "$cmd -filename ${str} " "-" "" $all_options + + # Check that only $STR is considered part of the filename's value. + # I.e., that we stop parsing the filename at the first + # whitespace or after the closing quote of $STR. + if {$variant == "require-delimiter"} { + res_test_gdb_complete_none \ + "1 [expect_filename "${str}" "BAR"]" \ + "$cmd -filename ${str} BAR" + } else { + res_test_gdb_complete_none "0 BAR" "$cmd -filename ${str} BAR" + } + gdb_test "$cmd -filename ${str} BAR --" "Unrecognized option at: BAR --" + } +} + # Run the options framework tests first. foreach_with_prefix cmd { "require-delimiter" @@ -1045,6 +1125,7 @@ foreach_with_prefix cmd { } test-enum $cmd test-string $cmd + test-filename $cmd } # Run the print integration tests, both as "standalone", and under