-
Notifications
You must be signed in to change notification settings - Fork 951
Description
It is currently possible with pyo3 to get &mut T and &T to the same object simultaneously.
This is trivially demonstrated with the following code:
#[pyclass]
struct Foo {
value: i32
}
#[pyfunction]
fn edit_foo(a: &mut Foo, b: &Foo) {
let start = b.value;
a.value += 1;
assert_eq!(b.value, start);
}
Because of the borrow rules the Rust compiler is safe to assume that a and b are not borrowing the same object. So it may do any number of optimisations to the assert_eq! line using the assumption that b.value is never modified.
This test can be written which breaks these rules by using the same python object for both arguments:
#[test]
fn test_edit_foo() {
use pyo3::types::IntoPyDict;
let gil = Python::acquire_gil();
let py = gil.python();
let foo = Py::new(py, Foo { value: 0 }).unwrap().to_object(py);
let locals = [
("foo", foo),
("edit_foo", wrap_pyfunction!(edit_foo)(py)),
].into_py_dict(py);
py.run("edit_foo(foo, foo)", None, Some(&locals)).unwrap();
}
This is clearly UB. With cargo test, the assert_eq! check panics and the test fails. With cargo test --release, the test passes, presumably because some optimisation has been applied.
A solution is to implement #342 to guard against this unsoundness.
If a user attempted to call edit_foo(foo, foo) from Python, the only choice I see is to raise a Python exception reading something like "TypeError: Attempted to access a Foo object mutably while also accessing it immutably".
I pushed this test on a branch; you can see the test failure here: https://github.com/davidhewitt/pyo3/runs/414604743?check_suite_focus=true