[binutils-gdb] Add wrappers for some Python APIs

Tom Tromey via Gdb-cvs <[email protected]>
Newsgroups gmane.comp.gdb.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=bdd07fae21bf92a40dc599da8e5aad751713c67f

commit bdd07fae21bf92a40dc599da8e5aad751713c67f
Author: Tom Tromey <[email protected]>
Date:   Sun Feb 22 12:29:21 2026 -0700

    Add wrappers for some Python APIs
    
    This adds some new functions that wrap Python APIs.  The wrapping
    follows some proposed rules for Python safety in gdb:
    
    * Functions returning a new reference return gdbpy_ref<>
    
    * Errors are reported via exceptions, not special values
    
    * Functions accepting a stolen reference take a gdbpy_ref<>&&
    
    Acked-By: Tom de Vries <[email protected]>

Diff:
---
 gdb/python/py-wrappers.h     | 361 +++++++++++++++++++++++++++++++++++++++++++
 gdb/python/python-internal.h |   2 +
 2 files changed, 363 insertions(+)

diff --git a/gdb/python/py-wrappers.h b/gdb/python/py-wrappers.h
new file mode 100644
index 00000000000..6c2b5e4d41e
--- /dev/null
+++ b/gdb/python/py-wrappers.h
@@ -0,0 +1,361 @@
+/* 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 */
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 4ca28bb6957..24540046e02 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -1387,4 +1387,6 @@ py_notimplemented ()
 #undef Py_RETURN_FALSE
 #undef Py_RETURN_NOTIMPLEMENTED
 
+#include "py-wrappers.h"
+
 #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.