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