pyo3/err/
err_state.rs

1use crate::{
2    exceptions::{PyBaseException, PyTypeError},
3    ffi,
4    types::{PyTraceback, PyType},
5    Bound, IntoPy, Py, PyAny, PyObject, PyTypeInfo, Python,
6};
7
8pub(crate) struct PyErrStateNormalized {
9    #[cfg(not(Py_3_12))]
10    ptype: Py<PyType>,
11    pub pvalue: Py<PyBaseException>,
12    #[cfg(not(Py_3_12))]
13    ptraceback: Option<Py<PyTraceback>>,
14}
15
16impl PyErrStateNormalized {
17    #[cfg(not(Py_3_12))]
18    pub(crate) fn ptype<'py>(&self, py: Python<'py>) -> Bound<'py, PyType> {
19        self.ptype.bind(py).clone()
20    }
21
22    #[cfg(Py_3_12)]
23    pub(crate) fn ptype<'py>(&self, py: Python<'py>) -> Bound<'py, PyType> {
24        use crate::types::any::PyAnyMethods;
25        self.pvalue.bind(py).get_type()
26    }
27
28    #[cfg(not(Py_3_12))]
29    pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
30        self.ptraceback
31            .as_ref()
32            .map(|traceback| traceback.bind(py).clone())
33    }
34
35    #[cfg(Py_3_12)]
36    pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
37        use crate::ffi_ptr_ext::FfiPtrExt;
38        use crate::types::any::PyAnyMethods;
39        unsafe {
40            ffi::PyException_GetTraceback(self.pvalue.as_ptr())
41                .assume_owned_or_opt(py)
42                .map(|b| b.downcast_into_unchecked())
43        }
44    }
45
46    #[cfg(Py_3_12)]
47    pub(crate) fn take(py: Python<'_>) -> Option<PyErrStateNormalized> {
48        unsafe { Py::from_owned_ptr_or_opt(py, ffi::PyErr_GetRaisedException()) }
49            .map(|pvalue| PyErrStateNormalized { pvalue })
50    }
51
52    #[cfg(not(Py_3_12))]
53    unsafe fn from_normalized_ffi_tuple(
54        py: Python<'_>,
55        ptype: *mut ffi::PyObject,
56        pvalue: *mut ffi::PyObject,
57        ptraceback: *mut ffi::PyObject,
58    ) -> Self {
59        PyErrStateNormalized {
60            ptype: Py::from_owned_ptr_or_opt(py, ptype).expect("Exception type missing"),
61            pvalue: Py::from_owned_ptr_or_opt(py, pvalue).expect("Exception value missing"),
62            ptraceback: Py::from_owned_ptr_or_opt(py, ptraceback),
63        }
64    }
65
66    pub fn clone_ref(&self, py: Python<'_>) -> Self {
67        Self {
68            #[cfg(not(Py_3_12))]
69            ptype: self.ptype.clone_ref(py),
70            pvalue: self.pvalue.clone_ref(py),
71            #[cfg(not(Py_3_12))]
72            ptraceback: self
73                .ptraceback
74                .as_ref()
75                .map(|ptraceback| ptraceback.clone_ref(py)),
76        }
77    }
78}
79
80pub(crate) struct PyErrStateLazyFnOutput {
81    pub(crate) ptype: PyObject,
82    pub(crate) pvalue: PyObject,
83}
84
85pub(crate) type PyErrStateLazyFn =
86    dyn for<'py> FnOnce(Python<'py>) -> PyErrStateLazyFnOutput + Send + Sync;
87
88pub(crate) enum PyErrState {
89    Lazy(Box<PyErrStateLazyFn>),
90    #[cfg(not(Py_3_12))]
91    FfiTuple {
92        ptype: PyObject,
93        pvalue: Option<PyObject>,
94        ptraceback: Option<PyObject>,
95    },
96    Normalized(PyErrStateNormalized),
97}
98
99/// Helper conversion trait that allows to use custom arguments for lazy exception construction.
100pub trait PyErrArguments: Send + Sync {
101    /// Arguments for exception
102    fn arguments(self, py: Python<'_>) -> PyObject;
103}
104
105impl<T> PyErrArguments for T
106where
107    T: IntoPy<PyObject> + Send + Sync,
108{
109    fn arguments(self, py: Python<'_>) -> PyObject {
110        self.into_py(py)
111    }
112}
113
114impl PyErrState {
115    pub(crate) fn lazy(ptype: Py<PyAny>, args: impl PyErrArguments + 'static) -> Self {
116        PyErrState::Lazy(Box::new(move |py| PyErrStateLazyFnOutput {
117            ptype,
118            pvalue: args.arguments(py),
119        }))
120    }
121
122    pub(crate) fn normalized(pvalue: Bound<'_, PyBaseException>) -> Self {
123        #[cfg(not(Py_3_12))]
124        use crate::types::any::PyAnyMethods;
125
126        Self::Normalized(PyErrStateNormalized {
127            #[cfg(not(Py_3_12))]
128            ptype: pvalue.get_type().into(),
129            #[cfg(not(Py_3_12))]
130            ptraceback: unsafe {
131                Py::from_owned_ptr_or_opt(
132                    pvalue.py(),
133                    ffi::PyException_GetTraceback(pvalue.as_ptr()),
134                )
135            },
136            pvalue: pvalue.into(),
137        })
138    }
139
140    pub(crate) fn normalize(self, py: Python<'_>) -> PyErrStateNormalized {
141        match self {
142            #[cfg(not(Py_3_12))]
143            PyErrState::Lazy(lazy) => {
144                let (ptype, pvalue, ptraceback) = lazy_into_normalized_ffi_tuple(py, lazy);
145                unsafe {
146                    PyErrStateNormalized::from_normalized_ffi_tuple(py, ptype, pvalue, ptraceback)
147                }
148            }
149            #[cfg(Py_3_12)]
150            PyErrState::Lazy(lazy) => {
151                // To keep the implementation simple, just write the exception into the interpreter,
152                // which will cause it to be normalized
153                raise_lazy(py, lazy);
154                PyErrStateNormalized::take(py)
155                    .expect("exception missing after writing to the interpreter")
156            }
157            #[cfg(not(Py_3_12))]
158            PyErrState::FfiTuple {
159                ptype,
160                pvalue,
161                ptraceback,
162            } => {
163                let mut ptype = ptype.into_ptr();
164                let mut pvalue = pvalue.map_or(std::ptr::null_mut(), Py::into_ptr);
165                let mut ptraceback = ptraceback.map_or(std::ptr::null_mut(), Py::into_ptr);
166                unsafe {
167                    ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
168                    PyErrStateNormalized::from_normalized_ffi_tuple(py, ptype, pvalue, ptraceback)
169                }
170            }
171            PyErrState::Normalized(normalized) => normalized,
172        }
173    }
174
175    #[cfg(not(Py_3_12))]
176    pub(crate) fn restore(self, py: Python<'_>) {
177        let (ptype, pvalue, ptraceback) = match self {
178            PyErrState::Lazy(lazy) => lazy_into_normalized_ffi_tuple(py, lazy),
179            PyErrState::FfiTuple {
180                ptype,
181                pvalue,
182                ptraceback,
183            } => (
184                ptype.into_ptr(),
185                pvalue.map_or(std::ptr::null_mut(), Py::into_ptr),
186                ptraceback.map_or(std::ptr::null_mut(), Py::into_ptr),
187            ),
188            PyErrState::Normalized(PyErrStateNormalized {
189                ptype,
190                pvalue,
191                ptraceback,
192            }) => (
193                ptype.into_ptr(),
194                pvalue.into_ptr(),
195                ptraceback.map_or(std::ptr::null_mut(), Py::into_ptr),
196            ),
197        };
198        unsafe { ffi::PyErr_Restore(ptype, pvalue, ptraceback) }
199    }
200
201    #[cfg(Py_3_12)]
202    pub(crate) fn restore(self, py: Python<'_>) {
203        match self {
204            PyErrState::Lazy(lazy) => raise_lazy(py, lazy),
205            PyErrState::Normalized(PyErrStateNormalized { pvalue }) => unsafe {
206                ffi::PyErr_SetRaisedException(pvalue.into_ptr())
207            },
208        }
209    }
210}
211
212#[cfg(not(Py_3_12))]
213fn lazy_into_normalized_ffi_tuple(
214    py: Python<'_>,
215    lazy: Box<PyErrStateLazyFn>,
216) -> (*mut ffi::PyObject, *mut ffi::PyObject, *mut ffi::PyObject) {
217    // To be consistent with 3.12 logic, go via raise_lazy, but also then normalize
218    // the resulting exception
219    raise_lazy(py, lazy);
220    let mut ptype = std::ptr::null_mut();
221    let mut pvalue = std::ptr::null_mut();
222    let mut ptraceback = std::ptr::null_mut();
223    unsafe {
224        ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
225        ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
226    }
227    (ptype, pvalue, ptraceback)
228}
229
230/// Raises a "lazy" exception state into the Python interpreter.
231///
232/// In principle this could be split in two; first a function to create an exception
233/// in a normalized state, and then a call to `PyErr_SetRaisedException` to raise it.
234///
235/// This would require either moving some logic from C to Rust, or requesting a new
236/// API in CPython.
237fn raise_lazy(py: Python<'_>, lazy: Box<PyErrStateLazyFn>) {
238    let PyErrStateLazyFnOutput { ptype, pvalue } = lazy(py);
239    unsafe {
240        if ffi::PyExceptionClass_Check(ptype.as_ptr()) == 0 {
241            ffi::PyErr_SetString(
242                PyTypeError::type_object_raw(py).cast(),
243                ffi::c_str!("exceptions must derive from BaseException").as_ptr(),
244            )
245        } else {
246            ffi::PyErr_SetObject(ptype.as_ptr(), pvalue.as_ptr())
247        }
248    }
249}