Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

take control of exception object creation #4669

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
}
}
Loading