From: Tom Tromey Date: Sun, 22 Feb 2026 19:29:34 +0000 (-0700) Subject: Add wrappers for Python implementation functions and methods X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fcf80ac34ba6d06ca45407869837187e4f900a4a;p=thirdparty%2Fbinutils-gdb.git Add wrappers for Python implementation functions and methods 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). Acked-By: Tom de Vries --- 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 24540046e02..0793c7fa2af 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -1388,5 +1388,6 @@ py_notimplemented () #undef Py_RETURN_NOTIMPLEMENTED #include "py-wrappers.h" +#include "py-safety.h" #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */