gdb/python: generalize serialize_mi_result()

This commit generalizes serialize_mi_result() to make usable in
different contexts than printing result of custom MI command.

To do so, the check whether passed Python object is a dictionary has been
moved to the caller - at the very least, different uses require different
error messages.  Also it has been renamed to serialize_mi_results() to better
match GDB/MI output syntax (see corresponding section in documentation,
in particular rules 'result-record' and 'async-output'.

Since it is now more generic function, it has been moved to py-mi.c.

This is a preparation for implementing Python support for sending custom
MI async events.

Approved-By: Andrew Burgess <aburgess@redhat.com>
This commit is contained in:
Jan Vrany
2023-10-10 11:22:56 +01:00
parent 1fb3cdd87e
commit 80a3485f81
3 changed files with 181 additions and 176 deletions

View File

@@ -296,3 +296,162 @@ gdbpy_execute_mi_command (PyObject *self, PyObject *args, PyObject *kw)
return uiout.result ();
}
/* Convert KEY_OBJ into a string that can be used as a field name in MI
output. KEY_OBJ must be a Python string object, and must only contain
characters suitable for use as an MI field name.
If KEY_OBJ is not a string, or if KEY_OBJ contains invalid characters,
then an error is thrown. Otherwise, KEY_OBJ is converted to a string
and returned. */
static gdb::unique_xmalloc_ptr<char>
py_object_to_mi_key (PyObject *key_obj)
{
/* The key must be a string. */
if (!PyUnicode_Check (key_obj))
{
gdbpy_ref<> key_repr (PyObject_Repr (key_obj));
gdb::unique_xmalloc_ptr<char> key_repr_string;
if (key_repr != nullptr)
key_repr_string = python_string_to_target_string (key_repr.get ());
if (key_repr_string == nullptr)
gdbpy_handle_exception ();
gdbpy_error (_("non-string object used as key: %s"),
key_repr_string.get ());
}
gdb::unique_xmalloc_ptr<char> key_string
= python_string_to_target_string (key_obj);
if (key_string == nullptr)
gdbpy_handle_exception ();
/* Predicate function, returns true if NAME is a valid field name for use
in MI result output, otherwise, returns false. */
auto is_valid_key_name = [] (const char *name) -> bool
{
gdb_assert (name != nullptr);
if (*name == '\0' || !isalpha (*name))
return false;
for (; *name != '\0'; ++name)
if (!isalnum (*name) && *name != '_' && *name != '-')
return false;
return true;
};
if (!is_valid_key_name (key_string.get ()))
{
if (*key_string.get () == '\0')
gdbpy_error (_("Invalid empty key in MI result"));
else
gdbpy_error (_("Invalid key in MI result: %s"), key_string.get ());
}
return key_string;
}
/* Serialize RESULT and print it in MI format to the current_uiout.
FIELD_NAME is used as the name of this result field.
RESULT can be a dictionary, a sequence, an iterator, or an object that
can be converted to a string, these are converted to the matching MI
output format (dictionaries as tuples, sequences and iterators as lists,
and strings as named fields).
If anything goes wrong while formatting the output then an error is
thrown.
This function is the recursive inner core of serialize_mi_result, and
should only be called from that function. */
static void
serialize_mi_result_1 (PyObject *result, const char *field_name)
{
struct ui_out *uiout = current_uiout;
if (PyDict_Check (result))
{
PyObject *key, *value;
Py_ssize_t pos = 0;
ui_out_emit_tuple tuple_emitter (uiout, field_name);
while (PyDict_Next (result, &pos, &key, &value))
{
gdb::unique_xmalloc_ptr<char> key_string
(py_object_to_mi_key (key));
serialize_mi_result_1 (value, key_string.get ());
}
}
else if (PySequence_Check (result) && !PyUnicode_Check (result))
{
ui_out_emit_list list_emitter (uiout, field_name);
Py_ssize_t len = PySequence_Size (result);
if (len == -1)
gdbpy_handle_exception ();
for (Py_ssize_t i = 0; i < len; ++i)
{
gdbpy_ref<> item (PySequence_ITEM (result, i));
if (item == nullptr)
gdbpy_handle_exception ();
serialize_mi_result_1 (item.get (), nullptr);
}
}
else if (PyIter_Check (result))
{
gdbpy_ref<> item;
ui_out_emit_list list_emitter (uiout, field_name);
while (true)
{
item.reset (PyIter_Next (result));
if (item == nullptr)
{
if (PyErr_Occurred () != nullptr)
gdbpy_handle_exception ();
break;
}
serialize_mi_result_1 (item.get (), nullptr);
}
}
else
{
if (PyLong_Check (result))
{
int overflow = 0;
gdb_py_longest val = gdb_py_long_as_long_and_overflow (result,
&overflow);
if (PyErr_Occurred () != nullptr)
gdbpy_handle_exception ();
if (overflow == 0)
{
uiout->field_signed (field_name, val);
return;
}
/* Fall through to the string case on overflow. */
}
gdb::unique_xmalloc_ptr<char> string (gdbpy_obj_to_string (result));
if (string == nullptr)
gdbpy_handle_exception ();
uiout->field_string (field_name, string.get ());
}
}
/* See python-internal.h. */
void
serialize_mi_results (PyObject *results)
{
gdb_assert (PyDict_Check (results));
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next (results, &pos, &key, &value))
{
gdb::unique_xmalloc_ptr<char> key_string
(py_object_to_mi_key (key));
serialize_mi_result_1 (value, key_string.get ());
}
}

View File

@@ -173,178 +173,6 @@ extern PyTypeObject micmdpy_object_type
static PyObject *invoke_cst;
/* Convert KEY_OBJ into a string that can be used as a field name in MI
output. KEY_OBJ must be a Python string object, and must only contain
characters suitable for use as an MI field name.
If KEY_OBJ is not a string, or if KEY_OBJ contains invalid characters,
then an error is thrown. Otherwise, KEY_OBJ is converted to a string
and returned. */
static gdb::unique_xmalloc_ptr<char>
py_object_to_mi_key (PyObject *key_obj)
{
/* The key must be a string. */
if (!PyUnicode_Check (key_obj))
{
gdbpy_ref<> key_repr (PyObject_Repr (key_obj));
gdb::unique_xmalloc_ptr<char> key_repr_string;
if (key_repr != nullptr)
key_repr_string = python_string_to_target_string (key_repr.get ());
if (key_repr_string == nullptr)
gdbpy_handle_exception ();
gdbpy_error (_("non-string object used as key: %s"),
key_repr_string.get ());
}
gdb::unique_xmalloc_ptr<char> key_string
= python_string_to_target_string (key_obj);
if (key_string == nullptr)
gdbpy_handle_exception ();
/* Predicate function, returns true if NAME is a valid field name for use
in MI result output, otherwise, returns false. */
auto is_valid_key_name = [] (const char *name) -> bool
{
gdb_assert (name != nullptr);
if (*name == '\0' || !isalpha (*name))
return false;
for (; *name != '\0'; ++name)
if (!isalnum (*name) && *name != '_' && *name != '-')
return false;
return true;
};
if (!is_valid_key_name (key_string.get ()))
{
if (*key_string.get () == '\0')
gdbpy_error (_("Invalid empty key in MI result"));
else
gdbpy_error (_("Invalid key in MI result: %s"), key_string.get ());
}
return key_string;
}
/* Serialize RESULT and print it in MI format to the current_uiout.
FIELD_NAME is used as the name of this result field.
RESULT can be a dictionary, a sequence, an iterator, or an object that
can be converted to a string, these are converted to the matching MI
output format (dictionaries as tuples, sequences and iterators as lists,
and strings as named fields).
If anything goes wrong while formatting the output then an error is
thrown.
This function is the recursive inner core of serialize_mi_result, and
should only be called from that function. */
static void
serialize_mi_result_1 (PyObject *result, const char *field_name)
{
struct ui_out *uiout = current_uiout;
if (PyDict_Check (result))
{
PyObject *key, *value;
Py_ssize_t pos = 0;
ui_out_emit_tuple tuple_emitter (uiout, field_name);
while (PyDict_Next (result, &pos, &key, &value))
{
gdb::unique_xmalloc_ptr<char> key_string
(py_object_to_mi_key (key));
serialize_mi_result_1 (value, key_string.get ());
}
}
else if (PySequence_Check (result) && !PyUnicode_Check (result))
{
ui_out_emit_list list_emitter (uiout, field_name);
Py_ssize_t len = PySequence_Size (result);
if (len == -1)
gdbpy_handle_exception ();
for (Py_ssize_t i = 0; i < len; ++i)
{
gdbpy_ref<> item (PySequence_ITEM (result, i));
if (item == nullptr)
gdbpy_handle_exception ();
serialize_mi_result_1 (item.get (), nullptr);
}
}
else if (PyIter_Check (result))
{
gdbpy_ref<> item;
ui_out_emit_list list_emitter (uiout, field_name);
while (true)
{
item.reset (PyIter_Next (result));
if (item == nullptr)
{
if (PyErr_Occurred () != nullptr)
gdbpy_handle_exception ();
break;
}
serialize_mi_result_1 (item.get (), nullptr);
}
}
else
{
if (PyLong_Check (result))
{
int overflow = 0;
gdb_py_longest val = gdb_py_long_as_long_and_overflow (result,
&overflow);
if (PyErr_Occurred () != nullptr)
gdbpy_handle_exception ();
if (overflow == 0)
{
uiout->field_signed (field_name, val);
return;
}
/* Fall through to the string case on overflow. */
}
gdb::unique_xmalloc_ptr<char> string (gdbpy_obj_to_string (result));
if (string == nullptr)
gdbpy_handle_exception ();
uiout->field_string (field_name, string.get ());
}
}
/* Serialize RESULT and print it in MI format to the current_uiout.
This function handles the top-level result initially returned from the
invoke method of the Python command implementation. At the top-level
the result must be a dictionary. The values within this dictionary can
be a wider range of types. Handling the values of the top-level
dictionary is done by serialize_mi_result_1, see that function for more
details.
If anything goes wrong while parsing and printing the MI output then an
error is thrown. */
static void
serialize_mi_result (PyObject *result)
{
/* At the top-level, the result must be a dictionary. */
if (!PyDict_Check (result))
gdbpy_error (_("Result from invoke must be a dictionary"));
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next (result, &pos, &key, &value))
{
gdb::unique_xmalloc_ptr<char> key_string
(py_object_to_mi_key (key));
serialize_mi_result_1 (value, key_string.get ());
}
}
/* Called when the MI command is invoked. PARSE contains the parsed
command line arguments from the user. */
@@ -381,14 +209,19 @@ mi_command_py::invoke (struct mi_parse *parse) const
gdb_assert (this->m_pyobj != nullptr);
gdb_assert (PyErr_Occurred () == nullptr);
gdbpy_ref<> result
gdbpy_ref<> results
(PyObject_CallMethodObjArgs ((PyObject *) this->m_pyobj.get (), invoke_cst,
argobj.get (), nullptr));
if (result == nullptr)
if (results == nullptr)
gdbpy_handle_exception ();
if (result != Py_None)
serialize_mi_result (result.get ());
if (results != Py_None)
{
/* At the top-level, the results must be a dictionary. */
if (!PyDict_Check (results.get ()))
gdbpy_error (_("Result from invoke must be a dictionary"));
serialize_mi_results (results.get ());
}
}
/* See declaration above. */

View File

@@ -486,6 +486,19 @@ struct gdbarch *arch_object_to_gdbarch (PyObject *obj);
extern PyObject *gdbpy_execute_mi_command (PyObject *self, PyObject *args,
PyObject *kw);
/* Serialize RESULTS and print it in MI format to the current_uiout.
This function handles the top-level results passed as a dictionary.
The caller is responsible for ensuring that. The values within this
dictionary can be a wider range of types. Handling the values of the top-level
dictionary is done by serialize_mi_result_1, see that function for more
details.
If anything goes wrong while parsing and printing the MI output then an
error is thrown. */
extern void serialize_mi_results (PyObject *results);
/* Convert Python object OBJ to a program_space pointer. OBJ must be a
gdb.Progspace reference. Return nullptr if the gdb.Progspace is not
valid (see gdb.Progspace.is_valid), otherwise return the program_space