From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id V9rYJ9ZFqGpRswwAWB0awg (envelope-from ) for ; Mon, 14 Sep 2026 15:07:02 -0400 Authentication-Results: simark.ca; dkim=fail reason="signature verification failed" (768-bit key; unprotected) header.d=tromey.com header.i=@tromey.com header.a=rsa-sha256 header.s=default header.b=RCxSYgRR; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 8D5761E066; Mon, 14 Sep 2026 15:07:02 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-0.8 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_INVALID,DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_BL_SPAMCOP_NET, RCVD_IN_DNSWL_MED,RCVD_IN_VALIDITY_CERTIFIED_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=no autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 687381E033 for ; Mon, 14 Sep 2026 15:07:01 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id E1C304B9DB6A for ; Mon, 14 Sep 2026 19:07:00 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E1C304B9DB6A Authentication-Results: sourceware.org; dkim=fail reason="signature verification failed" (768-bit key, unprotected) header.d=tromey.com header.i=@tromey.com header.a=rsa-sha256 header.s=default header.b=RCxSYgRR Received: from omta38.uswest2.a.cloudfilter.net (omta38.uswest2.a.cloudfilter.net [35.89.44.37]) by sourceware.org (Postfix) with ESMTPS id 165DF4BA2E1D for ; Mon, 14 Sep 2026 19:06:32 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 165DF4BA2E1D Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=tromey.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=tromey.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 165DF4BA2E1D Authentication-Results: sourceware.org; arc=none smtp.remote-ip=35.89.44.37 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1789412793; cv=none; b=i0AjUBWJlPgCfJg4OoYj2cG045cllFCoK/sIrCEtFz1AwAskOmVMbYCVU7BOriwpchUd1IO/G/5z+QGEV6QXw4CriDAlbw8vuS0lRHz+tH5PRCML19JlmXB/XRaQG97xC9XVcPqVlK4VxyyRVd3B3WhVggWSUONbx0EtUAapyUI= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1789412793; c=relaxed/simple; bh=1oXDnoUTmFcAxP68nT5YQDLqMwW3qSqrIfFn5SBBQQU=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=OdUstynHOrR5KU//yLxP5eq/FI0lfrCnASg6XbYMkD9ogo8FUfzw7/HcTJeRU2AKx10KNx3xZOidCU4xO/ElwBmrFRJnpHod1ZFnBzaRhM676+bU8jm1Xgh94Vj+T6gulpohIVJ/kTU3CT9MS5Wa3LDVjNU1jXZoNOsOe0SEDA8= ARC-Authentication-Results: i=1; sourceware.org; dkim=policy (768-bit key, unprotected) header.d=tromey.com header.i=@tromey.com header.a=rsa-sha256 header.s=default header.b=RCxSYgRR reason="signing key too small" DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 165DF4BA2E1D Received: from eig-obgw-6007b.ext.cloudfilter.net ([10.0.30.166]) by cmsmtp with ESMTPS id 6BbQx20pdv0nd6C0exe2B9; Mon, 14 Sep 2026 19:06:32 +0000 Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with ESMTPS id 6C0dxkX82F2DA6C0dxe3fp; Mon, 14 Sep 2026 19:06:31 +0000 X-Authority-Analysis: v=2.4 cv=FaM3xI+6 c=1 sm=1 tr=0 ts=6aa845b7 a=ApxJNpeYhEAb1aAlGBBbmA==:117 a=ApxJNpeYhEAb1aAlGBBbmA==:17 a=VdqzKS8jKosA:10 a=ItBw4LHWJt0A:10 a=W8GVta3JdMXG01yHWxAA:9 a=DCx65vhANUyCzuf5D8fC:22 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:Sender:Reply-To:Content-Type:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Unsubscribe-Post:List-Subscribe:List-Post:List-Owner:List-Archive; bh=gY9bilGVgdQp7ZiIZvqszPz67h45FDU2i6dY7V3lYys=; b=RCxSYgRRAlDo8a8f2y3lkVKsIE tmU9dxPND74dmkV+FIUku8BTJYoKWjiuanb2Dc4t7vPJ4C/1W+FROzKsqBs7Qwph3v57JD/mGcLPF FBIQxiRlFdxX+Em1ll8oO6yXS; Received: from 75-166-229-74.hlrn.qwest.net ([75.166.229.74]:57420 helo=localhost.localdomain) by box5379.bluehost.com with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.100) (envelope-from ) id 1x6C0d-000000007w0-0Kwf; Mon, 14 Sep 2026 13:06:31 -0600 From: Tom Tromey To: gdb-patches@sourceware.org Cc: Tom Tromey Subject: [PATCH] Wrap (and poison) PyObject_CallFunctionObjArgs Date: Mon, 14 Sep 2026 13:06:24 -0600 Message-ID: <20260914190624.4178522-1-tom@tromey.com> X-Mailer: git-send-email 2.49.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - box5379.bluehost.com X-AntiAbuse: Original Domain - sourceware.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - tromey.com X-BWhitelist: no X-Source-IP: 75.166.229.74 X-Source-L: No X-Exim-ID: 1x6C0d-000000007w0-0Kwf X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: 75-166-229-74.hlrn.qwest.net (localhost.localdomain) [75.166.229.74]:57420 X-Source-Auth: tom+tromey.com X-Email-Count: 1 X-Org: HG=bhshared;ORG=bluehost; X-Source-Cap: ZWx5bnJvYmk7ZWx5bnJvYmk7Ym94NTM3OS5ibHVlaG9zdC5jb20= X-Local-Domain: yes X-CMAE-Envelope: MS4xfE4nXHymNLUD0yA9btkkbmpWNf7p4e3uO9HVsjnnHP5LIBQm3778391ANk7FlHQ0H8VWGtQgZ0YK5+/3tZDGuhUUi/JpfXIZn/Oi+vTBgHDo9loFgThT tPjYNaJShTX/cb0Pfoixv00CvkgiRDypCnzm/csHuCEnnX4kBXxTxJ3gXHtQ6mBw0y72Xpkbe99vPAWq22ta0hZX+DIJE8Be/5Q= X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org 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 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 +PyObject * +unwrap_ref (const gdbpy_ref &val) +{ + gdb_assert (val != nullptr); + return val.get (); +} + +template +PyObject * +unwrap_ref (gdbpy_borrowed_ref 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 +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