From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id aXTTE+tlB2qUzT0AWB0awg (envelope-from ) for ; Fri, 15 May 2026 14:28:59 -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=Zqc8oumJ; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 4C6351E0B1; Fri, 15 May 2026 14:28:59 -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 615771E067 for ; Fri, 15 May 2026 14:28:58 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 785DA409FCBB for ; Fri, 15 May 2026 18:28:57 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 785DA409FCBB 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=Zqc8oumJ Received: from omta40.uswest2.a.cloudfilter.net (omta40.uswest2.a.cloudfilter.net [35.89.44.39]) by sourceware.org (Postfix) with ESMTPS id 9C38F4B9DB45 for ; Fri, 15 May 2026 18:23:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 9C38F4B9DB45 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 9C38F4B9DB45 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=1778869404; cv=none; b=d5bVDkr3/SPmp81RTpv+utkj571SHnNLroY0x0T5Sgxz8srMFjWe73oHGkFwz19yqa1W3DPa9kt1IVWbzr041YMnkhEeAL2bjXzH+mTH/ENB/u29LX0muUbYQf3qsUx6nX4ThHaG1eKTugvS+4IssQdfTcCOeJdxybawP7srl/8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778869404; c=relaxed/simple; bh=8E8urD6gfRv4CfwpHoXCp0DiU4bu9sDCPD3agvGB2AI=; h=DKIM-Signature:From:Date:Subject:MIME-Version:Message-Id:To; b=sKqmJuyVkVgr3/xnTELJqeJhAGAOWvVfMum4CHDGRuEpYEXkkwK8X8cCH9cVhINHPZyysvu0mlzV5eYsUYmlR9nTE8QjqHZWg3wYIo2oyyTS8CAon3z7CAFaSkM5tlJT8VNRp81BaWmLFqd9ljmAyjJlQ/qqwe2SZRaWr4Ml91w= 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=Zqc8oumJ reason="signing key too small" DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9C38F4B9DB45 Received: from eig-obgw-5002b.ext.cloudfilter.net ([10.0.29.226]) by cmsmtp with ESMTPS id NuxHwBSWCshqQNxBzwxoYu; Fri, 15 May 2026 18:23:23 +0000 Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with ESMTPS id NxBywLHRkZYlBNxBywVz9x; Fri, 15 May 2026 18:23:23 +0000 X-Authority-Analysis: v=2.4 cv=MJRgmNZl c=1 sm=1 tr=0 ts=6a07649b 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=tTm70WF/RZKi8sm0L22/RkmzFLhW2drG1ZO1+NLU/Kc=; b=Zqc8oumJftH8SOUbegjm681glS lGM0lT5pO5fifxm97x98zrAPjCgcHHMvEM5agwmwZHc0h2B3gpGNWoPOSPpi0aDpZSVYqvLbWGrx6 SPOA7OlR1FQk4aDkMllfja94o; Received: from 75-166-225-82.hlrn.qwest.net ([75.166.225.82]:46584 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 1wNxBy-00000002ZaG-0tUV; Fri, 15 May 2026 12:23:22 -0600 From: Tom Tromey Date: Fri, 15 May 2026 12:23:21 -0600 Subject: [PATCH 3/4] Add wrappers for Python implementation functions and methods MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260515-python-safety-initial-v1-3-8f155338df57@tromey.com> References: <20260515-python-safety-initial-v1-0-8f155338df57@tromey.com> In-Reply-To: <20260515-python-safety-initial-v1-0-8f155338df57@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: 1wNxBy-00000002ZaG-0tUV 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]:46584 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: MS4xfGd9je91a5L80/NKkYnkX0Dx3GMqBnFlccZ2Bm29oRwSIgMf1d1TgHhQc03/lTD8NVQytttb6zqKce72gb4UUEBCLRzB52jR2lO+o7AMRaUPKmhl0fs1 nGA+KUClFVDV6OncCCEOFMGwZBSeguJWZTBiYG0EJEMmHizx2mSB9Z21zIicJ3gv3DCL9EWBosPwErs8i89ksvP/Jz4bo3J3Eu0= 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 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 | 321 +++++++++++++++++++++++++++++++++++++++++++ gdb/python/python-internal.h | 1 + 2 files changed, 322 insertions(+) diff --git a/gdb/python/py-safety.h b/gdb/python/py-safety.h new file mode 100644 index 00000000000..d84358b72b4 --- /dev/null +++ b/gdb/python/py-safety.h @@ -0,0 +1,321 @@ +/* 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) +{ + if (value) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +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) + Py_RETURN_NONE; + 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) + Py_RETURN_NONE; + 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...); + Py_RETURN_NONE; + } + 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...); + Py_RETURN_NONE; + } + 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 8921854a120..ca667681cb7 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -1344,5 +1344,6 @@ extern int eval_python_command (const char *command, int start_symbol, const char *filename = nullptr); #include "py-wrappers.h" +#include "py-safety.h" #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */ -- 2.49.0