Skip to content

Commit 6bc772c

Browse files
committed
Re-stabilize Weak::as_ptr &friends for unsized T
As per T-lang consensus, this uses a branch to handle the dangling case. The discussed optimization of only doing the branch in the T: ?Sized case is left for a followup patch, as doing so is not trivial (as it requires specialization for correctness, not just optimization).
1 parent 8fec6c7 commit 6bc772c

File tree

5 files changed

+130
-35
lines changed

5 files changed

+130
-35
lines changed

library/alloc/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
#![feature(receiver_trait)]
121121
#![cfg_attr(bootstrap, feature(min_const_generics))]
122122
#![feature(min_specialization)]
123+
#![feature(set_ptr_value)]
123124
#![feature(slice_ptr_get)]
124125
#![feature(slice_ptr_len)]
125126
#![feature(staged_api)]

library/alloc/src/rc.rs

+23-18
Original file line numberDiff line numberDiff line change
@@ -1862,7 +1862,7 @@ struct WeakInner<'a> {
18621862
strong: &'a Cell<usize>,
18631863
}
18641864

1865-
impl<T> Weak<T> {
1865+
impl<T: ?Sized> Weak<T> {
18661866
/// Returns a raw pointer to the object `T` pointed to by this `Weak<T>`.
18671867
///
18681868
/// The pointer is valid only if there are some strong references. The pointer may be dangling,
@@ -1892,15 +1892,17 @@ impl<T> Weak<T> {
18921892
pub fn as_ptr(&self) -> *const T {
18931893
let ptr: *mut RcBox<T> = NonNull::as_ptr(self.ptr);
18941894

1895-
// SAFETY: we must offset the pointer manually, and said pointer may be
1896-
// a dangling weak (usize::MAX) if T is sized. data_offset is safe to call,
1897-
// because we know that a pointer to unsized T was derived from a real
1898-
// unsized T, as dangling weaks are only created for sized T. wrapping_offset
1899-
// is used so that we can use the same code path for the non-dangling
1900-
// unsized case and the potentially dangling sized case.
1901-
unsafe {
1902-
let offset = data_offset(ptr as *mut T);
1903-
set_data_ptr(ptr as *mut T, (ptr as *mut u8).wrapping_offset(offset))
1895+
if is_dangling(self.ptr) {
1896+
// If the pointer is dangling, we return a null pointer as the dangling sentinel.
1897+
// We can't return the usize::MAX sentinel, as that could valid if T is ZST.
1898+
// SAFETY: we have to return a known sentinel here that cannot be produced for
1899+
// a valid pointer, so that `from_raw` can reverse this transformation.
1900+
(ptr as *mut T).set_ptr_value(ptr::null_mut())
1901+
} else {
1902+
// SAFETY: If the pointer is not dangling, it describes to a valid allocation.
1903+
// The payload may be dropped at this point, and we have to maintain provenance,
1904+
// so use raw pointer manipulation.
1905+
unsafe { &raw mut (*ptr).value }
19041906
}
19051907
}
19061908

@@ -1982,22 +1984,25 @@ impl<T> Weak<T> {
19821984
/// [`new`]: Weak::new
19831985
#[stable(feature = "weak_into_raw", since = "1.45.0")]
19841986
pub unsafe fn from_raw(ptr: *const T) -> Self {
1985-
// SAFETY: data_offset is safe to call, because this pointer originates from a Weak.
19861987
// See Weak::as_ptr for context on how the input pointer is derived.
1987-
let offset = unsafe { data_offset(ptr) };
19881988

1989-
// Reverse the offset to find the original RcBox.
1990-
// SAFETY: we use wrapping_offset here because the pointer may be dangling (but only if T: Sized).
1991-
let ptr = unsafe {
1992-
set_data_ptr(ptr as *mut RcBox<T>, (ptr as *mut u8).wrapping_offset(-offset))
1989+
let ptr = if ptr.is_null() {
1990+
// If we get a null pointer, this is a dangling weak.
1991+
// SAFETY: this is the same sentinel as used in Weak::new and is_dangling
1992+
(ptr as *mut RcBox<T>).set_ptr_value(usize::MAX as *mut _)
1993+
} else {
1994+
// Otherwise, this describes a real allocation.
1995+
// SAFETY: data_offset is safe to call, as ptr describes a real allocation.
1996+
let offset = unsafe { data_offset(ptr) };
1997+
// Thus, we reverse the offset to get the whole RcBox.
1998+
// SAFETY: the pointer originated from a Weak, so this offset is safe.
1999+
unsafe { (ptr as *mut RcBox<T>).set_ptr_value((ptr as *mut u8).offset(-offset)) }
19932000
};
19942001

19952002
// SAFETY: we now have recovered the original Weak pointer, so can create the Weak.
19962003
Weak { ptr: unsafe { NonNull::new_unchecked(ptr) } }
19972004
}
1998-
}
19992005

2000-
impl<T: ?Sized> Weak<T> {
20012006
/// Attempts to upgrade the `Weak` pointer to an [`Rc`], delaying
20022007
/// dropping of the inner value if successful.
20032008
///

library/alloc/src/rc/tests.rs

+41
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,30 @@ fn into_from_weak_raw() {
208208
}
209209
}
210210

211+
#[test]
212+
fn test_into_from_weak_raw_unsized() {
213+
use std::fmt::Display;
214+
use std::string::ToString;
215+
216+
let arc: Rc<str> = Rc::from("foo");
217+
let weak: Weak<str> = Rc::downgrade(&arc);
218+
219+
let ptr = Weak::into_raw(weak.clone());
220+
let weak2 = unsafe { Weak::from_raw(ptr) };
221+
222+
assert_eq!(unsafe { &*ptr }, "foo");
223+
assert!(weak.ptr_eq(&weak2));
224+
225+
let arc: Rc<dyn Display> = Rc::new(123);
226+
let weak: Weak<dyn Display> = Rc::downgrade(&arc);
227+
228+
let ptr = Weak::into_raw(weak.clone());
229+
let weak2 = unsafe { Weak::from_raw(ptr) };
230+
231+
assert_eq!(unsafe { &*ptr }.to_string(), "123");
232+
assert!(weak.ptr_eq(&weak2));
233+
}
234+
211235
#[test]
212236
fn get_mut() {
213237
let mut x = Rc::new(3);
@@ -294,6 +318,23 @@ fn test_unsized() {
294318
assert_eq!(foo, foo.clone());
295319
}
296320

321+
#[test]
322+
fn test_maybe_thin_unsized() {
323+
// If/when custom thin DSTs exist, this test should be updated to use one
324+
use std::ffi::{CStr, CString};
325+
326+
let x: Rc<CStr> = Rc::from(CString::new("swordfish").unwrap().into_boxed_c_str());
327+
assert_eq!(format!("{:?}", x), "\"swordfish\"");
328+
let y: Weak<CStr> = Rc::downgrade(&x);
329+
drop(x);
330+
331+
// At this point, the weak points to a dropped DST
332+
assert!(y.upgrade().is_none());
333+
// But we still need to be able to get the alloc layout to drop.
334+
// CStr has no drop glue, but custom DSTs might, and need to work.
335+
drop(y);
336+
}
337+
297338
#[test]
298339
fn test_from_owned() {
299340
let foo = 123;

library/alloc/src/sync.rs

+24-17
Original file line numberDiff line numberDiff line change
@@ -1648,7 +1648,7 @@ struct WeakInner<'a> {
16481648
strong: &'a atomic::AtomicUsize,
16491649
}
16501650

1651-
impl<T> Weak<T> {
1651+
impl<T: ?Sized> Weak<T> {
16521652
/// Returns a raw pointer to the object `T` pointed to by this `Weak<T>`.
16531653
///
16541654
/// The pointer is valid only if there are some strong references. The pointer may be dangling,
@@ -1678,15 +1678,17 @@ impl<T> Weak<T> {
16781678
pub fn as_ptr(&self) -> *const T {
16791679
let ptr: *mut ArcInner<T> = NonNull::as_ptr(self.ptr);
16801680

1681-
// SAFETY: we must offset the pointer manually, and said pointer may be
1682-
// a dangling weak (usize::MAX) if T is sized. data_offset is safe to call,
1683-
// because we know that a pointer to unsized T was derived from a real
1684-
// unsized T, as dangling weaks are only created for sized T. wrapping_offset
1685-
// is used so that we can use the same code path for the non-dangling
1686-
// unsized case and the potentially dangling sized case.
1687-
unsafe {
1688-
let offset = data_offset(ptr as *mut T);
1689-
set_data_ptr(ptr as *mut T, (ptr as *mut u8).wrapping_offset(offset))
1681+
if is_dangling(self.ptr) {
1682+
// If the pointer is dangling, we return a null pointer as the dangling sentinel.
1683+
// We can't return the usize::MAX sentinel, as that could valid if T is ZST.
1684+
// SAFETY: we have to return a known sentinel here that cannot be produced for
1685+
// a valid pointer, so that `from_raw` can reverse this transformation.
1686+
(ptr as *mut T).set_ptr_value(ptr::null_mut())
1687+
} else {
1688+
// SAFETY: If the pointer is not dangling, it describes to a valid allocation.
1689+
// The payload may be dropped at this point, and we have to maintain provenance,
1690+
// so use raw pointer manipulation.
1691+
unsafe { &raw mut (*ptr).data }
16901692
}
16911693
}
16921694

@@ -1768,18 +1770,23 @@ impl<T> Weak<T> {
17681770
/// [`forget`]: std::mem::forget
17691771
#[stable(feature = "weak_into_raw", since = "1.45.0")]
17701772
pub unsafe fn from_raw(ptr: *const T) -> Self {
1771-
// SAFETY: data_offset is safe to call, because this pointer originates from a Weak.
17721773
// See Weak::as_ptr for context on how the input pointer is derived.
1773-
let offset = unsafe { data_offset(ptr) };
17741774

1775-
// Reverse the offset to find the original ArcInner.
1776-
// SAFETY: we use wrapping_offset here because the pointer may be dangling (but only if T: Sized)
1777-
let ptr = unsafe {
1778-
set_data_ptr(ptr as *mut ArcInner<T>, (ptr as *mut u8).wrapping_offset(-offset))
1775+
let ptr = if ptr.is_null() {
1776+
// If we get a null pointer, this is a dangling weak.
1777+
// SAFETY: this is the same sentinel as used in Weak::new and is_dangling
1778+
(ptr as *mut ArcInner<T>).set_ptr_value(usize::MAX as *mut _)
1779+
} else {
1780+
// Otherwise, this describes a real allocation.
1781+
// SAFETY: data_offset is safe to call, as ptr describes a real allocation.
1782+
let offset = unsafe { data_offset(ptr) };
1783+
// Thus, we reverse the offset to get the whole RcBox.
1784+
// SAFETY: the pointer originated from a Weak, so this offset is safe.
1785+
unsafe { (ptr as *mut ArcInner<T>).set_ptr_value((ptr as *mut u8).offset(-offset)) }
17791786
};
17801787

17811788
// SAFETY: we now have recovered the original Weak pointer, so can create the Weak.
1782-
unsafe { Weak { ptr: NonNull::new_unchecked(ptr) } }
1789+
Weak { ptr: unsafe { NonNull::new_unchecked(ptr) } }
17831790
}
17841791
}
17851792

library/alloc/src/sync/tests.rs

+41
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,30 @@ fn into_from_weak_raw() {
158158
}
159159
}
160160

161+
#[test]
162+
fn test_into_from_weak_raw_unsized() {
163+
use std::fmt::Display;
164+
use std::string::ToString;
165+
166+
let arc: Arc<str> = Arc::from("foo");
167+
let weak: Weak<str> = Arc::downgrade(&arc);
168+
169+
let ptr = Weak::into_raw(weak.clone());
170+
let weak2 = unsafe { Weak::from_raw(ptr) };
171+
172+
assert_eq!(unsafe { &*ptr }, "foo");
173+
assert!(weak.ptr_eq(&weak2));
174+
175+
let arc: Arc<dyn Display> = Arc::new(123);
176+
let weak: Weak<dyn Display> = Arc::downgrade(&arc);
177+
178+
let ptr = Weak::into_raw(weak.clone());
179+
let weak2 = unsafe { Weak::from_raw(ptr) };
180+
181+
assert_eq!(unsafe { &*ptr }.to_string(), "123");
182+
assert!(weak.ptr_eq(&weak2));
183+
}
184+
161185
#[test]
162186
fn test_cowarc_clone_make_mut() {
163187
let mut cow0 = Arc::new(75);
@@ -329,6 +353,23 @@ fn test_unsized() {
329353
assert!(y.upgrade().is_none());
330354
}
331355

356+
#[test]
357+
fn test_maybe_thin_unsized() {
358+
// If/when custom thin DSTs exist, this test should be updated to use one
359+
use std::ffi::{CStr, CString};
360+
361+
let x: Arc<CStr> = Arc::from(CString::new("swordfish").unwrap().into_boxed_c_str());
362+
assert_eq!(format!("{:?}", x), "\"swordfish\"");
363+
let y: Weak<CStr> = Arc::downgrade(&x);
364+
drop(x);
365+
366+
// At this point, the weak points to a dropped DST
367+
assert!(y.upgrade().is_none());
368+
// But we still need to be able to get the alloc layout to drop.
369+
// CStr has no drop glue, but custom DSTs might, and need to work.
370+
drop(y);
371+
}
372+
332373
#[test]
333374
fn test_from_owned() {
334375
let foo = 123;

0 commit comments

Comments
 (0)