[PATCH] Add a way to preserve overridden GDB commands for later invocation

Marco Barisione mbarisione@undo.io
Mon Oct 28 13:33:00 GMT 2019


When a new Python command overrides an existing command, the
functionality of the original command used to be lost.  As not all
features are exposed through the Python API, this makes it impossile to
build around existing commands.  This patch adds support for preserving
commands when overridden.

This feature is exposed through the Python API with a new
gdb.Command.invoke_overridden () method which can be called from the
implementation of the invoke () method.
To avoid unexpected behavioural changes, new commands implemented in
Python are, by default, freed when overridden.  A command which wants
the new behaviour can pass the new preserve_when_overridden argument to
gdb.Command.__init__ ().

All default commands (that is, the ones defined by GDB itself) are
preserved when overridden.

All new commands added thorugh GDB scripting (i.e. using the define
command) are not preserved when overridden.  This could be changed in a
later patch but it doesn't seem important (nor useful) at the moment.

This change is achieved by adding a preserve_when_overridden field to
struct cmd_list_element.  If true, when another command overrides it,
the destroyer function is not called and the previous command is saved
in the preserved_overridden_cmd field.  This way, the overridden command
is still accessible if needed.

2019-10-28  Marco Barisione  <mbarisione@undo.io>

	Add support for preserving the functionalities from overridden
	commands.
	* cli/cli-decode.c (delete_cmd): Rename to remove_cmd.
	(remove_cmd): Allow commands to be preserved after overriding
	them.
	(do_add_cmd): Support the preservation of overridden commands.
	(add_alias_cmd): Use delete_cmd instead of remove_cmd and update
	the assertions.
	* cli/cli-decode.h (struct cmd_list_element): Add
	preserve_when_overridden and preserved_overridden_cmd.
	* cli/cli-script.c (do_define_command): Make default commands
	be preserved by default.
	* python/py-cmd.c (cmdpy_invoke_overridden): Add an
	invoke_overridden command method to gdb.Command.
	(cmdpy_init): Add a preserve_when_overridden argument to
	gdb.Command.__init__.
	* NEWS: Document the addition of gdb.Command.invoke_overridden
	and the new preserve_when_overridden argument for
	gdb.Command.__init__.

gdb/doc/ChangeLog:

2019-10-28  Marco Barisione  <mbarisione@undo.io>

	* python.texi (Commands In Python): Document
	gdb.Command.invoke_overridden and the new
	preserve_when_overridden argument for gdb.Command.__init__.

gdb/testsuite/ChangeLog:

2019-10-28  Marco Barisione  <mbarisione@undo.io>

	* gdb.python/py-chain-invoke.exp: New file.
	* gdb.python/py-chain-invoke.py: New test.
---
 gdb/NEWS                                     |   8 +
 gdb/cli/cli-decode.c                         |  84 +++--
 gdb/cli/cli-decode.h                         |  10 +
 gdb/cli/cli-script.c                         |   3 +
 gdb/doc/python.texi                          |  32 +-
 gdb/python/py-cmd.c                          |  54 +++-
 gdb/testsuite/gdb.python/py-chain-invoke.exp | 315 +++++++++++++++++++
 gdb/testsuite/gdb.python/py-chain-invoke.py  |  72 +++++
 8 files changed, 554 insertions(+), 24 deletions(-)
 create mode 100644 gdb/testsuite/gdb.python/py-chain-invoke.exp
 create mode 100644 gdb/testsuite/gdb.python/py-chain-invoke.py

diff --git a/gdb/NEWS b/gdb/NEWS
index 25e67e43c8..e0f1f0a9c1 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -71,6 +71,14 @@
   ** gdb.Block now supports the dictionary syntax for accessing symbols in
      this block (e.g. block['local_variable']).
 
+  ** gdb.Command has a new method 'invoke_overridden' to invoke the
+     command with the same name, if any, which was overridden by the new
+     command.  gdb.Command.__init__ has a new 'preserve_when_overridden'
+     argument which, if true (the default is false), means that the
+     command, once overridden by another command of the same name, can
+     still be invoked through the new command's 'invoke_overridden'
+     method.
+
 * New commands
 
 | [COMMAND] | SHELL_COMMAND
diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c
index 7ace72fb7e..e3fe9aa26c 100644
--- a/gdb/cli/cli-decode.c
+++ b/gdb/cli/cli-decode.c
@@ -30,12 +30,14 @@
 
 static void undef_cmd_error (const char *, const char *);
 
-static struct cmd_list_element *delete_cmd (const char *name,
-					    struct cmd_list_element **list,
-					    struct cmd_list_element **prehook,
-					    struct cmd_list_element **prehookee,
-					    struct cmd_list_element **posthook,
-					    struct cmd_list_element **posthookee);
+static struct cmd_list_element *remove_cmd (
+  const char *name,
+  struct cmd_list_element **list,
+  struct cmd_list_element **preserved_overridden_cmd,
+  struct cmd_list_element **prehook,
+  struct cmd_list_element **prehookee,
+  struct cmd_list_element **posthook,
+  struct cmd_list_element **posthookee);
 
 static struct cmd_list_element *find_cmd (const char *command,
 					  int len,
@@ -198,10 +200,12 @@ do_add_cmd (const char *name, enum command_class theclass,
 							    doc);
   struct cmd_list_element *p, *iter;
 
+  struct cmd_list_element *preserved_overridden_cmd;
   /* Turn each alias of the old command into an alias of the new
      command.  */
-  c->aliases = delete_cmd (name, list, &c->hook_pre, &c->hookee_pre,
-			   &c->hook_post, &c->hookee_post);
+  c->aliases = remove_cmd (name, list, &preserved_overridden_cmd,
+			   &c->hook_pre, &c->hookee_pre, &c->hook_post,
+			   &c->hookee_post);
   for (iter = c->aliases; iter; iter = iter->alias_chain)
     iter->cmd_pointer = c;
   if (c->hook_pre)
@@ -212,6 +216,7 @@ do_add_cmd (const char *name, enum command_class theclass,
     c->hook_post->hookee_post = c;
   if (c->hookee_post)
     c->hookee_post->hook_post = c;
+  c->preserved_overridden_cmd = preserved_overridden_cmd;
 
   if (*list == NULL || strcmp ((*list)->name, name) >= 0)
     {
@@ -300,14 +305,15 @@ add_alias_cmd (const char *name, cmd_list_element *old,
 {
   if (old == 0)
     {
+      struct cmd_list_element *preserved_overridden_cmd;
       struct cmd_list_element *prehook, *prehookee, *posthook, *posthookee;
-      struct cmd_list_element *aliases = delete_cmd (name, list,
-						     &prehook, &prehookee,
-						     &posthook, &posthookee);
+      struct cmd_list_element *aliases =
+	remove_cmd (name, list, &preserved_overridden_cmd,
+		    &prehook, &prehookee, &posthook, &posthookee);
 
       /* If this happens, it means a programmer error somewhere.  */
-      gdb_assert (!aliases && !prehook && !prehookee
-		  && !posthook && ! posthookee);
+      gdb_assert (!aliases && !preserved_overridden_cmd && !prehook &&
+		  !prehookee && !posthook && ! posthookee);
       return 0;
     }
 
@@ -840,13 +846,25 @@ add_setshow_zuinteger_cmd (const char *name, enum command_class theclass,
 
 /* Remove the command named NAME from the command list.  Return the
    list commands which were aliased to the deleted command.  If the
-   command had no aliases, return NULL.  The various *HOOKs are set to
-   the pre- and post-hook commands for the deleted command.  If the
-   command does not have a hook, the corresponding out parameter is
+   command had no aliases, return NULL.
+
+   *PRESERVED_OVERRIDDEN_CMD is set to a command with the same name
+   which was overridden, but can still be called from the new command
+   implementation (that is, its preserve_when_overridden field is
+   true).  This is not necessarily the command which was removed.
+   For instance, if the command itself has preserved_overridden_cmd
+   set to false, but a previous overridden command has it set to
+   true.  If no previosuly overridden command still exists, the
+   corresponding out parameter is set to NULL.
+
+   The various *HOOKs are set to the pre- and post-hook commands for
+   the deleted command.  If the command does not have a hook, the
+   corresponding out parameter is
    set to NULL.  */
 
 static struct cmd_list_element *
-delete_cmd (const char *name, struct cmd_list_element **list,
+remove_cmd (const char *name, struct cmd_list_element **list,
+	    struct cmd_list_element **preserved_overridden_cmd,
 	    struct cmd_list_element **prehook,
 	    struct cmd_list_element **prehookee,
 	    struct cmd_list_element **posthook,
@@ -856,6 +874,7 @@ delete_cmd (const char *name, struct cmd_list_element **list,
   struct cmd_list_element **previous_chain_ptr;
   struct cmd_list_element *aliases = NULL;
 
+  *preserved_overridden_cmd = NULL;
   *prehook = NULL;
   *prehookee = NULL;
   *posthook = NULL;
@@ -866,8 +885,23 @@ delete_cmd (const char *name, struct cmd_list_element **list,
     {
       if (strcmp (iter->name, name) == 0)
 	{
-	  if (iter->destroyer)
-	    iter->destroyer (iter, iter->context);
+	  if (iter->preserve_when_overridden)
+	    *preserved_overridden_cmd = iter;
+	  else
+	    {
+	      /* Even if this item cannot be preserved when overridden, it
+		 may have overridden a command which was preserved.  */
+	      *preserved_overridden_cmd = iter->preserved_overridden_cmd;
+	      /* If the overridden command was not removed and freed, then
+		 it must have preserve_when_overridden be true.  This
+		 also means we don't need to recurse further.  */
+	      gdb_assert (
+	        *preserved_overridden_cmd == nullptr
+		|| (*preserved_overridden_cmd)->preserve_when_overridden);
+	      if (iter->destroyer)
+		iter->destroyer (iter, iter->context);
+	    }
+
 	  if (iter->hookee_pre)
 	    iter->hookee_pre->hook_pre = 0;
 	  *prehook = iter->hook_pre;
@@ -897,7 +931,17 @@ delete_cmd (const char *name, struct cmd_list_element **list,
 	      *prevp = iter->alias_chain;
 	    }
 
-	  delete iter;
+	  if (iter->preserve_when_overridden)
+	    {
+	      /* We preserved this command so we just need to break its
+		 part of the linked list, but not free it.  */
+	      iter->next = nullptr;
+	      gdb_assert (iter == *preserved_overridden_cmd);
+	    }
+	  else
+	    /* If the command cannot be preserved, we already called the
+	       destroyer and this command needs to be deleted.  */
+	    delete iter;
 
 	  /* We won't see another command with the same name.  */
 	  break;
diff --git a/gdb/cli/cli-decode.h b/gdb/cli/cli-decode.h
index 2ec4a97d81..8826922590 100644
--- a/gdb/cli/cli-decode.h
+++ b/gdb/cli/cli-decode.h
@@ -217,6 +217,16 @@ struct cmd_list_element
     /* Pointer to command strings of user-defined commands */
     counted_command_line user_commands;
 
+    /* Whether this command, once overridden by another command, can
+       still be called by the implementation of the new command.  */
+    bool preserve_when_overridden = true;
+
+    /* Pointer to command that was overridden by this command (and that
+       has preserve_when_overridden == true).
+       This command is kept around as it can still be invoked by the
+       overriding command.  */
+    struct cmd_list_element *preserved_overridden_cmd = nullptr;
+
     /* Pointer to command that is hooked by this one, (by hook_pre)
        so the hook can be removed when this one is deleted.  */
     struct cmd_list_element *hookee_pre = nullptr;
diff --git a/gdb/cli/cli-script.c b/gdb/cli/cli-script.c
index 8abd48c678..ea07ef7f36 100644
--- a/gdb/cli/cli-script.c
+++ b/gdb/cli/cli-script.c
@@ -1448,6 +1448,9 @@ do_define_command (const char *comname, int from_tty,
 		  ? c->doc : xstrdup ("User-defined."), list);
   newc->user_commands = std::move (cmds);
 
+  /* Commands defined by the user are never preserved once overridden.  */
+  newc->preserve_when_overridden = false;
+
   /* If this new command is a hook, then mark both commands as being
      tied.  */
   if (hookc)
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 13191fc9ae..dccfd0b244 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3617,7 +3617,7 @@ You can implement new @value{GDBN} CLI commands in Python.  A CLI
 command is implemented using an instance of the @code{gdb.Command}
 class, most commonly using a subclass.
 
-@defun Command.__init__ (name, @var{command_class} @r{[}, @var{completer_class} @r{[}, @var{prefix}@r{]]})
+@defun Command.__init__ (name, @var{command_class} @r{[}, @var{completer_class} @r{[}, @var{prefix} @r{[}, @var{preserve_when_overridden}@r{]]]})
 The object initializer for @code{Command} registers the new command
 with @value{GDBN}.  This initializer is normally invoked from the
 subclass' own @code{__init__} method.
@@ -3644,6 +3644,12 @@ error will occur when completion is attempted.
 command is a prefix command; sub-commands of this command may be
 registered.
 
+@var{preserve_when_overridden} is an optional argument.  If @code{True},
+then the new command is not deleted when it's overridden by another
+command with the same name, otherwise it's deleted.  When a command is
+preserved, the overriding command can still invoke it through its
+@code{invoke_overridden} method.
+
 The help text for the new command is taken from the Python
 documentation string for the command's class, if there is one.  If no
 documentation string is provided, the default value ``This command is
@@ -3686,6 +3692,30 @@ print gdb.string_to_argv ("1 2\ \\\"3 '4 \"5' \"6 '7\"")
 
 @end defun
 
+@defun Command.invoke_overridden (argument, from_tty)
+If the command overrode another command of the same name, this can be
+invoked through this method as long as the overridden command supports this
+feature.
+
+@itemize @bullet
+@item
+All commands defined by @value{GDBN} itself support being invoked when
+overridden
+
+@item
+Commands defined through the @code{define} command are never preserved
+when overridden.
+
+@item
+Commands defined through @code{gdb.Command} are preserved only if they set
+the @code{preserve_when_overridden} argument to the @code{__init__} method
+to @code{true}.
+@end itemize
+
+If there's no overridden and preserved command, this method will raise a
+@code{gdb.error}.
+@end defun
+
 @cindex completion of Python commands
 @defun Command.complete (text, word)
 This method is called by @value{GDBN} when the user attempts
diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c
index 87d1888c52..46ff1eeb3f 100644
--- a/gdb/python/py-cmd.c
+++ b/gdb/python/py-cmd.c
@@ -417,6 +417,38 @@ gdbpy_parse_command_name (const char *name,
   return NULL;
 }
 
+/* Python function which invokes the command overwritten by self.  */
+static PyObject *
+cmdpy_invoke_overridden (PyObject *self, PyObject *py_args, PyObject *kw)
+{
+  static const char *keywords[] = { "args", "from_tty", NULL };
+
+  const char *args = NULL;
+  PyObject *from_tty_obj = NULL;
+  if (!gdb_PyArg_ParseTupleAndKeywords (py_args, kw,
+					"sO",
+					keywords,
+					&args,
+					&from_tty_obj))
+    return NULL;
+
+  cmdpy_object *obj = (cmdpy_object *) self;
+  cmd_list_element *preserved_overridden_cmd =
+    obj->command->preserved_overridden_cmd;
+  if (preserved_overridden_cmd == nullptr)
+    {
+      PyErr_Format (gdbpy_gdb_error,
+		    _("The '%s' command did not override any other command"),
+		    obj->command->name);
+      return NULL;
+    }
+
+  int from_tty = PyObject_IsTrue (from_tty_obj);
+  cmd_func (preserved_overridden_cmd, args, from_tty);
+
+  Py_RETURN_NONE;
+}
+
 /* Object initializer; sets up gdb-side structures for command.
 
    Use: __init__(NAME, COMMAND_CLASS [, COMPLETER_CLASS][, PREFIX]]).
@@ -448,8 +480,10 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw)
   struct cmd_list_element **cmd_list;
   char *cmd_name, *pfx_name;
   static const char *keywords[] = { "name", "command_class", "completer_class",
-				    "prefix", NULL };
+				    "prefix", "preserve_when_overridden",
+				    NULL };
   PyObject *is_prefix = NULL;
+  PyObject *preserve_when_overridden_obj = NULL;
   int cmp;
 
   if (obj->command)
@@ -461,9 +495,10 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw)
       return -1;
     }
 
-  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "si|iO",
+  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "si|iOO",
 					keywords, &name, &cmdtype,
-					&completetype, &is_prefix))
+					&completetype, &is_prefix,
+					&preserve_when_overridden_obj))
     return -1;
 
   if (cmdtype != no_class && cmdtype != class_run
@@ -521,6 +556,14 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw)
 	  return -1;
 	}
     }
+
+  bool preserve_when_overridden = false;
+  if (preserve_when_overridden_obj != nullptr
+      && PyObject_IsTrue (preserve_when_overridden_obj))
+    {
+      preserve_when_overridden = true;
+    }
+
   if (PyObject_HasAttr (self, gdbpy_doc_cst))
     {
       gdbpy_ref<> ds_obj (PyObject_GetAttr (self, gdbpy_doc_cst));
@@ -563,6 +606,7 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw)
       /* There appears to be no API to set this.  */
       cmd->func = cmdpy_function;
       cmd->destroyer = cmdpy_destroyer;
+      cmd->preserve_when_overridden = preserve_when_overridden;
 
       obj->command = cmd;
       set_cmd_context (cmd, self_ref.release ());
@@ -644,6 +688,10 @@ static PyMethodDef cmdpy_object_methods[] =
 {
   { "dont_repeat", cmdpy_dont_repeat, METH_NOARGS,
     "Prevent command repetition when user enters empty line." },
+  { "invoke_overridden", (PyCFunction) cmdpy_invoke_overridden,
+    METH_VARARGS | METH_KEYWORDS,
+    "invoke_overridden (args, from_tty)\n\
+Invoke the command which was overridden by this command." },
 
   { 0 }
 };
diff --git a/gdb/testsuite/gdb.python/py-chain-invoke.exp b/gdb/testsuite/gdb.python/py-chain-invoke.exp
new file mode 100644
index 0000000000..bd07f58592
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-chain-invoke.exp
@@ -0,0 +1,315 @@
+# Copyright (C) 2009-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/>.
+
+# This file is part of the GDB testsuite.  It tests accessing overridden
+# commands.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+# Prepare for testing.
+#
+# This quits GDB (if running), starts a new one, and loads any required
+# external scripts.
+
+proc prepare_gdb {} {
+  global srcdir subdir testfile
+
+  gdb_exit
+  gdb_start
+  gdb_reinitialize_dir $srcdir/$subdir
+
+  # Skip all tests if Python scripting is not enabled.
+  if { [skip_python_tests] } { continue }
+
+  gdb_test_no_output "set confirm off"
+
+  # Load the code which adds commands.
+  set remote_python_file \
+    [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+  gdb_test_no_output "source ${remote_python_file}" "load python file"
+}
+
+# Add a command called CMD through the Python API.
+#
+# The command will print messages including the string MSG when invoked.
+#
+# If PRESERVE_WHEN_OVERRIDDEN is 1, the command is not freed when
+# overridden and can be invoked by the overriding command though the
+# invoke_overridden method.
+
+proc add_py_cmd { cmd msg preserve_when_overridden } {
+  gdb_test_no_output \
+    "python TestCommand ('${cmd}', '${msg}', ${preserve_when_overridden})"
+}
+
+# Add a command called CMD through GDB scripting.
+#
+# The command will print messages including the string MSG when invoked.
+
+proc add_gdb_script_cmd { cmd msg } {
+  gdb_define_cmd $cmd [list "echo gdb: ${msg}\\n"]
+}
+
+# Define an alias.
+
+proc define_alias { alias_name original_name } {
+  gdb_test_no_output "alias ${alias_name} = ${original_name}"
+}
+
+# test_py_commands_deleted [MSG1 [MSG2 [...]]]
+# Check that the Python class instances for the commands with the
+# specified messages were deleted.
+#
+# The specified commands must have been added with add_py_cmd passing
+# 0 as preserve_when_overridden, meaning that the instance became useless
+# (and thus freed) once overridden.
+
+proc test_py_commands_deleted { args } {
+  # Quote the strings.
+  set expected_quoted {}
+  foreach value $args {
+    lappend expected_quoted "'${value}'"
+  }
+  # Add commas to separated elements (to make it into the content of a
+  # Pyyhon list.
+  set py_args [join $expected_quoted ", "]
+  # Check that the arguments we got (and made into a list) correspond
+  # to what was actually deleted.
+  gdb_test_no_output \
+    "python check_were_deleted (\[${py_args}\])" "check commands deleted"
+}
+
+# test_sequence_exact CMD LIST
+# Like gdb_test_exact but, for convenience, it accepts a list of lines
+# instead of a single line.
+proc test_sequence_exact { cmd lines } {
+  set expected_string [join $lines "\n"]
+  gdb_test_exact $cmd $expected_string
+}
+
+proc cs { preserve_when_overridden } {
+  if { $preserve_when_overridden } {
+    return "preserved Python command"
+  } else {
+    return "non-preserved Python command"
+  }
+}
+
+proc test_override_delete_with_py { py_preserve_when_overridden } {
+  with_test_prefix "override delete with [cs $py_preserve_when_overridden]" {
+    prepare_gdb
+    define_alias "new-delete" "del"
+    add_py_cmd "delete" "new delete" $py_preserve_when_overridden
+
+    set expected_list {
+      "py-before: new delete"
+      "No breakpoint number 9999."
+      "py-after: new delete"
+    }
+
+    test_sequence_exact "delete 9999" $expected_list
+    test_sequence_exact "del 9999" $expected_list
+    test_sequence_exact "new-delete 9999" $expected_list
+
+    # No custom Python command should have been overridden.
+    test_py_commands_deleted
+  }
+}
+
+proc test_gdb_script_overridden_by_py_cmd { py_preserve_when_overridden } {
+  with_test_prefix "override a defined command with\
+		    [cs $py_preserve_when_overridden]" {
+    prepare_gdb
+    add_gdb_script_cmd "new-command" "new command"
+    define_alias "new-alias" "new-command"
+    add_py_cmd "new-command" "new command" $py_preserve_when_overridden
+
+    set expected_list {
+      "py-before: new command"
+      "py-no-overridden: new command"
+      "py-after: new command"
+    }
+
+    test_sequence_exact "new-command" $expected_list
+    test_sequence_exact "new-alias" $expected_list
+
+    # No custom Python command should have been overridden.
+    test_py_commands_deleted
+  }
+}
+
+proc test_non_preserve_py_cmd_overridden_by_py {
+	second_preserve_when_overridden } {
+  with_test_prefix "override a [cs 0] with\
+		    [cs $second_preserve_when_overridden]" {
+    prepare_gdb
+    add_py_cmd "new-command" "new command 1" 0
+    add_py_cmd "new-command" "new command 2" $second_preserve_when_overridden
+
+    test_sequence_exact "new-command" {
+      "py-before: new command 2"
+      "py-no-overridden: new command 2"
+      "py-after: new command 2"
+    }
+
+    # The first Python command was overridden and, as its functionality
+    # is not preserved when overridden, it was deleted.
+    # The second Python command was not overridden so it should never
+    # be deleted.
+    test_py_commands_deleted "new command 1"
+  }
+}
+
+proc test_preserve_py_cmd_overridden_by_py {
+	second_preserve_when_overridden } {
+  with_test_prefix "override [cs 1] with\
+		    [cs $second_preserve_when_overridden]" {
+    prepare_gdb
+    add_py_cmd "new-command" "new command 1" 1
+    add_py_cmd "new-command" "new command 2" $second_preserve_when_overridden
+
+    test_sequence_exact "new-command" {
+      "py-before: new command 2"
+      "py-before: new command 1"
+      "py-no-overridden: new command 1"
+      "py-after: new command 1"
+      "py-after: new command 2"
+    }
+
+    # The first Python command was overridden and, as its functionality
+    # is preserved when overridden, it was not deleted.
+    # The second Python command was not overridden so it should never
+    # be deleted.
+    test_py_commands_deleted
+  }
+}
+
+proc test_py_cmd_overridden_by_gdb_script { py_preserve_when_overridden } {
+  with_test_prefix "override a [cs $py_preserve_when_overridden] with a\
+		    defined one" {
+    prepare_gdb
+    add_py_cmd "new-command" "new command" $py_preserve_when_overridden
+    add_gdb_script_cmd "new-command" "new GDB script command"
+
+    test_sequence_exact "new-command" {
+      "gdb: new GDB script command"
+    }
+
+    if { $py_preserve_when_overridden } {
+      # The Python command was overridden but, as it can be preserved,
+      # it was not deleted (even if it's not accessible from the GDB
+      # script).
+      test_py_commands_deleted
+    } else {
+      # The Python command was overridden and, as it cannot be
+      # preserved, it deleted.
+      test_py_commands_deleted "new command"
+    }
+  }
+}
+
+proc test_preserve_py_cmd_gdb_script_py_cmd {
+	second_py_preserve_when_overridden } {
+  with_test_prefix "override a [cs 1] with a defined one with a\
+	            [cs $second_py_preserve_when_overridden]" {
+    prepare_gdb
+    add_py_cmd "new-command" "new py command 1" 1
+    add_gdb_script_cmd "new-command" "new GDB script command"
+    add_py_cmd "new-command" "new py command 2"\
+	       $second_py_preserve_when_overridden
+
+    # The GDB script command should not be preserved, but both Python
+    # ones should.
+    test_sequence_exact "new-command" {
+      "py-before: new py command 2"
+      "py-before: new py command 1"
+      "py-no-overridden: new py command 1"
+      "py-after: new py command 1"
+      "py-after: new py command 2"
+    }
+
+    # The first Python command was overridden and, as its functionality
+    # is preserved when overridden, it was not deleted.
+    test_py_commands_deleted
+  }
+}
+
+proc test_override_many { later_preserve_when_overridden } {
+  with_test_prefix "override delete with many commands, with a top level\
+                    [cs $later_preserve_when_overridden]" {
+    prepare_gdb
+    define_alias "new-delete-defined-early" "delete"
+    add_py_cmd "delete" "py delete 1" 0
+    add_py_cmd "delete" "py delete 2" 1
+    add_py_cmd "delete" "py delete 3" 1
+    add_py_cmd "delete" "py delete 4" 0
+    define_alias "new-delete-defined-middle" "delete"
+    add_py_cmd "delete" "py delete 5" 1
+    add_gdb_script_cmd "delete" "GDB delete 1"
+    add_py_cmd "delete" "py delete 6" 0
+    add_gdb_script_cmd "delete" "GDB delete 2"
+    add_py_cmd "delete" "py delete 7" $later_preserve_when_overridden
+    define_alias "new-delete-defined-late" "delete"
+
+    set expected_list {
+      "py-before: py delete 7"
+      "py-before: py delete 5"
+      "py-before: py delete 3"
+      "py-before: py delete 2"
+      "No breakpoint number 9999."
+      "py-after: py delete 2"
+      "py-after: py delete 3"
+      "py-after: py delete 5"
+      "py-after: py delete 7"
+    }
+
+    # Long command form.
+    test_sequence_exact "delete 9999" $expected_list
+
+    # Short command form.
+    test_sequence_exact "del 9999" $expected_list
+
+    # Aliases.
+    # There are three aliases: one defined before any command, one after
+    # some commands are defined, and one defined after all the commands
+    # were defined.  When they were defined should be irrelevant and they
+    # should all work the same.
+    test_sequence_exact "new-delete-defined-early 9999" $expected_list
+    test_sequence_exact "new-delete-defined-middle 9999" $expected_list
+    test_sequence_exact "new-delete-defined-late 9999" $expected_list
+
+    test_py_commands_deleted "py delete 1" "py delete 4" "py delete 6"
+  }
+}
+
+set current_lang "c"
+
+with_test_prefix "overriding commands" {
+  # All the test commands accept a 0/1 argument which control whether the
+  # latest defined command should be preserved when overridden.  The
+  # status of this command should not affect the result, so we try with
+  # both combinations.
+  foreach arg { 0 1 } {
+    test_override_delete_with_py $arg
+    test_gdb_script_overridden_by_py_cmd $arg
+    test_non_preserve_py_cmd_overridden_by_py $arg
+    test_preserve_py_cmd_overridden_by_py $arg
+    test_py_cmd_overridden_by_gdb_script $arg
+    test_preserve_py_cmd_gdb_script_py_cmd $arg
+    test_override_many $arg
+  }
+}
diff --git a/gdb/testsuite/gdb.python/py-chain-invoke.py b/gdb/testsuite/gdb.python/py-chain-invoke.py
new file mode 100644
index 0000000000..e72357af16
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-chain-invoke.py
@@ -0,0 +1,72 @@
+# Copyright (C) 2008-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/>.
+
+# This file is part of the GDB testsuite.  It tests the
+# gdb.Command.invoke_overridden method.
+
+import gc
+import sys
+import textwrap
+
+import gdb
+
+deleted = []
+
+class TestCommand (gdb.Command):
+
+    def __init__ (self, cmd_name, msg, preserve_when_overridden):
+        self._cmd_name = cmd_name
+        self._msg = msg
+
+        gdb.Command.__init__ (
+            self, cmd_name, gdb.COMMAND_NONE,
+            preserve_when_overridden=preserve_when_overridden)
+
+    def __del__ (self):
+        deleted.append (self._msg)
+
+    def invoke (self, args, from_tty):
+        print ('py-before: %s' % self._msg)
+        try:
+            self.invoke_overridden (args, from_tty)
+        except gdb.error as exc:
+            expected_exc_msg = (
+                "The '%s' command did not override any other command" %
+                self._cmd_name)
+            if str (exc) == expected_exc_msg:
+                print ('py-no-overridden: %s' % self._msg)
+            else:
+                raise
+        print ('py-after: %s' % self._msg)
+
+def check_were_deleted (expected):
+    # Once GDB drops a reference to the gdb.Command instance, the object
+    # should be deleted.  Just to be sure, we can force a collection.
+    gc.collect ()
+
+    expected.sort ()
+    deleted.sort ()
+
+    if expected != deleted:
+        print (textwrap.dedent ('''\
+            Mismatch in the commands which were deleted:
+            - Actually deleted commands (%d): %r
+            - Expected deleted commands (%d): %r\
+            ''' % (
+                len (deleted),
+                deleted,
+                len (expected),
+                expected
+                )))
-- 
2.17.1



More information about the Gdb-patches mailing list