changeset 42849:8db8fa1de2ef

rust-cpython: introduce restricted variant of RefCell This should catch invalid borrow_mut() calls. Still the ref-sharing interface is unsafe.
author Yuya Nishihara <yuya@tcha.org>
date Sun, 01 Sep 2019 17:37:30 +0900
parents 01d3ce3281cf
children 8f549c46bc64
files rust/hg-cpython/src/dirstate/dirs_multiset.rs rust/hg-cpython/src/dirstate/dirstate_map.rs rust/hg-cpython/src/ref_sharing.rs
diffstat 3 files changed, 56 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/rust/hg-cpython/src/dirstate/dirs_multiset.rs	Sun Sep 01 17:35:14 2019 +0900
+++ b/rust/hg-cpython/src/dirstate/dirs_multiset.rs	Sun Sep 01 17:37:30 2019 +0900
@@ -16,11 +16,12 @@
     Python,
 };
 
-use crate::{dirstate::extract_dirstate, ref_sharing::PySharedState};
+use crate::dirstate::extract_dirstate;
+use crate::ref_sharing::{PySharedRefCell, PySharedState};
 use hg::{DirsMultiset, DirstateMapError, DirstateParseError, EntryState};
 
 py_class!(pub class Dirs |py| {
-    data inner: RefCell<DirsMultiset>;
+    data inner: PySharedRefCell<DirsMultiset>;
     data py_shared_state: PySharedState;
 
     // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
@@ -53,7 +54,7 @@
 
         Self::create_instance(
             py,
-            RefCell::new(inner),
+            PySharedRefCell::new(inner),
             PySharedState::default()
         )
     }
@@ -104,7 +105,11 @@
 
 impl Dirs {
     pub fn from_inner(py: Python, d: DirsMultiset) -> PyResult<Self> {
-        Self::create_instance(py, RefCell::new(d), PySharedState::default())
+        Self::create_instance(
+            py,
+            PySharedRefCell::new(d),
+            PySharedState::default(),
+        )
     }
 
     fn translate_key(py: Python, res: &Vec<u8>) -> PyResult<Option<PyBytes>> {
--- a/rust/hg-cpython/src/dirstate/dirstate_map.rs	Sun Sep 01 17:35:14 2019 +0900
+++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs	Sun Sep 01 17:37:30 2019 +0900
@@ -21,7 +21,7 @@
 use crate::{
     dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
     dirstate::{decapsule_make_dirstate_tuple, dirs_multiset::Dirs},
-    ref_sharing::PySharedState,
+    ref_sharing::{PySharedRefCell, PySharedState},
 };
 use hg::{
     DirsMultiset, DirstateEntry, DirstateMap as RustDirstateMap,
@@ -41,14 +41,14 @@
 //     All attributes also have to have a separate refcount data attribute for
 //     leaks, with all methods that go along for reference sharing.
 py_class!(pub class DirstateMap |py| {
-    data inner: RefCell<RustDirstateMap>;
+    data inner: PySharedRefCell<RustDirstateMap>;
     data py_shared_state: PySharedState;
 
     def __new__(_cls, _root: PyObject) -> PyResult<Self> {
         let inner = RustDirstateMap::default();
         Self::create_instance(
             py,
-            RefCell::new(inner),
+            PySharedRefCell::new(inner),
             PySharedState::default()
         )
     }
--- a/rust/hg-cpython/src/ref_sharing.rs	Sun Sep 01 17:35:14 2019 +0900
+++ b/rust/hg-cpython/src/ref_sharing.rs	Sun Sep 01 17:37:30 2019 +0900
@@ -9,7 +9,7 @@
 
 use crate::exceptions::AlreadyBorrowed;
 use cpython::{PyResult, Python};
-use std::cell::{Cell, RefCell, RefMut};
+use std::cell::{Cell, Ref, RefCell, RefMut};
 
 /// Manages the shared state between Python and Rust
 #[derive(Default)]
@@ -61,7 +61,7 @@
     pub fn leak_immutable<T>(
         &self,
         py: Python,
-        data: &RefCell<T>,
+        data: &PySharedRefCell<T>,
     ) -> PyResult<&'static T> {
         if self.mutably_borrowed.get() {
             return Err(AlreadyBorrowed::new(
@@ -84,6 +84,38 @@
     }
 }
 
+/// `RefCell` wrapper to be safely used in conjunction with `PySharedState`.
+///
+/// Only immutable operation is allowed through this interface.
+#[derive(Debug)]
+pub struct PySharedRefCell<T> {
+    inner: RefCell<T>,
+}
+
+impl<T> PySharedRefCell<T> {
+    pub const fn new(value: T) -> PySharedRefCell<T> {
+        Self {
+            inner: RefCell::new(value),
+        }
+    }
+
+    pub fn borrow(&self) -> Ref<T> {
+        // py_shared_state isn't involved since
+        // - inner.borrow() would fail if self is mutably borrowed,
+        // - and inner.borrow_mut() would fail while self is borrowed.
+        self.inner.borrow()
+    }
+
+    pub fn as_ptr(&self) -> *mut T {
+        self.inner.as_ptr()
+    }
+
+    pub unsafe fn borrow_mut(&self) -> RefMut<T> {
+        // must be borrowed by self.py_shared_state(py).borrow_mut().
+        self.inner.borrow_mut()
+    }
+}
+
 /// Holds a mutable reference to data shared between Python and Rust.
 pub struct PyRefMut<'a, T> {
     inner: RefMut<'a, T>,
@@ -158,7 +190,7 @@
 /// }
 ///
 /// py_class!(pub class MyType |py| {
-///     data inner: RefCell<MyStruct>;
+///     data inner: PySharedRefCell<MyStruct>;
 ///     data py_shared_state: PySharedState;
 /// });
 ///
@@ -177,16 +209,21 @@
                 py: Python<'a>,
             ) -> PyResult<crate::ref_sharing::PyRefMut<'a, $inner_struct>>
             {
+                // assert $data_member type
+                use crate::ref_sharing::PySharedRefCell;
+                let data: &PySharedRefCell<_> = self.$data_member(py);
                 self.py_shared_state(py)
-                    .borrow_mut(py, self.$data_member(py).borrow_mut())
+                    .borrow_mut(py, unsafe { data.borrow_mut() })
             }
 
             fn leak_immutable<'a>(
                 &'a self,
                 py: Python<'a>,
             ) -> PyResult<&'static $inner_struct> {
-                self.py_shared_state(py)
-                    .leak_immutable(py, self.$data_member(py))
+                // assert $data_member type
+                use crate::ref_sharing::PySharedRefCell;
+                let data: &PySharedRefCell<_> = self.$data_member(py);
+                self.py_shared_state(py).leak_immutable(py, data)
             }
         }
 
@@ -295,7 +332,7 @@
 /// }
 ///
 /// py_class!(pub class MyType |py| {
-///     data inner: RefCell<MyStruct>;
+///     data inner: PySharedRefCell<MyStruct>;
 ///     data py_shared_state: PySharedState;
 ///
 ///     def __iter__(&self) -> PyResult<MyTypeItemsIterator> {