| /* 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; | |
| } |