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(®ular_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}