use core::fmt;
use core::hash::Hash;
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
use core::ops::Deref;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr;
use super::{Arc, ArcBorrow};
#[repr(transparent)]
pub struct OffsetArc<T: ?Sized> {
pub(crate) ptr: ptr::NonNull<T>,
pub(crate) phantom: PhantomData<T>,
}
unsafe impl<T: ?Sized + Sync + Send> Send for OffsetArc<T> {}
unsafe impl<T: ?Sized + Sync + Send> Sync for OffsetArc<T> {}
impl<T: ?Sized + RefUnwindSafe> UnwindSafe for OffsetArc<T> {}
impl<T: ?Sized> Deref for OffsetArc<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr.as_ptr() }
}
}
impl<T: ?Sized> Clone for OffsetArc<T> {
#[inline]
fn clone(&self) -> Self {
Arc::into_raw_offset(self.clone_arc())
}
}
impl<T: ?Sized> Drop for OffsetArc<T> {
fn drop(&mut self) {
let _ = Arc::from_raw_offset(OffsetArc {
ptr: self.ptr,
phantom: PhantomData,
});
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for OffsetArc<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<T: ?Sized + PartialEq> PartialEq for OffsetArc<T> {
fn eq(&self, other: &OffsetArc<T>) -> bool {
*(*self) == *(*other)
}
#[allow(clippy::partialeq_ne_impl)]
fn ne(&self, other: &OffsetArc<T>) -> bool {
*(*self) != *(*other)
}
}
impl<T: ?Sized + Eq> Eq for OffsetArc<T> {}
impl<T: ?Sized + PartialOrd> PartialOrd for OffsetArc<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
(**self).partial_cmp(&**other)
}
}
impl<T: ?Sized + Ord> Ord for OffsetArc<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
(**self).cmp(&**other)
}
}
impl<T: ?Sized + Hash> Hash for OffsetArc<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
(**self).hash(state)
}
}
impl<T> OffsetArc<T> {
#[inline]
pub fn make_mut(&mut self) -> &mut T
where
T: Clone,
{
struct DropGuard<'a, T> {
arc: ManuallyDrop<Arc<T>>,
this: &'a mut OffsetArc<T>,
}
impl<'a, T> Drop for DropGuard<'a, T> {
fn drop(&mut self) {
unsafe {
let arc = ManuallyDrop::take(&mut self.arc);
ptr::write(self.this, Arc::into_raw_offset(arc));
}
}
}
let this = unsafe { ptr::read(self) };
let mut guard = DropGuard {
arc: ManuallyDrop::new(Arc::from_raw_offset(this)),
this: self,
};
let ret = Arc::make_mut(&mut *guard.arc) as *mut T;
drop(guard);
unsafe { &mut *ret }
}
}
impl<T: ?Sized> OffsetArc<T> {
#[inline]
pub fn with_arc<F, U>(&self, f: F) -> U
where
F: FnOnce(&Arc<T>) -> U,
{
let transient = unsafe { ManuallyDrop::new(Arc::from_raw(self.ptr.as_ptr())) };
f(&transient)
}
#[inline]
pub fn clone_arc(&self) -> Arc<T> {
OffsetArc::with_arc(self, |a| a.clone())
}
#[inline]
pub const fn borrow_arc(&self) -> ArcBorrow<'_, T> {
ArcBorrow(self.ptr, PhantomData)
}
#[inline]
pub fn strong_count(this: &Self) -> usize {
Self::with_arc(this, |arc| Arc::strong_count(arc))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn offset_arc_smoke() {
let arc = Arc::new(42);
let offset = Arc::into_raw_offset(arc);
assert_eq!(*offset, 42);
assert_eq!(OffsetArc::strong_count(&offset), 1);
let offset2 = offset.clone();
assert_eq!(OffsetArc::strong_count(&offset), 2);
assert_eq!(offset, offset2);
let regular_arc = offset2.clone_arc();
assert_eq!(Arc::strong_count(®ular_arc), 3);
drop(regular_arc);
drop(offset2);
assert_eq!(OffsetArc::strong_count(&offset), 1);
let mut offset = offset;
*offset.make_mut() = 99;
assert_eq!(*offset, 99);
let offset2 = offset.clone();
*offset.make_mut() = 100;
assert_eq!(*offset, 100);
assert_eq!(*offset2, 99);
}
#[test]
fn offset_arc_str() {
let s: OffsetArc<str> = OffsetArc::from("hello world");
assert_eq!(&*s, "hello world");
assert_eq!(OffsetArc::strong_count(&s), 1);
let b = s.borrow_arc();
assert_eq!(&*b, "hello world");
let s2 = s.clone();
assert_eq!(OffsetArc::strong_count(&s), 2);
assert_eq!(s, s2);
}
#[test]
#[cfg(feature = "std")]
fn safe_offset_arc_make_mut_reentrant_drop_panic_uaf() {
use core::sync::atomic::{AtomicBool, Ordering};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::{Arc as StdArc, Mutex};
struct ReentrantClone {
value: usize,
sibling: StdArc<Mutex<Option<Arc<ReentrantClone>>>>,
drop_panicked: StdArc<AtomicBool>,
}
impl Clone for ReentrantClone {
fn clone(&self) -> Self {
drop(self.sibling.lock().unwrap().take());
Self {
value: self.value,
sibling: self.sibling.clone(),
drop_panicked: self.drop_panicked.clone(),
}
}
}
impl Drop for ReentrantClone {
fn drop(&mut self) {
if !self.drop_panicked.swap(true, Ordering::SeqCst) {
panic!("old payload drop sentinel");
}
}
}
let sibling = StdArc::new(Mutex::new(None));
let drop_panicked = StdArc::new(AtomicBool::new(false));
let arc = Arc::new(ReentrantClone {
value: 37,
sibling: sibling.clone(),
drop_panicked,
});
*sibling.lock().unwrap() = Some(arc.clone());
let mut offset: OffsetArc<ReentrantClone> = Arc::into_raw_offset(arc);
let result = catch_unwind(AssertUnwindSafe(|| {
let _ = OffsetArc::make_mut(&mut offset);
}));
assert!(result.is_err());
assert_eq!(offset.value, 37);
}
}