| 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 | |