diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4df0019 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +## 0.2.2 +- Switch `TypeErasedArc` and `TypeErasedRc` to use `ManuallyDrop` when converting the raw pointer into a concrete `Arc`, `Rc` or `Weak` to avoid incorrect behavior in case any method we call panics. Previously, the temporary would be dropped even if it shouldn't have, causing potential UB (use after free). +- Add `Prc::try_from_rc`, `Prc::try_project`, `Parc::try_from_arc`, and `Parc::try_project`. These are fallible versions of the same methods without `try_`, where the `FnOnce` that's passed to these functions returns an `Option`. This allows for using `Prc` and `Parc` where the projected reference might be unavailable. + +## 0.2.1 +- Fix incorrect lifetime bounds on `Prc::from_rc` and `Parc::from_arc`. + +## 0.2.0 +- Fix the soundness issue from 0.1.0 by requiring that any `T` returned by any projecting function is `T: 'static`. This change technically restricts the types we can use with `Prc` and `Parc`, but doesn't interfere with any intended usage of the crate. + +*(yanked due to flipped bounds on `project` functions causing a soundness hole)* + +## 0.1.0 +Initial release. + +*(yanked due to a soundness hole where it was possible to use a reference to a reference that didn't live long enough)* \ No newline at end of file diff --git a/src/prc.rs b/src/prc.rs index 84458ce..2f71de4 100644 --- a/src/prc.rs +++ b/src/prc.rs @@ -159,6 +159,7 @@ impl Prc { /// let local = 5; /// let prc = Prc::from_rc(&rc, |tuple| &local); /// ``` + #[inline] pub fn from_rc(rc: &Rc, project: F) -> Self where U: ?Sized, @@ -175,6 +176,48 @@ impl Prc { } } + /// Constructs a new `Option>` from an existing `Rc` by trying to project a field. + /// + /// If the function passed into this returns `None`, this method will also return `None`. + /// + /// # Panics + /// If `f` panics, the panic is propagated to the caller and the rc won't be cloned. + /// + /// # Example + /// ``` + /// use std::rc::Rc; + /// use pared::prc::Prc; + /// + /// enum Enum { + /// Str(String), + /// Int(isize), + /// } + /// + /// let rc = Rc::new(Enum::Int(5)); + /// let prc = Prc::try_from_rc(&rc, |x| match x { + /// Enum::Str(s) => None, + /// Enum::Int(i) => Some(i), + /// }); + /// + /// assert!(matches!(prc, Some(prc) if *prc == 5 )); + /// ``` + #[inline] + pub fn try_from_rc(rc: &Rc, project: F) -> Option + where + U: ?Sized, + T: 'static, + F: for<'x> FnOnce(&'x U) -> Option<&'x T>, + { + let projected = project(rc)?; + // SAFETY: fn shouldn't be able to capture any local references + // which should mean that the projection done by f is safe + let projected = unsafe { NonNull::new_unchecked(projected as *const T as *mut T) }; + Some(Self { + rc: TypeErasedRc::new(rc.clone()), + projected, + }) + } + /// Constructs a new `Prc` from an existing `Prc` by projecting a field. /// /// # Panics @@ -194,6 +237,7 @@ impl Prc { /// let local = 5; /// let projected = prc.project(|tuple| &local); /// ``` + #[inline] pub fn project(&self, project: F) -> Prc where U: ?Sized + 'static, @@ -209,6 +253,46 @@ impl Prc { } } + /// Constructs a new `Option>` from an existing `Prc` by trying to projecting a field. + /// + /// If the function passed into this returns `None`, this method will also return `None`. + /// + /// # Panics + /// If `f` panics, the panic is propagated to the caller and the underlying rc won't be cloned. + /// + /// # Example + /// ``` + /// use pared::prc::Prc; + /// + /// enum Enum { + /// Str(String), + /// Int(isize), + /// } + /// + /// let prc = Prc::new(Enum::Int(5)); + /// let projected = prc.try_project(|x| match x { + /// Enum::Str(s) => None, + /// Enum::Int(i) => Some(i), + /// }); + /// + /// assert!(matches!(projected, Some(p) if *p == 5 )); + /// ``` + #[inline] + pub fn try_project(&self, project: F) -> Option> + where + U: ?Sized + 'static, + F: for<'x> FnOnce(&'x T) -> Option<&'x U>, + { + let projected = project(self)?; + // SAFETY: fn shouldn't be able to capture any local references + // which should mean that the projection done by f is safe + let projected = unsafe { NonNull::new_unchecked(projected as *const U as *mut U) }; + Some(Prc:: { + rc: self.rc.clone(), + projected, + }) + } + /// Provides a raw pointer to the data. /// /// The counts are not affected in any way and the `Prc` is not consumed. The pointer is valid for @@ -268,6 +352,7 @@ impl Prc { /// ``` /// /// [`Rc::weak_count`]: https://doc.rust-lang.org/std/rc/struct.Rc.html#method.weak_count + #[inline] pub fn weak_count(this: &Prc) -> usize { this.rc.weak_count() } @@ -286,6 +371,7 @@ impl Prc { /// ``` /// /// [`Rc::weak_count`]: https://doc.rust-lang.org/std/rc/struct.Rc.html#method.strong_count + #[inline] pub fn strong_count(this: &Prc) -> usize { this.rc.strong_count() } @@ -312,18 +398,21 @@ impl Prc { } impl AsRef for Prc { + #[inline] fn as_ref(&self) -> &T { self.deref() } } impl core::borrow::Borrow for Prc { + #[inline] fn borrow(&self) -> &T { self.deref() } } impl Clone for Prc { + #[inline] fn clone(&self) -> Self { Self { rc: self.rc.clone(), @@ -363,6 +452,7 @@ impl std::error::Error for Prc where T: std::error::Error + ?Sized, { + #[inline] fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.deref().source() } @@ -373,6 +463,7 @@ where T: ?Sized + 'static, F: Into>, { + #[inline] fn from(value: F) -> Self { Prc::from_rc(&value.into(), |x| x) } @@ -382,6 +473,7 @@ impl FromIterator for Prc<[T]> where T: 'static, { + #[inline] fn from_iter>(iter: I) -> Self { iter.into_iter().collect::>().into() } @@ -391,6 +483,7 @@ impl Hash for Prc where T: Hash + ?Sized, { + #[inline] fn hash(&self, state: &mut H) { self.deref().hash(state) } @@ -400,6 +493,7 @@ impl PartialEq> for Prc where T: PartialEq + ?Sized, { + #[inline] fn eq(&self, other: &Prc) -> bool { let this: &T = self; let other: &T = other; @@ -413,6 +507,7 @@ impl Ord for Prc where T: Ord + ?Sized, { + #[inline] fn cmp(&self, other: &Self) -> core::cmp::Ordering { let this: &T = self; let other: &T = other; @@ -424,6 +519,7 @@ impl PartialOrd> for Prc where T: PartialOrd + ?Sized, { + #[inline] fn partial_cmp(&self, other: &Prc) -> Option { self.deref().partial_cmp(other) } @@ -433,6 +529,7 @@ impl core::fmt::Pointer for Prc where T: ?Sized, { + #[inline] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Pointer::fmt(&self.projected, f) } @@ -501,6 +598,7 @@ impl Weak { /// /// [`null`]: core::ptr::null "ptr::null" #[must_use] + #[inline] pub fn as_ptr(&self) -> *const T { NonNull::as_ptr(self.projected) } @@ -527,6 +625,7 @@ impl Weak { /// /// assert!(weak_five.upgrade().is_none()); /// ``` + #[inline] pub fn upgrade(&self) -> Option> { Some(Prc { rc: self.weak.upgrade()?, @@ -535,6 +634,7 @@ impl Weak { } /// Returns the number of strong pointers pointing to this allocation. + #[inline] pub fn strong_count(&self) -> usize { self.weak.strong_count() } @@ -544,6 +644,7 @@ impl Weak { /// See [`std::sync::Weak::weak_count`] for more details. /// /// [`std::sync::Weak::weak_count`]: https://doc.rust-lang.org/std/rc/struct.Weak.html#method.weak_count + #[inline] pub fn weak_count(&self) -> usize { self.weak.weak_count() } @@ -553,12 +654,14 @@ impl Weak { /// /// This function is able to compare `Weak` pointers even when either or both of them /// can't successfully `upgrade` anymore. + #[inline] pub fn ptr_eq(&self, other: &Weak) -> bool { core::ptr::eq(self.projected.as_ptr(), other.projected.as_ptr()) } } impl Clone for Weak { + #[inline] fn clone(&self) -> Self { Self { weak: self.weak.clone(), diff --git a/src/sync.rs b/src/sync.rs index 2e859fa..bb34c0b 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -219,6 +219,48 @@ impl Parc { } } + /// Constructs a new `Option>` from an existing `Arc` by trying to project a field. + /// + /// If the function passed into this returns `None`, this method will also return `None`. + /// + /// # Panics + /// If `f` panics, the panic is propagated to the caller and the rc won't be cloned. + /// + /// # Example + /// ``` + /// use std::sync::Arc; + /// use pared::sync::Parc; + /// + /// enum Enum { + /// Str(String), + /// Int(isize), + /// } + /// + /// let arc = Arc::new(Enum::Int(5)); + /// let parc = Parc::try_from_arc(&arc, |x| match x { + /// Enum::Str(s) => None, + /// Enum::Int(i) => Some(i), + /// }); + /// + /// assert!(matches!(parc, Some(parc) if *parc == 5 )); + /// ``` + #[inline] + pub fn try_from_arc(arc: &Arc, project: F) -> Option + where + U: ?Sized + Sync + Send, + T: 'static, + F: for<'x> FnOnce(&'x U) -> Option<&'x T>, + { + let projected = project(arc)?; + // SAFETY: fn shouldn't be able to capture any local references + // which should mean that the projection done by f is safe + let projected = unsafe { NonNull::new_unchecked(projected as *const T as *mut T) }; + Some(Self { + arc: TypeErasedArc::new(arc.clone()), + projected, + }) + } + /// Constructs a new `Parc` from an existing `Parc` by projecting a field. /// /// # Panics @@ -256,6 +298,47 @@ impl Parc { projected, } } + + /// Constructs a new `Option>` from an existing `Parc` + /// by trying to projecting a field. + /// + /// If the function passed into this returns `None`, this method will also return `None`. + /// + /// # Panics + /// If `f` panics, the panic is propagated to the caller and the underlying rc won't be cloned. + /// + /// # Example + /// ``` + /// use pared::sync::Parc; + /// + /// enum Enum { + /// Str(String), + /// Int(isize), + /// } + /// + /// let prc = Parc::new(Enum::Int(5)); + /// let projected = prc.try_project(|x| match x { + /// Enum::Str(s) => None, + /// Enum::Int(i) => Some(i), + /// }); + /// + /// assert!(matches!(projected, Some(p) if *p == 5 )); + /// ``` + pub fn try_project(&self, project: F) -> Option> + where + T: Send + Sync, + U: ?Sized + 'static, + F: for<'x> FnOnce(&'x T) -> Option<&'x U>, + { + let projected = project(self)?; + // SAFETY: fn shouldn't be able to capture any local references + // which should mean that the projection done by f is safe + let projected = unsafe { NonNull::new_unchecked(projected as *const U as *mut U) }; + Some(Parc:: { + arc: self.arc.clone(), + projected, + }) + } /// Provides a raw pointer to the data. /// /// The counts are not affected in any way and the `Parc` is not consumed. The pointer is valid for diff --git a/tests/parc.rs b/tests/parc.rs index 9271ed1..7b201f7 100644 --- a/tests/parc.rs +++ b/tests/parc.rs @@ -111,3 +111,34 @@ fn projection_of_dyn() { assert_eq!(formatted, "Hello!"); } + +#[test] +fn fallible_projections() { + enum Test { + A(String), + B, + } + + fn try_project(t: &Test) -> Option<&str> { + match t { + Test::A(s) => Some(s), + Test::B => None, + } + } + + let arc = Arc::new(Test::B); + let parc = Parc::try_from_arc(&arc, try_project); + assert!(parc.is_none()); + + let parc = Parc::new(Test::B); + let parc = parc.try_project(try_project); + assert!(parc.is_none()); + + let arc = Arc::new(Test::A("Hi!".to_owned())); + let parc = Parc::try_from_arc(&arc, try_project); + assert!(matches!(parc, Some(p) if &*p == "Hi!")); + + let parc = Parc::new(Test::A("Hi!".to_owned())); + let parc = parc.try_project(try_project); + assert!(matches!(parc, Some(p) if &*p == "Hi!")); +} diff --git a/tests/prc.rs b/tests/prc.rs index f08ce78..a3daa14 100644 --- a/tests/prc.rs +++ b/tests/prc.rs @@ -111,3 +111,34 @@ fn projection_of_dyn() { assert_eq!(formatted, "Hello!"); } + +#[test] +fn fallible_projections() { + enum Test { + A(String), + B, + } + + fn try_project(t: &Test) -> Option<&str> { + match t { + Test::A(s) => Some(s), + Test::B => None, + } + } + + let rc = Rc::new(Test::B); + let prc = Prc::try_from_rc(&rc, try_project); + assert!(prc.is_none()); + + let prc = Prc::new(Test::B); + let prc = prc.try_project(try_project); + assert!(prc.is_none()); + + let rc = Rc::new(Test::A("Hi!".to_owned())); + let prc = Prc::try_from_rc(&rc, try_project); + assert!(matches!(prc, Some(p) if &*p == "Hi!")); + + let prc = Prc::new(Test::A("Hi!".to_owned())); + let prc = prc.try_project(try_project); + assert!(matches!(prc, Some(p) if &*p == "Hi!")); +}