2019-07-03 Pedro Alves <palves@redhat.com>
+ * cli/cli-option.c (union option_value) <string>: New field.
+ (struct option_def_and_value): Add ctor, move ctor, dtor and
+ use DISABLE_COPY_AND_ASSIGN.
+ (option_def_and_value::clear_value): New.
+ (parse_option, save_option_value_in_ctx, get_val_type_str)
+ (add_setshow_cmds_for_options): Handle var_string.
+ * cli-option.h (union option_def::var_address) <string>: New
+ field.
+ (struct string_option_def): New.
+ * maint-test-options.c (struct test_options_opts): Add default
+ ctor and use DISABLE_COPY_AND_ASSIGN.
+ <string_opt>: New field.
+ (test_options_opts::~test_options_opts): New.
+ (test_options_opts::dump): Also dump "-string".
+ (test_options_option_defs): Install "string.
+
+2019-07-03 Pedro Alves <palves@redhat.com>
+
* cli/cli-option.c (parse_option) <var_enum>: Don't return an
option_value with a null enumeration.
(complete_options): Save the option values in the context.
/* For var_enum options. */
const char *enumeration;
+
+ /* For var_string options. This is malloc-allocated. */
+ char *string;
};
/* Holds an options definition and its value. */
/* The option's value, if any. */
gdb::optional<option_value> value;
+
+ /* Constructor. */
+ option_def_and_value (const option_def &option_, void *ctx_,
+ gdb::optional<option_value> &&value_ = {})
+ : option (option_),
+ ctx (ctx_),
+ value (std::move (value_))
+ {
+ clear_value (option_, value_);
+ }
+
+ /* Move constructor. Need this because for some types the values
+ are allocated on the heap. */
+ option_def_and_value (option_def_and_value &&rval)
+ : option (rval.option),
+ ctx (rval.ctx),
+ value (std::move (rval.value))
+ {
+ clear_value (rval.option, rval.value);
+ }
+
+ DISABLE_COPY_AND_ASSIGN (option_def_and_value);
+
+ ~option_def_and_value ()
+ {
+ if (value.has_value ())
+ {
+ if (option.type == var_string)
+ xfree (value->string);
+ }
+ }
+
+private:
+
+ /* Clear the option_value, without releasing it. This is used after
+ the value has been moved to some other option_def_and_value
+ instance. This is needed because for some types the value is
+ allocated on the heap, so we must clear the pointer in the
+ source, to avoid a double free. */
+ static void clear_value (const option_def &option,
+ gdb::optional<option_value> &value)
+ {
+ if (value.has_value ())
+ {
+ if (option.type == var_string)
+ value->string = nullptr;
+ }
+ }
};
static void save_option_value_in_ctx (gdb::optional<option_def_and_value> &ov);
val.enumeration = parse_cli_var_enum (args, match->enums);
return option_def_and_value {*match, match_ctx, val};
}
+ case var_string:
+ {
+ if (check_for_argument (args, "--"))
+ {
+ /* Treat e.g., "maint test-options -string --" as if there
+ was no argument after "-string". */
+ error (_("-%s requires an argument"), match->name);
+ }
+
+ const char *arg_start = *args;
+ *args = skip_to_space (*args);
+
+ if (*args == arg_start)
+ error (_("-%s requires an argument"), match->name);
+
+ option_value val;
+ val.string = savestring (arg_start, *args - arg_start);
+ return option_def_and_value {*match, match_ctx, val};
+ }
default:
/* Not yet. */
*ov->option.var_address.enumeration (ov->option, ov->ctx)
= ov->value->enumeration;
break;
+ case var_string:
+ *ov->option.var_address.string (ov->option, ov->ctx)
+ = ov->value->string;
+ ov->value->string = nullptr;
+ break;
default:
gdb_assert_not_reached ("unhandled option type");
}
}
return buffer.c_str ();
}
+ case var_string:
+ return "STRING";
default:
return nullptr;
}
nullptr, option.show_cmd_cb,
set_list, show_list);
}
+ else if (option.type == var_string)
+ {
+ add_setshow_string_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"));
}
unsigned int *(*uinteger) (const option_def &, void *ctx);
int *(*integer) (const option_def &, void *ctx);
const char **(*enumeration) (const option_def &, void *ctx);
+ char **(*string) (const option_def &, void *ctx);
}
var_address;
}
};
+/* A var_string command line option. */
+
+template<typename Context>
+struct string_option_def : option_def
+{
+ string_option_def (const char *long_option_,
+ 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_string,
+ (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>;
+ }
+};
+
/* A group of options that all share the same context pointer to pass
to the options' get-current-value callbacks. */
struct option_def_group
readline, for proper testing of TAB completion.
These maintenance commands support options of all the different
- available kinds of commands (boolean, enum, flag, uinteger):
+ available kinds of commands (boolean, enum, flag, string, uinteger):
(gdb) maint test-options require-delimiter -[TAB]
- -bool -enum -flag -uinteger -xx1 -xx2
+ -bool -enum -flag -string -uinteger -xx1 -xx2
(gdb) maint test-options require-delimiter -bool o[TAB]
off on
const char *enum_opt = test_options_enum_values_xxx;
unsigned int uint_opt = 0;
int zuint_unl_opt = 0;
+ char *string_opt = nullptr;
+
+ test_options_opts () = default;
+
+ DISABLE_COPY_AND_ASSIGN (test_options_opts);
+
+ ~test_options_opts ()
+ {
+ xfree (string_opt);
+ }
/* Dump the options to FILE. ARGS is the remainder unprocessed
arguments. */
{
fprintf_unfiltered (file,
_("-flag %d -xx1 %d -xx2 %d -bool %d "
- "-enum %s -uint %s -zuint-unl %s -- %s\n"),
+ "-enum %s -uint %s -zuint-unl %s -string '%s' -- %s\n"),
flag_opt,
xx1_opt,
xx2_opt,
(zuint_unl_opt == -1
? "unlimited"
: plongest (zuint_unl_opt)),
+ (string_opt != nullptr
+ ? string_opt
+ : ""),
args);
}
};
nullptr, /* show_doc */
nullptr, /* help_doc */
},
+
+ /* A string option. */
+ gdb::option::string_option_def<test_options_opts> {
+ "string",
+ [] (test_options_opts *opts) { return &opts->string_opt; },
+ nullptr, /* show_cmd_cb */
+ N_("A string option."),
+ },
};
/* Create an option_def_group for the test_options_opts options, with
2019-07-03 Pedro Alves <palves@redhat.com>
+ * gdb.base/options.exp (expect_none, expect_flag, expect_bool)
+ (expect_integer): Adjust to expect "-string".
+ (expect_string): New.
+ (all_options): Expect "-string".
+ (test-flag, test-boolean): Adjust to expect "-string".
+ (test-string): New proc.
+ (top level): Call it.
+
+2019-07-03 Pedro Alves <palves@redhat.com>
+
* gdb.base/options.exp (test-misc, test-flag, test-boolean)
(test-uinteger, test-enum): Adjust res_test_gdb_... calls to pass
the expected output in the success.
# 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 "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -- $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 "-flag 1 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -- $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 "-flag 0 -xx1 0 -xx2 0 -bool 1 -enum xxx -uint 0 -zuint-unl 0 -string '' -- $operand"
}
# Return a string for the expected result of running "maint
# 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"
+ return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint $val -zuint-unl 0 -string '' -- $operand"
} elseif {$option == "zuinteger-unlimited"} {
- return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl $val -- $operand"
+ return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl $val -string '' -- $operand"
} else {
error "unsupported option: $option"
}
}
+# Return a string for the expected result of running "maint
+# test-options xxx", with -string set to $STR. OPERAND is the
+# expected operand.
+proc expect_string {str operand} {
+ return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '$str' -- $operand"
+}
+
set all_options {
"-bool"
"-enum"
"-flag"
+ "-string"
"-uinteger"
"-xx1"
"-xx2"
# 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"
+ "-flag 0 -xx1 1 -xx2 1 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -- non flags args"
# Extract 2 known flags in front of unknown flags.
gdb_test "$cmd -xx1 -xx2 -a -b -c -xx1 --" \
# E.g., "frame apply all -past-main COMMAND".
if {$variant == "require-delimiter"} {
+ set match_list $all_options
+ lappend match_list "off" "on"
res_test_gdb_complete_multiple \
"1 [expect_none ""]" \
- "$cmd -bool " "" "" {
- "-bool"
- "-enum"
- "-flag"
- "-uinteger"
- "-xx1"
- "-xx2"
- "-zuinteger-unlimited"
- "off"
- "on"
- }
+ "$cmd -bool " "" "" $match_list
} else {
res_test_gdb_complete_none "0 " "$cmd -bool "
}
gdb_test "$cmd -enum www --" "Undefined item: \"www\"."
}
+# String option tests.
+proc_with_prefix test-string {variant} {
+ global all_options
+
+ set cmd [make_cmd $variant]
+
+ res_test_gdb_complete_none \
+ "1 [expect_none ""]" \
+ "$cmd -string "
+
+ # Check that "-" where a value is expected does not show the
+ # command's options. I.e., a string's value is not optional.
+ # Check both completion and running the command.
+ res_test_gdb_complete_none \
+ "1 [expect_none ""]" \
+ "$cmd -string -"
+ gdb_test "$cmd -string --"\
+ "-string requires an argument"
+ if {$variant == "require-delimiter"} {
+ gdb_test "$cmd -string" [expect_none "-string"]
+ } else {
+ gdb_test "$cmd -string"\
+ "-string requires an argument"
+ }
+
+ res_test_gdb_complete_none \
+ "1 [expect_none ""]" \
+ "$cmd -string STR"
+ gdb_test "$cmd -string STR --" [expect_string "STR" ""]
+
+ # Completing at "-" after parsing STR should list all options.
+ res_test_gdb_complete_multiple \
+ "1 [expect_string "STR" "-"]" \
+ "$cmd -string STR " "-" "" $all_options
+
+ # Check that only FOO is considered part of the string's value.
+ # I.e., that we stop parsing the string at the first whitespace.
+ if {$variant == "require-delimiter"} {
+ res_test_gdb_complete_none \
+ "1 [expect_string "FOO" "BAR"]" \
+ "$cmd -string FOO BAR"
+ } else {
+ res_test_gdb_complete_none "0 BAR" "$cmd -string FOO BAR"
+ }
+ gdb_test "$cmd -string FOO BAR --" "Unrecognized option at: BAR --"
+}
+
# Run the options framework tests first.
foreach_with_prefix cmd {
"require-delimiter"
test-uinteger $cmd $subcmd
}
test-enum $cmd
+ test-string $cmd
}
# Run the print integration tests, both as "standalone", and under