Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Tom Tromey <tom@tromey.com>
To: gdb-patches@sourceware.org
Cc: Tom Tromey <tom@tromey.com>
Subject: [PATCH] Wrap (and poison) PyObject_CallFunctionObjArgs
Date: Mon, 14 Sep 2026 13:06:24 -0600	[thread overview]
Message-ID: <20260914190624.4178522-1-tom@tromey.com> (raw)

This patch adds a new wrapper for PyObject_CallFunctionObjArgs.  The
wrapper function knows how to unwrap gdbpy_ref<> and
gdbpy_borrowed_ref<>.  It rejects null arguments, and it automatically
supplies the trailing NULL required by PyObject_CallFunctionObjArgs.
Finally, PyObject_CallFunctionObjArgs is poisoned to avoid introducing
new calls.

The idea behind this change is that passing anything other than
PyObject* to this function will cause failures; this patch turns
silent failures (like passing a gdbpy_borrowed_ref<>) into a
compile-time failure.
---
 gdb/python/py-disasm.c        |  5 +--
 gdb/python/py-event.c         | 11 +++----
 gdb/python/py-framefilter.c   |  9 +++--
 gdb/python/py-prettyprint.c   |  3 +-
 gdb/python/py-record-btrace.c |  8 ++---
 gdb/python/py-tui.c           |  6 ++--
 gdb/python/py-unwind.c        |  2 +-
 gdb/python/python-internal.h  | 62 +++++++++++++++++++++++++++++++++++
 gdb/python/python.c           | 42 ++++++++++--------------
 gdb/varobj.c                  |  8 +++--
 10 files changed, 101 insertions(+), 55 deletions(-)

diff --git a/gdb/python/py-disasm.c b/gdb/python/py-disasm.c
index 48a2a4f6f8b..d99c899cf38 100644
--- a/gdb/python/py-disasm.c
+++ b/gdb/python/py-disasm.c
@@ -1233,10 +1233,7 @@ gdbpy_print_insn (struct gdbarch *gdbarch, CORE_ADDR memaddr,
 
   /* Call into the registered disassembler to (possibly) perform the
      disassembly.  */
-  gdbpy_ref<> result
-    (PyObject_CallFunctionObjArgs (hook.get (),
-				   disasm_info.get (),
-				   nullptr));
+  gdbpy_ref<> result = gdbpy_object_call_function_obj_args (hook, disasm_info);
 
   if (result == nullptr)
     {
diff --git a/gdb/python/py-event.c b/gdb/python/py-event.c
index e75ca91c784..27230bdfdb9 100644
--- a/gdb/python/py-event.c
+++ b/gdb/python/py-event.c
@@ -83,12 +83,11 @@ evpy_emit_event (gdbpy_opt_borrowed_ref<> event,
       if (func == NULL)
 	return -1;
 
-      /* This local exists so we don't pass an object through
-	 '...'.  */
-      PyObject *ev = event;
-      gdbpy_ref<> func_result (PyObject_CallFunctionObjArgs (func, ev,
-							     nullptr));
-
+      gdbpy_ref<> func_result;
+      if (event == nullptr)
+	func_result = gdbpy_object_call_function_obj_args (func);
+      else
+	func_result = gdbpy_object_call_function_obj_args (func, event);
       if (func_result == NULL)
 	{
 	  /* Print the trace here, but keep going -- we want to try to
diff --git a/gdb/python/py-framefilter.c b/gdb/python/py-framefilter.c
index 2777b944097..b4734d73d45 100644
--- a/gdb/python/py-framefilter.c
+++ b/gdb/python/py-framefilter.c
@@ -1103,11 +1103,10 @@ bootstrap_python_frame_filters (const frame_info_ptr &frame,
   if (py_frame_high == NULL)
     return NULL;
 
-  gdbpy_ref<> iterable (PyObject_CallFunctionObjArgs (sort_func.get (),
-						      frame_obj.get (),
-						      py_frame_low.get (),
-						      py_frame_high.get (),
-						      NULL));
+  gdbpy_ref<> iterable = gdbpy_object_call_function_obj_args (sort_func,
+							      frame_obj,
+							      py_frame_low,
+							      py_frame_high);
   if (iterable == NULL)
     return NULL;
 
diff --git a/gdb/python/py-prettyprint.c b/gdb/python/py-prettyprint.c
index ce6ed699f3e..a7cbb6e045c 100644
--- a/gdb/python/py-prettyprint.c
+++ b/gdb/python/py-prettyprint.c
@@ -78,8 +78,7 @@ search_pp_list (PyObject *list, PyObject *value)
 	    continue;
 	}
 
-      gdbpy_ref<> printer (PyObject_CallFunctionObjArgs (function, value,
-							 NULL));
+      gdbpy_ref<> printer = gdbpy_object_call_function_obj_args (function, value);
       if (printer == NULL)
 	return NULL;
       else if (printer != Py_None)
diff --git a/gdb/python/py-record-btrace.c b/gdb/python/py-record-btrace.c
index a73167a5bdd..9249a340e06 100644
--- a/gdb/python/py-record-btrace.c
+++ b/gdb/python/py-record-btrace.c
@@ -826,11 +826,9 @@ recpy_call_filter (const uint64_t payload, std::optional<uint64_t> ip,
   else
     py_ip = gdb_py_object_from_ulongest (*ip);
 
-  gdbpy_ref<> py_result (PyObject_CallFunctionObjArgs ((PyObject *) ptw_filter,
-							py_payload.get (),
-							py_ip.get (),
-							nullptr));
-
+  gdbpy_ref<> py_result
+    = gdbpy_object_call_function_obj_args ((PyObject *) ptw_filter,
+					   py_payload, py_ip);
   if (py_result == nullptr)
     {
       gdbpy_print_stack ();
diff --git a/gdb/python/py-tui.c b/gdb/python/py-tui.c
index 465fbd2fe6d..9ecfff0ecf6 100644
--- a/gdb/python/py-tui.c
+++ b/gdb/python/py-tui.c
@@ -418,10 +418,8 @@ gdbpy_tui_window_maker::operator() (const char *win_name)
      which, this method should not be called.  */
   gdb_assert (m_constr != nullptr);
 
-  gdbpy_ref<> user_window
-    (PyObject_CallFunctionObjArgs (m_constr.get (),
-				   wrapper.get (),
-				   nullptr));
+  gdbpy_ref<> user_window = gdbpy_object_call_function_obj_args (m_constr,
+								 wrapper);
   if (user_window == nullptr)
     {
       gdbpy_print_stack ();
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index 345203f8633..65bbd406510 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -898,7 +898,7 @@ frame_unwind_python::sniff (const frame_info_ptr &this_frame,
 
   /* A (gdb.UnwindInfo, str) tuple, or None.  */
   gdbpy_ref<> pyo_execute_ret
-    (PyObject_CallFunctionObjArgs (pyo_execute.get (), pfo.get (), nullptr));
+    = gdbpy_object_call_function_obj_args (pyo_execute, pfo);
   if (pyo_execute_ret == nullptr)
     {
       /* If the unwinder is cancelled due to a Ctrl-C, then propagate
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 2e8f35729cd..b08f2343f15 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -212,6 +212,68 @@ gdbpy_call_method (const gdbpy_ref<> &o, const char *method, Args... args)
 # define PyObject_CallMethod POISONED_PyObject_CallMethod
 #endif
 
+namespace detail
+{
+
+/* These are helpers for gdbpy_object_call_function_obj_args.  Each
+   function takes a single argument and returns a non-NULL
+   PyObject*.  */
+
+static inline PyObject *
+unwrap_ref (PyObject *val)
+{
+  gdb_assert (val != nullptr);
+  return val;
+}
+
+template<typename T>
+PyObject *
+unwrap_ref (const gdbpy_ref<T> &val)
+{
+  gdb_assert (val != nullptr);
+  return val.get ();
+}
+
+template<typename T>
+PyObject *
+unwrap_ref (gdbpy_borrowed_ref<T> val)
+{
+  /* Note that VAL cannot be nullptr here by construction.  */
+  return (PyObject *) val;
+}
+
+}
+
+/* A wrapper for PyObject_CallFunctionObjArgs that takes various kinds
+   of gdb wrappers, in addition to "PyObject *".  This variant does
+   not allow NULL arguments.  While PyObject_CallFunctionObjArgs
+   requires a trailing NULL, this function does not -- it supplies the
+   required trailing NULL on its own.
+
+   As a safety measure, no argument may be NULL.  While this may be
+   slightly inconvenient at times (you can't early-terminate the
+   arguments, you have to add a special case at the call site), it
+   avoids bugs where early termination was unintentional.  */
+template<typename Arg, typename... Args>
+static inline gdbpy_ref<>
+gdbpy_object_call_function_obj_args (Arg &&fn, Args && ...args)
+{
+  PyObject *result
+    = PyObject_CallFunctionObjArgs (detail::unwrap_ref (fn),
+				    detail::unwrap_ref (args)...,
+				    nullptr);
+  return gdbpy_ref<> (result);
+}
+
+/* Poison PyObject_CallFunctionObjArgs.  The typesafe wrapper
+   gdbpy_objects_call_function_obj_args should be used instead.  */
+#undef PyObject_CallFunctionObjArgs
+#ifdef __GNUC__
+# pragma GCC poison PyObject_CallFunctionObjArgs
+#else
+# define PyObject_CallFunctionObjArgs POISONED_PyObject_CallFunctionObjArgs
+#endif
+
 /* The 'name' parameter of PyErr_NewException was missing the 'const'
    qualifier in Python <= 3.4.  Hence, we wrap it in a function to
    avoid errors when compiled with -Werror.  */
diff --git a/gdb/python/python.c b/gdb/python/python.c
index df9f7c69006..1996915501a 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1262,8 +1262,7 @@ gdbpy_before_prompt_hook (const struct extension_language_defn *extlang,
 	    }
 
 	  gdbpy_ref<> result
-	    (PyObject_CallFunctionObjArgs (hook.get (), current_prompt.get (),
-					   NULL));
+	    = gdbpy_object_call_function_obj_args (hook, current_prompt);
 	  if (result == NULL)
 	    {
 	      gdbpy_print_stack ();
@@ -1364,11 +1363,10 @@ gdbpy_colorize (const std::string &filename, const std::string &contents,
      contents (a bytes object).  This function should return either a bytes
      object, the same contents with styling applied, or None to indicate
      that no styling should be performed.  */
-  gdbpy_ref<> result (PyObject_CallFunctionObjArgs (hook.get (),
-						    fname_arg.get (),
-						    contents_arg.get (),
-						    lang_arg.get (),
-						    nullptr));
+  gdbpy_ref<> result = gdbpy_object_call_function_obj_args (hook,
+							    fname_arg,
+							    contents_arg,
+							    lang_arg);
   if (result == nullptr)
     {
       gdbpy_print_stack ();
@@ -1433,10 +1431,9 @@ gdbpy_colorize_disasm (const std::string &content, gdbarch *gdbarch)
       return {};
     }
 
-  gdbpy_ref<> result (PyObject_CallFunctionObjArgs (hook.get (),
-						    content_arg.get (),
-						    gdbarch_arg.get (),
-						    nullptr));
+  gdbpy_ref<> result = gdbpy_object_call_function_obj_args (hook,
+							    content_arg,
+							    gdbarch_arg);
   if (result == nullptr)
     {
       gdbpy_print_stack ();
@@ -1903,8 +1900,7 @@ gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang,
 
   /* Call the function, passing in the Python objfile object.  */
   gdbpy_ref<> pyo_execute_ret
-    (PyObject_CallFunctionObjArgs (pyo_handler.get (), pyo_objfile.get (),
-				   nullptr));
+    = gdbpy_object_call_function_obj_args (pyo_handler, pyo_objfile);
   if (pyo_execute_ret == nullptr)
     {
       /* If the handler is cancelled due to a Ctrl-C, then propagate
@@ -2003,9 +1999,8 @@ gdbpy_find_objfile_from_buildid (const struct extension_language_defn *extlang,
 
   /* Call the function, passing in the Python objfile object.  */
   gdbpy_ref<> pyo_execute_ret
-    (PyObject_CallFunctionObjArgs (pyo_handler.get (), pyo_pspace.get (),
-				   pyo_buildid.get (), pyo_filename.get (),
-				   nullptr));
+    = gdbpy_object_call_function_obj_args (pyo_handler, pyo_pspace,
+					   pyo_buildid, pyo_filename);
   if (pyo_execute_ret == nullptr)
     {
       /* If the handler is cancelled due to a Ctrl-C, then propagate
@@ -2055,8 +2050,6 @@ static void
 gdbpy_start_type_printers (const struct extension_language_defn *extlang,
 			   struct ext_lang_type_printers *ext_printers)
 {
-  PyObject *printers_obj = NULL;
-
   if (!gdb_python_initialized)
     return;
 
@@ -2077,11 +2070,11 @@ gdbpy_start_type_printers (const struct extension_language_defn *extlang,
       return;
     }
 
-  printers_obj = PyObject_CallFunctionObjArgs (func.get (), (char *) NULL);
-  if (printers_obj == NULL)
+  gdbpy_ref<> printers_obj = gdbpy_object_call_function_obj_args (func);
+  if (printers_obj == nullptr)
     gdbpy_print_stack ();
   else
-    ext_printers->py_type_printers = printers_obj;
+    ext_printers->py_type_printers = printers_obj.release ();
 }
 
 /* If TYPE is recognized by some type printer, store in *PRETTIED_TYPE
@@ -2130,10 +2123,9 @@ gdbpy_apply_type_printers (const struct extension_language_defn *extlang,
       return EXT_LANG_RC_ERROR;
     }
 
-  gdbpy_ref<> result_obj (PyObject_CallFunctionObjArgs (func.get (),
-							printers_obj,
-							type_obj.get (),
-							(char *) NULL));
+  gdbpy_ref<> result_obj = gdbpy_object_call_function_obj_args (func,
+								printers_obj,
+								type_obj);
   if (result_obj == NULL)
     {
       gdbpy_print_stack ();
diff --git a/gdb/varobj.c b/gdb/varobj.c
index 812de2b27ba..85ae1a21da6 100644
--- a/gdb/varobj.c
+++ b/gdb/varobj.c
@@ -467,11 +467,13 @@ varobj_delete (struct varobj *var, bool only_children)
 static PyObject *
 instantiate_pretty_printer (PyObject *constructor, struct value *value)
 {
-  gdbpy_ref<> val_obj (value_to_value_object (value));
+  gdbpy_ref<> val_obj = value_to_value_object (value);
   if (val_obj == nullptr)
-    return NULL;
+    return nullptr;
 
-  return PyObject_CallFunctionObjArgs (constructor, val_obj.get (), NULL);
+  gdbpy_ref<> result = gdbpy_object_call_function_obj_args (constructor,
+							    val_obj);
+  return result.release ();
 }
 
 #endif
-- 
2.49.0


             reply	other threads:[~2026-09-14 19:07 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-14 19:06 Tom Tromey [this message]
2026-09-14 20:09 ` Simon Marchi
2026-09-14 22:15   ` Tom Tromey

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260914190624.4178522-1-tom@tromey.com \
    --to=tom@tromey.com \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox