Skip to main content

triomphe/
offset_arc.rs

1use core::fmt;
2use core::hash::Hash;
3use core::marker::PhantomData;
4use core::mem::ManuallyDrop;
5use core::ops::Deref;
6use core::panic::{RefUnwindSafe, UnwindSafe};
7use core::ptr;
8
9use super::{Arc, ArcBorrow};
10
11/// An `Arc`, except it holds a pointer to the T instead of to the
12/// entire ArcInner.
13///
14/// An `OffsetArc<T>` has the same layout and ABI as a non-null
15/// `const T*` in C, and may be used in FFI function signatures.
16///
17/// ```text
18///  Arc<T>    OffsetArc<T>
19///   |          |
20///   v          v
21///  ---------------------
22/// | RefCount | T (data) | [ArcInner<T>]
23///  ---------------------
24/// ```
25///
26/// This means that this is a direct pointer to
27/// its contained data (and can be read from by both C++ and Rust),
28/// but we can also convert it to a "regular" `Arc<T>` by removing the offset.
29///
30/// This is very useful if you have an Arc-containing struct shared between Rust and C++,
31/// and wish for C++ to be able to read the data behind the `Arc` without incurring
32/// an FFI call overhead.
33#[repr(transparent)]
34pub struct OffsetArc<T: ?Sized> {
35    // Safety invariant: `ptr` is non-null, properly aligned, and points to the
36    // initialized `data: T` field of a live `ArcInner<T>` heap allocation. It
37    // retains full provenance over the entire `ArcInner<T>` allocation (as
38    // obtained from `Arc::into_raw`), and this `OffsetArc` owns one strong
39    // reference count on that `ArcInner<T>`.
40    pub(crate) ptr: ptr::NonNull<T>,
41    pub(crate) phantom: PhantomData<T>,
42}
43
44// SAFETY:
45// `OffsetArc<T>` is semantically an `Arc<T>` stored at the `data` field offset:
46// moving it across threads transfers ownership of one strong reference to the
47// underlying `ArcInner<T>`, which may share `T` across threads (`T: Sync`) and
48// drop `T` on the last thread (`T: Send`).
49unsafe impl<T: ?Sized + Sync + Send> Send for OffsetArc<T> {}
50
51// SAFETY:
52// Sharing `&OffsetArc<T>` across threads only provides shared access (`&T`) to
53// the payload (`T: Sync`), unless cloned and dropped on another thread (`T: Send`).
54unsafe impl<T: ?Sized + Sync + Send> Sync for OffsetArc<T> {}
55
56impl<T: ?Sized + RefUnwindSafe> UnwindSafe for OffsetArc<T> {}
57
58impl<T: ?Sized> Deref for OffsetArc<T> {
59    type Target = T;
60
61    #[inline]
62    fn deref(&self) -> &Self::Target {
63        // SAFETY:
64        // By the `OffsetArc::ptr` field invariant, `self.ptr` is non-null,
65        // properly aligned, and points to an initialized `T` inside a live
66        // `ArcInner<T>` allocation whose strong count is at least 1 for the
67        // lifetime of `&self`. Shared access to `T` is sound because `Arc` only
68        // permits mutation when uniquely owned (`make_mut` / `get_mut`).
69        unsafe { &*self.ptr.as_ptr() }
70    }
71}
72
73impl<T: ?Sized> Clone for OffsetArc<T> {
74    #[inline]
75    fn clone(&self) -> Self {
76        Arc::into_raw_offset(self.clone_arc())
77    }
78}
79
80impl<T: ?Sized> Drop for OffsetArc<T> {
81    fn drop(&mut self) {
82        let _ = Arc::from_raw_offset(OffsetArc {
83            ptr: self.ptr,
84            phantom: PhantomData,
85        });
86    }
87}
88
89impl<T: ?Sized + fmt::Debug> fmt::Debug for OffsetArc<T> {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        fmt::Debug::fmt(&**self, f)
92    }
93}
94
95impl<T: ?Sized + PartialEq> PartialEq for OffsetArc<T> {
96    fn eq(&self, other: &OffsetArc<T>) -> bool {
97        *(*self) == *(*other)
98    }
99
100    #[allow(clippy::partialeq_ne_impl)]
101    fn ne(&self, other: &OffsetArc<T>) -> bool {
102        *(*self) != *(*other)
103    }
104}
105
106impl<T: ?Sized + Eq> Eq for OffsetArc<T> {}
107
108impl<T: ?Sized + PartialOrd> PartialOrd for OffsetArc<T> {
109    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
110        (**self).partial_cmp(&**other)
111    }
112}
113
114impl<T: ?Sized + Ord> Ord for OffsetArc<T> {
115    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
116        (**self).cmp(&**other)
117    }
118}
119
120impl<T: ?Sized + Hash> Hash for OffsetArc<T> {
121    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
122        (**self).hash(state)
123    }
124}
125
126impl<T> OffsetArc<T> {
127    /// If uniquely owned, provide a mutable reference
128    /// Else create a copy, and mutate that
129    ///
130    /// This is functionally the same thing as `Arc::make_mut`
131    #[inline]
132    pub fn make_mut(&mut self) -> &mut T
133    where
134        T: Clone,
135    {
136        // It is possible for `Arc::make_mut` to replace the Arc, or panic during
137        // cloning or dropping the replaced old allocation.
138        // We use a drop guard to ensure `self` is always updated to point to the current Arc,
139        // even if a panic unwinds out of `Arc::make_mut`.
140        struct DropGuard<'a, T> {
141            // Safety invariant: `arc` is initialized and owns the active strong
142            // reference originally moved out of `*this` (or its replacement
143            // installed by `Arc::make_mut`) until `DropGuard::drop` takes it.
144            arc: ManuallyDrop<Arc<T>>,
145            this: &'a mut OffsetArc<T>,
146        }
147
148        impl<'a, T> Drop for DropGuard<'a, T> {
149            fn drop(&mut self) {
150                // SAFETY:
151                // 1. `ManuallyDrop::take(&mut self.arc)`: By `DropGuard`'s
152                //    invariant, `self.arc` is initialized and `take` is called
153                //    only once here in `Drop::drop`. Even if `Arc::make_mut`
154                //    unwinds during `T::clone` or during the old `Arc<T>`'s
155                //    `Drop` in `*this = Arc::new(...)`, Rust place-assignment
156                //    semantics move the old value out and write the new `Arc<T>`
157                //    into `*this` before dropping the old temporary, so
158                //    `self.arc` always holds a valid, live `Arc<T>`.
159                // 2. `ptr::write(self.this, Arc::into_raw_offset(arc))`:
160                //    `self.this` is a valid, aligned mutable reference whose
161                //    previous `OffsetArc<T>` was logically moved out by
162                //    `ptr::read` before constructing `DropGuard`. Writing the
163                //    reconstructed `OffsetArc<T>` without dropping `*self.this`
164                //    restores `*self.this`'s field invariant and transfers the
165                //    strong reference back to `*self.this`.
166                unsafe {
167                    let arc = ManuallyDrop::take(&mut self.arc);
168                    ptr::write(self.this, Arc::into_raw_offset(arc));
169                }
170            }
171        }
172
173        // SAFETY:
174        // `self` is a valid, aligned reference to an initialized `OffsetArc<T>`.
175        // `ptr::read(self)` bitwise-copies the `OffsetArc<T>` out so its strong
176        // reference can be transferred to `Arc::from_raw_offset(this)`.
177        // Postcondition: `*self` is logically moved-out until `guard` drops (both
178        // on normal return and on unwind), so `*self` must not be read or
179        // dropped before `guard` runs.
180        let this = unsafe { ptr::read(self) };
181        let mut guard = DropGuard {
182            arc: ManuallyDrop::new(Arc::from_raw_offset(this)),
183            this: self,
184        };
185        // Obtain the mutable reference. `Arc::make_mut` guarantees that
186        // `guard.arc` is uniquely owned (strong count == 1) when it returns.
187        let ret = Arc::make_mut(&mut *guard.arc) as *mut T;
188        drop(guard);
189        // SAFETY:
190        // `ret` was obtained from `Arc::make_mut(&mut *guard.arc)`, so it is
191        // non-null, aligned, and points to an initialized `T` in a uniquely
192        // owned `ArcInner<T>` allocation. Dropping `guard` above converted that
193        // same `Arc<T>` back into `*self` via `Arc::into_raw_offset` and
194        // `ptr::write`, which only stores the data pointer into `self.ptr`
195        // without deallocating, moving, or creating conflicting references to
196        // the heap `T`. Because `self` is mutably borrowed for the caller's
197        // lifetime, `&mut *ret` has exclusive access for that lifetime.
198        unsafe { &mut *ret }
199    }
200}
201
202impl<T: ?Sized> OffsetArc<T> {
203    /// Temporarily converts |self| into a bonafide Arc and exposes it to the
204    /// provided callback. The refcount is not modified.
205    #[inline]
206    pub fn with_arc<F, U>(&self, f: F) -> U
207    where
208        F: FnOnce(&Arc<T>) -> U,
209    {
210        // Synthesize transient Arc, which never touches the refcount of the ArcInner.
211        //
212        // SAFETY:
213        // By `OffsetArc::ptr`'s field invariant, `self.ptr.as_ptr()` was created
214        // by `Arc::into_raw` and retains full provenance over the live
215        // `ArcInner<T>`. Wrapping the reconstructed `Arc<T>` in `ManuallyDrop`
216        // ensures `Arc::from_raw` does not decrement the strong reference count
217        // owned by `*self` when `with_arc` returns or if `f` unwinds.
218        let transient = unsafe { ManuallyDrop::new(Arc::from_raw(self.ptr.as_ptr())) };
219
220        // Expose the transient Arc to the callback, which may clone it if it wants
221        // and forward the result to the user
222        f(&transient)
223    }
224
225    /// Clone it as an `Arc`
226    #[inline]
227    pub fn clone_arc(&self) -> Arc<T> {
228        OffsetArc::with_arc(self, |a| a.clone())
229    }
230
231    /// Produce a pointer to the data that can be converted back
232    /// to an `Arc`
233    #[inline]
234    pub const fn borrow_arc(&self) -> ArcBorrow<'_, T> {
235        ArcBorrow(self.ptr, PhantomData)
236    }
237
238    /// The reference count of this `Arc`.
239    ///
240    /// The number does not include borrowed pointers,
241    /// or temporary `Arc` pointers created with functions like
242    /// [`ArcBorrow::with_arc`].
243    ///
244    /// The function is called `strong_count` to mirror `std::sync::Arc::strong_count`,
245    /// however `triomphe::Arc` does not support weak references.
246    #[inline]
247    pub fn strong_count(this: &Self) -> usize {
248        Self::with_arc(this, |arc| Arc::strong_count(arc))
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn offset_arc_smoke() {
258        let arc = Arc::new(42);
259        let offset = Arc::into_raw_offset(arc);
260
261        assert_eq!(*offset, 42);
262        assert_eq!(OffsetArc::strong_count(&offset), 1);
263
264        let offset2 = offset.clone();
265        assert_eq!(OffsetArc::strong_count(&offset), 2);
266        assert_eq!(offset, offset2);
267
268        let regular_arc = offset2.clone_arc();
269        assert_eq!(Arc::strong_count(&regular_arc), 3);
270        drop(regular_arc);
271
272        drop(offset2);
273        assert_eq!(OffsetArc::strong_count(&offset), 1);
274
275        let mut offset = offset;
276        *offset.make_mut() = 99;
277        assert_eq!(*offset, 99);
278
279        let offset2 = offset.clone();
280        *offset.make_mut() = 100;
281        assert_eq!(*offset, 100);
282        assert_eq!(*offset2, 99);
283    }
284
285    #[test]
286    fn offset_arc_str() {
287        let s: OffsetArc<str> = OffsetArc::from("hello world");
288        assert_eq!(&*s, "hello world");
289        assert_eq!(OffsetArc::strong_count(&s), 1);
290
291        let b = s.borrow_arc();
292        assert_eq!(&*b, "hello world");
293
294        let s2 = s.clone();
295        assert_eq!(OffsetArc::strong_count(&s), 2);
296        assert_eq!(s, s2);
297    }
298
299    #[test]
300    #[cfg(feature = "std")]
301    fn safe_offset_arc_make_mut_reentrant_drop_panic_uaf() {
302        use core::sync::atomic::{AtomicBool, Ordering};
303        use std::panic::{catch_unwind, AssertUnwindSafe};
304        use std::sync::{Arc as StdArc, Mutex};
305
306        struct ReentrantClone {
307            value: usize,
308            sibling: StdArc<Mutex<Option<Arc<ReentrantClone>>>>,
309            drop_panicked: StdArc<AtomicBool>,
310        }
311
312        impl Clone for ReentrantClone {
313            fn clone(&self) -> Self {
314                // `Arc::make_mut` observed two owners before calling us. Removing the
315                // other owner here makes its transient owner the last one.
316                drop(self.sibling.lock().unwrap().take());
317                Self {
318                    value: self.value,
319                    sibling: self.sibling.clone(),
320                    drop_panicked: self.drop_panicked.clone(),
321                }
322            }
323        }
324
325        impl Drop for ReentrantClone {
326            fn drop(&mut self) {
327                if !self.drop_panicked.swap(true, Ordering::SeqCst) {
328                    panic!("old payload drop sentinel");
329                }
330            }
331        }
332
333        let sibling = StdArc::new(Mutex::new(None));
334        let drop_panicked = StdArc::new(AtomicBool::new(false));
335        let arc = Arc::new(ReentrantClone {
336            value: 37,
337            sibling: sibling.clone(),
338            drop_panicked,
339        });
340        *sibling.lock().unwrap() = Some(arc.clone());
341        let mut offset: OffsetArc<ReentrantClone> = Arc::into_raw_offset(arc);
342
343        let result = catch_unwind(AssertUnwindSafe(|| {
344            let _ = OffsetArc::make_mut(&mut offset);
345        }));
346        assert!(result.is_err());
347
348        assert_eq!(offset.value, 37);
349    }
350}