use crate::panic::PanicException;
use crate::type_object::PyTypeObject;
use crate::types::PyType;
use crate::{
exceptions::{self, PyBaseException},
ffi,
};
use crate::{
AsPyPointer, FromPyPointer, IntoPy, Py, PyAny, PyNativeType, PyObject, Python,
ToBorrowedObject, ToPyObject,
};
use libc::c_int;
use std::borrow::Cow;
use std::cell::UnsafeCell;
use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr::NonNull;
mod err_state;
mod impls;
pub use err_state::PyErrArguments;
use err_state::{boxed_args, PyErrState, PyErrStateNormalized};
pub struct PyErr {
state: UnsafeCell<Option<PyErrState>>,
}
unsafe impl Send for PyErr {}
unsafe impl Sync for PyErr {}
pub type PyResult<T> = Result<T, PyErr>;
#[derive(Debug)]
pub struct PyDowncastError<'a> {
from: &'a PyAny,
to: Cow<'static, str>,
}
impl<'a> PyDowncastError<'a> {
pub fn new(from: &'a PyAny, to: impl Into<Cow<'static, str>>) -> Self {
PyDowncastError {
from,
to: to.into(),
}
}
}
impl PyErr {
pub fn new<T, A>(args: A) -> PyErr
where
T: PyTypeObject,
A: PyErrArguments + Send + Sync + 'static,
{
Python::with_gil(|py| PyErr::from_type(T::type_object(py), args))
}
pub fn from_type<A>(ty: &PyType, args: A) -> PyErr
where
A: PyErrArguments + Send + Sync + 'static,
{
if unsafe { ffi::PyExceptionClass_Check(ty.as_ptr()) } == 0 {
return exceptions_must_derive_from_base_exception(ty.py());
}
PyErr::from_state(PyErrState::Lazy {
ptype: ty.into(),
pvalue: boxed_args(args),
})
}
pub fn from_instance(obj: &PyAny) -> PyErr {
let ptr = obj.as_ptr();
let state = if unsafe { ffi::PyExceptionInstance_Check(ptr) } != 0 {
PyErrState::Normalized(PyErrStateNormalized {
ptype: unsafe {
Py::from_borrowed_ptr(obj.py(), ffi::PyExceptionInstance_Class(ptr))
},
pvalue: unsafe { Py::from_borrowed_ptr(obj.py(), obj.as_ptr()) },
ptraceback: None,
})
} else if unsafe { ffi::PyExceptionClass_Check(obj.as_ptr()) } != 0 {
PyErrState::FfiTuple {
ptype: unsafe { Some(Py::from_borrowed_ptr(obj.py(), ptr)) },
pvalue: None,
ptraceback: None,
}
} else {
return exceptions_must_derive_from_base_exception(obj.py());
};
PyErr::from_state(state)
}
pub fn ptype<'py>(&'py self, py: Python<'py>) -> &'py PyType {
self.normalized(py).ptype.as_ref(py)
}
pub fn pvalue<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.normalized(py).pvalue.as_ref(py)
}
pub fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyAny> {
self.normalized(py)
.ptraceback
.as_ref()
.map(|obj| obj.as_ref(py))
}
#[inline]
pub fn occurred(_: Python) -> bool {
unsafe { !ffi::PyErr_Occurred().is_null() }
}
pub fn fetch(py: Python) -> PyErr {
unsafe {
let mut ptype: *mut ffi::PyObject = std::ptr::null_mut();
let mut pvalue: *mut ffi::PyObject = std::ptr::null_mut();
let mut ptraceback: *mut ffi::PyObject = std::ptr::null_mut();
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
let err = PyErr::new_from_ffi_tuple(py, ptype, pvalue, ptraceback);
if ptype == PanicException::type_object(py).as_ptr() {
let msg: String = PyAny::from_borrowed_ptr_or_opt(py, pvalue)
.and_then(|obj| obj.extract().ok())
.unwrap_or_else(|| String::from("Unwrapped panic from Python code"));
eprintln!(
"--- PyO3 is resuming a panic after fetching a PanicException from Python. ---"
);
eprintln!("Python stack trace below:");
err.print(py);
std::panic::resume_unwind(Box::new(msg))
}
err
}
}
pub fn new_type<'p>(
_: Python<'p>,
name: &str,
base: Option<&PyType>,
dict: Option<PyObject>,
) -> NonNull<ffi::PyTypeObject> {
let base: *mut ffi::PyObject = match base {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
let dict: *mut ffi::PyObject = match dict {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
unsafe {
let null_terminated_name =
CString::new(name).expect("Failed to initialize nul terminated exception name");
NonNull::new_unchecked(ffi::PyErr_NewException(
null_terminated_name.as_ptr() as *mut c_char,
base,
dict,
) as *mut ffi::PyTypeObject)
}
}
unsafe fn new_from_ffi_tuple(
py: Python,
ptype: *mut ffi::PyObject,
pvalue: *mut ffi::PyObject,
ptraceback: *mut ffi::PyObject,
) -> PyErr {
PyErr::from_state(PyErrState::FfiTuple {
ptype: Py::from_owned_ptr_or_opt(py, ptype),
pvalue: Py::from_owned_ptr_or_opt(py, pvalue),
ptraceback: Py::from_owned_ptr_or_opt(py, ptraceback),
})
}
pub fn print(&self, py: Python) {
self.clone_ref(py).restore(py);
unsafe { ffi::PyErr_PrintEx(0) }
}
pub fn print_and_set_sys_last_vars(&self, py: Python) {
self.clone_ref(py).restore(py);
unsafe { ffi::PyErr_PrintEx(1) }
}
pub fn matches<T>(&self, py: Python, exc: T) -> bool
where
T: ToBorrowedObject,
{
exc.with_borrowed_ptr(py, |exc| unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(), exc) != 0
})
}
pub fn is_instance<T>(&self, py: Python) -> bool
where
T: PyTypeObject,
{
unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(), T::type_object(py).as_ptr()) != 0
}
}
pub fn instance<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.normalized(py).pvalue.as_ref(py)
}
pub fn into_instance(self, py: Python) -> Py<PyBaseException> {
let out = self.normalized(py).pvalue.as_ref(py).into();
std::mem::forget(self);
out
}
#[inline]
pub fn restore(self, py: Python) {
let (ptype, pvalue, ptraceback) = self
.state
.into_inner()
.expect("Cannot restore a PyErr while normalizing it")
.into_ffi_tuple(py);
unsafe { ffi::PyErr_Restore(ptype, pvalue, ptraceback) }
}
pub fn warn(py: Python, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> {
let message = CString::new(message)?;
unsafe {
error_on_minusone(
py,
ffi::PyErr_WarnEx(
category.as_ptr(),
message.as_ptr(),
stacklevel as ffi::Py_ssize_t,
),
)
}
}
pub fn clone_ref(&self, py: Python) -> PyErr {
PyErr::from_state(PyErrState::Normalized(self.normalized(py).clone()))
}
fn from_state(state: PyErrState) -> PyErr {
PyErr {
state: UnsafeCell::new(Some(state)),
}
}
fn ptype_ptr(&self) -> *mut ffi::PyObject {
match unsafe { &*self.state.get() } {
Some(PyErrState::Lazy { ptype, .. }) => ptype.as_ptr(),
Some(PyErrState::FfiTuple { ptype, .. }) => ptype.as_ptr(),
Some(PyErrState::Normalized(n)) => n.ptype.as_ptr(),
None => panic!("Cannot access exception type while normalizing"),
}
}
fn normalized(&self, py: Python) -> &PyErrStateNormalized {
if let Some(PyErrState::Normalized(n)) = unsafe { &*self.state.get() } {
return n;
}
let state = unsafe {
(*self.state.get())
.take()
.expect("Cannot normalize a PyErr while already normalizing it.")
};
let (mut ptype, mut pvalue, mut ptraceback) = state.into_ffi_tuple(py);
unsafe {
ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
let self_state = &mut *self.state.get();
*self_state = Some(PyErrState::Normalized(PyErrStateNormalized {
ptype: Py::from_owned_ptr_or_opt(py, ptype)
.unwrap_or_else(|| exceptions::PySystemError::type_object(py).into()),
pvalue: Py::from_owned_ptr_or_opt(py, pvalue).unwrap_or_else(|| {
exceptions::PySystemError::new_err("Exception value missing")
.instance(py)
.into_py(py)
}),
ptraceback: PyObject::from_owned_ptr_or_opt(py, ptraceback),
}));
match self_state {
Some(PyErrState::Normalized(n)) => n,
_ => unreachable!(),
}
}
}
}
impl std::fmt::Debug for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
f.write_str(format!("PyErr {{ type: {:?} }}", self.ptype_ptr()).as_str())
}
}
impl std::fmt::Display for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
Python::with_gil(|py| self.instance(py).fmt(f))
}
}
impl std::error::Error for PyErr {}
impl IntoPy<PyObject> for PyErr {
fn into_py(self, py: Python) -> PyObject {
self.into_instance(py).into()
}
}
impl ToPyObject for PyErr {
fn to_object(&self, py: Python) -> PyObject {
self.clone_ref(py).into_py(py)
}
}
impl<'a> IntoPy<PyObject> for &'a PyErr {
fn into_py(self, py: Python) -> PyObject {
self.clone_ref(py).into_py(py)
}
}
impl<'a> std::convert::From<PyDowncastError<'a>> for PyErr {
fn from(err: PyDowncastError) -> PyErr {
exceptions::PyTypeError::new_err(err.to_string())
}
}
impl<'a> std::error::Error for PyDowncastError<'a> {}
impl<'a> std::fmt::Display for PyDowncastError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(
f,
"Can't convert {} to {}",
self.from
.repr()
.map(|s| s.to_string_lossy())
.unwrap_or_else(|_| self.from.get_type().name()),
self.to
)
}
}
pub fn panic_after_error(_py: Python) -> ! {
unsafe {
ffi::PyErr_Print();
}
panic!("Python API call failed");
}
#[inline]
pub fn error_on_minusone(py: Python, result: c_int) -> PyResult<()> {
if result != -1 {
Ok(())
} else {
Err(PyErr::fetch(py))
}
}
#[inline]
fn exceptions_must_derive_from_base_exception(py: Python) -> PyErr {
PyErr::from_state(PyErrState::Lazy {
ptype: exceptions::PyTypeError::type_object(py).into(),
pvalue: boxed_args("exceptions must derive from BaseException"),
})
}
#[cfg(test)]
mod tests {
use super::PyErrState;
use crate::exceptions;
use crate::panic::PanicException;
use crate::{PyErr, Python};
#[test]
fn set_typeerror() {
let gil = Python::acquire_gil();
let py = gil.python();
let err: PyErr = exceptions::PyTypeError::new_err(());
err.restore(py);
assert!(PyErr::occurred(py));
drop(PyErr::fetch(py));
}
#[test]
fn fetching_panic_exception_panics() {
if option_env!("RUSTFLAGS")
.map(|s| s.contains("-Cpanic=abort"))
.unwrap_or(false)
{
return;
}
let gil = Python::acquire_gil();
let py = gil.python();
let err: PyErr = PanicException::new_err("new panic");
err.restore(py);
assert!(PyErr::occurred(py));
let started_unwind =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| PyErr::fetch(py))).is_err();
assert!(started_unwind);
}
#[test]
fn test_pyerr_send_sync() {
fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}
is_send::<PyErr>();
is_sync::<PyErr>();
is_send::<PyErrState>();
is_sync::<PyErrState>();
}
}