gdb: add filename option support

This commit adds support for filename options to GDB's option
sub-system (see cli/cli-option.{c,h}).

The new filename options support quoted and escaped filenames, and tab
completion is fully supported.

This commit adds the new option, and adds these options to the
'maintenance test-options' command as '-filename', along with some
tests that exercise this new option.

I've split the -filename testing into two.  In gdb.base/options.exp we
use the -filename option with some arbitrary strings.  This tests that
GDB can correctly extract the value from a filename option, and that
GDB can complete other options after a filename option.  However,
these tests don't actually pass real filenames, nor do they test
filename completion.

In gdb.base/filename-completion.exp I have added some tests that test
the -filename option with real filenames, and exercise filename tab
completion.

This commit doesn't include any real uses of the new filename options,
that will come in the next commit.
This commit is contained in:
Andrew Burgess 2024-09-11 15:25:32 +01:00
parent 91f8973ba3
commit 3105b46ed1
5 changed files with 222 additions and 18 deletions

View File

@ -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<const option_def_group> 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<option_def_and_value> &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");
}

View File

@ -308,6 +308,26 @@ struct string_option_def : option_def
}
};
/* A var_filename command line option. */
template<typename Context>
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<std::string, Context>;
}
};
/* A group of options that all share the same context pointer to pass
to the options' get-current-value callbacks. */
struct option_def_group

View File

@ -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<test_options_opts> {
"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

View File

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

View File

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