From: Andrew Burgess Date: Thu, 14 May 2026 08:39:57 +0000 (+0100) Subject: gdb/python: add type traits check for all PyObject sub-classes X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=60cd57ff6ea641cd1261bd7f16f11f2ad6aab727;p=thirdparty%2Fbinutils-gdb.git gdb/python: add type traits check for all PyObject sub-classes All of our custom Python types are created as structs, like this: struct some_new_type : public PyObject { ... various fields ... }; Then instances of this struct are created by calling PyObject_New, either directly within GDB's C++ code, or within Python when a user's Python script creates an instance of that class. The problem is that Python is written in C, and PyObject_New doesn't call any constructors for `some_new_type`, nor for any of the fields within `some_new_type`. If `some_new_type` is Plain Old Data (POD), then this is fine. Or, to be more C++ specific, if `some_new_type` is trivially default constructable, then we're fine. But if a field within `some_new_type` has a non-trivial constructor, then we're in trouble as that constructor will never be run. An example of a problematic field type is frame_info_ptr. The constructor for this type registers the new object with a central management object, recording the `this` pointer, using this type within `some_new_type` will not work as expected; frame invalidation will not show up within the frame_info_ptr as you might expect. And so, this type trait exists. Whenever a struct is created to define a new Python type we should add a line like: static_assert (gdb::is_python_allocatable_v); This will fail if any field of `some_new_type` are unsuitable for this use. We don't actually check is_trivially_default_constructible here. Some types, e.g. ui_file_style::color, have non-trivial (or no default) constructors, but are still safe to use within `some_new_type` because their constructors just initialise data fields; there's nothing "special" that the constructor does that cannot be achieved by assigning the fields after creation with PyObject_New. What actually matters is that the type is trivially destructible (Python won't call C++ destructors, so destructors with side effects, like deregistering from a list, would be skipped) and trivially copyable (Python may copy objects with memcpy). Types like frame_info_ptr, whose constructors and destructors have side effects such as registering with a central management object, will be caught because they are neither trivially destructible nor trivially copyable. Simple POD types like ui_file_style are trivially destructible and copyable, so pass this trait. This commit adds the new type trait, and makes use of it in all cases but one, pending_frame_object in python/py-unwind.c, has a field of type frame_info_ptr, which is currently broken. This will be fixed, and the static_assert added, in the next commit. Approved-By: Tom Tromey --- diff --git a/gdb/python/py-arch.c b/gdb/python/py-arch.c index ac0c40dcf45..7a0cb0a2a59 100644 --- a/gdb/python/py-arch.c +++ b/gdb/python/py-arch.c @@ -27,6 +27,8 @@ struct arch_object : public PyObject struct gdbarch *gdbarch; }; +static_assert (gdb::is_python_allocatable_v); + static const registry::key> arch_object_data; diff --git a/gdb/python/py-block.c b/gdb/python/py-block.c index 6fe18e45564..903f800683a 100644 --- a/gdb/python/py-block.c +++ b/gdb/python/py-block.c @@ -33,6 +33,8 @@ struct block_object : public PyObject struct objfile *objfile; }; +static_assert (gdb::is_python_allocatable_v); + struct block_syms_iterator_object : public PyObject { /* The block. */ @@ -47,6 +49,8 @@ struct block_syms_iterator_object : public PyObject block_object *source; }; +static_assert (gdb::is_python_allocatable_v); + /* Require a valid block. All access to block_object->block should be gated by this call. */ #define BLPY_REQUIRE_VALID(block_obj, block) \ diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c index 68d4f77c608..d1fec6a7700 100644 --- a/gdb/python/py-breakpoint.c +++ b/gdb/python/py-breakpoint.c @@ -46,6 +46,8 @@ struct gdbpy_breakpoint_location_object : public PyObject gdbpy_breakpoint_object *owner; }; +static_assert (gdb::is_python_allocatable_v); + /* Require that BREAKPOINT and LOCATION->OWNER are the same; throw a Python exception if they are not. */ #define BPLOCPY_REQUIRE_VALID(Breakpoint, Location) \ diff --git a/gdb/python/py-cmd.c b/gdb/python/py-cmd.c index c515446dad8..24938fcda15 100644 --- a/gdb/python/py-cmd.c +++ b/gdb/python/py-cmd.c @@ -63,6 +63,8 @@ struct cmdpy_object : public PyObject struct cmd_list_element *sub_list; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject cmdpy_object_type; /* Constants used by this module. */ diff --git a/gdb/python/py-color.c b/gdb/python/py-color.c index 971209958cf..29a42977adc 100644 --- a/gdb/python/py-color.c +++ b/gdb/python/py-color.c @@ -43,6 +43,8 @@ struct colorpy_object : public PyObject ui_file_style::color color; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject colorpy_object_type; /* See py-color.h. */ diff --git a/gdb/python/py-connection.c b/gdb/python/py-connection.c index 43e03ad81f7..02330b28068 100644 --- a/gdb/python/py-connection.c +++ b/gdb/python/py-connection.c @@ -42,6 +42,8 @@ struct connection_object : public PyObject struct process_stratum_target *target; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject connection_object_type; extern PyTypeObject remote_connection_object_type; diff --git a/gdb/python/py-corefile.c b/gdb/python/py-corefile.c index 5acfd98d326..fbefbd267f4 100644 --- a/gdb/python/py-corefile.c +++ b/gdb/python/py-corefile.c @@ -65,6 +65,8 @@ struct corefile_mapped_file_object : public PyObject bool is_main_exec_p; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject corefile_mapped_file_object_type; /* A gdb.CorefileMappedFileRegion object. */ @@ -80,6 +82,8 @@ struct corefile_mapped_file_region_object : public PyObject ULONGEST file_offset; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject corefile_mapped_file_region_object_type; /* Clear the inferior pointer in a Corefile object OBJ when an inferior is diff --git a/gdb/python/py-disasm.c b/gdb/python/py-disasm.c index d4d50e90035..e5d7174b7d6 100644 --- a/gdb/python/py-disasm.c +++ b/gdb/python/py-disasm.c @@ -50,6 +50,8 @@ struct disasm_info_object : public PyObject struct disasm_info_object *next; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject disasm_info_object_type; /* Implement gdb.disassembler.DisassembleAddressPart type. An object of @@ -69,6 +71,8 @@ struct disasm_addr_part_object : public PyObject struct gdbarch *gdbarch; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject disasm_addr_part_object_type; /* Implement gdb.disassembler.DisassembleTextPart type. An object of @@ -84,6 +88,8 @@ struct disasm_text_part_object : public PyObject enum disassembler_style style; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject disasm_text_part_object_type; extern PyTypeObject disasm_part_object_type; @@ -103,6 +109,8 @@ struct disasm_result_object : public PyObject std::vector> *parts; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject disasm_result_object_type; /* When this is false we fast path out of gdbpy_print_insn, which should diff --git a/gdb/python/py-events.h b/gdb/python/py-events.h index e44b4b4a761..169a5f4a4db 100644 --- a/gdb/python/py-events.h +++ b/gdb/python/py-events.h @@ -32,6 +32,8 @@ struct eventregistry_object : public PyObject PyObject *callbacks; }; +static_assert (gdb::is_python_allocatable_v); + /* Struct holding references to event registries both in python and c. This is meant to be a singleton. */ diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c index 374b934a0d9..0a1a0dffbf9 100644 --- a/gdb/python/py-frame.c +++ b/gdb/python/py-frame.c @@ -44,6 +44,8 @@ struct frame_object : public PyObject int frame_id_is_next; }; +static_assert (gdb::is_python_allocatable_v); + /* Require a valid frame. This must be called inside a TRY_CATCH, or another context in which a gdb exception is allowed. */ #define FRAPY_REQUIRE_VALID(frame_obj, frame) \ diff --git a/gdb/python/py-instruction.c b/gdb/python/py-instruction.c index df64d85988f..d8dad094ee9 100644 --- a/gdb/python/py-instruction.c +++ b/gdb/python/py-instruction.c @@ -32,6 +32,8 @@ PyTypeObject py_insn_type = { struct py_insn_obj: public PyObject {}; +static_assert (gdb::is_python_allocatable_v); + /* Getter function for gdb.Instruction attributes. */ static PyObject * diff --git a/gdb/python/py-lazy-string.c b/gdb/python/py-lazy-string.c index ba3920e07c3..c9d06338b0a 100644 --- a/gdb/python/py-lazy-string.c +++ b/gdb/python/py-lazy-string.c @@ -51,6 +51,8 @@ struct lazy_string_object : public PyObject PyObject *type; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject lazy_string_object_type; static PyObject * diff --git a/gdb/python/py-linetable.c b/gdb/python/py-linetable.c index d2d85026a32..1db13af4c12 100644 --- a/gdb/python/py-linetable.c +++ b/gdb/python/py-linetable.c @@ -27,6 +27,8 @@ struct linetable_entry_object : public PyObject CORE_ADDR pc; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject linetable_entry_object_type; struct linetable_object : public PyObject @@ -37,6 +39,8 @@ struct linetable_object : public PyObject PyObject *symtab; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject linetable_object_type; struct ltpy_iterator_object : public PyObject @@ -49,6 +53,8 @@ struct ltpy_iterator_object : public PyObject PyObject *source; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject ltpy_iterator_object_type; /* Internal helper function to extract gdb.Symtab from a gdb.LineTable diff --git a/gdb/python/py-membuf.c b/gdb/python/py-membuf.c index e3bf5e2ceab..fa5156b885e 100644 --- a/gdb/python/py-membuf.c +++ b/gdb/python/py-membuf.c @@ -31,6 +31,8 @@ struct membuf_object : public PyObject CORE_ADDR length; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject membuf_object_type; /* Wrap BUFFER, ADDRESS, and LENGTH into a gdb.Membuf object. ADDRESS is diff --git a/gdb/python/py-micmd.c b/gdb/python/py-micmd.c index 73f5297bd70..9ae7fc51bee 100644 --- a/gdb/python/py-micmd.c +++ b/gdb/python/py-micmd.c @@ -74,6 +74,8 @@ struct micmdpy_object : public PyObject char *mi_command_name; }; +static_assert (gdb::is_python_allocatable_v); + /* The MI command implemented in Python. */ struct mi_command_py : public mi_command diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c index bc028f1746c..35f55c3dbd0 100644 --- a/gdb/python/py-param.c +++ b/gdb/python/py-param.c @@ -141,6 +141,8 @@ struct parmpy_object : public PyObject const char **enumeration; }; +static_assert (gdb::is_python_allocatable_v); + /* Wraps a setting around an existing parmpy_object. This abstraction is used to manipulate the value in S->VALUE in a type safe manner using the setting interface. */ diff --git a/gdb/python/py-prettyprint.c b/gdb/python/py-prettyprint.c index 72ad5efcf07..d3faf861fcf 100644 --- a/gdb/python/py-prettyprint.c +++ b/gdb/python/py-prettyprint.c @@ -784,6 +784,8 @@ gdbpy_get_print_options (value_print_options *opts) struct printer_object : public PyObject {}; +static_assert (gdb::is_python_allocatable_v); + /* 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 f46b434313e..e60bc44e81f 100644 --- a/gdb/python/py-record-btrace.c +++ b/gdb/python/py-record-btrace.c @@ -48,6 +48,8 @@ struct btpy_list_object : public PyObject PyTypeObject* element_type; }; +static_assert (gdb::is_python_allocatable_v); + /* Python type for btrace lists. */ static PyTypeObject btpy_list_type = { diff --git a/gdb/python/py-record.c b/gdb/python/py-record.c index 3ceedecc165..15e1b9f3316 100644 --- a/gdb/python/py-record.c +++ b/gdb/python/py-record.c @@ -67,6 +67,8 @@ struct recpy_gap_object : public PyObject Py_ssize_t number; }; +static_assert (gdb::is_python_allocatable_v); + /* Implementation of record.method. */ static PyObject * diff --git a/gdb/python/py-record.h b/gdb/python/py-record.h index 35ab54cf6ab..f62fcc2a7e9 100644 --- a/gdb/python/py-record.h +++ b/gdb/python/py-record.h @@ -34,6 +34,8 @@ struct recpy_record_object : public PyObject enum record_method method; }; +static_assert (gdb::is_python_allocatable_v); + /* Python recorded element object. This is generic enough to represent recorded instructions as well as recorded function call segments, hence the generic name. */ @@ -49,6 +51,8 @@ struct recpy_element_object : public PyObject Py_ssize_t number; }; +static_assert (gdb::is_python_allocatable_v); + /* Python RecordInstruction type. */ extern PyTypeObject recpy_insn_type; diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h index dc0b14814af..3d2906fd7f5 100644 --- a/gdb/python/py-ref.h +++ b/gdb/python/py-ref.h @@ -21,6 +21,7 @@ #define GDB_PYTHON_PY_REF_H #include "gdbsupport/gdb_ref_ptr.h" +#include "python-traits.h" /* A policy class for gdb::ref_ptr for Python reference counting. */ struct gdbpy_ref_policy @@ -102,4 +103,6 @@ struct gdbpy_dict_wrapper : public PyObject } }; +static_assert (gdb::is_python_allocatable_v); + #endif /* GDB_PYTHON_PY_REF_H */ diff --git a/gdb/python/py-registers.c b/gdb/python/py-registers.c index 14cf19a6e66..cc7b09079da 100644 --- a/gdb/python/py-registers.c +++ b/gdb/python/py-registers.c @@ -45,6 +45,8 @@ struct register_descriptor_iterator_object : public PyObject struct gdbarch *gdbarch; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject register_descriptor_iterator_object_type; /* A register descriptor. */ @@ -57,6 +59,8 @@ struct register_descriptor_object : public PyObject struct gdbarch *gdbarch; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject register_descriptor_object_type; /* Structure for iterator over register groups. */ @@ -69,6 +73,8 @@ struct reggroup_iterator_object : public PyObject struct gdbarch *gdbarch; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject reggroup_iterator_object_type; /* A register group object. */ @@ -78,6 +84,8 @@ struct reggroup_object : public PyObject const struct reggroup *reggroup; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject reggroup_object_type; /* Map from GDB's internal reggroup objects to the Python diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c index 60a9ea4792f..c45eba60867 100644 --- a/gdb/python/py-style.c +++ b/gdb/python/py-style.c @@ -46,6 +46,8 @@ struct style_object : public PyObject char *style_name; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject style_object_type; /* Initialize the 'style' module. */ diff --git a/gdb/python/py-symbol.c b/gdb/python/py-symbol.c index 76255f53f85..5b1d843a36f 100644 --- a/gdb/python/py-symbol.c +++ b/gdb/python/py-symbol.c @@ -31,6 +31,8 @@ struct symbol_object : public PyObject struct symbol *symbol; }; +static_assert (gdb::is_python_allocatable_v); + /* Require a valid symbol. All access to symbol_object->symbol should be gated by this call. */ #define SYMPY_REQUIRE_VALID(symbol_obj, symbol) \ diff --git a/gdb/python/py-symtab.c b/gdb/python/py-symtab.c index f1fbfa58d65..9a343167624 100644 --- a/gdb/python/py-symtab.c +++ b/gdb/python/py-symtab.c @@ -32,6 +32,8 @@ struct symtab_object : public PyObject struct symtab *symtab; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject symtab_object_type; static const gdbpy_registry> stpy_registry; @@ -61,6 +63,8 @@ struct sal_object : public PyObject sal_object *next; }; +static_assert (gdb::is_python_allocatable_v); + /* This is called when an objfile is about to be freed. Invalidate the sal object as further actions on the sal would result in bad data. All access to obj->sal should be gated by diff --git a/gdb/python/py-tui.c b/gdb/python/py-tui.c index 111e0f4c9e3..ca0fb89ea49 100644 --- a/gdb/python/py-tui.c +++ b/gdb/python/py-tui.c @@ -53,6 +53,8 @@ struct gdbpy_tui_window: public PyObject bool is_valid () const; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject gdbpy_tui_window_object_type; /* A TUI window written in Python. */ diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c index cbb984309eb..c002d97d0c6 100644 --- a/gdb/python/py-type.c +++ b/gdb/python/py-type.c @@ -33,6 +33,8 @@ struct type_object : public PyObject struct type *type; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject type_object_type; /* A Field object. */ @@ -52,6 +54,8 @@ struct typy_iterator_object : public PyObject type_object *source; }; +static_assert (gdb::is_python_allocatable_v); + extern PyTypeObject type_iterator_object_type; /* This is used to initialize various gdb.TYPE_ constants. */ diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c index 6bff66849df..eef926c6dfa 100644 --- a/gdb/python/py-unwind.c +++ b/gdb/python/py-unwind.c @@ -104,6 +104,8 @@ struct unwind_info_object : public PyObject std::vector *saved_regs; }; +static_assert (gdb::is_python_allocatable_v); + /* The data we keep for a frame we can unwind: frame ID and an array of (register_number, register_value) pairs. */ diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c index 04b34758b90..5b38110396e 100644 --- a/gdb/python/py-value.c +++ b/gdb/python/py-value.c @@ -64,6 +64,8 @@ struct value_object : public PyObject PyObject *content_bytes; }; +static_assert (gdb::is_python_allocatable_v); + /* List of all values which are currently exposed to Python. It is maintained so that when an objfile is discarded, preserve_values can copy the values' types if needed. */ diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 693829e920d..82f3262ae07 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -367,6 +367,8 @@ struct gdbpy_breakpoint_object : public PyObject int is_finish_bp; }; +static_assert (gdb::is_python_allocatable_v); + /* Require that BREAKPOINT be a valid breakpoint ID; throw a Python exception if it is invalid. */ #define BPPY_REQUIRE_VALID(Breakpoint) \ diff --git a/gdb/python/python-traits.h b/gdb/python/python-traits.h new file mode 100644 index 00000000000..d1d3053cc10 --- /dev/null +++ b/gdb/python/python-traits.h @@ -0,0 +1,93 @@ +/* Type traits relating to GDB's Python integration. + + Copyright (C) 2008-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 . */ + +#ifndef GDB_PYTHON_PYTHON_TRAITS_H +#define GDB_PYTHON_PYTHON_TRAITS_H + +namespace gdb +{ +/* All of our custom Python types are created as structs, like this: + + struct some_new_type : public PyObject + { + ... various fields ... + }; + + Then instances of this struct are created by calling PyObject_New, + either directly within GDB's C++ code, or within Python when a user's + Python script creates an instance of that class. + + The problem is that Python is written in C, and PyObject_New doesn't + call any constructors for `some_new_type`, nor for any of the fields + within `some_new_type`. + + If `some_new_type` is Plain Old Data (POD), then this is fine. Or, to + be more C++ specific, if `some_new_type` is trivially default + constructable, then we're fine. + + But if a field within `some_new_type` has a non-trivial constructor, + then we're in trouble as that constructor will never be run. + + An example of a problematic field type is frame_info_ptr. The + constructor for this type registers the new object with a central + management object, recording the `this` pointer, using this type within + `some_new_type` will not work as expected; frame invalidation will not + show up within the frame_info_ptr as you might expect. + + And so, this type trait exists. Whenever a struct is created to define + a new Python type we should add a line like: + + static_assert (gdb::is_python_allocatable_v); + + This will fail if any field of `some_new_type` is unsuitable for this + use. + + We don't actually check is_trivially_default_constructible here. Some + types, e.g. ui_file_style::color, have non-trivial (or no default) + constructors, but are still safe to use within `some_new_type` because + their constructors just initialise data fields; there's nothing + "special" that the constructor does that cannot be achieved by + assigning the fields after creation with PyObject_New. + + What actually matters is that the type is trivially destructible + (Python won't call C++ destructors, so destructors with side effects, + like deregistering from a list, would be skipped) and trivially + copyable (Python may copy objects with memcpy). Types like + frame_info_ptr, whose constructors and destructors have side effects + such as registering with a central management object, will be caught + because they are neither trivially destructible nor trivially copyable. + Types like ui_file_style are trivially destructible and copyable, so + pass this trait. */ + +template +struct is_python_allocatable +{ + static constexpr bool value = + std::is_trivially_destructible_v + && std::is_trivially_copyable_v; +}; + +/* Helper for the above trait to make it more usable. */ + +template +inline constexpr bool is_python_allocatable_v + = is_python_allocatable::value; +} + +#endif /* GDB_PYTHON_PYTHON_TRAITS_H */