gdb/python: check for invalid prefixes in Command/Parameter creation

Message ID eb3d899277c79ecd89df420f3cfdbe09ee5c46ca.1744386884.git.aburgess@redhat.com
State New
Headers
Series gdb/python: check for invalid prefixes in Command/Parameter creation |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed

Commit Message

Andrew Burgess April 11, 2025, 3:55 p.m. UTC
  The manual for gdb.Parameter says:

  If NAME consists of multiple words, and no prefix parameter group
  can be found, an exception is raised.

This makes sense; we cannot create a parameter within a prefix group,
if the prefix doesn't exist.  And this almost works, so:

  (gdb) python gdb.Parameter("xxx foo", gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)
  Python Exception <class 'RuntimeError'>: Could not find command prefix xxx.
  Error occurred in Python: Could not find command prefix xxx.

The prefix 'xxx' doesn't exist, and we get an error.  But, if we try
multiple levels of prefix:

  (gdb) python gdb.Parameter("print xxx foo", gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)

This completes without error, however, we didn't get what we were
maybe expecting:

  (gdb) show print xxx foo
  Undefined show print command: "xxx foo".  Try "help show print".

But we did get:

  (gdb) show print foo
  The current value of 'print foo' is "off".

GDB stopped scanning the prefix string at the unknown 'xxx', and just
created the parameter there.  I don't think this makes sense, nor is
it inline with the manual.

An identical problem exists with gdb.Command creation; GDB stops
parsing the prefix at the first unknown prefix, and just creates the
command there.  The manual for gdb.Command says:

  NAME is the name of the command.  If NAME consists of multiple
  words, then the initial words are looked for as prefix commands.
  In this case, if one of the prefix commands does not exist, an
  exception is raised.

So again, the correct action is, I believe, to raise an exception.

The problem is in gdbpy_parse_command_name (python/py-cmd.c), GDB
calls lookup_cmd_1 to look through the prefix string and return the
last prefix group.  If the very first prefix word is invalid then
lookup_cmd_1 returns NULL, and this case is handled.  However, if
there is a valid prefix, followed by an invalid prefix, then
lookup_cmd_1 will return a pointer to the last valid prefix list, and
will update the input argument to point to the start of the invalid
prefix word.  This final case, where the input is left pointing to an
unknown prefix, was previously not handled.

I've fixed gdbpy_parse_command_name, and added tests for command and
parameter creation to cover this case.
---
 gdb/python/py-cmd.c                       |  2 +-
 gdb/testsuite/gdb.python/py-cmd.exp       | 27 ++++++++++++++++
 gdb/testsuite/gdb.python/py-parameter.exp | 38 +++++++++++++++++++++++
 3 files changed, 66 insertions(+), 1 deletion(-)


base-commit: ea0498f46e440199907213d920b7964d4fc38dc5
  

Comments

Tom Tromey April 15, 2025, 9:34 p.m. UTC | #1
>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:

Andrew> So again, the correct action is, I believe, to raise an exception.

Agreed.

The patch looks good to me.  Thanks.
Approved-By: Tom Tromey <tom@tromey.com>

Tom
  

Patch

diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c
index 5d98d03e363..c53138a525f 100644
--- a/gdb/python/py-cmd.c
+++ b/gdb/python/py-cmd.c
@@ -385,7 +385,7 @@  gdbpy_parse_command_name (const char *name,
 
   prefix_text2 = prefix_text.c_str ();
   elt = lookup_cmd_1 (&prefix_text2, *start_list, NULL, NULL, 1);
-  if (elt == NULL || elt == CMD_LIST_AMBIGUOUS)
+  if (elt == nullptr || elt == CMD_LIST_AMBIGUOUS || *prefix_text2 != '\0')
     {
       PyErr_Format (PyExc_RuntimeError, _("Could not find command prefix %s."),
 		    prefix_text.c_str ());
diff --git a/gdb/testsuite/gdb.python/py-cmd.exp b/gdb/testsuite/gdb.python/py-cmd.exp
index f76c17622fa..5ed52a2fc67 100644
--- a/gdb/testsuite/gdb.python/py-cmd.exp
+++ b/gdb/testsuite/gdb.python/py-cmd.exp
@@ -328,4 +328,31 @@  proc_with_prefix test_command_redefining_itself {} {
 	"call command redefining itself 2"
 }
 
+# Try to create commands using unknown prefixes and check GDB gives an
+# error.  There's also a test in here for an ambiguous prefix, which
+# gives the same error.
+proc_with_prefix test_unknown_prefix {} {
+    clean_restart
+
+    gdb_test_no_output "python gdb.Command('foo1', gdb.COMMAND_NONE, prefix=True)"
+    gdb_test_no_output "python gdb.Command('foo cmd', gdb.COMMAND_NONE)"
+
+    foreach prefix { "xxx" "foo xxx" "foo1 xxx" } {
+	gdb_test "python gdb.Command('$prefix cmd', gdb.COMMAND_NONE)" \
+	    [multi_line \
+		 "Python Exception <class 'RuntimeError'>: Could not find command prefix $prefix\\." \
+		 "Error occurred in Python: Could not find command prefix $prefix\\."]
+    }
+
+    gdb_test_no_output "python gdb.Command('foo2', gdb.COMMAND_NONE, prefix=True)"
+
+    foreach prefix { "foo" "foo xxx" "foo1 xxx" "foo2 xxx" } {
+	gdb_test "python gdb.Command('$prefix cmd2', gdb.COMMAND_NONE)" \
+	    [multi_line \
+		 "Python Exception <class 'RuntimeError'>: Could not find command prefix $prefix\\." \
+		 "Error occurred in Python: Could not find command prefix $prefix\\."]
+    }
+}
+
 test_command_redefining_itself
+test_unknown_prefix
diff --git a/gdb/testsuite/gdb.python/py-parameter.exp b/gdb/testsuite/gdb.python/py-parameter.exp
index c15bef15a2b..39a24ca1663 100644
--- a/gdb/testsuite/gdb.python/py-parameter.exp
+++ b/gdb/testsuite/gdb.python/py-parameter.exp
@@ -669,6 +669,43 @@  proc_with_prefix test_ambiguous_parameter {} {
 	"Parameter .* is ambiguous.*Error occurred in Python.*"
     gdb_test "python print(gdb.parameter('test-ambiguous-value-1a'))" \
 	"Could not find parameter.*Error occurred in Python.*"
+
+    # Create command prefixs 'set foo1' and 'show foo1'.
+    gdb_test_no_output "python gdb.Command('set foo1', gdb.COMMAND_NONE, prefix=True)"
+    gdb_test_no_output "python gdb.Command('show foo1', gdb.COMMAND_NONE, prefix=True)"
+
+    # Create a parameter under 'foo1', but use a truncated prefix.  At
+    # this point though, the prefix is not ambiguous.
+    gdb_test_no_output "python gdb.Parameter('foo bar', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)"
+    gdb_test "python print(gdb.parameter('foo1 bar'))" "False"
+
+    # Create another prefix command, similar in name to the first.
+    gdb_test_no_output "python gdb.Command('set foo2', gdb.COMMAND_NONE, prefix=True)"
+    gdb_test_no_output "python gdb.Command('show foo2', gdb.COMMAND_NONE, prefix=True)"
+
+    # An attempt to create a parameter using an ambiguous prefix will give an error.
+    gdb_test "python gdb.Parameter('foo baz', gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" \
+	[multi_line \
+	     "Python Exception <class 'RuntimeError'>: Could not find command prefix foo\\." \
+	     "Error occurred in Python: Could not find command prefix foo\\."]
+}
+
+# Check that creating a gdb.Parameter with an unknown command prefix results in an error.
+proc_with_prefix test_unknown_prefix {} {
+    gdb_test_multiline "create parameter" \
+	"python" "" \
+	"class UnknownPrefixParam(gdb.Parameter):" "" \
+	"   def __init__ (self, name):" "" \
+	"      super().__init__ (name, gdb.COMMAND_NONE, gdb.PARAM_BOOLEAN)" "" \
+	"      self.value = True" "" \
+	"end"
+
+    foreach prefix { "unknown-prefix" "style unknown-prefix" "style disassembler unknown-prefix"} {
+	gdb_test "python UnknownPrefixParam('$prefix new-param')" \
+	    [multi_line \
+		 "Python Exception <class 'RuntimeError'>: Could not find command prefix $prefix\\." \
+		 "Error occurred in Python: Could not find command prefix $prefix\\."]
+    }
 }
 
 test_directories
@@ -685,5 +722,6 @@  test_integer_parameter
 test_throwing_parameter
 test_language
 test_ambiguous_parameter
+test_unknown_prefix
 
 rename py_param_test_maybe_no_output ""