// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). //! Types for optional pointers with niche optimization. //! //! The main type is [`CartableOptionPointer`], which is like `Option` but //! with a niche so that the resulting `Yoke` has a niche. The following four //! types can be stored in the `CartableOptionPointer`: //! //! 1. `&T` //! 2. `Box` //! 3. `Rc` //! 4. `Arc` //! //! These four types implement the sealed unsafe trait [`CartablePointerLike`]. //! In addition, all except `Box` impl [`CloneableCartablePointerLike`], //! which allows [`CartableOptionPointer`] to implement `Clone`. use crate::CloneableCart; #[cfg(feature = "alloc")] use alloc::boxed::Box; #[cfg(feature = "alloc")] use alloc::rc::Rc; #[cfg(feature = "alloc")] use alloc::sync::Arc; #[cfg(test)] use core::cell::Cell; use core::marker::PhantomData; use core::ptr::NonNull; use stable_deref_trait::StableDeref; // Safety note: this method MUST return the same value for the same T, even if i.e. the method gets // instantiated in different crates. This can be untrue in surprising ways! For example, just // returning a const-ref-to-const would not guarantee that. // The current implementation always returns the same address for any T, see // [the reference](https://doc.rust-lang.org/reference/items/static-items.html#statics--generics): // there is exactly one `SENTINEL` item for any T. #[inline] fn sentinel_for() -> NonNull { static SENTINEL: &u8 = &0x1a; // SUB // Safety: SENTINEL is indeed not a null pointer, even after the casts. unsafe { NonNull::new_unchecked(SENTINEL as *const u8 as *mut T) } } #[cfg(test)] thread_local! { static DROP_INVOCATIONS: Cell = const { Cell::new(0) }; } mod private { pub trait Sealed {} } use private::Sealed; /// An object fully representable by a non-null pointer. /// /// # Safety /// /// Implementer safety: /// /// 1. `into_raw` transfers ownership of the values referenced by StableDeref to the caller, /// if there is ownership to transfer /// 2. `drop_raw` returns ownership back to the impl, if there is ownership to transfer /// /// Note: if `into_raw` returns the sentinel pointer, memory leaks may occur, but this will not /// lead to undefined behaviour. /// /// Note: the pointer `NonNull` may or may not be aligned and it should never /// be dereferenced. Rust allows unaligned pointers; see [`std::ptr::read_unaligned`]. pub unsafe trait CartablePointerLike: StableDeref + Sealed { /// The raw type used for [`Self::into_raw`] and [`Self::drop_raw`]. #[doc(hidden)] type Raw; /// Converts this pointer-like into a pointer. #[doc(hidden)] fn into_raw(self) -> NonNull; /// Drops any memory associated with this pointer-like. /// /// # Safety /// /// Caller safety: /// /// 1. The pointer MUST have been returned by this impl's `into_raw`. /// 2. The pointer MUST NOT be dangling. #[doc(hidden)] unsafe fn drop_raw(pointer: NonNull); } /// An object that implements [`CartablePointerLike`] that also /// supports cloning without changing the address of referenced data. /// /// # Safety /// /// Implementer safety: /// /// 1. `addref_raw` must create a new owner such that an additional call to /// `drop_raw` does not create a dangling pointer /// 2. `addref_raw` must not change the address of any referenced data. pub unsafe trait CloneableCartablePointerLike: CartablePointerLike { /// Clones this pointer-like. /// /// # Safety /// /// Caller safety: /// /// 1. The pointer MUST have been returned by this impl's `into_raw`. /// 2. The pointer MUST NOT be dangling. #[doc(hidden)] unsafe fn addref_raw(pointer: NonNull); } impl<'a, T> Sealed for &'a T {} // Safety: // 1. There is no ownership to transfer // 2. There is no ownership to transfer unsafe impl<'a, T> CartablePointerLike for &'a T { type Raw = T; #[inline] fn into_raw(self) -> NonNull { self.into() } #[inline] unsafe fn drop_raw(_pointer: NonNull) { // No-op: references are borrowed from elsewhere } } // Safety: // 1. There is no ownership // 2. The impl is a no-op so no addresses are changed. unsafe impl<'a, T> CloneableCartablePointerLike for &'a T { #[inline] unsafe fn addref_raw(_pointer: NonNull) { // No-op: references are borrowed from elsewhere } } #[cfg(feature = "alloc")] impl Sealed for Box {} #[cfg(feature = "alloc")] // Safety: // 1. `Box::into_raw` says: "After calling this function, the caller is responsible for the // memory previously managed by the Box." // 2. `Box::from_raw` says: "After calling this function, the raw pointer is owned by the // resulting Box." unsafe impl CartablePointerLike for Box { type Raw = T; #[inline] fn into_raw(self) -> NonNull { // Safety: `Box::into_raw` says: "The pointer will be properly aligned and non-null." unsafe { NonNull::new_unchecked(Box::into_raw(self)) } } #[inline] unsafe fn drop_raw(pointer: NonNull) { // Safety: per the method's precondition, `pointer` is dereferenceable and was returned by // `Self::into_raw`, i.e. by `Box::into_raw`. In this circumstances, calling // `Box::from_raw` is safe. let _box = unsafe { Box::from_raw(pointer.as_ptr()) }; // Boxes are always dropped #[cfg(test)] DROP_INVOCATIONS.with(|x| x.set(x.get() + 1)) } } #[cfg(feature = "alloc")] impl Sealed for Rc {} #[cfg(feature = "alloc")] // Safety: // 1. `Rc::into_raw` says: "Consumes the Rc, returning the wrapped pointer. To avoid a memory // leak the pointer must be converted back to an Rc using Rc::from_raw." // 2. See 1. unsafe impl CartablePointerLike for Rc { type Raw = T; #[inline] fn into_raw(self) -> NonNull { // Safety: Rcs must contain data (and not be null) unsafe { NonNull::new_unchecked(Rc::into_raw(self) as *mut T) } } #[inline] unsafe fn drop_raw(pointer: NonNull) { // Safety: per the method's precondition, `pointer` is dereferenceable and was returned by // `Self::into_raw`, i.e. by `Rc::into_raw`. In this circumstances, calling // `Rc::from_raw` is safe. let _rc = unsafe { Rc::from_raw(pointer.as_ptr()) }; // Rc is dropped if refcount is 1 #[cfg(test)] if Rc::strong_count(&_rc) == 1 { DROP_INVOCATIONS.with(|x| x.set(x.get() + 1)) } } } #[cfg(feature = "alloc")] // Safety: // 1. The impl increases the refcount such that `Drop` will decrease it. // 2. The impl increases refcount without changing the address of data. unsafe impl CloneableCartablePointerLike for Rc { #[inline] unsafe fn addref_raw(pointer: NonNull) { // Safety: The caller safety of this function says that: // 1. The pointer was obtained through Rc::into_raw // 2. The associated Rc instance is valid // Further, this impl is not defined for anything but the global allocator. unsafe { Rc::increment_strong_count(pointer.as_ptr()); } } } #[cfg(feature = "alloc")] impl Sealed for Arc {} #[cfg(feature = "alloc")] // Safety: // 1. `Rc::into_raw` says: "Consumes the Arc, returning the wrapped pointer. To avoid a memory // leak the pointer must be converted back to an Arc using Arc::from_raw." // 2. See 1. unsafe impl CartablePointerLike for Arc { type Raw = T; #[inline] fn into_raw(self) -> NonNull { // Safety: Arcs must contain data (and not be null) unsafe { NonNull::new_unchecked(Arc::into_raw(self) as *mut T) } } #[inline] unsafe fn drop_raw(pointer: NonNull) { // Safety: per the method's precondition, `pointer` is dereferenceable and was returned by // `Self::into_raw`, i.e. by `Rc::into_raw`. In this circumstances, calling // `Rc::from_raw` is safe. let _arc = unsafe { Arc::from_raw(pointer.as_ptr()) }; // Arc is dropped if refcount is 1 #[cfg(test)] if Arc::strong_count(&_arc) == 1 { DROP_INVOCATIONS.with(|x| x.set(x.get() + 1)) } } } #[cfg(feature = "alloc")] // Safety: // 1. The impl increases the refcount such that `Drop` will decrease it. // 2. The impl increases refcount without changing the address of data. unsafe impl CloneableCartablePointerLike for Arc { #[inline] unsafe fn addref_raw(pointer: NonNull) { // Safety: The caller safety of this function says that: // 1. The pointer was obtained through Arc::into_raw // 2. The associated Arc instance is valid // Further, this impl is not defined for anything but the global allocator. unsafe { Arc::increment_strong_count(pointer.as_ptr()); } } } /// A type with similar semantics as `Option>` but with a niche. /// /// This type cannot be publicly constructed. To use this in a `Yoke`, see /// [`Yoke::convert_cart_into_option_pointer`]. /// /// [`Yoke::convert_cart_into_option_pointer`]: crate::Yoke::convert_cart_into_option_pointer #[derive(Debug)] pub struct CartableOptionPointer where C: CartablePointerLike, { /// The inner pointer. /// /// # Invariants /// /// 1. Must be either `SENTINEL_PTR` or created from `CartablePointerLike::into_raw` /// 2. If non-sentinel, must _always_ be for a valid SelectedRc inner: NonNull, _cartable: PhantomData, } impl CartableOptionPointer where C: CartablePointerLike, { /// Creates a new instance corresponding to a `None` value. #[inline] pub(crate) fn none() -> Self { Self { inner: sentinel_for::(), _cartable: PhantomData, } } /// Creates a new instance corresponding to a `Some` value. #[inline] pub(crate) fn from_cartable(cartable: C) -> Self { let inner = cartable.into_raw(); debug_assert_ne!(inner, sentinel_for::()); Self { inner, _cartable: PhantomData, } } /// Returns whether this instance is `None`. From the return value: /// /// - If `true`, the instance is `None` /// - If `false`, the instance is a valid `SelectedRc` #[inline] pub fn is_none(&self) -> bool { self.inner == sentinel_for::() } } impl Drop for CartableOptionPointer where C: CartablePointerLike, { #[inline] fn drop(&mut self) { let ptr = self.inner; if ptr != sentinel_for::() { // By the invariants, `ptr` is a valid raw value since it's // either that or sentinel, and we just checked for sentinel. // We will replace it with the sentinel and then drop `ptr`. self.inner = sentinel_for::(); // Safety: by the invariants, `ptr` is a valid raw value. unsafe { C::drop_raw(ptr) } } } } impl Clone for CartableOptionPointer where C: CloneableCartablePointerLike, { #[inline] fn clone(&self) -> Self { let ptr = self.inner; if ptr != sentinel_for::() { // By the invariants, `ptr` is a valid raw value since it's // either that or sentinel, and we just checked for sentinel. // Safety: by the invariants, `ptr` is a valid raw value. unsafe { C::addref_raw(ptr) } } Self { inner: self.inner, _cartable: PhantomData, } } } // Safety: logically an Option. Has same bounds as Option. // The `StableDeref` parts of `C` continue to be `StableDeref`. unsafe impl CloneableCart for CartableOptionPointer where C: CloneableCartablePointerLike + CloneableCart { } // Safety: logically an Option. Has same bounds as Option unsafe impl Send for CartableOptionPointer where C: Sync + CartablePointerLike {} // Safety: logically an Option. Has same bounds as Option unsafe impl Sync for CartableOptionPointer where C: Send + CartablePointerLike {} #[cfg(test)] mod tests { use super::*; use crate::Yoke; use core::mem::size_of; const SAMPLE_BYTES: &[u8] = b"abCDEfg"; const W: usize = size_of::(); #[test] fn test_sizes() { assert_eq!(W * 4, size_of::>()); assert_eq!(W * 4, size_of::>>()); assert_eq!( W * 4, size_of::>>() ); assert_eq!(W * 4, size_of::>>()); assert_eq!(W * 5, size_of::>>>()); assert_eq!( W * 4, size_of::>>>() ); } #[test] fn test_new_sentinel() { let start = DROP_INVOCATIONS.with(Cell::get); { let _ = CartableOptionPointer::>::none(); } assert_eq!(start, DROP_INVOCATIONS.with(Cell::get)); { let _ = CartableOptionPointer::>::none(); } assert_eq!(start, DROP_INVOCATIONS.with(Cell::get)); } #[test] fn test_new_rc() { let start = DROP_INVOCATIONS.with(Cell::get); { let _ = CartableOptionPointer::>::from_cartable(SAMPLE_BYTES.into()); } assert_eq!(start + 1, DROP_INVOCATIONS.with(Cell::get)); } #[test] fn test_rc_clone() { let start = DROP_INVOCATIONS.with(Cell::get); { let x = CartableOptionPointer::>::from_cartable(SAMPLE_BYTES.into()); assert_eq!(start, DROP_INVOCATIONS.with(Cell::get)); { let _ = x.clone(); } assert_eq!(start, DROP_INVOCATIONS.with(Cell::get)); { let _ = x.clone(); let _ = x.clone(); let _ = x.clone(); } assert_eq!(start, DROP_INVOCATIONS.with(Cell::get)); } assert_eq!(start + 1, DROP_INVOCATIONS.with(Cell::get)); } }