From: Philippe Waroquiers Date: Mon, 4 May 2020 20:25:24 +0000 (+0200) Subject: Add a selftest that detects a 'corrupted' command tree structure in GDB. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=58e6ac70065131e82e0256f571e5277553096051;p=binutils-gdb.git Add a selftest that detects a 'corrupted' command tree structure in GDB. The GDB data structure that records the GDB commands is made of 'struct cmd_list_element' defined in cli-decode.h. A cmd_list_element has various pointers to other cmd_list_element structures, All these pointers are together building a graph of commands. However, when following the 'next' and '*prefixlist' pointers of cmd_list_element, the structure must better be a tree. If such pointers do not form a tree, then some other elements of cmd_list_element cannot get a correct semantic. In particular, the prefixname has no correct meaning if the same prefix command can be reached via 2 different paths. This commit introduces a selftest that detects (at least some cases of) errors leading to 'next' and '*prefixlist' not giving a tree structure. The new 'command_structure_invariants' selftest detects one single case where the command structure is not a tree: (gdb) maintenance selftest command_structure_invariants Running selftest command_structure_invariants. list 0x56362e204b98 duplicated, reachable via prefix 'show ' and 'info set '. Duplicated list first command is 'ada' Self test failed: self-test failed at ../../classfix/gdb/unittests/command-def-selftests.c:160 Ran 1 unit tests, 1 failed (gdb) This was fixed by the previous commit. 2020-05-15 Philippe Waroquiers * unittests/help-doc-selftests.c: Rename to unittests/command-def-selftests.c * unittests/command-def-selftests.c (help_doc_tests): Update some comments. (command_structure_tests, traverse_command_structure): New namespace and function. (command_structure_invariants_tests): New function. (_initialize_command_def_selftests) Renamed from _initialize_help_doc_selftests, register command_structure_invariants selftest. --- diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 619745e3240..30500f4a742 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,16 @@ +2020-05-15 Philippe Waroquiers + + * unittests/help-doc-selftests.c: Rename to + unittests/command-def-selftests.c + * unittests/command-def-selftests.c (help_doc_tests): Update some + comments. + (command_structure_tests, traverse_command_structure): New namespace + and function. + (command_structure_invariants_tests): New function. + (_initialize_command_def_selftests) Renamed from + _initialize_help_doc_selftests, register command_structure_invariants + selftest. + 2020-05-15 Philippe Waroquiers * cli/cli-cmds.c (_initialize_cli_cmds): Define 'info set' as diff --git a/gdb/Makefile.in b/gdb/Makefile.in index e3ce6a285f7..32d0eee7c63 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -427,13 +427,13 @@ SELFTESTS_SRCS = \ unittests/array-view-selftests.c \ unittests/child-path-selftests.c \ unittests/cli-utils-selftests.c \ + unittests/command-def-selftests.c \ unittests/common-utils-selftests.c \ unittests/copy_bitwise-selftests.c \ unittests/environ-selftests.c \ unittests/filtered_iterator-selftests.c \ unittests/format_pieces-selftests.c \ unittests/function-view-selftests.c \ - unittests/help-doc-selftests.c \ unittests/lookup_name_info-selftests.c \ unittests/memory-map-selftests.c \ unittests/memrange-selftests.c \ diff --git a/gdb/unittests/command-def-selftests.c b/gdb/unittests/command-def-selftests.c new file mode 100644 index 00000000000..db70743b3f4 --- /dev/null +++ b/gdb/unittests/command-def-selftests.c @@ -0,0 +1,183 @@ +/* Self tests for GDB command definitions for GDB, the GNU debugger. + + Copyright (C) 2019-2020 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 . */ + +#include "defs.h" +#include "cli/cli-cmds.h" +#include "cli/cli-decode.h" +#include "gdbsupport/selftest.h" + +#include + +namespace selftests { + +/* Verify some invariants of GDB commands documentation. */ + +namespace help_doc_tests { + +static unsigned int nr_failed_invariants; + +/* Report a broken invariant and increments nr_failed_invariants. */ + +static void +broken_doc_invariant (const char *prefix, const char *name, const char *msg) +{ + fprintf_filtered (gdb_stdout, + "help doc broken invariant: command '%s%s' help doc %s\n", + prefix, name, msg); + nr_failed_invariants++; +} + +/* Recursively walk the commandlist structures, and check doc invariants: + - The first line of the doc must end with a '.'. + - the doc must not end with a new line. + If an invariant is not respected, produce a message and increment + nr_failed_invariants. + Note that we do not call SELF_CHECK in this function, as we want + all commands to be checked before making the test fail. */ + +static void +check_doc (struct cmd_list_element *commandlist, const char *prefix) +{ + struct cmd_list_element *c; + + /* Walk through the commands. */ + for (c = commandlist; c; c = c->next) + { + /* Checks the doc has a first line terminated with a '.'. */ + const char *p = c->doc; + + /* Position p on the first LF, or on terminating null byte. */ + while (*p && *p != '\n') + p++; + if (p == c->doc) + broken_doc_invariant + (prefix, c->name, + "is missing the first line terminated with a '.' character"); + else if (*(p-1) != '.') + broken_doc_invariant + (prefix, c->name, + "first line is not terminated with a '.' character"); + + /* Checks the doc is not terminated with a new line. */ + if (c->doc[strlen (c->doc) - 1] == '\n') + broken_doc_invariant + (prefix, c->name, + "has a superfluous trailing end of line"); + + /* Check if this command has subcommands and is not an + abbreviation. We skip checking subcommands of abbreviations + in order to avoid duplicates in the output. */ + if (c->prefixlist != NULL && !c->abbrev_flag) + { + /* Recursively call ourselves on the subcommand list, + passing the right prefix in. */ + check_doc (*c->prefixlist, c->prefixname); + } + } +} + +static void +help_doc_invariants_tests () +{ + nr_failed_invariants = 0; + check_doc (cmdlist, ""); + SELF_CHECK (nr_failed_invariants == 0); +} + +} /* namespace help_doc_tests */ + +/* Verify some invariants of GDB command structure. */ + +namespace command_structure_tests { + +unsigned int nr_duplicates = 0; + +/* A map associating a list with the prefix leading to it. */ + +std::map lists; + +/* Store each command list in lists, associated with the prefix to reach it. A + list must only be found once. */ + +static void +traverse_command_structure (struct cmd_list_element **list, + const char *prefix) +{ + struct cmd_list_element *c; + + auto dupl = lists.find (list); + if (dupl != lists.end ()) + { + fprintf_filtered (gdb_stdout, + "list %p duplicated," + " reachable via prefix '%s' and '%s'." + " Duplicated list first command is '%s'\n", + list, + prefix, dupl->second, + (*list)->name); + nr_duplicates++; + return; + } + + lists.insert ({list, prefix}); + + /* Walk through the commands. */ + for (c = *list; c; c = c->next) + { + /* If this command has subcommands and is not an alias, + traverse the subcommands. */ + if (c->prefixlist != NULL && c->cmd_pointer == nullptr) + { + /* Recursively call ourselves on the subcommand list, + passing the right prefix in. */ + traverse_command_structure (c->prefixlist, c->prefixname); + } + } +} + +/* Verify that a list of commands is present in the tree only once. */ + +static void +command_structure_invariants_tests () +{ + nr_duplicates = 0; + traverse_command_structure (&cmdlist, ""); + + /* Release memory, be ready to be re-run. */ + lists.clear (); + + SELF_CHECK (nr_duplicates == 0); +} + +} + +} /* namespace selftests */ + +void _initialize_command_def_selftests (); +void +_initialize_command_def_selftests () +{ + selftests::register_test + ("help_doc_invariants", + selftests::help_doc_tests::help_doc_invariants_tests); + + selftests::register_test + ("command_structure_invariants", + selftests::command_structure_tests::command_structure_invariants_tests); +} diff --git a/gdb/unittests/help-doc-selftests.c b/gdb/unittests/help-doc-selftests.c deleted file mode 100644 index 16ffc4f9907..00000000000 --- a/gdb/unittests/help-doc-selftests.c +++ /dev/null @@ -1,108 +0,0 @@ -/* Self tests for help doc for GDB, the GNU debugger. - - Copyright (C) 2019-2020 Free Software Foundation, Inc. - - This file is part of GDB. - - 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 . */ - -#include "defs.h" -#include "cli/cli-cmds.h" -#include "cli/cli-decode.h" -#include "gdbsupport/selftest.h" - -namespace selftests { -namespace help_doc_tests { - -static unsigned int nr_failed_invariants; - -/* Report a broken invariant and increments nr_failed_invariants. */ - -static void -broken_doc_invariant (const char *prefix, const char *name, const char *msg) -{ - fprintf_filtered (gdb_stdout, - "help doc broken invariant: command '%s%s' help doc %s\n", - prefix, name, msg); - nr_failed_invariants++; -} - -/* Recursively walk the commandlist structures, and check doc invariants: - - The first line of the doc must end with a '.'. - - the doc must not end with a new line. - If an invariant is not respected, produce a message and increment - nr_failed_invariants. - Note that we do not call SELF_CHECK in this function, as we want - all commands to be checked before making the test fail. */ - -static void -check_doc (struct cmd_list_element *commandlist, const char *prefix) -{ - struct cmd_list_element *c; - - /* Walk through the commands. */ - for (c = commandlist; c; c = c->next) - { - /* Checks the doc has a first line terminated with a '.'. */ - const char *p = c->doc; - - /* Position p on the first LF, or on terminating null byte. */ - while (*p && *p != '\n') - p++; - if (p == c->doc) - broken_doc_invariant - (prefix, c->name, - "is missing the first line terminated with a '.' character"); - else if (*(p-1) != '.') - broken_doc_invariant - (prefix, c->name, - "first line is not terminated with a '.' character"); - - /* Checks the doc is not terminated with a new line. */ - if (c->doc[strlen (c->doc) - 1] == '\n') - broken_doc_invariant - (prefix, c->name, - "has a superfluous trailing end of line"); - - /* Check if this command has subcommands and is not an - abbreviation. We skip checking subcommands of abbreviations - in order to avoid duplicates in the output. */ - if (c->prefixlist != NULL && !c->abbrev_flag) - { - /* Recursively call ourselves on the subcommand list, - passing the right prefix in. */ - check_doc (*c->prefixlist, c->prefixname); - } - } -} - -static void -help_doc_invariants_tests () -{ - nr_failed_invariants = 0; - check_doc (cmdlist, ""); - SELF_CHECK (nr_failed_invariants == 0); -} - -} /* namespace help_doc_tests */ -} /* namespace selftests */ - -void _initialize_help_doc_selftests (); -void -_initialize_help_doc_selftests () -{ - selftests::register_test - ("help_doc_invariants", - selftests::help_doc_tests::help_doc_invariants_tests); -}