gdb: add new helpers for retrieving a type's fully qualified name

Py_TYPE (self)->tp_name is the traditional idiomatic way to get a Python
type's fully qualified name. However, in the context of the Python
limited API, PyTypeObject is opaque, so the 'tp_name' attribute is no
longer accessible. Additionally, retrieving the type of a Python object
requires Py_TYPE, which is only available as part of the stable API
starting with Python 3.14.

This patch increases minimal Python limited API version from 3.11 to 3.14.
It also introduces two new helpers to retrieve a type's fully qualified
name: gdb_py_tp_name() and gdbpy_py_obj_tp_name(), which extract the fully
qualified name from a PyTypeObject and a PyObject, respectively. Ifdefery
allows these wrappers to select the appropriate API depending on the Python
version and whether the Python limited API is enabled. For any Python
version less than 3.13, gdb_py_tp_name() fallbacks using __qualname__
instead. However, the result may differ slightly in some cases, e.g. the
module name may be missing.

Finally, this patch adapts the existing code to use these wrappers, and
adjusts some test expectations to use the fully qualified name (or
__qualname__ for versions <= 3.13) where it was not previously used.
Note that the corner case where the module name would be missing does not
appear in the testsuite.

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

Approved-By: Tom Tromey <tom@tromey.com>
This commit is contained in:
Matthieu Longo 2026-01-28 13:09:51 +00:00
parent b1cc575c42
commit e3998113f9
24 changed files with 169 additions and 55 deletions

View file

@ -341,7 +341,8 @@ archpy_repr (PyObject *self)
auto arch_info = gdbarch_bfd_arch_info (gdbarch);
return PyUnicode_FromFormat ("<%s arch_name=%s printable_name=%s>",
Py_TYPE (self)->tp_name, arch_info->arch_name,
gdbpy_py_obj_tp_name (self),
arch_info->arch_name,
arch_info->printable_name);
}

View file

@ -522,7 +522,8 @@ blpy_repr (PyObject *self)
if (++written_symbols < len)
str += ", ";
}
return PyUnicode_FromFormat ("<%s %s {%s}>", Py_TYPE (self)->tp_name,
return PyUnicode_FromFormat ("<%s %s {%s}>",
gdbpy_py_obj_tp_name (self),
name, str.c_str ());
}

View file

@ -1065,9 +1065,11 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
static PyObject *
bppy_repr (PyObject *self)
{
const char *tp_name = gdbpy_py_obj_tp_name (self);
const auto bp = (struct gdbpy_breakpoint_object*) self;
if (bp->bp == nullptr)
return PyUnicode_FromFormat ("<%s (invalid)>", Py_TYPE (self)->tp_name);
return PyUnicode_FromFormat ("<%s (invalid)>", tp_name);
std::string str = " ";
if (bp->bp->thread != -1)
@ -1079,7 +1081,7 @@ bppy_repr (PyObject *self)
str.pop_back ();
return PyUnicode_FromFormat ("<%s%s number=%d hits=%d%s>",
Py_TYPE (self)->tp_name,
tp_name,
(bp->bp->enable_state == bp_enabled
? "" : " disabled"), bp->bp->number,
bp->bp->hit_count, str.c_str ());
@ -1771,7 +1773,8 @@ bplocpy_repr (PyObject *py_self)
str += fn_name;
}
return PyUnicode_FromFormat ("<%s %s>", Py_TYPE (self)->tp_name,
return PyUnicode_FromFormat ("<%s %s>",
gdbpy_py_obj_tp_name (py_self),
str.c_str ());
}

View file

@ -201,7 +201,7 @@ connpy_repr (PyObject *obj)
return gdb_py_invalid_object_repr (obj);
return PyUnicode_FromFormat ("<%s num=%d, what=\"%s\">",
Py_TYPE (obj)->tp_name,
gdbpy_py_obj_tp_name (obj),
target->connection_number,
make_target_connection_string (target).c_str ());
}

View file

@ -361,7 +361,7 @@ cfpy_repr (PyObject *self)
bfd *core_bfd = get_inferior_core_bfd (obj->inferior);
gdb_assert (core_bfd != nullptr);
return PyUnicode_FromFormat ("<%s inferior=%d filename='%s'>",
Py_TYPE (self)->tp_name,
gdbpy_py_obj_tp_name (self),
obj->inferior->num,
bfd_get_filename (core_bfd));
}

View file

@ -304,7 +304,7 @@ disasmpy_info_repr (PyObject *self)
const char *arch_name
= (gdbarch_bfd_arch_info (obj->gdbarch))->printable_name;
return PyUnicode_FromFormat ("<%s address=%s architecture=%s>",
Py_TYPE (obj)->tp_name,
gdbpy_py_obj_tp_name (self),
core_addr_to_string_nz (obj->address),
arch_name);
}
@ -995,7 +995,7 @@ disasmpy_result_init (PyObject *self, PyObject *args, PyObject *kwargs)
{
PyErr_Format (PyExc_ValueError,
_("Cannot use 'string' and 'parts' when creating %s."),
Py_TYPE (self)->tp_name);
gdbpy_py_obj_tp_name (self));
return -1;
}
@ -1079,7 +1079,7 @@ disasmpy_result_repr (PyObject *self)
gdb_assert (obj->parts != nullptr);
return PyUnicode_FromFormat ("<%s length=%d string=\"%U\">",
Py_TYPE (obj)->tp_name,
gdbpy_py_obj_tp_name (self),
obj->length,
disasmpy_result_str (self));
}
@ -1294,7 +1294,7 @@ gdbpy_print_insn (struct gdbarch *gdbarch, CORE_ADDR memaddr,
PyErr_Format
(PyExc_TypeError,
_("Result from Disassembler must be gdb.DisassemblerResult, not %s."),
Py_TYPE (result.get ())->tp_name);
gdbpy_py_obj_tp_name (result.get ()));
gdbpy_print_stack ();
return std::optional<int> (-1);
}
@ -1381,8 +1381,9 @@ disasmpy_dealloc_result (PyObject *self)
static int
disasmpy_part_init (PyObject *self, PyObject *args, PyObject *kwargs)
{
PyErr_SetString (PyExc_RuntimeError,
_("Cannot create instances of DisassemblerPart."));
PyErr_Format (PyExc_RuntimeError,
_("Cannot create instances of %s."),
gdbpy_py_obj_tp_name (self));
return -1;
}
@ -1419,7 +1420,7 @@ disasmpy_text_part_repr (PyObject *self)
gdb_assert (obj->string != nullptr);
return PyUnicode_FromFormat ("<%s string='%s', style='%s'>",
Py_TYPE (obj)->tp_name,
gdbpy_py_obj_tp_name (self),
obj->string->c_str (),
get_style_name (obj->style));
}
@ -1462,7 +1463,7 @@ disasmpy_addr_part_repr (PyObject *self)
disasm_addr_part_object *obj = (disasm_addr_part_object *) self;
return PyUnicode_FromFormat ("<%s address='%s'>",
Py_TYPE (obj)->tp_name,
gdbpy_py_obj_tp_name (self),
core_addr_to_string_nz (obj->address));
}

View file

@ -95,7 +95,7 @@ frapy_repr (PyObject *self)
const frame_id &fid = frame_obj->frame_id;
return PyUnicode_FromFormat ("<%s level=%d frame-id=%s>",
Py_TYPE (self)->tp_name,
gdbpy_py_obj_tp_name (self),
frame_relative_level (f_info),
fid.to_string ().c_str ());
}
@ -544,7 +544,7 @@ frapy_read_var (PyObject *self, PyObject *args, PyObject *kw)
{
PyErr_Format (PyExc_TypeError,
_("argument 1 must be gdb.Symbol or str, not %s"),
Py_TYPE (sym_obj)->tp_name);
gdbpy_py_obj_tp_name (sym_obj));
return NULL;
}

View file

@ -354,7 +354,7 @@ thpy_repr (PyObject *self)
thread_info *thr = thread_obj->thread;
return PyUnicode_FromFormat ("<%s id=%s target-id=\"%s\">",
Py_TYPE (self)->tp_name,
gdbpy_py_obj_tp_name (self),
print_full_thread_id (thr),
target_pid_to_str (thr->ptid).c_str ());
}

View file

@ -379,7 +379,7 @@ gdbpy_notify_mi (PyObject *self, PyObject *args, PyObject *kwargs)
PyErr_Format
(PyExc_ValueError,
_("MI notification data must be either None or a dictionary, not %s"),
Py_TYPE (data)->tp_name);
gdbpy_py_obj_tp_name (data));
return nullptr;
}

View file

@ -509,7 +509,7 @@ micmdpy_set_installed (PyObject *self, PyObject *newvalue, void *closure)
{
PyErr_Format (PyExc_TypeError,
_("gdb.MICommand.installed must be set to a bool, not %s"),
newvalue == Py_None ? "None" : Py_TYPE(newvalue)->tp_name);
newvalue == Py_None ? "None" : gdbpy_py_obj_tp_name (newvalue));
return -1;
}

74
gdb/python/py-obj-type.c Normal file
View file

@ -0,0 +1,74 @@
/* Helpers related to Python object type
Copyright (C) 2026 Free Software Foundation, Inc.
This file is part of GDB.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "python-internal.h"
#include "py-obj-type.h"
/* Return the type's fully qualified name from a PyTypeObject. */
const char *
gdb_py_tp_name (PyTypeObject *py_type) noexcept
{
#if PY_VERSION_HEX >= 0x030d0000
/* Note: PyType_GetFullyQualifiedName() was added in version 3.13, and is
part of the stable ABI since version 3.13. */
PyObject *fully_qualified_name = PyType_GetFullyQualifiedName (py_type);
if (fully_qualified_name == nullptr)
return nullptr;
return PyUnicode_AsUTF8AndSize (fully_qualified_name, nullptr);
#else /* PY_VERSION_HEX < 0x030d0000 && ! defined (Py_LIMITED_API) */
/* For non-heap types, the fully qualified name corresponds to tp_name. */
if (! (PyType_GetFlags (py_type) & Py_TPFLAGS_HEAPTYPE))
return py_type->tp_name;
/* In the absence of PyType_GetFullyQualifiedName(), we fallback using
__qualname__ instead. However, the result may differ slightly in some
cases, e.g. the module name may be missing. */
# if PY_VERSION_HEX >= 0x030b0000
/* Note: PyType_GetQualName() was added in version 3.11. */
PyObject *qualname = PyType_GetQualName (py_type);
if (qualname == nullptr)
return nullptr;
return PyUnicode_AsUTF8AndSize (qualname, nullptr);
# else
/* In the absence of PyType_GetQualName(), fallback on using PyHeapTypeObject
which is not part of the public API.
Tested on 3.10 which is the oldest supported version at the time of this
writing, i.e. February 2026. Hopefully, this workaround should go away
when the minimum supported Python version is increased above 3.10. */
PyHeapTypeObject *ht = (PyHeapTypeObject *) py_type;
if (ht->ht_qualname == nullptr)
return nullptr;
return PyUnicode_AsUTF8AndSize (ht->ht_qualname, nullptr);
# endif
#endif
}
/* Return the type's fully qualified name from a PyObject. */
const char *
gdbpy_py_obj_tp_name (PyObject *self) noexcept
{
/* Note: Py_TYPE () is part of the stable ABI since version 3.14. */
return gdb_py_tp_name (Py_TYPE (self));
}

29
gdb/python/py-obj-type.h Normal file
View file

@ -0,0 +1,29 @@
/* Helpers related to Python object type
Copyright (C) 2026 Free Software Foundation, Inc.
This file is part of GDB.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#ifndef GDB_PYTHON_PY_OBJ_TYPE_H
#define GDB_PYTHON_PY_OBJ_TYPE_H
/* Return the type's fully qualified name from a PyTypeObject. */
extern const char *gdb_py_tp_name (PyTypeObject *py_type) noexcept;
/* Return the type's fully qualified name from a PyObject. */
extern const char *gdbpy_py_obj_tp_name (PyObject *self) noexcept;
#endif /* GDB_PYTHON_PY_OBJ_TYPE_H */

View file

@ -267,7 +267,7 @@ stylepy_init_from_parts (PyObject *self, PyObject *fg, PyObject *bg,
PyErr_Format
(PyExc_TypeError,
_("'foreground' argument must be gdb.Color or None, not %s."),
Py_TYPE (fg)->tp_name);
gdbpy_py_obj_tp_name (fg));
return -1;
}
@ -276,7 +276,7 @@ stylepy_init_from_parts (PyObject *self, PyObject *fg, PyObject *bg,
PyErr_Format
(PyExc_TypeError,
_("'background' argument must be gdb.Color or None, not %s."),
Py_TYPE (bg)->tp_name);
gdbpy_py_obj_tp_name (bg));
return -1;
}
@ -483,7 +483,7 @@ stylepy_set_foreground (PyObject *self, PyObject *newvalue, void *closure)
if (!gdbpy_is_color (newvalue))
{
PyErr_Format (PyExc_TypeError, _("value must be gdb.Color, not %s"),
Py_TYPE (newvalue)->tp_name);
gdbpy_py_obj_tp_name (newvalue));
return -1;
}
@ -541,7 +541,7 @@ stylepy_set_background (PyObject *self, PyObject *newvalue, void *closure)
if (!gdbpy_is_color (newvalue))
{
PyErr_Format (PyExc_TypeError, _("value must be gdb.Color, not %s"),
Py_TYPE (newvalue)->tp_name);
gdbpy_py_obj_tp_name (newvalue));
return -1;
}
@ -623,7 +623,7 @@ stylepy_set_intensity (PyObject *self, PyObject *newvalue, void *closure)
PyErr_Format
(PyExc_TypeError,
_("value must be a Long (a gdb.INTENSITY constant), not %s"),
Py_TYPE (newvalue)->tp_name);
gdbpy_py_obj_tp_name (newvalue));
return -1;
}
@ -733,12 +733,12 @@ stylepy_repr (PyObject *self)
if (style_obj->style_name == nullptr)
return PyUnicode_FromFormat ("<%s fg=%s, bg=%s, intensity=%s>",
Py_TYPE (self)->tp_name,
gdbpy_py_obj_tp_name (self),
fg_str.get (), bg_str.get (),
intensity_str);
else
return PyUnicode_FromFormat ("<%s name='%s', fg=%s, bg=%s, intensity=%s>",
Py_TYPE (self)->tp_name,
gdbpy_py_obj_tp_name (self),
style_obj->style_name, fg_str.get (),
bg_str.get (), intensity_str);
}

View file

@ -384,7 +384,8 @@ sympy_repr (PyObject *self)
if (symbol == nullptr)
return gdb_py_invalid_object_repr (self);
return PyUnicode_FromFormat ("<%s print_name=%s>", Py_TYPE (self)->tp_name,
return PyUnicode_FromFormat ("<%s print_name=%s>",
gdbpy_py_obj_tp_name (self),
symbol->print_name ());
}

View file

@ -1081,7 +1081,8 @@ typy_repr (PyObject *self)
auto py_typename = PyUnicode_Decode (type_name.c_str (), type_name.size (),
host_charset (), NULL);
return PyUnicode_FromFormat ("<%s code=%s name=%U>", Py_TYPE (self)->tp_name,
return PyUnicode_FromFormat ("<%s code=%s name=%U>",
gdbpy_py_obj_tp_name (self),
code, py_typename);
}

View file

@ -247,7 +247,7 @@ unwind_infopy_repr (PyObject *self)
if (frame == nullptr)
return PyUnicode_FromFormat ("<%s for an invalid frame>",
Py_TYPE (self)->tp_name);
gdbpy_py_obj_tp_name (self));
std::string saved_reg_names;
struct gdbarch *gdbarch = pending_frame->gdbarch;
@ -262,7 +262,7 @@ unwind_infopy_repr (PyObject *self)
}
return PyUnicode_FromFormat ("<%s frame #%d, saved_regs=(%s)>",
Py_TYPE (self)->tp_name,
gdbpy_py_obj_tp_name (self),
frame_relative_level (frame),
saved_reg_names.c_str ());
}
@ -456,7 +456,7 @@ pending_framepy_repr (PyObject *self)
}
return PyUnicode_FromFormat ("<%s level=%d, sp=%s, pc=%s>",
Py_TYPE (self)->tp_name,
gdbpy_py_obj_tp_name (self),
frame_relative_level (frame),
sp_str,
pc_str);
@ -924,7 +924,7 @@ frame_unwind_python::sniff (const frame_info_ptr &this_frame,
gdb_assert (pyo_unwind_info != nullptr);
if (!PyObject_TypeCheck (pyo_unwind_info, &unwind_info_object_type))
error (_("an Unwinder should return gdb.UnwindInfo, not %s."),
Py_TYPE (pyo_unwind_info)->tp_name);
gdbpy_py_obj_tp_name (pyo_unwind_info));
{
unwind_info_object *unwind_info =

View file

@ -362,7 +362,7 @@ gdb_py_generic_getattro (PyObject *self, PyObject *attr)
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,
gdbpy_py_obj_tp_name (self),
PyUnicode_AsUTF8AndSize (attr, nullptr));
return nullptr;
}
@ -700,5 +700,5 @@ gdbpy_fix_doc_string_indentation (gdb::unique_xmalloc_ptr<char> doc)
PyObject *
gdb_py_invalid_object_repr (PyObject *self)
{
return PyUnicode_FromFormat ("<%s (invalid)>", Py_TYPE (self)->tp_name);
return PyUnicode_FromFormat ("<%s (invalid)>", gdbpy_py_obj_tp_name (self));
}

View file

@ -59,6 +59,7 @@
#include <Python.h>
#include <frameobject.h>
#include "py-ref.h"
#include "py-obj-type.h"
static_assert (PY_VERSION_HEX >= 0x03040000);
@ -1126,12 +1127,13 @@ gdbpy_type_ready (PyTypeObject *type, PyObject *mod = nullptr)
{
if (PyType_Ready (type) < 0)
return -1;
const char *tp_name = gdb_py_tp_name (type);
if (mod == nullptr)
{
gdb_assert (startswith (type->tp_name, "gdb."));
gdb_assert (startswith (tp_name, "gdb."));
mod = gdb_module;
}
const char *dot = strrchr (type->tp_name, '.');
const char *dot = strrchr (tp_name, '.');
gdb_assert (dot != nullptr);
return gdb_pymodule_addobject (mod, dot + 1, (PyObject *) type);
}

View file

@ -1579,7 +1579,7 @@ gdbpy_write (PyObject *self, PyObject *args, PyObject *kw)
PyErr_Format
(PyExc_TypeError,
_("'style' argument must be gdb.Style or None, not %s."),
Py_TYPE (style_obj)->tp_name);
gdbpy_py_obj_tp_name (style_obj));
return nullptr;
}