From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id gV46LR2VD2o/oA0AWB0awg (envelope-from ) for ; Thu, 21 May 2026 19:28:29 -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=DmL9MJDu; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id B03A31E091; Thu, 21 May 2026 19:28:29 -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 4D86B1E024 for ; Thu, 21 May 2026 19:28:28 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 49EDF48F669D for ; Thu, 21 May 2026 23:28:27 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 49EDF48F669D 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=DmL9MJDu Received: from omta038.useast.a.cloudfilter.net (omta038.useast.a.cloudfilter.net [44.202.169.37]) by sourceware.org (Postfix) with ESMTPS id B244548F90F5 for ; Thu, 21 May 2026 23:27:59 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B244548F90F5 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 B244548F90F5 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=1779406079; cv=none; b=DQKiivG7w8ALCs/NL8xWoYB/ueLDGbn0YZtjQkyd0oihwKZeqqR+eyMnEOxr9VoOWSFWO4pkIRqQwehvMcCqeYqr36dqQ9y/SkiO9pULi7x9QIcZjUb9/pMet89IHkT2XWFjp2LXTINOQueWj6GHt7bNR9c62XBS+95QpAFNGrY= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779406079; c=relaxed/simple; bh=nC/Ibsd2y+3m64luvNAPPdzE0QJlRG/PvL+9Km90BNw=; h=DKIM-Signature:From:Date:Subject:MIME-Version:Message-Id:To; b=FrgIGRhho/WBzp6p7sJaT+ggKpoTfucQ+SsupZ2UZfjXalU1DGEZSO2zSxFY/YNZ5Q8MDDrSVuoBPCAWiGHIqGwtkq5ViIlbUNSd6JBSQT14QiVpxOU/73mefZZb+6M2oMN7mzDYNDsi0wvCCcE0+68zFS/DXZMyMiISuW3Rax0= 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=DmL9MJDu reason="signing key too small" DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B244548F90F5 Received: from eig-obgw-6004b.ext.cloudfilter.net ([10.0.30.210]) by cmsmtp with ESMTPS id Q5OfwuccAKSRRQCo3w0zpK; Thu, 21 May 2026 23:27:59 +0000 Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with ESMTPS id QCo2woDyK7Q5NQCo2wWhGy; Thu, 21 May 2026 23:27:59 +0000 X-Authority-Analysis: v=2.4 cv=UIDdHDfy c=1 sm=1 tr=0 ts=6a0f94ff a=ApxJNpeYhEAb1aAlGBBbmA==:117 a=ApxJNpeYhEAb1aAlGBBbmA==:17 a=IkcTkHD0fZMA:10 a=NGcC8JguVDcA:10 a=ItBw4LHWJt0A:10 a=mDV3o1hIAAAA:8 a=Jkv30pto87dm7PprY3cA: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=MZUY3Q/UPI6WucbNF60KhaNaVtzZiwoawxgLqU/t0Jk=; b=DmL9MJDuC2/TtKzuX5+VMPTM/N R5yFe9m/s1iH+g5fouURxV4joFYc7AD4fdbBgxJL+bKJ6Ox1rvN8pywHwS2KB02A/vBS5MFOQKmEh cC6w1j0koT12Ly2NHSL6QT5kv; 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-1mGL; Thu, 21 May 2026 17:27:58 -0600 From: Tom Tromey Date: Thu, 21 May 2026 17:27:54 -0600 Subject: [PATCH v3 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: <20260521-python-safety-initial-v3-3-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-1mGL 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: 6 X-Org: HG=bhshared;ORG=bluehost; X-Source-Cap: ZWx5bnJvYmk7ZWx5bnJvYmk7Ym94NTM3OS5ibHVlaG9zdC5jb20= X-Local-Domain: yes X-CMAE-Envelope: MS4xfGGA3wSDPn54GPteP3TDVclcRQuj8nVdv3d3mWMbQZCnaIHoOqcq5nSMWpvwczx20/Ct/x8EnDPMsDFIdK59vu7IiOwru7j6ItdxW86KBugmX4nq5/ce ZcJC97iR0yBodAwa/g6tH9igoaPtlJQRzyiG9y9dmHjudIaDElVKXrYLx2B3gtjQC7ZSBhibD3xgBGN2VYlDZJG3pZawFdL0VPA= 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 that while this patch is usable as-is, it is not 100% complete, in sense that there is still future work to do when converting other parts of the gdb Python code. For instance, there should be one more wrapper for case where a method takes a single argument (though we probably cannot use METH_O unfortunately). --- gdb/python/py-safety.h | 346 +++++++++++++++++++++++++++++++++++++++++++ gdb/python/python-internal.h | 1 + 2 files changed, 347 insertions(+) diff --git a/gdb/python/py-safety.h b/gdb/python/py-safety.h new file mode 100644 index 00000000000..3294f38c8b6 --- /dev/null +++ b/gdb/python/py-safety.h @@ -0,0 +1,346 @@ +/* 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. These are a detail of the method-wrapping + code. Note that unlike the "safe" wrapper APIs that are commonly + used in gdb, these are all Python-facing and will return NULL on + error. */ + +static inline PyObject * +to_python (bool value) +{ + 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 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 calls the + method METH. Any exceptions are caught and converted, and the + return value of METH 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); + } +} + +/* A variant of wrapped_method that accepts a const method. */ +template +PyObject * +wrapped_method (Ret (Class::*meth) (Args...) const, 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 (const char *name, const char *doc) +{ + using namespace safety_details; + return { + name, + [] (PyObject *self, PyObject *args) -> PyObject * + { + return wrapped_method (M, static_cast (self)); + }, + METH_NOARGS, + doc, + }; +} + +/* 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 (const char *name, const char *doc) +{ + using namespace safety_details; + return { + name, + (PyCFunction) fn_wrapper, + /* gdb's rule is that varargs should also use keywords. */ + METH_VARARGS | METH_KEYWORDS, + doc, + }; +} + +/* 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 (const char *name, const char *doc) +{ + using namespace safety_details; + return { + name, + (PyCFunction) varargs_wrapper, + /* gdb's rule is that varargs should also use keywords. */ + METH_VARARGS | METH_KEYWORDS, + doc, + }; +} + +/* A function that wraps a "repr" or "str" method. */ +template +PyObject * +wrap_tp_callback (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 3ec75ca08d5..c7a496899e0 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -1385,5 +1385,6 @@ py_notimplemented () #undef Py_RETURN_NOTIMPLEMENTED #include "py-wrappers.h" +#include "py-safety.h" #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */ -- 2.49.0