Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH 0/4] Convert py-frame.c to Python safety API
@ 2026-08-19 23:54 Tom Tromey
  2026-08-19 23:54 ` [PATCH 1/4] Basic safety conversion of py-frame.c Tom Tromey
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Tom Tromey @ 2026-08-19 23:54 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This series converts py-frame.c to the Python safety API.

I've split it into multiple parts to make it perhaps a bit easier to
review.  Often I find these patches pretty hard to read, since they
involve converting a bunch of functions into methods, making them
fairly messy.

A few FIXME comments are added; as with other cases, these can all be
removed once the conversion is complete.  At the current rate this may
be a while.

Tested on x86-64 Fedora 40.

Signed-off-by: Tom Tromey <tom@tromey.com>
---
Tom Tromey (4):
      Basic safety conversion of py-frame.c
      Convert gdbpy_newest_frame and gdbpy_selected_frame to safety API
      Convert gdbpy_frame_stop_reason_string to the safety API
      Convert frapy_richcompare to safety API

 gdb/python/py-frame.c                 | 852 +++++++++++++---------------------
 gdb/python/py-inferior.c              |  24 +-
 gdb/python/py-safety.h                |  53 +++
 gdb/python/python-internal.h          |   7 +-
 gdb/python/python.c                   |  12 +-
 gdb/testsuite/gdb.python/py-frame.exp |   3 +
 6 files changed, 408 insertions(+), 543 deletions(-)
---
base-commit: 07ee9e316ab6d53b5ed1aae8fe7448a559abe4be
change-id: 20260819-python-safety-frame-c795afcc47df

Best regards,
-- 
Tom Tromey <tom@tromey.com>


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

* [PATCH 1/4] Basic safety conversion of py-frame.c
  2026-08-19 23:54 [PATCH 0/4] Convert py-frame.c to Python safety API Tom Tromey
@ 2026-08-19 23:54 ` Tom Tromey
  2026-08-19 23:54 ` [PATCH 2/4] Convert gdbpy_newest_frame and gdbpy_selected_frame to safety API Tom Tromey
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Tom Tromey @ 2026-08-19 23:54 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This converts the bulk of py-frame.c to the new Python safety API.
---
 gdb/python/py-frame.c | 764 +++++++++++++++++++-------------------------------
 1 file changed, 289 insertions(+), 475 deletions(-)

diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index afa89f0d112..5c5ff8cf0de 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -32,22 +32,125 @@ struct frame_object : public PyObject
 {
   struct frame_id frame_id;
   struct gdbarch *gdbarch;
+
+  /* Marks that the FRAME_ID member actually holds the ID of the frame next
+     to this, and not this frames' ID itself.  This is a hack to permit Python
+     frame objects which represent invalid frames (i.e., the last frame_info
+     in a corrupt stack).  The problem arises from the fact that this code
+     relies on FRAME_ID to uniquely identify a frame, which is not always true
+     for the last "frame" in a corrupt stack (it can have a null ID, or the same
+     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;
+
+  /* Return the associated frame.  Throws an exception on error.  */
+  frame_info_ptr require_frame ()
+  {
+    frame_info_ptr frame = frame_object_to_frame_info (this);
+    /* FIXME: Python safety.  frame_object_to_frame_info should be
+       converted, but the callers aren't ready yet.  */
+    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.  */
+  gdbpy_ref<> str ()
+  {
+    return gdbpy_unicode_from_string (frame_id.to_string ());
+  }
+
+  /* Implement repr() for gdb.Frame.  */
+  gdbpy_ref<> repr ();
+
+  /* Implementation of gdb.Frame.is_valid (self) -> Boolean.  Returns
+     True if the frame corresponding to the frame_id of this object
+     still exists in the inferior.  */
+  bool is_valid ()
+  {
+    return frame_object_to_frame_info (this) != nullptr;
+  }
+
+  /* Implementation of gdb.Frame.name (self) -> String.
+     Returns the name of the function corresponding to this frame.  */
+  gdb::unique_xmalloc_ptr<char> name ();
+
+  /* Implementation of gdb.Frame.type (self) -> Integer.
+     Returns the frame type, namely one of the gdb.*_FRAME constants.  */
+  ULONGEST type ();
+
+  /* Implementation of gdb.Frame.architecture (self) -> gdb.Architecture.
+     Returns the frame's architecture as a gdb.Architecture object.  */
+  gdbpy_ref<> arch ();
+
+  /* Implementation of gdb.Frame.unwind_stop_reason (self) -> Integer.
+     Returns one of the gdb.FRAME_UNWIND_* constants.  */
+  int unwind_stop_reason ();
+
+  /* Implementation of gdb.Frame.pc (self) -> Long.
+     Returns the frame's resume address.  */
+  ULONGEST pc ();
+
+  /* Implementation of gdb.Frame.read_register (self, register) -> gdb.Value.
+     Returns the value of a register in this frame.  */
+  gdbpy_ref<> read_register (gdbpy_borrowed_ref<> args,
+			     gdbpy_opt_borrowed_ref<> kw);
+
+  /* Implementation of gdb.Frame.block (self) -> gdb.Block.
+     Returns the frame's code block.  */
+  gdbpy_ref<> block ();
+
+  /* Implementation of gdb.Frame.function (self) -> gdb.Symbol.
+     Returns the symbol for the function corresponding to this frame.  */
+  gdbpy_ref<> function ();
+
+  /* Implementation of gdb.Frame.older (self) -> gdb.Frame.
+     Returns the frame immediately older (outer) to this frame, or None if
+     there isn't one.  */
+  gdbpy_ref<> older ();
+
+  /* Implementation of gdb.Frame.newer (self) -> gdb.Frame.
+     Returns the frame immediately newer (inner) to this frame, or None if
+     there isn't one.  */
+  gdbpy_ref<> newer ();
+
+  /* Implementation of gdb.Frame.find_sal (self) -> gdb.Symtab_and_line.
+     Returns the frame's symtab and line.  */
+  gdbpy_ref<> find_sal ();
+
+  /* Implementation of gdb.Frame.read_var_value (self, variable,
+     [block]) -> gdb.Value.  If the optional block argument is provided
+     start the search from that block, otherwise search from the frame's
+     current block (determined by examining the resume address of the
+     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.  */
+  gdbpy_ref<> read_var (gdbpy_borrowed_ref<> args,
+			gdbpy_opt_borrowed_ref<> kw);
+
+  /* Select this frame.  */
+  void select ();
+
+  /* The stack frame level for this frame.  */
+  int level ();
+
+  /* The language for this frame.  */
+  const char *language ();
+
+  /* The static link for this frame.  */
+  gdbpy_ref<> static_link ();
+
+  static PyTypeObject *corresponding_object_type;
 };
 
 static_assert (gdb::is_python_allocatable_v<frame_object>);
 
-/* 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)
-
 /* Returns the frame_info object corresponding to the given Python Frame
    object.  If the frame doesn't exist anymore (the frame id doesn't
    correspond to any frame in the inferior), returns NULL.  */
+/* FIXME: Python safety.  This function should be converted, but the
+   callers aren't ready yet.  */
 
 frame_info_ptr
 frame_object_to_frame_info (PyObject *obj)
@@ -62,291 +165,127 @@ frame_object_to_frame_info (PyObject *obj)
   return frame;
 }
 
-/* Called by the Python interpreter to obtain string representation
-   of the object.  */
-
-static PyObject *
-frapy_str (PyObject *self)
-{
-  const frame_id &fid = ((frame_object *) self)->frame_id;
-  return PyUnicode_FromString (fid.to_string ().c_str ());
-}
-
-/* Implement repr() for gdb.Frame.  */
-
-static PyObject *
-frapy_repr (PyObject *self)
+gdbpy_ref<>
+frame_object::repr ()
 {
-  frame_object *frame_obj = (frame_object *) self;
-  frame_info_ptr f_info = frame_find_by_id (frame_obj->frame_id);
+  frame_info_ptr f_info = frame_find_by_id (frame_id);
   if (f_info == nullptr)
-    return gdb_py_invalid_object_repr (self);
-
-  const frame_id &fid = frame_obj->frame_id;
-  return PyUnicode_FromFormat ("<%s level=%d frame-id=%s>",
-			       gdbpy_py_obj_tp_name (self).c_str (),
-			       frame_relative_level (f_info),
-			       fid.to_string ().c_str ());
+    /* FIXME: Python safety.  gdb_py_invalid_object_repr should
+       throw on error. */
+    return gdbpy_ref<> (gdb_py_invalid_object_repr (this));
+
+  return gdbpy_unicode_from_format ("<%s level=%d frame-id=%s>",
+				    gdbpy_py_obj_tp_name (this).c_str (),
+				    frame_relative_level (f_info),
+				    frame_id.to_string ().c_str ());
 }
 
-/* Implementation of gdb.Frame.is_valid (self) -> Boolean.
-   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)
+gdb::unique_xmalloc_ptr<char>
+frame_object::name ()
 {
-  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)
-    return py_false ().release ();
-
-  return py_true ().release ();
-}
-
-/* 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)
-{
-  frame_info_ptr frame;
-  gdb::unique_xmalloc_ptr<char> name;
+  frame_info_ptr frame = require_frame ();
   enum language lang;
-  PyObject *result;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-
-      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 ().release ();
-    }
-
-  return result;
+  return find_frame_funname (frame, &lang, nullptr);
 }
 
-/* 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)
+ULONGEST
+frame_object::type ()
 {
-  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 ();
+  return get_frame_type (frame);
 }
 
-/* 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)
+gdbpy_ref<>
+frame_object::arch ()
 {
-  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 ();
+  return gdbarch_to_arch_object (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)
+int
+frame_object::unwind_stop_reason ()
 {
-  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 ();
+  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)
+ULONGEST
+frame_object::pc ()
 {
-  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 ();
+  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)
+gdbpy_ref<>
+frame_object::read_register (gdbpy_borrowed_ref<> args,
+			     gdbpy_opt_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;
-
-  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;
+  gdbpy_arg_parse_tuple_and_keywords (args, kw, "O", keywords, &pyo_reg_id);
 
-      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 ();
 
-      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: Python safety.  gdbpy_parse_register_id should throw
+	 on error.  */
+      throw gdb_python_exception ();
     }
 
-  return result.release ();
-}
+  gdb_assert (regnum >= 0);
+  value *val
+    = value_of_register (regnum, get_next_frame_sentinel_okay (frame));
 
-/* Implementation of gdb.Frame.block (self) -> gdb.Block.
-   Returns the frame's code block.  */
+  if (val == nullptr)
+    gdbpy_err_set_string (PyExc_ValueError, _("Can't read register."));
 
-static PyObject *
-frapy_block (PyObject *self, PyObject *args)
-{
-  frame_info_ptr frame;
-  const struct block *block = NULL, *fn_block;
+  return value_to_value_object (val);
+}
 
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-      block = get_frame_block (frame, NULL);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
+gdbpy_ref<>
+frame_object::block ()
+{
+  frame_info_ptr frame = require_frame ();
+  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)
+gdbpy_ref<>
+frame_object::function ()
 {
-  struct symbol *sym = NULL;
-  frame_info_ptr frame;
-
-  try
-    {
-      enum language funlang;
+  frame_info_ptr frame = require_frame ();
 
-      FRAPY_REQUIRE_VALID (self, frame);
+  struct symbol *sym = nullptr;
+  enum language funlang;
+  gdb::unique_xmalloc_ptr<char> funname
+    = find_frame_funname (frame, &funlang, &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 ();
+  if (sym != nullptr)
+    return symbol_to_symbol_object (sym);
 
-  return py_none ().release ();
+  return py_none ();
 }
 
 /* Convert a frame_info struct to a Python Frame object.
    Sets a Python exception and returns NULL on error.  */
+/* FIXME: Python safety.  This function should be converted, but the
+   callers aren't ready yet.  */
 
 gdbpy_ref<>
 frame_info_to_frame_object (const frame_info_ptr &frame)
@@ -369,106 +308,51 @@ frame_info_to_frame_object (const frame_info_ptr &frame)
   return frame_obj;
 }
 
-/* Implementation of gdb.Frame.older (self) -> gdb.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)
+gdbpy_ref<>
+frame_object::older ()
 {
-  frame_info_ptr frame, prev = NULL;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
+  frame_info_ptr frame = require_frame ();
+  frame_info_ptr prev = get_prev_frame (frame);
 
-      prev = get_prev_frame (frame);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
-
-  gdbpy_ref<> prev_obj;
   if (prev)
-    prev_obj = frame_info_to_frame_object (prev);
-  else
-    prev_obj = py_none ();
+    return frame_info_to_frame_object (prev);
 
-  return prev_obj.release ();
+  return 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)
+gdbpy_ref<>
+frame_object::newer ()
 {
-  frame_info_ptr frame, next = NULL;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-
-      next = get_next_frame (frame);
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
+  frame_info_ptr frame = require_frame ();
+  frame_info_ptr next = get_next_frame (frame);
 
-  gdbpy_ref<> next_obj;
   if (next)
-    next_obj = frame_info_to_frame_object (next);
-  else
-    next_obj = py_none ();
+    return frame_info_to_frame_object (next);
 
-  return next_obj.release ();
+  return 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)
+gdbpy_ref<>
+frame_object::find_sal ()
 {
-  frame_info_ptr frame;
-
-  try
-    {
-      FRAPY_REQUIRE_VALID (self, frame);
-
-      symtab_and_line sal = find_frame_sal (frame);
-      return symtab_and_line_to_sal_object (sal).release ();
-    }
-  catch (const gdb_exception &except)
-    {
-      return gdbpy_handle_gdb_exception (nullptr, except);
-    }
+  frame_info_ptr frame = require_frame ();
+  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,
-   [block]) -> gdb.Value.  If the optional block argument is provided
-   start the search from that block, otherwise search from the frame's
-   current block (determined by examining the resume address of the
-   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)
+gdbpy_ref<>
+frame_object::read_var (gdbpy_borrowed_ref<> args,
+			gdbpy_opt_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))
@@ -476,8 +360,10 @@ 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: Python safety.  python_string_to_target_string should
+	 throw on error.  */
+      if (var_name == nullptr)
+	throw gdb_python_exception ();
 
       if (block_obj != nullptr)
 	{
@@ -488,139 +374,65 @@ 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 ();
 
-      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"),
-		    gdbpy_py_obj_tp_name (sym_obj).c_str ());
-      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"),
+		      gdbpy_py_obj_tp_name (sym_obj).c_str ());
+
+  frame_info_ptr frame = require_frame ();
+  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)
+void
+frame_object::select ()
 {
-  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);
-    }
-
-  return py_none ().release ();
+  frame_info_ptr fi = require_frame ();
+  select_frame (fi);
 }
 
-/* The stack frame level for this frame.  */
-
-static PyObject *
-frapy_level (PyObject *self, PyObject *args)
+int
+frame_object::level ()
 {
-  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);
-    }
+  frame_info_ptr fi = require_frame ();
+  return frame_relative_level (fi);
 }
 
-/* The language for this frame.  */
-
-static PyObject *
-frapy_language (PyObject *self, PyObject *args)
+const char *
+frame_object::language ()
 {
-  try
-    {
-      frame_info_ptr fi;
-      FRAPY_REQUIRE_VALID (self, fi);
+  frame_info_ptr fi = require_frame ();
 
-      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);
-    }
+  return lang_def->name ();
 }
 
-/* The static link for this frame.  */
-
-static PyObject *
-frapy_static_link (PyObject *self, PyObject *args)
+gdbpy_ref<>
+frame_object::static_link ()
 {
-  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 ();
+  link = frame_follow_static_link (link);
 
   if (link == nullptr)
-    return py_none ().release ();
+    return py_none ();
 
-  return frame_info_to_frame_object (link).release ();
+  return frame_info_to_frame_object (link);
 }
 
 /* Implementation of gdb.newest_frame () -> gdb.Frame.
@@ -712,6 +524,8 @@ frapy_richcompare (PyObject *self, PyObject *other, int op)
   return py_false ().release ();
 }
 
+PyTypeObject *frame_object::corresponding_object_type = &frame_object_type;
+
 /* Sets up the Frame API in the gdb module.  */
 
 static int
@@ -750,54 +564,54 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_frames);
 \f
 
 static PyMethodDef frame_object_methods[] = {
-  { "is_valid", frapy_is_valid, METH_NOARGS,
+  noargs_method<frame_object, &frame_object::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."),
+  noargs_method<frame_object, &frame_object::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."),
+  noargs_method<frame_object, &frame_object::type> ("type",
     "type () -> Integer.\n\
-Return the type of the frame." },
-  { "architecture", frapy_arch, METH_NOARGS,
+Return the type of the frame."),
+  noargs_method<frame_object, &frame_object::arch> ("architecture",
     "architecture () -> gdb.Architecture.\n\
-Return the architecture of the frame." },
-  { "unwind_stop_reason", frapy_unwind_stop_reason, METH_NOARGS,
-    "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 architecture of the frame."),
+  noargs_method<frame_object, &frame_object::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."),
+  noargs_method<frame_object, &frame_object::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."),
+  varargs_method<frame_object, &frame_object::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."),
+  noargs_method<frame_object, &frame_object::block> ("block",
     "block () -> gdb.Block.\n\
-Return the frame's code block." },
-  { "function", frapy_function, METH_NOARGS,
+Return the frame's code block."),
+  noargs_method<frame_object, &frame_object::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."),
+  noargs_method<frame_object, &frame_object::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."),
+  noargs_method<frame_object, &frame_object::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."),
+  noargs_method<frame_object, &frame_object::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."),
+  varargs_method<frame_object, &frame_object::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."),
+  noargs_method<frame_object, &frame_object::select> ("select",
+    "Select this frame as the user's current frame."),
+  noargs_method<frame_object, &frame_object::level> ("level",
+    "The stack level of this frame."),
+  noargs_method<frame_object, &frame_object::language> ("language",
+    "The language of this frame."),
+  noargs_method<frame_object, &frame_object::static_link> ("static_link",
+    "The static link of this frame, or None."),
   {NULL}  /* Sentinel */
 };
 
@@ -811,13 +625,13 @@ PyTypeObject frame_object_type = {
   0,				  /* tp_getattr */
   0,				  /* tp_setattr */
   0,				  /* tp_compare */
-  frapy_repr,			  /* tp_repr */
+  wrap_tp_callback<frame_object, &frame_object::repr>, /* tp_repr */
   0,				  /* tp_as_number */
   0,				  /* tp_as_sequence */
   0,				  /* tp_as_mapping */
   0,				  /* tp_hash  */
   0,				  /* tp_call */
-  frapy_str,			  /* tp_str */
+  wrap_tp_callback<frame_object, &frame_object::str>, /* tp_str */
   0,				  /* tp_getattro */
   0,				  /* tp_setattro */
   0,				  /* tp_as_buffer */

-- 
2.49.0


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

* [PATCH 2/4] Convert gdbpy_newest_frame and gdbpy_selected_frame to safety API
  2026-08-19 23:54 [PATCH 0/4] Convert py-frame.c to Python safety API Tom Tromey
  2026-08-19 23:54 ` [PATCH 1/4] Basic safety conversion of py-frame.c Tom Tromey
@ 2026-08-19 23:54 ` Tom Tromey
  2026-08-19 23:54 ` [PATCH 3/4] Convert gdbpy_frame_stop_reason_string to the " Tom Tromey
  2026-08-19 23:54 ` [PATCH 4/4] Convert frapy_richcompare to " Tom Tromey
  3 siblings, 0 replies; 5+ messages in thread
From: Tom Tromey @ 2026-08-19 23:54 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This convert gdbpy_newest_frame and gdbpy_selected_frame to the Python
safety API.  Some extra work was needed in py-inferior.c; note that
the code there is "temporary" -- once event emission is converted, the
try/catch can be removed.  Also, a new noargs_function wrapper was
needed.
---
 gdb/python/py-frame.c        | 44 ++++++++++++++++----------------------------
 gdb/python/py-inferior.c     | 24 ++++++++++++++++++------
 gdb/python/py-safety.h       | 21 +++++++++++++++++++++
 gdb/python/python-internal.h |  4 ++--
 gdb/python/python.c          |  8 ++++----
 5 files changed, 61 insertions(+), 40 deletions(-)

diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index 5c5ff8cf0de..3910f19ef83 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -438,41 +438,29 @@ frame_object::static_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 ()
 {
-  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 ();
+  /* FIXME: Python safety.  Convert frame_info_to_frame_object.  */
+  gdbpy_ref<> result = frame_info_to_frame_object (get_current_frame ());
+  if (result == nullptr)
+    throw gdb_python_exception ();
+  return result;
 }
 
 /* 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 ()
 {
-  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.");
+  /* FIXME: Python safety.  Convert frame_info_to_frame_object.  */
+  gdbpy_ref<> result = frame_info_to_frame_object (frame);
+  if (result == nullptr)
+    throw gdb_python_exception ();
+  return result;
 }
 
 /* Implementation of gdb.stop_reason_string (Integer) -> String.
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
index 426aec31e9e..780f7271c09 100644
--- a/gdb/python/py-inferior.c
+++ b/gdb/python/py-inferior.c
@@ -1023,13 +1023,25 @@ python_context_changed (user_selected_what selection)
     }
 
   gdbpy_ref<> frame_obj;
-  if (has_stack_frames ())
-    frame_obj = gdbpy_ref<> (gdbpy_selected_frame (nullptr, nullptr));
-  else
-    frame_obj = py_none ();
-
-  if (frame_obj == nullptr)
+  /* FIXME: Python safety.  Eventually this function will be converted
+     and this try/catch can be removed.  */
+  try
+    {
+      if (has_stack_frames ())
+	frame_obj = gdbpy_selected_frame ();
+      else
+	frame_obj = py_none ();
+    }
+  catch (const gdb_python_exception &e)
+    {
+      gdbpy_print_stack ();
+      return;
+    }
+  catch (const gdb_exception &exc)
     {
+      /* This is a bit roundabout but we're going to be deleting this
+	 code someday anyway.  */
+      (void) gdbpy_handle_gdb_exception (nullptr, exc);
       gdbpy_print_stack ();
       return;
     }
diff --git a/gdb/python/py-safety.h b/gdb/python/py-safety.h
index 3294f38c8b6..06324868817 100644
--- a/gdb/python/py-safety.h
+++ b/gdb/python/py-safety.h
@@ -233,6 +233,27 @@ varargs_wrapper (PyObject *self, PyObject *args, PyObject *kw)
 
 } /* namespace safety_details */
 
+/* Create a PyMethodDef for a no-argument function.  It takes the
+   underlying function F as template parameters, and the name and
+   documentation as arguments.  The function F is wrapped to call
+   to_python and to catch exceptions per the safety protocol.  F
+   should not accept any arguments.  */
+template<auto F>
+constexpr PyMethodDef
+noargs_function (const char *name, const char *doc)
+{
+  using namespace safety_details;
+  return {
+    name,
+    [] (PyObject *self, PyObject *args) -> PyObject *
+    {
+      return wrapped_function<F> ();
+    },
+    METH_NOARGS,
+    doc,
+  };
+}
+
 /* Create a PyMethodDef for a no-argument method.  It takes the
    underlying class C and a pointer-to-method M as template
    parameters, and the name and documentation as arguments.  The
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 5529e9fd4d7..76ccdc6b0c6 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -477,8 +477,8 @@ gdbpy_ref<> gdbpy_lookup_static_symbols (gdbpy_borrowed_ref<> 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_ref<> gdbpy_selected_frame ();
 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,
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 14c243b135e..72c2d7bb10d 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -3159,12 +3159,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,
+  noargs_function<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."),
+  noargs_function<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." },

-- 
2.49.0


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

* [PATCH 3/4] Convert gdbpy_frame_stop_reason_string to the safety API
  2026-08-19 23:54 [PATCH 0/4] Convert py-frame.c to Python safety API Tom Tromey
  2026-08-19 23:54 ` [PATCH 1/4] Basic safety conversion of py-frame.c Tom Tromey
  2026-08-19 23:54 ` [PATCH 2/4] Convert gdbpy_newest_frame and gdbpy_selected_frame to safety API Tom Tromey
@ 2026-08-19 23:54 ` Tom Tromey
  2026-08-19 23:54 ` [PATCH 4/4] Convert frapy_richcompare to " Tom Tromey
  3 siblings, 0 replies; 5+ messages in thread
From: Tom Tromey @ 2026-08-19 23:54 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This converts gdbpy_frame_stop_reason_string to the Python safety API.

This changes the function to accept keyword arguments as well,
following the outcome of an earlier discussion.  A new test is added
for this.
---
 gdb/python/py-frame.c                 | 20 ++++++++------------
 gdb/python/python-internal.h          |  3 ++-
 gdb/python/python.c                   |  4 ++--
 gdb/testsuite/gdb.python/py-frame.exp |  3 +++
 4 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index 3910f19ef83..4dd174cf5ae 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -466,24 +466,20 @@ gdbpy_selected_frame ()
 /* Implementation of gdb.stop_reason_string (Integer) -> String.
    Return a string explaining the unwind stop reason.  */
 
-PyObject *
-gdbpy_frame_stop_reason_string (PyObject *self, PyObject *args)
+const char *
+gdbpy_frame_stop_reason_string (gdbpy_borrowed_ref<> args,
+				gdbpy_opt_borrowed_ref<> kw)
 {
   int reason;
-  const char *str;
 
-  if (!PyArg_ParseTuple (args, "i", &reason))
-    return NULL;
+  static const char *keywords[] = { "reason", nullptr };
+  gdbpy_arg_parse_tuple_and_keywords (args, kw, "i", keywords, &reason);
 
   if (reason < UNWIND_FIRST || reason > UNWIND_LAST)
-    {
-      PyErr_SetString (PyExc_ValueError,
-		       _("Invalid frame stop reason."));
-      return NULL;
-    }
+    gdbpy_err_set_string (PyExc_ValueError,
+			  _("Invalid frame stop reason."));
 
-  str = unwind_stop_reason_to_string ((enum unwind_stop_reason) reason);
-  return PyUnicode_Decode (str, strlen (str), host_charset (), NULL);
+  return unwind_stop_reason_to_string ((enum unwind_stop_reason) reason);
 }
 
 /* Implements the equality comparison for Frame objects.
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 76ccdc6b0c6..5796b80037a 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -465,7 +465,8 @@ extern PyObject *gdbpy_history_count (PyObject *self, PyObject *args);
 PyObject *gdbpy_convenience_variable (PyObject *self, PyObject *args);
 PyObject *gdbpy_set_convenience_variable (PyObject *self, PyObject *args);
 PyObject *gdbpy_breakpoints (PyObject *, PyObject *);
-PyObject *gdbpy_frame_stop_reason_string (PyObject *, PyObject *);
+const char *gdbpy_frame_stop_reason_string (gdbpy_borrowed_ref<> args,
+					    gdbpy_opt_borrowed_ref<> kw);
 gdbpy_ref<> gdbpy_lookup_symbol (gdbpy_borrowed_ref<> args,
 				 gdbpy_opt_borrowed_ref<> kw);
 gdbpy_ref<> gdbpy_lookup_global_symbol (gdbpy_borrowed_ref<> args,
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 72c2d7bb10d..84e7c21377a 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -3165,9 +3165,9 @@ Return the newest frame object."),
   noargs_function<gdbpy_selected_frame> ("selected_frame",
     "selected_frame () -> gdb.Frame.\n\
 Return the selected frame object."),
-  { "frame_stop_reason_string", gdbpy_frame_stop_reason_string, METH_VARARGS,
+  varargs_function<gdbpy_frame_stop_reason_string> ("frame_stop_reason_string",
     "stop_reason_string (Integer) -> String.\n\
-Return a string explaining unwind stop reason." },
+Return a string explaining unwind stop reason."),
 
   { "start_recording", gdbpy_start_recording, METH_VARARGS,
     "start_recording ([method] [, format]) -> gdb.Record.\n\
diff --git a/gdb/testsuite/gdb.python/py-frame.exp b/gdb/testsuite/gdb.python/py-frame.exp
index aebec651964..30692ed6dd8 100644
--- a/gdb/testsuite/gdb.python/py-frame.exp
+++ b/gdb/testsuite/gdb.python/py-frame.exp
@@ -137,6 +137,9 @@ gdb_test "python print ('result = %s' % (f0.type () == gdb.NORMAL_FRAME))" " = T
 gdb_test "python print ('result = %s' % (f0.unwind_stop_reason () == gdb.FRAME_UNWIND_NO_REASON))" \
     " = True" "test Frame.unwind_stop_reason"
 gdb_test "python print ('result = %s' % gdb.frame_stop_reason_string (gdb.FRAME_UNWIND_INNER_ID))" " = previous frame inner to this frame \\(corrupt stack\\?\\)" "test gdb.frame_stop_reason_string"
+gdb_test "python print ('result = %s' % gdb.frame_stop_reason_string (reason=gdb.FRAME_UNWIND_INNER_ID))" \
+    " = previous frame inner to this frame \\(corrupt stack\\?\\)" \
+    "test gdb.frame_stop_reason_string with keyword"
 gdb_test "python print ('result = %s' % f0.pc ())" " = ${::decimal}" "test Frame.pc"
 gdb_test "python print ('result = %s' % (f0.older () == f1))" " = True" "test Frame.older"
 gdb_test "python print ('result = %s' % (f1.newer () == f0))" " = True" "test Frame.newer"

-- 
2.49.0


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

* [PATCH 4/4] Convert frapy_richcompare to safety API
  2026-08-19 23:54 [PATCH 0/4] Convert py-frame.c to Python safety API Tom Tromey
                   ` (2 preceding siblings ...)
  2026-08-19 23:54 ` [PATCH 3/4] Convert gdbpy_frame_stop_reason_string to the " Tom Tromey
@ 2026-08-19 23:54 ` Tom Tromey
  3 siblings, 0 replies; 5+ messages in thread
From: Tom Tromey @ 2026-08-19 23:54 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This converts frapy_richcompare to the Python safety API.  A new
wrap_richcompare template function is added.  As 'richcompare' can
return three results (or throw), the wrapped method returns a
std::optional<bool>; this is documented by the wrapper.

I considered a specialization of wrap_richcompare that automatically
ensures that the compared-to value is of the same class as 'this' --
this would be useful in a few (but not all) spots in gdb.  However
this seemed like a refinement that could easily be added later.

This also removes a part of a comment that I think is incorrect.
---
 gdb/python/py-frame.c  | 24 +++++++++++-------------
 gdb/python/py-safety.h | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+), 13 deletions(-)

diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index 4dd174cf5ae..4aedd7db563 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -141,6 +141,9 @@ struct frame_object : public PyObject
   /* The static link for this frame.  */
   gdbpy_ref<> static_link ();
 
+  /* Implementation of the Python richcompare API.  */
+  std::optional<bool> richcompare (gdbpy_borrowed_ref<> other, int op);
+
   static PyTypeObject *corresponding_object_type;
 };
 
@@ -482,30 +485,25 @@ gdbpy_frame_stop_reason_string (gdbpy_borrowed_ref<> args,
   return unwind_stop_reason_to_string ((enum unwind_stop_reason) reason);
 }
 
-/* Implements the equality comparison for Frame objects.
-   All other comparison operators will throw a TypeError Python exception,
-   as they aren't valid for frames.  */
+/* Implements the equality comparison for Frame objects.  */
 
-static PyObject *
-frapy_richcompare (PyObject *self, PyObject *other, int op)
+std::optional<bool>
+frame_object::richcompare (gdbpy_borrowed_ref<> other, int op)
 {
   int result;
 
   if (!PyObject_TypeCheck (other, &frame_object_type)
       || (op != Py_EQ && op != Py_NE))
-    return py_notimplemented ().release ();
+    return std::nullopt;
 
-  frame_object *self_frame = (frame_object *) self;
-  frame_object *other_frame = (frame_object *) other;
+  frame_object *other_frame = other;
 
-  if (self_frame->frame_id == other_frame->frame_id)
+  if (frame_id == other_frame->frame_id)
     result = Py_EQ;
   else
     result = Py_NE;
 
-  if (op == result)
-    return py_true ().release ();
-  return py_false ().release ();
+  return op == result;
 }
 
 PyTypeObject *frame_object::corresponding_object_type = &frame_object_type;
@@ -623,7 +621,7 @@ PyTypeObject frame_object_type = {
   "GDB frame object",		  /* tp_doc */
   0,				  /* tp_traverse */
   0,				  /* tp_clear */
-  frapy_richcompare,		  /* tp_richcompare */
+  wrap_richcompare<frame_object, &frame_object::richcompare>, /* tp_richcompare */
   0,				  /* tp_weaklistoffset */
   0,				  /* tp_iter */
   0,				  /* tp_iternext */
diff --git a/gdb/python/py-safety.h b/gdb/python/py-safety.h
index 06324868817..e2cd8bc3f30 100644
--- a/gdb/python/py-safety.h
+++ b/gdb/python/py-safety.h
@@ -364,4 +364,36 @@ wrap_setter (PyObject *arg, PyObject *value, void *closure)
   return 0;
 }
 
+/* A function that wraps a richcompare method.
+
+   A Python tp_richcompare function can either raise an exception,
+   return True or False, or return "not implemented".  The wrapped
+   method must return a std::optional<bool>, which allows all these
+   results: exceptions are simply thrown, true and false are ordinary
+   returns, and the return of an empty optional means "not
+   implemented".  */
+template<typename C, std::optional<bool> (C::*M) (gdbpy_borrowed_ref<>, int)>
+PyObject *
+wrap_richcompare (PyObject *arg, PyObject *value, int op)
+{
+  using namespace safety_details;
+  try
+    {
+      C *self = static_cast<C *> (arg);
+      std::optional<bool> result = (self->*M) (value, op);
+      if (result.has_value ())
+	return to_python (*result);
+      return py_notimplemented ().release ();
+    }
+  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);
+    }
+}
+
 #endif /* GDB_PYTHON_PY_SAFETY_H */

-- 
2.49.0


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

end of thread, other threads:[~2026-08-19 23:57 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-19 23:54 [PATCH 0/4] Convert py-frame.c to Python safety API Tom Tromey
2026-08-19 23:54 ` [PATCH 1/4] Basic safety conversion of py-frame.c Tom Tromey
2026-08-19 23:54 ` [PATCH 2/4] Convert gdbpy_newest_frame and gdbpy_selected_frame to safety API Tom Tromey
2026-08-19 23:54 ` [PATCH 3/4] Convert gdbpy_frame_stop_reason_string to the " Tom Tromey
2026-08-19 23:54 ` [PATCH 4/4] Convert frapy_richcompare to " Tom Tromey

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