From patchwork Fri May 15 19:59:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Tromey X-Patchwork-Id: 135094 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 0A849409FCBD for ; Fri, 15 May 2026 20:00:31 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 0A849409FCBD 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=CIZEnIFM X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from omta038.useast.a.cloudfilter.net (omta038.useast.a.cloudfilter.net [44.202.169.37]) by sourceware.org (Postfix) with ESMTPS id 0FB67409FCBF for ; Fri, 15 May 2026 19:59:29 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 0FB67409FCBF 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 0FB67409FCBF Authentication-Results: sourceware.org; arc=none smtp.remote-ip=44.202.169.37 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778875169; cv=none; b=VuxsS1nuCoNkU0XosdJcAkbijU21PZyyr0x9b1pxu0WYqvDqfzghhqp//TQGZFERElo9maVmxH71IWRvRV7lL1WySj713Fpy3XRUGxX317rSC/XRdUuP9cSgyFCk61gqVH0aaChYe5dH3YbTMqu5cgUrUfy9pyFkDEpTuCtsz08= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778875169; c=relaxed/simple; bh=kLqC7xcz5ZEyEZJO5u/ABkEkMi98qZTkW6p15KkzcEU=; h=DKIM-Signature:From:Date:Subject:MIME-Version:Message-Id:To; b=KoBGZA2YlP6N/TZ6tGygDanH76KzAfy4346qgLeS5CqxlrDiQkQ6eGc2ykZ4nrX+D28TRwTPpPD6XWucR6Ys2SxuN1nhlb5s8Z4VT7LRUzF3ofrm4sup82C9DKv7MnFuDONT5cXGZSxPFx0WOLxBDMkFndXmsrJO9alXRWEdp04= 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=CIZEnIFM reason="signing key too small" DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 0FB67409FCBF Received: from eig-obgw-5003b.ext.cloudfilter.net ([10.0.29.155]) by cmsmtp with ESMTPS id Ny1kwZAfNuVXCNygywEmBK; Fri, 15 May 2026 19:59:28 +0000 Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with ESMTPS id NyguwUVloCKa1Nyguw9R63; Fri, 15 May 2026 19:59:25 +0000 X-Authority-Analysis: v=2.4 cv=IaKHWXqa c=1 sm=1 tr=0 ts=6a077b20 a=ApxJNpeYhEAb1aAlGBBbmA==:117 a=ApxJNpeYhEAb1aAlGBBbmA==:17 a=IkcTkHD0fZMA:10 a=NGcC8JguVDcA:10 a=ItBw4LHWJt0A:10 a=9TWcnZURJYLH-kCpFvEA:9 a=QEXdDO2ut3YA:10 a=DCx65vhANUyCzuf5D8fC:22 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=5Bp5vejn7GFWaIM/0WOs2gqWk3I9Jd53PS7LWo+lQKg=; b=CIZEnIFMUgDymNqk//sBbjp6/k Asb1+fJnqQAd/Pw9XXx2wSePYV5WIgjIW9+KRojiOG0PuZlQ0pvPJeR64pYVlmLTBCNuLoTH8icSs wAecTiBBruIwcbPiKnyUqRr8t; Received: from 75-166-225-82.hlrn.qwest.net ([75.166.225.82]:60112 helo=[192.168.0.17]) by box5379.bluehost.com with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.99.2) (envelope-from ) id 1wNygu-0000000418T-0coT; Fri, 15 May 2026 13:59:24 -0600 From: Tom Tromey Date: Fri, 15 May 2026 13:59:21 -0600 Subject: [PATCH v2 1/4] Add gdbpy_borrowed_ref MIME-Version: 1.0 Message-Id: <20260515-python-safety-initial-v2-1-6129cadf258a@tromey.com> References: <20260515-python-safety-initial-v2-0-6129cadf258a@tromey.com> In-Reply-To: <20260515-python-safety-initial-v2-0-6129cadf258a@tromey.com> To: gdb-patches@sourceware.org Cc: Tom Tromey X-Mailer: b4 0.14.3 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.225.82 X-Source-L: No X-Exim-ID: 1wNygu-0000000418T-0coT X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: 75-166-225-82.hlrn.qwest.net ([192.168.0.17]) [75.166.225.82]:60112 X-Source-Auth: tom+tromey.com X-Email-Count: 2 X-Org: HG=bhshared;ORG=bluehost; X-Source-Cap: ZWx5bnJvYmk7ZWx5bnJvYmk7Ym94NTM3OS5ibHVlaG9zdC5jb20= X-Local-Domain: yes X-CMAE-Envelope: MS4xfI5n0VFfaFHH7+InPdVP1/JDd3HnQsqwmckPcTCU4Ez1yJnjQUVfpyyFsJwW5TTacMhTypyNOqrtyllGOEVpBw+r3HJ6fDbCDJOFoYIUD7iIMAtRaNYa kEXBbe3rgwPcG6GZjdp+JvAf90Kr4Xb6whtVv4tulXsOSQ3pN9aGF2TLXOOvtQfQX4a8WFSdQfzZYJ8ieF/WF2WQBGcZIkhvP3Q= X-Spam-Status: No, score=-3012.1 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, GIT_PATCH_0, JMQ_SPF_NEUTRAL, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, TXREP, T_SPF_PERMERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org This adds new gdbpy_opt_borrowed_ref and gdbpy_borrowed_ref classes. These class is primarily for code "documentation" purposes -- it makes it clear to the reader that a given reference is borrowed. However, they also add a tiny bit of safety, in that conversion to gdbpy_ref<> will either be rejected (by the "opt" class) or acquire a new reference. --- gdb/python/py-ref.h | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/gdb/python/py-ref.h b/gdb/python/py-ref.h index dc0b14814af..ef6b9fb427c 100644 --- a/gdb/python/py-ref.h +++ b/gdb/python/py-ref.h @@ -41,6 +41,86 @@ struct gdbpy_ref_policy template using gdbpy_ref = gdb::ref_ptr; +/* A class representing an optional borrowed reference. It is + "optional" because NULL is a valid value. + + This is a simple wrapper for a PyObject*. Aside from documenting + what the code does, the main advantage of using this is that + conversion to a gdbpy_ref<> is prevented. + + An optional borrowed reference is only used in situations where + Python says NULL is valid. For example, it is used as the type of + the "keywords" argument to a varargs method. Most code should + prefer an ordinary gdbpy_borrowed_ref, see below. */ +class gdbpy_opt_borrowed_ref +{ +public: + + gdbpy_opt_borrowed_ref (PyObject *obj) + : m_obj (obj) + { + } + + template + gdbpy_opt_borrowed_ref (const gdbpy_ref &ref) + : m_obj (ref.get ()) + { + } + + operator PyObject * () + { + return m_obj; + } + + operator gdbpy_ref<> () = delete; + +protected: + PyObject *m_obj; +}; + +/* A borrowed reference that is guaranteed not to be NULL. + + Like gdbpy_opt_borrowed_ref, this mostly serves a documentary + purpose. However, it also allows a checked cast to any subclass of + PyObject, and conversion to a gdbpy_ref<> will automatically + acquire a new reference -- a safety improvement over plain + PyObject*. */ +class gdbpy_borrowed_ref : public gdbpy_opt_borrowed_ref +{ +public: + + gdbpy_borrowed_ref (PyObject *obj) + : gdbpy_opt_borrowed_ref (obj) + { + gdb_assert (m_obj != nullptr); + } + + template + gdbpy_borrowed_ref (const gdbpy_ref &ref) + : gdbpy_opt_borrowed_ref (ref) + { + gdb_assert (m_obj != nullptr); + } + + gdbpy_borrowed_ref (std::nullptr_t) = delete; + + /* Allow a (checked) conversion to any subclass of PyObject. */ + template> + operator T * () + { + gdb_assert (PyObject_TypeCheck (m_obj, T::corresponding_object_type)); + return static_cast (m_obj); + } + + /* When converting a borrowed reference to a gdbpy_ref<>, a new + reference is acquired. */ + operator gdbpy_ref<> () + { + return gdbpy_ref<>::new_reference (m_obj); + } +}; + /* A wrapper class for Python extension objects that have a __dict__ attribute. Any Python C object extension needing __dict__ should inherit from this From patchwork Fri May 15 19:59:22 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Tromey X-Patchwork-Id: 135095 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id D169B409FCAD for ; Fri, 15 May 2026 20:00:46 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org D169B409FCAD 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=yAMHspB4 X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from omta40.uswest2.a.cloudfilter.net (omta40.uswest2.a.cloudfilter.net [35.89.44.39]) by sourceware.org (Postfix) with ESMTPS id 7AC975E4CF00 for ; Fri, 15 May 2026 19:59:27 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 7AC975E4CF00 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 7AC975E4CF00 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=35.89.44.39 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778875167; cv=none; b=OLkl5jvIDtfpeuC6n/s/fJ2EK5nbofZAWq5F9xvfZv9T7toGj1RxTLgIJa9Aeg9fk9PBnIndoI4DPckdM3RUWEnYViY1V06xE1JO6YrpOsUrFg8na+3KoujXFxxwDtrde3nmrCcpe5vUPw3RtGBc2k//XlolfXzJqrd36GMnFJU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778875167; c=relaxed/simple; bh=BmYwiqvFB8utkaTfu4KHirAtAgaU0Kd2dBHOJDfEANw=; h=DKIM-Signature:From:Date:Subject:MIME-Version:Message-Id:To; b=bAa8UfAUzO1OMhZfRCJRYEAcVVuIu1EWJE9W6ixB5i8mDaQG9caf+a05OCQElWQmHZJl+8uqHjL2KOqgViBVaVMBCucO27j5Ypbf6xDyLDRpaw8pkZN6T/tl3yvSSLZWYcu+Q4OgYId9CEHMJ3xURcTtgWOmhgHegEBMCXx8MJQ= 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=yAMHspB4 reason="signing key too small" DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7AC975E4CF00 Received: from eig-obgw-6005b.ext.cloudfilter.net ([10.0.30.162]) by cmsmtp with ESMTPS id NwEJwBoQKshqQNygwwyeEt; Fri, 15 May 2026 19:59:26 +0000 Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with ESMTPS id NyguwmV8fD6ICNyguwcdwk; Fri, 15 May 2026 19:59:24 +0000 X-Authority-Analysis: v=2.4 cv=DcUXqutW c=1 sm=1 tr=0 ts=6a077b1e a=ApxJNpeYhEAb1aAlGBBbmA==:117 a=ApxJNpeYhEAb1aAlGBBbmA==:17 a=IkcTkHD0fZMA:10 a=NGcC8JguVDcA:10 a=ItBw4LHWJt0A:10 a=mDV3o1hIAAAA:8 a=DW31djgLIwoHD-49Y34A:9 a=QEXdDO2ut3YA:10 a=DCx65vhANUyCzuf5D8fC:22 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=SMSnZqK9wS3uHg11Fl1Ztks6bzMWf9+gnN00l00oehk=; b=yAMHspB493mtYoQ6Tve1xd5out AWq7S2Y2wooo8WVTQSSdMV0j2kheOswJVV4lxrjqoE+Hn0AX34fmsIWsRbafKO6Rl8JNkLE7xVB0W 7WdRQo/t5BCfPXmQIib1PYvu/; Received: from 75-166-225-82.hlrn.qwest.net ([75.166.225.82]:60112 helo=[192.168.0.17]) by box5379.bluehost.com with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.99.2) (envelope-from ) id 1wNygu-0000000418T-1kzX; Fri, 15 May 2026 13:59:24 -0600 From: Tom Tromey Date: Fri, 15 May 2026 13:59:22 -0600 Subject: [PATCH v2 2/4] Add wrappers for some Python APIs MIME-Version: 1.0 Message-Id: <20260515-python-safety-initial-v2-2-6129cadf258a@tromey.com> References: <20260515-python-safety-initial-v2-0-6129cadf258a@tromey.com> In-Reply-To: <20260515-python-safety-initial-v2-0-6129cadf258a@tromey.com> To: gdb-patches@sourceware.org Cc: Tom Tromey X-Mailer: b4 0.14.3 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.225.82 X-Source-L: No X-Exim-ID: 1wNygu-0000000418T-1kzX X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: 75-166-225-82.hlrn.qwest.net ([192.168.0.17]) [75.166.225.82]:60112 X-Source-Auth: tom+tromey.com X-Email-Count: 3 X-Org: HG=bhshared;ORG=bluehost; X-Source-Cap: ZWx5bnJvYmk7ZWx5bnJvYmk7Ym94NTM3OS5ibHVlaG9zdC5jb20= X-Local-Domain: yes X-CMAE-Envelope: MS4xfN2dza618BtiMWNmujOPbV3c45poEnJ5bbQ/so72JGcMSYmfY3FcaKIpYG4Tsxdj9BjddY/s3AXm81iT7l+jjkKP60wgIwB/BNlsMj89Xn1QDXD5sbRw htq1K4IOjAsJbf8QdwMDs/fJtWxBzltZALg56WCxuIX3jLmZspHns6ecRIb1++1Jvw92Hy0OCUsgv/VLQZCsUqG3OjsoW/1POzE= X-Spam-Status: No, score=-3012.1 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, GIT_PATCH_0, JMQ_SPF_NEUTRAL, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, TXREP, T_SPF_PERMERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org This adds some new functions that wrap Python APIs. The wrapping follows some proposed rules for Python safety in gdb: * Functions returning a new reference return gdbpy_ref<> * Errors are reported via exceptions, not special values * Functions accepting a stolen reference take a gdbpy_ref<>&& --- gdb/python/py-wrappers.h | 334 +++++++++++++++++++++++++++++++++++++++++++ gdb/python/python-internal.h | 2 + 2 files changed, 336 insertions(+) diff --git a/gdb/python/py-wrappers.h b/gdb/python/py-wrappers.h new file mode 100644 index 00000000000..78c1f8070cf --- /dev/null +++ b/gdb/python/py-wrappers.h @@ -0,0 +1,334 @@ +/* Wrappers for some Python safety. + + 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 . */ + +#ifndef GDB_PYTHON_PY_WRAPPERS_H +#define GDB_PYTHON_PY_WRAPPERS_H + +#include "py-ref.h" + +/* Gdb implements its own wrappers for many Python APIs. This is done + in an attempt to be more safe. + + In particular, in gdb: + + - APIs returning a new reference will return gdbpy_ref<>. This + makes reference counting errors less likely. + + - APIs will throw an exception rather than return a special value + (NULL or -1). This makes error checking simpler. + + - APIs requiring a stolen reference take a gdbpy_ref<>&&, to make + reference counting errors less likely. + + This file holds the currently-defined wrappers. If new APIs are + needed, the normal approach is to add a wrapper here. + + APIs here are named after the underlying Python function, but using + lower case and an "_" at each word break. */ + +/* The type of exception thrown when the Python exception has been + set. */ +struct gdb_python_exception +{ + gdb_python_exception () + { + gdb_assert (PyErr_Occurred ()); + } +}; + +template +gdbpy_ref +gdbpy_new () +{ + gdbpy_ref result (PyObject_New (T, T::corresponding_object_type)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_ref<> +gdbpy_bool_from_long (long value) +{ + /* This cannot fail. */ + return gdbpy_ref<> (PyBool_FromLong (value)); +} + +static inline char * +gdbpy_bytes_as_string (gdbpy_borrowed_ref ref) +{ + char *result = PyBytes_AsString (ref); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline void +gdbpy_bytes_as_string_and_size (gdbpy_borrowed_ref ref, + char **buffer, + Py_ssize_t *length) +{ + if (PyBytes_AsStringAndSize (ref, buffer, length) == -1) + throw gdb_python_exception (); +} + +static inline gdbpy_ref<> +gdbpy_bytes_from_string (const char *str) +{ + gdb_assert (str != nullptr); + gdbpy_ref<> result (PyBytes_FromString (str)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_ref<> +gdbpy_bytes_from_string_and_size (const char *str, Py_ssize_t len) +{ + /* Python allows STR==nullptr but it leaves the object + uninitialized, and I think we should avoid this in gdb. */ + gdb_assert (str != nullptr); + gdbpy_ref<> result (PyBytes_FromStringAndSize (str, len)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline Py_ssize_t +gdbpy_bytes_size (gdbpy_borrowed_ref ref) +{ + Py_ssize_t result = PyBytes_Size (ref); + if (result == -1) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_ref<> +gdbpy_new_list (Py_ssize_t len) +{ + gdbpy_ref<> result (PyList_New (len)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline void +gdbpy_list_append (gdbpy_borrowed_ref list, gdbpy_borrowed_ref val) +{ + if (PyList_Append (list, val) < 0) + throw gdb_python_exception (); +} + +static inline gdbpy_ref<> +gdbpy_new_dict () +{ + gdbpy_ref<> result (PyDict_New ()); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline void +gdbpy_dict_set_item_string (gdbpy_borrowed_ref dict, + const char *key, + gdbpy_borrowed_ref value) +{ + if (PyDict_SetItemString (dict, key, value) != 0) + throw gdb_python_exception (); +} + +static inline void +gdbpy_dict_del_item_string (gdbpy_borrowed_ref dict, + const char *key) +{ + if (PyDict_DelItemString (dict, key) == -1) + throw gdb_python_exception (); +} + +static inline gdbpy_borrowed_ref +gdbpy_dict_get_item_with_error (gdbpy_borrowed_ref dict, + gdbpy_borrowed_ref key) +{ + PyObject *result = PyDict_GetItemWithError (dict, key); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_ref<> +gdbpy_dict_keys (gdbpy_borrowed_ref dict) +{ + gdbpy_ref<> result (PyDict_Keys (dict)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_ref<> +gdbpy_unicode_from_string (std::string_view str) +{ + gdbpy_ref<> result (PyUnicode_FromStringAndSize (str.data (), str.size ())); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_ref<> +gdbpy_unicode_from_format (const char *fmt, ...) +{ + va_list args; + va_start (args, fmt); + gdbpy_ref<> result (PyUnicode_FromFormatV (fmt, args)); + if (result == nullptr) + throw gdb_python_exception (); + va_end (args); + return result; +} + +[[noreturn]] static inline void +gdbpy_err_set_string (gdbpy_borrowed_ref type, const char *str) +{ + PyErr_SetString (type, str); + throw gdb_python_exception (); +} + +/* This is a template because PyErr_FormatV was only added in Python + 3.5. */ +template +[[noreturn]] void +gdbpy_err_format (gdbpy_borrowed_ref type, const char *fmt, Arg... args) +{ + PyErr_Format (type, fmt, std::forward (args)...); + throw gdb_python_exception (); +} + +template +void +gdbpy_arg_parse_tuple_and_keywords (gdbpy_borrowed_ref args, + gdbpy_opt_borrowed_ref kw, + const char *fmt, + const char **keywords, + Arg... outputs) +{ + /* It would be cool if callers could use references to the + out-parameters and also if gdbpy_borrowed_ref could be used for + those. That requires some hairy template metaprogramming + though. */ + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, fmt, keywords, + std::forward (outputs)...)) + throw gdb_python_exception (); +} + +static inline void +gdbpy_arg_parse_tuple (gdbpy_borrowed_ref param, const char *format, ...) +{ + va_list args; + va_start (args, format); + if (!PyArg_VaParse (param, format, args)) + throw gdb_python_exception (); + va_end (args); +} + +static inline long +gdbpy_long_as_long (gdbpy_borrowed_ref arg) +{ + long result = PyLong_AsLong (arg); + if (result == -1 && PyErr_Occurred ()) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_ref<> +gdbpy_tuple_new (Py_ssize_t len) +{ + gdbpy_ref<> result (PyTuple_New (len)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_borrowed_ref +gdbpy_tuple_get_item (gdbpy_borrowed_ref tuple, Py_ssize_t pos) +{ + PyObject *result = PyTuple_GetItem (tuple, pos); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline void +gdbpy_tuple_set_item (gdbpy_borrowed_ref tuple, Py_ssize_t pos, + gdbpy_ref<> &&item) +{ + if (PyTuple_SetItem (tuple, pos, item.release ()) == -1) + throw gdb_python_exception (); +} + +static inline Py_ssize_t +gdbpy_tuple_size (gdbpy_borrowed_ref tuple) +{ + Py_ssize_t result = PyTuple_Size (tuple); + if (result == -1) + throw gdb_python_exception (); + return result; +} + +static inline Py_ssize_t +gdbpy_sequence_size (gdbpy_borrowed_ref seq) +{ + Py_ssize_t result = PySequence_Size (seq); + if (result == -1) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_ref<> +gdbpy_sequence_get_item (gdbpy_borrowed_ref seq, Py_ssize_t i) +{ + gdbpy_ref<> result (PySequence_GetItem (seq, i)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline void +gdbpy_sequence_del_item (gdbpy_borrowed_ref seq, Py_ssize_t i) +{ + if (PySequence_DelItem (seq, i) == -1) + throw gdb_python_exception (); +} + +static inline gdbpy_ref<> +gdbpy_sequence_list (gdbpy_borrowed_ref seq) +{ + gdbpy_ref<> result (PySequence_List (seq)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +static inline gdbpy_ref<> +gdbpy_sequence_concat (gdbpy_borrowed_ref first, gdbpy_borrowed_ref second) +{ + gdbpy_ref<> result (PySequence_Concat (first, second)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +#endif /* GDB_PYTHON_PY_WRAPPERS_H */ diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 693829e920d..6df0c62e2b3 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -1382,4 +1382,6 @@ py_notimplemented () #undef Py_RETURN_FALSE #undef Py_RETURN_NOTIMPLEMENTED +#include "py-wrappers.h" + #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */ From patchwork Fri May 15 19:59:23 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Tromey X-Patchwork-Id: 135096 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 53A29409FCB6 for ; Fri, 15 May 2026 20:01:52 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 53A29409FCB6 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=qLpfK+gS X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from omta036.useast.a.cloudfilter.net (omta036.useast.a.cloudfilter.net [44.202.169.35]) by sourceware.org (Postfix) with ESMTPS id 44C39409FCBE for ; Fri, 15 May 2026 19:59:27 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 44C39409FCBE 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 44C39409FCBE Authentication-Results: sourceware.org; arc=none smtp.remote-ip=44.202.169.35 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778875167; cv=none; b=NLSPBbClYiP4GLEIdmIy2IUiIYpSAEKDAfWae4ywGvYAWxIHjE3JkB5r/4MKAQ97fwlKoOzfWeAVST41kLhIYoj8y5ZCI7fwABIzx4RUNbieqbjLzqEtQBIcwZkDpkgHMwnqj0mLRMtynIwd+XdW//2AVuENtpvYwEBtE1WERM8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778875167; c=relaxed/simple; bh=IdTkhkMC8a1tCQMIZzISn93CQKMezmX51T+QibPsJP8=; h=DKIM-Signature:From:Date:Subject:MIME-Version:Message-Id:To; b=vEiUMluD2lKpIOHufvEeEpYY7RIQqBHgAUALFTilho23thvbZvekhOFphykSX/gdSK2qul0hprXvBgIct/yx40MZj29rlT7cz4Xc8sNIuMziYjceRuxrEZXp/nWAWr7czAKIlZj38PJYTzJ4DpHfSNqUzDc2SU96T0YCg2YoamM= 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=qLpfK+gS reason="signing key too small" DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 44C39409FCBE Received: from eig-obgw-5002b.ext.cloudfilter.net ([10.0.29.226]) by cmsmtp with ESMTPS id NwFbwe8WGnv6VNygwwIpgC; Fri, 15 May 2026 19:59:26 +0000 Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with ESMTPS id NyguwP7KtZYlBNygvwZYsx; Fri, 15 May 2026 19:59:25 +0000 X-Authority-Analysis: v=2.4 cv=MJRgmNZl c=1 sm=1 tr=0 ts=6a077b1e a=ApxJNpeYhEAb1aAlGBBbmA==:117 a=ApxJNpeYhEAb1aAlGBBbmA==:17 a=IkcTkHD0fZMA:10 a=NGcC8JguVDcA:10 a=ItBw4LHWJt0A:10 a=mDV3o1hIAAAA:8 a=BXsQ0g1lTVAEWues1i8A:9 a=QEXdDO2ut3YA:10 a=O8hF6Hzn-FEA:10 a=DCx65vhANUyCzuf5D8fC:22 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=mydfsgNQvmSgdSJG6q/HHCckrvhKe+u9MlkmZJSiA8g=; b=qLpfK+gSKg2aoRF25fGFZpDsbE q2ATjkylPqQ2zHrFGSBCZWyrlhiZasPjWv2aEjED6dflClcAfgpiCRWjssmQIcPQ4DY2kiRdIFLvo o97oO2/Py7m/HX9FzjidyFkhV; Received: from 75-166-225-82.hlrn.qwest.net ([75.166.225.82]:60112 helo=[192.168.0.17]) by box5379.bluehost.com with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.99.2) (envelope-from ) id 1wNygu-0000000418T-2qFJ; Fri, 15 May 2026 13:59:24 -0600 From: Tom Tromey Date: Fri, 15 May 2026 13:59:23 -0600 Subject: [PATCH v2 3/4] Add wrappers for Python implementation functions and methods MIME-Version: 1.0 Message-Id: <20260515-python-safety-initial-v2-3-6129cadf258a@tromey.com> References: <20260515-python-safety-initial-v2-0-6129cadf258a@tromey.com> In-Reply-To: <20260515-python-safety-initial-v2-0-6129cadf258a@tromey.com> To: gdb-patches@sourceware.org Cc: Tom Tromey X-Mailer: b4 0.14.3 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.225.82 X-Source-L: No X-Exim-ID: 1wNygu-0000000418T-2qFJ X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: 75-166-225-82.hlrn.qwest.net ([192.168.0.17]) [75.166.225.82]:60112 X-Source-Auth: tom+tromey.com X-Email-Count: 4 X-Org: HG=bhshared;ORG=bluehost; X-Source-Cap: ZWx5bnJvYmk7ZWx5bnJvYmk7Ym94NTM3OS5ibHVlaG9zdC5jb20= X-Local-Domain: yes X-CMAE-Envelope: MS4xfArtVAHPRMD2FRPFzX13XOQdM42lVKeX4k5ecCENTk9TnRFFw2ejgdSyY21ROmcyQc5oOf5eKSx9b1Aiq1bVz/KcX3FOycXm5WS8xC39eB4Lik/K4D+W EF6hJQGN/l95I/dM8urfBmFpiynb7OKuDj9URbkKhOapptm0LUeQDsWOnD2p2EpMhzpGLwvWk9Yy4S5y8imgop9OKq+r6k1JVTk= X-Spam-Status: No, score=-3012.1 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, GIT_PATCH_0, JMQ_SPF_NEUTRAL, KAM_SHORT, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, TXREP, T_SPF_PERMERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org This adds some wrappers for Python implementation functions and methods, and a couple of new constexpr functions to create PyMethodDef entries. This provides a few safety benefits: * The new-style API approach (see previous patch) is implemented by the wrapper. That is, exceptions are caught here and transformed. * The implementation functions can now return any reasonable type, with automatic conversion by the wrapper. * The function API and the appropriate METH_* flags are handled together, avoiding any possible discrepancy. This approach also means that we can modify the old rule that gdb calls must be wrapped in a try/catch -- the try/catch is now provided by the wrapper function, so the implementation can be written in a more natural way. Note this patch is not 100% complete. There should be one more wrapper for case where a method takes a single argument (though we probably cannot use METH_O unfortunately). There may be some other holes as well. --- gdb/python/py-safety.h | 320 +++++++++++++++++++++++++++++++++++++++++++ gdb/python/python-internal.h | 1 + 2 files changed, 321 insertions(+) diff --git a/gdb/python/py-safety.h b/gdb/python/py-safety.h new file mode 100644 index 00000000000..62018dacc0f --- /dev/null +++ b/gdb/python/py-safety.h @@ -0,0 +1,320 @@ +/* Wrappers for some Python safety. + + 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 . */ + +#ifndef GDB_PYTHON_PY_SAFETY_H +#define GDB_PYTHON_PY_SAFETY_H + +#include "gdbsupport/traits.h" +#include "py-ref.h" +#include "py-wrappers.h" +#include "charset.h" + +/* This file holds wrapper templates for the various ways that gdb + code might be exposed to Python. These wrappers are part of gdb's + "Python safety" approach -- utilities designed to try to prevent + refcount problems, missing error checks, and that also remove the + need to wrap calls into gdb in an explicit try/catch. + + See py-wrappers.h for some more discussion of this. + + Implementation functions and methods -- the stuff you write to + expose some part of gdb to Python -- are written in a certain + style. + + An implementation function is a module-level function. For example + something like 'gdb.register_window_type' is implemented as a + function. An implementation method is a method of the gdb class + implementing a certain Python type; for example + 'gdb.TuiWindow.width' is implemented via a method of + gdbpy_tui_window. + + Each of these will accept gdbpy_borrowed_ref arguments (or in some + more limited situations, a gdbpy_opt_borrowed_ref) and return any + relevant type, which will be automatically converted (see the + 'to_python' overloads below) to the correct Python type. Note that + if the 'to_python' functions aren't appropriate in your case, you + can always use the escape hatch of returning a gdbpy_ref<> and + handling type conversion manually. + + There are some constexpr functions below that wrap the + implementation methods, and create PyMethodDef objects and the + like. + + Implementation methods are expected to use the wrappers in + py-wrappers.h and not generally call into Python directly. + + Implementation details of the method-wrapping safety code are put + into this namespace, just to emphasize that these shouldn't be used + elsewhere. Skip past the namespace to find the public APIs. */ +namespace safety_details +{ +/* Overloads of "to_python" are used by the safety wrappers to convert + a function's real return value to a Python object. A new reference + will always be returned. For the time being these are kept as a + detail of the method-wrapping code. However we may want to + consider exposing these more generally. */ + +static inline PyObject * +to_python (bool value) +{ + /* Note that this cannot fail. */ + return PyBool_FromLong (value); +} + +template> +static inline PyObject * +to_python (T value) +{ + if constexpr (std::is_signed::value) + return gdb_py_object_from_longest (value).release (); + else + return gdb_py_object_from_ulongest (value).release (); +} + +static inline PyObject * +to_python (const char *value) +{ + if (value == nullptr) + return py_none ().release (); + return PyUnicode_Decode (value, strlen (value), host_charset (), nullptr); +} + +static inline PyObject * +to_python (std::string &&value) +{ + return PyUnicode_Decode (value.c_str (), value.size (), + host_charset (), nullptr); +} + +static inline PyObject * +to_python (const std::string &value) +{ + return PyUnicode_Decode (value.c_str (), value.size (), + host_charset (), nullptr); +} + +static inline PyObject * +to_python (gdb::unique_xmalloc_ptr &&value) +{ + if (value == nullptr) + return py_none ().release (); + return PyUnicode_Decode (value.get (), strlen (value.get ()), + host_charset (), nullptr); +} + +static inline PyObject * +to_python (gdbpy_ref<> &&value) +{ + return value.release (); +} + +/* An instantiation of this function is used when calling a gdb method + from Python. It accepts some number of arguments (normally + gdbpy_borrowed_ref or gdbpy_opt_borrowed_ref), and then then calls + the underlying function F. Any exceptions are caught and + converted, and the return value of F is converted to a Python + object as appropriate. */ +template +PyObject * +wrapped_function (Args... args) +{ + try + { + using result_type = typename std::invoke_result_t; + + if constexpr (std::is_void_v) + { + F (args...); + return py_none ().release (); + } + else + return to_python (F (args...)); + } + catch (const gdb_python_exception &pye) + { + gdb_assert (PyErr_Occurred () != nullptr); + return nullptr; + } + catch (const gdb_exception &exc) + { + return gdbpy_handle_gdb_exception (nullptr, exc); + } +} + +/* An instantiation of this function is used when calling a gdb method + from Python. It accepts some number of arguments (normally + gdbpy_borrowed_ref or gdbpy_opt_borrowed_ref), and then then calls + the underlying function F. Any exceptions are caught and + converted, and the return value of F is converted to a Python + object as appropriate. */ +template +PyObject * +wrapped_method (Ret (Class::*meth) (Args...), Class *self, Args... args) +{ + try + { + if constexpr (std::is_void_v) + { + (self->*meth) (args...); + return py_none ().release (); + } + else + return to_python ((self->*meth) (args...)); + } + catch (const gdb_python_exception &pye) + { + gdb_assert (PyErr_Occurred () != nullptr); + return nullptr; + } + catch (const gdb_exception &exc) + { + return gdbpy_handle_gdb_exception (nullptr, exc); + } +} + +template +PyObject * +fn_wrapper (PyObject *self, PyObject *args, PyObject *kw) +{ + return wrapped_function (gdbpy_borrowed_ref (args), + gdbpy_opt_borrowed_ref (kw)); +} + +template +PyObject * +varargs_wrapper (PyObject *self, PyObject *args, PyObject *kw) +{ + return wrapped_method (M, static_cast (self), + gdbpy_borrowed_ref (args), + gdbpy_opt_borrowed_ref (kw)); +} + +} /* namespace safety_details */ + +/* Create a PyMethodDef for a no-argument method. It takes the + underlying class C and a pointer-to-method M as template + parameters, and the name and documentation as arguments. The + method M is wrapped to call to_python and to catch exceptions per + the safety protocol. */ +template +constexpr PyMethodDef +noargs_method (std::string_view name, std::string_view doc) +{ + using namespace safety_details; + return { + name.data (), + [] (PyObject *self, PyObject *args) -> PyObject * + { + return wrapped_method (M, static_cast (self)); + }, + METH_NOARGS, + doc.data (), + }; +} + +/* This is used to create the PyMethodDef for a varargs function. It + takes the underlying implementation function as a template + argument, and also arguments for the method name and documentation + string. + + The underlying function should accept a gdbpy_borrowed_ref argument + (the arguments to the Python function), and then a + gdbpy_opt_borrowed_ref for the keywords. The function can return + any type (see the to_python overloads); and should throw an + exception on error. If gdb_python_exception is thrown, the Python + exception must already have been set. + + The gdb policy is that varargs methods must also accept keywords, + and this is enforced here. */ +template +constexpr PyMethodDef +varargs_function (std::string_view name, std::string_view doc) +{ + using namespace safety_details; + return { + name.data (), + (PyCFunction) fn_wrapper, + /* gdb's rule is that varargs should also use keywords. */ + METH_VARARGS | METH_KEYWORDS, + doc.data (), + }; +} + +/* Like a varargs function, but this implements a method on some + Python type that gdb provides. The implementation class and a + pointer-to-method must be specified. */ +template +constexpr PyMethodDef +varargs_method (std::string_view name, std::string_view doc) +{ + using namespace safety_details; + return { + name.data (), + (PyCFunction) varargs_wrapper, + /* gdb's rule is that varargs should also use keywords. */ + METH_VARARGS | METH_KEYWORDS, + doc.data (), + }; +} + +/* A function that wraps a "repr" or "str" method. */ +template +PyObject * +wrap_repr (PyObject *arg) +{ + using namespace safety_details; + return wrapped_method (M, static_cast (arg)); +} + +/* A function that wraps a "get" method. */ +template +PyObject * +wrap_getter (PyObject *arg, void *closure) +{ + using namespace safety_details; + /* In gdb the closure argument is never used. */ + return wrapped_method (M, static_cast (arg)); +} + +/* A function that wraps a "set" method. */ +template +int +wrap_setter (PyObject *arg, PyObject *value, void *closure) +{ + using namespace safety_details; + try + { + C *self = static_cast (arg); + /* In gdb the closure argument is never used. */ + (self->*M) (gdbpy_opt_borrowed_ref (value)); + } + catch (const gdb_python_exception &pye) + { + gdb_assert (PyErr_Occurred () != nullptr); + return -1; + } + catch (const gdb_exception &exc) + { + return gdbpy_handle_gdb_exception (-1, exc); + } + + return 0; +} + +#endif /* GDB_PYTHON_PY_SAFETY_H */ diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index 6df0c62e2b3..e4f35f8cd88 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -1383,5 +1383,6 @@ py_notimplemented () #undef Py_RETURN_NOTIMPLEMENTED #include "py-wrappers.h" +#include "py-safety.h" #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */ From patchwork Fri May 15 19:59:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Tromey X-Patchwork-Id: 135097 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id E88F54BA23C5 for ; Fri, 15 May 2026 20:02:10 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E88F54BA23C5 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=Mw8zEM9w X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from omta040.useast.a.cloudfilter.net (omta040.useast.a.cloudfilter.net [44.202.169.39]) by sourceware.org (Postfix) with ESMTPS id 28A355E4CF08 for ; Fri, 15 May 2026 19:59:29 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 28A355E4CF08 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 28A355E4CF08 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=44.202.169.39 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778875169; cv=none; b=ZeJc8quSAhZ2EAgkG8LJGyVZnbvJfLDkXINDiuIPRoszYRR8sRHmuPzFW7vqR1fnTzWiB+Hiar7LMt9goORczDzKjNNk5X0v5Dr9VHSyiowwfP0/zZ6Vd68Kv4WGdGK1Ee/BDTf1OQOQTmpAVLNU+hmL5qqakRbwTciM3lqH4kk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778875169; c=relaxed/simple; bh=NuT63+Bbe7pnYzneCx7YZGWoMJZFKaNQqcjwA2jmbnE=; h=DKIM-Signature:From:Date:Subject:MIME-Version:Message-Id:To; b=RXcbvnmIVS6Dng3JK4Eq+QCBOTFqQ/lRMFrSMCMQPtSrYU37h39NM91HC9LmcKx68kUbzjta8Rr9LiWh3Yod6jP+T4rr0l35SoJ//gRzF3Ix08yKB4UGxDeRB4iOaApgyzPiYCdm5OCDs+4r46wsAjxlKiJ+52XUN7ojdMYThis= 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=Mw8zEM9w reason="signing key too small" DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 28A355E4CF08 Received: from eig-obgw-5003b.ext.cloudfilter.net ([10.0.29.155]) by cmsmtp with ESMTPS id NxxCwKJDv4RkpNygywrcQo; Fri, 15 May 2026 19:59:28 +0000 Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with ESMTPS id NygvwUVntCKa1Nygvw9R84; Fri, 15 May 2026 19:59:25 +0000 X-Authority-Analysis: v=2.4 cv=IaKHWXqa c=1 sm=1 tr=0 ts=6a077b20 a=ApxJNpeYhEAb1aAlGBBbmA==:117 a=ApxJNpeYhEAb1aAlGBBbmA==:17 a=IkcTkHD0fZMA:10 a=NGcC8JguVDcA:10 a=ItBw4LHWJt0A:10 a=idmLz7Bo26IfJ7ekqPEA:9 a=3ZKOabzyN94A:10 a=QEXdDO2ut3YA:10 a=DCx65vhANUyCzuf5D8fC:22 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=Cc:To:In-Reply-To:References:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From:Sender: Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=1ifym71fZJ5Pjb+YoJcTyWwpAzfjthCRba2ytwi5QJw=; b=Mw8zEM9wHCtSvoYmrtSiySi1HL 14BieIh2EWdEpC1fo5T2BH1KAPDx65wz8BQTW+juxSmDXmjOLmDkXZRpvSHnNdfB5wnM5WrGy2noK xCKN6Z4FxBXNDpXTcW3ykKU1o; Received: from 75-166-225-82.hlrn.qwest.net ([75.166.225.82]:60112 helo=[192.168.0.17]) by box5379.bluehost.com with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 (Exim 4.99.2) (envelope-from ) id 1wNygu-0000000418T-456d; Fri, 15 May 2026 13:59:25 -0600 From: Tom Tromey Date: Fri, 15 May 2026 13:59:24 -0600 Subject: [PATCH v2 4/4] Convert py-tui.c to the "python safety" approach MIME-Version: 1.0 Message-Id: <20260515-python-safety-initial-v2-4-6129cadf258a@tromey.com> References: <20260515-python-safety-initial-v2-0-6129cadf258a@tromey.com> In-Reply-To: <20260515-python-safety-initial-v2-0-6129cadf258a@tromey.com> To: gdb-patches@sourceware.org Cc: Tom Tromey X-Mailer: b4 0.14.3 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.225.82 X-Source-L: No X-Exim-ID: 1wNygu-0000000418T-456d X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: 75-166-225-82.hlrn.qwest.net ([192.168.0.17]) [75.166.225.82]:60112 X-Source-Auth: tom+tromey.com X-Email-Count: 5 X-Org: HG=bhshared;ORG=bluehost; X-Source-Cap: ZWx5bnJvYmk7ZWx5bnJvYmk7Ym94NTM3OS5ibHVlaG9zdC5jb20= X-Local-Domain: yes X-CMAE-Envelope: MS4xfI5n0VFfaFHH7+InPdVP1/JDd3HnQsqwmckPcTCU4Ez1yJnjQUVfpyyFsJwW5TTacMhTypyNOqrtyllGOEVpBw+r3HJ6fDbCDJOFoYIUD7iIMAtRaNYa kEXBbe3rgwPcG6GZjdp+JvAf90Kr4Xb6whtVv4tulXsOSQ3pN9aGF2TLXOOvtQfQX4a8WFSdQfzZYJ8ieF/WF2WQBGcZIkhvP3Q= X-Spam-Status: No, score=-3012.1 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, GIT_PATCH_0, JMQ_SPF_NEUTRAL, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, TXREP, T_SPF_PERMERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org This patch mostly converts py-tui.c to use the new Python safety code. In particular: * All methods of gdb.TuiWindow are now implemented as straightforward methods of gdbpy_tui_window. * gdb.register_tui_window is converted and simply returns 'void'. I converted this particular file because it was relatively straightforward, while also demonstrating most of the features of the new approach. For example, explicit result checks aren't needed, try/catch can be removed, and the methods are now written in a natural style. Note that more conversion remains to be done here: * gdbpy_tui_enabled hasn't been converted and still does explicit checks. * There's one explicit check in gdbpy_tui_window::set_title. Fully implementing the safety approach means that some low-level things should eventually be converted to throw; but some work has to be deferred until a lot of the work is complete. --- gdb/python/py-tui.c | 208 ++++++++++++++++++------------------------- gdb/python/python-internal.h | 5 +- gdb/python/python.c | 5 +- 3 files changed, 92 insertions(+), 126 deletions(-) diff --git a/gdb/python/py-tui.c b/gdb/python/py-tui.c index 111e0f4c9e3..0755d7b92e3 100644 --- a/gdb/python/py-tui.c +++ b/gdb/python/py-tui.c @@ -50,7 +50,34 @@ struct gdbpy_tui_window: public PyObject tui_py_window *window; /* Return true if this object is valid. */ - bool is_valid () const; + bool is_valid (); + + /* Require that this object be valid. Throws exception if not. */ + void require_valid () + { + if (!is_valid ()) + gdbpy_err_format (PyExc_RuntimeError, _("TUI window is invalid.")); + } + + /* Erase the TUI window. */ + void erase (); + + /* Python function that writes some text to a TUI window. */ + void write (gdbpy_borrowed_ref args, gdbpy_opt_borrowed_ref kw); + + /* Return the width of the TUI window. */ + int width (); + + /* Return the height of the TUI window. */ + int height (); + + /* Return the title of the TUI window. */ + const std::string &title (); + + /* Set the title of the TUI window. */ + void set_title (gdbpy_opt_borrowed_ref new_title); + + static PyTypeObject *corresponding_object_type; }; extern PyTypeObject gdbpy_tui_window_object_type; @@ -150,7 +177,7 @@ class tui_py_window : public tui_win_info /* See gdbpy_tui_window declaration above. */ bool -gdbpy_tui_window::is_valid () const +gdbpy_tui_window::is_valid () { return window != nullptr && tui_active; } @@ -406,173 +433,109 @@ gdbpy_tui_window_maker::operator() (const char *win_name) /* Implement "gdb.register_window_type". */ -PyObject * -gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw) +void +gdbpy_register_tui_window (gdbpy_borrowed_ref args, + gdbpy_opt_borrowed_ref kw) { static const char *keywords[] = { "name", "constructor", nullptr }; - const char *name; PyObject *cons_obj; + gdbpy_arg_parse_tuple_and_keywords (args, kw, "sO", keywords, + &name, &cons_obj); - if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "sO", keywords, - &name, &cons_obj)) - return nullptr; - - try - { - gdbpy_tui_window_maker constr (gdbpy_ref<>::new_reference (cons_obj)); - tui_register_window (name, constr); - } - catch (const gdb_exception &except) - { - return gdbpy_handle_gdb_exception (nullptr, except); - } - - return py_none ().release (); + gdbpy_tui_window_maker constr (gdbpy_ref<>::new_reference (cons_obj)); + tui_register_window (name, constr); } -/* Require that "Window" be a valid window. */ - -#define REQUIRE_WINDOW(Window) \ - do { \ - if (!(Window)->is_valid ()) \ - return PyErr_Format (PyExc_RuntimeError, \ - _("TUI window is invalid.")); \ - } while (0) - -/* Require that "Window" be a valid window. */ - -#define REQUIRE_WINDOW_FOR_SETTER(Window) \ - do { \ - if (!(Window)->is_valid ()) \ - { \ - PyErr_Format (PyExc_RuntimeError, \ - _("TUI window is invalid.")); \ - return -1; \ - } \ - } while (0) - -/* Python function which checks the validity of a TUI window - object. */ -static PyObject * -gdbpy_tui_is_valid (PyObject *self, PyObject *args) -{ - gdbpy_tui_window *win = (gdbpy_tui_window *) self; - - if (win->is_valid ()) - return py_true ().release (); - return py_false ().release (); -} - /* Python function that erases the TUI window. */ -static PyObject * -gdbpy_tui_erase (PyObject *self, PyObject *args) +void +gdbpy_tui_window::erase () { - gdbpy_tui_window *win = (gdbpy_tui_window *) self; - - REQUIRE_WINDOW (win); - - win->window->erase (); - - return py_none ().release (); + require_valid (); + window->erase (); } /* Python function that writes some text to a TUI window. */ -static PyObject * -gdbpy_tui_write (PyObject *self, PyObject *args, PyObject *kw) +void +gdbpy_tui_window::write (gdbpy_borrowed_ref args, gdbpy_opt_borrowed_ref kw) { + require_valid (); + static const char *keywords[] = { "string", "full_window", nullptr }; - gdbpy_tui_window *win = (gdbpy_tui_window *) self; const char *text; int full_window = 0; - if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|i", keywords, - &text, &full_window)) - return nullptr; - - REQUIRE_WINDOW (win); + gdbpy_arg_parse_tuple_and_keywords (args, kw, "s|i", keywords, + &text, &full_window); - win->window->output (text, full_window); - - return py_none ().release (); + window->output (text, full_window); } -/* Return the width of the TUI window. */ -static PyObject * -gdbpy_tui_width (PyObject *self, void *closure) +int +gdbpy_tui_window::width () { - gdbpy_tui_window *win = (gdbpy_tui_window *) self; - REQUIRE_WINDOW (win); - gdbpy_ref<> result - = gdb_py_object_from_longest (win->window->viewport_width ()); - return result.release (); + require_valid (); + return window->viewport_width (); } -/* Return the height of the TUI window. */ -static PyObject * -gdbpy_tui_height (PyObject *self, void *closure) +int +gdbpy_tui_window::height () { - gdbpy_tui_window *win = (gdbpy_tui_window *) self; - REQUIRE_WINDOW (win); - gdbpy_ref<> result - = gdb_py_object_from_longest (win->window->viewport_height ()); - return result.release (); + require_valid (); + return window->viewport_height (); } -/* Return the title of the TUI window. */ -static PyObject * -gdbpy_tui_title (PyObject *self, void *closure) +const std::string & +gdbpy_tui_window::title () { - gdbpy_tui_window *win = (gdbpy_tui_window *) self; - REQUIRE_WINDOW (win); - return host_string_to_python_string (win->window->title ().c_str ()).release (); + require_valid (); + return window->title (); } /* Set the title of the TUI window. */ -static int -gdbpy_tui_set_title (PyObject *self, PyObject *newvalue, void *closure) +void +gdbpy_tui_window::set_title (gdbpy_opt_borrowed_ref new_title) { - gdbpy_tui_window *win = (gdbpy_tui_window *) self; + require_valid (); - REQUIRE_WINDOW_FOR_SETTER (win); - - if (newvalue == nullptr) - { - PyErr_Format (PyExc_TypeError, _("Cannot delete \"title\" attribute.")); - return -1; - } + if (new_title == nullptr) + gdbpy_err_format (PyExc_TypeError, + _("Cannot delete \"title\" attribute.")); + /* FIXME: safety */ gdb::unique_xmalloc_ptr value - = python_string_to_host_string (newvalue); + = python_string_to_host_string (new_title); if (value == nullptr) - return -1; + throw gdb_python_exception (); - win->window->set_title (value.get ()); - return 0; + window->set_title (value.get ()); } static gdb_PyGetSetDef tui_object_getset[] = { - { "width", gdbpy_tui_width, NULL, "Width of the window.", NULL }, - { "height", gdbpy_tui_height, NULL, "Height of the window.", NULL }, - { "title", gdbpy_tui_title, gdbpy_tui_set_title, "Title of the window.", - NULL }, - { NULL } /* Sentinel */ + { "width", wrap_getter, nullptr, + "Width of the window.", nullptr }, + { "height", wrap_getter, nullptr, + "Height of the window.", nullptr }, + { "title", wrap_getter, + wrap_setter, + "Title of the window.", nullptr }, + { nullptr } /* Sentinel */ }; static PyMethodDef tui_object_methods[] = { - { "is_valid", gdbpy_tui_is_valid, METH_NOARGS, + noargs_method ("is_valid", "is_valid () -> Boolean\n\ -Return true if this TUI window is valid, false if not." }, - { "erase", gdbpy_tui_erase, METH_NOARGS, - "Erase the TUI window." }, - { "write", (PyCFunction) gdbpy_tui_write, METH_VARARGS | METH_KEYWORDS, - "Append a string to the TUI window." }, - { NULL } /* Sentinel. */ +Return true if this TUI window is valid, false if not."), + noargs_method + ("erase", "Erase the TUI window."), + varargs_method ("write", + "Append a string to the TUI window."), + { nullptr } /* Sentinel. */ }; PyTypeObject gdbpy_tui_window_object_type = @@ -616,6 +579,9 @@ PyTypeObject gdbpy_tui_window_object_type = 0, /* tp_alloc */ }; +PyTypeObject *gdbpy_tui_window::corresponding_object_type + = &gdbpy_tui_window_object_type; + /* Called when TUI is enabled or disabled. */ static void diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index e4f35f8cd88..9e05cdd67e7 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -496,8 +496,9 @@ gdb::unique_xmalloc_ptr gdbpy_parse_command_name (const char *name, struct cmd_list_element ***base_list, struct cmd_list_element **start_list, struct cmd_list_element **prefix_cmd = nullptr); -PyObject *gdbpy_register_tui_window (PyObject *self, PyObject *args, - PyObject *kw); + +void gdbpy_register_tui_window (gdbpy_borrowed_ref args, + gdbpy_opt_borrowed_ref kw); gdbpy_ref<> symtab_and_line_to_sal_object (struct symtab_and_line sal); gdbpy_ref<> symtab_to_symtab_object (struct symtab *symtab); diff --git a/gdb/python/python.c b/gdb/python/python.c index fd254a340d8..849154ff20c 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -3274,10 +3274,9 @@ or None if not set." }, Set the value of the convenience variable $NAME." }, #ifdef TUI - { "register_window_type", (PyCFunction) gdbpy_register_tui_window, - METH_VARARGS | METH_KEYWORDS, + varargs_function ("register_window_type", "register_window_type (NAME, CONSTRUCTOR) -> None\n\ -Register a TUI window constructor." }, +Register a TUI window constructor."), #endif /* TUI */ { "architecture_names", gdbpy_all_architecture_names, METH_NOARGS,