mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-23 10:03:47 +08:00
32af040ef2
Hannes pointed out that gdb.execute_mi() will crash. This patch fixes the bug. Reviewed-By: Guinevere Larsen <guinevere@redhat.com>
411 lines
10 KiB
C
411 lines
10 KiB
C
/* Python interface to MI commands
|
|
|
|
Copyright (C) 2023-2024 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 "python-internal.h"
|
|
#include "py-uiout.h"
|
|
#include "utils.h"
|
|
#include "ui.h"
|
|
#include "interps.h"
|
|
#include "target.h"
|
|
#include "mi/mi-parse.h"
|
|
#include "mi/mi-console.h"
|
|
#include "mi/mi-interp.h"
|
|
|
|
void
|
|
py_ui_out::add_field (const char *name, const gdbpy_ref<> &obj)
|
|
{
|
|
if (obj == nullptr)
|
|
{
|
|
m_error.emplace ();
|
|
return;
|
|
}
|
|
|
|
object_desc &desc = current ();
|
|
if (desc.type == ui_out_type_list)
|
|
{
|
|
if (PyList_Append (desc.obj.get (), obj.get ()) < 0)
|
|
m_error.emplace ();
|
|
}
|
|
else
|
|
{
|
|
if (PyDict_SetItemString (desc.obj.get (), name, obj.get ()) < 0)
|
|
m_error.emplace ();
|
|
}
|
|
}
|
|
|
|
void
|
|
py_ui_out::do_begin (ui_out_type type, const char *id)
|
|
{
|
|
if (m_error.has_value ())
|
|
return;
|
|
|
|
gdbpy_ref<> new_obj (type == ui_out_type_list
|
|
? PyList_New (0)
|
|
: PyDict_New ());
|
|
if (new_obj == nullptr)
|
|
{
|
|
m_error.emplace ();
|
|
return;
|
|
}
|
|
|
|
object_desc new_desc;
|
|
if (id != nullptr)
|
|
new_desc.field_name = id;
|
|
new_desc.obj = std::move (new_obj);
|
|
new_desc.type = type;
|
|
|
|
m_objects.push_back (std::move (new_desc));
|
|
}
|
|
|
|
void
|
|
py_ui_out::do_end (ui_out_type type)
|
|
{
|
|
if (m_error.has_value ())
|
|
return;
|
|
|
|
object_desc new_obj = std::move (current ());
|
|
m_objects.pop_back ();
|
|
add_field (new_obj.field_name.c_str (), new_obj.obj);
|
|
}
|
|
|
|
void
|
|
py_ui_out::do_field_signed (int fldno, int width, ui_align align,
|
|
const char *fldname, LONGEST value,
|
|
const ui_file_style &style)
|
|
{
|
|
if (m_error.has_value ())
|
|
return;
|
|
|
|
gdbpy_ref<> val = gdb_py_object_from_longest (value);
|
|
add_field (fldname, val);
|
|
}
|
|
|
|
void
|
|
py_ui_out::do_field_unsigned (int fldno, int width, ui_align align,
|
|
const char *fldname, ULONGEST value)
|
|
{
|
|
if (m_error.has_value ())
|
|
return;
|
|
|
|
gdbpy_ref<> val = gdb_py_object_from_ulongest (value);
|
|
add_field (fldname, val);
|
|
}
|
|
|
|
void
|
|
py_ui_out::do_field_string (int fldno, int width, ui_align align,
|
|
const char *fldname, const char *string,
|
|
const ui_file_style &style)
|
|
{
|
|
if (m_error.has_value ())
|
|
return;
|
|
|
|
gdbpy_ref<> val = host_string_to_python_string (string);
|
|
add_field (fldname, val);
|
|
}
|
|
|
|
void
|
|
py_ui_out::do_field_fmt (int fldno, int width, ui_align align,
|
|
const char *fldname, const ui_file_style &style,
|
|
const char *format, va_list args)
|
|
{
|
|
if (m_error.has_value ())
|
|
return;
|
|
|
|
std::string str = string_vprintf (format, args);
|
|
do_field_string (fldno, width, align, fldname, str.c_str (), style);
|
|
}
|
|
|
|
/* Implementation of the gdb.execute_mi command. */
|
|
|
|
PyObject *
|
|
gdbpy_execute_mi_command (PyObject *self, PyObject *args, PyObject *kw)
|
|
{
|
|
gdb::unique_xmalloc_ptr<char> mi_command;
|
|
std::vector<gdb::unique_xmalloc_ptr<char>> arg_strings;
|
|
|
|
Py_ssize_t n_args = PyTuple_Size (args);
|
|
if (n_args < 0)
|
|
return nullptr;
|
|
|
|
if (n_args == 0)
|
|
{
|
|
PyErr_SetString (PyExc_TypeError,
|
|
_("gdb.execute_mi requires command argument"));
|
|
return nullptr;
|
|
}
|
|
|
|
for (Py_ssize_t i = 0; i < n_args; ++i)
|
|
{
|
|
/* Note this returns a borrowed reference. */
|
|
PyObject *arg = PyTuple_GetItem (args, i);
|
|
if (arg == nullptr)
|
|
return nullptr;
|
|
gdb::unique_xmalloc_ptr<char> str = python_string_to_host_string (arg);
|
|
if (str == nullptr)
|
|
return nullptr;
|
|
if (i == 0)
|
|
mi_command = std::move (str);
|
|
else
|
|
arg_strings.push_back (std::move (str));
|
|
}
|
|
|
|
py_ui_out uiout;
|
|
|
|
try
|
|
{
|
|
scoped_restore save_uiout = make_scoped_restore (¤t_uiout, &uiout);
|
|
auto parser = std::make_unique<mi_parse> (std::move (mi_command),
|
|
std::move (arg_strings));
|
|
mi_execute_command (parser.get ());
|
|
}
|
|
catch (const gdb_exception &except)
|
|
{
|
|
return gdbpy_handle_gdb_exception (nullptr, except);
|
|
}
|
|
|
|
return uiout.result ().release ();
|
|
}
|
|
|
|
/* 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 ());
|
|
}
|
|
}
|
|
|
|
/* See python-internal.h. */
|
|
|
|
PyObject *
|
|
gdbpy_notify_mi (PyObject *self, PyObject *args, PyObject *kwargs)
|
|
{
|
|
static const char *keywords[] = { "name", "data", nullptr };
|
|
char *name = nullptr;
|
|
PyObject *data = Py_None;
|
|
|
|
if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "s|O", keywords,
|
|
&name, &data))
|
|
return nullptr;
|
|
|
|
/* Validate notification name. */
|
|
const int name_len = strlen (name);
|
|
if (name_len == 0)
|
|
{
|
|
PyErr_SetString (PyExc_ValueError, _("MI notification name is empty."));
|
|
return nullptr;
|
|
}
|
|
for (int i = 0; i < name_len; i++)
|
|
{
|
|
if (!isalnum (name[i]) && name[i] != '-')
|
|
{
|
|
PyErr_Format
|
|
(PyExc_ValueError,
|
|
_("MI notification name contains invalid character: %c."),
|
|
name[i]);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
/* Validate additional data. */
|
|
if (!(data == Py_None || PyDict_Check (data)))
|
|
{
|
|
PyErr_Format
|
|
(PyExc_ValueError,
|
|
_("MI notification data must be either None or a dictionary, not %s"),
|
|
Py_TYPE (data)->tp_name);
|
|
return nullptr;
|
|
}
|
|
|
|
SWITCH_THRU_ALL_UIS ()
|
|
{
|
|
struct mi_interp *mi = as_mi_interp (top_level_interpreter ());
|
|
|
|
if (mi == nullptr)
|
|
continue;
|
|
|
|
target_terminal::scoped_restore_terminal_state term_state;
|
|
target_terminal::ours_for_output ();
|
|
|
|
gdb_printf (mi->event_channel, "%s", name);
|
|
if (data != Py_None)
|
|
{
|
|
ui_out *mi_uiout = mi->interp_ui_out ();
|
|
ui_out_redirect_pop redir (mi_uiout, mi->event_channel);
|
|
scoped_restore restore_uiout
|
|
= make_scoped_restore (¤t_uiout, mi_uiout);
|
|
|
|
serialize_mi_results (data);
|
|
}
|
|
gdb_flush (mi->event_channel);
|
|
}
|
|
|
|
Py_RETURN_NONE;
|
|
}
|