Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [RFC 0/4] Better Python safety
@ 2026-02-22 19:49 Tom Tromey
  2026-02-22 19:49 ` [RFC 1/4] Add gdbpy_borrowed_ref Tom Tromey
                   ` (7 more replies)
  0 siblings, 8 replies; 18+ messages in thread
From: Tom Tromey @ 2026-02-22 19:49 UTC (permalink / raw)
  To: gdb-patches

This series is a rough draft showing how I think Python safety could
be improved.

The basic idea is to use C++ features: more fully use gdbpy_ref<> to
avoid reference-counting bugs, introduce a new gdbpy_borrowed_ref to
manage borrowed references; throw exceptions on failure rather than
explicit error checks; and finally wrap Python C APIs to enforce these
rules.

This approach also lets us implement Python methods in a more natural
style.  Explicit try/catch when calling gdb APIs is no longer needed,
and methods can simply return the appropriate type.

This series is nowhere near complete, but I did mostly convert
py-arch.c and py-frame.c.  (Discussion of some holes below.)

A nice example of the simplification is shown by
gdbpy_all_architecture_names, which is now just:

gdbpy_ref<>
gdbpy_all_architecture_names (gdbpy_borrowed_ref self)
{
  gdbpy_ref<> list = gdbpy_new_list (0);

  std::vector<const char *> name_list = gdbarch_printable_names ();
  for (const char *name : name_list)
    gdbpy_list_append (list, gdbpy_unicode_from_string (name));

 return list;
}

This shows pretty much all the features: no more error checking and it
returns a gdbpy_ref<> since that is convenient.


I think a few more features could still be added:

* I didn't handle single-argument methods in patch 3.  It's tempting
  to use METH_O but we have at least one that accepts "|s".

* I didn't write wrappers for tp_str / tp_repr ... or the methods in
  PyNumberMethods or PyMappingMethods.  This isn't difficult.

* I think the approach to handling subclasses of PyObject could be
  greatly improved.  In particular I think we could use real C++
  classes by judicious use of placement new and explicit destructor
  calls.  The "corresponding_object_type" stuff you'll see in here is
  sort of a gesture in this direction (though that code also helps
  with some type-safety elsewhere as well).  Essentially I think we
  could end up making new instances with just 'new'.

  I didn't want to really touch this until the stable ABI work related
  to type-instantiation is done.

* Finally gdb is using PyObject_New a lot but my reading while
  researching this series indicates that this is wrong.  However the
  wrongness is still (I guess temporarily) preserved in a wrapper in
  this series.

* There are some comments in the code where I didn't fully convert
  something.  This would just be a temporary state.

Let me know what you think.

Tom


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC 1/4] Add gdbpy_borrowed_ref
  2026-02-22 19:49 [RFC 0/4] Better Python safety Tom Tromey
@ 2026-02-22 19:49 ` Tom Tromey
  2026-02-24  4:57   ` Simon Marchi
  2026-02-22 19:49 ` [RFC 2/4] Add wrappers for some Python APIs Tom Tromey
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 18+ messages in thread
From: Tom Tromey @ 2026-02-22 19:49 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This adds a new gdbpy_borrowed_ref class.  This class is primarily for
code "documentation" purposes -- it makes it clear to the reader that
a given reference is borrowed.  However, it also adds a tiny bit of
safety, in that conversion to gdbpy_ref<> will acquire a new
reference.
---
 gdb/python/py-ref.h | 47 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h
index 0a56436634d..56508996eaa 100644
--- a/gdb/python/py-ref.h
+++ b/gdb/python/py-ref.h
@@ -41,6 +41,53 @@ struct gdbpy_ref_policy
 template<typename T = PyObject> using gdbpy_ref
   = gdb::ref_ptr<T, gdbpy_ref_policy>;
 
+/* A class representing a borrowed reference.
+
+   This is a simple wrapper for a PyObject*.  Aside from documenting
+   what the code does, the main advantage of using this is that
+   conversion to a gdbpy_ref<> is guaranteed to make a new
+   reference.  */
+class gdbpy_borrowed_ref
+{
+public:
+
+  gdbpy_borrowed_ref (PyObject *obj)
+    : m_obj (obj)
+  {
+  }
+
+  template<typename T>
+  gdbpy_borrowed_ref (const gdbpy_ref<T> &ref)
+    : m_obj (ref.get ())
+  {
+  }
+
+  /* Allow a (checked) conversion to any subclass of PyObject.  */
+  template<typename T,
+	   typename = std::is_convertible<T *, PyObject *>>
+  operator T * ()
+  {
+    gdb_assert (PyObject_TypeCheck (m_obj, T::corresponding_object_type));
+    return static_cast<T *> (m_obj);
+  }
+
+  operator PyObject * ()
+  {
+    return m_obj;
+  }
+
+  /* When converting a borrowed reference to a gdbpy_ref<>, a new
+     reference is acquired.  */
+  operator gdbpy_ref<> ()
+  {
+    gdb_assert (m_obj != nullptr);
+    return gdbpy_ref<>::new_reference (m_obj);
+  }
+
+private:
+  PyObject *m_obj;
+};
+
 /* A wrapper class for Python extension objects that have a __dict__ attribute.
 
    Any Python C object extension needing __dict__ should inherit from this
-- 
2.49.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC 2/4] Add wrappers for some Python APIs
  2026-02-22 19:49 [RFC 0/4] Better Python safety Tom Tromey
  2026-02-22 19:49 ` [RFC 1/4] Add gdbpy_borrowed_ref Tom Tromey
@ 2026-02-22 19:49 ` Tom Tromey
  2026-02-22 19:49 ` [RFC 3/4] Add constexpr functions to create PyMethodDef entries Tom Tromey
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: Tom Tromey @ 2026-02-22 19:49 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

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<>&&
  (there aren't any of these yet)
---
 gdb/python/py-wrappers.h | 163 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 163 insertions(+)
 create mode 100644 gdb/python/py-wrappers.h

diff --git a/gdb/python/py-wrappers.h b/gdb/python/py-wrappers.h
new file mode 100644
index 00000000000..6471012a75a
--- /dev/null
+++ b/gdb/python/py-wrappers.h
@@ -0,0 +1,163 @@
+/* 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
+   this more type-safe.
+
+   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 ());
+  }
+};
+
+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;
+}
+
+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;
+}
+
+static inline void
+gdbpy_list_append (gdbpy_borrowed_ref list, gdbpy_borrowed_ref val)
+{
+  if (PyList_Append (list, val) < 0)
+    throw gdb_python_exception ();
+}
+
+static inline gdbpy_ref<>
+gdbpy_new_dict ()
+{
+  gdbpy_ref<> result (PyDict_New ());
+  if (result == nullptr)
+    throw gdb_python_exception ();
+  return result;
+}
+
+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 ();
+}
+
+static inline gdbpy_ref<>
+gdbpy_unicode_from_string (const char *str)
+{
+  gdbpy_ref<> result (PyUnicode_FromString (str));
+  if (result == nullptr)
+    throw gdb_python_exception ();
+  return result;
+}
+
+static inline gdbpy_ref<>
+gdbpy_unicode_from_format (const char *fmt, ...)
+{
+  va_list args;
+  va_start (args, fmt);
+  gdbpy_ref<> result (PyUnicode_FromFormatV (fmt, args));
+  if (result == nullptr)
+    throw gdb_python_exception ();
+  va_end (args);
+  return result;
+}
+
+[[noreturn]] static inline void
+gdbpy_err_set_string (PyObject *type, const char *str)
+{
+  PyErr_SetString (type, str);
+  throw gdb_python_exception ();
+}
+
+/* This is a template because PyErr_FormatV was only added in Python
+   3.5.  */
+template<typename... Arg>
+[[noreturn]] void
+gdbpy_err_format (PyObject *type, const char *fmt, Arg... args)
+{
+  PyErr_Format (type, fmt, std::forward<Arg> (args)...);
+  throw gdb_python_exception ();
+}
+
+template<typename... Arg>
+void
+gdbpy_arg_parse_tuple_and_keywords (gdbpy_borrowed_ref args,
+				    gdbpy_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,
+					std::forward<Arg> (outputs)...))
+    throw gdb_python_exception ();
+}
+
+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;
+}
+
+#endif /* GDB_PYTHON_PY_WRAPPERS_H */
-- 
2.49.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC 3/4] Add constexpr functions to create PyMethodDef entries
  2026-02-22 19:49 [RFC 0/4] Better Python safety Tom Tromey
  2026-02-22 19:49 ` [RFC 1/4] Add gdbpy_borrowed_ref Tom Tromey
  2026-02-22 19:49 ` [RFC 2/4] Add wrappers for some Python APIs Tom Tromey
@ 2026-02-22 19:49 ` Tom Tromey
  2026-02-22 19:49 ` [RFC 4/4] Convert some Python code to new-style Tom Tromey
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: Tom Tromey @ 2026-02-22 19:49 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This adds a couple of new constexpr functions to create PyMethodDef
entries.  This provides a few safety benefits:

* The new-style API approach (see previous patch) is implemented by
  the wrapper.  That is, exceptions are caught here and transformed.

* The implementation functions can now return any reasonable type,
  with automatic conversion by the wrapper.

* The function API and the appropriate METH_* flags are handled
  together, avoiding any possible discrepancy.

This approach also means that we can modify the old rule that gdb
calls must be wrapped in a try/catch -- the try/catch is now provided
by the wrapper function, so the implementation can be written in a
more natural way.

Note this patch is not really complete.  There should be one more
wrapper for case where a method takes a single argument (though we
probably cannot use METH_O unfortunately).

And, it would be nice to share the wrapper implementations.  However
this requires some relatively hairy template metaprogramming.
---
 gdb/python/py-safety.h | 190 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 190 insertions(+)
 create mode 100644 gdb/python/py-safety.h

diff --git a/gdb/python/py-safety.h b/gdb/python/py-safety.h
new file mode 100644
index 00000000000..fd6802468ed
--- /dev/null
+++ b/gdb/python/py-safety.h
@@ -0,0 +1,190 @@
+/* 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_SAFETY_H
+#define GDB_PYTHON_PY_SAFETY_H
+
+#include <type_traits>
+#include "py-ref.h"
+#include "py-wrappers.h"
+
+/* Implementation details of the method-wrapping safety code are put
+   into this namespace, just to emphasize that these shouldn't be used
+   elsewhere.  */
+
+namespace safety_details
+{
+/* Overloads of "result_converter" are used by the safety wrappers to
+   convert a function's real return value to a Python object.  A new
+   reference will always be returned.  */
+
+static inline PyObject *
+result_converter (bool value)
+{
+  if (value)
+    Py_RETURN_TRUE;
+  Py_RETURN_FALSE;
+}
+
+static inline PyObject *
+result_converter (LONGEST value)
+{
+  return gdb_py_object_from_longest (value).release ();
+}
+
+static inline PyObject *
+result_converter (int value)
+{
+  return gdb_py_object_from_longest (value).release ();
+}
+
+static inline PyObject *
+result_converter (ULONGEST value)
+{
+  return gdb_py_object_from_ulongest (value).release ();
+}
+
+static inline PyObject *
+result_converter (const char *value)
+{
+  // FIXME do we actually want PyUnicode_Decode with host_charset here
+  return PyUnicode_FromString (value);
+}
+
+static inline PyObject *
+result_converter (gdbpy_ref<> &&value)
+{
+  return value.release ();
+}
+
+/* This is a template function because using a lambda in wrap_varargs
+   caused compiler errors due to the necessary cast to
+   PyCFunction.  */
+template<auto F>
+PyObject *
+wrapped_for_varargs (PyObject *self, PyObject *args, PyObject *kw)
+{
+  try
+    {
+      gdbpy_borrowed_ref self_arg (self);
+      gdbpy_borrowed_ref args_arg (args);
+      gdbpy_borrowed_ref kw_arg (kw);
+      using result_type = std::invoke_result_t<decltype (F),
+					       gdbpy_borrowed_ref,
+					       gdbpy_borrowed_ref,
+					       gdbpy_borrowed_ref>;
+      if constexpr (std::is_void_v<result_type>)
+	{
+	  F (self_arg, args_arg, kw_arg);
+	  Py_RETURN_NONE;
+	}
+      else
+	return result_converter (F (self_arg, args_arg, kw_arg));
+    }
+  catch (const gdb_python_exception &pye)
+    {
+      gdb_assert (PyErr_Occurred () != nullptr);
+      return nullptr;
+    }
+  catch (const gdb_exception &exc)
+    {
+      return gdbpy_handle_gdb_exception (nullptr, exc);
+    }
+}
+
+} /* namespace safety_details */
+
+/* This is used to create the PyMethodDef for a no-argument method.
+   It takes the underlying implementation function as a template
+   argument, and also arguments for the method name and documentation
+   string.
+
+   The underlying function should accept a single gdbpy_borrowed_ref
+   argument.  This is the 'self' argument.  The function can return
+   any type (see the result_converter overloads); and should throw an
+   exception on error.  If gdb_python_exception is thrown, the Python
+   exception must already have been set.
+*/
+template<auto F>
+constexpr PyMethodDef
+wrap_noargs (std::string_view name, std::string_view doc)
+{
+  using namespace safety_details;
+  return {
+    name.data (),
+    [] (PyObject *self, PyObject *args) -> PyObject *
+    {
+      try
+	{
+	  gdbpy_borrowed_ref self_arg (self);
+	  using result_type = std::invoke_result_t<decltype (F),
+						   gdbpy_borrowed_ref>;
+	  if constexpr (std::is_void_v<result_type>)
+	    {
+	      F (self_arg);
+	      Py_RETURN_NONE;
+	    }
+	  else
+	    return result_converter (F (self_arg));
+	}
+      catch (const gdb_python_exception &pye)
+	{
+	  gdb_assert (PyErr_Occurred () != nullptr);
+	  return nullptr;
+	}
+      catch (const gdb_exception &exc)
+	{
+	  return gdbpy_handle_gdb_exception (nullptr, exc);
+	}
+    },
+    METH_NOARGS,
+    doc.data (),
+  };
+}
+
+/* This is used to create the PyMethodDef for a varargs method.  It
+   takes the underlying implementation function as a template
+   argument, and also arguments for the method name and documentation
+   string.
+
+   The underlying function should accept a three gdbpy_borrowed_ref
+   arguments: the 'self' argument, the arguments, and the keywords.
+   The function can return any type (see the result_converter
+   overloads); and should throw an exception on error.  If
+   gdb_python_exception is thrown, the Python exception must already
+   have been set.
+
+   The gdb policy is that varargs methods must also accept keywords,
+   and this is enforced here.
+*/
+template<auto F>
+constexpr PyMethodDef
+wrap_varargs (std::string_view name, std::string_view doc)
+{
+  using namespace safety_details;
+  return {
+    name.data (),
+    (PyCFunction) wrapped_for_varargs<F>,
+    /* gdb's rule is that varargs should also use keywords.  */
+    METH_VARARGS | METH_KEYWORDS,
+    doc.data (),
+  };
+}
+
+#endif /* GDB_PYTHON_PY_SAFETY_H */
-- 
2.49.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC 4/4] Convert some Python code to new-style
  2026-02-22 19:49 [RFC 0/4] Better Python safety Tom Tromey
                   ` (2 preceding siblings ...)
  2026-02-22 19:49 ` [RFC 3/4] Add constexpr functions to create PyMethodDef entries Tom Tromey
@ 2026-02-22 19:49 ` Tom Tromey
  2026-02-23 20:28 ` [RFC 0/4] Better Python safety Simon Marchi
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: Tom Tromey @ 2026-02-22 19:49 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This converts various spots in the gdb Python layer to use the
new-style 'safety' approach.  py-arch.c and py-frame.c are essentially
completely converted.  Some other spots are touched on an ad hoc
basis.
---
 gdb/python/py-arch.c         | 222 +++++--------
 gdb/python/py-frame.c        | 617 +++++++++++------------------------
 gdb/python/py-registers.c    |  39 +--
 gdb/python/py-tui.c          |  56 ++--
 gdb/python/python-internal.h |  15 +-
 gdb/python/python.c          |  12 +-
 6 files changed, 342 insertions(+), 619 deletions(-)

diff --git a/gdb/python/py-arch.c b/gdb/python/py-arch.c
index f40d7da1763..aa99dc34329 100644
--- a/gdb/python/py-arch.c
+++ b/gdb/python/py-arch.c
@@ -30,18 +30,6 @@ struct arch_object : public PyObject
 static const registry<gdbarch>::key<PyObject, gdb::noop_deleter<PyObject>>
      arch_object_data;
 
-/* Require a valid Architecture.  */
-#define ARCHPY_REQUIRE_VALID(arch_obj, arch)			\
-  do {								\
-    arch = arch_object_to_gdbarch (arch_obj);			\
-    if (arch == NULL)						\
-      {								\
-	PyErr_SetString (PyExc_RuntimeError,			\
-			 _("Architecture is invalid."));	\
-	return NULL;						\
-      }								\
-  } while (0)
-
 extern PyTypeObject arch_object_type;
 
 /* Associates an arch_object with GDBARCH as gdbarch_data via the gdbarch
@@ -72,6 +60,18 @@ arch_object_to_gdbarch (PyObject *obj)
   return py_arch->gdbarch;
 }
 
+/* Return the gdbarch associated with OBJ.  Throws an exception on
+   error.  */
+
+static gdbarch *
+require_arch (gdbpy_borrowed_ref obj)
+{
+  gdbarch *result = arch_object_to_gdbarch (obj);
+  if (result == nullptr)
+    gdbpy_err_set_string (PyExc_RuntimeError, _("Architecture is invalid."));
+  return result;
+}
+
 /* See python-internal.h.  */
 
 bool
@@ -103,16 +103,11 @@ gdbarch_to_arch_object (struct gdbarch *gdbarch)
 /* Implementation of gdb.Architecture.name (self) -> String.
    Returns the name of the architecture as a string value.  */
 
-static PyObject *
-archpy_name (PyObject *self, PyObject *args)
+static const char *
+archpy_name (gdbpy_borrowed_ref self)
 {
-  struct gdbarch *gdbarch = NULL;
-  const char *name;
-
-  ARCHPY_REQUIRE_VALID (self, gdbarch);
-
-  name = (gdbarch_bfd_arch_info (gdbarch))->printable_name;
-  return PyUnicode_FromString (name);
+  gdbarch *gdbarch = require_arch (self);
+  return gdbarch_bfd_arch_info (gdbarch)->printable_name;
 }
 
 /* Implementation of
@@ -121,56 +116,45 @@ archpy_name (PyObject *self, PyObject *args)
    in the list is a Python dict object.
 */
 
-static PyObject *
-archpy_disassemble (PyObject *self, PyObject *args, PyObject *kw)
+static gdbpy_ref<>
+archpy_disassemble (gdbpy_borrowed_ref self, gdbpy_borrowed_ref args,
+		    gdbpy_borrowed_ref kw)
 {
   static const char *keywords[] = { "start_pc", "end_pc", "count", NULL };
   CORE_ADDR start = 0, end = 0;
   CORE_ADDR pc;
   long count = 0, i;
   PyObject *start_obj = nullptr, *end_obj = nullptr, *count_obj = nullptr;
-  struct gdbarch *gdbarch = NULL;
 
-  ARCHPY_REQUIRE_VALID (self, gdbarch);
+  struct gdbarch *gdbarch = require_arch (self);
 
-  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O|OO",
-					keywords, &start_obj, &end_obj,
-					&count_obj))
-    return NULL;
+  gdbpy_arg_parse_tuple_and_keywords (args, kw, "O|OO",
+				      keywords, &start_obj, &end_obj,
+				      &count_obj);
 
   if (get_addr_from_python (start_obj, &start) < 0)
-    return nullptr;
+    throw gdb_python_exception (); /* FIXME */
 
   if (end_obj != nullptr)
     {
       if (get_addr_from_python (end_obj, &end) < 0)
-	return nullptr;
+	throw gdb_python_exception (); /* FIXME */
 
       if (end < start)
-	{
-	  PyErr_SetString (PyExc_ValueError,
-			   _("Argument 'end_pc' should be greater than or "
-			     "equal to the argument 'start_pc'."));
-
-	  return NULL;
-	}
+	gdbpy_err_set_string (PyExc_ValueError,
+			      _("Argument 'end_pc' should be greater than or "
+				"equal to the argument 'start_pc'."));
     }
   if (count_obj)
     {
-      count = PyLong_AsLong (count_obj);
-      if (PyErr_Occurred () || count < 0)
-	{
-	  PyErr_SetString (PyExc_TypeError,
-			   _("Argument 'count' should be an non-negative "
-			     "integer."));
-
-	  return NULL;
-	}
+      count = gdbpy_long_as_long (count_obj);
+      if (count < 0)
+	gdbpy_err_set_string (PyExc_TypeError,
+			      _("Argument 'count' should be an non-negative "
+				"integer."));
     }
 
-  gdbpy_ref<> result_list (PyList_New (0));
-  if (result_list == NULL)
-    return NULL;
+  gdbpy_ref<> result_list = gdbpy_new_list (0);
 
   for (pc = start, i = 0;
        /* All args are specified.  */
@@ -182,68 +166,52 @@ archpy_disassemble (PyObject *self, PyObject *args, PyObject *kw)
        /* Both end_pc and count are not specified.  */
        || (end_obj == NULL && count_obj == NULL && pc == start);)
     {
-      int insn_len = 0;
-      gdbpy_ref<> insn_dict (PyDict_New ());
+      gdbpy_ref<> insn_dict = gdbpy_new_dict ();
 
-      if (insn_dict == NULL)
-	return NULL;
-      if (PyList_Append (result_list.get (), insn_dict.get ()))
-	return NULL;  /* PyList_Append Sets the exception.  */
+      gdbpy_list_append (result_list, insn_dict);
 
       string_file stb;
-
-      try
-	{
-	  insn_len = gdb_print_insn (gdbarch, pc, &stb, NULL);
-	}
-      catch (const gdb_exception &except)
-	{
-	  return gdbpy_handle_gdb_exception (nullptr, except);
-	}
+      int insn_len = gdb_print_insn (gdbarch, pc, &stb, NULL);
 
       gdbpy_ref<> pc_obj = gdb_py_object_from_ulongest (pc);
       if (pc_obj == nullptr)
-	return nullptr;
+	throw gdb_python_exception (); /* FIXME */
 
-      gdbpy_ref<> asm_obj
-	(PyUnicode_FromString (!stb.empty () ? stb.c_str () : "<unknown>"));
-      if (asm_obj == nullptr)
-	return nullptr;
+      gdbpy_ref<> asm_obj = gdbpy_unicode_from_string (!stb.empty ()
+						       ? stb.c_str ()
+						       : "<unknown>");
 
       gdbpy_ref<> len_obj = gdb_py_object_from_longest (insn_len);
       if (len_obj == nullptr)
-	return nullptr;
+	throw gdb_python_exception (); /* FIXME */
 
-      if (PyDict_SetItemString (insn_dict.get (), "addr", pc_obj.get ())
-	  || PyDict_SetItemString (insn_dict.get (), "asm", asm_obj.get ())
-	  || PyDict_SetItemString (insn_dict.get (), "length", len_obj.get ()))
-	return NULL;
+      gdbpy_dict_set_item_string (insn_dict, "addr", pc_obj);
+      gdbpy_dict_set_item_string (insn_dict, "asm", asm_obj);
+      gdbpy_dict_set_item_string (insn_dict, "length", len_obj);
 
       pc += insn_len;
       i++;
     }
 
-  return result_list.release ();
+  return result_list;
 }
 
 /* Implementation of gdb.Architecture.registers (self, reggroup) -> Iterator.
    Returns an iterator over register descriptors for registers in GROUP
    within the architecture SELF.  */
 
-static PyObject *
-archpy_registers (PyObject *self, PyObject *args, PyObject *kw)
+static gdbpy_ref<>
+archpy_registers (gdbpy_borrowed_ref self, gdbpy_borrowed_ref args,
+		  gdbpy_borrowed_ref kw)
 {
   static const char *keywords[] = { "reggroup", NULL };
-  struct gdbarch *gdbarch = NULL;
   const char *group_name = NULL;
 
   /* Parse method arguments.  */
-  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "|s", keywords,
-					&group_name))
-    return NULL;
+  gdbpy_arg_parse_tuple_and_keywords (args, kw, "|s", keywords, &group_name);
 
   /* Extract the gdbarch from the self object.  */
-  ARCHPY_REQUIRE_VALID (self, gdbarch);
+  struct gdbarch *gdbarch = require_arch (self);
 
   return gdbpy_new_register_descriptor_iterator (gdbarch, group_name);
 }
@@ -252,35 +220,30 @@ archpy_registers (PyObject *self, PyObject *args, PyObject *kw)
    Returns an iterator that will give up all valid register groups in the
    architecture SELF.  */
 
-static PyObject *
-archpy_register_groups (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+archpy_register_groups (gdbpy_borrowed_ref self)
 {
-  struct gdbarch *gdbarch = NULL;
-
-  /* Extract the gdbarch from the self object.  */
-  ARCHPY_REQUIRE_VALID (self, gdbarch);
+  struct gdbarch *gdbarch = require_arch (self);
   return gdbpy_new_reggroup_iterator (gdbarch);
 }
 
 /* Implementation of gdb.integer_type.  */
-static PyObject *
-archpy_integer_type (PyObject *self, PyObject *args, PyObject *kw)
+static gdbpy_ref<>
+archpy_integer_type (gdbpy_borrowed_ref self, gdbpy_borrowed_ref args,
+		     gdbpy_borrowed_ref kw)
 {
   static const char *keywords[] = { "size", "signed", NULL };
   int size;
   PyObject *is_signed_obj = Py_True;
 
-  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "i|O!", keywords,
-					&size,
-					&PyBool_Type, &is_signed_obj))
-    return nullptr;
+  gdbpy_arg_parse_tuple_and_keywords (args, kw, "i|O!", keywords, &size,
+				      &PyBool_Type, &is_signed_obj);
 
   /* Assume signed by default.  */
   gdb_assert (PyBool_Check (is_signed_obj));
   bool is_signed = is_signed_obj == Py_True;
 
-  struct gdbarch *gdbarch;
-  ARCHPY_REQUIRE_VALID (self, gdbarch);
+  struct gdbarch *gdbarch = require_arch (self);
 
   const struct builtin_type *builtins = builtin_type (gdbarch);
   struct type *type = nullptr;
@@ -309,22 +272,19 @@ archpy_integer_type (PyObject *self, PyObject *args, PyObject *kw)
       break;
 
     default:
-      PyErr_SetString (PyExc_ValueError,
-		       _("no integer type of that size is available"));
-      return nullptr;
+      gdbpy_err_set_string (PyExc_ValueError,
+			    _("no integer type of that size is available"));
     }
 
-  return type_to_type_object (type).release ();
+  return type_to_type_object (type);
 }
 
 /* Implementation of gdb.void_type.  */
-static PyObject *
-archpy_void_type (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+archpy_void_type (gdbpy_borrowed_ref self)
 {
-  struct gdbarch *gdbarch;
-  ARCHPY_REQUIRE_VALID (self, gdbarch);
-
-  return type_to_type_object (builtin_type (gdbarch)->builtin_void).release ();
+  struct gdbarch *gdbarch = require_arch (self);
+  return type_to_type_object (builtin_type (gdbarch)->builtin_void);
 }
 
 /* __repr__ implementation for gdb.Architecture.  */
@@ -345,24 +305,16 @@ archpy_repr (PyObject *self)
 /* Implementation of gdb.architecture_names().  Return a list of all the
    BFD architecture names that GDB understands.  */
 
-PyObject *
-gdbpy_all_architecture_names (PyObject *self, PyObject *args)
+gdbpy_ref<>
+gdbpy_all_architecture_names (gdbpy_borrowed_ref self)
 {
-  gdbpy_ref<> list (PyList_New (0));
-  if (list == nullptr)
-    return nullptr;
+  gdbpy_ref<> list = gdbpy_new_list (0);
 
   std::vector<const char *> name_list = gdbarch_printable_names ();
   for (const char *name : name_list)
-    {
-      gdbpy_ref <> py_name (PyUnicode_FromString (name));
-      if (py_name == nullptr)
-	return nullptr;
-      if (PyList_Append (list.get (), py_name.get ()) < 0)
-	return nullptr;
-    }
+    gdbpy_list_append (list, gdbpy_unicode_from_string (name));
 
- return list.release ();
+ return list;
 }
 
 /* Initializes the Architecture class in the gdb module.  */
@@ -379,32 +331,26 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_arch);
 \f
 
 static PyMethodDef arch_object_methods [] = {
-  { "name", archpy_name, METH_NOARGS,
-    "name () -> String.\n\
-Return the name of the architecture as a string value." },
-  { "disassemble", (PyCFunction) archpy_disassemble,
-    METH_VARARGS | METH_KEYWORDS,
+  wrap_noargs<archpy_name> ("name", "name () -> String.\n\
+Return the name of the architecture as a string value."),
+  wrap_varargs<archpy_disassemble> ("disassemble",
     "disassemble (start_pc [, end_pc [, count]]) -> List.\n\
 Return a list of at most COUNT disassembled instructions from START_PC to\n\
-END_PC." },
-  { "integer_type", (PyCFunction) archpy_integer_type,
-    METH_VARARGS | METH_KEYWORDS,
+END_PC."),
+  wrap_varargs<archpy_integer_type> ("integer_type",
     "integer_type (size [, signed]) -> type\n\
 Return an integer Type corresponding to the given bitsize and signed-ness.\n\
-If not specified, the type defaults to signed." },
-  { "void_type", (PyCFunction) archpy_void_type,
-    METH_NOARGS,
+If not specified, the type defaults to signed."),
+  wrap_noargs<archpy_void_type> ("void_type",
     "void_type () -> type\n\
-Return a void Type." },
-  { "registers", (PyCFunction) archpy_registers,
-    METH_VARARGS | METH_KEYWORDS,
+Return a void Type."),
+  wrap_varargs<archpy_registers> ("registers",
     "registers ([ group-name ]) -> Iterator.\n\
 Return an iterator of register descriptors for the registers in register\n\
-group GROUP-NAME." },
-  { "register_groups", archpy_register_groups,
-    METH_NOARGS,
+group GROUP-NAME."),
+  wrap_noargs<archpy_register_groups> ("register_groups",
     "register_groups () -> Iterator.\n\
-Return an iterator over all of the register groups in this architecture." },
+Return an iterator over all of the register groups in this architecture."),
   {NULL}  /* Sentinel */
 };
 
diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index 592a1d5a2b5..247b3419b9a 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -42,16 +42,9 @@ struct frame_object : public PyObject
      ID as the  previous frame).  Whenever get_prev_frame returns NULL, we
      record the frame_id of the next frame and set FRAME_ID_IS_NEXT to 1.  */
   int frame_id_is_next;
-};
 
-/* Require a valid frame.  This must be called inside a TRY_CATCH, or
-   another context in which a gdb exception is allowed.  */
-#define FRAPY_REQUIRE_VALID(frame_obj, frame)		\
-    do {						\
-      frame = frame_object_to_frame_info (frame_obj);	\
-      if (frame == NULL)				\
-	error (_("Frame is invalid."));			\
-    } while (0)
+  static PyTypeObject *corresponding_object_type;
+};
 
 /* Returns the frame_info object corresponding to the given Python Frame
    object.  If the frame doesn't exist anymore (the frame id doesn't
@@ -73,6 +66,18 @@ frame_object_to_frame_info (PyObject *obj)
   return frame;
 }
 
+/* Return the frame associated with OBJ.  Throws an exception on
+   error.  */
+
+static frame_info_ptr
+require_frame (gdbpy_borrowed_ref obj)
+{
+  frame_info_ptr frame = frame_object_to_frame_info (obj);
+  if (frame == nullptr)
+    gdbpy_err_format (PyExc_RuntimeError, _("Frame is invalid."));
+  return frame;
+}
+
 /* Called by the Python interpreter to obtain string representation
    of the object.  */
 
@@ -104,257 +109,142 @@ frapy_repr (PyObject *self)
    Returns True if the frame corresponding to the frame_id of this
    object still exists in the inferior.  */
 
-static PyObject *
-frapy_is_valid (PyObject *self, PyObject *args)
+static bool
+frapy_is_valid (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame = NULL;
-
-  try
-    {
-      frame = frame_object_to_frame_info (self);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  if (frame == NULL)
-    Py_RETURN_FALSE;
-
-  Py_RETURN_TRUE;
+  return frame_object_to_frame_info (self) != nullptr;
 }
 
 /* Implementation of gdb.Frame.name (self) -> String.
    Returns the name of the function corresponding to this frame.  */
 
-static PyObject *
-frapy_name (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+frapy_name (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame;
-  gdb::unique_xmalloc_ptr<char> name;
+  frame_info_ptr frame = require_frame (self);
   enum language lang;
-  PyObject *result;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
+  gdb::unique_xmalloc_ptr<char> name = find_frame_funname (frame, &lang,
+							   nullptr);
+  if (name != nullptr)
+    return host_string_to_python_string (name.get ());
 
-      name = find_frame_funname (frame, &lang, NULL);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  if (name)
-    {
-      result = PyUnicode_Decode (name.get (), strlen (name.get ()),
-				 host_charset (), NULL);
-    }
-  else
-    {
-      result = Py_None;
-      Py_INCREF (Py_None);
-    }
-
-  return result;
+  return gdbpy_ref<>::new_reference (Py_None);
 }
 
 /* Implementation of gdb.Frame.type (self) -> Integer.
    Returns the frame type, namely one of the gdb.*_FRAME constants.  */
 
-static PyObject *
-frapy_type (PyObject *self, PyObject *args)
+static ULONGEST
+frapy_type (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame;
-  enum frame_type type = NORMAL_FRAME;/* Initialize to appease gcc warning.  */
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-
-      type = get_frame_type (frame);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  return gdb_py_object_from_longest (type).release ();
+  frame_info_ptr frame = require_frame (self);
+  enum frame_type type = get_frame_type (frame);
+  return type;
 }
 
 /* Implementation of gdb.Frame.architecture (self) -> gdb.Architecture.
    Returns the frame's architecture as a gdb.Architecture object.  */
 
-static PyObject *
-frapy_arch (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+frapy_arch (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame = NULL;    /* Initialize to appease gcc warning.  */
-  frame_object *obj = (frame_object *) self;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  return gdbarch_to_arch_object (obj->gdbarch).release ();
+  require_frame (self);
+  frame_object *obj = self;
+  return gdbarch_to_arch_object (obj->gdbarch);
 }
 
 /* Implementation of gdb.Frame.unwind_stop_reason (self) -> Integer.
    Returns one of the gdb.FRAME_UNWIND_* constants.  */
 
-static PyObject *
-frapy_unwind_stop_reason (PyObject *self, PyObject *args)
+static int
+frapy_unwind_stop_reason (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame = NULL;    /* Initialize to appease gcc warning.  */
-  enum unwind_stop_reason stop_reason;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  stop_reason = get_frame_unwind_stop_reason (frame);
-
-  return gdb_py_object_from_longest (stop_reason).release ();
+  frame_info_ptr frame = require_frame (self);
+  return get_frame_unwind_stop_reason (frame);
 }
 
 /* Implementation of gdb.Frame.pc (self) -> Long.
    Returns the frame's resume address.  */
 
-static PyObject *
-frapy_pc (PyObject *self, PyObject *args)
+static ULONGEST
+frapy_pc (gdbpy_borrowed_ref self)
 {
-  CORE_ADDR pc = 0;	      /* Initialize to appease gcc warning.  */
-  frame_info_ptr frame;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-
-      pc = get_frame_pc (frame);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  return gdb_py_object_from_ulongest (pc).release ();
+  frame_info_ptr frame = require_frame (self);
+  return get_frame_pc (frame);
 }
 
 /* Implementation of gdb.Frame.read_register (self, register) -> gdb.Value.
    Returns the value of a register in this frame.  */
 
-static PyObject *
-frapy_read_register (PyObject *self, PyObject *args, PyObject *kw)
+static gdbpy_ref<>
+frapy_read_register (gdbpy_borrowed_ref self, gdbpy_borrowed_ref args,
+		     gdbpy_borrowed_ref kw)
 {
   PyObject *pyo_reg_id;
-  gdbpy_ref<> result;
 
   static const char *keywords[] = { "register", nullptr };
-  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O", keywords, &pyo_reg_id))
-    return nullptr;
+  gdbpy_arg_parse_tuple_and_keywords (args, kw, "O", keywords, &pyo_reg_id);
 
-  try
-    {
-      scoped_value_mark free_values;
-      frame_info_ptr frame;
-      int regnum;
-
-      FRAPY_REQUIRE_VALID (self, frame);
-
-      if (!gdbpy_parse_register_id (get_frame_arch (frame), pyo_reg_id,
-				    &regnum))
-	return nullptr;
-
-      gdb_assert (regnum >= 0);
-      value *val
-	= value_of_register (regnum, get_next_frame_sentinel_okay (frame));
+  scoped_value_mark free_values;
+  frame_info_ptr frame = require_frame (self);
 
-      if (val == NULL)
-	PyErr_SetString (PyExc_ValueError, _("Can't read register."));
-      else
-	result = value_to_value_object (val);
-    }
-  catch (const gdb_exception &except)
+  int regnum;
+  if (!gdbpy_parse_register_id (get_frame_arch (frame), pyo_reg_id, &regnum))
     {
-      return gdbpy_handle_gdb_exception (nullptr, except);
+      // FIXME future conversion
+      throw gdb_python_exception ();
     }
 
-  return result.release ();
+  gdb_assert (regnum >= 0);
+  value *val
+    = value_of_register (regnum, get_next_frame_sentinel_okay (frame));
+
+  if (val == nullptr)
+    gdbpy_err_set_string (PyExc_ValueError, _("Can't read register."));
+
+  return value_to_value_object (val);
 }
 
 /* Implementation of gdb.Frame.block (self) -> gdb.Block.
    Returns the frame's code block.  */
 
-static PyObject *
-frapy_block (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+frapy_block (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame;
-  const struct block *block = NULL, *fn_block;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-      block = get_frame_block (frame, NULL);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
+  frame_info_ptr frame = require_frame (self);
+  const struct block *block = get_frame_block (frame, nullptr);
 
+  const struct block *fn_block;
   for (fn_block = block;
        fn_block != NULL && fn_block->function () == NULL;
        fn_block = fn_block->superblock ())
     ;
 
   if (block == NULL || fn_block == NULL || fn_block->function () == NULL)
-    {
-      PyErr_SetString (PyExc_RuntimeError,
-		       _("Cannot locate block for frame."));
-      return NULL;
-    }
+    gdbpy_err_set_string (PyExc_RuntimeError,
+			  _("Cannot locate block for frame."));
 
-  return block_to_block_object (block,
-				fn_block->function ()->objfile ()).release ();
+  return block_to_block_object (block, fn_block->function ()->objfile ());
 }
 
 
 /* Implementation of gdb.Frame.function (self) -> gdb.Symbol.
    Returns the symbol for the function corresponding to this frame.  */
 
-static PyObject *
-frapy_function (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+frapy_function (gdbpy_borrowed_ref self)
 {
-  struct symbol *sym = NULL;
-  frame_info_ptr frame;
+  frame_info_ptr frame = require_frame (self);
 
-  try
-    {
-      enum language funlang;
+  struct symbol *sym = nullptr;
+  enum language funlang;
+  gdb::unique_xmalloc_ptr<char> funname
+    = find_frame_funname (frame, &funlang, &sym);
 
-      FRAPY_REQUIRE_VALID (self, frame);
+  if (sym != nullptr)
+    return symbol_to_symbol_object (sym);
 
-      gdb::unique_xmalloc_ptr<char> funname
-	= find_frame_funname (frame, &funlang, &sym);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  if (sym)
-    return symbol_to_symbol_object (sym).release ();
-
-  Py_RETURN_NONE;
+  return gdbpy_ref<>::new_reference (Py_None);
 }
 
 /* Convert a frame_info struct to a Python Frame object.
@@ -400,82 +290,43 @@ frame_info_to_frame_object (const frame_info_ptr &frame)
    Returns the frame immediately older (outer) to this frame, or None if
    there isn't one.  */
 
-static PyObject *
-frapy_older (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+frapy_older (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame, prev = NULL;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-
-      prev = get_prev_frame (frame);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
+  frame_info_ptr frame = require_frame (self);
+  frame_info_ptr prev = get_prev_frame (frame);
 
-  gdbpy_ref<> prev_obj;
   if (prev)
-    prev_obj = frame_info_to_frame_object (prev);
-  else
-    prev_obj = gdbpy_ref<>::new_reference (Py_None);
+    return frame_info_to_frame_object (prev);
 
-  return prev_obj.release ();
+  return gdbpy_ref<>::new_reference (Py_None);
 }
 
 /* Implementation of gdb.Frame.newer (self) -> gdb.Frame.
    Returns the frame immediately newer (inner) to this frame, or None if
    there isn't one.  */
 
-static PyObject *
-frapy_newer (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+frapy_newer (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame, next = NULL;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
+  frame_info_ptr frame = require_frame (self);
+  frame_info_ptr next = get_next_frame (frame);
 
-      next = get_next_frame (frame);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  gdbpy_ref<> next_obj;
   if (next)
-    next_obj = frame_info_to_frame_object (next);
-  else
-    next_obj = gdbpy_ref<>::new_reference (Py_None);
+    return frame_info_to_frame_object (next);
 
-  return next_obj.release ();
+  return gdbpy_ref<>::new_reference (Py_None);
 }
 
 /* Implementation of gdb.Frame.find_sal (self) -> gdb.Symtab_and_line.
    Returns the frame's symtab and line.  */
 
-static PyObject *
-frapy_find_sal (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+frapy_find_sal (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame;
-  gdbpy_ref<> sal_obj;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-
-      symtab_and_line sal = find_frame_sal (frame);
-      sal_obj = symtab_and_line_to_sal_object (sal);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  return sal_obj.release ();
+  frame_info_ptr frame = require_frame (self);
+  symtab_and_line sal = find_frame_sal (frame);
+  return symtab_and_line_to_sal_object (sal);
 }
 
 /* Implementation of gdb.Frame.read_var_value (self, variable,
@@ -485,20 +336,19 @@ frapy_find_sal (PyObject *self, PyObject *args)
    frame).  The variable argument must be a string or an instance of a
    gdb.Symbol.  The block argument must be an instance of gdb.Block.  Returns
    NULL on error, with a python exception set.  */
-static PyObject *
-frapy_read_var (PyObject *self, PyObject *args, PyObject *kw)
+static gdbpy_ref<>
+frapy_read_var (gdbpy_borrowed_ref self, gdbpy_borrowed_ref args,
+		gdbpy_borrowed_ref kw)
 {
-  frame_info_ptr frame;
   PyObject *sym_obj, *block_obj = NULL;
-  struct symbol *var = NULL;	/* gcc-4.3.2 false warning.  */
-  const struct block *block = NULL;
 
   static const char *keywords[] = { "variable", "block", nullptr };
-  if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O|O!", keywords,
-					&sym_obj, &block_object_type,
-					&block_obj))
-    return nullptr;
+  gdbpy_arg_parse_tuple_and_keywords (args, kw, "O|O!", keywords,
+				      &sym_obj, &block_object_type,
+				      &block_obj);
 
+  const struct block *block = NULL;
+  struct symbol *var = NULL;	/* gcc-4.3.2 false warning.  */
   if (PyObject_TypeCheck (sym_obj, &symbol_object_type))
     var = symbol_object_to_symbol (sym_obj);
   else if (gdbpy_is_string (sym_obj))
@@ -506,8 +356,9 @@ frapy_read_var (PyObject *self, PyObject *args, PyObject *kw)
       gdb::unique_xmalloc_ptr<char>
 	var_name (python_string_to_target_string (sym_obj));
 
-      if (!var_name)
-	return NULL;
+      // FIXME future conversion
+      if (var_name == nullptr)
+	throw gdb_python_exception ();
 
       if (block_obj != nullptr)
 	{
@@ -518,183 +369,93 @@ frapy_read_var (PyObject *self, PyObject *args, PyObject *kw)
 	  gdb_assert (block != nullptr);
 	}
 
-      try
-	{
-	  struct block_symbol lookup_sym;
-	  FRAPY_REQUIRE_VALID (self, frame);
-
-	  if (!block)
-	    block = get_frame_block (frame, NULL);
-	  lookup_sym = lookup_symbol (var_name.get (), block,
-				      SEARCH_VFT, nullptr);
-	  var = lookup_sym.symbol;
-	  block = lookup_sym.block;
-	}
-      catch (const gdb_exception &except)
-	{
-	  return gdbpy_handle_gdb_exception (nullptr, except);
-	}
+      frame_info_ptr frame = require_frame (self);
 
-      if (!var)
-	{
-	  PyErr_Format (PyExc_ValueError,
-			_("Variable '%s' not found."), var_name.get ());
+      if (!block)
+	block = get_frame_block (frame, NULL);
+      block_symbol lookup_sym = lookup_symbol (var_name.get (), block,
+					       SEARCH_VFT, nullptr);
+      var = lookup_sym.symbol;
+      block = lookup_sym.block;
 
-	  return NULL;
-	}
+      if (var == nullptr)
+	gdbpy_err_format (PyExc_ValueError,
+			  _("Variable '%s' not found."), var_name.get ());
     }
   else
-    {
-      PyErr_Format (PyExc_TypeError,
-		    _("argument 1 must be gdb.Symbol or str, not %s"),
-		    Py_TYPE (sym_obj)->tp_name);
-      return NULL;
-    }
-
-  gdbpy_ref<> result;
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-
-      scoped_value_mark free_values;
-      struct value *val = read_var_value (var, block, frame);
-      result = value_to_value_object (val);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  return result.release ();
+    gdbpy_err_format (PyExc_TypeError,
+		      _("argument 1 must be gdb.Symbol or str, not %s"),
+		      Py_TYPE (sym_obj)->tp_name);
+
+  frame_info_ptr frame = require_frame (self);
+  scoped_value_mark free_values;
+  struct value *val = read_var_value (var, block, frame);
+  return value_to_value_object (val);
 }
 
 /* Select this frame.  */
 
-static PyObject *
-frapy_select (PyObject *self, PyObject *args)
+static void
+frapy_select (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr fi;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, fi);
-
-      select_frame (fi);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  Py_RETURN_NONE;
+  frame_info_ptr fi = require_frame (self);
+  select_frame (fi);
 }
 
 /* The stack frame level for this frame.  */
 
-static PyObject *
-frapy_level (PyObject *self, PyObject *args)
+static int
+frapy_level (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr fi;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, fi);
-
-      return gdb_py_object_from_longest (frame_relative_level (fi)).release ();
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  Py_RETURN_NONE;
+  frame_info_ptr fi = require_frame (self);
+  return frame_relative_level (fi);
 }
 
 /* The language for this frame.  */
 
-static PyObject *
-frapy_language (PyObject *self, PyObject *args)
+static const char *
+frapy_language (gdbpy_borrowed_ref self)
 {
-  try
-    {
-      frame_info_ptr fi;
-      FRAPY_REQUIRE_VALID (self, fi);
+  frame_info_ptr fi = require_frame (self);
 
-      enum language lang = get_frame_language (fi);
-      const language_defn *lang_def = language_def (lang);
+  enum language lang = get_frame_language (fi);
+  const language_defn *lang_def = language_def (lang);
 
-      return host_string_to_python_string (lang_def->name ()).release ();
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  Py_RETURN_NONE;
+  return lang_def->name ();
 }
 
 /* The static link for this frame.  */
 
-static PyObject *
-frapy_static_link (PyObject *self, PyObject *args)
+static gdbpy_ref<>
+frapy_static_link (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr link;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, link);
-
-      link = frame_follow_static_link (link);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
+  frame_info_ptr link = require_frame (self);
+  link = frame_follow_static_link (link);
 
   if (link == nullptr)
-    Py_RETURN_NONE;
+    return gdbpy_ref<>::new_reference (Py_None);
 
-  return frame_info_to_frame_object (link).release ();
+  return frame_info_to_frame_object (link);
 }
 
 /* Implementation of gdb.newest_frame () -> gdb.Frame.
    Returns the newest frame object.  */
 
-PyObject *
-gdbpy_newest_frame (PyObject *self, PyObject *args)
+gdbpy_ref<>
+gdbpy_newest_frame (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame = NULL;
-
-  try
-    {
-      frame = get_current_frame ();
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  return frame_info_to_frame_object (frame).release ();
+  return frame_info_to_frame_object (get_current_frame ());
 }
 
 /* Implementation of gdb.selected_frame () -> gdb.Frame.
    Returns the selected frame object.  */
 
-PyObject *
-gdbpy_selected_frame (PyObject *self, PyObject *args)
+gdbpy_ref<>
+gdbpy_selected_frame (gdbpy_borrowed_ref self)
 {
-  frame_info_ptr frame = NULL;
-
-  try
-    {
-      frame = get_selected_frame ("No frame is currently selected.");
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  return frame_info_to_frame_object (frame).release ();
+  frame_info_ptr frame
+    = get_selected_frame ("No frame is currently selected.");
+  return frame_info_to_frame_object (frame);
 }
 
 /* Implementation of gdb.stop_reason_string (Integer) -> String.
@@ -788,54 +549,53 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_frames);
 \f
 
 static PyMethodDef frame_object_methods[] = {
-  { "is_valid", frapy_is_valid, METH_NOARGS,
+  wrap_noargs<frapy_is_valid> ("is_valid",
     "is_valid () -> Boolean.\n\
-Return true if this frame is valid, false if not." },
-  { "name", frapy_name, METH_NOARGS,
+Return true if this frame is valid, false if not."),
+  wrap_noargs<frapy_name> ("name",
     "name () -> String.\n\
-Return the function name of the frame, or None if it can't be determined." },
-  { "type", frapy_type, METH_NOARGS,
+Return the function name of the frame, or None if it can't be determined."),
+  wrap_noargs<frapy_type> ("type",
     "type () -> Integer.\n\
-Return the type of the frame." },
-  { "architecture", frapy_arch, METH_NOARGS,
+Return the type of the frame."),
+  wrap_noargs<frapy_arch> ("architecture",
     "architecture () -> gdb.Architecture.\n\
-Return the architecture of the frame." },
-  { "unwind_stop_reason", frapy_unwind_stop_reason, METH_NOARGS,
+Return the architecture of the frame."),
+  wrap_noargs<frapy_unwind_stop_reason> ("unwind_stop_reason",
     "unwind_stop_reason () -> Integer.\n\
-Return the reason why it's not possible to find frames older than this." },
-  { "pc", frapy_pc, METH_NOARGS,
+Return the reason why it's not possible to find frames older than this."),
+  wrap_noargs<frapy_pc> ("pc",
     "pc () -> Long.\n\
-Return the frame's resume address." },
-  { "read_register", (PyCFunction) frapy_read_register,
-    METH_VARARGS | METH_KEYWORDS,
+Return the frame's resume address."),
+  wrap_varargs<frapy_read_register> ("read_register",
     "read_register (register_name) -> gdb.Value\n\
-Return the value of the register in the frame." },
-  { "block", frapy_block, METH_NOARGS,
+Return the value of the register in the frame."),
+  wrap_noargs<frapy_block> ("block",
     "block () -> gdb.Block.\n\
-Return the frame's code block." },
-  { "function", frapy_function, METH_NOARGS,
+Return the frame's code block."),
+  wrap_noargs<frapy_function> ("function",
     "function () -> gdb.Symbol.\n\
-Returns the symbol for the function corresponding to this frame." },
-  { "older", frapy_older, METH_NOARGS,
+Returns the symbol for the function corresponding to this frame."),
+  wrap_noargs<frapy_older> ("older",
     "older () -> gdb.Frame.\n\
-Return the frame that called this frame." },
-  { "newer", frapy_newer, METH_NOARGS,
+Return the frame that called this frame."),
+  wrap_noargs<frapy_newer> ("newer",
     "newer () -> gdb.Frame.\n\
-Return the frame called by this frame." },
-  { "find_sal", frapy_find_sal, METH_NOARGS,
+Return the frame called by this frame."),
+  wrap_noargs<frapy_find_sal> ("find_sal",
     "find_sal () -> gdb.Symtab_and_line.\n\
-Return the frame's symtab and line." },
-  { "read_var", (PyCFunction) frapy_read_var, METH_VARARGS | METH_KEYWORDS,
+Return the frame's symtab and line."),
+  wrap_varargs<frapy_read_var> ("read_var",
     "read_var (variable) -> gdb.Value.\n\
-Return the value of the variable in this frame." },
-  { "select", frapy_select, METH_NOARGS,
-    "Select this frame as the user's current frame." },
-  { "level", frapy_level, METH_NOARGS,
-    "The stack level of this frame." },
-  { "language", frapy_language, METH_NOARGS,
-    "The language of this frame." },
-  { "static_link", frapy_static_link, METH_NOARGS,
-    "The static link of this frame, or None." },
+Return the value of the variable in this frame."),
+  wrap_noargs<frapy_select> ("select",
+    "Select this frame as the user's current frame."),
+  wrap_noargs<frapy_level> ("level",
+    "The stack level of this frame."),
+  wrap_noargs<frapy_language> ("language",
+    "The language of this frame."),
+  wrap_noargs<frapy_static_link> ("static_link",
+    "The static link of this frame, or None."),
   {NULL}  /* Sentinel */
 };
 
@@ -878,3 +638,6 @@ PyTypeObject frame_object_type = {
   0,				  /* tp_init */
   0,				  /* tp_alloc */
 };
+
+PyTypeObject *frame_object::corresponding_object_type
+    = &frame_object_type;
diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
index 44bc53e6a9d..c24154b64d1 100644
--- a/gdb/python/py-registers.c
+++ b/gdb/python/py-registers.c
@@ -43,6 +43,8 @@ struct register_descriptor_iterator_object : public PyObject
 
   /* Pointer back to the architecture we're finding registers for.  */
   struct gdbarch *gdbarch;
+
+  static PyTypeObject *corresponding_object_type;
 };
 
 extern PyTypeObject register_descriptor_iterator_object_type;
@@ -67,6 +69,8 @@ struct reggroup_iterator_object : public PyObject
 
   /* Pointer back to the architecture we're finding registers for.  */
   struct gdbarch *gdbarch;
+
+  static PyTypeObject *corresponding_object_type;
 };
 
 extern PyTypeObject reggroup_iterator_object_type;
@@ -222,20 +226,17 @@ gdbpy_reggroup_iter_next (PyObject *self)
 /* Return a new gdb.RegisterGroupsIterator over all the register groups in
    GDBARCH.  */
 
-PyObject *
+gdbpy_ref<>
 gdbpy_new_reggroup_iterator (struct gdbarch *gdbarch)
 {
   gdb_assert (gdbarch != nullptr);
 
   /* Create a new object and fill in its internal state.  */
-  reggroup_iterator_object *iter
-    = PyObject_New (reggroup_iterator_object,
-		    &reggroup_iterator_object_type);
-  if (iter == NULL)
-    return NULL;
+  gdbpy_ref<reggroup_iterator_object> iter
+    = gdbpy_new<reggroup_iterator_object> ();
   iter->index = 0;
   iter->gdbarch = gdbarch;
-  return (PyObject *) iter;
+  return iter;
 }
 
 /* Create and return a new gdb.RegisterDescriptorIterator object which
@@ -246,7 +247,7 @@ gdbpy_new_reggroup_iterator (struct gdbarch *gdbarch)
 
    This function can return NULL if GROUP_NAME isn't found.  */
 
-PyObject *
+gdbpy_ref<>
 gdbpy_new_register_descriptor_iterator (struct gdbarch *gdbarch,
 					const char *group_name)
 {
@@ -259,25 +260,19 @@ gdbpy_new_register_descriptor_iterator (struct gdbarch *gdbarch,
     {
       grp = reggroup_find (gdbarch, group_name);
       if (grp == NULL)
-	{
-	  PyErr_SetString (PyExc_ValueError,
-			   _("Unknown register group name."));
-	  return NULL;
-	}
+	gdbpy_err_set_string (PyExc_ValueError,
+			      _("Unknown register group name."));
     }
   /* Create a new iterator object initialised for this architecture and
      fill in all of the details.  */
-  register_descriptor_iterator_object *iter
-    = PyObject_New (register_descriptor_iterator_object,
-		    &register_descriptor_iterator_object_type);
-  if (iter == NULL)
-    return NULL;
+  gdbpy_ref<register_descriptor_iterator_object> iter
+    = gdbpy_new<register_descriptor_iterator_object> ();
   iter->regnum = 0;
   iter->gdbarch = gdbarch;
   gdb_assert (grp != NULL);
   iter->reggroup = grp;
 
-  return (PyObject *) iter;
+  return iter;
 }
 
 /* Return a reference to the gdb.RegisterDescriptorIterator object.  */
@@ -477,6 +472,9 @@ PyTypeObject register_descriptor_iterator_object_type = {
   register_descriptor_iterator_object_methods		/*tp_methods */
 };
 
+PyTypeObject *register_descriptor_iterator_object::corresponding_object_type
+  = &register_descriptor_iterator_object_type;
+
 static gdb_PyGetSetDef gdbpy_register_descriptor_getset[] = {
   { "name", gdbpy_register_descriptor_name, NULL,
     "The name of this register.", NULL },
@@ -547,6 +545,9 @@ PyTypeObject reggroup_iterator_object_type = {
   0				  /*tp_methods */
 };
 
+PyTypeObject *reggroup_iterator_object::corresponding_object_type
+  = &reggroup_iterator_object_type;
+
 static gdb_PyGetSetDef gdbpy_reggroup_getset[] = {
   { "name", gdbpy_reggroup_name, NULL,
     "The name of this register group.", NULL },
diff --git a/gdb/python/py-tui.c b/gdb/python/py-tui.c
index be19193770f..d5173656f8d 100644
--- a/gdb/python/py-tui.c
+++ b/gdb/python/py-tui.c
@@ -51,6 +51,8 @@ struct gdbpy_tui_window: public PyObject
 
   /* Return true if this object is valid.  */
   bool is_valid () const;
+
+  static PyTypeObject *corresponding_object_type;
 };
 
 extern PyTypeObject gdbpy_tui_window_object_type;
@@ -435,11 +437,14 @@ gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw)
 
 /* Require that "Window" be a valid window.  */
 
-#define REQUIRE_WINDOW(Window)					\
-    do {							\
-      if (!(Window)->is_valid ())				\
-	return PyErr_Format (PyExc_RuntimeError,		\
-			     _("TUI window is invalid."));	\
+#define REQUIRE_WINDOW(Window)				\
+    do {						\
+      if (!(Window)->is_valid ())			\
+	{						\
+	  PyErr_Format (PyExc_RuntimeError,		\
+			_("TUI window is invalid."));	\
+	  return nullptr;					\
+	}						\
     } while (0)
 
 /* Require that "Window" be a valid window.  */
@@ -456,27 +461,30 @@ gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw)
 
 /* Python function which checks the validity of a TUI window
    object.  */
-static PyObject *
-gdbpy_tui_is_valid (PyObject *self, PyObject *args)
+static bool
+gdbpy_tui_is_valid (gdbpy_borrowed_ref self)
 {
-  gdbpy_tui_window *win = (gdbpy_tui_window *) self;
+  gdbpy_tui_window *win = self;
+  return win->is_valid ();
+}
 
-  if (win->is_valid ())
-    Py_RETURN_TRUE;
-  Py_RETURN_FALSE;
+static void
+require_window (gdbpy_tui_window *win)
+{
+  if (!win->is_valid ())
+    {
+      PyErr_Format (PyExc_RuntimeError, _("TUI window is invalid."));
+      throw gdb_python_exception ();
+    }
 }
 
 /* Python function that erases the TUI window.  */
-static PyObject *
-gdbpy_tui_erase (PyObject *self, PyObject *args)
+static void
+gdbpy_tui_erase (gdbpy_borrowed_ref self)
 {
-  gdbpy_tui_window *win = (gdbpy_tui_window *) self;
-
-  REQUIRE_WINDOW (win);
-
+  gdbpy_tui_window *win = self;
+  require_window (win);
   win->window->erase ();
-
-  Py_RETURN_NONE;
 }
 
 /* Python function that writes some text to a TUI window.  */
@@ -565,11 +573,10 @@ static gdb_PyGetSetDef tui_object_getset[] =
 
 static PyMethodDef tui_object_methods[] =
 {
-  { "is_valid", gdbpy_tui_is_valid, METH_NOARGS,
+  wrap_noargs<gdbpy_tui_is_valid> ("is_valid",
     "is_valid () -> Boolean\n\
-Return true if this TUI window is valid, false if not." },
-  { "erase", gdbpy_tui_erase, METH_NOARGS,
-    "Erase the TUI window." },
+Return true if this TUI window is valid, false if not."),
+  wrap_noargs<gdbpy_tui_erase> ("erase", "Erase the TUI window."),
   { "write", (PyCFunction) gdbpy_tui_write, METH_VARARGS | METH_KEYWORDS,
     "Append a string to the TUI window." },
   { NULL } /* Sentinel.  */
@@ -616,6 +623,9 @@ PyTypeObject gdbpy_tui_window_object_type =
   0,				  /* tp_alloc */
 };
 
+PyTypeObject *gdbpy_tui_window::corresponding_object_type
+    = &gdbpy_tui_window_object_type;
+
 /* Called when TUI is enabled or disabled.  */
 
 static void
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index fdd353ffbeb..289e7a8ea23 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -470,8 +470,8 @@ PyObject *gdbpy_lookup_static_symbols (PyObject *self, PyObject *args,
 PyObject *gdbpy_start_recording (PyObject *self, PyObject *args);
 PyObject *gdbpy_current_recording (PyObject *self, PyObject *args);
 PyObject *gdbpy_stop_recording (PyObject *self, PyObject *args);
-PyObject *gdbpy_newest_frame (PyObject *self, PyObject *args);
-PyObject *gdbpy_selected_frame (PyObject *self, PyObject *args);
+gdbpy_ref<> gdbpy_newest_frame (gdbpy_borrowed_ref self);
+gdbpy_ref<> gdbpy_selected_frame (gdbpy_borrowed_ref self);
 PyObject *gdbpy_lookup_type (PyObject *self, PyObject *args, PyObject *kw);
 int gdbpy_is_field (PyObject *obj);
 PyObject *gdbpy_create_lazy_string_object (CORE_ADDR address, long length,
@@ -518,11 +518,11 @@ PyObject *objfpy_get_xmethods (PyObject *, void *);
 PyObject *gdbpy_lookup_objfile (PyObject *self, PyObject *args, PyObject *kw);
 
 gdbpy_ref<> gdbarch_to_arch_object (struct gdbarch *gdbarch);
-PyObject *gdbpy_all_architecture_names (PyObject *self, PyObject *args);
+gdbpy_ref<> gdbpy_all_architecture_names (gdbpy_borrowed_ref self);
 
-PyObject *gdbpy_new_register_descriptor_iterator (struct gdbarch *gdbarch,
-						  const char *group_name);
-PyObject *gdbpy_new_reggroup_iterator (struct gdbarch *gdbarch);
+gdbpy_ref<> gdbpy_new_register_descriptor_iterator (struct gdbarch *gdbarch,
+						    const char *group_name);
+gdbpy_ref<> gdbpy_new_reggroup_iterator (struct gdbarch *gdbarch);
 
 gdbpy_ref<thread_object> create_thread_object (struct thread_info *tp);
 gdbpy_ref<> thread_to_thread_object (thread_info *thr);;
@@ -1332,4 +1332,7 @@ class gdbpy_memoizing_registry_storage
 extern int eval_python_command (const char *command, int start_symbol,
 				const char *filename = nullptr);
 
+#include "py-wrappers.h"
+#include "py-safety.h"
+
 #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 8739864a861..5ee434167db 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -3036,12 +3036,12 @@ Arguments (also strings) are passed to the command." },
   { "current_objfile", gdbpy_get_current_objfile, METH_NOARGS,
     "Return the current Objfile being loaded, or None." },
 
-  { "newest_frame", gdbpy_newest_frame, METH_NOARGS,
+  wrap_noargs<gdbpy_newest_frame> ("newest_frame",
     "newest_frame () -> gdb.Frame.\n\
-Return the newest frame object." },
-  { "selected_frame", gdbpy_selected_frame, METH_NOARGS,
+Return the newest frame object."),
+  wrap_noargs<gdbpy_selected_frame> ("selected_frame",
     "selected_frame () -> gdb.Frame.\n\
-Return the selected frame object." },
+Return the selected frame object."),
   { "frame_stop_reason_string", gdbpy_frame_stop_reason_string, METH_VARARGS,
     "stop_reason_string (Integer) -> String.\n\
 Return a string explaining unwind stop reason." },
@@ -3157,9 +3157,9 @@ Set the value of the convenience variable $NAME." },
 Register a TUI window constructor." },
 #endif	/* TUI */
 
-  { "architecture_names", gdbpy_all_architecture_names, METH_NOARGS,
+  wrap_noargs<gdbpy_all_architecture_names> ("architecture_names",
     "architecture_names () -> List.\n\
-Return a list of all the architecture names GDB understands." },
+Return a list of all the architecture names GDB understands."),
 
   { "connections", gdbpy_connections, METH_NOARGS,
     "connections () -> List.\n\
-- 
2.49.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 0/4] Better Python safety
  2026-02-22 19:49 [RFC 0/4] Better Python safety Tom Tromey
                   ` (3 preceding siblings ...)
  2026-02-22 19:49 ` [RFC 4/4] Convert some Python code to new-style Tom Tromey
@ 2026-02-23 20:28 ` Simon Marchi
  2026-02-23 21:00 ` Simon Marchi
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: Simon Marchi @ 2026-02-23 20:28 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches



On 2026-02-22 14:49, Tom Tromey wrote:
> This series is a rough draft showing how I think Python safety could
> be improved.
> 
> The basic idea is to use C++ features: more fully use gdbpy_ref<> to
> avoid reference-counting bugs, introduce a new gdbpy_borrowed_ref to
> manage borrowed references; throw exceptions on failure rather than
> explicit error checks; and finally wrap Python C APIs to enforce these
> rules.
> 
> This approach also lets us implement Python methods in a more natural
> style.  Explicit try/catch when calling gdb APIs is no longer needed,
> and methods can simply return the appropriate type.
> 
> This series is nowhere near complete, but I did mostly convert
> py-arch.c and py-frame.c.  (Discussion of some holes below.)
> 
> A nice example of the simplification is shown by
> gdbpy_all_architecture_names, which is now just:
> 
> gdbpy_ref<>
> gdbpy_all_architecture_names (gdbpy_borrowed_ref self)
> {
>   gdbpy_ref<> list = gdbpy_new_list (0);
> 
>   std::vector<const char *> name_list = gdbarch_printable_names ();
>   for (const char *name : name_list)
>     gdbpy_list_append (list, gdbpy_unicode_from_string (name));
> 
>  return list;
> }
> 
> This shows pretty much all the features: no more error checking and it
> returns a gdbpy_ref<> since that is convenient.
> 
> 
> I think a few more features could still be added:
> 
> * I didn't handle single-argument methods in patch 3.  It's tempting
>   to use METH_O but we have at least one that accepts "|s".
> 
> * I didn't write wrappers for tp_str / tp_repr ... or the methods in
>   PyNumberMethods or PyMappingMethods.  This isn't difficult.
> 
> * I think the approach to handling subclasses of PyObject could be
>   greatly improved.  In particular I think we could use real C++
>   classes by judicious use of placement new and explicit destructor
>   calls.  The "corresponding_object_type" stuff you'll see in here is
>   sort of a gesture in this direction (though that code also helps
>   with some type-safety elsewhere as well).  Essentially I think we
>   could end up making new instances with just 'new'.
> 
>   I didn't want to really touch this until the stable ABI work related
>   to type-instantiation is done.
> 
> * Finally gdb is using PyObject_New a lot but my reading while
>   researching this series indicates that this is wrong.  However the
>   wrongness is still (I guess temporarily) preserved in a wrapper in
>   this series.
> 
> * There are some comments in the code where I didn't fully convert
>   something.  This would just be a temporary state.
> 
> Let me know what you think.
> 
> Tom
> 

The series doesn't seem to apply well, and it doesn't specify a base
commit.  What commit is this based on?  Alternatively, can you share
your branch directly?

Thanks,

Simon

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 0/4] Better Python safety
  2026-02-22 19:49 [RFC 0/4] Better Python safety Tom Tromey
                   ` (4 preceding siblings ...)
  2026-02-23 20:28 ` [RFC 0/4] Better Python safety Simon Marchi
@ 2026-02-23 21:00 ` Simon Marchi
  2026-02-23 23:23   ` Tom Tromey
  2026-02-23 21:22 ` Tom Tromey
  2026-03-04 17:39 ` Matthieu Longo
  7 siblings, 1 reply; 18+ messages in thread
From: Simon Marchi @ 2026-02-23 21:00 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches



On 2026-02-22 14:49, Tom Tromey wrote:
> This series is a rough draft showing how I think Python safety could
> be improved.
> 
> The basic idea is to use C++ features: more fully use gdbpy_ref<> to
> avoid reference-counting bugs, introduce a new gdbpy_borrowed_ref to
> manage borrowed references; throw exceptions on failure rather than
> explicit error checks; and finally wrap Python C APIs to enforce these
> rules.
> 
> This approach also lets us implement Python methods in a more natural
> style.  Explicit try/catch when calling gdb APIs is no longer needed,
> and methods can simply return the appropriate type.
> 
> This series is nowhere near complete, but I did mostly convert
> py-arch.c and py-frame.c.  (Discussion of some holes below.)
> 
> A nice example of the simplification is shown by
> gdbpy_all_architecture_names, which is now just:
> 
> gdbpy_ref<>
> gdbpy_all_architecture_names (gdbpy_borrowed_ref self)
> {
>   gdbpy_ref<> list = gdbpy_new_list (0);
> 
>   std::vector<const char *> name_list = gdbarch_printable_names ();
>   for (const char *name : name_list)
>     gdbpy_list_append (list, gdbpy_unicode_from_string (name));
> 
>  return list;
> }

Just some comments before I get into the code itself:

We have something really similar in the other project I work on,
Babeltrace.  The base API is in C, and it is very refcount heavy.  We
wrote C++ bindings around that, that I think are really nice and make it
almost impossible to make a mistake in refcount.

We have BorrowedObject:

    https://git.efficios.com/?p=babeltrace.git;a=blob;f=src/cpp-common/bt2/borrowed-object.hpp;h=3ea62072df3f0dbfbf2ad3434980c4f7f597dd39;hb=HEAD

which holds a weak reference on an object.  It can't hold nullptr,
meaning that if you receive a BorrowedObject of some kind, you know
there is an object in it.

We then have OptionalBorrowedObject, which is like BorrowedObject but
can be nullptr / empty:

    https://git.efficios.com/?p=babeltrace.git;a=blob;f=src/cpp-common/bt2/shared-object.hpp;h=8d6666e2ed4c658150ff449245764287c5c72f94;hb=HEAD

This is similar to gdbpy_borrowed_ref.

The distinction between BorrowedObject and OptionalBorrowedObject is
nice, as it allows declaring parameters or return values that can't be
nullptr / empty, a bit like C++ references vs pointers.  It might be
good to have that too.  The wrapper around PyList_Append, for instance,
would take non-optional parameters.

And finally, SharedObject, which holds a strong reference:

    https://git.efficios.com/?p=babeltrace.git;a=blob;f=src/cpp-common/bt2/optional-borrowed-object.hpp;h=cfabdf203542924742d8eaf24bb7ff33ebfbf14e;hb=HEAD

This is similar to gdbpy_ref.

Finally (1), is there something specific to GDB here?  I'm wondering if we
could make it a separate project (and import it in GDB), because it's
something I would be tempted to use outside of GDB.

And finally (2), I was kind of surprised that something like this didn't
exist already, we're certainly not the first ones to want to use the
CPython in a modern C++ program.  I searched around and found nanobind:

https://nanobind.readthedocs.io/en/latest/

It seems to do more than what we want (like automatic C++ <-> Python
data structure conversion), but the wrappers shown here seem to be like
what we want:

https://nanobind.readthedocs.io/en/latest/exchanging.html#option-3-wrappers

I'm not convinced that going with this library would be easier in the
long run, but I think we should at least take a moment to check what is
out there before writing something new.

Simon

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 0/4] Better Python safety
  2026-02-22 19:49 [RFC 0/4] Better Python safety Tom Tromey
                   ` (5 preceding siblings ...)
  2026-02-23 21:00 ` Simon Marchi
@ 2026-02-23 21:22 ` Tom Tromey
  2026-03-04 17:39 ` Matthieu Longo
  7 siblings, 0 replies; 18+ messages in thread
From: Tom Tromey @ 2026-02-23 21:22 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

>>>>> "Tom" == Tom Tromey <tom@tromey.com> writes:

Tom> * I didn't write wrappers for tp_str / tp_repr ... or the methods in
Tom>   PyNumberMethods or PyMappingMethods.  This isn't difficult.

I've been experimenting more and I implemented this, plus I made it so
the wrapper template is shared between all the users.

Tom

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 0/4] Better Python safety
  2026-02-23 21:00 ` Simon Marchi
@ 2026-02-23 23:23   ` Tom Tromey
  2026-02-23 23:56     ` Tom Tromey
  0 siblings, 1 reply; 18+ messages in thread
From: Tom Tromey @ 2026-02-23 23:23 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Tom Tromey, gdb-patches

>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:

Simon> We have BorrowedObject:
Simon>     https://git.efficios.com/?p=babeltrace.git;a=blob;f=src/cpp-common/bt2/borrowed-object.hpp;h=3ea62072df3f0dbfbf2ad3434980c4f7f597dd39;hb=HEAD
Simon> which holds a weak reference on an object.  It can't hold nullptr,
Simon> meaning that if you receive a BorrowedObject of some kind, you know
Simon> there is an object in it.

Initially I made gdbpy_borrowed_ref reject NULL pointers, but Python
will pass NULL as the keyword argument to varargs methods.  But I think
a separate class can be done.

Simon> Finally (1), is there something specific to GDB here?  I'm wondering if we
Simon> could make it a separate project (and import it in GDB), because it's
Simon> something I would be tempted to use outside of GDB.

Yeah, it is tied into other parts of gdb to some extent.

It checks for gdb exceptions specifically and gdbpy_convert_exception
knows about QUIT and whatnot.

gdbpy_ref is just an specialization of a template in gdbsupport.

The result converters use LONGEST, ULONGEST, and gdb::unique_xmalloc_ptr
(dunno if all those are in the patches I already sent, but anyway they
are clearly needed).

The string converter uses host_charset().

I vaguely plan to eventually use gdb_python_exception to solve the
"exception denaturation" problem, which would mean integrating it more
with the existing gdb exception system.

Simon> https://nanobind.readthedocs.io/en/latest/exchanging.html#option-3-wrappers

Simon> I'm not convinced that going with this library would be easier in the
Simon> long run, but I think we should at least take a moment to check what is
Simon> out there before writing something new.

To me this looks more complicated than anything we need.
I did look at it predecessor, pybind11, a little before starting.

Tom

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 0/4] Better Python safety
  2026-02-23 23:23   ` Tom Tromey
@ 2026-02-23 23:56     ` Tom Tromey
  2026-02-24  1:05       ` Simon Marchi
  0 siblings, 1 reply; 18+ messages in thread
From: Tom Tromey @ 2026-02-23 23:56 UTC (permalink / raw)
  To: Tom Tromey; +Cc: Simon Marchi, gdb-patches

Tom> Initially I made gdbpy_borrowed_ref reject NULL pointers, but Python
Tom> will pass NULL as the keyword argument to varargs methods.  But I think
Tom> a separate class can be done.

I implemented this.

gdbpy_optional_ref is used for keyword arguments and also by setters.
Not sure if it will see any other uses.

Tom

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 0/4] Better Python safety
  2026-02-23 23:56     ` Tom Tromey
@ 2026-02-24  1:05       ` Simon Marchi
  2026-02-24 16:29         ` Tom Tromey
  0 siblings, 1 reply; 18+ messages in thread
From: Simon Marchi @ 2026-02-24  1:05 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches



On 2026-02-23 18:56, Tom Tromey wrote:
> Tom> Initially I made gdbpy_borrowed_ref reject NULL pointers, but Python
> Tom> will pass NULL as the keyword argument to varargs methods.  But I think
> Tom> a separate class can be done.
> 
> I implemented this.
> 
> gdbpy_optional_ref is used for keyword arguments and also by setters.
> Not sure if it will see any other uses.

Is this kind of ref also borrowed?  If so I would include "borrowed" in
its name, otherwise it's confusing.  Perhaps gdbpy_opt_borrowed_ref?  Or
flip the naming around, make the "default" borrowed and find a name for
the "strong" refs.

Simon

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 1/4] Add gdbpy_borrowed_ref
  2026-02-22 19:49 ` [RFC 1/4] Add gdbpy_borrowed_ref Tom Tromey
@ 2026-02-24  4:57   ` Simon Marchi
  2026-02-25  3:55     ` Tom Tromey
  0 siblings, 1 reply; 18+ messages in thread
From: Simon Marchi @ 2026-02-24  4:57 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches



On 2026-02-22 14:49, Tom Tromey wrote:
> This adds a new gdbpy_borrowed_ref class.  This class is primarily for
> code "documentation" purposes -- it makes it clear to the reader that
> a given reference is borrowed.  However, it also adds a tiny bit of
> safety, in that conversion to gdbpy_ref<> will acquire a new
> reference.
> ---
>  gdb/python/py-ref.h | 47 +++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 47 insertions(+)
> 
> diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h
> index 0a56436634d..56508996eaa 100644
> --- a/gdb/python/py-ref.h
> +++ b/gdb/python/py-ref.h
> @@ -41,6 +41,53 @@ struct gdbpy_ref_policy
>  template<typename T = PyObject> using gdbpy_ref
>    = gdb::ref_ptr<T, gdbpy_ref_policy>;
>  
> +/* A class representing a borrowed reference.
> +
> +   This is a simple wrapper for a PyObject*.  Aside from documenting
> +   what the code does, the main advantage of using this is that
> +   conversion to a gdbpy_ref<> is guaranteed to make a new
> +   reference.  */
> +class gdbpy_borrowed_ref
> +{
> +public:
> +
> +  gdbpy_borrowed_ref (PyObject *obj)
> +    : m_obj (obj)
> +  {
> +  }
> +
> +  template<typename T>
> +  gdbpy_borrowed_ref (const gdbpy_ref<T> &ref)
> +    : m_obj (ref.get ())
> +  {
> +  }
> +
> +  /* Allow a (checked) conversion to any subclass of PyObject.  */
> +  template<typename T,
> +	   typename = std::is_convertible<T *, PyObject *>>
> +  operator T * ()
> +  {
> +    gdb_assert (PyObject_TypeCheck (m_obj, T::corresponding_object_type));
> +    return static_cast<T *> (m_obj);
> +  }

Shouldn't gdbpy_borrowed_ref be templated?  Allowing any gdbpy_ref to be
downcast to any subclass of PyObject seems a bit permissive.  At least
there is a runtime check, but it would be nice to have more type safety
at compile time.

I imagine:

  arch_object *arch = gdbpy_borrowed_ref<arch_object> (...);  // works
  arch_object *arch = gdbpy_borrowed_ref<PyObject> (...);  // does not work

That being said, apart from the wrappers themselves, I don't expect that
we'll need to do that often.  I suppose that it would be possible to
access the fields like this?

gdbpy_borrowed_ref<arch_object> obj;
obj->gdbarch; // accesses arch_object::gdbarch

I also toyed with defining some types like:

struct PyDict : public PyObject {};
struct PyList : public PyObject {};
struct PyLong : public PyObject {};
struct PyTuple : public PyObject {};
struct PyType : public PyObject {};

gdbpy_new_list would return a gdbpy_ref<PyList>.
gdbpy_list_append would take a gdbpy_borrowed_ref<PyList>.

And then we could have some ways of down-casting, if needed.

Simon

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 0/4] Better Python safety
  2026-02-24  1:05       ` Simon Marchi
@ 2026-02-24 16:29         ` Tom Tromey
  0 siblings, 0 replies; 18+ messages in thread
From: Tom Tromey @ 2026-02-24 16:29 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Tom Tromey, gdb-patches

>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:

>> gdbpy_optional_ref is used for keyword arguments and also by setters.
>> Not sure if it will see any other uses.

Simon> Is this kind of ref also borrowed?  If so I would include "borrowed" in
Simon> its name, otherwise it's confusing.  Perhaps gdbpy_opt_borrowed_ref?  Or
Simon> flip the naming around, make the "default" borrowed and find a name for
Simon> the "strong" refs.

Yeah, it is also borrowed.  I can rename it easily enough.
I think it's fine for optionally-borrowed-ref to have a longer name
since this concept is barely used.

Tom

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 1/4] Add gdbpy_borrowed_ref
  2026-02-24  4:57   ` Simon Marchi
@ 2026-02-25  3:55     ` Tom Tromey
  2026-02-25 15:24       ` Simon Marchi
  2026-02-26  1:38       ` Tom Tromey
  0 siblings, 2 replies; 18+ messages in thread
From: Tom Tromey @ 2026-02-25  3:55 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Tom Tromey, gdb-patches

>> +  template<typename T,
>> +	   typename = std::is_convertible<T *, PyObject *>>
>> +  operator T * ()
>> +  {
>> +    gdb_assert (PyObject_TypeCheck (m_obj, T::corresponding_object_type));
>> +    return static_cast<T *> (m_obj);
>> +  }

Simon> Shouldn't gdbpy_borrowed_ref be templated?  Allowing any gdbpy_ref to be
Simon> downcast to any subclass of PyObject seems a bit permissive.  At least
Simon> there is a runtime check, but it would be nice to have more type safety
Simon> at compile time.

Yeah, however this does not actually come up very much.

In fact all the casts I am aware are of 'self', which means we could
maybe solve this a special case: turn functions into methods on the
object, and then have the wrappers wrap a pointer-to-member-function.

This would mean that 'this' would be implicitly a borrowed reference,
but OTOH this isn't likely to be a big source of confusion and it would
make the code even simpler.

Simon>   arch_object *arch = gdbpy_borrowed_ref<arch_object> (...);  // works
Simon>   arch_object *arch = gdbpy_borrowed_ref<PyObject> (...);  // does not work

Simon> That being said, apart from the wrappers themselves, I don't expect that
Simon> we'll need to do that often.  I suppose that it would be possible to
Simon> access the fields like this?

As long as there's an operator*.  But if we did the method approach,
there'd be basically no need for this at all.

Simon> I also toyed with defining some types like:

Simon> struct PyDict : public PyObject {};
Simon> struct PyList : public PyObject {};
Simon> struct PyLong : public PyObject {};
Simon> struct PyTuple : public PyObject {};
Simon> struct PyType : public PyObject {};

Simon> gdbpy_new_list would return a gdbpy_ref<PyList>.
Simon> gdbpy_list_append would take a gdbpy_borrowed_ref<PyList>.

Simon> And then we could have some ways of down-casting, if needed.

It could be done.  I'm ambivalent about it since I don't think this is a
common source of errors in gdb, I guess mainly because the needed
type-checks are re-done by Python anyway.

OTOH we've had multiple refcount bugs and multiple cases where exception
checks are missed.  Those are the problems I really set out to solve.

Tom

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 1/4] Add gdbpy_borrowed_ref
  2026-02-25  3:55     ` Tom Tromey
@ 2026-02-25 15:24       ` Simon Marchi
  2026-02-26  1:38       ` Tom Tromey
  1 sibling, 0 replies; 18+ messages in thread
From: Simon Marchi @ 2026-02-25 15:24 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches



On 2026-02-24 22:55, Tom Tromey wrote:
>>> +  template<typename T,
>>> +	   typename = std::is_convertible<T *, PyObject *>>
>>> +  operator T * ()
>>> +  {
>>> +    gdb_assert (PyObject_TypeCheck (m_obj, T::corresponding_object_type));
>>> +    return static_cast<T *> (m_obj);
>>> +  }
> 
> Simon> Shouldn't gdbpy_borrowed_ref be templated?  Allowing any gdbpy_ref to be
> Simon> downcast to any subclass of PyObject seems a bit permissive.  At least
> Simon> there is a runtime check, but it would be nice to have more type safety
> Simon> at compile time.
> 
> Yeah, however this does not actually come up very much.
> 
> In fact all the casts I am aware are of 'self', which means we could
> maybe solve this a special case: turn functions into methods on the
> object, and then have the wrappers wrap a pointer-to-member-function.
> 
> This would mean that 'this' would be implicitly a borrowed reference,
> but OTOH this isn't likely to be a big source of confusion and it would
> make the code even simpler.
> 
> Simon>   arch_object *arch = gdbpy_borrowed_ref<arch_object> (...);  // works
> Simon>   arch_object *arch = gdbpy_borrowed_ref<PyObject> (...);  // does not work
> 
> Simon> That being said, apart from the wrappers themselves, I don't expect that
> Simon> we'll need to do that often.  I suppose that it would be possible to
> Simon> access the fields like this?
> 
> As long as there's an operator*.  But if we did the method approach,
> there'd be basically no need for this at all.
> 
> Simon> I also toyed with defining some types like:
> 
> Simon> struct PyDict : public PyObject {};
> Simon> struct PyList : public PyObject {};
> Simon> struct PyLong : public PyObject {};
> Simon> struct PyTuple : public PyObject {};
> Simon> struct PyType : public PyObject {};
> 
> Simon> gdbpy_new_list would return a gdbpy_ref<PyList>.
> Simon> gdbpy_list_append would take a gdbpy_borrowed_ref<PyList>.
> 
> Simon> And then we could have some ways of down-casting, if needed.
> 
> It could be done.  I'm ambivalent about it since I don't think this is a
> common source of errors in gdb, I guess mainly because the needed
> type-checks are re-done by Python anyway.
> 
> OTOH we've had multiple refcount bugs and multiple cases where exception
> checks are missed.  Those are the problems I really set out to solve.

Ack.  I agree it's fine to go for that, at least at first.

Simon

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 1/4] Add gdbpy_borrowed_ref
  2026-02-25  3:55     ` Tom Tromey
  2026-02-25 15:24       ` Simon Marchi
@ 2026-02-26  1:38       ` Tom Tromey
  1 sibling, 0 replies; 18+ messages in thread
From: Tom Tromey @ 2026-02-26  1:38 UTC (permalink / raw)
  To: Tom Tromey; +Cc: Simon Marchi, gdb-patches

Tom> In fact all the casts I am aware are of 'self', which means we could
Tom> maybe solve this a special case: turn functions into methods on the
Tom> object, and then have the wrappers wrap a pointer-to-member-function.

Tom> This would mean that 'this' would be implicitly a borrowed reference,
Tom> but OTOH this isn't likely to be a big source of confusion and it would
Tom> make the code even simpler.

I wrote a draft of this & pushed it to my github branch.
I converted one file to this approach.  Now it looks like:

    struct arch_object : public PyObject
    {
    [...]
      const char *name ()
      {
        return gdbarch_bfd_arch_info (require ())->printable_name;
      }
    ...
    };

    static PyMethodDef arch_object_methods [] = {
      noargs_method<arch_object, &arch_object::name> ("name",
        "name () -> String.\n\
    Return the name of the architecture as a string value."),

I wish it could be a little more concise, but I'm not sure it can be
without a macro.

So maybe this is the way to go.

Tom

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 0/4] Better Python safety
  2026-02-22 19:49 [RFC 0/4] Better Python safety Tom Tromey
                   ` (6 preceding siblings ...)
  2026-02-23 21:22 ` Tom Tromey
@ 2026-03-04 17:39 ` Matthieu Longo
  2026-03-04 21:02   ` Tom Tromey
  7 siblings, 1 reply; 18+ messages in thread
From: Matthieu Longo @ 2026-03-04 17:39 UTC (permalink / raw)
  To: gdb-patches

On 22/02/2026 19:49, Tom Tromey wrote:
> * I think the approach to handling subclasses of PyObject could be
>    greatly improved.  In particular I think we could use real C++
>    classes by judicious use of placement new and explicit destructor
>    calls.  The "corresponding_object_type" stuff you'll see in here is
>    sort of a gesture in this direction (though that code also helps
>    with some type-safety elsewhere as well).  Essentially I think we
>    could end up making new instances with just 'new'.
> 
>    I didn't want to really touch this until the stable ABI work related
>    to type-instantiation is done.
> 

Yes please :)

> * Finally gdb is using PyObject_New a lot but my reading while
>    researching this series indicates that this is wrong.  However the
>    wrongness is still (I guess temporarily) preserved in a wrapper in
>    this series.> 
> Tom
> 

I haven't published the patch series migrating all the C extension types, but what I discovered is summarized in https://docs.python.org/3/howto/isolating-extensions.html#heap-types.

Specifically this paragraph:

   > Instances of heap types hold a reference to their type. This ensures
   > that the type isn’t destroyed before all its instances are, but may
   > result in reference cycles that need to be broken by the garbage collector.

   > To avoid memory leaks, instances of heap types must implement the
   > garbage collection protocol. That is, heap types should:
   >  * Have the Py_TPFLAGS_HAVE_GC flag.
   >  * Define a traverse function using Py_tp_traverse, which visits the
   >    type (e.g. using Py_VISIT(Py_TYPE(self))).

So after the migration, all the extension types have the Py_TPFLAGS_HAVE_GC flag, are allocated via PyObject_GC_New(), and every PyType_Slot sets Py_tp_traverse, Py_tp_clear, and Py_tp_dealloc.

Some of the types had circular dependencies due to a type referencing the other one similarly to what happens between two nodes of a doubly linked list. In those cases, the circular dependency have to be broken.
Also, some types already should have required garbage collection management even with static types.

Are your findings converging with mine ?

Matthieu

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC 0/4] Better Python safety
  2026-03-04 17:39 ` Matthieu Longo
@ 2026-03-04 21:02   ` Tom Tromey
  0 siblings, 0 replies; 18+ messages in thread
From: Tom Tromey @ 2026-03-04 21:02 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: gdb-patches

>> with some type-safety elsewhere as well).  Essentially I think we
>> could end up making new instances with just 'new'.
>> I didn't want to really touch this until the stable ABI work
>> related
>> to type-instantiation is done.

> Yes please :)

I'm not totally sure if the 'new' plan is possible.

Or maybe you were responding to me waiting until your type-instantiation
work is done.  FAOD I am still planning to wait on that.

> So after the migration, all the extension types have the
> Py_TPFLAGS_HAVE_GC flag, are allocated via PyObject_GC_New(), and
> every PyType_Slot sets Py_tp_traverse, Py_tp_clear, and Py_tp_dealloc.

> Some of the types had circular dependencies due to a type referencing
> the other one similarly to what happens between two nodes of a doubly
> linked list. In those cases, the circular dependency have to be
> broken.  Also, some types already should have required garbage
> collection management even with static types.

> Are your findings converging with mine ?

Yes I think so.

There's a bug in bugzilla about how gdb doesn't implement the 'traverse'
idea but should.  You can probably cause memory leaks right now by
making self-references via __dict__.

Tom

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-03-04 21:03 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-22 19:49 [RFC 0/4] Better Python safety Tom Tromey
2026-02-22 19:49 ` [RFC 1/4] Add gdbpy_borrowed_ref Tom Tromey
2026-02-24  4:57   ` Simon Marchi
2026-02-25  3:55     ` Tom Tromey
2026-02-25 15:24       ` Simon Marchi
2026-02-26  1:38       ` Tom Tromey
2026-02-22 19:49 ` [RFC 2/4] Add wrappers for some Python APIs Tom Tromey
2026-02-22 19:49 ` [RFC 3/4] Add constexpr functions to create PyMethodDef entries Tom Tromey
2026-02-22 19:49 ` [RFC 4/4] Convert some Python code to new-style Tom Tromey
2026-02-23 20:28 ` [RFC 0/4] Better Python safety Simon Marchi
2026-02-23 21:00 ` Simon Marchi
2026-02-23 23:23   ` Tom Tromey
2026-02-23 23:56     ` Tom Tromey
2026-02-24  1:05       ` Simon Marchi
2026-02-24 16:29         ` Tom Tromey
2026-02-23 21:22 ` Tom Tromey
2026-03-04 17:39 ` Matthieu Longo
2026-03-04 21:02   ` Tom Tromey

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox