##// END OF EJS Templates
rust-cpython: add panicking version of borrow_mut() and use it...
rust-cpython: add panicking version of borrow_mut() and use it The original borrow_mut() is renamed to try_borrow_mut(). Since leak_immutable() no longer incref the borrow count, the caller should know if the underlying value is borrowed or not. No Python world is involved. That's why we can simply use the panicking borrow_mut().

File last commit:

r44668:71457ff6 default
r44685:2a24ead0 default
Show More
ref_sharing.rs
668 lines | 21.3 KiB | application/rls-services+xml | RustLexer
Yuya Nishihara
rust-cpython: change license of ref_sharing.rs to MIT...
r43352 // ref_sharing.rs
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 //
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
//
Yuya Nishihara
rust-cpython: change license of ref_sharing.rs to MIT...
r43352 // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997
//! Macros for use in the `hg-cpython` bridge library.
use crate::exceptions::AlreadyBorrowed;
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 use cpython::{exc, PyClone, PyErr, PyObject, PyResult, Python};
Yuya Nishihara
rust-cpython: drop manual management of mutably_borrowed...
r43609 use std::cell::{Ref, RefCell, RefMut};
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 use std::ops::{Deref, DerefMut};
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 use std::sync::atomic::{AtomicUsize, Ordering};
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997
/// Manages the shared state between Python and Rust
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 ///
/// `PySharedState` is owned by `PySharedRefCell`, and is shared across its
/// derived references. The consistency of these references are guaranteed
/// as follows:
///
/// - The immutability of `py_class!` object fields. Any mutation of
/// `PySharedRefCell` is allowed only through its `borrow_mut()`.
/// - The `py: Python<'_>` token, which makes sure that any data access is
/// synchronized by the GIL.
Yuya Nishihara
rust-cpython: leverage RefCell::borrow() to guarantee there's no mutable ref...
r43608 /// - The underlying `RefCell`, which prevents `PySharedRefCell` data from
/// being directly borrowed or leaked while it is mutably borrowed.
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 /// - The `borrow_count`, which is the number of references borrowed from
/// `PyLeaked`. Just like `RefCell`, mutation is prohibited while `PyLeaked`
/// is borrowed.
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 /// - The `generation` counter, which increments on `borrow_mut()`. `PyLeaked`
/// reference is valid only if the `current_generation()` equals to the
/// `generation` at the time of `leak_immutable()`.
Yuya Nishihara
rust-cpython: move py_shared_state to PySharedRefCell object...
r43443 #[derive(Debug, Default)]
Yuya Nishihara
rust-cpython: make inner functions and structs of ref_sharing private...
r43582 struct PySharedState {
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 // The counter variable could be Cell<usize> since any operation on
// PySharedState is synchronized by the GIL, but being "atomic" makes
// PySharedState inherently Sync. The ordering requirement doesn't
// matter thanks to the GIL.
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 borrow_count: AtomicUsize,
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 generation: AtomicUsize,
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 }
impl PySharedState {
Yuya Nishihara
rust-cpython: make sure PySharedRef::borrow_mut() never panics...
r44650 fn try_borrow_mut<'a, T>(
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 &'a self,
py: Python<'a>,
pyrefmut: RefMut<'a, T>,
Yuya Nishihara
rust-cpython: remove useless PyRefMut wrapper
r43610 ) -> PyResult<RefMut<'a, T>> {
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 match self.current_borrow_count(py) {
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 0 => {
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 // Note that this wraps around to the same value if mutably
// borrowed more than usize::MAX times, which wouldn't happen
// in practice.
self.generation.fetch_add(1, Ordering::Relaxed);
Yuya Nishihara
rust-cpython: remove useless PyRefMut wrapper
r43610 Ok(pyrefmut)
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 }
_ => Err(AlreadyBorrowed::new(
py,
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 "Cannot borrow mutably while immutably borrowed",
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 )),
}
}
Yuya Nishihara
rust-cpython: store leaked reference to PySharedState in $leaked struct...
r43446 /// Return a reference to the wrapped data and its state with an
/// artificial static lifetime.
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 /// We need to be protected by the GIL for thread-safety.
Yuya Nishihara
rust-cpython: mark unsafe functions as such...
r43117 ///
/// # Safety
///
/// This is highly unsafe since the lifetime of the given data can be
/// extended. Do not call this function directly.
Yuya Nishihara
rust-cpython: make inner functions and structs of ref_sharing private...
r43582 unsafe fn leak_immutable<T>(
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 &self,
Yuya Nishihara
rust-cpython: drop manual management of mutably_borrowed...
r43609 _py: Python,
Yuya Nishihara
rust-cpython: leverage RefCell::borrow() to guarantee there's no mutable ref...
r43608 data: Ref<T>,
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 ) -> (&'static T, &'static PySharedState) {
Yuya Nishihara
rust-cpython: leverage RefCell::borrow() to guarantee there's no mutable ref...
r43608 let ptr: *const T = &*data;
let state_ptr: *const PySharedState = self;
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 (&*ptr, &*state_ptr)
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 }
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 fn current_borrow_count(&self, _py: Python) -> usize {
self.borrow_count.load(Ordering::Relaxed)
}
fn increase_borrow_count(&self, _py: Python) {
// Note that this wraps around if there are more than usize::MAX
// borrowed references, which shouldn't happen due to memory limit.
self.borrow_count.fetch_add(1, Ordering::Relaxed);
}
fn decrease_borrow_count(&self, _py: Python) {
let prev_count = self.borrow_count.fetch_sub(1, Ordering::Relaxed);
assert!(prev_count > 0);
}
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 fn current_generation(&self, _py: Python) -> usize {
self.generation.load(Ordering::Relaxed)
}
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 }
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 /// Helper to keep the borrow count updated while the shared object is
/// immutably borrowed without using the `RefCell` interface.
struct BorrowPyShared<'a> {
py: Python<'a>,
py_shared_state: &'a PySharedState,
}
impl<'a> BorrowPyShared<'a> {
fn new(
py: Python<'a>,
py_shared_state: &'a PySharedState,
) -> BorrowPyShared<'a> {
py_shared_state.increase_borrow_count(py);
BorrowPyShared {
py,
py_shared_state,
}
}
}
impl Drop for BorrowPyShared<'_> {
fn drop(&mut self) {
self.py_shared_state.decrease_borrow_count(self.py);
}
}
Yuya Nishihara
rust-cpython: introduce restricted variant of RefCell...
r43115 /// `RefCell` wrapper to be safely used in conjunction with `PySharedState`.
///
Yuya Nishihara
rust-cpython: make inner functions and structs of ref_sharing private...
r43582 /// This object can be stored in a `py_class!` object as a data field. Any
/// operation is allowed through the `PySharedRef` interface.
Yuya Nishihara
rust-cpython: introduce restricted variant of RefCell...
r43115 #[derive(Debug)]
pub struct PySharedRefCell<T> {
inner: RefCell<T>,
Yuya Nishihara
rust-cpython: add safe wrapper representing shared data borrowed from PyObject...
r43448 py_shared_state: PySharedState,
Yuya Nishihara
rust-cpython: introduce restricted variant of RefCell...
r43115 }
impl<T> PySharedRefCell<T> {
Yuya Nishihara
rust-cpython: move py_shared_state to PySharedRefCell object...
r43443 pub fn new(value: T) -> PySharedRefCell<T> {
Yuya Nishihara
rust-cpython: introduce restricted variant of RefCell...
r43115 Self {
inner: RefCell::new(value),
Yuya Nishihara
rust-cpython: move py_shared_state to PySharedRefCell object...
r43443 py_shared_state: PySharedState::default(),
Yuya Nishihara
rust-cpython: introduce restricted variant of RefCell...
r43115 }
}
Yuya Nishihara
rust-cpython: make inner functions and structs of ref_sharing private...
r43582 fn borrow<'a>(&'a self, _py: Python<'a>) -> Ref<'a, T> {
Yuya Nishihara
rust-cpython: introduce restricted variant of RefCell...
r43115 // py_shared_state isn't involved since
// - inner.borrow() would fail if self is mutably borrowed,
Yuya Nishihara
rust-cpython: make sure PySharedRef::borrow_mut() never panics...
r44650 // - and inner.try_borrow_mut() would fail while self is borrowed.
Yuya Nishihara
rust-cpython: introduce restricted variant of RefCell...
r43115 self.inner.borrow()
}
Yuya Nishihara
rust-cpython: make sure PySharedRef::borrow_mut() never panics...
r44650 fn try_borrow_mut<'a>(
&'a self,
py: Python<'a>,
) -> PyResult<RefMut<'a, T>> {
let inner_ref = self
.inner
.try_borrow_mut()
.map_err(|e| AlreadyBorrowed::new(py, e.to_string()))?;
self.py_shared_state.try_borrow_mut(py, inner_ref)
Yuya Nishihara
rust-cpython: introduce restricted variant of RefCell...
r43115 }
}
Yuya Nishihara
rust-cpython: add safe wrapper representing shared data borrowed from PyObject...
r43448 /// Sharable data member of type `T` borrowed from the `PyObject`.
pub struct PySharedRef<'a, T> {
py: Python<'a>,
owner: &'a PyObject,
data: &'a PySharedRefCell<T>,
}
impl<'a, T> PySharedRef<'a, T> {
/// # Safety
///
/// The `data` must be owned by the `owner`. Otherwise, the leak count
/// would get wrong.
pub unsafe fn new(
py: Python<'a>,
owner: &'a PyObject,
data: &'a PySharedRefCell<T>,
) -> Self {
Self { py, owner, data }
}
Raphaël Gomès
rust-refsharing: add missing lifetime parameter in ref_sharing...
r43566 pub fn borrow(&self) -> Ref<'a, T> {
Yuya Nishihara
rust-cpython: require GIL to borrow immutable reference from PySharedRefCell...
r43580 self.data.borrow(self.py)
Yuya Nishihara
rust-cpython: add safe wrapper representing shared data borrowed from PyObject...
r43448 }
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 /// Mutably borrows the wrapped value.
///
/// # Panics
///
/// Panics if the value is currently borrowed through `PySharedRef`
/// or `PyLeaked`.
pub fn borrow_mut(&self) -> RefMut<'a, T> {
self.try_borrow_mut().expect("already borrowed")
}
/// Mutably borrows the wrapped value, returning an error if the value
/// is currently borrowed.
pub fn try_borrow_mut(&self) -> PyResult<RefMut<'a, T>> {
Yuya Nishihara
rust-cpython: make sure PySharedRef::borrow_mut() never panics...
r44650 self.data.try_borrow_mut(self.py)
Yuya Nishihara
rust-cpython: add safe wrapper representing shared data borrowed from PyObject...
r43448 }
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 /// Returns a leaked reference.
Yuya Nishihara
rust-cpython: leverage RefCell::borrow() to guarantee there's no mutable ref...
r43608 ///
/// # Panics
///
/// Panics if this is mutably borrowed.
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 pub fn leak_immutable(&self) -> PyLeaked<&'static T> {
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 let state = &self.data.py_shared_state;
Yuya Nishihara
rust-cpython: leverage RefCell::borrow() to guarantee there's no mutable ref...
r43608 // make sure self.data isn't mutably borrowed; otherwise the
// generation number can't be trusted.
let data_ref = self.borrow();
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 unsafe {
let (static_ref, static_state_ref) =
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 state.leak_immutable(self.py, data_ref);
PyLeaked::new(self.py, self.owner, static_ref, static_state_ref)
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 }
Yuya Nishihara
rust-cpython: add safe wrapper representing shared data borrowed from PyObject...
r43448 }
}
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 /// Allows a `py_class!` generated struct to share references to one of its
/// data members with Python.
///
/// # Parameters
///
/// * `$name` is the same identifier used in for `py_class!` macro call.
/// * `$inner_struct` is the identifier of the underlying Rust struct
/// * `$data_member` is the identifier of the data member of `$inner_struct`
/// that will be shared.
Yuya Nishihara
rust-cpython: add safe wrapper representing shared data borrowed from PyObject...
r43448 /// * `$shared_accessor` is the function name to be generated, which allows
/// safe access to the data member.
///
/// # Safety
///
/// `$data_member` must persist while the `$name` object is alive. In other
/// words, it must be an accessor to a data field of the Python object.
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 ///
/// # Example
///
/// ```
/// struct MyStruct {
/// inner: Vec<u32>;
/// }
///
/// py_class!(pub class MyType |py| {
Yuya Nishihara
rust-cpython: introduce restricted variant of RefCell...
r43115 /// data inner: PySharedRefCell<MyStruct>;
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 /// });
///
Yuya Nishihara
rust-cpython: add safe wrapper representing shared data borrowed from PyObject...
r43448 /// py_shared_ref!(MyType, MyStruct, inner, inner_shared);
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 /// ```
macro_rules! py_shared_ref {
(
$name: ident,
$inner_struct: ident,
Yuya Nishihara
rust-cpython: add safe wrapper representing shared data borrowed from PyObject...
r43448 $data_member: ident,
$shared_accessor: ident
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 ) => {
impl $name {
Yuya Nishihara
rust-cpython: add safe wrapper representing shared data borrowed from PyObject...
r43448 /// Returns a safe reference to the shared `$data_member`.
///
/// This function guarantees that `PySharedRef` is created with
/// the valid `self` and `self.$data_member(py)` pair.
fn $shared_accessor<'a>(
&'a self,
py: Python<'a>,
) -> $crate::ref_sharing::PySharedRef<'a, $inner_struct> {
use cpython::PythonObject;
use $crate::ref_sharing::PySharedRef;
let owner = self.as_object();
let data = self.$data_member(py);
unsafe { PySharedRef::new(py, owner, data) }
}
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 }
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 };
}
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 /// Manage immutable references to `PyObject` leaked into Python iterators.
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 ///
/// This reference will be invalidated once the original value is mutably
/// borrowed.
Yuya Nishihara
rust-cpython: rename PyLeakedRef to PyLeaked...
r43603 pub struct PyLeaked<T> {
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 inner: PyObject,
Yuya Nishihara
rust-cpython: remove useless wrappers from PyLeaked, just move by map()...
r44649 data: T,
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 py_shared_state: &'static PySharedState,
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 /// Generation counter of data `T` captured when PyLeaked is created.
generation: usize,
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 }
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997
Yuya Nishihara
rust-cpython: rename PyLeakedRef to PyLeaked...
r43603 // DO NOT implement Deref for PyLeaked<T>! Dereferencing PyLeaked
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 // without taking Python GIL wouldn't be safe. Also, the underling reference
// is invalid if generation != py_shared_state.generation.
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579
Yuya Nishihara
rust-cpython: rename PyLeakedRef to PyLeaked...
r43603 impl<T> PyLeaked<T> {
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 /// # Safety
///
/// The `py_shared_state` must be owned by the `inner` Python object.
Yuya Nishihara
rust-cpython: make inner functions and structs of ref_sharing private...
r43582 fn new(
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 py: Python,
inner: &PyObject,
Yuya Nishihara
rust-cpython: put leaked reference in PyLeakedRef...
r43578 data: T,
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 py_shared_state: &'static PySharedState,
) -> Self {
Self {
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 inner: inner.clone_ref(py),
Yuya Nishihara
rust-cpython: remove useless wrappers from PyLeaked, just move by map()...
r44649 data: data,
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 py_shared_state,
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 generation: py_shared_state.current_generation(py),
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 }
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 }
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 /// Immutably borrows the wrapped value.
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 ///
/// Borrowing fails if the underlying reference has been invalidated.
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 pub fn try_borrow<'a>(
&'a self,
py: Python<'a>,
) -> PyResult<PyLeakedRef<'a, T>> {
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 self.validate_generation(py)?;
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 Ok(PyLeakedRef {
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 _borrow: BorrowPyShared::new(py, self.py_shared_state),
Yuya Nishihara
rust-cpython: remove useless wrappers from PyLeaked, just move by map()...
r44649 data: &self.data,
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 })
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 }
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 /// Mutably borrows the wrapped value.
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 ///
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 /// Borrowing fails if the underlying reference has been invalidated.
///
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 /// Typically `T` is an iterator. If `T` is an immutable reference,
/// `get_mut()` is useless since the inner value can't be mutated.
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 pub fn try_borrow_mut<'a>(
&'a mut self,
py: Python<'a>,
) -> PyResult<PyLeakedRefMut<'a, T>> {
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 self.validate_generation(py)?;
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 Ok(PyLeakedRefMut {
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 _borrow: BorrowPyShared::new(py, self.py_shared_state),
Yuya Nishihara
rust-cpython: remove useless wrappers from PyLeaked, just move by map()...
r44649 data: &mut self.data,
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 })
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 }
/// Converts the inner value by the given function.
///
/// Typically `T` is a static reference to a container, and `U` is an
/// iterator of that container.
///
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 /// # Panics
///
/// Panics if the underlying reference has been invalidated.
///
/// This is typically called immediately after the `PyLeaked` is obtained.
/// In which case, the reference must be valid and no panic would occur.
///
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 /// # Safety
///
/// The lifetime of the object passed in to the function `f` is cheated.
/// It's typically a static reference, but is valid only while the
Yuya Nishihara
rust-cpython: rename PyLeakedRef to PyLeaked...
r43603 /// corresponding `PyLeaked` is alive. Do not copy it out of the
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 /// function call.
pub unsafe fn map<U>(
Yuya Nishihara
rust-cpython: remove useless wrappers from PyLeaked, just move by map()...
r44649 self,
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 py: Python,
f: impl FnOnce(T) -> U,
Yuya Nishihara
rust-cpython: rename PyLeakedRef to PyLeaked...
r43603 ) -> PyLeaked<U> {
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 // Needs to test the generation value to make sure self.data reference
// is still intact.
self.validate_generation(py)
.expect("map() over invalidated leaked reference");
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 // f() could make the self.data outlive. That's why map() is unsafe.
// In order to make this function safe, maybe we'll need a way to
// temporarily restrict the lifetime of self.data and translate the
// returned object back to Something<'static>.
Yuya Nishihara
rust-cpython: remove useless wrappers from PyLeaked, just move by map()...
r44649 let new_data = f(self.data);
Yuya Nishihara
rust-cpython: rename PyLeakedRef to PyLeaked...
r43603 PyLeaked {
Yuya Nishihara
rust-cpython: remove useless wrappers from PyLeaked, just move by map()...
r44649 inner: self.inner,
data: new_data,
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 py_shared_state: self.py_shared_state,
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 generation: self.generation,
}
}
fn validate_generation(&self, py: Python) -> PyResult<()> {
if self.py_shared_state.current_generation(py) == self.generation {
Ok(())
} else {
Err(PyErr::new::<exc::RuntimeError, _>(
py,
"Cannot access to leaked reference after mutation",
))
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 }
}
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 }
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 /// Immutably borrowed reference to a leaked value.
pub struct PyLeakedRef<'a, T> {
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 _borrow: BorrowPyShared<'a>,
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 data: &'a T,
}
impl<T> Deref for PyLeakedRef<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.data
}
}
/// Mutably borrowed reference to a leaked value.
pub struct PyLeakedRefMut<'a, T> {
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 _borrow: BorrowPyShared<'a>,
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 data: &'a mut T,
}
impl<T> Deref for PyLeakedRefMut<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.data
}
}
impl<T> DerefMut for PyLeakedRefMut<'_, T> {
fn deref_mut(&mut self) -> &mut T {
self.data
}
}
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 /// Defines a `py_class!` that acts as a Python iterator over a Rust iterator.
Yuya Nishihara
rust-cpython: replace dyn Iterator<..> of mapping with concrete type...
r43158 ///
/// TODO: this is a bit awkward to use, and a better (more complicated)
/// procedural macro would simplify the interface a lot.
///
/// # Parameters
///
/// * `$name` is the identifier to give to the resulting Rust struct.
/// * `$leaked` corresponds to `$leaked` in the matching `py_shared_ref!` call.
/// * `$iterator_type` is the type of the Rust iterator.
/// * `$success_func` is a function for processing the Rust `(key, value)`
/// tuple on iteration success, turning it into something Python understands.
/// * `$success_func` is the return type of `$success_func`
///
/// # Example
///
/// ```
/// struct MyStruct {
/// inner: HashMap<Vec<u8>, Vec<u8>>;
/// }
///
/// py_class!(pub class MyType |py| {
/// data inner: PySharedRefCell<MyStruct>;
///
/// def __iter__(&self) -> PyResult<MyTypeItemsIterator> {
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 /// let leaked_ref = self.inner_shared(py).leak_immutable();
Yuya Nishihara
rust-cpython: leverage py_shared_iterator::from_inner() where appropriate
r43161 /// MyTypeItemsIterator::from_inner(
Yuya Nishihara
rust-cpython: replace dyn Iterator<..> of mapping with concrete type...
r43158 /// py,
Yuya Nishihara
rust-cpython: make PyLeakedRef operations relatively safe...
r43579 /// unsafe { leaked_ref.map(py, |o| o.iter()) },
Yuya Nishihara
rust-cpython: replace dyn Iterator<..> of mapping with concrete type...
r43158 /// )
/// }
/// });
///
/// impl MyType {
/// fn translate_key_value(
/// py: Python,
/// res: (&Vec<u8>, &Vec<u8>),
/// ) -> PyResult<Option<(PyBytes, PyBytes)>> {
/// let (f, entry) = res;
/// Ok(Some((
/// PyBytes::new(py, f),
/// PyBytes::new(py, entry),
/// )))
/// }
/// }
///
/// py_shared_ref!(MyType, MyStruct, inner, MyTypeLeakedRef);
///
Yuya Nishihara
rust-cpython: rename py_shared_iterator_impl to py_shared_iterator...
r43159 /// py_shared_iterator!(
Yuya Nishihara
rust-cpython: replace dyn Iterator<..> of mapping with concrete type...
r43158 /// MyTypeItemsIterator,
Yuya Nishihara
rust-cpython: rename PyLeakedRef to PyLeaked...
r43603 /// PyLeaked<HashMap<'static, Vec<u8>, Vec<u8>>>,
Yuya Nishihara
rust-cpython: replace dyn Iterator<..> of mapping with concrete type...
r43158 /// MyType::translate_key_value,
/// Option<(PyBytes, PyBytes)>
/// );
/// ```
Yuya Nishihara
rust-cpython: rename py_shared_iterator_impl to py_shared_iterator...
r43159 macro_rules! py_shared_iterator {
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 (
$name: ident,
Yuya Nishihara
rust-cpython: move $leaked struct out of macro...
r43447 $leaked: ty,
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 $success_func: expr,
$success_type: ty
) => {
py_class!(pub class $name |py| {
Yuya Nishihara
rust-cpython: remove useless Option<$leaked> from py_shared_iterator...
r43607 data inner: RefCell<$leaked>;
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997
def __next__(&self) -> PyResult<$success_type> {
Yuya Nishihara
rust-cpython: remove useless Option<$leaked> from py_shared_iterator...
r43607 let mut leaked = self.inner(py).borrow_mut();
let mut iter = leaked.try_borrow_mut(py)?;
match iter.next() {
None => Ok(None),
Some(res) => $success_func(py, res),
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 }
}
def __iter__(&self) -> PyResult<Self> {
Ok(self.clone_ref(py))
}
});
impl $name {
pub fn from_inner(
py: Python,
Yuya Nishihara
rust-cpython: remove Option<_> from interface of py_shared_iterator...
r43160 leaked: $leaked,
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 ) -> PyResult<Self> {
Self::create_instance(
py,
Yuya Nishihara
rust-cpython: remove useless Option<$leaked> from py_shared_iterator...
r43607 RefCell::new(leaked),
Raphaël Gomès
rust-cpython: add macro for sharing references...
r42997 )
}
}
};
}
Yuya Nishihara
rust-cpython: prepare for writing tests that require libpython...
r43583
#[cfg(test)]
#[cfg(any(feature = "python27-bin", feature = "python3-bin"))]
mod test {
use super::*;
use cpython::{GILGuard, Python};
py_class!(class Owner |py| {
data string: PySharedRefCell<String>;
});
py_shared_ref!(Owner, String, string, string_shared);
fn prepare_env() -> (GILGuard, Owner) {
let gil = Python::acquire_gil();
let py = gil.python();
let owner =
Owner::create_instance(py, PySharedRefCell::new("new".to_owned()))
.unwrap();
(gil, owner)
}
#[test]
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 fn test_leaked_borrow() {
let (gil, owner) = prepare_env();
let py = gil.python();
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 let leaked = owner.string_shared(py).leak_immutable();
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 let leaked_ref = leaked.try_borrow(py).unwrap();
assert_eq!(*leaked_ref, "new");
}
#[test]
fn test_leaked_borrow_mut() {
let (gil, owner) = prepare_env();
let py = gil.python();
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 let leaked = owner.string_shared(py).leak_immutable();
Yuya Nishihara
rust-cpython: add stub wrapper that'll prevent leaked data from being mutated...
r43604 let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
let mut leaked_ref = leaked_iter.try_borrow_mut(py).unwrap();
assert_eq!(leaked_ref.next(), Some('n'));
assert_eq!(leaked_ref.next(), Some('e'));
assert_eq!(leaked_ref.next(), Some('w'));
assert_eq!(leaked_ref.next(), None);
}
#[test]
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 fn test_leaked_borrow_after_mut() {
let (gil, owner) = prepare_env();
let py = gil.python();
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 let leaked = owner.string_shared(py).leak_immutable();
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 owner.string_shared(py).borrow_mut().clear();
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 assert!(leaked.try_borrow(py).is_err());
}
#[test]
fn test_leaked_borrow_mut_after_mut() {
let (gil, owner) = prepare_env();
let py = gil.python();
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 let leaked = owner.string_shared(py).leak_immutable();
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 owner.string_shared(py).borrow_mut().clear();
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 assert!(leaked_iter.try_borrow_mut(py).is_err());
}
#[test]
#[should_panic(expected = "map() over invalidated leaked reference")]
fn test_leaked_map_after_mut() {
let (gil, owner) = prepare_env();
let py = gil.python();
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 let leaked = owner.string_shared(py).leak_immutable();
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 owner.string_shared(py).borrow_mut().clear();
Yuya Nishihara
rust-cpython: add generation counter to leaked reference...
r43605 let _leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
}
#[test]
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 fn test_try_borrow_mut_while_leaked_ref() {
Yuya Nishihara
rust-cpython: prepare for writing tests that require libpython...
r43583 let (gil, owner) = prepare_env();
let py = gil.python();
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 assert!(owner.string_shared(py).try_borrow_mut().is_ok());
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 let leaked = owner.string_shared(py).leak_immutable();
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 {
let _leaked_ref = leaked.try_borrow(py).unwrap();
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 assert!(owner.string_shared(py).try_borrow_mut().is_err());
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 {
let _leaked_ref2 = leaked.try_borrow(py).unwrap();
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 assert!(owner.string_shared(py).try_borrow_mut().is_err());
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 }
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 assert!(owner.string_shared(py).try_borrow_mut().is_err());
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 }
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 assert!(owner.string_shared(py).try_borrow_mut().is_ok());
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 }
#[test]
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 fn test_try_borrow_mut_while_leaked_ref_mut() {
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 let (gil, owner) = prepare_env();
let py = gil.python();
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 assert!(owner.string_shared(py).try_borrow_mut().is_ok());
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 let leaked = owner.string_shared(py).leak_immutable();
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
{
let _leaked_ref = leaked_iter.try_borrow_mut(py).unwrap();
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 assert!(owner.string_shared(py).try_borrow_mut().is_err());
Yuya Nishihara
rust-cpython: allow mutation unless leaked reference is borrowed...
r43606 }
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 assert!(owner.string_shared(py).try_borrow_mut().is_ok());
Yuya Nishihara
rust-cpython: prepare for writing tests that require libpython...
r43583 }
Yuya Nishihara
rust-cpython: leverage RefCell::borrow() to guarantee there's no mutable ref...
r43608
#[test]
#[should_panic(expected = "mutably borrowed")]
fn test_leak_while_borrow_mut() {
let (gil, owner) = prepare_env();
let py = gil.python();
let _mut_ref = owner.string_shared(py).borrow_mut();
Yuya Nishihara
rust-cpython: remove useless PyResult<> from leak_immutable()...
r43611 owner.string_shared(py).leak_immutable();
Yuya Nishihara
rust-cpython: leverage RefCell::borrow() to guarantee there's no mutable ref...
r43608 }
Yuya Nishihara
rust-cpython: make sure PySharedRef::borrow_mut() never panics...
r44650
#[test]
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 fn test_try_borrow_mut_while_borrow() {
let (gil, owner) = prepare_env();
let py = gil.python();
let _ref = owner.string_shared(py).borrow();
assert!(owner.string_shared(py).try_borrow_mut().is_err());
}
#[test]
#[should_panic(expected = "already borrowed")]
Yuya Nishihara
rust-cpython: make sure PySharedRef::borrow_mut() never panics...
r44650 fn test_borrow_mut_while_borrow() {
let (gil, owner) = prepare_env();
let py = gil.python();
let _ref = owner.string_shared(py).borrow();
Yuya Nishihara
rust-cpython: add panicking version of borrow_mut() and use it...
r44685 owner.string_shared(py).borrow_mut();
Yuya Nishihara
rust-cpython: make sure PySharedRef::borrow_mut() never panics...
r44650 }
Yuya Nishihara
rust-cpython: prepare for writing tests that require libpython...
r43583 }