1 | use crate::{ |
2 | exceptions::{PyBaseException, PyTypeError}, |
3 | ffi, |
4 | types::{PyTraceback, PyType}, |
5 | IntoPy, Py, PyAny, PyObject, PyTypeInfo, Python, |
6 | }; |
7 | |
8 | #[derive (Clone)] |
9 | pub(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 | |
17 | impl 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 | |
61 | pub(crate) struct PyErrStateLazyFnOutput { |
62 | pub(crate) ptype: PyObject, |
63 | pub(crate) pvalue: PyObject, |
64 | } |
65 | |
66 | pub(crate) type PyErrStateLazyFn = |
67 | dyn for<'py> FnOnce(Python<'py>) -> PyErrStateLazyFnOutput + Send + Sync; |
68 | |
69 | pub(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. |
81 | pub trait PyErrArguments: Send + Sync { |
82 | /// Arguments for exception |
83 | fn arguments(self, py: Python<'_>) -> PyObject; |
84 | } |
85 | |
86 | impl<T> PyErrArguments for T |
87 | where |
88 | T: IntoPy<PyObject> + Send + Sync, |
89 | { |
90 | fn arguments(self, py: Python<'_>) -> PyObject { |
91 | self.into_py(py) |
92 | } |
93 | } |
94 | |
95 | impl 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))] |
192 | fn 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. |
216 | fn 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 | |