Introduce the "with" command

( See original discussion and prototype here:
   https://sourceware.org/ml/gdb-patches/2019-05/msg00570.html )

 (gdb) help with
 Temporarily set SETTING to VALUE, run COMMAND, and restore SETTING.
 Usage: with SETTING [VALUE] [-- COMMAND]
 Usage: w SETTING [VALUE] [-- COMMAND]
 With no COMMAND, repeats the last executed command.
 SETTING is any setting you can change with the "set" subcommands.
 E.g.:
   with language pascal -- print obj
   with print elements unlimited -- print obj

As can be seen above, the "with" command is just like "set", but
instead of setting the setting permanently, it sets the setting, runs
a command and then restores the setting.

 (gdb) p g_s
 $1 = {a = 1, b = 2, c = 3}
 (gdb) with language ada -- print g_s
 $2 = (a => 1, b => 2, c => 3)
 Warning: the current language does not match this frame.
 (gdb) show language
 The current source language is "auto; currently c".
 (gdb) with print elements 100 -- with print object on -- print 1
 $3 = 1

You can shorten things a bit though, as long as unambiguous.  So this:

 (gdb) with print elements 100 -- with print object off -- print 1

is the same as:

 (gdb) w p el 100 -- w p o 0 -- p 1

Note that the patch adds a "w" alias for "with", as "w" is not
currently taken:

 (gdb) w
 Ambiguous command "w": watch, wh, whatis, where, while, while-stepping, winheight, ws.

Let me know if you'd prefer to reserve "w" for one of the other
commands above.  IMHO, this command will end up being used frequently
enough that it deserves the "w" shorthand.

A nice feature is that this is fully integrated with TAB-completion:

 (gdb) with p[TAB]
 pagination  print       prompt      python
 (gdb) with print [TAB]
 address                max-depth              static-members
 array                  max-symbolic-offset    symbol
 array-indexes          null-stop              symbol-filename
 asm-demangle           object                 symbol-loading
 demangle               pascal_static-members  thread-events
 elements               pretty                 type
 entry-values           raw                    union
 frame-arguments        repeats                vtbl
 inferior-events        sevenbit-strings
 (gdb) with print [TAB]

 (gdb) with print elements unlimited -- thread apply all -[TAB]
 -ascending  -c          -q          -s

 (gdb) with print elements unlimited -- print -[TAB]
 -address         -max-depth       -repeats         -vtbl
 -array           -null-stop       -static-members
 -array-indexes   -object          -symbol
 -elements        -pretty          -union

The main advantage of this new command compared to command options,
like the new "print -OPT", is that this command works with any
setting, and, it works nicely when you want to override a setting
while running a user-defined command, like:

 (gdb) with print pretty -- usercmd

The disadvantage is that it isn't as compact or easy to type.  I think
of command options and this command as complementary.  I think that
even with this new command, it makes sense to continue developing the
command options in the direction of exposing most-oft-used settings as
command options.

Inspired by Philippe's "/" command proposal, if no command is
specified, then the last command is re-invoked, under the overridden
setting:

 (gdb) p g_s
 $1 = {a = 1, b = 2, c = 3}
 (gdb) with language ada
 $2 = (a => 1, b => 2, c => 3)
 Warning: the current language does not match this frame.

Note: "with" requires "--" to separate the setting from the command.
It might be possible to do without that, but, I haven't tried it yet,
and I think that this can go in without it.  We can always downgrade
to making "--" optional if we manage to make it work.

On to the patch itself, the implementation of the command is simpler
than one might expect.  A few details:

- I factored out a bit from pipe_command into repeat_previous
  directly, because otherwise I'd need to copy&paste the same code and
  same error message in the with command.

- The parse_cli_var_uinteger / parse_cli_var_zuinteger_unlimited /
  do_set_command changes are necessary since we can now pass an empty
  string as argument.

- do_show_command was split in two, as a FIXME comment suggests, but
  for a different reason: we need to get a string version of a "set"
  command's value, and we already had code for that in
  do_show_command.  That code is now factored out to the new
  get_setshow_command_value_string function.

- There's a new "maint with" command added too:

   (gdb) help maint with
   Like "with", but works with "maintenance set" variables.
   Usage: maintenance with SETTING [VALUE] [-- COMMAND]
   With no COMMAND, repeats the last executed command.
   SETTING is any setting you can change with the "maintenance set"
   subcommands.

  "with" and "maint with" share 99% of the implementation.

  This might be useful on its own, but it's also useful for testing,
  since with this, we can use the "maint set/show test-settings"
  settings for exercising the "with" machinery with all the command
  type variants (all enum var_types).  This is done in the new
  gdb/base/with.exp testcase.

The documentation bits are originally based on Philippe's docs for the
"/" command, hence the attribution in the ChangeLog.

gdb/ChangeLog:
2019-07-03  Pedro Alves  <palves@redhat.com>

	* NEWS (New commands): Mention "with" and "maint with".
	* cli/cli-cmds.c (with_command_1, with_command_completer_1)
	(with_command, with_command_completer): New.
	(pipe_command): Adjust to new repeat_previous
	interface.
	(_initialize_cli_cmds): Install the "with" command and its "w"
	alias.
	* cli/cli-cmds.h (with_command_1, with_command_completer_1): New
	declarations.
	* cli/cli-setshow.c (parse_cli_var_uinteger)
	(parse_cli_var_zuinteger_unlimited, do_set_command): Handle empty
	argument strings for all var_types.
	(get_setshow_command_value_string): New, factored out from ...
	(do_show_command): ... this.
	* cli/cli-setshow.h: Include <string>.
	(get_setshow_command_value_string): Declare.
	* command.h (repeat_previous): Now returns const char *.  Adjust
	comment.
	* maint.c: Include "cli/cli-cmds.h".
	(maintenance_with_cmd, maintenance_with_cmd_completer): New.
	(_initialize_maint_cmds): Register the "maintenance with" command.
	* top.c (repeat_previous): Move bits from pipe_command here:
	Return the saved command line, if any; error out if there's no
	command to relaunch.

gdb/doc/ChangeLog:
2019-07-03  Pedro Alves  <palves@redhat.com>
	    Philippe Waroquiers  <philippe.waroquiers@skynet.be>

	* gdb.texinfo (Command Settings): New node documenting the general
	concept of settings, how to change them, and the new "with"
	command.
	(Maintenance Commands): Document "maint with".

gdb/testsuite/ChangeLog:
2019-07-03  Pedro Alves  <palves@redhat.com>

	* gdb.base/with.c: New file.
	* gdb.base/with.exp: New file.
This commit is contained in:
Pedro Alves 2019-07-03 13:34:20 +01:00
parent c6ac893109
commit fdbc98707b
14 changed files with 716 additions and 49 deletions

View file

@ -211,6 +211,116 @@ show_command (const char *arg, int from_tty)
cmd_show_list (showlist, from_tty, "");
}
/* See cli/cli-cmds.h. */
void
with_command_1 (const char *set_cmd_prefix,
cmd_list_element *setlist, const char *args, int from_tty)
{
const char *delim = strstr (args, "--");
const char *nested_cmd = nullptr;
if (delim == args)
error (_("Missing setting before '--' delimiter"));
if (delim == nullptr || *skip_spaces (&delim[2]) == '\0')
nested_cmd = repeat_previous ();
cmd_list_element *set_cmd = lookup_cmd (&args, setlist, set_cmd_prefix,
/*allow_unknown=*/ 0,
/*ignore_help_classes=*/ 1);
gdb_assert (set_cmd != nullptr);
if (set_cmd->var == nullptr)
error (_("Cannot use this setting with the \"with\" command"));
std::string temp_value
= (delim == nullptr ? args : std::string (args, delim - args));
if (nested_cmd == nullptr)
nested_cmd = skip_spaces (delim + 2);
std::string org_value = get_setshow_command_value_string (set_cmd);
/* Tweak the setting to the new temporary value. */
do_set_command (temp_value.c_str (), from_tty, set_cmd);
try
{
scoped_restore save_async = make_scoped_restore (&current_ui->async, 0);
/* Execute the nested command. */
execute_command (nested_cmd, from_tty);
}
catch (const gdb_exception &ex)
{
/* Restore the setting and rethrow. If restoring the setting
throws, swallow the new exception and warn. There's nothing
else we can reasonably do. */
try
{
do_set_command (org_value.c_str (), from_tty, set_cmd);
}
catch (const gdb_exception &ex2)
{
warning (_("Couldn't restore setting: %s"), ex2.what ());
}
throw;
}
/* Restore the setting. */
do_set_command (org_value.c_str (), from_tty, set_cmd);
}
/* See cli/cli-cmds.h. */
void
with_command_completer_1 (const char *set_cmd_prefix,
completion_tracker &tracker,
const char *text)
{
tracker.set_use_custom_word_point (true);
const char *delim = strstr (text, "--");
/* If we're still not past the "--" delimiter, complete the "with"
command as if it was a "set" command. */
if (delim == text
|| delim == nullptr
|| !isspace (delim[-1])
|| !(isspace (delim[2]) || delim[2] == '\0'))
{
std::string new_text = std::string (set_cmd_prefix) + text;
tracker.advance_custom_word_point_by (-(int) strlen (set_cmd_prefix));
complete_nested_command_line (tracker, new_text.c_str ());
return;
}
/* We're past the "--" delimiter. Complete on the sub command. */
const char *nested_cmd = skip_spaces (delim + 2);
tracker.advance_custom_word_point_by (nested_cmd - text);
complete_nested_command_line (tracker, nested_cmd);
}
/* The "with" command. */
static void
with_command (const char *args, int from_tty)
{
with_command_1 ("set ", setlist, args, from_tty);
}
/* "with" command completer. */
static void
with_command_completer (struct cmd_list_element *ignore,
completion_tracker &tracker,
const char *text, const char * /*word*/)
{
with_command_completer_1 ("set ", tracker, text);
}
/* Provide documentation on command or list given by COMMAND. FROM_TTY
is ignored. */
@ -878,12 +988,7 @@ pipe_command (const char *arg, int from_tty)
arg += delim.length (); /* Skip the delimiter. */
if (gdb_cmd.empty ())
{
repeat_previous ();
gdb_cmd = skip_spaces (get_saved_command_line ());
if (gdb_cmd.empty ())
error (_("No previous command to relaunch"));
}
gdb_cmd = repeat_previous ();
const char *shell_command = skip_spaces (arg);
if (*shell_command == '\0')
@ -1850,6 +1955,23 @@ Generic command for showing things about the debugger."),
/* Another way to get at the same thing. */
add_info ("set", show_command, _("Show all GDB settings."));
c = add_com ("with", class_vars, with_command, _("\
Temporarily set SETTING to VALUE, run COMMAND, and restore SETTING.\n\
Usage: with SETTING [VALUE] [-- COMMAND]\n\
Usage: w SETTING [VALUE] [-- COMMAND]\n\
With no COMMAND, repeats the last executed command.\n\
\n\
SETTING is any setting you can change with the \"set\" subcommands.\n\
E.g.:\n\
with language pascal -- print obj\n\
with print elements unlimited -- print obj\n\
\n\
You can change multiple settings using nested with, and use\n\
abbreviations for commands and/or values. E.g.:\n\
w la p -- w p el u -- p obj"));
set_cmd_completer_handle_brkchars (c, with_command_completer);
add_com_alias ("w", "with", class_vars, 1);
add_cmd ("commands", no_set_class, show_commands, _("\
Show the history of commands you typed.\n\
You can supply a command number to start with, or a `+' to start after\n\

View file

@ -142,4 +142,19 @@ extern gdb::optional<open_script>
extern int source_verbose;
extern int trace_commands;
/* Common code for the "with" and "maintenance with" commands.
SET_CMD_PREFIX is the spelling of the corresponding "set" command
prefix: i.e., "set " or "maintenance set ". SETLIST is the command
element for the same "set" command prefix. */
extern void with_command_1 (const char *set_cmd_prefix,
cmd_list_element *setlist,
const char *args, int from_tty);
/* Common code for the completers of the "with" and "maintenance with"
commands. SET_CMD_PREFIX is the spelling of the corresponding
"set" command prefix: i.e., "set " or "maintenance set ". */
extern void with_command_completer_1 (const char *set_cmd_prefix,
completion_tracker &tracker,
const char *text);
#endif /* CLI_CLI_CMDS_H */

View file

@ -190,7 +190,7 @@ parse_cli_var_uinteger (var_types var_type, const char **arg,
{
LONGEST val;
if (*arg == nullptr)
if (*arg == nullptr || **arg == '\0')
{
if (var_type == var_uinteger)
error_no_arg (_("integer to set it to, or \"unlimited\"."));
@ -225,7 +225,7 @@ parse_cli_var_zuinteger_unlimited (const char **arg, bool expression)
{
LONGEST val;
if (*arg == nullptr)
if (*arg == nullptr || **arg == '\0')
error_no_arg (_("integer to set it to, or \"unlimited\"."));
if (is_unlimited_literal (arg, expression))
@ -308,6 +308,9 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
gdb_assert (c->type == set_cmd);
if (arg == NULL)
arg = "";
switch (c->var_type)
{
case var_string:
@ -317,8 +320,6 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
char *q;
int ch;
if (arg == NULL)
arg = "";
newobj = (char *) xmalloc (strlen (arg) + 2);
p = arg;
q = newobj;
@ -364,9 +365,6 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
}
break;
case var_string_noescape:
if (arg == NULL)
arg = "";
if (*(char **) c->var == NULL || strcmp (*(char **) c->var, arg) != 0)
{
xfree (*(char **) c->var);
@ -376,14 +374,14 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
}
break;
case var_filename:
if (arg == NULL)
if (*arg == '\0')
error_no_arg (_("filename to set it to."));
/* FALLTHROUGH */
case var_optional_filename:
{
char *val = NULL;
if (arg != NULL)
if (*arg != '\0')
{
/* Clear trailing whitespace of filename. */
const char *ptr = arg + strlen (arg) - 1;
@ -455,7 +453,7 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
{
LONGEST val;
if (arg == NULL)
if (*arg == '\0')
{
if (c->var_type == var_integer)
error_no_arg (_("integer to set it to, or \"unlimited\"."));
@ -625,24 +623,13 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
}
}
/* Do a "show" command. ARG is NULL if no argument, or the
text of the argument, and FROM_TTY is nonzero if this command is
being entered directly by the user (i.e. these are just like any
other command). C is the command list element for the command. */
/* See cli/cli-setshow.h. */
void
do_show_command (const char *arg, int from_tty, struct cmd_list_element *c)
std::string
get_setshow_command_value_string (cmd_list_element *c)
{
struct ui_out *uiout = current_uiout;
gdb_assert (c->type == show_cmd);
string_file stb;
/* Possibly call the pre hook. */
if (c->pre_show_hook)
(c->pre_show_hook) (c);
switch (c->var_type)
{
case var_string:
@ -672,9 +659,7 @@ do_show_command (const char *arg, int from_tty, struct cmd_list_element *c)
stb.puts ("auto");
break;
default:
internal_error (__FILE__, __LINE__,
_("do_show_command: "
"invalid var_auto_boolean"));
gdb_assert_not_reached ("invalid var_auto_boolean");
break;
}
break;
@ -703,23 +688,42 @@ do_show_command (const char *arg, int from_tty, struct cmd_list_element *c)
}
break;
default:
error (_("gdb internal error: bad var_type in do_show_command"));
gdb_assert_not_reached ("bad var_type");
}
return std::move (stb.string ());
}
/* FIXME: cagney/2005-02-10: Need to split this in half: code to
convert the value into a string (esentially the above); and
code to print the value out. For the latter there should be
MI and CLI specific versions. */
/* Do a "show" command. ARG is NULL if no argument, or the
text of the argument, and FROM_TTY is nonzero if this command is
being entered directly by the user (i.e. these are just like any
other command). C is the command list element for the command. */
void
do_show_command (const char *arg, int from_tty, struct cmd_list_element *c)
{
struct ui_out *uiout = current_uiout;
gdb_assert (c->type == show_cmd);
/* Possibly call the pre hook. */
if (c->pre_show_hook)
(c->pre_show_hook) (c);
std::string val = get_setshow_command_value_string (c);
/* FIXME: cagney/2005-02-10: There should be MI and CLI specific
versions of code to print the value out. */
if (uiout->is_mi_like_p ())
uiout->field_stream ("value", stb);
uiout->field_string ("value", val.c_str ());
else
{
if (c->show_value_func != NULL)
c->show_value_func (gdb_stdout, from_tty, c, stb.c_str ());
c->show_value_func (gdb_stdout, from_tty, c, val.c_str ());
else
deprecated_show_value_hack (gdb_stdout, from_tty, c, stb.c_str ());
deprecated_show_value_hack (gdb_stdout, from_tty, c, val.c_str ());
}
c->func (c, NULL, from_tty);

View file

@ -17,6 +17,8 @@
#ifndef CLI_CLI_SETSHOW_H
#define CLI_CLI_SETSHOW_H
#include <string>
struct cmd_list_element;
/* Parse ARG, an option to a boolean variable.
@ -55,6 +57,9 @@ extern void do_set_command (const char *arg, int from_tty,
extern void do_show_command (const char *arg, int from_tty,
struct cmd_list_element *c);
/* Get a string version of C's current value. */
extern std::string get_setshow_command_value_string (cmd_list_element *c);
extern void cmd_show_list (struct cmd_list_element *list, int from_tty,
const char *prefix);