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