From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 8629 invoked by alias); 22 May 2010 16:54:51 -0000 Received: (qmail 8615 invoked by uid 22791); 22 May 2010 16:54:48 -0000 X-SWARE-Spam-Status: No, hits=-0.9 required=5.0 tests=AWL,BAYES_20,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,SPF_HELO_PASS,TW_CP,TW_MD,TW_RG,T_RP_MATCHES_RCVD X-Spam-Check-By: sourceware.org Received: from smtp-out.google.com (HELO smtp-out.google.com) (74.125.121.35) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Sat, 22 May 2010 16:54:43 +0000 Received: from kpbe13.cbf.corp.google.com (kpbe13.cbf.corp.google.com [172.25.105.77]) by smtp-out.google.com with ESMTP id o4MGsdek016423 for ; Sat, 22 May 2010 09:54:40 -0700 Received: from ruffy.mtv.corp.google.com (ruffy.mtv.corp.google.com [172.18.118.116]) by kpbe13.cbf.corp.google.com with ESMTP id o4MGscuN006966 for ; Sat, 22 May 2010 09:54:38 -0700 Received: by ruffy.mtv.corp.google.com (Postfix, from userid 67641) id 5D5A48439A; Sat, 22 May 2010 09:54:38 -0700 (PDT) To: gdb-patches@sourceware.org Subject: [RFA] new python features: gdb.GdbError, gdb.string_to_argv Message-Id: <20100522165438.5D5A48439A@ruffy.mtv.corp.google.com> Date: Sat, 22 May 2010 17:36:00 -0000 From: dje@google.com (Doug Evans) X-System-Of-Record: true X-IsSubscribed: yes Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2010-05/txt/msg00527.txt.bz2 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 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.