Skip to content

Commit

Permalink
take control of exception object creation
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Oct 30, 2024
1 parent f74d374 commit 929056f
Showing 1 changed file with 50 additions and 2 deletions.
52 changes: 50 additions & 2 deletions src/err/err_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
exceptions::{PyBaseException, PyTypeError},
ffi,
ffi_ptr_ext::FfiPtrExt,
types::{PyAnyMethods, PyTraceback, PyType},
types::{PyAnyMethods, PyString, PyTraceback, PyTuple, PyType},
Bound, Py, PyAny, PyErrArguments, PyObject, PyTypeInfo, Python,
};

Expand Down Expand Up @@ -310,14 +310,62 @@ fn lazy_into_normalized_ffi_tuple(
/// API in CPython.
fn raise_lazy(py: Python<'_>, lazy: Box<PyErrStateLazyFn>) {
let PyErrStateLazyFnOutput { ptype, pvalue } = lazy(py);
let state = create_normalized_exception(ptype.bind(py), pvalue.into_bound(py));
unsafe {
if ffi::PyExceptionClass_Check(ptype.as_ptr()) == 0 {
ffi::PyErr_SetString(
PyTypeError::type_object_raw(py).cast(),
ffi::c_str!("exceptions must derive from BaseException").as_ptr(),
)
} else {
ffi::PyErr_SetObject(ptype.as_ptr(), pvalue.as_ptr())
ffi::PyErr_SetObject(ptype.as_ptr(), state.pvalue.as_ptr())
}
}
}

fn create_normalized_exception<'py>(
ptype: &Bound<'py, PyAny>,
mut pvalue: Bound<'py, PyAny>,
) -> PyErrStateNormalized {
let py = ptype.py();

// 1: check type is a subclass of BaseException
let ptype: Bound<'py, PyType> = if unsafe { ffi::PyExceptionClass_Check(ptype.as_ptr()) } == 0 {
pvalue = PyString::new(py, "exceptions must derive from BaseException").into_any();
PyTypeError::type_object(py)
} else {
// Safety: PyExceptionClass_Check guarantees that ptype is a subclass of BaseException
unsafe { ptype.downcast_unchecked() }.clone()
};

// special cases for the value, inherited from Python, we should probably split out the
// None and tuple cases
let pvalue = if pvalue.is_exact_instance(&ptype) {
// Safety: already an exception value of the correct type
Ok(unsafe { pvalue.downcast_into_unchecked::<PyBaseException>() })
} else if pvalue.is_none() {
// None -> no arguments
ptype.call0().and_then(|pvalue| Ok(pvalue.downcast_into()?))
} else if let Ok(tup) = pvalue.downcast::<PyTuple>() {
// Tuple -> use as tuple of arguments
ptype
.call1(tup)
.and_then(|pvalue| Ok(pvalue.downcast_into()?))
} else {
// Anything else -> use as single argument
ptype
.call1((pvalue,))
.and_then(|pvalue| Ok(pvalue.downcast_into()?))
};

match pvalue {
Ok(pvalue) => PyErrStateNormalized {
#[cfg(not(Py_3_12))]
ptype,
pvalue: pvalue.unbind(),
#[cfg(not(Py_3_12))]
ptraceback: None,
},
Err(e) => e.normalized(py).clone_ref(py),
}
}

0 comments on commit 929056f

Please sign in to comment.