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::internal::state::AttachGuard;
13use crate::{
14    ffi, ffi_ptr_ext::FfiPtrExt, impl_::callback::PyCallbackOutput, impl_::panic::PanicTrap,
15    impl_::pymethods::IPowModulo, panic::PanicException, types::PyModule, Bound, PyResult, Python,
16};
17
18#[inline]
19pub unsafe fn module_exec(
20    module: *mut ffi::PyObject,
21    f: for<'a, 'py> fn(&'a Bound<'py, PyModule>) -> PyResult<()>,
22) -> c_int {
23    unsafe {
24        trampoline(|py| {
25            let module = module.assume_borrowed_or_err(py)?.cast::<PyModule>()?;
26            f(&module)?;
27            Ok(0)
28        })
29    }
30}
31
32/// A workaround for Rust not allowing function pointers as const generics: define a trait which
33/// has a constant function pointer.
34pub trait MethodDef<T> {
35    const METH: T;
36}
37
38/// Generates an implementation of `MethodDef` and then returns the trampoline function
39/// specialized to call the provided method.
40///
41/// Note that the functions returned by this macro are instantiations of generic functions. Code
42/// should not depend on these function pointers being stable (e.g. across compilation units);
43/// the intended purpose of these is to create function pointers which can be passed to the Python
44/// C-API to correctly wrap Rust functions.
45#[macro_export]
46#[doc(hidden)]
47macro_rules! get_trampoline_function {
48    ($trampoline:ident, $f:path) => {{
49        struct Def;
50        impl $crate::impl_::trampoline::MethodDef<$crate::impl_::trampoline::$trampoline::Func> for Def {
51            const METH: $crate::impl_::trampoline::$trampoline::Func = $f;
52        }
53        $crate::impl_::trampoline::$trampoline::<Def>
54    }};
55}
56
57pub use get_trampoline_function;
58
59/// Macro to define a trampoline function for a given function signature.
60///
61/// This macro generates:
62/// 1. An external "C" function that serves as the trampoline, generic on a specific function pointer.
63/// 2. A companion module containing a non-generic inner function, and the function pointer type.
64macro_rules! trampoline {
65    (pub fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty;) => {
66        /// External symbol called by Python, which calls the provided Rust function.
67        ///
68        /// The Rust function is supplied via the generic parameter `Meth`.
69        pub unsafe extern "C" fn $name<Meth: MethodDef<$name::Func>>(
70            $($arg_names: $arg_types,)*
71        ) -> $ret {
72            unsafe { $name::inner($($arg_names),*, Meth::METH) }
73        }
74
75        /// Companion module contains the function pointer type.
76        pub mod $name {
77            use super::*;
78
79            /// Non-generic inner function to ensure only one trampoline instantiated
80            #[inline]
81            pub(crate) unsafe fn inner($($arg_names: $arg_types),*, f: $name::Func) -> $ret {
82                unsafe { trampoline(|py| f(py, $($arg_names,)*)) }
83            }
84
85            /// The type of the function pointer for this trampoline.
86            pub type Func = for<'py> unsafe fn (Python<'py>, $($arg_types),*) -> PyResult<$ret>;
87        }
88    }
89}
90
91/// Noargs is a special case where the `_args` parameter is unused and not passed to the inner `Func`.
92pub unsafe extern "C" fn noargs<Meth: MethodDef<noargs::Func>>(
93    slf: *mut ffi::PyObject,
94    _args: *mut ffi::PyObject, // unused and value not defined
95) -> *mut ffi::PyObject {
96    unsafe { noargs::inner(slf, Meth::METH) }
97}
98
99pub mod noargs {
100    use super::*;
101
102    #[inline]
103    pub(crate) unsafe fn inner(slf: *mut ffi::PyObject, f: Func) -> *mut ffi::PyObject {
104        unsafe { trampoline(|py| f(py, slf)) }
105    }
106
107    pub type Func = unsafe fn(Python<'_>, *mut ffi::PyObject) -> PyResult<*mut ffi::PyObject>;
108}
109
110macro_rules! trampolines {
111    ($(pub fn $name:ident($($arg_names:ident: $arg_types:ty),* $(,)?) -> $ret:ty);* ;) => {
112        $(trampoline!(pub fn $name($($arg_names: $arg_types),*) -> $ret;));*;
113    }
114}
115
116trampolines!(
117    pub fn fastcall_cfunction_with_keywords(
118        slf: *mut ffi::PyObject,
119        args: *const *mut ffi::PyObject,
120        nargs: ffi::Py_ssize_t,
121        kwnames: *mut ffi::PyObject,
122    ) -> *mut ffi::PyObject;
123
124    pub fn cfunction_with_keywords(
125        slf: *mut ffi::PyObject,
126        args: *mut ffi::PyObject,
127        kwargs: *mut ffi::PyObject,
128    ) -> *mut ffi::PyObject;
129);
130
131// Trampolines used by slot methods
132trampolines!(
133    pub fn getattrofunc(slf: *mut ffi::PyObject, attr: *mut ffi::PyObject) -> *mut ffi::PyObject;
134
135    pub fn setattrofunc(
136        slf: *mut ffi::PyObject,
137        attr: *mut ffi::PyObject,
138        value: *mut ffi::PyObject,
139    ) -> c_int;
140
141    pub fn binaryfunc(slf: *mut ffi::PyObject, arg1: *mut ffi::PyObject) -> *mut ffi::PyObject;
142
143    pub fn descrgetfunc(
144        slf: *mut ffi::PyObject,
145        arg1: *mut ffi::PyObject,
146        arg2: *mut ffi::PyObject,
147    ) -> *mut ffi::PyObject;
148
149    pub fn getiterfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
150
151    pub fn hashfunc(slf: *mut ffi::PyObject) -> ffi::Py_hash_t;
152
153    pub fn inquiry(slf: *mut ffi::PyObject) -> c_int;
154
155    pub fn iternextfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
156
157    pub fn lenfunc(slf: *mut ffi::PyObject) -> ffi::Py_ssize_t;
158
159    pub fn newfunc(
160        subtype: *mut ffi::PyTypeObject,
161        args: *mut ffi::PyObject,
162        kwargs: *mut ffi::PyObject,
163    ) -> *mut ffi::PyObject;
164
165    pub fn initproc(
166        slf: *mut ffi::PyObject,
167        args: *mut ffi::PyObject,
168        kwargs: *mut ffi::PyObject,
169    ) -> c_int;
170
171    pub fn objobjproc(slf: *mut ffi::PyObject, arg1: *mut ffi::PyObject) -> c_int;
172
173    pub fn reprfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
174
175    pub fn richcmpfunc(
176        slf: *mut ffi::PyObject,
177        other: *mut ffi::PyObject,
178        op: c_int,
179    ) -> *mut ffi::PyObject;
180
181    pub fn ssizeargfunc(arg1: *mut ffi::PyObject, arg2: ffi::Py_ssize_t) -> *mut ffi::PyObject;
182
183    pub fn ternaryfunc(
184        slf: *mut ffi::PyObject,
185        arg1: *mut ffi::PyObject,
186        arg2: *mut ffi::PyObject,
187    ) -> *mut ffi::PyObject;
188
189    pub fn unaryfunc(slf: *mut ffi::PyObject) -> *mut ffi::PyObject;
190);
191
192#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
193trampoline! {
194    pub fn getbufferproc(slf: *mut ffi::PyObject, buf: *mut ffi::Py_buffer, flags: c_int) -> c_int;
195}
196
197/// Releasebufferproc is a special case where the function cannot return an error,
198/// so we use trampoline_unraisable.
199#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
200pub unsafe extern "C" fn releasebufferproc<Meth: MethodDef<releasebufferproc::Func>>(
201    slf: *mut ffi::PyObject,
202    buf: *mut ffi::Py_buffer,
203) {
204    unsafe { releasebufferproc::inner(slf, buf, Meth::METH) }
205}
206
207#[cfg(any(not(Py_LIMITED_API), Py_3_11))]
208pub mod releasebufferproc {
209    use super::*;
210
211    #[inline]
212    pub(crate) unsafe fn inner(slf: *mut ffi::PyObject, buf: *mut ffi::Py_buffer, f: Func) {
213        unsafe { trampoline_unraisable(|py| f(py, slf, buf), slf) }
214    }
215
216    pub type Func = unsafe fn(Python<'_>, *mut ffi::PyObject, *mut ffi::Py_buffer) -> PyResult<()>;
217}
218
219#[inline]
220pub(crate) unsafe fn dealloc(
221    slf: *mut ffi::PyObject,
222    f: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> (),
223) {
224    // After calling tp_dealloc the object is no longer valid,
225    // so pass null_mut() to the context.
226    //
227    // (Note that we don't allow the implementation `f` to fail.)
228    unsafe {
229        trampoline_unraisable(
230            |py| {
231                f(py, slf);
232                Ok(())
233            },
234            std::ptr::null_mut(),
235        )
236    }
237}
238
239// Ipowfunc is a unique case where PyO3 has its own type
240// to workaround a problem on 3.7 (see IPowModulo type definition).
241// Once 3.7 support dropped can just remove this.
242trampoline!(
243    pub fn ipowfunc(
244        arg1: *mut ffi::PyObject,
245        arg2: *mut ffi::PyObject,
246        arg3: IPowModulo,
247    ) -> *mut ffi::PyObject;
248);
249
250/// Implementation of trampoline functions, which sets up an AttachGuard and calls F.
251///
252/// Panics during execution are trapped so that they don't propagate through any
253/// outer FFI boundary.
254///
255/// The thread must already be attached to the interpreter when this is called.
256#[inline]
257pub(crate) unsafe fn trampoline<F, R>(body: F) -> R
258where
259    F: for<'py> FnOnce(Python<'py>) -> PyResult<R> + UnwindSafe,
260    R: PyCallbackOutput,
261{
262    let trap = PanicTrap::new("uncaught panic at ffi boundary");
263
264    // SAFETY: This function requires the thread to already be attached.
265    let guard = unsafe { AttachGuard::assume() };
266    let py = guard.python();
267    let out = panic_result_into_callback_output(
268        py,
269        panic::catch_unwind(move || -> PyResult<_> { body(py) }),
270    );
271    trap.disarm();
272    out
273}
274
275/// Converts the output of std::panic::catch_unwind into a Python function output, either by raising a Python
276/// exception or by unwrapping the contained success output.
277#[inline]
278fn panic_result_into_callback_output<R>(
279    py: Python<'_>,
280    panic_result: Result<PyResult<R>, Box<dyn Any + Send + 'static>>,
281) -> R
282where
283    R: PyCallbackOutput,
284{
285    let py_err = match panic_result {
286        Ok(Ok(value)) => return value,
287        Ok(Err(py_err)) => py_err,
288        Err(payload) => PanicException::from_panic_payload(payload),
289    };
290    py_err.restore(py);
291    R::ERR_VALUE
292}
293
294/// Implementation of trampoline for functions which can't return an error.
295///
296/// Panics during execution are trapped so that they don't propagate through any
297/// outer FFI boundary.
298///
299/// Exceptions produced are sent to `sys.unraisablehook`.
300///
301/// # Safety
302///
303/// - ctx must be either a valid ffi::PyObject or NULL
304/// - The thread must be attached to the interpreter when this is called.
305#[inline]
306unsafe fn trampoline_unraisable<F>(body: F, ctx: *mut ffi::PyObject)
307where
308    F: for<'py> FnOnce(Python<'py>) -> PyResult<()> + UnwindSafe,
309{
310    let trap = PanicTrap::new("uncaught panic at ffi boundary");
311
312    // SAFETY: Thread is known to be attached.
313    let guard = unsafe { AttachGuard::assume() };
314    let py = guard.python();
315
316    if let Err(py_err) = panic::catch_unwind(move || body(py))
317        .unwrap_or_else(|payload| Err(PanicException::from_panic_payload(payload)))
318    {
319        py_err.write_unraisable(py, unsafe { ctx.assume_borrowed_or_opt(py) }.as_deref());
320    }
321    trap.disarm();
322}