This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFA] new python features: gdb.GdbError, gdb.string_to_argv


Hi.

While writing some gdb commands in python I needed to be able to
flag users errors without causing a traceback to be printed,
and I wanted a utility to translate the argument string into an argv.

Ok to check in?

2010-05-22  Doug Evans  <dje@google.com>

	Add python gdb.GdbError and gdb.string_to_argv.
	* NEWS: Document them.
	* python/py-cmd.c (cmdpy_function): Don't print a traceback if
	the exception is gdb.GdbError.  Print a second traceback if there's
	an error computing the error message.
	(gdbpy_string_to_argv): New function.
	* python/py-utils.c (gdbpy_obj_to_string): New function.
	(gdbpy_exception_to_string): New function.
	* python/python-internal.h (gdbpy_string_to_argv): Declare.
	(gdbpy_obj_to_string, gdbpy_exception_to_string): Declare.
	(gdbpy_gdberror_exc): Declare.
	* python/python.c (gdbpy_gdberror_exc): New global.
	(_initialize_python): Initialize gdbpy_gdberror_exc and create
	gdb.GdbError.
	(GdbMethods): Add string_to_argv.

	doc/
	* gdb.texinfo (Exception Handling): Document gdb.GdbError.
	(Commands In Python): Document gdb.string_to_argv.

	testsuite/
	* gdb.python/py-cmd.exp: Add tests for gdb.GdbError and
	gdb.string_to_argv.

Index: doc/gdb.texinfo
===================================================================
RCS file: /cvs/src/src/gdb/doc/gdb.texinfo,v
retrieving revision 1.716
diff -u -p -r1.716 gdb.texinfo
--- doc/gdb.texinfo	29 Apr 2010 15:45:56 -0000	1.716
+++ doc/gdb.texinfo	22 May 2010 16:32:53 -0000
@@ -20082,6 +20082,29 @@ message as its value, and the Python cal
 Python statement closest to where the @value{GDBN} error occured as the
 traceback.
 
+When implementing @value{GDBN} commands in Python via @code{gdb.Command},
+it is useful to be able to throw an exception that doesn't cause a
+traceback to be printed.  For example, the user may have invoked the
+command incorrectly.  Use the @code{gdb.GdbError} exception
+to handle this case.  Example:
+
+@smallexample
+(gdb) python
+>class HelloWorld (gdb.Command):
+>  """Greet the whole world."""
+>  def __init__ (self):
+>    super (HelloWorld, self).__init__ ("hello-world", gdb.COMMAND_OBSCURE)
+>  def invoke (self, args, from_tty):
+>    argv = gdb.string_to_argv (args)
+>    if len (argv) != 0:
+>      raise gdb.GdbError ("hello-world takes no arguments")
+>    print "Hello, World!"
+>HelloWorld ()
+>end
+(gdb) hello-world 42
+hello-world takes no arguments
+@end smallexample
+
 @node Values From Inferior
 @subsubsection Values From Inferior
 @cindex values from inferior, with Python
@@ -20773,6 +20796,17 @@ that the command came from elsewhere.
 
 If this method throws an exception, it is turned into a @value{GDBN}
 @code{error} call.  Otherwise, the return value is ignored.
+
+To break @var{argument} up into an argv-like string use
+@code{gdb.string_to_argv}.
+Arguments are separated by spaces and may be quoted.
+Example:
+
+@smallexample
+print gdb.string_to_argv ("1 2\ \\\"3 '4 \"5' \"6 '7\"")
+['1', '2 "3', '4 "5', "6 '7"]
+@end smallexample
+
 @end defmethod
 
 @cindex completion of Python commands
Index: python/py-cmd.c
===================================================================
RCS file: /cvs/src/src/gdb/python/py-cmd.c,v
retrieving revision 1.6
diff -u -p -r1.6 py-cmd.c
--- python/py-cmd.c	17 May 2010 21:23:25 -0000	1.6
+++ python/py-cmd.c	22 May 2010 16:32:53 -0000
@@ -88,6 +88,7 @@ cmdpy_dont_repeat (PyObject *self, PyObj
 
 
 /* Called if the gdb cmd_list_element is destroyed.  */
+
 static void
 cmdpy_destroyer (struct cmd_list_element *self, void *context)
 {
@@ -111,6 +112,7 @@ cmdpy_destroyer (struct cmd_list_element
 }
 
 /* Called by gdb to invoke the command.  */
+
 static void
 cmdpy_function (struct cmd_list_element *command, char *args, int from_tty)
 {
@@ -145,30 +147,51 @@ cmdpy_function (struct cmd_list_element 
 				       ttyobj, NULL);
   Py_DECREF (argobj);
   Py_DECREF (ttyobj);
+
   if (! result)
     {
       PyObject *ptype, *pvalue, *ptraceback;
+      char *msg;
 
       PyErr_Fetch (&ptype, &pvalue, &ptraceback);
 
-      if (pvalue && PyString_Check (pvalue))
-	{
-	  /* Make a temporary copy of the string data.  */
-	  char *s = PyString_AsString (pvalue);
-	  char *copy = alloca (strlen (s) + 1);
+      /* Try to fetch an error message contained within ptype, pvalue.
+	 When fetching the error message we need to make our own copy,
+	 we no longer own ptype, pvalue after the call to PyErr_Restore.  */
 
-	  strcpy (copy, s);
-	  PyErr_Restore (ptype, pvalue, ptraceback);
+      msg = gdbpy_exception_to_string (ptype, pvalue);
+      (void) make_cleanup (xfree, msg);
+
+      if (msg == NULL)
+	{
+	  /* An error occurred computing the string representation of the
+	     error message.  This is rare, but we should inform the user.  */
+	  printf_filtered (_("An error occurred in a Python command\n"
+			     "and then another occurred computing the error message.\n"));
 	  gdbpy_print_stack ();
-	  error (_("Error occurred in Python command: %s"), copy);
 	}
-      else
+
+      /* Don't print the stack for gdb.GdbError exceptions.
+	 It is generally used to flag user errors.
+
+	 We also don't want to print "Error occurred in Python command"
+	 for user errors.  However, a missing message for gdb.GdbError
+	 exceptions is arguably a bug, so we flag it as such.  */
+
+      if (! PyErr_GivenExceptionMatches (ptype, gdbpy_gdberror_exc)
+	  || msg == NULL || *msg == '\0')
 	{
 	  PyErr_Restore (ptype, pvalue, ptraceback);
 	  gdbpy_print_stack ();
-	  error (_("Error occurred in Python command."));
+	  if (msg != NULL && *msg != '\0')
+	    error (_("Error occurred in Python command: %s"), msg);
+	  else
+	    error (_("Error occurred in Python command."));
 	}
+      else
+	error ("%s", msg);
     }
+
   Py_DECREF (result);
   do_cleanups (cleanup);
 }
@@ -589,3 +612,102 @@ static PyTypeObject cmdpy_object_type =
   0,				  /* tp_alloc */
   PyType_GenericNew		  /* tp_new */
 };
+
+
+
+/* Utility to build a buildargv-like result from ARGS.
+   This is based on libiberty/argv.c:buildargv and intentionally
+   behaves identically, and except for documented exceptions,
+   different behaviour is a bug.
+
+   Since this isn't a library routine, we don't handle malloc returning NULL.
+   Instead we just use xmalloc/xrealloc.  */
+
+PyObject *
+gdbpy_string_to_argv (PyObject *self, PyObject *args)
+{
+  PyObject *argp;
+  char *arg;
+  char *copybuf;
+  int squote = 0;
+  int dquote = 0;
+  int bsquote = 0;
+  PyObject *argv;
+  char *input;
+
+  if (!PyArg_ParseTuple (args, "s", &input))
+    return NULL;
+
+  argv = PyList_New (0);
+  copybuf = (char *) alloca (strlen (input) + 1);
+
+  /* Use do{}while to always execute the loop once.  Always return an
+     argv, even for null strings.  */
+
+  do
+    {
+      while (isspace (*input))
+	input++;
+
+      /* Begin scanning arg.  */
+
+      arg = copybuf;
+      while (*input)
+	{
+	  if (isspace (*input) && !squote && !dquote && !bsquote)
+	    break;
+	  else
+	    {
+	      if (bsquote)
+		{
+		  bsquote = 0;
+		  *arg++ = *input;
+		}
+	      else if (*input == '\\')
+		bsquote = 1;
+	      else if (squote)
+		{
+		  if (*input == '\'')
+		    squote = 0;
+		  else
+		    *arg++ = *input;
+		}
+	      else if (dquote)
+		{
+		  if (*input == '"')
+		    dquote = 0;
+		  else
+		    *arg++ = *input;
+		}
+	      else
+		{
+		  if (*input == '\'')
+		    squote = 1;
+		  else if (*input == '"')
+		    dquote = 1;
+		  else
+		    *arg++ = *input;
+		}
+	      input++;
+	    }
+	}
+
+      argp = PyString_FromStringAndSize (copybuf, arg - copybuf);
+      if (argp == NULL
+	  || PyList_Append (argv, argp) < 0)
+	{
+	  if (argp != NULL)
+	    {
+	      Py_DECREF (argp);
+	    }
+	  Py_DECREF (argv);
+	  return NULL;
+	}
+
+      while (isspace (*input))
+	input++;
+    }
+  while (*input);
+
+  return argv;
+}
Index: python/py-utils.c
===================================================================
RCS file: /cvs/src/src/gdb/python/py-utils.c,v
retrieving revision 1.4
diff -u -p -r1.4 py-utils.c
--- python/py-utils.c	17 May 2010 21:23:25 -0000	1.4
+++ python/py-utils.c	22 May 2010 16:32:53 -0000
@@ -222,3 +222,53 @@ gdbpy_is_string (PyObject *obj)
 {
   return PyString_Check (obj) || PyUnicode_Check (obj);
 }
+
+/* Return the string representation of OBJ, i.e., str (obj).
+   Space for the result is malloc'd, the caller must free.
+   If the result is NULL a python error occurred, the caller must clear it.  */
+
+char *
+gdbpy_obj_to_string (PyObject *obj)
+{
+  PyObject *str_obj = PyObject_Str (obj);
+
+  if (str_obj != NULL)
+    {
+      char *msg = xstrdup (PyString_AsString (str_obj));
+
+      Py_DECREF (str_obj);
+      return msg;
+    }
+
+  return NULL;
+}
+
+/* Return the string representation of the exception represented by
+   TYPE, VALUE which is assumed to have been obtained with PyErr_Fetch,
+   i.e., the error indicator is currently clear.
+   Space for the result is malloc'd, the caller must free.
+   If the result is NULL a python error occurred, the caller must clear it.  */
+
+char *
+gdbpy_exception_to_string (PyObject *ptype, PyObject *pvalue)
+{
+  PyObject *str_obj = PyObject_Str (pvalue);
+  char *str;
+
+  /* There are a few cases to consider.
+     For example:
+     pvalue is a string when PyErr_SetString is used.
+     pvalue is not a string when raise "foo" is used, instead it is None
+     and ptype is "foo".
+     So the algorithm we use is to print `str (pvalue)' if it's not
+     None, otherwise we print `str (ptype)'.
+     Using str (aka PyObject_Str) will fetch the error message from
+     gdb.GdbError ("message").  */
+
+  if (pvalue && pvalue != Py_None)
+    str = gdbpy_obj_to_string (pvalue);
+  else
+    str = gdbpy_obj_to_string (ptype);
+
+  return str;
+}
Index: python/python-internal.h
===================================================================
RCS file: /cvs/src/src/gdb/python/python-internal.h,v
retrieving revision 1.26
diff -u -p -r1.26 python-internal.h
--- python/python-internal.h	29 Apr 2010 15:45:56 -0000	1.26
+++ python/python-internal.h	22 May 2010 16:32:53 -0000
@@ -92,6 +92,7 @@ PyObject *gdbpy_block_for_pc (PyObject *
 PyObject *gdbpy_lookup_type (PyObject *self, PyObject *args, PyObject *kw);
 PyObject *gdbpy_create_lazy_string_object (CORE_ADDR address, long length,
 					   const char *encoding, struct type *type);
+PyObject *gdbpy_string_to_argv (PyObject *self, PyObject *args);
 PyObject *gdbpy_get_hook_function (const char *);
 PyObject *gdbpy_parameter (PyObject *self, PyObject *args);
 PyObject *gdbpy_parameter_value (enum var_types type, void *var);
@@ -179,6 +180,9 @@ PyObject *python_string_to_target_python
 char *python_string_to_host_string (PyObject *obj);
 PyObject *target_string_to_unicode (const gdb_byte *str, int length);
 int gdbpy_is_string (PyObject *obj);
+char *gdbpy_obj_to_string (PyObject *obj);
+char *gdbpy_exception_to_string (PyObject *ptype, PyObject *pvalue);
+
 int gdbpy_is_lazy_string (PyObject *result);
 gdb_byte *gdbpy_extract_lazy_string (PyObject *string,
 				     struct type **str_type, 
@@ -197,4 +201,6 @@ extern PyObject *gdbpy_children_cst;
 extern PyObject *gdbpy_to_string_cst;
 extern PyObject *gdbpy_display_hint_cst;
 
+extern PyObject *gdbpy_gdberror_exc;
+
 #endif /* GDB_PYTHON_INTERNAL_H */
Index: python/python.c
===================================================================
RCS file: /cvs/src/src/gdb/python/python.c,v
retrieving revision 1.38
diff -u -p -r1.38 python.c
--- python/python.c	19 May 2010 23:32:24 -0000	1.38
+++ python/python.c	22 May 2010 16:32:53 -0000
@@ -57,6 +57,8 @@ PyObject *gdbpy_children_cst;
 PyObject *gdbpy_display_hint_cst;
 PyObject *gdbpy_doc_cst;
 
+/* The GdbError exception.  */
+PyObject *gdbpy_gdberror_exc;
 
 /* Architecture and language to be used in callbacks from
    the Python interpreter.  */
@@ -655,6 +668,9 @@ Enables or disables printing of Python s
   PyModule_AddStringConstant (gdb_module, "HOST_CONFIG", (char*) host_name);
   PyModule_AddStringConstant (gdb_module, "TARGET_CONFIG", (char*) target_name);
 
+  gdbpy_gdberror_exc = PyErr_NewException ("gdb.GdbError", NULL, NULL);
+  PyModule_AddObject (gdb_module, "GdbError", gdbpy_gdberror_exc);
+
   gdbpy_initialize_auto_load ();
   gdbpy_initialize_values ();
   gdbpy_initialize_frames ();
@@ -771,6 +787,12 @@ Return the name of the current target ch
     "target_wide_charset () -> string.\n\
 Return the name of the current target wide charset." },
 
+  { "string_to_argv", gdbpy_string_to_argv, METH_VARARGS,
+    "string_to_argv (String) -> Array.\n\
+Parse String and return an argv-like array.\n\
+Arguments are separate by spaces and may be quoted."
+  },
+
   { "write", gdbpy_write, METH_VARARGS,
     "Write a string using gdb's filtered stream." },
   { "flush", gdbpy_flush, METH_NOARGS,
Index: testsuite/gdb.python/py-cmd.exp
===================================================================
RCS file: /cvs/src/src/gdb/testsuite/gdb.python/py-cmd.exp,v
retrieving revision 1.3
diff -u -p -r1.3 py-cmd.exp
--- testsuite/gdb.python/py-cmd.exp	24 Feb 2010 11:11:16 -0000	1.3
+++ testsuite/gdb.python/py-cmd.exp	22 May 2010 16:32:53 -0000
@@ -126,3 +126,35 @@ gdb_py_test_multiple "input new subcomma
   "end" ""
 
 gdb_test "info newsubcmd ugh" "newsubcmd output, arg = ugh" "call newsubcmd"
+
+# Test a command that throws gdb.GdbError.
+
+gdb_py_test_multiple "input command to throw error" \
+  "python" "" \
+  "class test_error_cmd (gdb.Command):" "" \
+  "  def __init__ (self):" "" \
+  "    super (test_error_cmd, self).__init__ (\"test_error_cmd\", gdb.COMMAND_OBSCURE)" "" \
+  "  def invoke (self, arg, from_tty):" "" \
+  "    raise gdb.GdbError ('you lose!')" "" \
+  "test_error_cmd ()" "" \
+  "end" ""
+
+gdb_test "test_error_cmd ugh" "you lose!" "call error command"
+
+# Test gdb.string_to_argv.
+
+gdb_test "python print gdb.string_to_argv (\"1 2 3\")" \
+  {\['1', '2', '3'\]} \
+  "string_to_argv (\"1 2 3\")"
+
+gdb_test "python print gdb.string_to_argv (\"'1 2' 3\")" \
+  {\['1 2', '3'\]} \
+  "string_to_argv (\"'1 2' 3\")"
+
+gdb_test "python print gdb.string_to_argv ('\"1 2\" 3')" \
+  {\['1 2', '3'\]} \
+  "string_to_argv ('\"1 2\" 3')"
+
+gdb_test "python print gdb.string_to_argv ('1\\ 2 3')" \
+  {\['1 2', '3'\]} \
+    "string_to_argv ('1\\ 2 3')"
Index: NEWS
===================================================================
RCS file: /cvs/src/src/gdb/NEWS,v
retrieving revision 1.379
diff -u -p -r1.379 NEWS
--- NEWS	30 Apr 2010 07:04:52 -0000	1.379
+++ NEWS	22 May 2010 16:51:50 -0000
@@ -87,7 +87,9 @@ is now deprecated.
    set/show in the CLI.
 
 ** New functions gdb.target_charset, gdb.target_wide_charset,
-   gdb.progspaces, and gdb.current_progspace.
+   gdb.progspaces, gdb.current_progspace, and gdb.string_to_argv.
+
+** New exception gdb.GdbError.
 
 ** Pretty-printers are now also looked up in the current program space.
 


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]