From patchwork Mon Oct 28 13:32:34 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Barisione X-Patchwork-Id: 35395 Received: (qmail 35710 invoked by alias); 28 Oct 2019 13:33:09 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 35670 invoked by uid 89); 28 Oct 2019 13:33:05 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-26.9 required=5.0 tests=BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_PASS autolearn=ham version=3.3.1 spammy=became, overriding, Quote, repetition X-HELO: mail-wm1-f45.google.com Received: from mail-wm1-f45.google.com (HELO mail-wm1-f45.google.com) (209.85.128.45) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Mon, 28 Oct 2019 13:33:00 +0000 Received: by mail-wm1-f45.google.com with SMTP id p21so9479392wmg.2 for ; Mon, 28 Oct 2019 06:33:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=undo-io.20150623.gappssmtp.com; s=20150623; h=from:to:subject:date:message-id; bh=OYaSxcMMpRUh7ykaKskJD3e2WeDjGKG7dGmO9VWK3Ms=; b=wGxcTDH2UZDV7lRzWT5yhgS9jaEIW6BtiFP/PboJFMQrIksI1AOtoDMsEcfe+1Hkej sa93INWTBUsh+PNShnjEDIYWTfQbxbyAUGKkq8XDyw5111nbMADuQIw7r4n19xrd1iKK hJEiiYIoO91eX9sN8bPaA8VpfaLLnHAZkREUUi1DP6WwHm0D8osAQLJLaPUwjPnPdO50 qiMt2wnaWgT70I6iBc5rCA+Q46pUcjU9J/HVVLs+FsAWF0P4FJT0NLtK7QgN4372fBVT kkLjjaA2o/a0vbo3uQaJM63cu4n5ePRqIpCGlMmtmBqA+DwFLxF9n34uhCS0k+Bm4UWr oW4A== Return-Path: Received: from undo-on-focaccia.undoers.io ([109.159.206.110]) by smtp.gmail.com with ESMTPSA id r3sm17848735wre.29.2019.10.28.06.32.56 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 28 Oct 2019 06:32:56 -0700 (PDT) From: Marco Barisione To: gdb-patches@sourceware.org Subject: [PATCH] Add a way to preserve overridden GDB commands for later invocation Date: Mon, 28 Oct 2019 13:32:34 +0000 Message-Id: <20191028133234.319-1-mbarisione@undo.io> X-IsSubscribed: yes 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 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 * 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 * 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 . + +# 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 . + +# 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 + )))