[PATCH 7 of 7] rust-cpython: mark all PyLeaked methods as unsafe
Raphaël Gomès
raphael.gomes at octobus.net
Tue Oct 22 08:45:35 EDT 2019
Nice series as always, everything makes sense to me. I will try to
dogfood the new API a little bit more in the near future.
On 10/22/19 10:17 AM, Yuya Nishihara wrote:
> On Tue, 22 Oct 2019 17:11:58 +0900, Yuya Nishihara wrote:
>> # HG changeset patch
>> # User Yuya Nishihara <yuya at tcha.org>
>> # Date 1571727874 -32400
>> # Tue Oct 22 16:04:34 2019 +0900
>> # Node ID 779af7c0979780d03451dd7cfe303e8dc64c6576
>> # Parent 29399437267567957a97dc9d924704ca748e3cec
>> rust-cpython: mark all PyLeaked methods as unsafe
> CC: Raphaël, Georges
>
> This might be interesting. The other patches in this series are refactoring
> stuff.
>
>> Unfortunately, these methods can be abused to obtain the inner 'static
>> reference. The simplest (pseudo-code) example is:
>>
>> let leaked: PyLeaked<&'static _> = shared.leak_immutable();
>> let static_ref: &'static _ = &*leaked.try_borrow(py)?;
>> // PyLeakedRef::deref() tries to bound the lifetime to itself, but
>> // the underlying data is a &'static reference, so the returned
>> // reference can be &'static.
>>
>> This problem can be easily fixed by coercing the lifetime, but there are
>> many other ways to achieve that, and there wouldn't be a generic solution:
>>
>> let leaked: PyLeaked<&'static [_]> = shared.leak_immutable();
>> let leaked_iter: PyLeaked<slice::Iter<'static, _>>
>> = unsafe { leaked.map(|v| v.iter()) };
>> let static_slice: &'static [_] = leaked_iter.try_borrow(py)?.as_slice();
>>
>> So basically I failed to design the safe borrowing interface. Maybe we'll
>> instead have to add much more restricted interface on top of the unsafe
>> PyLeaked methods? For instance, Iterator::next() could be implemented if
>> its Item type is not &'a (where 'a may be cheated.)
>>
>> Anyway, this seems not an easy issue, so it's probably better to leave the
>> current interface as unsafe, and get broader comments while upstreaming this
>> feature.
>>
>> diff --git a/rust/hg-cpython/src/ref_sharing.rs b/rust/hg-cpython/src/ref_sharing.rs
>> --- a/rust/hg-cpython/src/ref_sharing.rs
>> +++ b/rust/hg-cpython/src/ref_sharing.rs
>> @@ -294,7 +294,13 @@ impl<T> PyLeaked<T> {
>> /// Immutably borrows the wrapped value.
>> ///
>> /// Borrowing fails if the underlying reference has been invalidated.
>> - pub fn try_borrow<'a>(
>> + ///
>> + /// # Safety
>> + ///
>> + /// The lifetime of the innermost object is cheated. Do not obtain and
>> + /// copy it out of the borrow scope. See the example of `try_borrow_mut()`
>> + /// for details.
>> + pub unsafe fn try_borrow<'a>(
>> &'a self,
>> py: Python<'a>,
>> ) -> PyResult<PyLeakedRef<'a, T>> {
>> @@ -311,7 +317,24 @@ impl<T> PyLeaked<T> {
>> ///
>> /// Typically `T` is an iterator. If `T` is an immutable reference,
>> /// `get_mut()` is useless since the inner value can't be mutated.
>> - pub fn try_borrow_mut<'a>(
>> + ///
>> + /// # Safety
>> + ///
>> + /// The lifetime of the innermost object is cheated. Do not obtain and
>> + /// copy it out of the borrow scope. For example, the following code
>> + /// is unsafe:
>> + ///
>> + /// ```compile_fail
>> + /// let slice;
>> + /// {
>> + /// let iter = leaked.try_borrow_mut(py);
>> + /// // slice can outlive since the iterator is of Iter<'static, T>
>> + /// // type, but it shouldn't.
>> + /// slice = iter.as_slice();
>> + /// }
>> + /// println!("{:?}", slice);
>> + /// ```
>> + pub unsafe fn try_borrow_mut<'a>(
>> &'a mut self,
>> py: Python<'a>,
>> ) -> PyResult<PyLeakedRefMut<'a, T>> {
>> @@ -423,6 +446,11 @@ impl<T> DerefMut for PyLeakedRefMut<'_,
>> /// tuple on iteration success, turning it into something Python understands.
>> /// * `$success_func` is the return type of `$success_func`
>> ///
>> +/// # Safety
>> +///
>> +/// `$success_func` may take a reference, but it's lifetime may be cheated.
>> +/// Do not copy it out of the function call.
>> +///
>> /// # Example
>> ///
>> /// ```
>> @@ -476,9 +504,10 @@ macro_rules! py_shared_iterator {
>>
>> def __next__(&self) -> PyResult<$success_type> {
>> let mut leaked = self.inner(py).borrow_mut();
>> - let mut iter = leaked.try_borrow_mut(py)?;
>> + let mut iter = unsafe { leaked.try_borrow_mut(py)? };
>> match iter.next() {
>> None => Ok(None),
>> + // res may be a reference of cheated 'static lifetime
>> Some(res) => $success_func(py, res),
>> }
More information about the Mercurial-devel
mailing list