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 | |
6 | use std::{ |
7 | any::Any, |
8 | os::raw::c_int, |
9 | panic::{self, UnwindSafe}, |
10 | }; |
11 | |
12 | use crate::gil::GILGuard; |
13 | use 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 ] |
19 | pub 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)] |
27 | pub 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 | |
37 | macro_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 | |
49 | macro_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 | |
55 | trampolines!( |
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 |
71 | trampolines!( |
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))] |
126 | trampoline! { |
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 ] |
132 | pub 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 ] |
141 | pub(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. |
163 | trampoline!( |
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 ] |
178 | pub(crate) unsafe fn trampoline<F, R>(body: F) -> R |
179 | where |
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 ] |
199 | fn panic_result_into_callback_output<R>( |
200 | py: Python<'_>, |
201 | panic_result: Result<PyResult<R>, Box<dyn Any + Send + 'static>>, |
202 | ) -> R |
203 | where |
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 ] |
227 | unsafe fn trampoline_unraisable<F>(body: F, ctx: *mut ffi::PyObject) |
228 | where |
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 | |