From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-wr1-x42b.google.com (mail-wr1-x42b.google.com [IPv6:2a00:1450:4864:20::42b]) by sourceware.org (Postfix) with ESMTPS id 0163E394C030 for ; Mon, 25 Jan 2021 11:27:07 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org 0163E394C030 Received: by mail-wr1-x42b.google.com with SMTP id h9so1716320wrr.9 for ; Mon, 25 Jan 2021 03:27:06 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=QwHy4n32DYtTCWKfkG9LcF2cLK4dxBeYIlfv+rzNCPY=; b=sUZ15eTZAUcgCcxTTZh1mVGeyFJ5i1Aol6eC8Mwnkv8GNi4mKZbgEsa0B0s3WqA6yl 5lDW3OafM5gElZcfXRsQz7HHyFX6rp5IV2xvqvP1nWmy+MUgOwSHiXd2ed16vdG2tx2m iYpm1Z87JVqoBBFy0w9JTE/I6O2mq1f0qJoSFKrHbvsl9AjkkupBTY1vlqtlOHH4QIl1 XGJ+5JOq2kWHRiILAbAx5FYqEpBqvko+ec7IpJohw+rF+BDvkANAF/HI8CCpxFUySZnX 8MMuIMBK8EKMJcx/XhsAXRcSzW99zd+Xat7NctTgX/gpGGzM0f9Z2XsdR9zE9s+WLAj+ jzCQ== X-Gm-Message-State: AOAM5303nYdqMlwDcxLjDyMX2muugfY89coIOn/58HNjszH9K8DjB6aJ 6ERAloDpbUC1XOcpfbIBOj9p6IXDEtt1gNN7uffufH1nxr6rbdLuyMqbkko794GI0c93INuyFg4 pavMkSSJDu0D11Xau5O6chgTKuqBTam0rM2zbT8kWt0kUWOOg/RngbD1zVOhkjBwGGXECrOdh+4 yC X-Google-Smtp-Source: ABdhPJw50r7O3++/NmXJ+dM60UL6GK0afZg3Mnijl+mSQGmDF3FEwjZHxYLX2r2fE/R3+LClzDW7nw== X-Received: by 2002:a5d:40c5:: with SMTP id b5mr391753wrq.121.1611574023229; Mon, 25 Jan 2021 03:27:03 -0800 (PST) Received: from focaccia.undoers.io (cpc159317-cmbg20-2-0-cust151.5-4.cable.virginm.net. [81.111.29.152]) by smtp.gmail.com with ESMTPSA id q2sm20648220wma.6.2021.01.25.03.27.01 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 25 Jan 2021 03:27:02 -0800 (PST) From: Marco Barisione To: gdb-patches@sourceware.org Subject: [PATCH v2 5/5] gdb: Add support for renaming commands Date: Mon, 25 Jan 2021 11:26:49 +0000 Message-Id: <20210125112649.56362-6-mbarisione@undo.io> X-Mailer: git-send-email 2.28.0 In-Reply-To: <20210125112649.56362-1-mbarisione@undo.io> References: <20210108100706.96190-1-mbarisione@undo.io> <20210125112649.56362-1-mbarisione@undo.io> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-9.2 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPAM_BODY, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 25 Jan 2021 11:27:15 -0000 This patch adds: * A "rename" command. * A "-rename-existing-to" option for the "define" command. * A "rename_existing_to" optional argument to gdb.Command.__init__ which matches the behaviour of "define -rename-existing-to". The goal of this is to allow users to build on top of existing commands without losing their original implementation. Something similar could be achieved through hooks but they are limited: * Hooks cannot prevent the command from being executed without printing any error. * Hooks don't get passed the arguments passed to the command. * Post hooks can't know if the command failed. * Hooks cannot be defined in Python. gdb/ChangeLog: * NEWS: Add items for the new rename command, the new -rename-existing-to option for define and the new rename_existing_to argument for gdb.Command.__init__. * cli/cli-decode.c (delete_cmd): Rename to disconnect_cmd. (disconnect_cmd): Disconnect a command from other commands and lists rather than deleting the command. (delete_cmd_by_name): Add. (update_prefix_links): Add. (update_cmd): Add. (do_add_cmd): Add ability to rename existing commands rather than redefining them. (add_cmd): Ditto. (add_prefix_cmd): Ditto. (add_alias_cmd): Update to use disconnect_cmd. (break_cmd_relationships): Add. (rename_hook): Add. (user_defined_command): Move from cli/cli-script.c. (rename_cmd): Add. * cli/cli-decode.h (struct cmd_list_element): Call the destroyer function from the destructor. * cli/cli-script.c (do_define_command): Add ability to rename existing commands rather than redefining them. (user_defined_command): Move to cli/cli-decode.c (check_command_redefinition): Add. (enum cmd_hook_type): Move from do_define_command to the global scope. (get_hook_type): Add. (HOOK_STRING): Make a variable in get_hook_type. (HOOK_LEN): Ditto. (HOOK_POST_STRING): Ditto. (HOOK_POST_LEN): Ditto. (struct define_cmd_opts): Add. (make_define_cmd_options_def_group): Add. (define_command): Add -rename-existing-to option. (do_rename_command): Add. (rename_command): Add. (_initialize_cli_script): Add option parsing for the define command and add the rename command. * command.h (add_cmd): Add ability to rename existing commands rather than redefining them. (add_prefix_cmd): Ditto. (rename_cmd): Add. (user_defined_command): Add declaration of previously static function. * python/py-cmd.c (cmdpy_init): Add rename_existing_to argument. * python/python-internal.h (PyInt_Type): Add for compatibility between Python 2 and 3. gdb/doc/ChangeLog: * gdb.texinfo: Document the new rename command and the new -rename-existing-to option for define. * python.texi: Document the new rename_existing_to argument for gdb.Command.__init__. gdb/testsuite/ChangeLog: * gdb.base/command-renaming.exp: New test. * gdb.python/py-rename-existing.exp: New test. * gdb.python/py-rename-existing.py: New test. --- gdb/NEWS | 26 + gdb/cli/cli-decode.c | 645 +++++++++++++++--- gdb/cli/cli-decode.h | 2 + gdb/cli/cli-script.c | 324 +++++++-- gdb/command.h | 59 +- gdb/doc/gdb.texinfo | 55 +- gdb/doc/python.texi | 38 +- gdb/python/py-cmd.c | 183 ++++- gdb/python/python-internal.h | 1 + gdb/testsuite/gdb.base/command-renaming.exp | 571 ++++++++++++++++ .../gdb.python/py-rename-existing.exp | 364 ++++++++++ .../gdb.python/py-rename-existing.py | 46 ++ 12 files changed, 2097 insertions(+), 217 deletions(-) create mode 100644 gdb/testsuite/gdb.base/command-renaming.exp create mode 100644 gdb/testsuite/gdb.python/py-rename-existing.exp create mode 100644 gdb/testsuite/gdb.python/py-rename-existing.py diff --git a/gdb/NEWS b/gdb/NEWS index d2ed28857b0..94b2b52de75 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -47,6 +47,11 @@ maintenance flush register-cache maintenance flush dcache A new command to flush the dcache. +rename OLD_NAME NEW_NAME + Rename the command called OLD_NAME to NEW_NAME. All subcommands, hooks + and aliases associated to OLD_NAME are moved to refer to NEW_NAME. + If a command name contains spaces, it must be enclosed in double quotes. + * Changed commands break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM] @@ -80,6 +85,27 @@ set style version background COLOR set style version intensity VALUE Control the styling of GDB's version number text. +define [-rename-existing-to NEW_NAME_FOR_EXISTING_COMMAND] COMMAND_NAME + Added a -rename-existing-to option which causes the existing command + called COMMAND_NAME to be renamed to NEW_NAME_FOR_EXISTING_COMMAND + instead of being redefined and overwritten. This allows user-defined + commands to modify existing commands and extend them rather than + replacing them completely. + The behaviour is similar to using the "rename" command followed by + "define", but "define -rename-existing-to" keeps aliases, hooks and + subcommands associated to the newly defined COMMAND_NAME command. + If NEW_NAME_FOR_EXISTING_COMMAND contains spaces, it must be enclosed in + double quotes, unlike COMMAND_NAME which must not be enclosed in quotes. + +* Python API + + ** Added a new rename_existing_to optional parameter to gdb.Command. + If specified, the existing command with the same name as the new + command gets renamed to the value of rename_existing_to instead of + being redefined and overwritten. This allows Python code to modify + existing commands and extend them rather than replacing them + completely. See also "define -rename-existing-to". + *** Changes in GDB 10 * There are new feature names for ARC targets: "org.gnu.gdb.arc.core" diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c index aea7302213a..5069032eb72 100644 --- a/gdb/cli/cli-decode.c +++ b/gdb/cli/cli-decode.c @@ -30,12 +30,18 @@ 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 cmd_list_element *disconnect_cmd (const char *name, + struct cmd_list_element **list, + struct cmd_list_element ***prefixlist, + struct cmd_list_element **aliases, + struct cmd_list_element **prehook, + struct cmd_list_element **prehookee, + struct cmd_list_element **posthook, + struct cmd_list_element **posthookee); + +static bool delete_cmd_by_name (const char *name, + struct cmd_list_element **list, + struct cmd_list_element ***prefixlist); static struct cmd_list_element *find_cmd (const char *command, int len, @@ -160,53 +166,191 @@ set_cmd_completer_handle_brkchars (struct cmd_list_element *cmd, cmd->completer_handle_brkchars = func; } -/* Like ADD_CMD, but the command function fields are not modified. */ +/* Update the prefix-related fields associated with command C, which must + be part of the comand list *LIST, and, if not null, adds PREFIXLIST + as subcommands of C. -static struct cmd_list_element * -do_add_cmd (const char *name, enum command_class theclass, - const char *doc, struct cmd_list_element **list) + In particular, this function always updates the relationship of C with + the command it's a subcommand of (if any) and, if PREFIXLIST is not + null: + * Makes PREFIXLIST the list of subcommands of C (which makes C a prefix + command); + * Updates the subcommands in PREFIXLIST accordingly. */ + +static void +update_prefix_links (struct cmd_list_element *c, + struct cmd_list_element **list, + struct cmd_list_element **prefixlist) +{ + /* Update the subcommands to point to C as their prefix. */ + gdb_assert (c->prefixlist == nullptr); + c->prefixlist = prefixlist; + if (c->prefixlist != nullptr) + { + for (struct cmd_list_element *iter = *(c->prefixlist); + iter != nullptr; + iter = iter->next) + iter->prefix = c; + } + + /* Find the prefix cmd of C, and assign it to C->prefix. + See also add_prefix_cmd and update_prefix_field_of_prefixed_commands. */ + gdb_assert (c->prefix == nullptr); + struct cmd_list_element *prefixcmd = lookup_cmd_for_prefixlist (list, + cmdlist); + c->prefix = prefixcmd; +} + +/* Update command C, by adding it to *LIST and updating it from the other + arguments. + + C must not already be in any command list, or have any subcommands, + aliases or hooks associated. + + If NEW_NAME is not null, then the command name is updated to the + specified name. + + If PREFIXLIST is not null, then the *PREFIXLIST commands become + subcommands of C. + + If ALIASES is not null, then the specified commands become aliases of + C. + + The various HOOKs, if not null, specify commands hooking into C or + commands hooked into C. */ + +static void +update_cmd (struct cmd_list_element *c, + struct cmd_list_element **list, + const char *new_name = nullptr, + struct cmd_list_element **prefixlist = nullptr, + struct cmd_list_element *aliases = nullptr, + struct cmd_list_element *hook_pre = nullptr, + struct cmd_list_element *hookee_pre = nullptr, + struct cmd_list_element *hook_post = nullptr, + struct cmd_list_element *hookee_post = nullptr) { - struct cmd_list_element *c = new struct cmd_list_element (name, theclass, - doc); - struct cmd_list_element *p, *iter; - - /* 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); - for (iter = c->aliases; iter; iter = iter->alias_chain) + /* Update the name, if specified. */ + if (new_name != nullptr) + { + if (c->name_allocated) + xfree ((char *)c->name); + + c->name = xstrdup (new_name); + c->name_allocated = true; + } + + /* Add C to the command list LIST in the appropriate (sorted) position. */ + if (*list == NULL || strcmp ((*list)->name, c->name) >= 0) + { + c->next = *list; + *list = c; + } + else + { + struct cmd_list_element *p = *list; + while (p->next != nullptr && strcmp (p->next->name, c->name) <= 0) + p = p->next; + c->next = p->next; + p->next = c; + } + + /* Update prefix information. */ + update_prefix_links (c, list, prefixlist); + + /* Update the aliases. */ + gdb_assert (c->aliases == nullptr); + c->aliases = aliases; + for (struct cmd_list_element *iter = c->aliases; + iter != nullptr; + iter = iter->alias_chain) iter->cmd_pointer = c; - if (c->hook_pre) + + /* Updates hooks and hookees. */ + gdb_assert (c->hook_pre == nullptr); + c->hook_pre = hook_pre; + if (c->hook_pre != nullptr) c->hook_pre->hookee_pre = c; - if (c->hookee_pre) + + gdb_assert (c->hookee_pre == nullptr); + c->hookee_pre = hookee_pre; + if (c->hookee_pre != nullptr) c->hookee_pre->hook_pre = c; - if (c->hook_post) + + gdb_assert (c->hook_post == nullptr); + c->hook_post = hook_post; + if (c->hook_post != nullptr) c->hook_post->hookee_post = c; - if (c->hookee_post) + + gdb_assert (c->hookee_post == nullptr); + c->hookee_post = hookee_post; + if (c->hookee_post != nullptr) c->hookee_post->hook_post = c; +} - if (*list == NULL || strcmp ((*list)->name, name) >= 0) +/* Like ADD_CMD, but the command function fields are not modified. */ + +static struct cmd_list_element * +do_add_cmd (const char *name, enum command_class theclass, + const char *doc, struct cmd_list_element **list, + const char *rename_existing_to = nullptr, + struct cmd_list_element **rename_existing_to_list = nullptr) +{ + /* If a command called RENAME_EXISTING_TO already exists, delete it. */ + struct cmd_list_element **deleted_cmd_prefixlist = nullptr; + if (rename_existing_to != nullptr) { - c->next = *list; - *list = c; + gdb_assert (rename_existing_to_list != nullptr); + delete_cmd_by_name (rename_existing_to, rename_existing_to_list, + &deleted_cmd_prefixlist); } - else + + /* Create the new command. */ + struct cmd_list_element *c = new struct cmd_list_element (name, theclass, + doc); + + /* Remove the old NAME command from LIST and disconnect all the + subcommands, aliases, hooks as well. */ + struct cmd_list_element **prefixlist; + struct cmd_list_element *aliases; + struct cmd_list_element *hook_pre, *hookee_pre, *hook_post, *hookee_post; + struct cmd_list_element *existing_cmd + = disconnect_cmd (name, list, + &prefixlist, + &aliases, + &hook_pre, &hookee_pre, + &hook_post, &hookee_post); + + /* Add the new command to LIST turning aliases and hooks of the old + command into aliases/hooks of the new command. */ + update_cmd (c, list, + /* new_name = */ nullptr, + prefixlist, + aliases, + hook_pre, hookee_pre, hook_post, hookee_post); + + /* Finally, rename the existing command or, if renaming is not + requested, delete it. */ + if (rename_existing_to != nullptr) { - p = *list; - while (p->next && strcmp (p->next->name, name) <= 0) + gdb_assert (existing_cmd != nullptr); + update_cmd (existing_cmd, rename_existing_to_list, + rename_existing_to, + deleted_cmd_prefixlist); + if (prefixlist) { - p = p->next; + /* The existing command was a prefix command so it must remain + a prefix command (with no subcommands). This is needed as + the implementation of the command could try to access its + subcommands. See define_prefix_command for details on how + prefix lists work. */ + gdb_assert (existing_cmd->prefixlist == nullptr); + existing_cmd->prefixlist = new struct cmd_list_element*; + *(existing_cmd->prefixlist) = nullptr; } - c->next = p->next; - p->next = c; } - - /* Search the prefix cmd of C, and assigns it to C->prefix. - See also add_prefix_cmd and update_prefix_field_of_prefixed_commands. */ - struct cmd_list_element *prefixcmd = lookup_cmd_for_prefixlist (list, - cmdlist); - c->prefix = prefixcmd; - + else + delete existing_cmd; return c; } @@ -215,9 +359,13 @@ do_add_cmd (const char *name, enum command_class theclass, struct cmd_list_element * add_cmd (const char *name, enum command_class theclass, - const char *doc, struct cmd_list_element **list) + const char *doc, struct cmd_list_element **list, + const char *rename_existing_to, + struct cmd_list_element **rename_existing_to_list) { - cmd_list_element *result = do_add_cmd (name, theclass, doc, list); + cmd_list_element *result = do_add_cmd (name, theclass, doc, list, + rename_existing_to, + rename_existing_to_list); result->func = NULL; result->function.const_cfunc = NULL; return result; @@ -228,9 +376,13 @@ add_cmd (const char *name, enum command_class theclass, struct cmd_list_element * add_cmd (const char *name, enum command_class theclass, cmd_const_cfunc_ftype *fun, - const char *doc, struct cmd_list_element **list) + const char *doc, struct cmd_list_element **list, + const char *rename_existing_to, + struct cmd_list_element **rename_existing_to_list) { - cmd_list_element *result = do_add_cmd (name, theclass, doc, list); + cmd_list_element *result = do_add_cmd (name, theclass, doc, list, + rename_existing_to, + rename_existing_to_list); set_cmd_cfunc (result, fun); return result; } @@ -283,14 +435,19 @@ add_alias_cmd (const char *name, cmd_list_element *old, { if (old == 0) { + struct cmd_list_element **prefixlist; + struct cmd_list_element *aliases; 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 *old_cmd + = disconnect_cmd (name, list, + &prefixlist, + &aliases, + &prehook, &prehookee, + &posthook, &posthookee); /* If this happens, it means a programmer error somewhere. */ - gdb_assert (!aliases && !prehook && !prehookee - && !posthook && ! posthookee); + gdb_assert (!old_cmd); + /* As OLD_CMD is null, NAME didn't exist, so the prefix list, + * aliases and hooks are all null as well. */ return 0; } @@ -370,9 +527,13 @@ struct cmd_list_element * add_prefix_cmd (const char *name, enum command_class theclass, cmd_const_cfunc_ftype *fun, const char *doc, struct cmd_list_element **prefixlist, - int allow_unknown, struct cmd_list_element **list) + int allow_unknown, struct cmd_list_element **list, + const char *rename_existing_to, + struct cmd_list_element **rename_existing_to_list) { - struct cmd_list_element *c = add_cmd (name, theclass, fun, doc, list); + struct cmd_list_element *c = add_cmd (name, theclass, fun, doc, list, + rename_existing_to, + rename_existing_to_list); c->prefixlist = prefixlist; c->allow_unknown = allow_unknown; @@ -888,76 +1049,346 @@ add_setshow_zuinteger_cmd (const char *name, enum command_class theclass, NULL, NULL); } -/* 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 - set to NULL. */ - -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) +/* Break the relationships between C and other commands. + + This is done by removing subcommands, aliases and hooks from C and + making the other commands not point to C any more. + + *PREFIXLIST is set to point to the list of subcommands for the removed + command. + + *ALIASES is set to the list of commands which were aliased to C. If + the command had no aliases, it's set to NULLPTR. + + *ALIASES is set to the removed aliases. If the command has no aliases, + it's set to NULLPTR. + + The various *HOOKs are set to the pre- and post-hook commands for C. + If the command does not have a hook, the corresponding out parameter + is set to NULLPTR. */ + +static void +break_cmd_relationships (struct cmd_list_element *c, + struct cmd_list_element ***prefixlist, + struct cmd_list_element **aliases, + struct cmd_list_element **hook_pre, + struct cmd_list_element **hookee_pre, + struct cmd_list_element **hook_post, + struct cmd_list_element **hookee_post) { - struct cmd_list_element *iter; - struct cmd_list_element **previous_chain_ptr; - struct cmd_list_element *aliases = NULL; + /* Make the subcommands not point to this command any more. */ + if (c->prefixlist != nullptr) + { + for (struct cmd_list_element *iter = *(c->prefixlist); + iter != nullptr; + iter = iter->next) + iter->prefix = nullptr; + } + + /* Remove the subcommands. */ + *prefixlist = c->prefixlist; + c->prefixlist = nullptr; + + /* Make the aliases not point to this command any more. */ + for (struct cmd_list_element *iter = c->aliases; + iter != nullptr; + iter = iter->alias_chain) + iter->cmd_pointer = nullptr; + + /* Remove the aliases. */ + *aliases = c->aliases; + c->aliases = nullptr; + + /* If this command was an alias, remove it from the list of aliases. */ + if (c->cmd_pointer != nullptr) + { + struct cmd_list_element **prevp = &c->cmd_pointer->aliases; + struct cmd_list_element *a = *prevp; - *prehook = NULL; - *prehookee = NULL; - *posthook = NULL; - *posthookee = NULL; - previous_chain_ptr = list; + while (a != c) + { + prevp = &a->alias_chain; + a = *prevp; + } + *prevp = c->alias_chain; + } + + /* Save the previous hook lists in the output parameters. */ + *hook_pre = c->hook_pre; + *hookee_pre = c->hookee_pre; + *hook_post = c->hook_post; + *hookee_post = c->hookee_post; + + /* Break the hook relationships with other commands. */ + if (c->hook_pre) + c->hook_pre->hookee_pre = nullptr; + if (c->hookee_pre) + c->hookee_pre->hook_pre = nullptr; + if (c->hook_post) + c->hook_post->hookee_post = nullptr; + if (c->hookee_post) + c->hookee_post->hook_post = nullptr; + + /* And finally remove the hooks. */ + c->hook_pre = nullptr; + c->hookee_pre = nullptr; + c->hook_post = nullptr; + c->hookee_post = nullptr; +} + +/* Disconnect the command named NAME from the command list *LIST, from + its subcommands, and all associated aliases and hooks. + + *PREFIXLIST is set to point to the list of subcommands for the removed + command. + + *ALIASES is set to the list of commands which were aliased to the + removed command. If the command has no aliases, it's set to NULLPTR. + + The various *HOOKs are set to the pre- and post-hook commands for the + removed command. If the command does not have a hook, the + corresponding out parameter is set to NULLPTR. + + Return the diconnected command if it was found. Otherwise, return + NULLPTR (and the output parameters are all set to NULLPTR as well). */ + +static cmd_list_element * +disconnect_cmd (const char *name, struct cmd_list_element **list, + struct cmd_list_element ***prefixlist, + struct cmd_list_element **aliases, + struct cmd_list_element **hook_pre, + struct cmd_list_element **hookee_pre, + struct cmd_list_element **hook_post, + struct cmd_list_element **hookee_post) +{ + struct cmd_list_element **previous_chain_ptr = list; - for (iter = *previous_chain_ptr; iter; iter = *previous_chain_ptr) + for (struct cmd_list_element *iter = *previous_chain_ptr; + iter != nullptr; + iter = *previous_chain_ptr) { if (strcmp (iter->name, name) == 0) { - if (iter->destroyer) - iter->destroyer (iter, iter->context); - if (iter->hookee_pre) - iter->hookee_pre->hook_pre = 0; - *prehook = iter->hook_pre; - *prehookee = iter->hookee_pre; - if (iter->hookee_post) - iter->hookee_post->hook_post = 0; - *posthook = iter->hook_post; - *posthookee = iter->hookee_post; - - /* Update the link. */ + /* Found the only command with the specified name. */ + break_cmd_relationships (iter, + prefixlist, + aliases, + hook_pre, hookee_pre, + hook_post, hookee_post); + /* Update the link in the command list, that is remove the + command from its command list. */ *previous_chain_ptr = iter->next; + /* And finally unlink from the prefix command. */ + iter->prefix = nullptr; - aliases = iter->aliases; - - /* If this command was an alias, remove it from the list of - aliases. */ - if (iter->cmd_pointer) - { - struct cmd_list_element **prevp = &iter->cmd_pointer->aliases; - struct cmd_list_element *a = *prevp; - - while (a != iter) - { - prevp = &a->alias_chain; - a = *prevp; - } - *prevp = iter->alias_chain; - } - - delete iter; - - /* We won't see another command with the same name. */ - break; + return iter; } else previous_chain_ptr = &iter->next; } - return aliases; + /* Command not found. */ + + *prefixlist = nullptr; + *aliases = nullptr; + *hook_pre = nullptr; + *hookee_pre = nullptr; + *hook_post = nullptr; + *hookee_post = nullptr; + + return nullptr; } + +/* Delete command C and all associated aliases and hooks. + + Subcommands are not modified. If C had subcommands, it's the caller's + responsibility to either assign the subcommands to another command or + to delete them. + + If PREFIXLIST is not null, *PREFIXLIST is set to point to the list of + subcommands previously associated with C. + If PREFIXLIST is null, then C must be an alias or hook which cannot + have subcommands. */ + +static void +delete_cmd (struct cmd_list_element *c, + struct cmd_list_element ***prefixlist) +{ + /* The PREFIXLIST argument is required for all command types which could + have subcommands. That is, PREFIXLIST can be null only if C is a + hook or alias. */ + gdb_assert (prefixlist != nullptr + || (c->cmd_pointer == nullptr + || c->hookee_pre == nullptr + || c->hookee_post == nullptr)); + + /* Find the list for C. */ + struct cmd_list_element **list; + if (c->prefix != nullptr) + list = c->prefix->prefixlist; + else + list = &cmdlist; + + /* Delete C itself. */ + struct cmd_list_element **prefixlist_tmp; + struct cmd_list_element *aliases; + struct cmd_list_element *hook_pre, *hookee_pre, *hook_post, *hookee_post; + struct cmd_list_element *actual_removed_cmd + = disconnect_cmd (c->name, list, + &prefixlist_tmp, + &aliases, + &hook_pre, &hookee_pre, + &hook_post, &hookee_post); + gdb_assert (actual_removed_cmd == c); + + if (prefixlist != nullptr) + *prefixlist = prefixlist_tmp; + else + /* We asserted earlier that, if PREFIXLIST is null, then C is either + a hook or an alias, so prefixlist_tmp cannot be null. */ + gdb_assert (prefixlist_tmp == nullptr); + + /* Delete all aliases. */ + for (struct cmd_list_element *alias_iter = aliases; + alias_iter != nullptr; + alias_iter = alias_iter->alias_chain) + delete_cmd (alias_iter, nullptr); + + /* Delete hooks. */ + if (hook_pre != nullptr) + delete_cmd (hook_pre, nullptr); + if (hook_post != nullptr) + delete_cmd (hook_post, nullptr); + + /* There's nothing to do with hookee_pre and hookee_post. If they are + set then C is a hook and, if C is a hook, deleting it doesn't mean + that the hookee must be deleted. */ + + delete c; +} + +/* Delete the command called NAME in *LIST and all associated aliases and + hooks. + + See DELETE_CMD for details on PREFIXLIST and whether it can be null. + + Return TRUE if the command was found and deleted, FALSE otherwise. */ + +static bool +delete_cmd_by_name (const char *name, + struct cmd_list_element **list, + struct cmd_list_element ***prefixlist) +{ + struct cmd_list_element *cmd = lookup_cmd_exact (name, *list); + if (cmd != nullptr) + { + delete_cmd (cmd, prefixlist); + return true; + } + else + { + if (prefixlist != nullptr) + *prefixlist = nullptr; + return false; + } +} + +/* Rename HOOK to match the command OLD_CMD_NAME in *OLD_LIST which was + renamed to NEW_CMD_NAME in NEW_LIST. */ + +static void +rename_hook (struct cmd_list_element *hook, + const char *old_cmd_name, struct cmd_list_element **old_list, + const char *new_cmd_name, struct cmd_list_element **new_list) +{ + struct cmd_list_element **prefixlist; + struct cmd_list_element *aliases; + struct cmd_list_element *hook_pre, *hookee_pre, *hook_post, *hookee_post; + struct cmd_list_element *cmd = disconnect_cmd (hook->name, old_list, + &prefixlist, + &aliases, + &hook_pre, &hookee_pre, + &hook_post, &hookee_post); + + /* Rename the hook command keeping the part that comes before the + hookee's name (i.e. "hook-" or "hookpost-"). */ + std::string old_hook_name (hook->name); + size_t cmd_name_offset = old_hook_name.rfind (old_cmd_name); + gdb_assert (cmd_name_offset != std::string::npos); + std::string new_hook_name + = old_hook_name.substr (0, cmd_name_offset) + new_cmd_name; + + update_cmd (cmd, new_list, + new_hook_name.c_str (), + prefixlist, + aliases, + hook_pre, hookee_pre, hook_post, hookee_post); +} + +/* See command.h. */ + +void +user_defined_command (const char *ignore, int from_tty) +{ +} + +/* See command.h. */ + +struct cmd_list_element * +rename_cmd (const char *old_name, struct cmd_list_element **old_list, + const char *new_name, struct cmd_list_element **new_list) +{ + /* Remove the command from its current list. */ + struct cmd_list_element **old_name_prefixlist; + struct cmd_list_element *aliases; + struct cmd_list_element *hook_pre, *hookee_pre, *hook_post, *hookee_post; + struct cmd_list_element *cmd = disconnect_cmd (old_name, old_list, + &old_name_prefixlist, + &aliases, + &hook_pre, &hookee_pre, + &hook_post, &hookee_post); + if (cmd == nullptr) + { + /* No command called OLD_NAME exists in OLD_LIST. */ + return nullptr; + } + + /* If there was already a NEW_NAME command then it must be deleted. + Its subcommands, if present, are moved to the renamed command. */ + struct cmd_list_element **new_name_prefixlist; + delete_cmd_by_name (new_name, new_list, &new_name_prefixlist); + + /* Re-insert the command but in NEW_LIST. */ + update_cmd (cmd, new_list, + new_name, + new_name_prefixlist, + aliases, + hook_pre, hookee_pre, hook_post, hookee_post); + + /* Rename the hooks as well. */ + if (hook_pre) + rename_hook (hook_pre, old_name, old_list, new_name, new_list); + if (hook_post) + rename_hook (hook_post, old_name, old_list, new_name, new_list); + + /* If the old command had subcommands, then a new prefix-only command + must be created so they can be preserved. */ + if (old_name_prefixlist != nullptr && *old_name_prefixlist != nullptr) + { + struct cmd_list_element *prefix_for_old_name + = add_cmd (xstrdup (old_name), class_user, user_defined_command, + xstrdup ("User-defined."), old_list); + /* The new prefix is a prefix only so passing invalid subcommands + must lead to an error. */ + prefix_for_old_name->allow_unknown = false; + + update_prefix_links (prefix_for_old_name, old_list, + old_name_prefixlist); + } + + return cmd; +} + /* Shorthands to the commands above. */ diff --git a/gdb/cli/cli-decode.h b/gdb/cli/cli-decode.h index ddcb2ea9578..2197a16ad36 100644 --- a/gdb/cli/cli-decode.h +++ b/gdb/cli/cli-decode.h @@ -63,6 +63,8 @@ struct cmd_list_element ~cmd_list_element () { + if (destroyer != nullptr) + destroyer (this, context); if (doc && doc_allocated) xfree ((char *) doc); if (name_allocated) diff --git a/gdb/cli/cli-script.c b/gdb/cli/cli-script.c index 5841f545882..d7981d8bb2c 100644 --- a/gdb/cli/cli-script.c +++ b/gdb/cli/cli-script.c @@ -30,6 +30,7 @@ #include "cli/cli-decode.h" #include "cli/cli-script.h" #include "cli/cli-style.h" +#include "cli/cli-utils.h" #include "extension.h" #include "interps.h" @@ -49,7 +50,8 @@ recurse_read_control_structure gdb::function_view validator); static void do_define_command (const char *comname, int from_tty, - const counted_command_line *commands); + const counted_command_line *commands, + const char *rename_existing_to = nullptr); static void do_document_command (const char *comname, int from_tty, const counted_command_line *commands); @@ -1368,84 +1370,157 @@ validate_comname (const char **comname) return list; } -/* This is just a placeholder in the command data structures. */ +/* Ask the user if they really want to redefine the command called + CMD->name, thus overwriting CMD. + + Throws an error if the users says no. */ + static void -user_defined_command (const char *ignore, int from_tty) +check_command_redefinition (struct cmd_list_element *cmd) { + int q; + + if (cmd->theclass == class_user || cmd->theclass == class_alias) + { + /* If C is a prefix command that was previously defined, + tell the user its subcommands will be kept, and ask + if ok to redefine the command. */ + if (cmd->prefixlist != nullptr) + q = (cmd->user_commands.get () == nullptr + || query (_("Keeping subcommands of prefix command \"%s\".\n" + "Redefine command \"%s\"? "), cmd->name, cmd->name)); + else + q = query (_("Redefine command \"%s\"? "), cmd->name); + } + else + q = query (_("Really redefine built-in command \"%s\"? "), cmd->name); + if (!q) + error (_("Command \"%s\" not redefined."), cmd->name); +} + +/* Whether a command is a hook into another command and which type of hook + it is. */ + +enum cmd_hook_type +{ + /* Not a hook. */ + CMD_NO_HOOK = 0, + /* Hook executed before the associated command. */ + CMD_PRE_HOOK, + /* Hook executed after the associated command. */ + CMD_POST_HOOK +}; + +/* Given a command name NAME, returns an element of CMD_HOOK_TYPE + identifying whether NAME is a hook name and which type of hook it is. + + If HOOKEE_NAME is not null, it's set to point to the position in NAME + identifying the name of the command hooked by NAME. */ + +static cmd_hook_type +get_hook_type (const char *name, const char **hookee_name = nullptr) +{ + static const char hook_pre_prefix[] = "hook-"; + static const char hook_post_prefix[] = "hookpost-"; + static const size_t hook_pre_prefix_len = sizeof hook_pre_prefix - 1; + static const size_t hook_post_prefix_len = sizeof hook_post_prefix - 1; + + cmd_hook_type type; + const char *hookee_name_tmp; + + if (strncmp (name, hook_pre_prefix, hook_pre_prefix_len) == 0) + { + hookee_name_tmp = name + hook_pre_prefix_len; + type = CMD_PRE_HOOK; + } + else if (strncmp (name, hook_post_prefix, hook_post_prefix_len) == 0) + { + hookee_name_tmp = name + hook_post_prefix_len; + type = CMD_POST_HOOK; + } + else + { + hookee_name_tmp = nullptr; + type = CMD_NO_HOOK; + } + + if (hookee_name != nullptr) + *hookee_name = hookee_name_tmp; + + return type; } /* Define a user-defined command. If COMMANDS is NULL, then this is a top-level call and the commands will be read using read_command_lines. Otherwise, it is a "define" command in an existing command and the commands are provided. In the - non-top-level case, various prompts and warnings are disabled. */ + non-top-level case, various prompts and warnings are disabled. + + If RENAME_EXISTING_TO is non-NULL, then the existing command called + COMNAME will be renamed to RENAME_EXISTING_TO instead of being + overwritten. */ static void do_define_command (const char *comname, int from_tty, - const counted_command_line *commands) + const counted_command_line *commands, + const char *rename_existing_to) { - enum cmd_hook_type - { - CMD_NO_HOOK = 0, - CMD_PRE_HOOK, - CMD_POST_HOOK - }; struct cmd_list_element *c, *newc, *hookc = 0, **list; - const char *comfull; - int hook_type = CMD_NO_HOOK; - int hook_name_size = 0; - -#define HOOK_STRING "hook-" -#define HOOK_LEN 5 -#define HOOK_POST_STRING "hookpost-" -#define HOOK_POST_LEN 9 + struct cmd_list_element *rename_existing_to_cmd = nullptr; + struct cmd_list_element **rename_existing_to_list; + const char *comfull, *rename_existing_to_full; + int hook_type; + const char *hookee_name = nullptr; comfull = comname; list = validate_comname (&comname); + rename_existing_to_full = rename_existing_to; + if (rename_existing_to != nullptr) + rename_existing_to_list = validate_comname (&rename_existing_to); + else + rename_existing_to_list = nullptr; /* Not renaming. */ + + if (rename_existing_to_full != nullptr && + strcmp (rename_existing_to_full, comfull) == 0) + error (_("Cannot rename command \"%s\" as the new name is identical " + "to the existing one."), + comfull); + c = lookup_cmd_exact (comname, *list); + if (rename_existing_to_list != nullptr) + rename_existing_to_cmd = lookup_cmd_exact (rename_existing_to, + *rename_existing_to_list); - if (c && commands == nullptr) + if (commands == nullptr) { - int q; + /* Check whether the user is trying to redefine an existing command. */ + if (rename_existing_to_cmd != nullptr) + check_command_redefinition (rename_existing_to_cmd); + else if (c != nullptr && rename_existing_to == nullptr) + check_command_redefinition (c); + } - if (c->theclass == class_user || c->theclass == class_alias) - { - /* if C is a prefix command that was previously defined, - tell the user its subcommands will be kept, and ask - if ok to redefine the command. */ - if (c->prefixlist != nullptr) - q = (c->user_commands.get () == nullptr - || query (_("Keeping subcommands of prefix command \"%s\".\n" - "Redefine command \"%s\"? "), c->name, c->name)); - else - q = query (_("Redefine command \"%s\"? "), c->name); - } - else - q = query (_("Really redefine built-in command \"%s\"? "), c->name); - if (!q) - error (_("Command \"%s\" not redefined."), c->name); + /* Checks on the validity of renames. */ + if (rename_existing_to != nullptr) + { + if (c == nullptr) + error (_("Command \"%s\" does not exist, so it cannot " + "be renamed to \"%s\"."), + comfull, rename_existing_to_full); + + if (get_hook_type (rename_existing_to) != CMD_NO_HOOK) + error (_("Cannot define hooks by renaming commands.")); } /* If this new command is a hook, then mark the command which it is hooking. Note that we allow hooking `help' commands, so that we can hook the `stop' pseudo-command. */ - - if (!strncmp (comname, HOOK_STRING, HOOK_LEN)) - { - hook_type = CMD_PRE_HOOK; - hook_name_size = HOOK_LEN; - } - else if (!strncmp (comname, HOOK_POST_STRING, HOOK_POST_LEN)) - { - hook_type = CMD_POST_HOOK; - hook_name_size = HOOK_POST_LEN; - } - + hook_type = get_hook_type (comname, &hookee_name); if (hook_type != CMD_NO_HOOK) { /* Look up cmd it hooks. */ - hookc = lookup_cmd_exact (comname + hook_name_size, *list, + hookc = lookup_cmd_exact (hookee_name, *list, /* ignore_help_classes = */ false); if (!hookc && commands == nullptr) { @@ -1462,6 +1537,8 @@ do_define_command (const char *comname, int from_tty, comfull); } + /* ADD_CMD takes ownership of the command name, but not of + RENAME_EXISTING_TO. */ comname = xstrdup (comname); counted_command_line cmds; @@ -1480,7 +1557,8 @@ do_define_command (const char *comname, int from_tty, newc = add_cmd (comname, class_user, user_defined_command, (c != nullptr && c->theclass == class_user) - ? c->doc : xstrdup ("User-defined."), list); + ? c->doc : xstrdup ("User-defined."), list, + rename_existing_to, rename_existing_to_list); newc->user_commands = std::move (cmds); /* If we define or re-define a command that was previously defined @@ -1516,10 +1594,118 @@ do_define_command (const char *comname, int from_tty, } } +/* The options for the "define" command. */ + +struct define_cmd_opts +{ + /* For "-rename-existing-to". */ + char *rename_existing_to = nullptr; + + ~define_cmd_opts () + { + xfree (rename_existing_to); + } +}; + +static const gdb::option::option_def define_cmd_option_defs[] = { + + gdb::option::string_option_def { + "rename-existing-to", + [] (define_cmd_opts *opts) { return &opts->rename_existing_to; }, + nullptr, + N_("Rename the existing command called NAME to the specified name.\n\ +This is useful to allow the new implementation of a command to invoke\n\ +the original implementation. Existing subcommands, hooks and aliases\n\ +for the original command are moved to the newly defined one."), + }, + +}; + +/* Create an option_def_group for the "define" command's options, with + OPTS as context. */ + +static inline gdb::option::option_def_group +make_define_cmd_options_def_group (define_cmd_opts *opts) +{ + return {{define_cmd_option_defs}, opts}; +} + +/* Implementation of the "define" command. */ + +static void +define_command (const char *arg, int from_tty) +{ + define_cmd_opts opts; + + auto grp = make_define_cmd_options_def_group (&opts); + gdb::option::process_options + (&arg, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND, grp); + + do_define_command (arg, from_tty, nullptr, opts.rename_existing_to); +} + +/* Rename command OLD_NAME to NEW_NAME. */ + +static void +do_rename_command (const char *old_name, const char *new_name, int from_tty) +{ + const char *old_name_full = old_name; + struct cmd_list_element **old_list = validate_comname (&old_name); + + const char *new_name_full = new_name; + struct cmd_list_element **new_list = validate_comname (&new_name); + + if (strcmp (old_name_full, new_name_full) == 0) + error (_("Cannot rename command \"%s\" as the new name is identical " + "to the existing one."), + old_name_full); + + if (get_hook_type (old_name) != CMD_NO_HOOK) + error (_("Cannot rename hooks.")); + + if (get_hook_type (new_name) != CMD_NO_HOOK) + error (_("Cannot define hooks by renaming commands.")); + + /* Are we trying to redefine a command already called NEW_NAME_FULL? */ + struct cmd_list_element *existing_cmd_with_new_name + = lookup_cmd_exact (new_name, *new_list); + if (existing_cmd_with_new_name != nullptr) + check_command_redefinition (existing_cmd_with_new_name); + + struct cmd_list_element *cmd = rename_cmd (old_name, old_list, + new_name, new_list); + if (cmd == nullptr) + error (_("Command \"%s\" does not exist, so it cannot be renamed " + "to \"%s\"."), + old_name_full, new_name_full); +} + +/* Implementation of the "rename" command. */ + static void -define_command (const char *comname, int from_tty) +rename_command (const char *arg, int from_tty) { - do_define_command (comname, from_tty, nullptr); + if (arg == nullptr) + arg = ""; + + std::string old_name = extract_string_maybe_quoted (&arg); + std::string new_name = extract_string_maybe_quoted (&arg); + + arg = skip_spaces (arg); + + if (*arg != '\0') + { + error (_("Too many arguments: %s\n" + "If you are trying to use a command name with spaces, " + "use quotes."), + arg); + } + else if (old_name.empty ()) + error (_("Arguments required (name of command to rename and new name).")); + else if (new_name.empty ()) + error (_("Argument required (new name for \"%s\")."), old_name.c_str ()); + + do_rename_command (old_name.c_str (), new_name.c_str (), from_tty); } /* Document a user-defined command. If COMMANDS is NULL, then this is a @@ -1706,7 +1892,7 @@ _initialize_cli_script () { struct cmd_list_element *c; - /* "document", "define" and "define-prefix" use command_completer, + /* "document", "define", "define-prefix" and "rename" use command_completer, as this helps the user to either type the command name and/or its prefixes. */ document_cmd_element = add_com ("document", class_support, document_command, @@ -1715,15 +1901,27 @@ Document a user-defined command.\n\ Give command name as argument. Give documentation on following lines.\n\ End with a line of just \"end\".")); set_cmd_completer (document_cmd_element, command_completer); - define_cmd_element = add_com ("define", class_support, define_command, _("\ + + const auto define_opts = make_define_cmd_options_def_group (nullptr); + static const std::string define_help + = gdb::option::build_help (_("\ Define a new command name. Command name is argument.\n\ +Usage: define [-rename-existing-to NEW_NAME_FOR_EXISTING_COMMAND] NAME\n\ +\n\ +Options:\n\ +%OPTIONS%\n\ +\n\ Definition appears on following lines, one command per line.\n\ End with a line of just \"end\".\n\ Use the \"document\" command to give documentation for the new command.\n\ Commands defined in this way may accept an unlimited number of arguments\n\ accessed via $arg0 .. $argN. $argc tells how many arguments have\n\ -been passed.")); +been passed."), + define_opts); + define_cmd_element = add_com ("define", class_support, define_command, + define_help.c_str ()); set_cmd_completer (define_cmd_element, command_completer); + c = add_com ("define-prefix", class_support, define_prefix_command, _("\ Define or mark a command as a user-defined prefix command.\n\ @@ -1732,6 +1930,20 @@ other user defined commands.\n\ If the command already exists, it is changed to a prefix command.")); set_cmd_completer (c, command_completer); + c = add_com ("rename", class_support, rename_command, _("\ +Rename a command.\n\ +Usage: rename OLD_NAME NEW_NAME\n\ +\n\ +If a command called NEW_NAME exists, it's overwritten. Any subcommands\n\ +of NEW_NAME are not modified.\n\ +\n\ +If OLD_NAME has subcommands, they are preserved and OLD_NAME becomes a\n\ +prefix-only command, see the \"define-prefix\" command.\n\ +\n\ +Aliases and hooks are preserved and, after the rename, will point to\n\ +NEW_NAME.")); + set_cmd_completer (c, command_completer); + while_cmd_element = add_com ("while", class_support, while_command, _("\ Execute nested commands WHILE the conditional expression is non zero.\n\ The conditional expression must follow the word `while' and must in turn be\n\ diff --git a/gdb/command.h b/gdb/command.h index ca791cff809..b20737a6aee 100644 --- a/gdb/command.h +++ b/gdb/command.h @@ -175,24 +175,37 @@ extern bool valid_cmd_char_p (int c); It should start with ? for a command that is an abbreviation or with * for a command that most users don't need to know about. + If RENAME_EXISTING_TO is not null, then the existing command called + NAME in *LIST will be renamed to RENAME_EXISTING_TO in + *RENAME_EXISTING_TO_LIST. If a command called RENAME_EXISTING_TO + already exists in *RENAME_EXISTING_TO_LIST, it gets deleted first. + Ownership of RENAME_EXISTING_TO is not taken by this function (i.e. + the caller is responsible to free it if needed). + If NAME already existed in *LIST, all its hooks and aliases are moved to the new command. Return a pointer to the added command (not necessarily the head of *LIST). */ -extern struct cmd_list_element *add_cmd (const char *name, - enum command_class theclass, - cmd_const_cfunc_ftype *fun, - const char *doc, - struct cmd_list_element **list); +extern struct cmd_list_element *add_cmd + (const char *name, + enum command_class theclass, + cmd_const_cfunc_ftype *fun, + const char *doc, + struct cmd_list_element **list, + const char *rename_existing_to=nullptr, + struct cmd_list_element **rename_existing_to_list=nullptr); /* Like add_cmd, but no command function is specified. */ -extern struct cmd_list_element *add_cmd (const char *name, - enum command_class theclass, - const char *doc, - struct cmd_list_element **list); +extern struct cmd_list_element *add_cmd + (const char *name, + enum command_class theclass, + const char *doc, + struct cmd_list_element **list, + const char *rename_existing_to=nullptr, + struct cmd_list_element **rename_existing_to_list=nullptr); extern struct cmd_list_element *add_cmd_suppress_notification (const char *name, enum command_class theclass, @@ -210,12 +223,16 @@ extern struct cmd_list_element *add_alias_cmd (const char *, struct cmd_list_element **); -extern struct cmd_list_element *add_prefix_cmd (const char *, enum command_class, - cmd_const_cfunc_ftype *fun, - const char *, - struct cmd_list_element **, - int, - struct cmd_list_element **); +extern struct cmd_list_element *add_prefix_cmd + (const char *name, + enum command_class theclass, + cmd_const_cfunc_ftype *fun, + const char *doc, + struct cmd_list_element **prefixlist, + int allow_unknown, + struct cmd_list_element **list, + const char *rename_existing_to=nullptr, + struct cmd_list_element **rename_existing_to_list=nullptr); /* Like add_prefix_cmd, but sets the callback to a function that simply calls help_list. */ @@ -253,6 +270,15 @@ typedef void cmd_const_sfunc_ftype (const char *args, int from_tty, extern void set_cmd_sfunc (struct cmd_list_element *cmd, cmd_const_sfunc_ftype *sfunc); +/* Rename the command called OLD_NAME in *OLD_LIST to NEW_NAME in + *NEW_LIST. + + Return the renamed command or, if no such command exists, NULLPTR. */ +extern struct cmd_list_element *rename_cmd (const char *old_name, + struct cmd_list_element **old_list, + const char *new_name, + struct cmd_list_element **new_list); + /* A completion routine. Add possible completions to tracker. TEXT is the text beyond what was matched for the command itself @@ -560,6 +586,9 @@ extern void struct cmd_list_element **set_list, struct cmd_list_element **show_list); +/* Placeholder in the command data structures for user-defined commands. */ +extern void user_defined_command (const char *ignore, int from_tty); + /* Do a "show" command for each thing on a command list. */ extern void cmd_show_list (struct cmd_list_element *, int); diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 3e3c38dea3a..78bee27a333 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -26949,18 +26949,41 @@ define adder end @end smallexample +It's possible to replace existing commands and build on top of them, by +using the @code{-rename-existing-to} option: + +@smallexample +define -rename-existing-to original-run run + echo Will now run a program!\n + # Invoke the original "run" command which was renamed to + # "original-run". + original-run +end +@end smallexample + @table @code @kindex define -@item define @var{commandname} -Define a command named @var{commandname}. If there is already a command -by that name, you are asked to confirm that you want to redefine it. +@item define @r{[}-rename-existing-to @var{new-name}@r{]} @var{commandname} +Define a command named @var{commandname}. The argument @var{commandname} may be a bare command name consisting of letters, numbers, dashes, dots, and underscores. It may also start with any predefined or user-defined prefix command. For example, @samp{define target my-target} creates a user-defined @samp{target my-target} command. +By default, if there is already a command by that name, you are asked to +confirm that you want to redefine it. +If @code{-rename-existing-to} is specified, the existing command called +@var{commandname} is renamed to @var{new-name} first. All subcommands, +aliases (@pxref{Aliases}) and hooks (@pxref{Hooks}) for @var{commandname} +will refer to the newly defined @var{commandname}, not to the renamed +command. This is different from using the @code{rename} command +(@pxref{rename command}) and @code{define} separately as, in that case, +subcommands, aliases and hooks refer to the renamed command. +If @var{new-name} contains spaces, it must be enclosed in double quotes, +unlike @var{commandname} which should not be enclosed in quotes. + The definition of the command is made up of other @value{GDBN} command lines, which are given following the @code{define} command. The end of these commands is marked by a line containing @code{end}. @@ -27019,6 +27042,32 @@ command def (gdb) @end example +@kindex rename +@cindex rename command +@anchor{rename command} +@item rename @var{old-name} @var{new-name} +Rename the command called @var{old-name} to @var{new-name}. +All subcommands, aliases (@pxref{Aliases}) and hooks (@pxref{Hooks}) for +@var{old-name} will refer to @var{new-name} after the rename. +If a command name contains spaces, it must be enclosed in double quotes. + +Example: +@example +(gdb) define-prefix abc +(gdb) define abc def +Type commands for definition of "abc def". +End with a line saying just "end". +>echo this is the command\n +>end +(gdb) abc def +this is the command +(gdb) rename "abc def" renamed-command +(gdb) abc def +Undefined abc command: "def". Try "help abc". +(gdb) renamed-command +this is the command +@end example + @kindex dont-repeat @cindex don't repeat command @item dont-repeat diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 35568594f58..22155f595c6 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -3657,7 +3657,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{rename_existing_to}@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. @@ -3671,7 +3671,9 @@ There is no support for multi-line commands. @var{command_class} should be one of the @samp{COMMAND_} constants defined below. This argument tells @value{GDBN} how to categorize the -new command in the help system. +new command in the help system. This argument is required unless +@var{rename_existing_to} is specified, in which case it defaults to the +value used by the existing command being renamed. @var{completer_class} is an optional argument. If given, it should be one of the @samp{COMPLETE_} constants defined below. This argument @@ -3682,7 +3684,19 @@ error will occur when completion is attempted. @var{prefix} is an optional argument. If @code{True}, then the new command is a prefix command; sub-commands of this command may be -registered. +registered. If @var{rename_existing_to} is set, then @var{prefix} must +not be set as whether the command is a prefix command or not is inherited +from the existing command being renamed. + +@var{rename_existing_to} is an optional argument. If set, then the +existing command called @var{name} is renamed to @var{rename_existing_to}. +Existing subcommands, aliases and hooks are preserved and will refer to +the newly defined command. Additionally, then @var{prefix} must be unset +as whether the new command is a prefix or not will depend on whether the +original command was a prefix command or not. +When renaming an existing command, @var{command_class} and +@var{completer_class}, if unspecified, are copied from the existing +command. 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 @@ -3931,6 +3945,24 @@ registration of the command with @value{GDBN}. Depending on how the Python code is read into @value{GDBN}, you may need to import the @code{gdb} module explicitly. +The following code snippet shows how a CLI command building on top of an +existing command can be implemented: + +@smallexample +class MyQuit (gdb.Command): + """Print a message before executing the "quit" command.""" + + def __init__ (self): + super (MyQuit, self).__init__ ("quit", gdb.COMMAND_RUNNING, + rename_existing_to="original-quit") + + def invoke (self, arg, from_tty): + print ("Will now quit!") + gdb.execute ("original-quit %s" % arg, from_tty=from_tty) + +MyQuit () +@end smallexample + @node Parameters In Python @subsubsection Parameters In Python diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c index 9833bf863b0..e5cb7a881ca 100644 --- a/gdb/python/py-cmd.c +++ b/gdb/python/py-cmd.c @@ -431,15 +431,22 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) { cmdpy_object *obj = (cmdpy_object *) self; const char *name; - int cmdtype; - int completetype = -1; + long cmdtype; + long completetype = -1; char *docstring = NULL; struct cmd_list_element **cmd_list; char *cmd_name; static const char *keywords[] = { "name", "command_class", "completer_class", - "prefix", NULL }; + "prefix", "rename_existing_to", + NULL }; + PyObject *cmdtype_obj = NULL; + PyObject *completetype_obj = NULL; PyObject *is_prefix_obj = NULL; bool is_prefix = false; + const char *rename_existing_to = NULL; + char *rename_existing_to_cmd_name = NULL; + struct cmd_list_element *renamed_cmd = NULL; + struct cmd_list_element **rename_existing_to_cmd_list = nullptr; if (obj->command) { @@ -450,40 +457,130 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) return -1; } - if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "si|iO", - keywords, &name, &cmdtype, - &completetype, &is_prefix_obj)) + /* The second argument (command_class) is marked as optional because + it should not be specified if RENAME_EXISTING_TO is specified. + Otherwise it's required. */ + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|O!O!Os", + keywords, + &name, + &PyInt_Type, &cmdtype_obj, + &PyInt_Type, &completetype_obj, + &is_prefix_obj, + &rename_existing_to)) return -1; - if (cmdtype != no_class && cmdtype != class_run - && cmdtype != class_vars && cmdtype != class_stack - && cmdtype != class_files && cmdtype != class_support - && cmdtype != class_info && cmdtype != class_breakpoint - && cmdtype != class_trace && cmdtype != class_obscure - && cmdtype != class_maintenance && cmdtype != class_user - && cmdtype != class_tui) - { - PyErr_Format (PyExc_RuntimeError, _("Invalid command class argument.")); - return -1; - } - - if (completetype < -1 || completetype >= (int) N_COMPLETERS) - { - PyErr_Format (PyExc_RuntimeError, - _("Invalid completion type argument.")); - return -1; - } - cmd_name = gdbpy_parse_command_name (name, &cmd_list, &cmdlist); if (! cmd_name) return -1; + /* Deal with rename_existing_to before other arguments as it affects how + some of them are treated. */ + if (rename_existing_to != NULL) + { + renamed_cmd = lookup_cmd_exact (cmd_name, *cmd_list); + if (renamed_cmd == nullptr) + { + PyErr_Format (PyExc_RuntimeError, + _("Command \"%s\" does not exist, so it cannot " + "be renamed to \"%s\"."), + name, rename_existing_to); + xfree (cmd_name); + return -1; + } + + rename_existing_to_cmd_name = + gdbpy_parse_command_name (rename_existing_to, + &rename_existing_to_cmd_list, + &cmdlist); + if (! rename_existing_to_cmd_name) + { + xfree (cmd_name); + return -1; + } + } + + if (cmdtype_obj == NULL) + { + /* COMMAND_CLASS is optional only when rename_existing_to is + specified. This error matches the phrasing of the error the + interpreter would raise if COMMAND_CLASS was not marked as + optional in gdb_PyArg_ParseTupleAndKeywords. */ + if (renamed_cmd == NULL) + { + PyErr_Format (PyExc_TypeError, + _("function missing required argument " + "'command_class' (pos 2)")); + xfree (cmd_name); + xfree (rename_existing_to_cmd_name); + return -1; + } + + cmdtype = renamed_cmd->theclass; + } + else + { + cmdtype = PyInt_AsLong (cmdtype_obj); + if (PyErr_Occurred ()) + { + xfree (cmd_name); + xfree (rename_existing_to_cmd_name); + return -1; + } + if (cmdtype != no_class && cmdtype != class_run + && cmdtype != class_vars && cmdtype != class_stack + && cmdtype != class_files && cmdtype != class_support + && cmdtype != class_info && cmdtype != class_breakpoint + && cmdtype != class_trace && cmdtype != class_obscure + && cmdtype != class_maintenance && cmdtype != class_user + && cmdtype != class_tui) + { + PyErr_Format (PyExc_RuntimeError, + _("Invalid command class argument.")); + xfree (cmd_name); + xfree (rename_existing_to_cmd_name); + return -1; + } + } + + if (completetype_obj != NULL) + { + completetype = PyInt_AsLong (completetype_obj); + if (PyErr_Occurred ()) + { + xfree (cmd_name); + xfree (rename_existing_to_cmd_name); + return -1; + } + } + + if (completetype < -1 || completetype >= (int) N_COMPLETERS) + { + PyErr_Format (PyExc_RuntimeError, + _("Invalid completion type argument.")); + xfree (cmd_name); + xfree (rename_existing_to_cmd_name); + return -1; + } + if (is_prefix_obj != NULL) { + if (renamed_cmd != NULL) + { + PyErr_Format + (PyExc_TypeError, + _("Cannot specify argument 'prefix' with 'rename_existing_to'. " + "Whether the command is a prefix command depends on the " + "renamed command.")); + xfree (cmd_name); + xfree (rename_existing_to_cmd_name); + return -1; + } + int cmp = PyObject_IsTrue (is_prefix_obj); - if (cmp < 0) + if (cmp < 0) { xfree (cmd_name); + xfree (rename_existing_to_cmd_name); return -1; } is_prefix = cmp > 0; @@ -499,6 +596,7 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) if (docstring == NULL) { xfree (cmd_name); + xfree (rename_existing_to_cmd_name); return -1; } } @@ -521,11 +619,15 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) allow_unknown = PyObject_HasAttr (self, invoke_cst); cmd = add_prefix_cmd (cmd_name, (enum command_class) cmdtype, NULL, docstring, &obj->sub_list, - allow_unknown, cmd_list); + allow_unknown, cmd_list, + rename_existing_to_cmd_name, + rename_existing_to_cmd_list); } else cmd = add_cmd (cmd_name, (enum command_class) cmdtype, - docstring, cmd_list); + docstring, cmd_list, + rename_existing_to_cmd_name, + rename_existing_to_cmd_list); /* There appears to be no API to set this. */ cmd->func = cmdpy_function; @@ -535,15 +637,30 @@ cmdpy_init (PyObject *self, PyObject *args, PyObject *kw) obj->command = cmd; set_cmd_context (cmd, self_ref.release ()); - set_cmd_completer (cmd, ((completetype == -1) ? cmdpy_completer - : completers[completetype].completer)); - if (completetype == -1) - set_cmd_completer_handle_brkchars (cmd, - cmdpy_completer_handle_brkchars); + + if (completetype_obj == NULL && renamed_cmd != NULL) + { + /* No explicit completer was passed and the existing command + was renamed, so use the command completer information from + the original command. */ + set_cmd_completer (cmd, renamed_cmd->completer); + set_cmd_completer_handle_brkchars + (cmd, renamed_cmd->completer_handle_brkchars); + } + else + { + set_cmd_completer (cmd, ((completetype == -1) ? cmdpy_completer + : completers[completetype].completer)); + if (completetype == -1) + set_cmd_completer_handle_brkchars (cmd, + cmdpy_completer_handle_brkchars); + + } } catch (const gdb_exception &except) { xfree (cmd_name); + xfree (rename_existing_to_cmd_name); xfree (docstring); gdbpy_convert_exception (except); return -1; diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 56702cad53a..e4b4facdda9 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -95,6 +95,7 @@ #define Py_TPFLAGS_HAVE_ITER 0 #define Py_TPFLAGS_CHECKTYPES 0 +#define PyInt_Type PyLong_Type #define PyInt_Check PyLong_Check #define PyInt_AsLong PyLong_AsLong #define PyInt_AsSsize_t PyLong_AsSsize_t diff --git a/gdb/testsuite/gdb.base/command-renaming.exp b/gdb/testsuite/gdb.base/command-renaming.exp new file mode 100644 index 00000000000..238a735dfb0 --- /dev/null +++ b/gdb/testsuite/gdb.base/command-renaming.exp @@ -0,0 +1,571 @@ +# Copyright (C) 2009-2021 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 renaming of commands. + +standard_testfile + +clean_restart + +proc prepare_gdb {} { + gdb_exit + gdb_start +} + +# Test basic usage of the "rename" command. +# +proc test_rename_ok {} { + prepare_gdb + + with_test_prefix "basic" { + gdb_test_no_output "rename run my-run" + # The old "run" command doesn't exist any more. + gdb_test_exact "run" \ + "Undefined command: \"run\". Try \"help\"." + # The new one works. + gdb_test "my-run" ".*No executable file specified.*" + # The "r" alias still works. + gdb_test "r" ".*No executable file specified.*" + } + + with_test_prefix "hooks" { + gdb_define_cmd "original" { + "echo Original\\n" + } + gdb_test_no_output "alias alias-original = original" + gdb_define_cmd "hook-original" { + "echo Before original\\n" + } + gdb_define_cmd "hookpost-original" { + "echo After original\\n" + } + + gdb_test_no_output "rename original renamed" + # The old "original" command doesn't exist any more. + gdb_test_exact "original" \ + "Undefined command: \"original\". Try \"help\"." + # The new one works, including hooks. + gdb_test_exact "renamed" \ + "Before original\nOriginal\nAfter original" + # The "alias-original" alias still works, including hooks. + gdb_test_exact "alias-original" \ + "Before original\nOriginal\nAfter original" + } +} + +# Test that "rename" deals correctly with error conditions. + +proc test_rename_errors {} { + prepare_gdb + + with_test_prefix "non-existing command" { + gdb_test_exact "rename nope renamed" \ + "Command \"nope\" does not exist, so it cannot be renamed to \"renamed\"." + } + + with_test_prefix "same old and new name" { + gdb_test_exact "rename run run" \ + "Cannot rename command \"run\" as the new name is identical to the existing one." + } + + with_test_prefix "rename to hook" { + gdb_define_cmd "my-cmd" { + "echo Command\\n" + } + + with_test_prefix "pre" { + gdb_test_exact "rename my-cmd hook-echo" \ + "Cannot define hooks by renaming commands." + } + + with_test_prefix "post" { + gdb_test_exact "rename my-cmd hookpost-echo" \ + "Cannot define hooks by renaming commands." + } + } +} + +# Test that "rename" works when the prefix of the renamed command changes. + +proc test_rename_across_prefixes {} { + prepare_gdb + + with_test_prefix "rename with prefixes" { + gdb_test_no_output "define-prefix prefix1" + gdb_test_no_output "define-prefix prefix2" + gdb_test_no_output "define-prefix prefix2 sub-prefix" + + gdb_define_cmd "prefix1 foo" { + "echo Foo\\n" + } + + gdb_test_no_output "rename 'prefix1 foo' 'prefix2 sub-prefix renamed-foo'" + + gdb_test_exact "prefix1 foo" \ + "Undefined prefix1 command: \"foo\". Try \"help prefix1\"." + gdb_test_exact "prefix2 sub-prefix renamed-foo" "Foo" + } +} + +# Test that renaming of prefix commands works. + +proc test_rename_prefixes {} { + prepare_gdb + gdb_test_no_output "set confirm off" + + with_test_prefix "rename of prefixes" { + # Define two prefixes, each one with a subcommand. + foreach letter {"A" "B"} { + gdb_test_no_output "define-prefix prefix-${letter}" + gdb_define_cmd "prefix-${letter}" [subst { + "echo Original prefix ${letter}\\\\n" + }] + gdb_define_cmd "prefix-${letter} sub-${letter}" [subst { + "echo Subcommand ${letter} of prefix ${letter}\\\\n" + }] + } + + gdb_test_no_output "rename prefix-A prefix-B" + + # The subcommands still work as before, the rename didn't affect them. + gdb_test_exact "prefix-A sub-A" "Subcommand A of prefix A" + gdb_test_exact "prefix-B sub-B" "Subcommand B of prefix B" + # "prefix-B"'s implementation is what used to be "prefix-A"'s + # implementation. + gdb_test_exact "prefix-B" "Original prefix A" + # "prefix-A" is just a prefix command without any code associated with + # it, and it exists only for the sake of providing subcommands. + gdb_test "prefix-A" \ + ".*\"prefix-A\" must be followed by the name of a subcommand.*" + } +} + +# Test that "rename" works when it leads to a command being overwritten by +# the renamed one. +# +# PREFIX, if not empty, if the prefix for the renamed and overwritten +# commands. + +proc test_rename_redefine {prefix} { + global gdb_prompt + + prepare_gdb + + with_test_prefix "overwriting existing command by renaming, prefix=\"${prefix}\"" { + if { $prefix == "" } { + set foo_cmd "foo" + set bar_cmd "bar" + } else { + gdb_test_no_output "define-prefix ${prefix}" + set foo_cmd "${prefix} foo" + set bar_cmd "${prefix} bar" + } + + # Define "foo". + gdb_define_cmd $foo_cmd { + "echo Original foo\\n" + } + + # Define an alias to the original "foo" with the same prefix. + gdb_test_no_output "alias ${prefix} alias-to-original-foo-same-prefix = ${foo_cmd}" + # And another one but without a prefix. + gdb_test_no_output "alias alias-to-original-foo-no-prefix = ${foo_cmd}" + + # Define hooks for the original "foo". + gdb_define_cmd "${prefix} hook-foo" { + "echo Pre-hook for original foo\\n" + } + gdb_define_cmd "${prefix} hookpost-foo" { + "echo Post-hook for original foo\\n" + } + + # Define "bar". + gdb_define_cmd $bar_cmd { + "echo Original bar\\n" + } + + # Rename "bar" to "foo", which deletes the old "foo". + send_gdb "rename '${bar_cmd}' '${foo_cmd}'\n" + gdb_expect { + -re "Redefine command \"foo\".*y or n. $"\ + {send_gdb "y\n" + gdb_expect { + -re "$gdb_prompt $"\ + {pass "rename user command"} + timeout {fail "(timeout) rename user command"} + } + } + -re "$gdb_prompt $"\ + {fail "redefine user command"} + timeout {fail "(timeout) rename user command"} + } + + # "foo" should be the new command (which used to be called "bar", + # hence the message). + gdb_test_exact $foo_cmd "Original bar" + + # The aliases to the original "foo" should have been deleted. + gdb_test "${prefix} alias-to-original-foo-same-prefix" \ + ".*Undefined.* command:.*" + gdb_test "alias-to-original-foo-no-prefix" \ + ".*Undefined command:.*" + + # The hooks for the original "foo" should have been deleted as well. + gdb_test "${prefix} hook-foo" \ + ".*Undefined.* command:.*" + gdb_test "${prefix} hookpost-foo" \ + ".*Undefined.* command:.*" + } +} + +# Test that, when a command with hooks is renamed, the hooks get updated +# as well. + +proc test_rename_hooks_updated {} { + prepare_gdb + + with_test_prefix "hooks renamed if command renamed" { + # Define some hooks for "delete". + gdb_define_cmd "hook-delete" { + "echo Pre-hook\\n" + } + gdb_define_cmd "hookpost-delete" { + "echo Post-hook\\n" + } + # Define aliases for the hooks. + gdb_test_no_output "alias hd-pre = hook-delete" + gdb_test_no_output "alias hd-post = hookpost-delete" + # Rename "delete" to "my-prefix delete-cmd" + gdb_test_no_output "define-prefix my-prefix" + gdb_test_no_output "rename 'delete' 'my-prefix delete-cmd'" + + # The renamed command must still work. + gdb_test_exact "my-prefix delete-cmd 9999" \ + "Pre-hook\nNo breakpoint number 9999.\nPost-hook" + + # Hooks must have been renamed as well. + gdb_test_exact "my-prefix hook-delete-cmd" "Pre-hook" + gdb_test_exact "my-prefix hookpost-delete-cmd" "Post-hook" + # And the aliases to the hooks must still work. + gdb_test_exact "hd-pre" "Pre-hook" + gdb_test_exact "hd-post" "Post-hook" + # The old names for the hooks don't work any more (as they were + # renamed). + gdb_test "hook-delete" "Undefined command.*" + gdb_test "hookpost-delete" "Undefined command.*" + # The help for the hooks and their aliases must show the correct + # updated names. + gdb_test "help my-prefix hook-delete-cmd" \ + "my-prefix hook-delete-cmd, hd-pre.*" + gdb_test "help hd-pre" "my-prefix hook-delete-cmd, hd-pre.*" + gdb_test "help my-prefix hookpost-delete-cmd" \ + "my-prefix hookpost-delete-cmd, hd-post.*" + gdb_test "help hd-post" "my-prefix hookpost-delete-cmd, hd-post.*" + } +} + +# Helper function for defining a command called "new" and then redefining +# it with "define -rename-existing-to old new". +# +# If CREATE_ALIAS is 1, then an "alias-to-new" alias to the original +# command is created. +# +# If CREATE_PRE_HOOK is 1, then a hook for the original command is +# created. +# +# CREATE_POST_HOOK behaves likes CREATE_PRE_HOOK except that it creates +# a post-hook. + +proc define_with_rename {create_alias create_pre_hook create_post_hook} { + prepare_gdb + + gdb_define_cmd "new" { + "echo Original\\n" + } + + if {$create_alias} then { + gdb_test_no_output "alias alias-to-new = new" + } + + if {$create_pre_hook} then { + gdb_define_cmd "hook-new" { + "echo Pre-hook\\n" + } + } + + if {$create_post_hook} then { + gdb_define_cmd "hookpost-new" { + "echo Post-hook\\n" + } + } + + gdb_define_cmd "-rename-existing-to old new" { + "echo Redefined: before\\n" + "old" + "echo Redefined: after\\n" + } +} + + +# Test that "define -rename-existing-to" works with and without aliases +# and hooks. + +proc test_define_renaming_ok {} { + with_test_prefix "basic" { + define_with_rename 0 0 0 + gdb_test_exact "new" \ + "Redefined: before\nOriginal\nRedefined: after" + gdb_test_exact "old" "Original" + } + + with_test_prefix "alias" { + define_with_rename 1 0 0 + gdb_test_exact "new" \ + "Redefined: before\nOriginal\nRedefined: after" + gdb_test_exact "alias-to-new" \ + "Redefined: before\nOriginal\nRedefined: after" + gdb_test_exact "old" "Original" + } + + with_test_prefix "pre-hook" { + define_with_rename 0 1 0 + gdb_test_exact "new" \ + "Pre-hook\nRedefined: before\nOriginal\nRedefined: after" + gdb_test_exact "old" "Original" + } + + with_test_prefix "post-hook" { + define_with_rename 0 0 1 + gdb_test_exact "new" \ + "Redefined: before\nOriginal\nRedefined: after\nPost-hook" + gdb_test_exact "old" "Original" + } + + with_test_prefix "alias, pre-hook and post-hook" { + define_with_rename 1 1 1 + gdb_test_exact "new" \ + "Pre-hook\nRedefined: before\nOriginal\nRedefined: after\nPost-hook" + gdb_test_exact "alias-to-new" \ + "Pre-hook\nRedefined: before\nOriginal\nRedefined: after\nPost-hook" + gdb_test_exact "old" "Original" + } +} + +# Test that renaming with "define -rename-existing-to" works for builtin +# commands. + +proc test_define_renaming_builtin {} { + prepare_gdb + gdb_test_no_output "set confirm off" + + with_test_prefix "rename builtin" { + gdb_define_cmd "-rename-existing-to original-run run" { + "echo Redefined run\\n" + } + + gdb_test_exact "run" "Redefined run" + + gdb_test "original-run" ".*No executable file specified.*" + } +} + +# Test that "define -rename-existing-to" deals correctly with error +# conditions. + +proc test_define_renaming_errors {} { + prepare_gdb + + with_test_prefix "non-existing command" { + gdb_test_exact "define -rename-existing-to renamed nope" \ + "Command \"nope\" does not exist, so it cannot be renamed to \"renamed\"." + } + + with_test_prefix "same old and new name" { + gdb_test_exact "define -rename-existing-to run run" \ + "Cannot rename command \"run\" as the new name is identical to the existing one." + } + + with_test_prefix "rename to hook" { + gdb_define_cmd "my-cmd" { + "echo Command\\n" + } + + with_test_prefix "pre" { + gdb_test_exact "define -rename-existing-to hook-echo my-cmd" \ + "Cannot define hooks by renaming commands." + } + + with_test_prefix "post" { + gdb_test_exact "define -rename-existing-to hookpost-echo my-cmd" \ + "Cannot define hooks by renaming commands." + } + } +} + +# Test that using "define -rename-existing-to" to rename to a different +# prefix works. + +proc test_define_renaming_across_prefixes {} { + prepare_gdb + + with_test_prefix "define -rename-existing-to prefixes" { + gdb_test_no_output "define-prefix prefix1" + gdb_test_no_output "define-prefix prefix2" + gdb_test_no_output "define-prefix prefix2 sub-prefix" + + gdb_define_cmd "prefix1 foo" { + "echo First\\n" + } + + gdb_define_cmd "-rename-existing-to 'prefix2 sub-prefix renamed-foo' prefix1 foo" { + "echo Second\\n" + } + + gdb_test_exact "prefix2 sub-prefix renamed-foo" "First" + gdb_test_exact "prefix1 foo" "Second" + } +} + +# Test that renaming prefixes with "define -rename-existing-to" works. + +proc test_define_renaming_prefixes {} { + prepare_gdb + + with_test_prefix "define -rename-existing-to of prefixes" { + # Define a prefix with an implementation. + gdb_test_no_output "define-prefix prefix" + gdb_define_cmd "prefix" { + "echo Original prefix\\n" + } + # Define a subcommand. + gdb_define_cmd "prefix subcommand" { + "echo Subcommand\\n" + } + + # Define a new implementation of "prefix" renaming the existing to + # "renamed-prefix". + gdb_define_cmd "-rename-existing-to renamed-prefix prefix" { + "echo New prefix\\n" + } + + # "renamed-prefix" still works as a command. + gdb_test_exact "renamed-prefix" "Original prefix" + # The subcommand remains a subcommand of "prefix", not of its renamed + # implementation, so "renamed-prefix subcommand" doesn't exist (and + # never existed). + # This means that that "renamed-prefix subcommand" invokes the + # "renamed-prefix" command with "subcommand" as argument (i.e. + # subcommand is not a command), so the original implementation of + # what was "prefix" is invoked. + gdb_test_exact "renamed-prefix subcommand" "Original prefix" + # The newly defined prefix implementation works. + gdb_test_exact "prefix" "New prefix" + # And the subcommand works as well. + gdb_test_exact "prefix subcommand" "Subcommand" + } +} + +# Test that "define -rename-existing-to" works when it leads to a command +# being overwritten by the renamed one. +# +# PREFIX, if not empty, if the prefix for the renamed and overwritten +# commands. + +proc test_define_renaming_redefine {prefix} { + global gdb_prompt + + prepare_gdb + + with_test_prefix "overwriting existing command with define -rename-existing-to, prefix=\"${prefix}\"" { + if { $prefix == "" } { + set foo_cmd "foo" + set bar_cmd "bar" + } else { + gdb_test_no_output "define-prefix ${prefix}" + set foo_cmd "${prefix} foo" + set bar_cmd "${prefix} bar" + } + + # Define "foo". + gdb_define_cmd $foo_cmd { + "echo Original foo\\n" + } + + # Define an alias to the original "foo" with the same prefix. + gdb_test_no_output "alias ${prefix} alias-to-original-foo-same-prefix = ${foo_cmd}" + # And another one but without a prefix. + gdb_test_no_output "alias alias-to-original-foo-no-prefix = ${foo_cmd}" + + # Define "bar". + gdb_define_cmd $bar_cmd { + "echo Original bar\\n" + } + + # Redefine "bar" while renaming the original "bar" to "foo". + send_gdb "define -rename-existing-to '${foo_cmd}' ${bar_cmd}\n" + gdb_expect { + -re "Redefine command \"foo\".*y or n. $"\ + {send_gdb "y\n" + gdb_expect { + -re "Type commands for definition of \"${bar_cmd}\".\r\nEnd with a line saying just \"end\".\r\n>$"\ + {send_gdb "echo New bar\\n\n${foo_cmd}\nend\n" + gdb_expect { + -re "$gdb_prompt $"\ + {pass "redefine user command"} + timeout {fail "(timeout) redefine user command"} + } + } + timeout {fail "(timeout) redefine user command"} + } + } + -re "$gdb_prompt $"\ + {fail "redefine user command"} + timeout {fail "(timeout) redefine user command"} + } + + # "bar" should be the new command which calls the old orginal "bar". + gdb_test_exact $bar_cmd "New bar\nOriginal bar" + + # The aliases to the original foo should have been deleted. + gdb_test "${prefix} alias-to-original-foo-same-prefix" \ + ".*Undefined.* command:.*" + gdb_test "alias-to-original-foo-no-prefix" \ + ".*Undefined command:.*" + } +} + +with_test_prefix "command renaming" { + # Tests for "rename". + with_test_prefix "rename" { + test_rename_ok + test_rename_errors + test_rename_across_prefixes + test_rename_prefixes + test_rename_redefine "" + test_rename_redefine "my-prefix" + test_rename_hooks_updated + } + + # Tests for the "define -rename-existing-to". + with_test_prefix "define -rename-existing-to" { + test_define_renaming_ok + test_define_renaming_builtin + test_define_renaming_errors + test_define_renaming_across_prefixes + test_define_renaming_prefixes + test_define_renaming_redefine "" + test_define_renaming_redefine "my-prefix" + } +} diff --git a/gdb/testsuite/gdb.python/py-rename-existing.exp b/gdb/testsuite/gdb.python/py-rename-existing.exp new file mode 100644 index 00000000000..189633b068a --- /dev/null +++ b/gdb/testsuite/gdb.python/py-rename-existing.exp @@ -0,0 +1,364 @@ +# Copyright (C) 2009-2021 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 renaming of existing +# commands. + +load_lib gdb-python.exp +load_lib completion-support.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 renaming the existing +# command of the same name to RENAME_EXISTING_TO. +# +# When invoked, the command prints messages and call the renamed command. +# If MSG is not empty then it will be printed when the command is invoked. +# Otherwise, the command name is printed. +# +# EXTRA_ARGS is passed as it to gdb.Command.__init__ after the other +# arguments. + +proc add_py_cmd {cmd rename_existing_to {msg ""} {kwargs ""}} { + gdb_test_no_output \ + "python TestCommand ('${cmd}', '${rename_existing_to}', '${msg}', ${kwargs})" +} + +# 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 +} + +# Test that renaming existing command works and that, after a rename, +# aliases execute the new command. + +proc test_rename_simple {} { + with_test_prefix "rename command" { + prepare_gdb + + gdb_define_cmd "my-cmd" { + "echo my-cmd original\\n" + } + gdb_test_no_output "alias my-cmd-alias = my-cmd" + + add_py_cmd "my-cmd" "original-my-cmd" + + set expected_list { + "py-before: my-cmd" + "my-cmd original" + "py-after: my-cmd" + } + + test_sequence_exact "my-cmd" $expected_list + test_sequence_exact "my-cmd-alias" $expected_list + } +} + +# Test that renaming a command multiple times works. + +proc test_rename_multiple {} { + with_test_prefix "rename command multiple times" { + prepare_gdb + + gdb_define_cmd "my-cmd" { + "echo my-cmd original\\n" + } + + add_py_cmd "my-cmd" "original-my-cmd" "first my-cmd" + add_py_cmd "my-cmd" "first-redefined-my-cmd" "second my-cmd" + + test_sequence_exact "my-cmd" { + "py-before: second my-cmd" + "py-before: first my-cmd" + "my-cmd original" + "py-after: first my-cmd" + "py-after: second my-cmd" + } + } +} + +# Test that trying to rename a non-existing command raises a RuntimeError. + +proc test_rename_non_existing {} { + with_test_prefix "rename non-existing" { + prepare_gdb + + gdb_test \ + "python TestCommand ('this_doesnt_exist', 'this_doesnt_matter')" \ + ".*RuntimeError: Command \"this_doesnt_exist\" does not exist,\ + so it cannot be renamed to \"this_doesnt_matter\"\..*" + } +} + +# Test renaming of a command with a prefix. + +proc test_same_prefix {} { + with_test_prefix "rename keeping the same prefix" { + prepare_gdb + + add_py_cmd "info args" "info renamed-args" + + test_sequence_exact "info args" { + "py-before: info args" + "No frame selected." + "py-after: info args" + } + gdb_test_exact "info renamed-args" "No frame selected." + } +} + +# Test renaming of a command without a prefix to a command with a prefix. + +proc test_rename_adding_prefix {} { + with_test_prefix "rename adding a prefix" { + prepare_gdb + + gdb_define_cmd "my-cmd" { + "echo my-cmd original\\n" + } + + gdb_test_no_output "define-prefix my-prefix" + + add_py_cmd "my-cmd" "my-prefix my-cmd" + + test_sequence_exact "my-cmd" { + "py-before: my-cmd" + "my-cmd original" + "py-after: my-cmd" + } + gdb_test_exact "my-prefix my-cmd" "my-cmd original" + } +} + +# Test renaming of a command with a prefix to a command without prefix. + +proc test_rename_removing_prefix {} { + with_test_prefix "rename removing a prefix" { + prepare_gdb + + add_py_cmd "info args" "renamed-info-args" + + test_sequence_exact "info args" { + "py-before: info args" + "No frame selected." + "py-after: info args" + } + gdb_test_exact "renamed-info-args" "No frame selected." + } +} + +# Test renaming of a command with prefix to a different prefix. + +proc test_rename_changing_prefix {} { + with_test_prefix "rename changing prefix" { + prepare_gdb + + gdb_test_no_output "define-prefix my-prefix" + + add_py_cmd "info args" "my-prefix args" + + test_sequence_exact "info args" { + "py-before: info args" + "No frame selected." + "py-after: info args" + } + gdb_test_exact "my-prefix args" "No frame selected." + } +} + +# Test that replacing a prefix command replaces its implementation but +# doesn't affect its subcommands. + +proc test_replace_prefix {} { + with_test_prefix "replace a prefix command" { + prepare_gdb + + # Replace the "show" command (which has subcommands). + add_py_cmd "show" "original-show" + + # "original-show" won't print anything as it doesn't have subcommands. + test_sequence_exact "show" { + "py-before: show" + "py-after: show" + } + + gdb_test_no_output "set pagination off" + # Subcommands still work. + gdb_test_exact "show pagination" "State of pagination is off." + } +} + +# Test that prefix cannot be passed to gdb.Command.__init__ if +# rename_existing_to is used. + +proc test_prefix_arg_not_allowed {} { + with_test_prefix "prefix argument not allowed with rename_existing_to" { + prepare_gdb + + with_test_prefix "non-prefix command" { + gdb_test \ + "python gdb.Command ('run', rename_existing_to='renamed-run', prefix=True)" \ + ".*Cannot specify argument 'prefix' with 'rename_existing_to'.*" + gdb_test \ + "python gdb.Command ('run', rename_existing_to='renamed-run', prefix=False)" \ + ".*Cannot specify argument 'prefix' with 'rename_existing_to'.*" + } + + with_test_prefix "prefix command" { + gdb_test \ + "python gdb.Command ('show', rename_existing_to='renamed-show', prefix=True)" \ + ".*Cannot specify argument 'prefix' with 'rename_existing_to'.*" + gdb_test \ + "python gdb.Command ('show', rename_existing_to='renamed-run', prefix=False)" \ + ".*Cannot specify argument 'prefix' with 'rename_existing_to'.*" + } + } +} + +# Test that dont-repeat from a renamed command invoked by the new +# implementation of the command prevents the repetition of the new +# command. This is needed to make the new implementation work +# consistently with the original one. + +proc test_repeat {} { + with_test_prefix "repeat" { + prepare_gdb + + # Define a command which repeats and one which doesn't. + gdb_define_cmd "repeat-cmd" { + "echo This command repeats\\n" + } + gdb_define_cmd "no-repeat-cmd" { + "echo This command does NOT repeat\\n" + "dont-repeat\\n" + } + # Then redefine them. + add_py_cmd "repeat-cmd" "renamed-repeat-cmd" + add_py_cmd "no-repeat-cmd" "renamed-no-repeat-cmd" + + # The repeating command must still repeat. + set expected_repeat_list { + "py-before: repeat-cmd" + "This command repeats" + "py-after: repeat-cmd" + } + test_sequence_exact "repeat-cmd" $expected_repeat_list + with_test_prefix "repeating command" { + # Using an empty string works but is slow, probably because it + # waits for a timeout. A string with just whitespace does the same + # but fast. + test_sequence_exact " " $expected_repeat_list + } + + # The non-repeating command must not repeat. + test_sequence_exact "no-repeat-cmd" { + "py-before: no-repeat-cmd" + "This command does NOT repeat" + "py-after: no-repeat-cmd" + } + with_test_prefix "non-repeating command" { + gdb_test_exact " " "" + } + } +} + +# Test that the completer, if not specified, is copied from the original +# command. If specified, the specified one is used. + +proc test_complete {} { + with_test_prefix "command completion" { + prepare_gdb + + # Add a command whose arguments get completed as commands. + gdb_test_no_output \ + "python gdb.Command ('my-cmd', gdb.COMMAND_NONE, gdb.COMPLETE_COMMAND)" + + # Redefine the command. As no completer was specified, it must + # inherit the one from the original command. + add_py_cmd "my-cmd" "renamed-my-cmd1" + with_test_prefix "completer inherited" { + test_gdb_complete_unique "my-cmd dele" "my-cmd delete" + } + + # Redefine the command again. As a completer was specified, the + # command must use the specified completer, not the original one. + add_py_cmd "my-cmd" "renamed-my-cmd2" "" "completer_class=gdb.COMPLETE_NONE" + with_test_prefix "completer specified" { + test_gdb_complete_none "my-cmd dele" + } + } +} + +# Test the behaviour of command classes with renamed commands. + +proc test_command_class {} { + with_test_prefix "command class" { + prepare_gdb + + # Rename the "run" command. The command class, as it's not specified, + # is inherited from the original command. + add_py_cmd "run" "renamed-run1" + gdb_test "help running" \ + ".*renamed-run1 -- Start debugged program\..*run, r -- .*" + + # Rename it again but, this time, put the new command in a different + # command class. + add_py_cmd "run" "renamed-run2" "" "command_class=gdb.COMMAND_STATUS" + gdb_test "help status" \ + ".*run, r -- .*" + } +} + +with_test_prefix "renaming commands via Python" { + test_rename_simple + test_rename_multiple + test_rename_non_existing + test_same_prefix + test_rename_adding_prefix + test_rename_removing_prefix + test_rename_changing_prefix + test_replace_prefix + test_prefix_arg_not_allowed + test_repeat + test_complete + test_command_class +} diff --git a/gdb/testsuite/gdb.python/py-rename-existing.py b/gdb/testsuite/gdb.python/py-rename-existing.py new file mode 100644 index 00000000000..7db0f505c7a --- /dev/null +++ b/gdb/testsuite/gdb.python/py-rename-existing.py @@ -0,0 +1,46 @@ +# Copyright (C) 2008-2021 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 renaming of existing +# commands. + +import gdb + +class TestCommand (gdb.Command): + + def __init__ (self, cmd_name, rename_existing_to, msg=None, **kwargs): + if not msg: + msg = cmd_name + + self._cmd_name = cmd_name + self._rename_existing_to = rename_existing_to + self._msg = msg + + gdb.Command.__init__ ( + self, cmd_name, + rename_existing_to=rename_existing_to, + **kwargs) + + def invoke (self, args, from_tty): + print ('py-before: %s' % self._msg) + + # Invoke the original command. + try: + gdb.execute ('%s %s' % (self._rename_existing_to, args), + from_tty=from_tty) + except gdb.error as exc: + print (exc) + + print ('py-after: %s' % self._msg) -- 2.28.0