-
Notifications
You must be signed in to change notification settings - Fork 834
Add catch_unwind! macro to prevent panics crossing ffi boundaries #797
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,12 @@ | ||
// Copyright (c) 2017-present PyO3 Project and Contributors | ||
|
||
use crate::panic::PanicException; | ||
use crate::type_object::PyTypeObject; | ||
use crate::types::PyType; | ||
use crate::{exceptions, ffi}; | ||
use crate::{ | ||
AsPyPointer, FromPy, IntoPy, IntoPyPointer, Py, PyAny, PyObject, Python, ToBorrowedObject, | ||
ToPyObject, | ||
AsPyPointer, FromPy, FromPyPointer, IntoPy, IntoPyPointer, ObjectProtocol, Py, PyAny, PyObject, | ||
Python, ToBorrowedObject, ToPyObject, | ||
}; | ||
use libc::c_int; | ||
use std::ffi::CString; | ||
|
@@ -168,13 +169,33 @@ impl PyErr { | |
/// | ||
/// The error is cleared from the Python interpreter. | ||
/// If no error is set, returns a `SystemError`. | ||
pub fn fetch(_: Python) -> PyErr { | ||
/// | ||
/// If the error fetched is a `PanicException` (which would have originated from a panic in a | ||
/// pyo3 callback) then this function will resume the panic. | ||
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); | ||
PyErr::new_from_ffi_tuple(ptype, pvalue, ptraceback) | ||
|
||
let err = PyErr::new_from_ffi_tuple(ptype, pvalue, ptraceback); | ||
|
||
if ptype == PanicException::type_object().as_ptr() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really don't understand why we need this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is that unless caught a This means we need two main things:
I need the original panic payload to call Without the original payload, for now I just call Perhaps I should add a comment to the code documenting this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's not true.
So I'm not still convinced that PyO3 itself should cause panic for that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
By this I mean the current pyo3 behaviour of not using I agree with you that catching This is why I think the default behaviour should be to keep unwinding all the way to program exit - even if c1 fetches c2's I'll put some diagrams together this evening to try to explain better. |
||
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 | ||
} | ||
} | ||
|
||
|
@@ -564,6 +585,7 @@ pub fn error_on_minusone(py: Python, result: c_int) -> PyResult<()> { | |
#[cfg(test)] | ||
mod tests { | ||
use crate::exceptions; | ||
use crate::panic::PanicException; | ||
use crate::{PyErr, Python}; | ||
|
||
#[test] | ||
|
@@ -575,4 +597,16 @@ mod tests { | |
assert!(PyErr::occurred(py)); | ||
drop(PyErr::fetch(py)); | ||
} | ||
|
||
#[test] | ||
fn fetching_panic_exception_panics() { | ||
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
let err: PyErr = PanicException::py_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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
use crate::exceptions::BaseException; | ||
|
||
/// The exception raised when Rust code called from Python panics. | ||
/// | ||
/// Like SystemExit, this exception is derived from BaseException so that | ||
/// it will typically propagate all the way through the stack and cause the | ||
/// Python interpreter to exit. | ||
pub struct PanicException { | ||
_private: (), | ||
} | ||
|
||
pyo3_exception!(PanicException, BaseException); |
Uh oh!
There was an error while loading. Please reload this page.