blob: 6c2b5e4d41e986dfdd4d13cfb7fbcc5293a68396 [file]
/* Wrappers for some Python safety.
Copyright (C) 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/>. */
#ifndef GDB_PYTHON_PY_WRAPPERS_H
#define GDB_PYTHON_PY_WRAPPERS_H
#include "py-ref.h"
/* Gdb implements its own wrappers for many Python APIs. This is done
in an attempt to be more safe.
In particular, in gdb:
- APIs returning a new reference will return gdbpy_ref<>. This
makes reference counting errors less likely.
- APIs will throw an exception rather than return a special value
(NULL or -1). This makes error checking simpler.
- APIs requiring a stolen reference take a gdbpy_ref<>&&, to make
reference counting errors less likely.
This file holds the currently-defined wrappers. If new APIs are
needed, the normal approach is to add a wrapper here.
APIs here are named after the underlying Python function, but using
lower case and an "_" at each word break. */
/* The type of exception thrown when the Python exception has been
set. */
struct gdb_python_exception
{
gdb_python_exception ()
{
gdb_assert (PyErr_Occurred ());
}
};
/* Wrapper for PyObject_New. */
template<typename T>
gdbpy_ref<T>
gdbpy_new ()
{
gdbpy_ref<T> result (PyObject_New (T, T::corresponding_object_type));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyBool_FromLong. */
static inline gdbpy_ref<>
gdbpy_bool_from_long (long value)
{
/* This cannot fail. */
return gdbpy_ref<> (PyBool_FromLong (value));
}
/* Wrapper for PyBytes_AsString. */
static inline char *
gdbpy_bytes_as_string (gdbpy_borrowed_ref<> ref)
{
char *result = PyBytes_AsString (ref);
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyBytes_AsStringAndSize. */
static inline void
gdbpy_bytes_as_string_and_size (gdbpy_borrowed_ref<> ref,
char **buffer,
Py_ssize_t *length)
{
if (PyBytes_AsStringAndSize (ref, buffer, length) == -1)
throw gdb_python_exception ();
}
/* Wrapper for PyBytes_FromString. */
static inline gdbpy_ref<>
gdbpy_bytes_from_string (const char *str)
{
gdb_assert (str != nullptr);
gdbpy_ref<> result (PyBytes_FromString (str));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyBytes_FromStringAndSize. */
static inline gdbpy_ref<>
gdbpy_bytes_from_string_and_size (const char *str, Py_ssize_t len)
{
/* Python allows STR==nullptr but it leaves the object
uninitialized, and I think we should avoid this in gdb. */
gdb_assert (str != nullptr);
gdbpy_ref<> result (PyBytes_FromStringAndSize (str, len));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyBytes_Size. */
static inline Py_ssize_t
gdbpy_bytes_size (gdbpy_borrowed_ref<> ref)
{
Py_ssize_t result = PyBytes_Size (ref);
if (result == -1)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyList_New. */
static inline gdbpy_ref<>
gdbpy_new_list (Py_ssize_t len)
{
gdbpy_ref<> result (PyList_New (len));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyList_Append. */
static inline void
gdbpy_list_append (gdbpy_borrowed_ref<> list, gdbpy_borrowed_ref<> val)
{
if (PyList_Append (list, val) < 0)
throw gdb_python_exception ();
}
/* Wrapper for PyDict_New. */
static inline gdbpy_ref<>
gdbpy_new_dict ()
{
gdbpy_ref<> result (PyDict_New ());
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyDict_SetItemString. */
static inline void
gdbpy_dict_set_item_string (gdbpy_borrowed_ref<> dict,
const char *key,
gdbpy_borrowed_ref<> value)
{
if (PyDict_SetItemString (dict, key, value) != 0)
throw gdb_python_exception ();
}
/* Wrapper for PyDict_DelItemString. */
static inline void
gdbpy_dict_del_item_string (gdbpy_borrowed_ref<> dict,
const char *key)
{
if (PyDict_DelItemString (dict, key) == -1)
throw gdb_python_exception ();
}
/* Wrapper for PyDict_GetItemWithError. Note that this returns an
optional borrowed reference -- while it will throw an exception on
error, it will return NULL if the key is not in the dictionary. */
static inline gdbpy_opt_borrowed_ref<>
gdbpy_dict_get_item_with_error (gdbpy_borrowed_ref<> dict,
gdbpy_borrowed_ref<> key)
{
PyObject *result = PyDict_GetItemWithError (dict, key);
if (result == nullptr && PyErr_Occurred ())
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyDict_Keys. */
static inline gdbpy_ref<>
gdbpy_dict_keys (gdbpy_borrowed_ref<> dict)
{
gdbpy_ref<> result (PyDict_Keys (dict));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyUnicode_FromStringAndSize. */
static inline gdbpy_ref<>
gdbpy_unicode_from_string (std::string_view str)
{
gdbpy_ref<> result (PyUnicode_FromStringAndSize (str.data (), str.size ()));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyUnicode_FromFormatV. A template function is used to
avoid issues with throwing across va_end. */
template<typename... Arg>
gdbpy_ref<>
gdbpy_unicode_from_format (const char *fmt, Arg... args)
{
gdbpy_ref<> result (PyUnicode_FromFormat (fmt, args...));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyErr_SetString. This always throws. */
[[noreturn]] static inline void
gdbpy_err_set_string (gdbpy_borrowed_ref<> type, const char *str)
{
PyErr_SetString (type, str);
throw gdb_python_exception ();
}
/* Wrapper for PyErr_Format. This always throws. */
template<typename... Arg>
[[noreturn]] void
gdbpy_err_format (gdbpy_borrowed_ref<> type, const char *fmt, Arg... args)
{
PyErr_Format (type, fmt, args...);
throw gdb_python_exception ();
}
/* Wrapper for gdb_PyArg_ParseTupleAndKeywords. */
template<typename... Arg>
void
gdbpy_arg_parse_tuple_and_keywords (gdbpy_borrowed_ref<> args,
gdbpy_opt_borrowed_ref<> kw,
const char *fmt,
const char **keywords,
Arg... outputs)
{
/* It would be cool if callers could use references to the
out-parameters and also if gdbpy_borrowed_ref could be used for
those. That requires some hairy template metaprogramming
though. */
if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, fmt, keywords, outputs...))
throw gdb_python_exception ();
}
/* Wrapper for PyArg_ParseTuple. */
template<typename... Arg>
void
gdbpy_arg_parse_tuple (gdbpy_borrowed_ref<> param, const char *format,
Arg... args)
{
if (!PyArg_ParseTuple (param, format, args...))
throw gdb_python_exception ();
}
/* Wrapper for PyLong_AsLong. */
static inline long
gdbpy_long_as_long (gdbpy_borrowed_ref<> arg)
{
long result = PyLong_AsLong (arg);
if (result == -1 && PyErr_Occurred ())
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyTuple_New. */
static inline gdbpy_ref<>
gdbpy_tuple_new (Py_ssize_t len)
{
gdbpy_ref<> result (PyTuple_New (len));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyTuple_GetItem. */
static inline gdbpy_borrowed_ref<>
gdbpy_tuple_get_item (gdbpy_borrowed_ref<> tuple, Py_ssize_t pos)
{
PyObject *result = PyTuple_GetItem (tuple, pos);
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PyTuple_SetItem. */
static inline void
gdbpy_tuple_set_item (gdbpy_borrowed_ref<> tuple, Py_ssize_t pos,
gdbpy_ref<> &&item)
{
if (PyTuple_SetItem (tuple, pos, item.release ()) == -1)
throw gdb_python_exception ();
}
/* Wrapper for PyTuple_Size. */
static inline Py_ssize_t
gdbpy_tuple_size (gdbpy_borrowed_ref<> tuple)
{
Py_ssize_t result = PyTuple_Size (tuple);
if (result == -1)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PySequence_Size. */
static inline Py_ssize_t
gdbpy_sequence_size (gdbpy_borrowed_ref<> seq)
{
Py_ssize_t result = PySequence_Size (seq);
if (result == -1)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PySequence_GetItem. */
static inline gdbpy_ref<>
gdbpy_sequence_get_item (gdbpy_borrowed_ref<> seq, Py_ssize_t i)
{
gdbpy_ref<> result (PySequence_GetItem (seq, i));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PySequence_DelItem. */
static inline void
gdbpy_sequence_del_item (gdbpy_borrowed_ref<> seq, Py_ssize_t i)
{
if (PySequence_DelItem (seq, i) == -1)
throw gdb_python_exception ();
}
/* Wrapper for PySequence_List. */
static inline gdbpy_ref<>
gdbpy_sequence_list (gdbpy_borrowed_ref<> seq)
{
gdbpy_ref<> result (PySequence_List (seq));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
/* Wrapper for PySequence_Concat. */
static inline gdbpy_ref<>
gdbpy_sequence_concat (gdbpy_borrowed_ref<> first, gdbpy_borrowed_ref<> second)
{
gdbpy_ref<> result (PySequence_Concat (first, second));
if (result == nullptr)
throw gdb_python_exception ();
return result;
}
#endif /* GDB_PYTHON_PY_WRAPPERS_H */