Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH v2 0/6] gdb: minor fixes for Python limited C API support
@ 2026-01-27 17:02 Matthieu Longo
  2026-01-27 17:02 ` [PATCH v2 1/6] Python limited API: migrate Py_CompileStringExFlags and PyRun_SimpleString Matthieu Longo
                   ` (5 more replies)
  0 siblings, 6 replies; 24+ messages in thread
From: Matthieu Longo @ 2026-01-27 17:02 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey, Matthieu Longo

This patch series fixes a set of minor issues encountered while enabling the Python limited C API in GDB, and starts preparing the ground for migrating the existing C extension types from static types to heap-allocated ones, by removing the dependency on tp_dictoffset, which is unavailable when using the limited API. Those fixes were accumulated during the migration work, and are intentionally split out from an upcoming larger patch series. The first three patches in the series are small, and self-contained fixes. The next three ones continue the work undertaken in the third one, and have a dependency on previous patches.

All changes were tested by building GDB against the limited API and unlimited API, and no regressions were observed in the testsuite.

# Revision 1 -> 2

Patch series v1: https://inbox.sourceware.org/gdb-patches/20260107165606.1719366-1-matthieu.longo@arm.com
Major changes since the last revision:
 - addressed minor comments on patch 1 and 2.
 - patch 3: inherits from PyObject.
New:
 - patch 4: inherits from PyObject, but also had to provide a different approach to provide a dict because offsetof does not work anymore with inheritance. Anyway, this had to be fixed so two birds with one stone.
 - patch 5: enforces that the template type is a subclass of PyObject in gdbpy_ref_policy.
 - patch 6: finish migrating all the remaing declarations.

Regards,
Matthieu


Matthieu Longo (6):
  Python limited API: migrate Py_CompileStringExFlags and PyRun_SimpleString
  Python limited API: migrate PyImport_ExtendInittab
  gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF
  gdb: new setters and getters for __dict__, and attributes
  gdb: cast all Python extension objects passed to gdbpy_ref_policy to PyObject*
  gdb: make remaining Python extension objects inherit from PyObject

 gdb/python/py-arch.c          |  4 +-
 gdb/python/py-block.c         |  8 +--
 gdb/python/py-breakpoint.c    |  4 +-
 gdb/python/py-cmd.c           |  4 +-
 gdb/python/py-color.c         |  4 +-
 gdb/python/py-connection.c    |  4 +-
 gdb/python/py-corefile.c      | 26 +++-------
 gdb/python/py-disasm.c        | 16 ++----
 gdb/python/py-event.c         | 10 ++--
 gdb/python/py-event.h         |  8 +--
 gdb/python/py-events.h        |  4 +-
 gdb/python/py-frame.c         |  4 +-
 gdb/python/py-gdb-readline.c  |  5 +-
 gdb/python/py-inferior.c      | 29 ++---------
 gdb/python/py-infthread.c     | 10 ++--
 gdb/python/py-instruction.c   |  5 +-
 gdb/python/py-lazy-string.c   |  5 +-
 gdb/python/py-linetable.c     | 12 ++---
 gdb/python/py-membuf.c        |  5 +-
 gdb/python/py-micmd.c         |  4 +-
 gdb/python/py-objfile.c       | 18 +++----
 gdb/python/py-param.c         |  4 +-
 gdb/python/py-prettyprint.c   |  6 +--
 gdb/python/py-progspace.c     | 18 +++----
 gdb/python/py-record-btrace.c |  5 +-
 gdb/python/py-record.c        |  4 +-
 gdb/python/py-record.h        |  8 +--
 gdb/python/py-ref.h           | 47 ++++++++++++++++-
 gdb/python/py-registers.c     | 20 +++-----
 gdb/python/py-style.c         |  4 +-
 gdb/python/py-symbol.c        |  4 +-
 gdb/python/py-symtab.c        |  8 +--
 gdb/python/py-tui.c           |  4 +-
 gdb/python/py-type.c          | 26 ++++------
 gdb/python/py-unwind.c        |  8 +--
 gdb/python/py-utils.c         | 95 +++++++++++++++++++++++++++++++----
 gdb/python/py-value.c         |  4 +-
 gdb/python/python-internal.h  | 47 ++++++++++++-----
 gdb/python/python.c           | 27 ++++------
 39 files changed, 281 insertions(+), 247 deletions(-)

-- 
2.52.0


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

* [PATCH v2 1/6] Python limited API: migrate Py_CompileStringExFlags and PyRun_SimpleString
  2026-01-27 17:02 [PATCH v2 0/6] gdb: minor fixes for Python limited C API support Matthieu Longo
@ 2026-01-27 17:02 ` Matthieu Longo
  2026-01-27 17:54   ` Tom Tromey
  2026-01-27 17:02 ` [PATCH v2 2/6] Python limited API: migrate PyImport_ExtendInittab Matthieu Longo
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 24+ messages in thread
From: Matthieu Longo @ 2026-01-27 17:02 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey, Matthieu Longo

This patch replaces Py_CompileStringExFlags () with its limited C API
equivalent, Py_CompileString (). The eval_python_command () helper is
now exposed through the private GDB Python API as a utility function.
PyRun_SimpleString () is replaced with eval_python_command () to avoid
code duplication.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830
---
 gdb/python/py-gdb-readline.c |  5 +++--
 gdb/python/python-internal.h |  3 +++
 gdb/python/python.c          | 17 ++++++++---------
 3 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/gdb/python/py-gdb-readline.c b/gdb/python/py-gdb-readline.c
index f0fe967f073..e8e2c23547c 100644
--- a/gdb/python/py-gdb-readline.c
+++ b/gdb/python/py-gdb-readline.c
@@ -102,7 +102,7 @@ gdbpy_initialize_gdb_readline ()
      The third default finder is the one that will load readline, so the custom
      finder to disable the import of readline in GDB has to be placed before
      this third default finder.  */
-  if (PyRun_SimpleString ("\
+  const char *code = "\
 import sys\n\
 from importlib.abc import MetaPathFinder\n\
 \n\
@@ -113,7 +113,8 @@ class GdbRemoveReadlineFinder(MetaPathFinder):\n\
       raise ImportError(\"readline module disabled under GDB\")\n\
 \n\
 sys.meta_path.insert(2, GdbRemoveReadlineFinder())\n\
-") == 0)
+";
+  if (eval_python_command (code, Py_file_input) == 0)
     PyOS_ReadlineFunctionPointer = gdbpy_readline_wrapper;
 
   return 0;
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 85c76779a49..65d2eee38ed 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -1319,4 +1319,7 @@ class gdbpy_memoizing_registry_storage
   gdb::unordered_map<val_type *, obj_type *> m_objects;
 };
 
+extern int eval_python_command (const char *command, int start_symbol,
+				const char *filename = nullptr);
+
 #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 87fce272c42..989add70d1a 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -299,9 +299,9 @@ gdbpy_check_quit_flag (const struct extension_language_defn *extlang)
    NULL means that this is evaluating a string, not the contents of a
    file.  */
 
-static int
+int
 eval_python_command (const char *command, int start_symbol,
-		     const char *filename = nullptr)
+		     const char *filename)
 {
   PyObject *m, *d;
 
@@ -340,13 +340,12 @@ eval_python_command (const char *command, int start_symbol,
 	}
     }
 
-  /* Use this API because it is in Python 3.2.  */
-  gdbpy_ref<> code (Py_CompileStringExFlags (command,
-					     filename == nullptr
-					     ? "<string>"
-					     : filename,
-					     start_symbol,
-					     nullptr, -1));
+  /* Use this API because it is available with the Python limited API.  */
+  gdbpy_ref<> code (Py_CompileString (command,
+				      filename == nullptr
+				      ? "<string>"
+				      : filename,
+				      start_symbol));
 
   int result = -1;
   if (code != nullptr)
-- 
2.52.0


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

* [PATCH v2 2/6] Python limited API: migrate PyImport_ExtendInittab
  2026-01-27 17:02 [PATCH v2 0/6] gdb: minor fixes for Python limited C API support Matthieu Longo
  2026-01-27 17:02 ` [PATCH v2 1/6] Python limited API: migrate Py_CompileStringExFlags and PyRun_SimpleString Matthieu Longo
@ 2026-01-27 17:02 ` Matthieu Longo
  2026-01-27 17:54   ` Tom Tromey
  2026-01-27 17:02 ` [PATCH v2 3/6] gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF Matthieu Longo
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 24+ messages in thread
From: Matthieu Longo @ 2026-01-27 17:02 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey, Matthieu Longo

This patch replaces PyImport_ExtendInittab () with its limited C
API equivalent, PyImport_AppendInittab (), a convenience wrapper
around PyImport_ExtendInittab ().

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830
---
 gdb/python/python.c | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/gdb/python/python.c b/gdb/python/python.c
index 989add70d1a..79b5d24d1cf 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -2590,14 +2590,8 @@ do_start_initialization ()
 {
   /* Define all internal modules.  These are all imported (and thus
      created) during initialization.  */
-  struct _inittab mods[] =
-  {
-    { "_gdb", init__gdb_module },
-    { "_gdbevents", gdbpy_events_mod_func },
-    { nullptr, nullptr }
-  };
-
-  if (PyImport_ExtendInittab (mods) < 0)
+  if (PyImport_AppendInittab ("_gdb", init__gdb_module) < 0
+      || PyImport_AppendInittab ("_gdbevents", gdbpy_events_mod_func) < 0)
     return false;
 
   if (!py_initialize ())
-- 
2.52.0


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

* [PATCH v2 3/6] gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF
  2026-01-27 17:02 [PATCH v2 0/6] gdb: minor fixes for Python limited C API support Matthieu Longo
  2026-01-27 17:02 ` [PATCH v2 1/6] Python limited API: migrate Py_CompileStringExFlags and PyRun_SimpleString Matthieu Longo
  2026-01-27 17:02 ` [PATCH v2 2/6] Python limited API: migrate PyImport_ExtendInittab Matthieu Longo
@ 2026-01-27 17:02 ` Matthieu Longo
  2026-01-27 18:01   ` Tom Tromey
  2026-01-27 18:29   ` Tom Tromey
  2026-01-27 17:02 ` [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes Matthieu Longo
                   ` (2 subsequent siblings)
  5 siblings, 2 replies; 24+ messages in thread
From: Matthieu Longo @ 2026-01-27 17:02 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey, Matthieu Longo

When enabling the Python limited API, pointers to Python C extension
objects can no longer be implicitly converted to 'PyObject *' by the
compiler.

The lookup() method of gbdpy_registry returns a new reference to the
type object of the looked-up entry. It does so by calling Py_XINCREF()
to increment the reference counter of the returned type object. The
template parameter obj_type corresponds to the type of C extension
object type. With the Python limited API enabled, obj_type can no longer
be implicitly converted to 'PyObject *' when passed to Py_XINCREF().

This patch fixes the resulting compilation issue by adding an explicit
static_cast to 'PyObject *' before passing the value to Py_XINCREF().
As a side effect, this cast enforces, at compile time, that the template
type 'Storage::obj_type' passed to gdbpy_registry is a subclass of
PyObject. To provide a clearer diagnostic when an incorrect type is used,
a static_assert is added to gdbpy_registry, avoiding obscure errors
originating from the static_cast. Finally, the relevant C extension types
passed to gdbpy_registry are updated to inherit publicly from PyObject.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830
---
 gdb/python/py-symbol.c       | 4 ++--
 gdb/python/py-symtab.c       | 8 ++++----
 gdb/python/py-type.c         | 3 +--
 gdb/python/python-internal.h | 5 ++++-
 4 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/gdb/python/py-symbol.c b/gdb/python/py-symbol.c
index bd4023fa31a..29389739841 100644
--- a/gdb/python/py-symbol.c
+++ b/gdb/python/py-symbol.c
@@ -25,8 +25,8 @@
 #include "objfiles.h"
 #include "symfile.h"
 
-struct symbol_object {
-  PyObject_HEAD
+struct symbol_object: public PyObject
+{
   /* The GDB symbol structure this object is wrapping.  */
   struct symbol *symbol;
 };
diff --git a/gdb/python/py-symtab.c b/gdb/python/py-symtab.c
index 9c093e70fc8..04d2d7d0289 100644
--- a/gdb/python/py-symtab.c
+++ b/gdb/python/py-symtab.c
@@ -24,8 +24,8 @@
 #include "objfiles.h"
 #include "block.h"
 
-struct symtab_object {
-  PyObject_HEAD
+struct symtab_object: public PyObject
+{
   /* The GDB Symbol table structure.  */
   struct symtab *symtab;
 };
@@ -47,8 +47,8 @@ static const gdbpy_registry<gdbpy_memoizing_registry_storage<symtab_object,
       }							 \
   } while (0)
 
-struct sal_object {
-  PyObject_HEAD
+struct sal_object: public PyObject
+{
   /* The GDB Symbol table and line structure.  */
   struct symtab_and_line *sal;
   /* A Symtab and line object is associated with an objfile, so keep
diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c
index 46004b93acd..40adbf0bb7d 100644
--- a/gdb/python/py-type.c
+++ b/gdb/python/py-type.c
@@ -28,9 +28,8 @@
 #include "typeprint.h"
 #include "ada-lang.h"
 
-struct type_object
+struct type_object: public PyObject
 {
-  PyObject_HEAD
   struct type *type;
 };
 
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 65d2eee38ed..95619bf775e 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -1157,6 +1157,9 @@ class gdbpy_registry
   using obj_type = typename Storage::obj_type;
   using val_type = typename Storage::val_type;
 
+  static_assert(std::is_base_of<PyObject, obj_type>::value,
+		"obj_type must be a subclass of PyObject");
+
   /* Register Python object OBJ as being "owned" by OWNER.  When OWNER is
      about to be freed, OBJ will be invalidated.  */
   template <typename O>
@@ -1180,7 +1183,7 @@ class gdbpy_registry
   obj_type *lookup (O *owner, val_type *val) const
   {
     obj_type *obj = get_storage (owner)->lookup (val);
-    Py_XINCREF (obj);
+    Py_XINCREF (static_cast<PyObject *> (obj));
     return obj;
   }
 
-- 
2.52.0


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

* [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes
  2026-01-27 17:02 [PATCH v2 0/6] gdb: minor fixes for Python limited C API support Matthieu Longo
                   ` (2 preceding siblings ...)
  2026-01-27 17:02 ` [PATCH v2 3/6] gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF Matthieu Longo
@ 2026-01-27 17:02 ` Matthieu Longo
  2026-01-27 19:06   ` Tom Tromey
  2026-01-27 17:02 ` [PATCH v2 5/6] gdb: cast all Python extension objects passed to gdbpy_ref_policy to PyObject* Matthieu Longo
  2026-01-27 17:02 ` [PATCH v2 6/6] gdb: make remaining Python extension objects inherit from PyObject Matthieu Longo
  5 siblings, 1 reply; 24+ messages in thread
From: Matthieu Longo @ 2026-01-27 17:02 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey, Matthieu Longo

GDB is currently using the Python unlimited API. Migrating the codebase
to the Python limited API would have for benefit to make a GDB build
artifacts compatible with older and newer versions of Python that they
were built with.

This patch prepares the ground for migrating the existing C extension
types from static types to heap-allocated ones, by removing the
dependency on tp_dictoffset, which is unavailable when using the limited
API.

One of the most common incompatibilities in the current static type
declarations is the tp_dictoffset slot, which specifies the dictionary
offset within the instance structure. Historically, the unlimited
API has provided two approaches to supply a dictionary for __dict__:

 * A managed dictionary.
   Setting Py_TPFLAGS_MANAGED_DICT in tp_flags indicates that the
   instances of the type have a __dict__ attribute, and that the
   dictionary is managed by Python.
   According to the Python documentation, this is the recommended approach.
   However, this flag was introduced in 3.12, together with
   PyObject_VisitManagedDict() and PyObject_ClearManagedDict(), neither
   of which is part of the limited API (at least for now). As a result,
   this recommended approach is not viable in the context of the limited
   API.

 * An instance dictionary, for which the offset in the instance is
   provided via tp_dictoffset.
   According to the Python documentation, this "tp slot" is on the
   deprecation path, and Py_TPFLAGS_MANAGED_DICT should be used instead.
   Given the age of the GDB codebase and the requirement to support older
   Python versions (>= 3.4), no need to argue that today, the implementation
   of __dict__ relies on tp_dictoffset. However, in the context of the
   limited API, PyType_Slot does not provide a Py_tp_dictoffset member, so
   another approach is needed to provide __dict__ to instances of C extension
   types.

Given the constraints of the limited API, the proposed solution consists
in providing a dictionary through a common base class, gdbpy__dict__wrapper.
This helper class owns a dictionary member corresponding to __dict__, and
any C extension type requiring a __dict__ must inherit from it. Since
extension object must also be convertible to PyObject, this wrapper class
publicly inherits from PyObject as well.
Access to the dictionary is provided via a custom getter defined in a
PyGetSetDef, similarily to what was previously done with gdb_py_generic_dict().
Because __dict__ participates in attribute look-up, and since this dictionary
is neither managed by Python nor exposed via tp_dictoffset, custom
implementations of tp_getattro and tp_setattro are required to correctly
redirect attribute look-ups to the dictionary. These custom implementations
— equivalent to PyObject_GenericGetAttr() and PyObject_GenericSetAttr() —
must be installed via tp_getattro / tp_setattro for static types, or
Py_tp_getattro / Py_tp_setattro for heap-allocated types.

- gdbpy__dict__wrapper: a base class for C extension objects that own a
  __dict__.
- gdb_py_generic_dict_getter: a __dict__ getter for extension types
  derived from gdbpy__dict__wrapper.
- gdb_py_generic_dict_setter: a __dict__ setter provided for completeness.
  It should not be used, as __dict__ should be read-only after the object
  initialization.
- gdb_py_generic_getattro: equivalent of PyObject_GenericGetAttr, but
  fixes the look-up of __dict__.
- gdb_py_generic_setattro: equivalent of PyObject_GenericSetAttr, but
  fixes the look-up of __dict__.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830
---
 gdb/python/py-corefile.c     | 18 +++----
 gdb/python/py-event.c        | 10 ++--
 gdb/python/py-event.h        |  8 +--
 gdb/python/py-inferior.c     | 29 ++---------
 gdb/python/py-infthread.c    | 10 ++--
 gdb/python/py-objfile.c      | 18 +++----
 gdb/python/py-progspace.c    | 18 +++----
 gdb/python/py-ref.h          | 40 +++++++++++++++
 gdb/python/py-type.c         | 19 +++-----
 gdb/python/py-utils.c        | 95 +++++++++++++++++++++++++++++++-----
 gdb/python/python-internal.h | 35 +++++++++----
 11 files changed, 192 insertions(+), 108 deletions(-)

diff --git a/gdb/python/py-corefile.c b/gdb/python/py-corefile.c
index 6847722628f..4a6982231c8 100644
--- a/gdb/python/py-corefile.c
+++ b/gdb/python/py-corefile.c
@@ -26,20 +26,14 @@
 
 /* A gdb.Corefile object.  */
 
-struct corefile_object
+struct corefile_object: public gdbpy__dict__wrapper
 {
-  PyObject_HEAD
-
   /* The inferior this core file is attached to.  This will be set to NULL
      when the inferior is deleted, or if a different core file is loaded
      for the inferior.  When this is NULL the gdb.Corefile object is
      considered invalid.*/
   struct inferior *inferior;
 
-  /* Dictionary holding user-added attributes.  This is the __dict__
-     attribute of the object.  This is an owning reference.  */
-  PyObject *dict;
-
   /* A Tuple of gdb.CorefileMappedFile objects.  This tuple is only created
      the first time the user calls gdb.Corefile.mapped_files(), the result
      is cached here.  If this pointer is not NULL then this is an owning
@@ -511,8 +505,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_corefile);
 
 static gdb_PyGetSetDef corefile_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, nullptr,
-    "The __dict__ for the gdb.Corefile.", &corefile_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, nullptr,
+    "The __dict__ for the gdb.Corefile.", nullptr },
   { "filename", cfpy_get_filename, nullptr,
     "The filename of a valid Corefile object.", nullptr },
   { nullptr }
@@ -548,8 +542,8 @@ PyTypeObject corefile_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB corefile object",	  /* tp_doc */
@@ -566,7 +560,7 @@ PyTypeObject corefile_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (corefile_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0,				  /* tp_alloc */
   0,				  /* tp_new */
diff --git a/gdb/python/py-event.c b/gdb/python/py-event.c
index c985159a6f7..8fb21b7fdad 100644
--- a/gdb/python/py-event.c
+++ b/gdb/python/py-event.c
@@ -101,8 +101,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_event);
 
 static gdb_PyGetSetDef event_object_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, NULL,
-    "The __dict__ for this event.", &event_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, NULL,
+    "The __dict__ for this event.", NULL },
   { NULL }
 };
 
@@ -124,8 +124,8 @@ PyTypeObject event_object_type =
   0,                                          /* tp_hash  */
   0,                                          /* tp_call */
   0,                                          /* tp_str */
-  0,                                          /* tp_getattro */
-  0,                                          /* tp_setattro */
+  gdb_py_generic_getattro,                    /* tp_getattro */
+  gdb_py_generic_setattro,                    /* tp_setattro */
   0,                                          /* tp_as_buffer */
   Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */
   "GDB event object",                         /* tp_doc */
@@ -142,7 +142,7 @@ PyTypeObject event_object_type =
   0,                                          /* tp_dict */
   0,                                          /* tp_descr_get */
   0,                                          /* tp_descr_set */
-  offsetof (event_object, dict),              /* tp_dictoffset */
+  0,                                          /* tp_dictoffset */
   0,                                          /* tp_init */
   0                                           /* tp_alloc */
 };
diff --git a/gdb/python/py-event.h b/gdb/python/py-event.h
index 6c81d64eb4f..d21ca1e2873 100644
--- a/gdb/python/py-event.h
+++ b/gdb/python/py-event.h
@@ -31,12 +31,8 @@
 #include "py-event-types.def"
 #undef GDB_PY_DEFINE_EVENT_TYPE
 
-struct event_object
-{
-  PyObject_HEAD
-
-  PyObject *dict;
-};
+struct event_object: public gdbpy__dict__wrapper
+{};
 
 extern int emit_continue_event (ptid_t ptid);
 extern int emit_exited_event (const LONGEST *exit_code, struct inferior *inf);
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
index 8230f9d3943..69a7bfa3c37 100644
--- a/gdb/python/py-inferior.c
+++ b/gdb/python/py-inferior.c
@@ -32,25 +32,6 @@
 #include "progspace-and-thread.h"
 #include "gdbsupport/unordered_map.h"
 
-using thread_map_t
-  = gdb::unordered_map<thread_info *, gdbpy_ref<thread_object>>;
-
-struct inferior_object
-{
-  PyObject_HEAD
-
-  /* The inferior we represent.  */
-  struct inferior *inferior;
-
-  /* thread_object instances under this inferior.  This owns a
-     reference to each object it contains.  */
-  thread_map_t *threads;
-
-  /* Dictionary holding user-added attributes.
-     This is the __dict__ attribute of the object.  */
-  PyObject *dict;
-};
-
 extern PyTypeObject inferior_object_type;
 
 /* Deleter to clean up when an inferior is removed.  */
@@ -1061,8 +1042,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_inferior);
 
 static gdb_PyGetSetDef inferior_object_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, nullptr,
-    "The __dict__ for this inferior.", &inferior_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, nullptr,
+    "The __dict__ for this inferior.", nullptr },
   { "arguments", infpy_get_args, infpy_set_args,
     "Arguments to this program.", nullptr },
   { "num", infpy_get_num, NULL, "ID of inferior, as assigned by GDB.", NULL },
@@ -1144,8 +1125,8 @@ PyTypeObject inferior_object_type =
   0,				  /* tp_hash  */
   0,				  /* tp_call */
   0,				  /* tp_str */
-  0,				  /* tp_getattro */
-  0,				  /* tp_setattro */
+  gdb_py_generic_getattro,	  /* tp_getattro */
+  gdb_py_generic_setattro,	  /* tp_setattro */
   0,				  /* tp_as_buffer */
   Py_TPFLAGS_DEFAULT,		  /* tp_flags */
   "GDB inferior object",	  /* tp_doc */
@@ -1162,7 +1143,7 @@ PyTypeObject inferior_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (inferior_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0				  /* tp_alloc */
 };
diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c
index e5d3222f9ae..d75742360d4 100644
--- a/gdb/python/py-infthread.c
+++ b/gdb/python/py-infthread.c
@@ -415,8 +415,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_thread);
 
 static gdb_PyGetSetDef thread_object_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, nullptr,
-    "The __dict__ for this thread.", &thread_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, nullptr,
+    "The __dict__ for this thread.", nullptr },
   { "name", thpy_get_name, thpy_set_name,
     "The name of the thread, as set by the user or the OS.", NULL },
   { "details", thpy_get_details, NULL,
@@ -479,8 +479,8 @@ PyTypeObject thread_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB thread object",		  /* tp_doc */
@@ -497,7 +497,7 @@ PyTypeObject thread_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (thread_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0				  /* tp_alloc */
 };
diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
index 8cf365a27dc..802b85144ac 100644
--- a/gdb/python/py-objfile.c
+++ b/gdb/python/py-objfile.c
@@ -26,17 +26,11 @@
 #include "python.h"
 #include "inferior.h"
 
-struct objfile_object
+struct objfile_object: public gdbpy__dict__wrapper
 {
-  PyObject_HEAD
-
   /* The corresponding objfile.  */
   struct objfile *objfile;
 
-  /* Dictionary holding user-added attributes.
-     This is the __dict__ attribute of the object.  */
-  PyObject *dict;
-
   /* The pretty-printer list of functions.  */
   PyObject *printers;
 
@@ -739,8 +733,8 @@ Look up a static-linkage global symbol in this objfile and return it." },
 
 static gdb_PyGetSetDef objfile_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, NULL,
-    "The __dict__ for this objfile.", &objfile_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, NULL,
+    "The __dict__ for this objfile.", NULL },
   { "filename", objfpy_get_filename, NULL,
     "The objfile's filename, or None.", NULL },
   { "username", objfpy_get_username, NULL,
@@ -785,8 +779,8 @@ PyTypeObject objfile_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB objfile object",		  /* tp_doc */
@@ -803,7 +797,7 @@ PyTypeObject objfile_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (objfile_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0,				  /* tp_alloc */
   objfpy_new,			  /* tp_new */
diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
index ee26b761adb..a531026655f 100644
--- a/gdb/python/py-progspace.c
+++ b/gdb/python/py-progspace.c
@@ -29,17 +29,11 @@
 #include "observable.h"
 #include "inferior.h"
 
-struct pspace_object
+struct pspace_object: public gdbpy__dict__wrapper
 {
-  PyObject_HEAD
-
   /* The corresponding pspace.  */
   struct program_space *pspace;
 
-  /* Dictionary holding user-added attributes.
-     This is the __dict__ attribute of the object.  */
-  PyObject *dict;
-
   /* The pretty-printer list of functions.  */
   PyObject *printers;
 
@@ -758,8 +752,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_pspace);
 
 static gdb_PyGetSetDef pspace_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, NULL,
-    "The __dict__ for this progspace.", &pspace_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, NULL,
+    "The __dict__ for this progspace.", NULL },
   { "filename", pspy_get_filename, NULL,
     "The filename of the progspace's main symbol file, or None.", nullptr },
   { "symbol_file", pspy_get_symbol_file, nullptr,
@@ -821,8 +815,8 @@ PyTypeObject pspace_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB progspace object",	  /* tp_doc */
@@ -839,7 +833,7 @@ PyTypeObject pspace_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (pspace_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0,				  /* tp_alloc */
   0,				  /* tp_new */
diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h
index b09d88dc30d..a8f3ff9201b 100644
--- a/gdb/python/py-ref.h
+++ b/gdb/python/py-ref.h
@@ -42,4 +42,44 @@ struct gdbpy_ref_policy
 template<typename T = PyObject> using gdbpy_ref
   = gdb::ref_ptr<T, gdbpy_ref_policy<T>>;
 
+/* A wrapper class for Python extension objects that have a __dict__ attribute.
+
+   Any Python C object extension needing __dict__ should inherit from this
+   class. Given that the C extension object must also be convertible to
+   PyObject, this wrapper class publicly inherits from PyObject as well.
+
+   Access to the dict requires a custom getter defined via PyGetSetDef.
+     gdb_PyGetSetDef my_object_getset[] =
+     {
+       { "__dict__", gdb_py_generic_dict_getter, nullptr,
+         "The __dict__ for this object.", nullptr },
+       ...
+       { nullptr }
+     };
+
+   It is also important to note that __dict__ is used during the attribute
+   look-up. Since this dictionary is not managed by Python and is not exposed
+   via tp_dictoffset, custom attribute getter (tp_getattro) and setter
+   (tp_setattro) are required to correctly redirect attribute access to the
+   dictionary:
+     - gdb_py_generic_getattro (), assigned to tp_getattro for static types,
+       or Py_tp_getattro for heap-allocated types.
+     - gdb_py_generic_setattro (), assigned to tp_setattro for static types,
+       or Py_tp_setattro for heap-allocated types.  */
+struct gdbpy__dict__wrapper: public PyObject
+{
+  /* Dictionary holding user-added attributes.
+     This is the __dict__ attribute of the object.  */
+  PyObject *dict;
+
+  /* Compute the address of the __dict__ attribute for the given PyObject.
+     The CLOSURE argument is unused.  */
+  static PyObject **
+  compute_addr (PyObject *self, void *closure ATTRIBUTE_UNUSED)
+  {
+    auto *wrapper = reinterpret_cast<gdbpy__dict__wrapper *> (self);
+    return &wrapper->dict;
+  }
+};
+
 #endif /* GDB_PYTHON_PY_REF_H */
diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c
index 40adbf0bb7d..1cc583b38d2 100644
--- a/gdb/python/py-type.c
+++ b/gdb/python/py-type.c
@@ -36,13 +36,8 @@ struct type_object: public PyObject
 extern PyTypeObject type_object_type;
 
 /* A Field object.  */
-struct field_object
-{
-  PyObject_HEAD
-
-  /* Dictionary holding our attributes.  */
-  PyObject *dict;
-};
+struct field_object: public gdbpy__dict__wrapper
+{};
 
 extern PyTypeObject field_object_type;
 
@@ -1707,8 +1702,8 @@ PyTypeObject type_object_type =
 
 static gdb_PyGetSetDef field_object_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, NULL,
-    "The __dict__ for this field.", &field_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, NULL,
+    "The __dict__ for this field.", NULL },
   { NULL }
 };
 
@@ -1730,8 +1725,8 @@ PyTypeObject field_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB field object",		  /* tp_doc */
@@ -1748,7 +1743,7 @@ PyTypeObject field_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (field_object, dict),  /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0,				  /* tp_alloc */
   0,				  /* tp_new */
diff --git a/gdb/python/py-utils.c b/gdb/python/py-utils.c
index 131230f80b3..81c787ac43c 100644
--- a/gdb/python/py-utils.c
+++ b/gdb/python/py-utils.c
@@ -309,24 +309,97 @@ gdb_py_int_as_long (PyObject *obj, long *result)
 
 \f
 
-/* Generic implementation of the __dict__ attribute for objects that
-   have a dictionary.  The CLOSURE argument should be the type object.
-   This only handles positive values for tp_dictoffset.  */
+/* Generic implementation of the getter for the __dict__ attribute for objects
+   having a dictionary.  The CLOSURE argument is unused.  */
 
 PyObject *
-gdb_py_generic_dict (PyObject *self, void *closure)
+gdb_py_generic_dict_getter (PyObject *self, void *closure)
 {
-  PyObject *result;
-  PyTypeObject *type_obj = (PyTypeObject *) closure;
-  char *raw_ptr;
+  PyObject **py_dict_ptr = gdbpy__dict__wrapper::compute_addr (self, closure);
+  PyObject *py_dict = *py_dict_ptr;
+  if (py_dict == nullptr)
+    {
+      PyErr_SetString (PyExc_AttributeError,
+		       "This object has no __dict__");
+      return nullptr;
+    }
+  return Py_NewRef (py_dict);
+}
 
-  raw_ptr = (char *) self + type_obj->tp_dictoffset;
-  result = * (PyObject **) raw_ptr;
+/* Generic implementation of the setter for the __dict__ attribute for objects
+   having a dictionary.  The CLOSURE argument is unused.  */
 
-  Py_INCREF (result);
-  return result;
+int
+gdb_py_generic_dict_setter (PyObject *self, PyObject *value, void *closure)
+{
+  if (value == nullptr)
+    {
+      PyErr_SetString (PyExc_TypeError, "cannot delete __dict__");
+      return -1;
+    }
+
+  PyObject **py_dict_ptr = gdbpy__dict__wrapper::compute_addr (self, closure);
+
+  /* Delete the old value (if there is one).  */
+  if (*py_dict_ptr != nullptr)
+    Py_DECREF (*py_dict_ptr);
+
+  *py_dict_ptr = Py_NewRef (value);
+  return 0;
+}
+
+/* Generic attribute getter function similar to PyObject_GenericGetAttr () but
+   that should be used when the object has a dictionary __dict__.  */
+PyObject *
+gdb_py_generic_getattro (PyObject *self, PyObject *attr)
+{
+  PyObject *value = PyObject_GenericGetAttr (self, attr);
+  if (value != nullptr)
+    return value;
+
+  if (! PyErr_ExceptionMatches (PyExc_AttributeError))
+    return nullptr;
+
+  /* Clear previous AttributeError set by PyObject_GenericGetAttr.  */
+  PyErr_Clear();
+
+  gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr));
+  value = PyDict_GetItemWithError (dict.get (), attr);
+  if (value != nullptr)
+    return Py_NewRef (value);
+
+  PyErr_Format (PyExc_AttributeError,
+		"'%s' object has no attribute '%s'",
+		Py_TYPE (self)->tp_name,
+		PyUnicode_AsUTF8AndSize (attr, nullptr));
+  return nullptr;
 }
 
+/* Generic attribute setter function similar to PyObject_GenericSetAttr () but
+   that should be used when the object has a dictionary __dict__.  */
+int
+gdb_py_generic_setattro (PyObject *self, PyObject *attr, PyObject *value)
+{
+  if (PyObject_GenericSetAttr (self, attr, value) == 0)
+    return 0;
+
+  if (! PyErr_ExceptionMatches (PyExc_AttributeError))
+    return -1;
+
+  /* Clear previous AttributeError set by PyObject_GenericGetAttr.  */
+  PyErr_Clear();
+
+  gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr));
+  /* Delete the old value (if there is one).  */
+  PyObject *old_value = PyDict_GetItem (dict.get (), attr);
+  if (old_value != nullptr)
+    Py_DECREF (old_value);
+  /* Set the new value.  */
+  return PyDict_SetItem (dict.get (), attr, value);
+}
+
+\f
+
 /* Like PyModule_AddObject, but does not steal a reference to
    OBJECT.  */
 
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 95619bf775e..cde2dfa2d52 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -107,6 +107,15 @@ typedef unsigned long gdb_py_ulongest;
 
 #endif /* HAVE_LONG_LONG */
 
+#if PY_VERSION_HEX < 0x030a0000
+static inline PyObject*
+Py_NewRef (PyObject *obj)
+{
+  Py_INCREF (obj);
+  return obj;
+}
+#endif
+
 /* A template variable holding the format character (as for
    Py_BuildValue) for a given type.  */
 template<typename T>
@@ -384,22 +393,27 @@ struct gdbpy_breakpoint_object
 extern gdbpy_breakpoint_object *bppy_pending_object;
 
 
-struct thread_object
+struct thread_object: public gdbpy__dict__wrapper
 {
-  PyObject_HEAD
-
   /* The thread we represent.  */
   struct thread_info *thread;
 
   /* The Inferior object to which this thread belongs.  */
   PyObject *inf_obj;
-
-  /* Dictionary holding user-added attributes.  This is the __dict__
-     attribute of the object.  */
-  PyObject *dict;
 };
 
-struct inferior_object;
+using thread_map_t
+  = gdb::unordered_map<thread_info *, gdbpy_ref<thread_object>>;
+
+struct inferior_object: public gdbpy__dict__wrapper
+{
+  /* The inferior we represent.  */
+  struct inferior *inferior;
+
+  /* thread_object instances under this inferior.  This owns a
+     reference to each object it contains.  */
+  thread_map_t *threads;
+};
 
 extern struct cmd_list_element *set_python_list;
 extern struct cmd_list_element *show_python_list;
@@ -989,7 +1003,10 @@ gdbpy_ref<> gdb_py_object_from_longest (LONGEST l);
 gdbpy_ref<> gdb_py_object_from_ulongest (ULONGEST l);
 int gdb_py_int_as_long (PyObject *, long *);
 
-PyObject *gdb_py_generic_dict (PyObject *self, void *closure);
+PyObject *gdb_py_generic_dict_getter (PyObject *self, void *closure);
+int gdb_py_generic_dict_setter (PyObject *self, PyObject *value, void *closure);
+PyObject *gdb_py_generic_getattro (PyObject *self, PyObject *attr);
+int gdb_py_generic_setattro (PyObject *self, PyObject *attr, PyObject *value);
 
 int gdb_pymodule_addobject (PyObject *mod, const char *name,
 			    PyObject *object);
-- 
2.52.0


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

* [PATCH v2 5/6] gdb: cast all Python extension objects passed to gdbpy_ref_policy to PyObject*
  2026-01-27 17:02 [PATCH v2 0/6] gdb: minor fixes for Python limited C API support Matthieu Longo
                   ` (3 preceding siblings ...)
  2026-01-27 17:02 ` [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes Matthieu Longo
@ 2026-01-27 17:02 ` Matthieu Longo
  2026-01-27 18:28   ` Tom Tromey
  2026-01-27 17:02 ` [PATCH v2 6/6] gdb: make remaining Python extension objects inherit from PyObject Matthieu Longo
  5 siblings, 1 reply; 24+ messages in thread
From: Matthieu Longo @ 2026-01-27 17:02 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey, Matthieu Longo

When enabling the Python limited API, pointers to Python C extension
objects can no longer be implicitly converted to 'PyObject *' by the
compiler.

gdbpy_ref_policy is a templated class that provides a generic interface
for incrementing and decrementing the reference counter on the given
object. It is used as a specialisation of the policy parameter in
gdb::ref_ptr, together with PyObject as the parameter type. As a result,
gdbpy_ref_policy always expects an argument derived from PyObject.

This patch fixes the resulting compilation issue by adding an explicit
static_cast to 'PyObject *' before passing the value to Py_INCREF and
Py_DECREF. As a side effect, these casts enforce, at compile time, that
the template type passed to gdbpy_ref_policy is a subclass of PyObject.
To provide a clearer diagnostic when an incorrect type is used, a
static_assert is added to gdbpy_ref_policy, avoiding obscure errors
originating from the static_cast. Finally, all C Python extension types
passed to gdbpy_ref_policy are updated to inherit from PyObject.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830
---
 gdb/python/py-breakpoint.c   |  4 +---
 gdb/python/py-cmd.c          |  4 +---
 gdb/python/py-color.c        |  4 +---
 gdb/python/py-connection.c   |  4 +---
 gdb/python/py-corefile.c     |  8 ++------
 gdb/python/py-disasm.c       |  8 ++------
 gdb/python/py-events.h       |  4 +---
 gdb/python/py-frame.c        |  4 ++--
 gdb/python/py-membuf.c       |  5 ++---
 gdb/python/py-micmd.c        |  4 +---
 gdb/python/py-ref.h          |  7 +++++--
 gdb/python/py-registers.c    | 10 ++++------
 gdb/python/py-tui.c          |  4 +---
 gdb/python/python-internal.h |  4 +---
 14 files changed, 25 insertions(+), 49 deletions(-)

diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index fc53213966a..3953c441c5c 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -37,10 +37,8 @@
 
 extern PyTypeObject breakpoint_location_object_type;
 
-struct gdbpy_breakpoint_location_object
+struct gdbpy_breakpoint_location_object: public PyObject
 {
-  PyObject_HEAD
-
   /* An owning reference to the gdb breakpoint location object.  */
   bp_location *bp_loc;
 
diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c
index 6b40b1796c6..88bc150b765 100644
--- a/gdb/python/py-cmd.c
+++ b/gdb/python/py-cmd.c
@@ -50,10 +50,8 @@ static const struct cmdpy_completer completers[] =
 
 /* A gdb command.  For the time being only ordinary commands (not
    set/show commands) are allowed.  */
-struct cmdpy_object
+struct cmdpy_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The corresponding gdb command object, or NULL if the command is
      no longer installed.  */
   struct cmd_list_element *command;
diff --git a/gdb/python/py-color.c b/gdb/python/py-color.c
index b41dff2edd7..e174a7b6858 100644
--- a/gdb/python/py-color.c
+++ b/gdb/python/py-color.c
@@ -37,10 +37,8 @@ static struct {
 };
 
 /* A color.  */
-struct colorpy_object
+struct colorpy_object: public PyObject
 {
-  PyObject_HEAD
-
   /* Underlying value.  */
   ui_file_style::color color;
 };
diff --git a/gdb/python/py-connection.c b/gdb/python/py-connection.c
index bef50b07f18..e42939a995d 100644
--- a/gdb/python/py-connection.c
+++ b/gdb/python/py-connection.c
@@ -31,10 +31,8 @@
 
 /* The Python object that represents a connection.  */
 
-struct connection_object
+struct connection_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The process target that represents this connection.   When a
      connection_object is created this field will always point at a valid
      target.  Later, if GDB stops using this target (the target is popped
diff --git a/gdb/python/py-corefile.c b/gdb/python/py-corefile.c
index 4a6982231c8..622456aaae3 100644
--- a/gdb/python/py-corefile.c
+++ b/gdb/python/py-corefile.c
@@ -45,10 +45,8 @@ extern PyTypeObject corefile_object_type;
 
 /* A gdb.CorefileMapped object.  */
 
-struct corefile_mapped_file_object
+struct corefile_mapped_file_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The name of a file that was mapped when the core file was created.
      This is a 'str' object.  */
   PyObject *filename;
@@ -70,10 +68,8 @@ extern PyTypeObject corefile_mapped_file_object_type;
 
 /* A gdb.CorefileMappedFileRegion object.  */
 
-struct corefile_mapped_file_region_object
+struct corefile_mapped_file_region_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The start and end addresses for this mapping, these are addresses
      within the inferior's address space.  */
   CORE_ADDR start;
diff --git a/gdb/python/py-disasm.c b/gdb/python/py-disasm.c
index c1aa4d0586a..037223f05e3 100644
--- a/gdb/python/py-disasm.c
+++ b/gdb/python/py-disasm.c
@@ -28,10 +28,8 @@
 /* Implement gdb.disassembler.DisassembleInfo type.  An object of this type
    represents a single disassembler request from GDB.  */
 
-struct disasm_info_object
+struct disasm_info_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The architecture in which we are disassembling.  */
   struct gdbarch *gdbarch;
 
@@ -99,10 +97,8 @@ extern PyTypeObject disasm_part_object_type;
    the disassembled instruction (in bytes), and the string representing the
    disassembled instruction.  */
 
-struct disasm_result_object
+struct disasm_result_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The length of the disassembled instruction in bytes.  */
   int length;
 
diff --git a/gdb/python/py-events.h b/gdb/python/py-events.h
index 9fc7a86920e..a923a923e56 100644
--- a/gdb/python/py-events.h
+++ b/gdb/python/py-events.h
@@ -27,10 +27,8 @@
 /* Stores a list of objects to be notified when the event for which this
    registry tracks occurs.  */
 
-struct eventregistry_object
+struct eventregistry_object: public PyObject
 {
-  PyObject_HEAD
-
   PyObject *callbacks;
 };
 
diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index 027ccb4112d..bb61b058671 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -28,8 +28,8 @@
 #include "symfile.h"
 #include "objfiles.h"
 
-struct frame_object {
-  PyObject_HEAD
+struct frame_object: public PyObject
+{
   struct frame_id frame_id;
   struct gdbarch *gdbarch;
 
diff --git a/gdb/python/py-membuf.c b/gdb/python/py-membuf.c
index 832ab62cf52..46033a0dda3 100644
--- a/gdb/python/py-membuf.c
+++ b/gdb/python/py-membuf.c
@@ -19,9 +19,8 @@
 
 #include "python-internal.h"
 
-struct membuf_object {
-  PyObject_HEAD
-
+struct membuf_object: public PyObject
+{
   /* Pointer to the raw data, and array of gdb_bytes.  */
   void *buffer;
 
diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c
index c6a96dc3ad5..2b4b083d0e3 100644
--- a/gdb/python/py-micmd.c
+++ b/gdb/python/py-micmd.c
@@ -55,10 +55,8 @@ struct mi_command_py;
 
 /* Representation of a Python gdb.MICommand object.  */
 
-struct micmdpy_object
+struct micmdpy_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The object representing this command in the MI command table.  This
      pointer can be nullptr if the command is not currently installed into
      the MI command table (see gdb.MICommand.installed property).  */
diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h
index a8f3ff9201b..7594d4a9fc3 100644
--- a/gdb/python/py-ref.h
+++ b/gdb/python/py-ref.h
@@ -26,14 +26,17 @@
 template<typename T>
 struct gdbpy_ref_policy
 {
+  static_assert(std::is_base_of<PyObject, T>::value,
+		"T must be a subclass of PyObject");
+
   static void incref (T *ptr)
   {
-    Py_INCREF (ptr);
+    Py_INCREF (static_cast<PyObject *> (ptr));
   }
 
   static void decref (T *ptr)
   {
-    Py_DECREF (ptr);
+    Py_DECREF (static_cast<PyObject *> (ptr));
   }
 };
 
diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
index ac7480caea9..30b333c444c 100644
--- a/gdb/python/py-registers.c
+++ b/gdb/python/py-registers.c
@@ -49,9 +49,8 @@ struct register_descriptor_iterator_object {
 extern PyTypeObject register_descriptor_iterator_object_type;
 
 /* A register descriptor.  */
-struct register_descriptor_object {
-  PyObject_HEAD
-
+struct register_descriptor_object: public PyObject
+{
   /* The register this is a descriptor for.  */
   int regnum;
 
@@ -75,9 +74,8 @@ struct reggroup_iterator_object {
 extern PyTypeObject reggroup_iterator_object_type;
 
 /* A register group object.  */
-struct reggroup_object {
-  PyObject_HEAD
-
+struct reggroup_object: public PyObject
+{
   /* The register group being described.  */
   const struct reggroup *reggroup;
 };
diff --git a/gdb/python/py-tui.c b/gdb/python/py-tui.c
index 578ddfbcc67..be19193770f 100644
--- a/gdb/python/py-tui.c
+++ b/gdb/python/py-tui.c
@@ -44,10 +44,8 @@ class tui_py_window;
 
 /* A PyObject representing a TUI window.  */
 
-struct gdbpy_tui_window
+struct gdbpy_tui_window: public PyObject
 {
-  PyObject_HEAD
-
   /* The TUI window, or nullptr if the window has been deleted.  */
   tui_py_window *window;
 
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index cde2dfa2d52..1b4dcd4c5fe 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -350,10 +350,8 @@ extern PyTypeObject thread_object_type;
 
 extern bool gdbpy_breakpoint_init_breakpoint_type ();
 
-struct gdbpy_breakpoint_object
+struct gdbpy_breakpoint_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The breakpoint number according to gdb.  */
   int number;
 
-- 
2.52.0


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

* [PATCH v2 6/6] gdb: make remaining Python extension objects inherit from PyObject
  2026-01-27 17:02 [PATCH v2 0/6] gdb: minor fixes for Python limited C API support Matthieu Longo
                   ` (4 preceding siblings ...)
  2026-01-27 17:02 ` [PATCH v2 5/6] gdb: cast all Python extension objects passed to gdbpy_ref_policy to PyObject* Matthieu Longo
@ 2026-01-27 17:02 ` Matthieu Longo
  2026-01-27 18:29   ` Tom Tromey
  5 siblings, 1 reply; 24+ messages in thread
From: Matthieu Longo @ 2026-01-27 17:02 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey, Matthieu Longo

Previous patches made some Python extension objects ipublicly inherit
directly or indirectly from PyObject.
In the interest of consistency, this patch makes all remaining Python
extension objects still not inheriting from PyObject do so.
---
 gdb/python/py-arch.c          |  4 ++--
 gdb/python/py-block.c         |  8 ++++----
 gdb/python/py-disasm.c        |  8 ++------
 gdb/python/py-instruction.c   |  5 ++---
 gdb/python/py-lazy-string.c   |  5 ++---
 gdb/python/py-linetable.c     | 12 ++++++------
 gdb/python/py-param.c         |  4 +---
 gdb/python/py-prettyprint.c   |  6 ++----
 gdb/python/py-record-btrace.c |  5 ++---
 gdb/python/py-record.c        |  4 +---
 gdb/python/py-record.h        |  8 ++------
 gdb/python/py-registers.c     | 10 ++++------
 gdb/python/py-style.c         |  4 +---
 gdb/python/py-type.c          |  4 ++--
 gdb/python/py-unwind.c        |  8 ++------
 gdb/python/py-value.c         |  4 ++--
 16 files changed, 37 insertions(+), 62 deletions(-)

diff --git a/gdb/python/py-arch.c b/gdb/python/py-arch.c
index 765a21ae0b7..f761c7ba30b 100644
--- a/gdb/python/py-arch.c
+++ b/gdb/python/py-arch.c
@@ -22,8 +22,8 @@
 #include "disasm.h"
 #include "python-internal.h"
 
-struct arch_object {
-  PyObject_HEAD
+struct arch_object: public PyObject
+{
   struct gdbarch *gdbarch;
 };
 
diff --git a/gdb/python/py-block.c b/gdb/python/py-block.c
index 3a59d767b5c..4b02115a5f9 100644
--- a/gdb/python/py-block.c
+++ b/gdb/python/py-block.c
@@ -23,8 +23,8 @@
 #include "python-internal.h"
 #include "objfiles.h"
 
-struct block_object {
-  PyObject_HEAD
+struct block_object: public PyObject
+{
   /* The GDB block structure that represents a frame's code block.  */
   const struct block *block;
   /* The backing object file.  There is no direct relationship in GDB
@@ -33,8 +33,8 @@ struct block_object {
   struct objfile *objfile;
 };
 
-struct block_syms_iterator_object {
-  PyObject_HEAD
+struct block_syms_iterator_object: public PyObject
+{
   /* The block.  */
   const struct block *block;
   /* The iterator for that block.  */
diff --git a/gdb/python/py-disasm.c b/gdb/python/py-disasm.c
index 037223f05e3..8b796628d72 100644
--- a/gdb/python/py-disasm.c
+++ b/gdb/python/py-disasm.c
@@ -57,10 +57,8 @@ extern PyTypeObject disasm_info_object_type;
    that is an address that should be printed using a call to GDB's
    internal print_address function.  */
 
-struct disasm_addr_part_object
+struct disasm_addr_part_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The address to be formatted.  */
   bfd_vma address;
 
@@ -77,10 +75,8 @@ extern PyTypeObject disasm_addr_part_object_type;
    this type represents a small part of a disassembled instruction; a part
    that is a piece of test along with an associated style.  */
 
-struct disasm_text_part_object
+struct disasm_text_part_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The string that is this part.  */
   std::string *string;
 
diff --git a/gdb/python/py-instruction.c b/gdb/python/py-instruction.c
index b7ab6e4668f..df64d85988f 100644
--- a/gdb/python/py-instruction.c
+++ b/gdb/python/py-instruction.c
@@ -29,9 +29,8 @@ PyTypeObject py_insn_type = {
 
 /* Python instruction object.  */
 
-struct py_insn_obj {
-  PyObject_HEAD
-};
+struct py_insn_obj: public PyObject
+{};
 
 /* Getter function for gdb.Instruction attributes.  */
 
diff --git a/gdb/python/py-lazy-string.c b/gdb/python/py-lazy-string.c
index 41f958744c1..3e95a041dcc 100644
--- a/gdb/python/py-lazy-string.c
+++ b/gdb/python/py-lazy-string.c
@@ -23,9 +23,8 @@
 #include "valprint.h"
 #include "language.h"
 
-struct lazy_string_object {
-  PyObject_HEAD
-
+struct lazy_string_object: public PyObject
+{
   /*  Holds the address of the lazy string.  */
   CORE_ADDR address;
 
diff --git a/gdb/python/py-linetable.c b/gdb/python/py-linetable.c
index 0ea2d843dd3..2db3488b1d3 100644
--- a/gdb/python/py-linetable.c
+++ b/gdb/python/py-linetable.c
@@ -19,8 +19,8 @@
 
 #include "python-internal.h"
 
-struct linetable_entry_object {
-  PyObject_HEAD
+struct linetable_entry_object: public PyObject
+{
   /* The line table source line.  */
   int line;
   /* The pc associated with the source line.  */
@@ -29,8 +29,8 @@ struct linetable_entry_object {
 
 extern PyTypeObject linetable_entry_object_type;
 
-struct linetable_object {
-  PyObject_HEAD
+struct linetable_object: public PyObject
+{
   /* The symtab python object.  We store the Python object here as the
      underlying symtab can become invalid, and we have to run validity
      checks on it.  */
@@ -39,8 +39,8 @@ struct linetable_object {
 
 extern PyTypeObject linetable_object_type;
 
-struct ltpy_iterator_object {
-  PyObject_HEAD
+struct ltpy_iterator_object: public PyObject
+{
   /* The current entry in the line table for the iterator  */
   int current_index;
   /* Pointer back to the original source line table object.  Needed to
diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c
index f305661fa43..4f61b8efd3c 100644
--- a/gdb/python/py-param.c
+++ b/gdb/python/py-param.c
@@ -124,10 +124,8 @@ union parmpy_variable
 };
 
 /* A GDB parameter.  */
-struct parmpy_object
+struct parmpy_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The type of the parameter.  */
   enum var_types type;
 
diff --git a/gdb/python/py-prettyprint.c b/gdb/python/py-prettyprint.c
index a920e6f3cf7..8895f1c8771 100644
--- a/gdb/python/py-prettyprint.c
+++ b/gdb/python/py-prettyprint.c
@@ -783,10 +783,8 @@ gdbpy_get_print_options (value_print_options *opts)
 
 /* A ValuePrinter is just a "tag", so it has no state other than that
    required by Python.  */
-struct printer_object
-{
-  PyObject_HEAD
-};
+struct printer_object: public PyObject
+{};
 
 /* The ValuePrinter type object.  */
 PyTypeObject printer_object_type =
diff --git a/gdb/python/py-record-btrace.c b/gdb/python/py-record-btrace.c
index d9534c02331..637488d797c 100644
--- a/gdb/python/py-record-btrace.c
+++ b/gdb/python/py-record-btrace.c
@@ -29,9 +29,8 @@
 
 /* Python object for btrace record lists.  */
 
-struct btpy_list_object {
-  PyObject_HEAD
-
+struct btpy_list_object: public PyObject
+{
   /* The thread this list belongs to.  */
   thread_info *thread;
 
diff --git a/gdb/python/py-record.c b/gdb/python/py-record.c
index 3c985c172e1..bf0551cfeac 100644
--- a/gdb/python/py-record.c
+++ b/gdb/python/py-record.c
@@ -55,10 +55,8 @@ PyTypeObject recpy_aux_type = {
 };
 
 /* Python RecordGap object.  */
-struct recpy_gap_object
+struct recpy_gap_object: public PyObject
 {
-  PyObject_HEAD
-
   /* Reason code.  */
   int reason_code;
 
diff --git a/gdb/python/py-record.h b/gdb/python/py-record.h
index 039a1b125e3..0722a918c68 100644
--- a/gdb/python/py-record.h
+++ b/gdb/python/py-record.h
@@ -25,10 +25,8 @@
 #include "record.h"
 
 /* Python Record object.  */
-struct recpy_record_object
+struct recpy_record_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The thread this object refers to.  */
   thread_info *thread;
 
@@ -39,10 +37,8 @@ struct recpy_record_object
 /* Python recorded element object.  This is generic enough to represent
    recorded instructions as well as recorded function call segments, hence the
    generic name.  */
-struct recpy_element_object
+struct recpy_element_object: public PyObject
 {
-  PyObject_HEAD
-
   /* The thread this object refers to.  */
   thread_info *thread;
 
diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c
index 30b333c444c..d600b67be77 100644
--- a/gdb/python/py-registers.c
+++ b/gdb/python/py-registers.c
@@ -32,9 +32,8 @@ static const registry<gdbarch>::key<gdbpy_register_type>
      gdbpy_register_object_data;
 
 /* Structure for iterator over register descriptors.  */
-struct register_descriptor_iterator_object {
-  PyObject_HEAD
-
+struct register_descriptor_iterator_object: public PyObject
+{
   /* The register group that the user is iterating over.  This will never
      be NULL.  */
   const struct reggroup *reggroup;
@@ -61,9 +60,8 @@ struct register_descriptor_object: public PyObject
 extern PyTypeObject register_descriptor_object_type;
 
 /* Structure for iterator over register groups.  */
-struct reggroup_iterator_object {
-  PyObject_HEAD
-
+struct reggroup_iterator_object: public PyObject
+{
   /* The index into GROUPS for the next group to return.  */
   std::vector<const reggroup *>::size_type index;
 
diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c
index f404864251c..3172400fcaf 100644
--- a/gdb/python/py-style.c
+++ b/gdb/python/py-style.c
@@ -36,10 +36,8 @@ static struct {
 };
 
 /* A style.  */
-struct style_object
+struct style_object: public PyObject
 {
-  PyObject_HEAD
-
   /* Underlying style, only valid when STYLE_NAME is NULL.  */
   ui_file_style style;
 
diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c
index 1cc583b38d2..3b0525c6ec6 100644
--- a/gdb/python/py-type.c
+++ b/gdb/python/py-type.c
@@ -42,8 +42,8 @@ struct field_object: public gdbpy__dict__wrapper
 extern PyTypeObject field_object_type;
 
 /* A type iterator object.  */
-struct typy_iterator_object {
-  PyObject_HEAD
+struct typy_iterator_object: public PyObject
+{
   /* The current field index.  */
   int field;
   /* What to return.  */
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index f9fd848ea0e..07effe7fa88 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -66,10 +66,8 @@ show_pyuw_debug (struct ui_file *file, int from_tty,
       }							     \
   } while (0)
 
-struct pending_frame_object
+struct pending_frame_object: public PyObject
 {
-  PyObject_HEAD
-
   /* Frame we are unwinding.  */
   frame_info_ptr frame_info;
 
@@ -94,10 +92,8 @@ struct saved_reg
 /* The data we keep for the PyUnwindInfo: pending_frame, saved registers
    and frame ID.  */
 
-struct unwind_info_object
+struct unwind_info_object: public PyObject
 {
-  PyObject_HEAD
-
   /* gdb.PendingFrame for the frame we are unwinding.  */
   PyObject *pending_frame;
 
diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
index 6c28f13a3de..4f2f9cc4b0b 100644
--- a/gdb/python/py-value.c
+++ b/gdb/python/py-value.c
@@ -54,8 +54,8 @@
 #define builtin_type_pybool \
   language_bool_type (current_language, gdbpy_enter::get_gdbarch ())
 
-struct value_object {
-  PyObject_HEAD
+struct value_object: public PyObject
+{
   struct value_object *next;
   struct value_object *prev;
   struct value *value;
-- 
2.52.0


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

* Re: [PATCH v2 1/6] Python limited API: migrate Py_CompileStringExFlags and PyRun_SimpleString
  2026-01-27 17:02 ` [PATCH v2 1/6] Python limited API: migrate Py_CompileStringExFlags and PyRun_SimpleString Matthieu Longo
@ 2026-01-27 17:54   ` Tom Tromey
  0 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-27 17:54 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: gdb-patches, Tom Tromey

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> This patch replaces Py_CompileStringExFlags () with its limited C API
Matthieu> equivalent, Py_CompileString (). The eval_python_command () helper is
Matthieu> now exposed through the private GDB Python API as a utility function.
Matthieu> PyRun_SimpleString () is replaced with eval_python_command () to avoid
Matthieu> code duplication.

Matthieu> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830

Looks good, thank you.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

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

* Re: [PATCH v2 2/6] Python limited API: migrate PyImport_ExtendInittab
  2026-01-27 17:02 ` [PATCH v2 2/6] Python limited API: migrate PyImport_ExtendInittab Matthieu Longo
@ 2026-01-27 17:54   ` Tom Tromey
  0 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-27 17:54 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: gdb-patches, Tom Tromey

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> This patch replaces PyImport_ExtendInittab () with its limited C
Matthieu> API equivalent, PyImport_AppendInittab (), a convenience wrapper
Matthieu> around PyImport_ExtendInittab ().

Matthieu> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830

Thanks.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

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

* Re: [PATCH v2 3/6] gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF
  2026-01-27 17:02 ` [PATCH v2 3/6] gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF Matthieu Longo
@ 2026-01-27 18:01   ` Tom Tromey
  2026-01-27 18:29   ` Tom Tromey
  1 sibling, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-27 18:01 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: gdb-patches, Tom Tromey

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> This patch fixes the resulting compilation issue by adding an explicit
Matthieu> static_cast to 'PyObject *' before passing the value to Py_XINCREF().
Matthieu> As a side effect, this cast enforces, at compile time, that the template
Matthieu> type 'Storage::obj_type' passed to gdbpy_registry is a subclass of
Matthieu> PyObject. To provide a clearer diagnostic when an incorrect type is used,
Matthieu> a static_assert is added to gdbpy_registry, avoiding obscure errors
Matthieu> originating from the static_cast. Finally, the relevant C extension types
Matthieu> passed to gdbpy_registry are updated to inherit publicly from PyObject.

Thanks, this looks nice.

I suspect the static_cast isn't needed, but I think it also isn't
harmful, especially with the static assert in place.

Approved-By: Tom Tromey <tom@tromey.com>

Tom

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

* Re: [PATCH v2 5/6] gdb: cast all Python extension objects passed to gdbpy_ref_policy to PyObject*
  2026-01-27 17:02 ` [PATCH v2 5/6] gdb: cast all Python extension objects passed to gdbpy_ref_policy to PyObject* Matthieu Longo
@ 2026-01-27 18:28   ` Tom Tromey
  2026-01-28 11:58     ` Matthieu Longo
  0 siblings, 1 reply; 24+ messages in thread
From: Tom Tromey @ 2026-01-27 18:28 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: gdb-patches, Tom Tromey

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> +struct gdbpy_breakpoint_location_object: public PyObject

gdb style is spaces before and after the ":".

This is ok with this fixed, it occurs throughout the patch.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

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

* Re: [PATCH v2 3/6] gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF
  2026-01-27 17:02 ` [PATCH v2 3/6] gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF Matthieu Longo
  2026-01-27 18:01   ` Tom Tromey
@ 2026-01-27 18:29   ` Tom Tromey
  2026-01-28 11:58     ` Matthieu Longo
  1 sibling, 1 reply; 24+ messages in thread
From: Tom Tromey @ 2026-01-27 18:29 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: gdb-patches, Tom Tromey

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> +struct symbol_object: public PyObject

First time around I didn't notice the missing space.
This should be fixed throughout the patch.

Tom

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

* Re: [PATCH v2 6/6] gdb: make remaining Python extension objects inherit from PyObject
  2026-01-27 17:02 ` [PATCH v2 6/6] gdb: make remaining Python extension objects inherit from PyObject Matthieu Longo
@ 2026-01-27 18:29   ` Tom Tromey
  2026-01-28 11:58     ` Matthieu Longo
  0 siblings, 1 reply; 24+ messages in thread
From: Tom Tromey @ 2026-01-27 18:29 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: gdb-patches, Tom Tromey

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> Previous patches made some Python extension objects ipublicly inherit
Matthieu> directly or indirectly from PyObject.
Matthieu> In the interest of consistency, this patch makes all remaining Python
Matthieu> extension objects still not inheriting from PyObject do so.

This is ok with the missing space issue fixed.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

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

* Re: [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes
  2026-01-27 17:02 ` [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes Matthieu Longo
@ 2026-01-27 19:06   ` Tom Tromey
  2026-01-28 11:57     ` Matthieu Longo
  2026-01-28 17:43     ` Matthieu Longo
  0 siblings, 2 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-27 19:06 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: gdb-patches, Tom Tromey

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> GDB is currently using the Python unlimited API. Migrating the codebase
Matthieu> to the Python limited API would have for benefit to make a GDB build
Matthieu> artifacts compatible with older and newer versions of Python that they
Matthieu> were built with.

Matthieu> This patch prepares the ground for migrating the existing C extension
Matthieu> types from static types to heap-allocated ones, by removing the
Matthieu> dependency on tp_dictoffset, which is unavailable when using the limited
Matthieu> API.

Thanks.

Matthieu> - gdbpy__dict__wrapper: a base class for C extension objects that own a
Matthieu>   __dict__.

I think a double underscore in a name is reserved by the C++
implementation, so "gdbpy__dict__wrapper" can't be used.  I think the
same name with single underscores would be fine.

Matthieu> - gdb_py_generic_dict_setter: a __dict__ setter provided for completeness.
Matthieu>   It should not be used, as __dict__ should be read-only after the object
Matthieu>   initialization.

If it shouldn't be used, it should just not exist, I think.

Matthieu> -using thread_map_t
Matthieu> -  = gdb::unordered_map<thread_info *, gdbpy_ref<thread_object>>;
Matthieu> -
Matthieu> -struct inferior_object
Matthieu> -{
Matthieu> -  PyObject_HEAD

This moved to python-internal.h, but I don't understand why that move
is needed.

Matthieu> +  /* Compute the address of the __dict__ attribute for the given PyObject.
Matthieu> +     The CLOSURE argument is unused.  */
Matthieu> +  static PyObject **
Matthieu> +  compute_addr (PyObject *self, void *closure ATTRIBUTE_UNUSED)

In classes there doesn't have to be a newline before the method name.
Also since the closure isn't used here it should probably just be
dropped.  If it's needed in the future we can add it then, but I'm going
to guess it won't be.

Matthieu> +/* Generic attribute getter function similar to PyObject_GenericGetAttr () but
Matthieu> +   that should be used when the object has a dictionary __dict__.  */
Matthieu> +PyObject *
Matthieu> +gdb_py_generic_getattro (PyObject *self, PyObject *attr)
Matthieu> +{
Matthieu> +  PyObject *value = PyObject_GenericGetAttr (self, attr);
Matthieu> +  if (value != nullptr)
Matthieu> +    return value;
Matthieu> +
Matthieu> +  if (! PyErr_ExceptionMatches (PyExc_AttributeError))
Matthieu> +    return nullptr;
Matthieu> +
Matthieu> +  /* Clear previous AttributeError set by PyObject_GenericGetAttr.  */
Matthieu> +  PyErr_Clear();
Matthieu> +
Matthieu> +  gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr));
Matthieu> +  value = PyDict_GetItemWithError (dict.get (), attr);

I think this has to do check 'dict == nullptr' before this call.

Matthieu> +  if (value != nullptr)
Matthieu> +    return Py_NewRef (value);
Matthieu> +
Matthieu> +  PyErr_Format (PyExc_AttributeError,
Matthieu> +		"'%s' object has no attribute '%s'",
Matthieu> +		Py_TYPE (self)->tp_name,
Matthieu> +		PyUnicode_AsUTF8AndSize (attr, nullptr));
Matthieu> +  return nullptr;

PyDict_GetItemWithError sets the error, so is this just here to make a
nicer message?  That's fine if so, though I am not sure if you have to
clear the existing error here first?

Matthieu> +  gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr));
Matthieu> +  /* Delete the old value (if there is one).  */
Matthieu> +  PyObject *old_value = PyDict_GetItem (dict.get (), attr);

dict==nullptr check

Matthieu> +  if (old_value != nullptr)
Matthieu> +    Py_DECREF (old_value);
Matthieu> +  /* Set the new value.  */
Matthieu> +  return PyDict_SetItem (dict.get (), attr, value);

I think this ordering means self-assignment can break.  IMO it's better
to use gdbpy_ref to manage the lifetime of the old value.

Matthieu> +#if PY_VERSION_HEX < 0x030a0000
Matthieu> +static inline PyObject*
Matthieu> +Py_NewRef (PyObject *obj)

The return type is missing a space before the "*"

Tom

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

* Re: [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes
  2026-01-27 19:06   ` Tom Tromey
@ 2026-01-28 11:57     ` Matthieu Longo
  2026-01-28 17:43     ` Matthieu Longo
  1 sibling, 0 replies; 24+ messages in thread
From: Matthieu Longo @ 2026-01-28 11:57 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

On 27/01/2026 19:06, Tom Tromey wrote:
>>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:
> 
> Matthieu> GDB is currently using the Python unlimited API. Migrating the codebase
> Matthieu> to the Python limited API would have for benefit to make a GDB build
> Matthieu> artifacts compatible with older and newer versions of Python that they
> Matthieu> were built with.
> 
> Matthieu> This patch prepares the ground for migrating the existing C extension
> Matthieu> types from static types to heap-allocated ones, by removing the
> Matthieu> dependency on tp_dictoffset, which is unavailable when using the limited
> Matthieu> API.
> 
> Thanks.
> 
> Matthieu> - gdbpy__dict__wrapper: a base class for C extension objects that own a
> Matthieu>   __dict__.
> 
> I think a double underscore in a name is reserved by the C++
> implementation, so "gdbpy__dict__wrapper" can't be used.  I think the
> same name with single underscores would be fine.
> 

Renamed.

> Matthieu> - gdb_py_generic_dict_setter: a __dict__ setter provided for completeness.
> Matthieu>   It should not be used, as __dict__ should be read-only after the object
> Matthieu>   initialization.
> 
> If it shouldn't be used, it should just not exist, I think.
> 

Removed.

> Matthieu> -using thread_map_t
> Matthieu> -  = gdb::unordered_map<thread_info *, gdbpy_ref<thread_object>>;
> Matthieu> -
> Matthieu> -struct inferior_object
> Matthieu> -{
> Matthieu> -  PyObject_HEAD
> 
> This moved to python-internal.h, but I don't understand why that move
> is needed.
> 
> Matthieu> +  /* Compute the address of the __dict__ attribute for the given PyObject.
> Matthieu> +     The CLOSURE argument is unused.  */
> Matthieu> +  static PyObject **
> Matthieu> +  compute_addr (PyObject *self, void *closure ATTRIBUTE_UNUSED)
> 
> In classes there doesn't have to be a newline before the method name.
> Also since the closure isn't used here it should probably just be
> dropped.  If it's needed in the future we can add it then, but I'm going
> to guess it won't be.
> 

Fixed.

> Matthieu> +/* Generic attribute getter function similar to PyObject_GenericGetAttr () but
> Matthieu> +   that should be used when the object has a dictionary __dict__.  */
> Matthieu> +PyObject *
> Matthieu> +gdb_py_generic_getattro (PyObject *self, PyObject *attr)
> Matthieu> +{
> Matthieu> +  PyObject *value = PyObject_GenericGetAttr (self, attr);
> Matthieu> +  if (value != nullptr)
> Matthieu> +    return value;
> Matthieu> +
> Matthieu> +  if (! PyErr_ExceptionMatches (PyExc_AttributeError))
> Matthieu> +    return nullptr;
> Matthieu> +
> Matthieu> +  /* Clear previous AttributeError set by PyObject_GenericGetAttr.  */
> Matthieu> +  PyErr_Clear();
> Matthieu> +

Also, in both gdb_py_generic_getattro() and gdb_py_generic_setattro(), I moved the look-up of __dict__ before PyErr_Clear().
If the look-up of __dict__ were to fail for some reason, we still want the exception raised by PyObject_GenericGetAttr / PyObject_GenericSetAttr to be there.

> Matthieu> +  gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr));
> Matthieu> +  value = PyDict_GetItemWithError (dict.get (), attr);
> 
> I think this has to do check 'dict == nullptr' before this call.
> 

Indeed, I forgot that. Fixed.

> Matthieu> +  if (value != nullptr)
> Matthieu> +    return Py_NewRef (value);
> Matthieu> +
> Matthieu> +  PyErr_Format (PyExc_AttributeError,
> Matthieu> +		"'%s' object has no attribute '%s'",
> Matthieu> +		Py_TYPE (self)->tp_name,
> Matthieu> +		PyUnicode_AsUTF8AndSize (attr, nullptr));
> Matthieu> +  return nullptr;
> 
> PyDict_GetItemWithError sets the error, so is this just here to make a
> nicer message?  That's fine if so, though I am not sure if you have to
> clear the existing error here first?
> 

https://github.com/python/cpython/blob/08d7bd28fecca524c648dda240022add704b8f8a/Objects/dictobject.c#L2505
/* Variant of PyDict_GetItem() that doesn't suppress exceptions.
    This returns NULL *with* an exception set if an exception occurred.
    It returns NULL *without* an exception set if the key wasn't present.
*/
PyDict_GetItemWithError has a misleading name, because if the key is not found, no PyExc_AttributeError is set.
This situation results in a new exception being raised:
   Python Exception <class 'SystemError'>: error return without exception set
Hence this surprising manual setting of an exception to makes everything fit into place.

I am going to add a comment to clarify the intent, because I had even forgotten why I did this in the first place.

> Matthieu> +  gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr));
> Matthieu> +  /* Delete the old value (if there is one).  */
> Matthieu> +  PyObject *old_value = PyDict_GetItem (dict.get (), attr);
> 
> dict==nullptr check
> 

Fixed.

> Matthieu> +  if (old_value != nullptr)
> Matthieu> +    Py_DECREF (old_value);
> Matthieu> +  /* Set the new value.  */
> Matthieu> +  return PyDict_SetItem (dict.get (), attr, value);
> 
> I think this ordering means self-assignment can break.  IMO it's better
> to use gdbpy_ref to manage the lifetime of the old value.
> 

It seems that I got the whole thing wrong with my assumption that I needed to decref the old value.
This is already managed in insertdict() called from PyDict_SetItem()
https://github.com/python/cpython/blob/08d7bd28fecca524c648dda240022add704b8f8a/Objects/dictobject.c#L1934
This PyDict_GetItem () is useless, I will remove it.

> Matthieu> +#if PY_VERSION_HEX < 0x030a0000
> Matthieu> +static inline PyObject*
> Matthieu> +Py_NewRef (PyObject *obj)
> 
> The return type is missing a space before the "*"
> 

Fixed.

> Tom

Since all others patches are approved with the small modifications you suggested, I am going to only post a new revision of this patch in this same thread to avoid a new round.

Matthieu

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

* Re: [PATCH v2 3/6] gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF
  2026-01-27 18:29   ` Tom Tromey
@ 2026-01-28 11:58     ` Matthieu Longo
  0 siblings, 0 replies; 24+ messages in thread
From: Matthieu Longo @ 2026-01-28 11:58 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

On 27/01/2026 18:29, Tom Tromey wrote:
>>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:
> 
> Matthieu> +struct symbol_object: public PyObject
> 
> First time around I didn't notice the missing space.
> This should be fixed throughout the patch.
> 
> Tom

Fixed.

Matthieu

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

* Re: [PATCH v2 5/6] gdb: cast all Python extension objects passed to gdbpy_ref_policy to PyObject*
  2026-01-27 18:28   ` Tom Tromey
@ 2026-01-28 11:58     ` Matthieu Longo
  0 siblings, 0 replies; 24+ messages in thread
From: Matthieu Longo @ 2026-01-28 11:58 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

On 27/01/2026 18:28, Tom Tromey wrote:
>>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:
> 
> Matthieu> +struct gdbpy_breakpoint_location_object: public PyObject
> 
> gdb style is spaces before and after the ":".
> 
> This is ok with this fixed, it occurs throughout the patch.
> Approved-By: Tom Tromey <tom@tromey.com>
> 
> Tom

Fixed.

Matthieu

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

* Re: [PATCH v2 6/6] gdb: make remaining Python extension objects inherit from PyObject
  2026-01-27 18:29   ` Tom Tromey
@ 2026-01-28 11:58     ` Matthieu Longo
  0 siblings, 0 replies; 24+ messages in thread
From: Matthieu Longo @ 2026-01-28 11:58 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

On 27/01/2026 18:29, Tom Tromey wrote:
>>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:
> 
> Matthieu> Previous patches made some Python extension objects ipublicly inherit
> Matthieu> directly or indirectly from PyObject.
> Matthieu> In the interest of consistency, this patch makes all remaining Python
> Matthieu> extension objects still not inheriting from PyObject do so.
> 
> This is ok with the missing space issue fixed.
> Approved-By: Tom Tromey <tom@tromey.com>
> 
> Tom

Fixed.

Matthieu

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

* Re: [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes
  2026-01-27 19:06   ` Tom Tromey
  2026-01-28 11:57     ` Matthieu Longo
@ 2026-01-28 17:43     ` Matthieu Longo
  2026-01-28 17:51       ` Tom Tromey
  1 sibling, 1 reply; 24+ messages in thread
From: Matthieu Longo @ 2026-01-28 17:43 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

On 27/01/2026 19:06, Tom Tromey wrote:
> Matthieu> -using thread_map_t
> Matthieu> -  = gdb::unordered_map<thread_info *, gdbpy_ref<thread_object>>;
> Matthieu> -
> Matthieu> -struct inferior_object
> Matthieu> -{
> Matthieu> -  PyObject_HEAD
> 
> This moved to python-internal.h, but I don't understand why that move
> is needed.

I moved the declaration to python-internal.h because of the following build issue after adding the static_assert in gdbpy_ref_policy.

   CXX    python/py-exitedevent.o
In file included from /usr/include/c++/14/bits/move.h:37,
                  from /usr/include/c++/14/bits/stl_function.h:60,
                  from /usr/include/c++/14/functional:49,
                  from ../../gdb/../gdbsupport/ptid.h:35,
                  from ../../gdb/../gdbsupport/common-defs.h:212,
                  from ./../../gdb/defs.h:26,
                  from <command-line>:
/usr/include/c++/14/type_traits: In instantiation of ‘struct std::is_base_of<_object, inferior_object>’:
../../gdb/python/py-ref.h:29:47:   required from ‘struct gdbpy_ref_policy<inferior_object>’
    29 |   static_assert(std::is_base_of<PyObject, T>::value,
       |                                               ^~~~~
../../gdb/python/py-exitedevent.c:42:18:   required from here
    42 |   if (inf_obj == NULL || evpy_add_attribute (exited_event.get (),
       |                  ^~~~
/usr/include/c++/14/type_traits:1493:30: error: invalid use of incomplete type ‘struct inferior_object’
  1493 |     : public __bool_constant<__is_base_of(_Base, _Derived)>
       |                              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../gdb/python/py-events.h:24,
                  from ../../gdb/python/py-event.h:23,
                  from ../../gdb/python/py-exitedevent.c:20:
../../gdb/python/python-internal.h:405:8: note: forward declaration of ‘struct inferior_object’
   405 | struct inferior_object;
       |        ^~~~~~~~~~~~~~~
In file included from ../../gdb/python/python-internal.h:61:
../../gdb/python/py-ref.h: In instantiation of ‘struct gdbpy_ref_policy<inferior_object>’:
../../gdb/python/py-exitedevent.c:42:18:   required from here
    42 |   if (inf_obj == NULL || evpy_add_attribute (exited_event.get (),
       |                  ^~~~
../../gdb/python/py-ref.h:29:47: error: ‘value’ is not a member of ‘std::is_base_of<_object, inferior_object>’
    29 |   static_assert(std::is_base_of<PyObject, T>::value,
       |                                               ^~~~~

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

* Re: [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes
  2026-01-28 17:43     ` Matthieu Longo
@ 2026-01-28 17:51       ` Tom Tromey
  0 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-28 17:51 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: Tom Tromey, gdb-patches

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> I moved the declaration to python-internal.h because of the
Matthieu> following build issue after adding the static_assert in
Matthieu> gdbpy_ref_policy.

Ok, I see, thank you.

Tom

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

* Re: [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes
  2026-01-29 10:42   ` Matthieu Longo
@ 2026-01-29 14:45     ` Tom Tromey
  0 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-29 14:45 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: Tom Tromey, gdb-patches

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> What about this ?

Matthieu>   value = PyDict_GetItemWithError (dict.get (), attr);
Matthieu>   if (value != nullptr)
Matthieu>     return Py_NewRef (value);

Matthieu>   /* If PyDict_GetItemWithError() returns NULL because an error occurred, it
Matthieu>      sets an exception.  Propagate it by returning NULL.  */
Matthieu>   if (PyErr_Occurred () != nullptr)
Matthieu>     return nullptr;

Matthieu>   /* If the key is not found, PyDict_GetItemWithError() returns NULL without
Matthieu>      setting an exception.  Failing to set one here would later result in:
Matthieu>        <class 'SystemError'>: error return without exception set
Matthieu>      Therefore, we must explicitly raise an AttributeError in this case.  */
Matthieu>   PyErr_Format (PyExc_AttributeError,
Matthieu> 		"'%s' object has no attribute '%s'",
Matthieu> 		Py_TYPE (self)->tp_name,
Matthieu> 		PyUnicode_AsUTF8AndSize (attr, nullptr));
Matthieu>   return nullptr;

Looks good.  If that was my only comment (I already forgot) then this is
OK.

Approved-By: Tom Tromey <tom@tromey.com>

thank you,
Tom

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

* Re: [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes
  2026-01-28 18:00 ` Tom Tromey
@ 2026-01-29 10:42   ` Matthieu Longo
  2026-01-29 14:45     ` Tom Tromey
  0 siblings, 1 reply; 24+ messages in thread
From: Matthieu Longo @ 2026-01-29 10:42 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

On 28/01/2026 18:00, Tom Tromey wrote:
>>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:
> 
> Matthieu> GDB is currently using the Python unlimited API. Migrating the codebase
> Matthieu> to the Python limited API would have for benefit to make a GDB build
> Matthieu> artifacts compatible with older and newer versions of Python that they
> Matthieu> were built with.
> 
> Thanks again.
> 
> Matthieu> +  value = PyDict_GetItemWithError (dict.get (), attr);
> Matthieu> +  if (value != nullptr)
> Matthieu> +    return Py_NewRef (value);
> Matthieu> +
> Matthieu> +  /* PyDict_GetItemWithError() has a misleading name that lets one believe that
> Matthieu> +     the function sets PyExc_AttributeError when the key is not found, whereas
> Matthieu> +     it actually does not and only returns NULL.
> Matthieu> +     Not setting the exception for an unfound key causes the raising of another
> Matthieu> +     exception:
> Matthieu> +       <class 'SystemError'>: error return without exception set
> Matthieu> +     Hence this surprising manual setting of an exception to makes everything
> Matthieu> +     fit into place.  */
> Matthieu> +  PyErr_Format (PyExc_AttributeError,
> Matthieu> +		"'%s' object has no attribute '%s'",
> Matthieu> +		Py_TYPE (self)->tp_name,
> Matthieu> +		PyUnicode_AsUTF8AndSize (attr, nullptr));
> 
> 
> It seems to me that there should be an additional check between the call
> to PyDict_GetItemWithError and PyErr_Format, like:
> 
>      if (PyErr_Occurred () != nullptr)
>        return nullptr;
> 
> Since according to the PyDict_GetItemWithError docs, this function can
> return null with the error set.
> 
> With that change the comment could probably be shortened a bit since
> it's no longer surprising to have to set the exception.
> 
> Tom

I missed that. Thanks for noticing.

What about this ?

   value = PyDict_GetItemWithError (dict.get (), attr);
   if (value != nullptr)
     return Py_NewRef (value);

   /* If PyDict_GetItemWithError() returns NULL because an error occurred, it
      sets an exception.  Propagate it by returning NULL.  */
   if (PyErr_Occurred () != nullptr)
     return nullptr;

   /* If the key is not found, PyDict_GetItemWithError() returns NULL without
      setting an exception.  Failing to set one here would later result in:
        <class 'SystemError'>: error return without exception set
      Therefore, we must explicitly raise an AttributeError in this case.  */
   PyErr_Format (PyExc_AttributeError,
		"'%s' object has no attribute '%s'",
		Py_TYPE (self)->tp_name,
		PyUnicode_AsUTF8AndSize (attr, nullptr));
   return nullptr;

Matthieu

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

* Re: [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes
  2026-01-28 12:02 [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes Matthieu Longo
@ 2026-01-28 18:00 ` Tom Tromey
  2026-01-29 10:42   ` Matthieu Longo
  0 siblings, 1 reply; 24+ messages in thread
From: Tom Tromey @ 2026-01-28 18:00 UTC (permalink / raw)
  To: Matthieu Longo; +Cc: gdb-patches, Tom Tromey

>>>>> "Matthieu" == Matthieu Longo <matthieu.longo@arm.com> writes:

Matthieu> GDB is currently using the Python unlimited API. Migrating the codebase
Matthieu> to the Python limited API would have for benefit to make a GDB build
Matthieu> artifacts compatible with older and newer versions of Python that they
Matthieu> were built with.

Thanks again.

Matthieu> +  value = PyDict_GetItemWithError (dict.get (), attr);
Matthieu> +  if (value != nullptr)
Matthieu> +    return Py_NewRef (value);
Matthieu> +
Matthieu> +  /* PyDict_GetItemWithError() has a misleading name that lets one believe that
Matthieu> +     the function sets PyExc_AttributeError when the key is not found, whereas
Matthieu> +     it actually does not and only returns NULL.
Matthieu> +     Not setting the exception for an unfound key causes the raising of another
Matthieu> +     exception:
Matthieu> +       <class 'SystemError'>: error return without exception set
Matthieu> +     Hence this surprising manual setting of an exception to makes everything
Matthieu> +     fit into place.  */
Matthieu> +  PyErr_Format (PyExc_AttributeError,
Matthieu> +		"'%s' object has no attribute '%s'",
Matthieu> +		Py_TYPE (self)->tp_name,
Matthieu> +		PyUnicode_AsUTF8AndSize (attr, nullptr));


It seems to me that there should be an additional check between the call
to PyDict_GetItemWithError and PyErr_Format, like:

    if (PyErr_Occurred () != nullptr)
      return nullptr;

Since according to the PyDict_GetItemWithError docs, this function can
return null with the error set.

With that change the comment could probably be shortened a bit since
it's no longer surprising to have to set the exception.

Tom

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

* Re: [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes
@ 2026-01-28 12:02 Matthieu Longo
  2026-01-28 18:00 ` Tom Tromey
  0 siblings, 1 reply; 24+ messages in thread
From: Matthieu Longo @ 2026-01-28 12:02 UTC (permalink / raw)
  To: gdb-patches, Tom Tromey; +Cc: Matthieu Longo

GDB is currently using the Python unlimited API. Migrating the codebase
to the Python limited API would have for benefit to make a GDB build
artifacts compatible with older and newer versions of Python that they
were built with.

This patch prepares the ground for migrating the existing C extension
types from static types to heap-allocated ones, by removing the
dependency on tp_dictoffset, which is unavailable when using the limited
API.

One of the most common incompatibilities in the current static type
declarations is the tp_dictoffset slot, which specifies the dictionary
offset within the instance structure. Historically, the unlimited
API has provided two approaches to supply a dictionary for __dict__:

 * A managed dictionary.
   Setting Py_TPFLAGS_MANAGED_DICT in tp_flags indicates that the
   instances of the type have a __dict__ attribute, and that the
   dictionary is managed by Python.
   According to the Python documentation, this is the recommended approach.
   However, this flag was introduced in 3.12, together with
   PyObject_VisitManagedDict() and PyObject_ClearManagedDict(), neither
   of which is part of the limited API (at least for now). As a result,
   this recommended approach is not viable in the context of the limited
   API.

 * An instance dictionary, for which the offset in the instance is
   provided via tp_dictoffset.
   According to the Python documentation, this "tp slot" is on the
   deprecation path, and Py_TPFLAGS_MANAGED_DICT should be used instead.
   Given the age of the GDB codebase and the requirement to support older
   Python versions (>= 3.4), no need to argue that today, the implementation
   of __dict__ relies on tp_dictoffset. However, in the context of the
   limited API, PyType_Slot does not provide a Py_tp_dictoffset member, so
   another approach is needed to provide __dict__ to instances of C extension
   types.

Given the constraints of the limited API, the proposed solution consists
in providing a dictionary through a common base class, gdbpy__dict__wrapper.
This helper class owns a dictionary member corresponding to __dict__, and
any C extension type requiring a __dict__ must inherit from it. Since
extension object must also be convertible to PyObject, this wrapper class
publicly inherits from PyObject as well.
Access to the dictionary is provided via a custom getter defined in a
PyGetSetDef, similarily to what was previously done with gdb_py_generic_dict().
Because __dict__ participates in attribute look-up, and since this dictionary
is neither managed by Python nor exposed via tp_dictoffset, custom
implementations of tp_getattro and tp_setattro are required to correctly
redirect attribute look-ups to the dictionary. These custom implementations
— equivalent to PyObject_GenericGetAttr() and PyObject_GenericSetAttr() —
must be installed via tp_getattro / tp_setattro for static types, or
Py_tp_getattro / Py_tp_setattro for heap-allocated types.

- gdbpy__dict__wrapper: a base class for C extension objects that own a
  __dict__.
- gdb_py_generic_dict_getter: a __dict__ getter for extension types
  derived from gdbpy__dict__wrapper.
- gdb_py_generic_getattro: equivalent of PyObject_GenericGetAttr, but
  fixes the look-up of __dict__.
- gdb_py_generic_setattro: equivalent of PyObject_GenericSetAttr, but
  fixes the look-up of __dict__.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830
---
 gdb/python/py-corefile.c     | 18 +++-----
 gdb/python/py-event.c        | 10 ++--
 gdb/python/py-event.h        |  8 +---
 gdb/python/py-inferior.c     | 29 ++----------
 gdb/python/py-infthread.c    | 10 ++--
 gdb/python/py-objfile.c      | 18 +++-----
 gdb/python/py-progspace.c    | 18 +++-----
 gdb/python/py-ref.h          | 38 +++++++++++++++
 gdb/python/py-type.c         | 19 +++-----
 gdb/python/py-utils.c        | 89 +++++++++++++++++++++++++++++++-----
 gdb/python/python-internal.h | 34 ++++++++++----
 11 files changed, 183 insertions(+), 108 deletions(-)

diff --git a/gdb/python/py-corefile.c b/gdb/python/py-corefile.c
index 6847722628f..24b573b2dbd 100644
--- a/gdb/python/py-corefile.c
+++ b/gdb/python/py-corefile.c
@@ -26,20 +26,14 @@
 
 /* A gdb.Corefile object.  */
 
-struct corefile_object
+struct corefile_object : public gdbpy_dict_wrapper
 {
-  PyObject_HEAD
-
   /* The inferior this core file is attached to.  This will be set to NULL
      when the inferior is deleted, or if a different core file is loaded
      for the inferior.  When this is NULL the gdb.Corefile object is
      considered invalid.*/
   struct inferior *inferior;
 
-  /* Dictionary holding user-added attributes.  This is the __dict__
-     attribute of the object.  This is an owning reference.  */
-  PyObject *dict;
-
   /* A Tuple of gdb.CorefileMappedFile objects.  This tuple is only created
      the first time the user calls gdb.Corefile.mapped_files(), the result
      is cached here.  If this pointer is not NULL then this is an owning
@@ -511,8 +505,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_corefile);
 
 static gdb_PyGetSetDef corefile_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, nullptr,
-    "The __dict__ for the gdb.Corefile.", &corefile_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, nullptr,
+    "The __dict__ for the gdb.Corefile.", nullptr },
   { "filename", cfpy_get_filename, nullptr,
     "The filename of a valid Corefile object.", nullptr },
   { nullptr }
@@ -548,8 +542,8 @@ PyTypeObject corefile_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB corefile object",	  /* tp_doc */
@@ -566,7 +560,7 @@ PyTypeObject corefile_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (corefile_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0,				  /* tp_alloc */
   0,				  /* tp_new */
diff --git a/gdb/python/py-event.c b/gdb/python/py-event.c
index c985159a6f7..8fb21b7fdad 100644
--- a/gdb/python/py-event.c
+++ b/gdb/python/py-event.c
@@ -101,8 +101,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_event);
 
 static gdb_PyGetSetDef event_object_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, NULL,
-    "The __dict__ for this event.", &event_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, NULL,
+    "The __dict__ for this event.", NULL },
   { NULL }
 };
 
@@ -124,8 +124,8 @@ PyTypeObject event_object_type =
   0,                                          /* tp_hash  */
   0,                                          /* tp_call */
   0,                                          /* tp_str */
-  0,                                          /* tp_getattro */
-  0,                                          /* tp_setattro */
+  gdb_py_generic_getattro,                    /* tp_getattro */
+  gdb_py_generic_setattro,                    /* tp_setattro */
   0,                                          /* tp_as_buffer */
   Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */
   "GDB event object",                         /* tp_doc */
@@ -142,7 +142,7 @@ PyTypeObject event_object_type =
   0,                                          /* tp_dict */
   0,                                          /* tp_descr_get */
   0,                                          /* tp_descr_set */
-  offsetof (event_object, dict),              /* tp_dictoffset */
+  0,                                          /* tp_dictoffset */
   0,                                          /* tp_init */
   0                                           /* tp_alloc */
 };
diff --git a/gdb/python/py-event.h b/gdb/python/py-event.h
index 6c81d64eb4f..ec2e7bc03c5 100644
--- a/gdb/python/py-event.h
+++ b/gdb/python/py-event.h
@@ -31,12 +31,8 @@
 #include "py-event-types.def"
 #undef GDB_PY_DEFINE_EVENT_TYPE
 
-struct event_object
-{
-  PyObject_HEAD
-
-  PyObject *dict;
-};
+struct event_object : public gdbpy_dict_wrapper
+{};
 
 extern int emit_continue_event (ptid_t ptid);
 extern int emit_exited_event (const LONGEST *exit_code, struct inferior *inf);
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
index 8230f9d3943..69a7bfa3c37 100644
--- a/gdb/python/py-inferior.c
+++ b/gdb/python/py-inferior.c
@@ -32,25 +32,6 @@
 #include "progspace-and-thread.h"
 #include "gdbsupport/unordered_map.h"
 
-using thread_map_t
-  = gdb::unordered_map<thread_info *, gdbpy_ref<thread_object>>;
-
-struct inferior_object
-{
-  PyObject_HEAD
-
-  /* The inferior we represent.  */
-  struct inferior *inferior;
-
-  /* thread_object instances under this inferior.  This owns a
-     reference to each object it contains.  */
-  thread_map_t *threads;
-
-  /* Dictionary holding user-added attributes.
-     This is the __dict__ attribute of the object.  */
-  PyObject *dict;
-};
-
 extern PyTypeObject inferior_object_type;
 
 /* Deleter to clean up when an inferior is removed.  */
@@ -1061,8 +1042,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_inferior);
 
 static gdb_PyGetSetDef inferior_object_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, nullptr,
-    "The __dict__ for this inferior.", &inferior_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, nullptr,
+    "The __dict__ for this inferior.", nullptr },
   { "arguments", infpy_get_args, infpy_set_args,
     "Arguments to this program.", nullptr },
   { "num", infpy_get_num, NULL, "ID of inferior, as assigned by GDB.", NULL },
@@ -1144,8 +1125,8 @@ PyTypeObject inferior_object_type =
   0,				  /* tp_hash  */
   0,				  /* tp_call */
   0,				  /* tp_str */
-  0,				  /* tp_getattro */
-  0,				  /* tp_setattro */
+  gdb_py_generic_getattro,	  /* tp_getattro */
+  gdb_py_generic_setattro,	  /* tp_setattro */
   0,				  /* tp_as_buffer */
   Py_TPFLAGS_DEFAULT,		  /* tp_flags */
   "GDB inferior object",	  /* tp_doc */
@@ -1162,7 +1143,7 @@ PyTypeObject inferior_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (inferior_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0				  /* tp_alloc */
 };
diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c
index e5d3222f9ae..d75742360d4 100644
--- a/gdb/python/py-infthread.c
+++ b/gdb/python/py-infthread.c
@@ -415,8 +415,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_thread);
 
 static gdb_PyGetSetDef thread_object_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, nullptr,
-    "The __dict__ for this thread.", &thread_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, nullptr,
+    "The __dict__ for this thread.", nullptr },
   { "name", thpy_get_name, thpy_set_name,
     "The name of the thread, as set by the user or the OS.", NULL },
   { "details", thpy_get_details, NULL,
@@ -479,8 +479,8 @@ PyTypeObject thread_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB thread object",		  /* tp_doc */
@@ -497,7 +497,7 @@ PyTypeObject thread_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (thread_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0				  /* tp_alloc */
 };
diff --git a/gdb/python/py-objfile.c b/gdb/python/py-objfile.c
index 8cf365a27dc..36acdb06a25 100644
--- a/gdb/python/py-objfile.c
+++ b/gdb/python/py-objfile.c
@@ -26,17 +26,11 @@
 #include "python.h"
 #include "inferior.h"
 
-struct objfile_object
+struct objfile_object : public gdbpy_dict_wrapper
 {
-  PyObject_HEAD
-
   /* The corresponding objfile.  */
   struct objfile *objfile;
 
-  /* Dictionary holding user-added attributes.
-     This is the __dict__ attribute of the object.  */
-  PyObject *dict;
-
   /* The pretty-printer list of functions.  */
   PyObject *printers;
 
@@ -739,8 +733,8 @@ Look up a static-linkage global symbol in this objfile and return it." },
 
 static gdb_PyGetSetDef objfile_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, NULL,
-    "The __dict__ for this objfile.", &objfile_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, NULL,
+    "The __dict__ for this objfile.", NULL },
   { "filename", objfpy_get_filename, NULL,
     "The objfile's filename, or None.", NULL },
   { "username", objfpy_get_username, NULL,
@@ -785,8 +779,8 @@ PyTypeObject objfile_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB objfile object",		  /* tp_doc */
@@ -803,7 +797,7 @@ PyTypeObject objfile_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (objfile_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0,				  /* tp_alloc */
   objfpy_new,			  /* tp_new */
diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
index ee26b761adb..19f5e533b0a 100644
--- a/gdb/python/py-progspace.c
+++ b/gdb/python/py-progspace.c
@@ -29,17 +29,11 @@
 #include "observable.h"
 #include "inferior.h"
 
-struct pspace_object
+struct pspace_object : public gdbpy_dict_wrapper
 {
-  PyObject_HEAD
-
   /* The corresponding pspace.  */
   struct program_space *pspace;
 
-  /* Dictionary holding user-added attributes.
-     This is the __dict__ attribute of the object.  */
-  PyObject *dict;
-
   /* The pretty-printer list of functions.  */
   PyObject *printers;
 
@@ -758,8 +752,8 @@ GDBPY_INITIALIZE_FILE (gdbpy_initialize_pspace);
 
 static gdb_PyGetSetDef pspace_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, NULL,
-    "The __dict__ for this progspace.", &pspace_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, NULL,
+    "The __dict__ for this progspace.", NULL },
   { "filename", pspy_get_filename, NULL,
     "The filename of the progspace's main symbol file, or None.", nullptr },
   { "symbol_file", pspy_get_symbol_file, nullptr,
@@ -821,8 +815,8 @@ PyTypeObject pspace_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB progspace object",	  /* tp_doc */
@@ -839,7 +833,7 @@ PyTypeObject pspace_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (pspace_object, dict), /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0,				  /* tp_alloc */
   0,				  /* tp_new */
diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h
index b09d88dc30d..4d44ca90e21 100644
--- a/gdb/python/py-ref.h
+++ b/gdb/python/py-ref.h
@@ -42,4 +42,42 @@ struct gdbpy_ref_policy
 template<typename T = PyObject> using gdbpy_ref
   = gdb::ref_ptr<T, gdbpy_ref_policy<T>>;
 
+/* A wrapper class for Python extension objects that have a __dict__ attribute.
+
+   Any Python C object extension needing __dict__ should inherit from this
+   class. Given that the C extension object must also be convertible to
+   PyObject, this wrapper class publicly inherits from PyObject as well.
+
+   Access to the dict requires a custom getter defined via PyGetSetDef.
+     gdb_PyGetSetDef my_object_getset[] =
+     {
+       { "__dict__", gdb_py_generic_dict_getter, nullptr,
+         "The __dict__ for this object.", nullptr },
+       ...
+       { nullptr }
+     };
+
+   It is also important to note that __dict__ is used during the attribute
+   look-up. Since this dictionary is not managed by Python and is not exposed
+   via tp_dictoffset, custom attribute getter (tp_getattro) and setter
+   (tp_setattro) are required to correctly redirect attribute access to the
+   dictionary:
+     - gdb_py_generic_getattro (), assigned to tp_getattro for static types,
+       or Py_tp_getattro for heap-allocated types.
+     - gdb_py_generic_setattro (), assigned to tp_setattro for static types,
+       or Py_tp_setattro for heap-allocated types.  */
+struct gdbpy_dict_wrapper : public PyObject
+{
+  /* Dictionary holding user-added attributes.
+     This is the __dict__ attribute of the object.  */
+  PyObject *dict;
+
+  /* Compute the address of the __dict__ attribute for the given PyObject.  */
+  static PyObject **compute_addr (PyObject *self)
+  {
+    auto *wrapper = reinterpret_cast<gdbpy_dict_wrapper *> (self);
+    return &wrapper->dict;
+  }
+};
+
 #endif /* GDB_PYTHON_PY_REF_H */
diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c
index f39cb0240c8..76f001b99da 100644
--- a/gdb/python/py-type.c
+++ b/gdb/python/py-type.c
@@ -36,13 +36,8 @@ struct type_object : public PyObject
 extern PyTypeObject type_object_type;
 
 /* A Field object.  */
-struct field_object
-{
-  PyObject_HEAD
-
-  /* Dictionary holding our attributes.  */
-  PyObject *dict;
-};
+struct field_object : public gdbpy_dict_wrapper
+{};
 
 extern PyTypeObject field_object_type;
 
@@ -1707,8 +1702,8 @@ PyTypeObject type_object_type =
 
 static gdb_PyGetSetDef field_object_getset[] =
 {
-  { "__dict__", gdb_py_generic_dict, NULL,
-    "The __dict__ for this field.", &field_object_type },
+  { "__dict__", gdb_py_generic_dict_getter, NULL,
+    "The __dict__ for this field.", NULL },
   { NULL }
 };
 
@@ -1730,8 +1725,8 @@ PyTypeObject field_object_type =
   0,				  /*tp_hash */
   0,				  /*tp_call*/
   0,				  /*tp_str*/
-  0,				  /*tp_getattro*/
-  0,				  /*tp_setattro*/
+  gdb_py_generic_getattro,	  /*tp_getattro*/
+  gdb_py_generic_setattro,	  /*tp_setattro*/
   0,				  /*tp_as_buffer*/
   Py_TPFLAGS_DEFAULT,		  /*tp_flags*/
   "GDB field object",		  /* tp_doc */
@@ -1748,7 +1743,7 @@ PyTypeObject field_object_type =
   0,				  /* tp_dict */
   0,				  /* tp_descr_get */
   0,				  /* tp_descr_set */
-  offsetof (field_object, dict),  /* tp_dictoffset */
+  0,				  /* tp_dictoffset */
   0,				  /* tp_init */
   0,				  /* tp_alloc */
   0,				  /* tp_new */
diff --git a/gdb/python/py-utils.c b/gdb/python/py-utils.c
index 131230f80b3..e8485386d8d 100644
--- a/gdb/python/py-utils.c
+++ b/gdb/python/py-utils.c
@@ -309,24 +309,91 @@ gdb_py_int_as_long (PyObject *obj, long *result)
 
 \f
 
-/* Generic implementation of the __dict__ attribute for objects that
-   have a dictionary.  The CLOSURE argument should be the type object.
-   This only handles positive values for tp_dictoffset.  */
+/* Generic implementation of the getter for the __dict__ attribute for objects
+   having a dictionary.  The CLOSURE argument is unused.  */
 
 PyObject *
-gdb_py_generic_dict (PyObject *self, void *closure)
+gdb_py_generic_dict_getter (PyObject *self,
+			    void *closure ATTRIBUTE_UNUSED)
 {
-  PyObject *result;
-  PyTypeObject *type_obj = (PyTypeObject *) closure;
-  char *raw_ptr;
+  PyObject **py_dict_ptr = gdbpy_dict_wrapper::compute_addr (self);
+  PyObject *py_dict = *py_dict_ptr;
+  if (py_dict == nullptr)
+    {
+      PyErr_SetString (PyExc_AttributeError,
+		       "This object has no __dict__");
+      return nullptr;
+    }
+  return Py_NewRef (py_dict);
+}
 
-  raw_ptr = (char *) self + type_obj->tp_dictoffset;
-  result = * (PyObject **) raw_ptr;
+/* Generic attribute getter function similar to PyObject_GenericGetAttr () but
+   that should be used when the object has a dictionary __dict__.  */
+PyObject *
+gdb_py_generic_getattro (PyObject *self, PyObject *attr)
+{
+  PyObject *value = PyObject_GenericGetAttr (self, attr);
+  if (value != nullptr)
+    return value;
+
+  if (! PyErr_ExceptionMatches (PyExc_AttributeError))
+    return nullptr;
+
+  gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr));
+  if (dict == nullptr)
+    return nullptr;
+
+  /* Clear previous AttributeError set by PyObject_GenericGetAttr when it
+     did not find the attribute, and try to get the attribute from __dict__.  */
+  PyErr_Clear();
+
+  value = PyDict_GetItemWithError (dict.get (), attr);
+  if (value != nullptr)
+    return Py_NewRef (value);
+
+  /* PyDict_GetItemWithError() has a misleading name that lets one believe that
+     the function sets PyExc_AttributeError when the key is not found, whereas
+     it actually does not and only returns NULL.
+     Not setting the exception for an unfound key causes the raising of another
+     exception:
+       <class 'SystemError'>: error return without exception set
+     Hence this surprising manual setting of an exception to makes everything
+     fit into place.  */
+  PyErr_Format (PyExc_AttributeError,
+		"'%s' object has no attribute '%s'",
+		Py_TYPE (self)->tp_name,
+		PyUnicode_AsUTF8AndSize (attr, nullptr));
+  return nullptr;
+}
 
-  Py_INCREF (result);
-  return result;
+/* Generic attribute setter function similar to PyObject_GenericSetAttr () but
+   that should be used when the object has a dictionary __dict__.  */
+int
+gdb_py_generic_setattro (PyObject *self, PyObject *attr, PyObject *value)
+{
+  if (PyObject_GenericSetAttr (self, attr, value) == 0)
+    return 0;
+
+  if (! PyErr_ExceptionMatches (PyExc_AttributeError))
+    return -1;
+
+  gdbpy_ref<> dict (gdb_py_generic_dict_getter (self, nullptr));
+  if (dict == nullptr)
+    return -1;
+
+  /* Clear previous AttributeError set by PyObject_GenericGetAttr() when it
+     did not find the attribute, and try to set the attribute into __dict__.  */
+  PyErr_Clear();
+
+  /* Set the new value.
+     Note: the old value is managed by PyDict_SetItem(), so no need to get
+     a borrowed reference on it and decrement its reference counter before
+     setting a new value.  */
+  return PyDict_SetItem (dict.get (), attr, value);
 }
 
+\f
+
 /* Like PyModule_AddObject, but does not steal a reference to
    OBJECT.  */
 
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 95619bf775e..f6915a62b7a 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -107,6 +107,15 @@ typedef unsigned long gdb_py_ulongest;
 
 #endif /* HAVE_LONG_LONG */
 
+#if PY_VERSION_HEX < 0x030a0000
+static inline PyObject *
+Py_NewRef (PyObject *obj)
+{
+  Py_INCREF (obj);
+  return obj;
+}
+#endif
+
 /* A template variable holding the format character (as for
    Py_BuildValue) for a given type.  */
 template<typename T>
@@ -384,22 +393,27 @@ struct gdbpy_breakpoint_object
 extern gdbpy_breakpoint_object *bppy_pending_object;
 
 
-struct thread_object
+struct thread_object : public gdbpy_dict_wrapper
 {
-  PyObject_HEAD
-
   /* The thread we represent.  */
   struct thread_info *thread;
 
   /* The Inferior object to which this thread belongs.  */
   PyObject *inf_obj;
-
-  /* Dictionary holding user-added attributes.  This is the __dict__
-     attribute of the object.  */
-  PyObject *dict;
 };
 
-struct inferior_object;
+using thread_map_t
+  = gdb::unordered_map<thread_info *, gdbpy_ref<thread_object>>;
+
+struct inferior_object : public gdbpy_dict_wrapper
+{
+  /* The inferior we represent.  */
+  struct inferior *inferior;
+
+  /* thread_object instances under this inferior.  This owns a
+     reference to each object it contains.  */
+  thread_map_t *threads;
+};
 
 extern struct cmd_list_element *set_python_list;
 extern struct cmd_list_element *show_python_list;
@@ -989,7 +1003,9 @@ gdbpy_ref<> gdb_py_object_from_longest (LONGEST l);
 gdbpy_ref<> gdb_py_object_from_ulongest (ULONGEST l);
 int gdb_py_int_as_long (PyObject *, long *);
 
-PyObject *gdb_py_generic_dict (PyObject *self, void *closure);
+PyObject *gdb_py_generic_dict_getter (PyObject *self, void *closure);
+PyObject *gdb_py_generic_getattro (PyObject *self, PyObject *attr);
+int gdb_py_generic_setattro (PyObject *self, PyObject *attr, PyObject *value);
 
 int gdb_pymodule_addobject (PyObject *mod, const char *name,
 			    PyObject *object);
-- 
2.52.0


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

end of thread, other threads:[~2026-01-29 14:48 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-27 17:02 [PATCH v2 0/6] gdb: minor fixes for Python limited C API support Matthieu Longo
2026-01-27 17:02 ` [PATCH v2 1/6] Python limited API: migrate Py_CompileStringExFlags and PyRun_SimpleString Matthieu Longo
2026-01-27 17:54   ` Tom Tromey
2026-01-27 17:02 ` [PATCH v2 2/6] Python limited API: migrate PyImport_ExtendInittab Matthieu Longo
2026-01-27 17:54   ` Tom Tromey
2026-01-27 17:02 ` [PATCH v2 3/6] gdbpy_registry: cast C extension type object to PyObject * before Py_XINCREF Matthieu Longo
2026-01-27 18:01   ` Tom Tromey
2026-01-27 18:29   ` Tom Tromey
2026-01-28 11:58     ` Matthieu Longo
2026-01-27 17:02 ` [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes Matthieu Longo
2026-01-27 19:06   ` Tom Tromey
2026-01-28 11:57     ` Matthieu Longo
2026-01-28 17:43     ` Matthieu Longo
2026-01-28 17:51       ` Tom Tromey
2026-01-27 17:02 ` [PATCH v2 5/6] gdb: cast all Python extension objects passed to gdbpy_ref_policy to PyObject* Matthieu Longo
2026-01-27 18:28   ` Tom Tromey
2026-01-28 11:58     ` Matthieu Longo
2026-01-27 17:02 ` [PATCH v2 6/6] gdb: make remaining Python extension objects inherit from PyObject Matthieu Longo
2026-01-27 18:29   ` Tom Tromey
2026-01-28 11:58     ` Matthieu Longo
2026-01-28 12:02 [PATCH v2 4/6] gdb: new setters and getters for __dict__, and attributes Matthieu Longo
2026-01-28 18:00 ` Tom Tromey
2026-01-29 10:42   ` Matthieu Longo
2026-01-29 14:45     ` Tom Tromey

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