Add a selftest that detects a 'corrupted' command tree structure in GDB.
authorPhilippe Waroquiers <philippe.waroquiers@skynet.be>
Mon, 4 May 2020 20:25:24 +0000 (22:25 +0200)
committerPhilippe Waroquiers <philippe.waroquiers@skynet.be>
Fri, 15 May 2020 20:17:45 +0000 (22:17 +0200)
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  <philippe.waroquiers@skynet.be>

* 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.

gdb/ChangeLog
gdb/Makefile.in
gdb/unittests/command-def-selftests.c [new file with mode: 0644]
gdb/unittests/help-doc-selftests.c [deleted file]

index 619745e32402b692f435482cb1c3159054e99601..30500f4a7427796b60f39f1e20a9fbc308c34607 100644 (file)
@@ -1,3 +1,16 @@
+2020-05-15  Philippe Waroquiers  <philippe.waroquiers@skynet.be>
+
+       * 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  <philippe.waroquiers@skynet.be>
 
        * cli/cli-cmds.c (_initialize_cli_cmds): Define 'info set' as
index e3ce6a285f7ebd93627c6a62cff394e89281e944..32d0eee7c6376d0f82aa671560a8cc9ed55c2a3e 100644 (file)
@@ -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 (file)
index 0000000..db70743
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+#include "cli/cli-cmds.h"
+#include "cli/cli-decode.h"
+#include "gdbsupport/selftest.h"
+
+#include <map>
+
+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<cmd_list_element **, const char *> 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 (file)
index 16ffc4f..0000000
+++ /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 <http://www.gnu.org/licenses/>.  */
-
-#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);
-}