pyo3/impl_/
trampoline.rs

1//! Trampolines for various pyfunction and pymethod implementations.
2//!
3//! They exist to monomorphise std::panic::catch_unwind once into PyO3, rather than inline in every
4//! function, thus saving a huge amount of compile-time complexity.
5
6use std::{
7    any::Any,
8    os::raw::c_int,
9    panic::{self, UnwindSafe},
10};
11
12use crate::gil::GILGuard;
13use crate::{
14    ffi, ffi_ptr_ext::FfiPtrExt, impl_::callback::PyCallbackOutput, impl_::panic::PanicTrap,
15    impl_::pymethods::IPowModulo, panic::PanicException, types::PyModule, Py, PyResult, Python,
16};
17
18#[inline]
19pub unsafe fn module_init(
20    f: for<'py> unsafe fn(Python<'py>) -> PyResult<Py<PyModule>>,
21) -> *mut ffi::PyObject {
22    trampoline(|py| f(py).map(|module| module.into_ptr()))
23}
24
25#[inline]
26#[allow(clippy::used_underscore_binding)]
27pub unsafe fn noargs(
28    slf: *mut ffi::PyObject,
29    _args: *mut ffi::PyObject,
30    f: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<*mut ffi::PyObject>,
31) -> *mut ffi::PyObject {
32    #[cfg(not(GraalPy))] // this is not specified and GraalPy does not pass null here
33    debug_assert!(_args.is_null());
34    trampoline(|py| f(py, slf))
35}
36
37macro_rules! trampoline {
38    (pub fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty;) => {
39        #[inline]
40        pub unsafe fn $name(
41            $($arg_names: $arg_types,)*
42            f: for<'py> unsafe fn (Python<'py>, $($arg_types),*) -> PyResult<$ret>,
43        ) -> $ret {
44            trampoline(|py| f(py, $($arg_names,)*))
45        }
46    }
47}
48
49macro_rules! trampolines {
50    ($(pub fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty);* ;) => {
51        $(trampoline!(pub fn $name($($arg_names: $arg_types),*) -> $ret;));*;
52    }
53}
54
55trampolines!(
56    pub fn fastcall_with_keywords(
57        slf: *mut ffi::PyObject,
58        args: *const *mut ffi::PyObject,
59        nargs: ffi::Py_ssize_t,
60        kwnames: *mut ffi::PyObject,
61    ) -> *mut ffi::PyObject;
62
63    pub fn cfunction_with_keywords(
64        slf: *mut ffi::PyObject,
65        args: *mut ffi::PyObject,
66        kwargs: *mut ffi::PyObject,
67    ) -> *mut ffi::PyObject;
68);
69
70// Trampolines used by slot methods
71trampolines!(
72    pub fn getattrofunc(slf: *mut ffi::PyObject, attr: *mut ffi::PyObject) -> *mut ffi::PyObject;
73
74    pub fn setattrofunc(
75        slf: *mut ffi::PyObject,
76        attr: *mut ffi::PyObject,
77        value: *mut ffi::PyObject,
78    ) -> c_int;
79
80    pub fn binaryfunc(slf: *mut ffi::PyObject, arg1: *mut ffi::PyObject) -> *mut ffi::PyObject;
81
82    pub fn descrgetfunc(
83        slf: *mut ffi::PyObject,
84        arg1: *mut ffi::PyObject,
85        arg2: *mut ffi::PyObject,
86    ) -> *mut ffi::PyObject;
87
88    pub fn getiterfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
89
90    pub fn hashfunc(slf: *mut ffi::PyObject) -> ffi::Py_hash_t;
91
92    pub fn inquiry(slf: *mut ffi::PyObject) -> c_int;
93
94    pub fn iternextfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
95
96    pub fn lenfunc(slf: *mut ffi::PyObject) -> ffi::Py_ssize_t;
97
98    pub fn newfunc(
99        subtype: *mut ffi::PyTypeObject,
100        args: *mut ffi::PyObject,
101        kwargs: *mut ffi::PyObject,
102    ) -> *mut ffi::PyObject;
103
104    pub fn objobjproc(slf: *mut ffi::PyObject, arg1: *mut ffi::PyObject) -> c_int;
105
106    pub fn reprfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
107
108    pub fn richcmpfunc(
109        slf: *mut ffi::PyObject,
110        other: *mut ffi::PyObject,
111        op: c_int,
112    ) -> *mut ffi::PyObject;
113
114    pub fn ssizeargfunc(arg1: *mut ffi::PyObject, arg2: ffi::Py_ssize_t) -> *mut ffi::PyObject;
115
116    pub fn ternaryfunc(
117        slf: *mut ffi::PyObject,
118        arg1: *mut ffi::PyObject,
119        arg2: *mut ffi::PyObject,
120    ) -> *mut ffi::PyObject;
121
122    pub fn unaryfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
123);
124
125#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
126trampoline! {
127    pub fn getbufferproc(slf: *mut ffi::PyObject, buf: *mut ffi::Py_buffer, flags: c_int) -> c_int;
128}
129
130#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
131#[inline]
132pub unsafe fn releasebufferproc(
133    slf: *mut ffi::PyObject,
134    buf: *mut ffi::Py_buffer,
135    f: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject, *mut ffi::Py_buffer) -> PyResult<()>,
136) {
137    trampoline_unraisable(|py| f(py, slf, buf), slf)
138}
139
140#[inline]
141pub(crate) unsafe fn dealloc(
142    slf: *mut ffi::PyObject,
143    f: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> (),
144) {
145    // After calling tp_dealloc the object is no longer valid,
146    // so pass null_mut() to the context.
147    //
148    // (Note that we don't allow the implementation `f` to fail.)
149    trampoline_unraisable(
150        |py| {
151            f(py, slf);
152            Ok(())
153        },
154        std::ptr::null_mut(),
155    )
156}
157
158// Ipowfunc is a unique case where PyO3 has its own type
159// to workaround a problem on 3.7 (see IPowModulo type definition).
160// Once 3.7 support dropped can just remove this.
161trampoline!(
162    pub fn ipowfunc(
163        arg1: *mut ffi::PyObject,
164        arg2: *mut ffi::PyObject,
165        arg3: IPowModulo,
166    ) -> *mut ffi::PyObject;
167);
168
169/// Implementation of trampoline functions, which sets up a GILPool and calls F.
170///
171/// Panics during execution are trapped so that they don't propagate through any
172/// outer FFI boundary.
173///
174/// The GIL must already be held when this is called.
175#[inline]
176pub(crate) unsafe fn trampoline<F, R>(body: F) -> R
177where
178    F: for<'py> FnOnce(Python<'py>) -> PyResult<R> + UnwindSafe,
179    R: PyCallbackOutput,
180{
181    let trap = PanicTrap::new("uncaught panic at ffi boundary");
182
183    // SAFETY: This function requires the GIL to already be held.
184    let guard = GILGuard::assume();
185    let py = guard.python();
186    let out = panic_result_into_callback_output(
187        py,
188        panic::catch_unwind(move || -> PyResult<_> { body(py) }),
189    );
190    trap.disarm();
191    out
192}
193
194/// Converts the output of std::panic::catch_unwind into a Python function output, either by raising a Python
195/// exception or by unwrapping the contained success output.
196#[inline]
197fn panic_result_into_callback_output<R>(
198    py: Python<'_>,
199    panic_result: Result<PyResult<R>, Box<dyn Any + Send + 'static>>,
200) -> R
201where
202    R: PyCallbackOutput,
203{
204    let py_err = match panic_result {
205        Ok(Ok(value)) => return value,
206        Ok(Err(py_err)) => py_err,
207        Err(payload) => PanicException::from_panic_payload(payload),
208    };
209    py_err.restore(py);
210    R::ERR_VALUE
211}
212
213/// Implementation of trampoline for functions which can't return an error.
214///
215/// Panics during execution are trapped so that they don't propagate through any
216/// outer FFI boundary.
217///
218/// Exceptions produced are sent to `sys.unraisablehook`.
219///
220/// # Safety
221///
222/// - ctx must be either a valid ffi::PyObject or NULL
223/// - The GIL must already be held when this is called.
224#[inline]
225unsafe fn trampoline_unraisable<F>(body: F, ctx: *mut ffi::PyObject)
226where
227    F: for<'py> FnOnce(Python<'py>) -> PyResult<()> + UnwindSafe,
228{
229    let trap = PanicTrap::new("uncaught panic at ffi boundary");
230
231    // SAFETY: The GIL is already held.
232    let guard = GILGuard::assume();
233    let py = guard.python();
234
235    if let Err(py_err) = panic::catch_unwind(move || body(py))
236        .unwrap_or_else(|payload| Err(PanicException::from_panic_payload(payload)))
237    {
238        py_err.write_unraisable(py, ctx.assume_borrowed_or_opt(py).as_deref());
239    }
240    trap.disarm();
241}