Skip to content

Commit ab8ef86

Browse files
committed
eagerly normalize fetched exception on Python 3.11 and older
1 parent 5266a72 commit ab8ef86

File tree

3 files changed

+50
-102
lines changed

3 files changed

+50
-102
lines changed

newsfragments/4655.changed.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Eagerly normalize exceptions in `PyErr::take()` and `PyErr::fetch()` on Python 3.11 and older.

src/err/err_state.rs

+49-48
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use std::cell::UnsafeCell;
33
use crate::{
44
exceptions::{PyBaseException, PyTypeError},
55
ffi,
6-
types::{PyTraceback, PyType},
6+
ffi_ptr_ext::FfiPtrExt,
7+
types::{PyAnyMethods, PyTraceback, PyType},
78
Bound, Py, PyAny, PyErrArguments, PyObject, PyTypeInfo, Python,
89
};
910

@@ -34,19 +35,6 @@ impl PyErrState {
3435
})))
3536
}
3637

37-
#[cfg(not(Py_3_12))]
38-
pub(crate) fn ffi_tuple(
39-
ptype: PyObject,
40-
pvalue: Option<PyObject>,
41-
ptraceback: Option<PyObject>,
42-
) -> Self {
43-
Self::from_inner(PyErrStateInner::FfiTuple {
44-
ptype,
45-
pvalue,
46-
ptraceback,
47-
})
48-
}
49-
5038
pub(crate) fn normalized(normalized: PyErrStateNormalized) -> Self {
5139
Self::from_inner(PyErrStateInner::Normalized(normalized))
5240
}
@@ -151,19 +139,61 @@ impl PyErrStateNormalized {
151139

152140
#[cfg(Py_3_12)]
153141
pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
154-
use crate::ffi_ptr_ext::FfiPtrExt;
155-
use crate::types::any::PyAnyMethods;
156142
unsafe {
157143
ffi::PyException_GetTraceback(self.pvalue.as_ptr())
158144
.assume_owned_or_opt(py)
159145
.map(|b| b.downcast_into_unchecked())
160146
}
161147
}
162148

163-
#[cfg(Py_3_12)]
164149
pub(crate) fn take(py: Python<'_>) -> Option<PyErrStateNormalized> {
165-
unsafe { Py::from_owned_ptr_or_opt(py, ffi::PyErr_GetRaisedException()) }
166-
.map(|pvalue| PyErrStateNormalized { pvalue })
150+
#[cfg(Py_3_12)]
151+
{
152+
// Safety: PyErr_GetRaisedException can be called when attached to Python and
153+
// returns either NULL or an owned reference.
154+
unsafe { ffi::PyErr_GetRaisedException().assume_owned_or_opt(py) }.map(|pvalue| {
155+
PyErrStateNormalized {
156+
// Safety: PyErr_GetRaisedException returns a valid exception type.
157+
pvalue: unsafe { pvalue.downcast_into_unchecked() }.unbind(),
158+
}
159+
})
160+
}
161+
162+
#[cfg(not(Py_3_12))]
163+
{
164+
let (ptype, pvalue, ptraceback) = unsafe {
165+
let mut ptype: *mut ffi::PyObject = std::ptr::null_mut();
166+
let mut pvalue: *mut ffi::PyObject = std::ptr::null_mut();
167+
let mut ptraceback: *mut ffi::PyObject = std::ptr::null_mut();
168+
169+
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
170+
171+
// Ensure that the exception coming from the interpreter is normalized.
172+
if !ptype.is_null() {
173+
ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
174+
}
175+
176+
// Safety: PyErr_NormalizeException will have produced up to three owned
177+
// references of the correct types.
178+
(
179+
ptype
180+
.assume_owned_or_opt(py)
181+
.map(|b| b.downcast_into_unchecked()),
182+
pvalue
183+
.assume_owned_or_opt(py)
184+
.map(|b| b.downcast_into_unchecked()),
185+
ptraceback
186+
.assume_owned_or_opt(py)
187+
.map(|b| b.downcast_into_unchecked()),
188+
)
189+
};
190+
191+
ptype.map(|ptype| PyErrStateNormalized {
192+
ptype: ptype.unbind(),
193+
pvalue: pvalue.expect("normalized exception value missing").unbind(),
194+
ptraceback: ptraceback.map(Bound::unbind),
195+
})
196+
}
167197
}
168198

169199
#[cfg(not(Py_3_12))]
@@ -204,12 +234,6 @@ pub(crate) type PyErrStateLazyFn =
204234

205235
enum PyErrStateInner {
206236
Lazy(Box<PyErrStateLazyFn>),
207-
#[cfg(not(Py_3_12))]
208-
FfiTuple {
209-
ptype: PyObject,
210-
pvalue: Option<PyObject>,
211-
ptraceback: Option<PyObject>,
212-
},
213237
Normalized(PyErrStateNormalized),
214238
}
215239

@@ -231,20 +255,6 @@ impl PyErrStateInner {
231255
PyErrStateNormalized::take(py)
232256
.expect("exception missing after writing to the interpreter")
233257
}
234-
#[cfg(not(Py_3_12))]
235-
PyErrStateInner::FfiTuple {
236-
ptype,
237-
pvalue,
238-
ptraceback,
239-
} => {
240-
let mut ptype = ptype.into_ptr();
241-
let mut pvalue = pvalue.map_or(std::ptr::null_mut(), Py::into_ptr);
242-
let mut ptraceback = ptraceback.map_or(std::ptr::null_mut(), Py::into_ptr);
243-
unsafe {
244-
ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
245-
PyErrStateNormalized::from_normalized_ffi_tuple(py, ptype, pvalue, ptraceback)
246-
}
247-
}
248258
PyErrStateInner::Normalized(normalized) => normalized,
249259
}
250260
}
@@ -253,15 +263,6 @@ impl PyErrStateInner {
253263
fn restore(self, py: Python<'_>) {
254264
let (ptype, pvalue, ptraceback) = match self {
255265
PyErrStateInner::Lazy(lazy) => lazy_into_normalized_ffi_tuple(py, lazy),
256-
PyErrStateInner::FfiTuple {
257-
ptype,
258-
pvalue,
259-
ptraceback,
260-
} => (
261-
ptype.into_ptr(),
262-
pvalue.map_or(std::ptr::null_mut(), Py::into_ptr),
263-
ptraceback.map_or(std::ptr::null_mut(), Py::into_ptr),
264-
),
265266
PyErrStateInner::Normalized(PyErrStateNormalized {
266267
ptype,
267268
pvalue,

src/err/mod.rs

-54
Original file line numberDiff line numberDiff line change
@@ -355,60 +355,6 @@ impl PyErr {
355355
/// expected to have been set, for example from [`PyErr::occurred`] or by an error return value
356356
/// from a C FFI function, use [`PyErr::fetch`].
357357
pub fn take(py: Python<'_>) -> Option<PyErr> {
358-
Self::_take(py)
359-
}
360-
361-
#[cfg(not(Py_3_12))]
362-
fn _take(py: Python<'_>) -> Option<PyErr> {
363-
let (ptype, pvalue, ptraceback) = unsafe {
364-
let mut ptype: *mut ffi::PyObject = std::ptr::null_mut();
365-
let mut pvalue: *mut ffi::PyObject = std::ptr::null_mut();
366-
let mut ptraceback: *mut ffi::PyObject = std::ptr::null_mut();
367-
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
368-
369-
// Convert to Py immediately so that any references are freed by early return.
370-
let ptype = PyObject::from_owned_ptr_or_opt(py, ptype);
371-
let pvalue = PyObject::from_owned_ptr_or_opt(py, pvalue);
372-
let ptraceback = PyObject::from_owned_ptr_or_opt(py, ptraceback);
373-
374-
// A valid exception state should always have a non-null ptype, but the other two may be
375-
// null.
376-
let ptype = match ptype {
377-
Some(ptype) => ptype,
378-
None => {
379-
debug_assert!(
380-
pvalue.is_none(),
381-
"Exception type was null but value was not null"
382-
);
383-
debug_assert!(
384-
ptraceback.is_none(),
385-
"Exception type was null but traceback was not null"
386-
);
387-
return None;
388-
}
389-
};
390-
391-
(ptype, pvalue, ptraceback)
392-
};
393-
394-
if ptype.as_ptr() == PanicException::type_object_raw(py).cast() {
395-
let msg = pvalue
396-
.as_ref()
397-
.and_then(|obj| obj.bind(py).str().ok())
398-
.map(|py_str| py_str.to_string_lossy().into())
399-
.unwrap_or_else(|| String::from("Unwrapped panic from Python code"));
400-
401-
let state = PyErrState::ffi_tuple(ptype, pvalue, ptraceback);
402-
Self::print_panic_and_unwind(py, state, msg)
403-
}
404-
405-
Some(PyErr::from_state(PyErrState::ffi_tuple(
406-
ptype, pvalue, ptraceback,
407-
)))
408-
}
409-
410-
#[cfg(Py_3_12)]
411-
fn _take(py: Python<'_>) -> Option<PyErr> {
412358
let state = PyErrStateNormalized::take(py)?;
413359
let pvalue = state.pvalue.bind(py);
414360
if pvalue.get_type().as_ptr() == PanicException::type_object_raw(py).cast() {

0 commit comments

Comments
 (0)