Mercurial > public > mercurial-scm > hg-stable
comparison rust/pyo3-sharedref/src/lib.rs @ 52640:78b2894cd58c
rust-pyo3-sharedref: demonstrate SharedByPyObject borrow unsafety
We take the existing doc-comment from rust-cpython and make it
compile (validated by `cargo test`). With the added explanations,
the ordinary comment was no longer useful, we could therefore
remove it.
The new explanation stresses that "not leaking the internal faked
reference" is definitely not enough, because the problem is about
*all references* that can be derived from it.
We ended up duplicating the explanation, because that is a way
to ensure that people do not miss it. Also, it was a bit misleading
that the previous example was for `try_borrow_mut()`, so we made
a similar, simpler one for `try_borrow()`.
author | Georges Racinet <georges.racinet@cloudcrane.io> |
---|---|
date | Mon, 16 Dec 2024 13:08:55 +0100 |
parents | 76a0bdb0e4ca |
children | 189491cea922 |
comparison
equal
deleted
inserted
replaced
52639:76a0bdb0e4ca | 52640:78b2894cd58c |
---|---|
467 /// Even though [`SharedByPyObject`] tries to enforce the real lifetime of the | 467 /// Even though [`SharedByPyObject`] tries to enforce the real lifetime of the |
468 /// underlying object, the object having the artificial `'static` lifetime | 468 /// underlying object, the object having the artificial `'static` lifetime |
469 /// may be exposed to your Rust code. You must be careful to not make a bare | 469 /// may be exposed to your Rust code. You must be careful to not make a bare |
470 /// reference outlive the actual object lifetime. | 470 /// reference outlive the actual object lifetime. |
471 /// | 471 /// |
472 /// ```ignore | 472 /// See [`Self::try_borrow_mut()`] for an example of the kind of trouble that |
473 /// let mut outer = empty; | 473 /// can arise. |
474 /// let mut shared_iter = shared.map(py, |o| o.chars()); | |
475 /// { | |
476 /// let mut iter = unsafe { shared_iter.try_borrow_mut(py) }; | |
477 /// let inner = iter.next(); // Good, in borrow scope | |
478 /// outer = inner; // Bad, &'static T may outlive | |
479 /// } | |
480 /// ``` | |
481 pub struct SharedByPyObject<T: ?Sized> { | 474 pub struct SharedByPyObject<T: ?Sized> { |
482 owner: PyObject, | 475 owner: PyObject, |
483 state: &'static PySharedState, | 476 state: &'static PySharedState, |
484 /// Generation counter of data `T` captured when SharedByPyObject is | 477 /// Generation counter of data `T` captured when SharedByPyObject is |
485 /// created. | 478 /// created. |
496 impl<T: ?Sized> SharedByPyObject<T> { | 489 impl<T: ?Sized> SharedByPyObject<T> { |
497 // No panicking version of borrow() and borrow_mut() are implemented | 490 // No panicking version of borrow() and borrow_mut() are implemented |
498 // because the underlying value is supposed to be mutated in Python | 491 // because the underlying value is supposed to be mutated in Python |
499 // world, and the Rust library designer can't prevent it. | 492 // world, and the Rust library designer can't prevent it. |
500 | 493 |
501 // try_borrow() and try_borrow_mut() are unsafe because self.data may | |
502 // have a function returning the inner &'static reference. | |
503 // If T is &'static U, its lifetime can be easily coerced to &'a U, but | |
504 // how could we do that for Whatever<'static> in general? | |
505 | |
506 /// Immutably borrows the wrapped value. | 494 /// Immutably borrows the wrapped value. |
507 /// | 495 /// |
508 /// Borrowing fails if the underlying reference has been invalidated. | 496 /// Borrowing fails if the underlying reference has been invalidated. |
509 /// | 497 /// |
510 /// # Safety | 498 /// # Safety |
511 /// | 499 /// |
512 /// The lifetime of the innermost object is artificial. Do not obtain and | 500 /// The lifetime of the innermost object is artificial. Do not obtain and |
513 /// copy it out of the borrow scope. | 501 /// copy it out of the borrow scope. |
502 /// | |
503 /// The lifetime of the innermost object is artificial. Do not obtain and | |
504 /// copy it out of the borrow scope. More generally, the returned `&T` | |
505 /// may have a method returning an inner reference, which would typically | |
506 /// be `'static` and not safe without the `owner` Python object, so the | |
507 /// problem might be less obvious than in the example below. | |
508 /// | |
509 /// The following example does compile and illustrates the problem. | |
510 /// In this case, the data is a `Vec<String>` and the leaked reference | |
511 /// `&'static str`, which points to some element of the vector. This | |
512 /// illustrates that the leaks are not necessarily to the whole of the | |
513 /// shared data. | |
514 /// | |
515 /// ```no_run | |
516 /// # use pyo3::prelude::*; | |
517 /// # use pyo3_sharedref::PyShareable; | |
518 /// #[pyclass] | |
519 /// struct Owner { | |
520 /// value: PyShareable<Vec<String>> | |
521 /// } | |
522 /// | |
523 /// #[pymethods] | |
524 /// impl Owner { | |
525 /// #[new] | |
526 /// fn new(s: &str) -> Self { | |
527 /// let split: Vec<_> = s.split(' ').map(String::from).collect(); | |
528 /// Self { value: split.into() } | |
529 /// } | |
530 /// } | |
531 /// | |
532 /// const EMPTY: &'static str = ""; | |
533 /// | |
534 /// let mut outer = EMPTY; | |
535 /// Python::with_gil(|py| { | |
536 /// let owner = Bound::new(py, Owner::new("hello")).unwrap(); | |
537 /// let shareable = &owner.borrow().value; | |
538 /// let shared = unsafe { shareable.share(&owner) }; | |
539 /// { | |
540 /// let inner = unsafe { shared.try_borrow(py) }.unwrap(); | |
541 /// outer = &inner[0]; // Bad, &'static str does outlive the scope | |
542 /// } | |
543 /// }); | |
544 /// ``` | |
514 pub unsafe fn try_borrow<'a>( | 545 pub unsafe fn try_borrow<'a>( |
515 &'a self, | 546 &'a self, |
516 py: Python<'a>, | 547 py: Python<'a>, |
517 ) -> PyResult<SharedByPyObjectRef<'a, T>> { | 548 ) -> PyResult<SharedByPyObjectRef<'a, T>> { |
518 self.validate_generation(py)?; | 549 self.validate_generation(py)?; |
524 | 555 |
525 /// Mutably borrows the wrapped value. | 556 /// Mutably borrows the wrapped value. |
526 /// | 557 /// |
527 /// Borrowing fails if the underlying reference has been invalidated. | 558 /// Borrowing fails if the underlying reference has been invalidated. |
528 /// | 559 /// |
529 /// Typically `T` is an iterator. If `T` is an immutable reference, | 560 /// Typically `T` would be an iterator obtained by the [`Self::map`] |
530 /// `get_mut()` is useless since the inner value can't be mutated. | 561 /// method. |
531 /// | 562 /// |
532 /// # Safety | 563 /// # Safety |
533 /// | 564 /// |
534 /// The lifetime of the innermost object is artificial. Do not obtain and | 565 /// The lifetime of the innermost object is artificial. Do not obtain and |
535 /// copy it out of the borrow scope. | 566 /// copy it out of the borrow scope. More generally, the returned `&T` |
567 /// may have a method returning an inner reference, which would typically | |
568 /// be `'static` and not safe without the `owner` Python object, so the | |
569 /// problem might be less obvious than in the example below. | |
570 /// | |
571 /// The following example does compile and illustrates the problem. | |
572 /// It is very close to the example given in [`Self::try_borrow`] because | |
573 /// the problem does not arise from the mutability of the reference | |
574 /// returned by this function. | |
575 /// | |
576 /// In this case, the data is a `Vec<String>` and the leaked reference | |
577 /// `&'static str`, which points to some element of the vector. This | |
578 /// illustrates that the leaks are not necessarily to the whole of the | |
579 /// shared data. | |
580 /// | |
581 /// ```no_run | |
582 /// # use pyo3::prelude::*; | |
583 /// # use pyo3_sharedref::PyShareable; | |
584 /// #[pyclass] | |
585 /// struct Owner { | |
586 /// value: PyShareable<Vec<String>> | |
587 /// } | |
588 /// | |
589 /// #[pymethods] | |
590 /// impl Owner { | |
591 /// #[new] | |
592 /// fn new(s: &str) -> Self { | |
593 /// let split: Vec<_> = s.split(' ').map(String::from).collect(); | |
594 /// Self { value: split.into() } | |
595 /// } | |
596 /// } | |
597 /// | |
598 /// const EMPTY: &'static str = ""; | |
599 /// | |
600 /// let mut outer = EMPTY; | |
601 /// Python::with_gil(|py| { | |
602 /// let owner = Bound::new(py, Owner::new("hello")).unwrap(); | |
603 /// let shareable = &owner.borrow().value; | |
604 /// let shared = unsafe { shareable.share(&owner) }; | |
605 /// let mut shared_iter = unsafe { shared.map(py, |o| o.iter()) }; | |
606 /// { | |
607 /// let mut iter = unsafe { | |
608 /// shared_iter.try_borrow_mut(py) | |
609 /// }.unwrap(); | |
610 /// let inner = iter.next().unwrap(); // Good, in borrow scope | |
611 /// outer = inner; // Bad, &'static str does outlive the scope | |
612 /// } | |
613 /// }); | |
614 /// ``` | |
536 pub unsafe fn try_borrow_mut<'a>( | 615 pub unsafe fn try_borrow_mut<'a>( |
537 &'a mut self, | 616 &'a mut self, |
538 py: Python<'a>, | 617 py: Python<'a>, |
539 ) -> PyResult<SharedByPyObjectRefMut<'a, T>> { | 618 ) -> PyResult<SharedByPyObjectRefMut<'a, T>> { |
540 self.validate_generation(py)?; | 619 self.validate_generation(py)?; |