From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id CToOKEaVD2o/oA0AWB0awg (envelope-from ) for ; Thu, 21 May 2026 19:29:10 -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=sl/kFNRK; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 9F9E41E091; Thu, 21 May 2026 19:29:10 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_INVALID,DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham 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 9D4F31E024 for ; Thu, 21 May 2026 19:29:09 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2E7EA48F665B for ; Thu, 21 May 2026 23:29:09 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2E7EA48F665B 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=sl/kFNRK Received: from omta036.useast.a.cloudfilter.net (omta036.useast.a.cloudfilter.net [44.202.169.35]) by sourceware.org (Postfix) with ESMTPS id 37C6448F90EF for ; Thu, 21 May 2026 23:28:00 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 37C6448F90EF 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 37C6448F90EF 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=1779406080; cv=none; b=kzU4lveU7Uq+FvcfqHJ12m6WkFGvu8s4HVd2t+wQNPa2bihH3nWsp/e6NRTspovW39BFg7E+pIzVUkDm7qkgM6DTjw34X6z/bxnqqp6jJ4uBezkub5Gr0ZPARm3gSeSnVXvQYEBsq/Qyb88dgtvQVocE9CmBgfJrDqZJf72qTZ0= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779406080; c=relaxed/simple; bh=4gIOTyaheGdK513qZP2k2c9pvw9JBmrZSsp/pgoQM8Q=; h=DKIM-Signature:From:Date:Subject:MIME-Version:Message-Id:To; b=PCURJXQUR69pBSLDVbPxRNcIoxoGcznPE0gfquEmav2DMNceo6/qwDduww7t7kFwvXvWgzg5BBjVU3898eoF0w6neCDNiyiKmG4R+P68xZQ9zQWkDTZFVSy8x/zwK4edM9A0obTZnfVD5rH3PvdlGHrlId8IxBPgTNor8eRWSqQ= 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=sl/kFNRK reason="signing key too small" DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 37C6448F90EF Received: from eig-obgw-5004b.ext.cloudfilter.net ([10.0.29.208]) by cmsmtp with ESMTPS id Q8jBwoqkggwLnQCo3wrm5S; Thu, 21 May 2026 23:27:59 +0000 Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with ESMTPS id QCo2wF9JKnfJUQCo2w9aEb; Thu, 21 May 2026 23:27:58 +0000 X-Authority-Analysis: v=2.4 cv=KajSsRYD c=1 sm=1 tr=0 ts=6a0f94fe a=ApxJNpeYhEAb1aAlGBBbmA==:117 a=ApxJNpeYhEAb1aAlGBBbmA==:17 a=IkcTkHD0fZMA:10 a=NGcC8JguVDcA:10 a=ItBw4LHWJt0A:10 a=mDV3o1hIAAAA:8 a=9N4TOJ-CW-J8vYyAuvsA: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=iZRLz+4711hW8z7wKMhvP5xHJziDybpP8+Ong8hZzJc=; b=sl/kFNRKWO6kEEUxV5Po8llGS/ NPG1PCJXd4SgOfksKy27+i870eRZcliOkfKavW/IE8DRAIDA8ZNcKPfML6YrlcYQPDJbVj8yQHF/Q HJ+fWXtSXVpLyZL6rN3SH4nXI; Received: from 75-166-225-82.hlrn.qwest.net ([75.166.225.82]:37536 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 1wQCo2-00000002l41-0YeA; Thu, 21 May 2026 17:27:58 -0600 From: Tom Tromey Date: Thu, 21 May 2026 17:27:53 -0600 Subject: [PATCH v3 2/4] Add wrappers for some Python APIs MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260521-python-safety-initial-v3-2-d0679c36e499@tromey.com> References: <20260521-python-safety-initial-v3-0-d0679c36e499@tromey.com> In-Reply-To: <20260521-python-safety-initial-v3-0-d0679c36e499@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: 1wQCo2-00000002l41-0YeA 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]:37536 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: MS4xfHdw8XiN7L+7eBK50iqR6DwiwvI00H0upnRYdW2gC23H/5IVVUNgA901JqPRr1Sdgtc/WJqJInX48JmfUunGdWEifDDMfXPCnjEqAc65NEfhbUNktjLV 1JOvcgnJGPYhjih8726EPBFm+Z/FQTNH/LyYzN1P7hgR8xgrvckvR7om9GYXCf+AzKn+ANhZcX2BgXGoexZgVAp4XXrw/ajGG18= 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 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 | 361 +++++++++++++++++++++++++++++++++++++++++++ gdb/python/python-internal.h | 2 + 2 files changed, 363 insertions(+) diff --git a/gdb/python/py-wrappers.h b/gdb/python/py-wrappers.h new file mode 100644 index 00000000000..6c2b5e4d41e --- /dev/null +++ b/gdb/python/py-wrappers.h @@ -0,0 +1,361 @@ +/* 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 ()); + } +}; + +/* Wrapper for PyObject_New. */ +template +gdbpy_ref +gdbpy_new () +{ + gdbpy_ref result (PyObject_New (T, T::corresponding_object_type)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +/* Wrapper for PyBool_FromLong. */ +static inline gdbpy_ref<> +gdbpy_bool_from_long (long value) +{ + /* This cannot fail. */ + return gdbpy_ref<> (PyBool_FromLong (value)); +} + +/* Wrapper for PyBytes_AsString. */ +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; +} + +/* Wrapper for PyBytes_AsStringAndSize. */ +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 (); +} + +/* Wrapper for PyBytes_FromString. */ +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; +} + +/* Wrapper for PyBytes_FromStringAndSize. */ +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; +} + +/* Wrapper for PyBytes_Size. */ +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; +} + +/* Wrapper for PyList_New. */ +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; +} + +/* Wrapper for PyList_Append. */ +static inline void +gdbpy_list_append (gdbpy_borrowed_ref<> list, gdbpy_borrowed_ref<> val) +{ + if (PyList_Append (list, val) < 0) + throw gdb_python_exception (); +} + +/* Wrapper for PyDict_New. */ +static inline gdbpy_ref<> +gdbpy_new_dict () +{ + gdbpy_ref<> result (PyDict_New ()); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +/* Wrapper for PyDict_SetItemString. */ +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 (); +} + +/* Wrapper for PyDict_DelItemString. */ +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 (); +} + +/* Wrapper for PyDict_GetItemWithError. Note that this returns an + optional borrowed reference -- while it will throw an exception on + error, it will return NULL if the key is not in the dictionary. */ +static inline gdbpy_opt_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 && PyErr_Occurred ()) + throw gdb_python_exception (); + return result; +} + +/* Wrapper for PyDict_Keys. */ +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; +} + +/* Wrapper for PyUnicode_FromStringAndSize. */ +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; +} + +/* Wrapper for PyUnicode_FromFormatV. A template function is used to + avoid issues with throwing across va_end. */ +template +gdbpy_ref<> +gdbpy_unicode_from_format (const char *fmt, Arg... args) +{ + gdbpy_ref<> result (PyUnicode_FromFormat (fmt, args...)); + if (result == nullptr) + throw gdb_python_exception (); + return result; +} + +/* Wrapper for PyErr_SetString. This always throws. */ +[[noreturn]] static inline void +gdbpy_err_set_string (gdbpy_borrowed_ref<> type, const char *str) +{ + PyErr_SetString (type, str); + throw gdb_python_exception (); +} + +/* Wrapper for PyErr_Format. This always throws. */ +template +[[noreturn]] void +gdbpy_err_format (gdbpy_borrowed_ref<> type, const char *fmt, Arg... args) +{ + PyErr_Format (type, fmt, args...); + throw gdb_python_exception (); +} + +/* Wrapper for gdb_PyArg_ParseTupleAndKeywords. */ +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, outputs...)) + throw gdb_python_exception (); +} + +/* Wrapper for PyArg_ParseTuple. */ +template +void +gdbpy_arg_parse_tuple (gdbpy_borrowed_ref<> param, const char *format, + Arg... args) +{ + if (!PyArg_ParseTuple (param, format, args...)) + throw gdb_python_exception (); +} + +/* Wrapper for PyLong_AsLong. */ +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; +} + +/* Wrapper for PyTuple_New. */ +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; +} + +/* Wrapper for PyTuple_GetItem. */ +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; +} + +/* Wrapper for PyTuple_SetItem. */ +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 (); +} + +/* Wrapper for PyTuple_Size. */ +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; +} + +/* Wrapper for PySequence_Size. */ +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; +} + +/* Wrapper for PySequence_GetItem. */ +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; +} + +/* Wrapper for PySequence_DelItem. */ +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 (); +} + +/* Wrapper for PySequence_List. */ +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; +} + +/* Wrapper for PySequence_Concat. */ +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 82f3262ae07..3ec75ca08d5 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -1384,4 +1384,6 @@ py_notimplemented () #undef Py_RETURN_FALSE #undef Py_RETURN_NOTIMPLEMENTED +#include "py-wrappers.h" + #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */ -- 2.49.0