[RFC v3 09/25] gdb/python: add function () method to gdb.Type object

Jan Vrany jan.vrany@labware.com
Wed Jan 29 12:43:31 GMT 2025


This commit adds a new method to Python type objects that returns
possibly new function type returning that type. Parameter types can
be specified too.

This will be useful later to create types for function symbols created
using Python extension code.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
 gdb/NEWS                             |   3 +
 gdb/doc/python.texi                  |  16 +++
 gdb/gdbtypes.c                       |   8 ++
 gdb/gdbtypes.h                       |   3 +
 gdb/python/py-type.c                 | 189 +++++++++++++++++++++++++++
 gdb/testsuite/gdb.python/py-arch.exp |   4 +
 gdb/testsuite/gdb.python/py-type.exp |  45 +++++++
 7 files changed, 268 insertions(+)

diff --git a/gdb/NEWS b/gdb/NEWS
index b93e8f73497..f07bda4a065 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -163,6 +163,9 @@ binary-upload in qSupported reply
   ** Added gdb.Architecture.void_type. Returns a gdb.Type representing "void"
      type for that architecture.
 
+  ** Added gdb.Type.function.  Returns a new gdb.Type representing a function
+     returning that type.  Parameter types can be specified too.
+
 * Debugger Adapter Protocol changes
 
   ** The "scopes" request will now return a scope holding global
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index af9e1f8df5e..4c473cadc6b 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -1450,6 +1450,7 @@ A boolean indicating whether this type is string-like.  Like
 language of the type.
 @end defvar
 
+@anchor{python_type_owner}
 @defvar Type.owner
 For objfile-owned types, this property contains the owning @code{gdb.Objfile}
 (@pxref{Objfiles In Python}). For architecture-owned types, this property
@@ -1581,6 +1582,21 @@ Return a new @code{gdb.Type} object which represents a pointer to this
 type.
 @end defun
 
+@defun Type.function (@r{[}param_type@dots{}@r{]}, @r{[}varargs@r{]})
+Return a new @code{gdb.Type} object which represents a type of function
+returning this type.  Returned function type is always marked as prototyped.
+
+@var{param_type@dots{}} positional-only arguments specify parameter types.
+Passing @code{void} type is not allowed.  Moreover, all types (meaning this
+type and all of @var{param_type@dots{}}) must be either arch-owned or
+objfile-owned by the very same objfile (@pxref{python_type_owner,,
+The Type.owner attribute}).  If aforementioned rules are violated,
+@value{GDBN} throws a @code{ValueError}.
+
+The optional @var{varargs} keyword-only argument specifies whether to create
+vararg function type.  Defaults to @code{False}.
+@end defun
+
 @defun Type.strip_typedefs ()
 Return a new @code{gdb.Type} that represents the real type,
 after removing all layers of typedefs.
diff --git a/gdb/gdbtypes.c b/gdb/gdbtypes.c
index 741da7c1460..e6001c85d7a 100644
--- a/gdb/gdbtypes.c
+++ b/gdb/gdbtypes.c
@@ -5906,6 +5906,14 @@ type::is_array_like ()
   return defn->is_array_like (this);
 }
 
+/* See gdbtypes.h.  */
+
+bool
+type::is_void ()
+{
+  return check_typedef (this)->code () == TYPE_CODE_VOID;
+}
+
 

 
 static const registry<gdbarch>::key<struct builtin_type> gdbtypes_data;
diff --git a/gdb/gdbtypes.h b/gdb/gdbtypes.h
index 3a79ff2ba76..d51d5eab191 100644
--- a/gdb/gdbtypes.h
+++ b/gdb/gdbtypes.h
@@ -1510,6 +1510,9 @@ struct type
      representations of arrays by the type's language.  */
   bool is_array_like ();
 
+  /* Return true if this type is "void".  Follows typedefs. */
+  bool is_void ();
+
   /* Return the language that this type came from.  */
   enum language language () const
   { return main_type->m_lang; }
diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c
index 4338b2ecfb1..79359fe2c2c 100644
--- a/gdb/python/py-type.c
+++ b/gdb/python/py-type.c
@@ -788,6 +788,192 @@ typy_unqualified (PyObject *self, PyObject *args)
   return type_to_type_object (type);
 }
 
+/* Helper function to compute "owning type" for new type composed
+   of FIRST and REST types according to type ownership rules:
+
+       An objfile-owned type may only refer to other types from
+       that objfile or to arch-owned types.
+       Arch-owned types may only refer to other arch-owned types.
+
+  This functions ensure the above holds and returns:
+
+  - If all types (both FIRST and all of REST) are arch-owned then
+    it returns FIRST.
+  - If one or more types are objfile-owned by the very same objfile then
+    it returns first objfile-owned type encountered.
+  - If two or more types are objfile-owned by _different_ objfiles, then
+    it return NULL and sets Python exception (using PyErr_Format).
+
+  Caller should check returned value for NULL (meaning the ownership rules
+  were violated) and if non-NILL, use returned value to instantiate a
+  type_allocator for the new type.
+*/
+static type *
+compute_owning_type (struct type *first, std::vector<struct type *> &rest)
+{
+  struct type *owning_type = first;
+  struct objfile *owning_objfile;
+
+  if (first->is_objfile_owned ())
+    owning_objfile = first->objfile_owner ();
+  else
+    owning_objfile = nullptr;
+
+  for (struct type *type : rest)
+    {
+      if (type == nullptr)
+	continue;
+
+      if (!type->is_objfile_owned ())
+	continue;
+
+      if (owning_objfile == nullptr)
+	{
+	  owning_type = type;
+	  owning_objfile = type->objfile_owner ();
+	  continue;
+	}
+
+      if (type->objfile_owner () != owning_objfile)
+	{
+	  PyErr_Format(PyExc_ValueError,
+		       _("Type ownership rules violated. Type %s is owned by "
+			 "objfile (%s) but type %s is owned by different "
+			 "objfile (%s)."),
+			owning_type->name (),
+			objfile_name (owning_objfile),
+			type->name (),
+			objfile_name (type->objfile_owner ()));
+	  return nullptr;
+	}
+    }
+  return owning_type;
+}
+
+
+/* Return a function type. */
+static PyObject *
+typy_function (PyObject *self, PyObject *args, PyObject *kw)
+{
+  struct type *type = ((type_object *) self)->type;
+
+  /* Process arguments.  We cannot simply use PyArg_ParseTupleAndKeywords
+     because this method take variable number of arguments.  */
+  if (args == nullptr || !PyTuple_Check (args))
+    {
+      PyErr_Format (PyExc_ValueError,
+		    _("Arguments is %s, not a tuple."),
+		    Py_TYPE (args)->tp_name);
+      return nullptr;
+    }
+  int nparam_types = PyTuple_Size (args);
+  if (nparam_types == -1)
+    {
+      /* Can this really happen?  At this point we _know_ that
+	 ARGS is a tuple (or subclass).  */
+      PyErr_Format (PyExc_ValueError,
+		    _("Failed retrieve number of parameters."));
+      return nullptr;
+    }
+  std::vector<struct type *> param_types (nparam_types);
+  for (int i = 0; i < nparam_types; i++)
+    {
+      PyObject *param_type_obj = PySequence_GetItem (args, i);
+
+      if (param_type_obj == nullptr)
+	{
+	  PyErr_Format (PyExc_ValueError,
+			_("Failed to retrieve parameter at index %d."), i);
+	  return nullptr;
+	}
+      else
+	{
+	  param_types[i] = type_object_to_type (param_type_obj);
+	  if (param_types[i] == nullptr)
+	    {
+	      PyErr_Format (PyExc_TypeError,
+			    _("Argument at index %d is %s, not a gdb.Type "
+			      "object."),
+			    i, Py_TYPE (param_type_obj)->tp_name);
+	      return nullptr;
+	    }
+	  if (param_types[i]->is_void ())
+	    {
+		PyErr_Format (PyExc_ValueError,
+			      _("Argument at index %d is a void type but "
+				"void as parameter type is not allowed."),
+				  i);
+		return nullptr;
+	    }
+	}
+    }
+
+  if (kw != nullptr)
+    {
+      if (!PyDict_Check (kw))
+	{
+	  PyErr_Format (PyExc_ValueError,
+			_("Arguments is %s, not a dict."),
+			Py_TYPE (args)->tp_name);
+	  return nullptr;
+	}
+
+      PyObject *key, *value;
+      Py_ssize_t pos = 0;
+      while (PyDict_Next (kw, &pos, &key, &value))
+	{
+	  if (!PyUnicode_Check (key))
+	    {
+	      PyErr_Format (PyExc_ValueError,
+			    _("Keyword argument key is %s, not a string."),
+			    Py_TYPE (key)->tp_name);
+	      return nullptr;
+	    }
+	  if (PyUnicode_CompareWithASCIIString (key, "varargs") != 0)
+	    {
+	      PyErr_Format (PyExc_ValueError,
+			    _("Invalid keyword argument \"%U\"."),
+			    key);
+	      return nullptr;
+	    }
+	  if (!PyBool_Check (value))
+	    {
+	      PyErr_Format (PyExc_ValueError,
+			    _("Value of \"varargs\" argument is \"%s\", "
+			      "not a bool."),
+			    Py_TYPE (value)->tp_name);
+	      return nullptr;
+	    }
+	  if (value == Py_True)
+	    {
+	      param_types.push_back (nullptr);
+	    }
+	}
+    }
+
+  struct type *owning_type = compute_owning_type (type, param_types);
+  if (owning_type == nullptr)
+    {
+      /* Note that Python exception has already been set by
+	 compute_owning_type.  */
+      return nullptr;
+    }
+
+  try
+    {
+      type_allocator alloc (owning_type);
+      type = create_function_type
+	       (alloc, type, param_types.size (), param_types.data ());
+      type->set_is_prototyped (true);
+    }
+  catch (const gdb_exception &except)
+    {
+      return gdbpy_handle_gdb_exception (nullptr, except);
+    }
+
+  return type_to_type_object (type);
+}
+
 /* Return the size of the type represented by SELF, in bytes.  */
 static PyObject *
 typy_get_sizeof (PyObject *self, void *closure)
@@ -1656,6 +1842,9 @@ Return the type of a template argument." },
   { "unqualified", typy_unqualified, METH_NOARGS,
     "unqualified () -> Type\n\
 Return a variant of this type without const or volatile attributes." },
+  { "function", (PyCFunction) typy_function, METH_VARARGS | METH_KEYWORDS,
+    "function () -> Type\n\
+Return a function type returning value of this type." },
   { "values", typy_values, METH_NOARGS,
     "values () -> list\n\
 Return a list holding all the fields of this type.\n\
diff --git a/gdb/testsuite/gdb.python/py-arch.exp b/gdb/testsuite/gdb.python/py-arch.exp
index f7c1c73e4f8..0c359d4ea6a 100644
--- a/gdb/testsuite/gdb.python/py-arch.exp
+++ b/gdb/testsuite/gdb.python/py-arch.exp
@@ -110,6 +110,10 @@ gdb_test "python print(arch.void_type())" \
     "void" \
     "get void type"
 
+gdb_test "python print(arch.void_type().owner == arch)" \
+    "True" \
+    "check void type owner"
+
 # Test for gdb.architecture_names().  First we're going to grab the
 # complete list of architecture names using the 'complete' command.
 set arch_names []
diff --git a/gdb/testsuite/gdb.python/py-type.exp b/gdb/testsuite/gdb.python/py-type.exp
index 3473ca22646..d5329b9f9d0 100644
--- a/gdb/testsuite/gdb.python/py-type.exp
+++ b/gdb/testsuite/gdb.python/py-type.exp
@@ -368,6 +368,51 @@ if { [build_inferior "${binfile}" "c"] == 0 } {
   gdb_test "python print(gdb.parse_and_eval ('uu').type.owner)" \
       "<gdb.Objfile .*>"
 
+  gdb_test_no_output "python int_t = gdb.lookup_type('int')"
+  gdb_test_no_output "python uu_t = gdb.parse_and_eval ('uu').type"
+
+  gdb_test "python print(repr(int_t.function()))" \
+      "<gdb.Type code=TYPE_CODE_FUNC name=int \\(void\\)>"
+
+  gdb_test "python print(int_t.function().owner == int_t.owner)" \
+      "True"
+
+  gdb_test "python print(repr(int_t.function(int_t, int_t, int_t)))" \
+      "<gdb.Type code=TYPE_CODE_FUNC name=int \\(int, int, int\\)>"
+
+  gdb_test "python print(int_t.function(int_t, int_t, int_t).owner == int_t.owner)" \
+      "True"
+
+  gdb_test "python print(int_t.function(uu_t).owner)" \
+      "<gdb.Objfile .*>"
+
+  gdb_test "python print(repr(int_t.function(int_t, varargs=True)))" \
+      "<gdb.Type code=TYPE_CODE_FUNC name=int \\(int, ...\\)>"
+
+  gdb_test "python print(repr(int_t.function(varargs=True)))" \
+      "<gdb.Type code=TYPE_CODE_FUNC name=int \\(void\\)>"
+
+  gdb_test "python print(repr(int_t.function(varargs=False)))" \
+      "<gdb.Type code=TYPE_CODE_FUNC name=int \\(void\\)>"
+
+  gdb_test "python print(repr(int_t.function(123)))" \
+      "TypeError.*:.*"
+
+  gdb_test "python print(repr(int_t.function(gdb.lookup_type('void'))))" \
+      "ValueError.*:.*"
+
+  gdb_test "python print(repr(int_t.function(int_t, gdb.lookup_type('void'))))" \
+      "ValueError.*:.*"
+
+  gdb_test "python print(repr(int_t.function(int_t, varargs=\[1,2,3\])))" \
+      "ValueError.*:.*"
+
+  gdb_test "python print(repr(int_t.function(int_t, kw=False)))" \
+      "ValueError.*:.*"
+
+  gdb_test "python print(repr(int_t.function(gdb.lookup_type('void'), varargs=True)))" \
+      "ValueError.*:.*"
+
   set sint [get_sizeof int 0]
   gdb_test "python print(gdb.parse_and_eval('aligncheck').type.alignof)" \
       $sint
-- 
2.47.2



More information about the Gdb-patches mailing list