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 unsafe { trampoline(|py: Python<'_>| f(py).map(|module: Py| 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 unsafe { trampoline(|py: Python<'_>| 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 unsafe {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 unsafe { 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 unsafe {
150 trampoline_unraisable(
151 |py| {
152 f(py, slf);
153 Ok(())
154 },
155 ctx:std::ptr::null_mut(),
156 )
157 }
158}
159
160// Ipowfunc is a unique case where PyO3 has its own type
161// to workaround a problem on 3.7 (see IPowModulo type definition).
162// Once 3.7 support dropped can just remove this.
163trampoline!(
164 pub fn ipowfunc(
165 arg1: *mut ffi::PyObject,
166 arg2: *mut ffi::PyObject,
167 arg3: IPowModulo,
168 ) -> *mut ffi::PyObject;
169);
170
171/// Implementation of trampoline functions, which sets up a GILPool and calls F.
172///
173/// Panics during execution are trapped so that they don't propagate through any
174/// outer FFI boundary.
175///
176/// The GIL must already be held when this is called.
177#[inline]
178pub(crate) unsafe fn trampoline<F, R>(body: F) -> R
179where
180 F: for<'py> dynFnOnce(Python<'py>) -> PyResult<R> + UnwindSafe,
181 R: PyCallbackOutput,
182{
183 let trap: PanicTrap = PanicTrap::new(msg:"uncaught panic at ffi boundary");
184
185 // SAFETY: This function requires the GIL to already be held.
186 let guard: GILGuard = unsafe { GILGuard::assume() };
187 let py: Python<'_> = guard.python();
188 let out: R = panic_result_into_callback_output(
189 py,
190 panic_result:panic::catch_unwind(move || -> PyResult<_> { body(py) }),
191 );
192 trap.disarm();
193 out
194}
195
196/// Converts the output of std::panic::catch_unwind into a Python function output, either by raising a Python
197/// exception or by unwrapping the contained success output.
198#[inline]
199fn panic_result_into_callback_output<R>(
200 py: Python<'_>,
201 panic_result: Result<PyResult<R>, Box<dyn Any + Send + 'static>>,
202) -> R
203where
204 R: PyCallbackOutput,
205{
206 let py_err: PyErr = match panic_result {
207 Ok(Ok(value: R)) => return value,
208 Ok(Err(py_err: PyErr)) => py_err,
209 Err(payload: Box) => PanicException::from_panic_payload(payload),
210 };
211 py_err.restore(py);
212 R::ERR_VALUE
213}
214
215/// Implementation of trampoline for functions which can't return an error.
216///
217/// Panics during execution are trapped so that they don't propagate through any
218/// outer FFI boundary.
219///
220/// Exceptions produced are sent to `sys.unraisablehook`.
221///
222/// # Safety
223///
224/// - ctx must be either a valid ffi::PyObject or NULL
225/// - The GIL must already be held when this is called.
226#[inline]
227unsafe fn trampoline_unraisable<F>(body: F, ctx: *mut ffi::PyObject)
228where
229 F: for<'py> dynFnOnce(Python<'py>) -> PyResult<()> + UnwindSafe,
230{
231 let trap: PanicTrap = PanicTrap::new(msg:"uncaught panic at ffi boundary");
232
233 // SAFETY: The GIL is already held.
234 let guard: GILGuard = unsafe { GILGuard::assume() };
235 let py: Python<'_> = guard.python();
236
237 if let Err(py_err: PyErr) = panic::catch_unwind(move || body(py))
238 .unwrap_or_else(|payload: Box| Err(PanicException::from_panic_payload(payload)))
239 {
240 py_err.write_unraisable(py, obj:unsafe { ctx.assume_borrowed_or_opt(py) }.as_deref());
241 }
242 trap.disarm();
243}
244