* 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; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ messages in thread
* [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 4/6] gdb: new setters and getters for __dict__, and attributes Matthieu Longo
0 siblings, 1 reply; 9+ 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] 9+ 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
@ 2026-01-27 17:02 ` Matthieu Longo
2026-01-27 19:06 ` Tom Tromey
0 siblings, 1 reply; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ 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; 9+ 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] 9+ messages in thread
end of thread, other threads:[~2026-01-29 14:48 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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
-- strict thread matches above, loose matches on Subject: below --
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 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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox