From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id af++JKt7B2qu7j0AWB0awg (envelope-from ) for ; Fri, 15 May 2026 16:01:47 -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=qLpfK+gS; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 90FA81E0B1; Fri, 15 May 2026 16:01:47 -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 7D1E91E067 for ; Fri, 15 May 2026 16:01:46 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id A91BC409FCB5 for ; Fri, 15 May 2026 20:01:45 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A91BC409FCB5 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 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 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit 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-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 | 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 */ -- 2.49.0