mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2026-08-27 00:26:02 -04:00
Py_TYPE (self)->tp_name is the traditional idiomatic way to get a Python type's fully qualified name. However, in the context of the Python limited API, PyTypeObject is opaque, so the 'tp_name' attribute is no longer accessible. Additionally, retrieving the type of a Python object requires Py_TYPE, which is only available as part of the stable API starting with Python 3.14. This patch increases minimal Python limited API version from 3.11 to 3.14. It also introduces two new helpers to retrieve a type's fully qualified name: gdb_py_tp_name() and gdbpy_py_obj_tp_name(), which extract the fully qualified name from a PyTypeObject and a PyObject, respectively. Ifdefery allows these wrappers to select the appropriate API depending on the Python version and whether the Python limited API is enabled. For any Python version less than 3.13, gdb_py_tp_name() fallbacks using __qualname__ instead. However, the result may differ slightly in some cases, e.g. the module name may be missing. Finally, this patch adapts the existing code to use these wrappers, and adjusts some test expectations to use the fully qualified name (or __qualname__ for versions <= 3.13) where it was not previously used. Note that the corner case where the module name would be missing does not appear in the testsuite. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830 Approved-By: Tom Tromey <tom@tromey.com>
410 lines
10 KiB
C
410 lines
10 KiB
C
/* Python interface to MI commands
|
|
|
|
Copyright (C) 2023-2026 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' || !c_isalpha (*name))
|
|
return false;
|
|
|
|
for (; *name != '\0'; ++name)
|
|
if (!c_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_GetItem (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 (!c_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"),
|
|
gdbpy_py_obj_tp_name (data));
|
|
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;
|
|
}
|