-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
From<&[T]> for Rc
creates underaligned reference
#54908
Comments
This also affects IMO the best fix is to make |
Oh wow this code is crazy. The "data field" it talks about is not some field of a struct, it's the pointer part of a fat pointer. That could have been made more clear...^^ |
cc @rust-lang/lang and @rust-lang/libs |
"Almost backwards-compatible" is probably not good enough :) But we can add |
Good question. I am going to lobby for "yes", but no decision has been made yet. |
The relevant code in one place (not including impl From<String> for Rc<str> {
#[inline]
fn from(v: String) -> Rc<str> {
Rc::from(&v[..])
}
}
impl<'a, T: Clone> From<&'a [T]> for Rc<[T]> {
#[inline]
fn from(v: &[T]) -> Rc<[T]> {
<Self as RcFromSlice<T>>::from_slice(v)
}
}
impl<T: ?Sized> From<Box<T>> for Rc<T> {
#[inline]
fn from(v: Box<T>) -> Rc<T> {
Rc::from_box(v)
}
}
struct RcBox<T: ?Sized> {
strong: Cell<usize>,
weak: Cell<usize>,
value: T,
}
impl<'a> From<&'a str> for Rc<str> {
#[inline]
fn from(v: &str) -> Rc<str> {
let rc = Rc::<[u8]>::from(v.as_bytes());
unsafe { Rc::from_raw(Rc::into_raw(rc) as *const str) }
}
}
impl<T> From<Vec<T>> for Rc<[T]> {
#[inline]
fn from(mut v: Vec<T>) -> Rc<[T]> {
unsafe {
let rc = Rc::copy_from_slice(&v);
// Allow the Vec to free its memory, but not destroy its contents
v.set_len(0);
rc
}
}
}
pub struct Rc<T: ?Sized> {
ptr: NonNull<RcBox<T>>,
phantom: PhantomData<T>,
}
trait RcFromSlice<T> {
fn from_slice(slice: &[T]) -> Self;
}
impl<T: Clone> RcFromSlice<T> for Rc<[T]> {
#[inline]
default fn from_slice(v: &[T]) -> Self {
// Panic guard while cloning T elements.
// In the event of a panic, elements that have been written
// into the new RcBox will be dropped, then the memory freed.
struct Guard<T> {
mem: NonNull<u8>,
elems: *mut T,
layout: Layout,
n_elems: usize,
}
impl<T> Drop for Guard<T> {
fn drop(&mut self) {
use core::slice::from_raw_parts_mut;
unsafe {
let slice = from_raw_parts_mut(self.elems, self.n_elems);
ptr::drop_in_place(slice);
Global.dealloc(self.mem, self.layout.clone());
}
}
}
unsafe {
let v_ptr = v as *const [T];
let ptr = Self::allocate_for_ptr(v_ptr);
let mem = ptr as *mut _ as *mut u8;
let layout = Layout::for_value(&*ptr);
// Pointer to first element
let elems = &mut (*ptr).value as *mut [T] as *mut T;
let mut guard = Guard{
mem: NonNull::new_unchecked(mem),
elems: elems,
layout: layout,
n_elems: 0,
};
for (i, item) in v.iter().enumerate() {
ptr::write(elems.add(i), item.clone());
guard.n_elems += 1;
}
// All clear. Forget the guard so it doesn't free the new RcBox.
forget(guard);
Rc { ptr: NonNull::new_unchecked(ptr), phantom: PhantomData }
}
}
}
impl<T: Copy> RcFromSlice<T> for Rc<[T]> {
#[inline]
fn from_slice(v: &[T]) -> Self {
unsafe { Rc::copy_from_slice(v) }
}
}
impl<T> Rc<[T]> {
// Copy elements from slice into newly allocated Rc<[T]>
//
// Unsafe because the caller must either take ownership or bind `T: Copy`
unsafe fn copy_from_slice(v: &[T]) -> Rc<[T]> {
let v_ptr = v as *const [T];
let ptr = Self::allocate_for_ptr(v_ptr);
ptr::copy_nonoverlapping(
v.as_ptr(),
&mut (*ptr).value as *mut [T] as *mut T,
v.len());
Rc { ptr: NonNull::new_unchecked(ptr), phantom: PhantomData }
}
}
impl<T: ?Sized> Rc<T> {
// Allocates an `RcBox<T>` with sufficient space for an unsized value
unsafe fn allocate_for_ptr(ptr: *const T) -> *mut RcBox<T> {
// Create a fake RcBox to find allocation size and alignment
let fake_ptr = ptr as *mut RcBox<T>;
let layout = Layout::for_value(&*fake_ptr);
let mem = Global.alloc(layout)
.unwrap_or_else(|_| handle_alloc_error(layout));
// Initialize the real RcBox
let inner = set_data_ptr(ptr as *mut T, mem.as_ptr() as *mut u8) as *mut RcBox<T>;
ptr::write(&mut (*inner).strong, Cell::new(1));
ptr::write(&mut (*inner).weak, Cell::new(1));
inner
}
fn from_box(v: Box<T>) -> Rc<T> {
unsafe {
let box_unique = Box::into_unique(v);
let bptr = box_unique.as_ptr();
let value_size = size_of_val(&*bptr);
let ptr = Self::allocate_for_ptr(bptr);
// Copy value as bytes
ptr::copy_nonoverlapping(
bptr as *const T as *const u8,
&mut (*ptr).value as *mut _ as *mut u8,
value_size);
// Free the allocation without dropping its contents
box_free(box_unique);
Rc { ptr: NonNull::new_unchecked(ptr), phantom: PhantomData }
}
}
}
// Sets the data pointer of a `?Sized` raw pointer.
//
// For a slice/trait object, this sets the `data` field and leaves the rest
// unchanged. For a sized raw pointer, this simply sets the pointer.
unsafe fn set_data_ptr<T: ?Sized, U>(mut ptr: *mut T, data: *mut U) -> *mut T {
ptr::write(&mut ptr as *mut _ as *mut *mut u8, data as *mut u8);
ptr
} |
@Centril asked me to clarify that the alignment I am talking about here is the one of the actual data part pointed to by the fat pointer: A |
@RalfJung noted that this is unlikely to lead to miscompilation so I'm lowering the prio a bit. |
The code originally used |
We are going to support object-safe arbitrary self types, so at least in that case they need to be valid for safety (f not for non-UB). |
I am submitting a PR that resolves this issue by manually calculating the layout of The code is similar to that used in |
I guess that also works, but I think we should also have a way to compute the size and alignment of an unaligned raw pointer. @SimonSapin Inference changes are excluded from stability, right? Is there precedence for changing a function argument type to something that coerces to the original type? |
I can’t think of such a precedent out of hand, but calling them is not the only thing that can be done with functions. Passing it to generic code that has a I think I missed something in how that relates to this thread, though. What API change are you suggesting? |
Changing |
This feels risky to me. @rust-lang/libs what do you think? |
I’d prefer to add new functions that take |
I can see that. The stable ones should get warnings though about these references being subject to all the usual validity requirements for references. Might be worth deprecating? Or, at least, have a lint in clippy that complains when they are used with anything involving a raw pointer. |
Do you mean warn when they are used with a reference that was just created unsafely from a raw pointer? There’s nothing wrong with using those functions in safe code with valid references, is there? I’m not sure about deprecating, especially if the new pointer-based variants need to be |
Something lime that, yes.
Oh sure, that was under the assumption that we can make them safe. |
Yes I agree with Simon that we shouldn't change the stable apis, and if there's a decision to emit wabings or deprecate let's have a dedicated thread for that rather than being buried in the discussion here |
Fix undefined behavior in Rc/Arc allocation Manually calculate allocation layout for `Rc`/`Arc` to avoid undefined behavior Closes #54908
Just found this by way of Ralf’s latest stacked borrows post. It seems to me that alignment is a red herring. The question is, should If the answer is yes, then it doesn’t generally make sense to fake a pointer and then call If the answer is no, on the other hand, then you shouldn’t have to pass a data pointer at all, never mind alignment; it would probably be best to base this on the proposed API for “manipulating the metadata for fat pointers” (same link as above), and add a way to request size and alignment directly from the metadata value itself. But I wouldn’t recommend this route, since as I mentioned, it would be incompatible with multiple use cases for custom DSTs. |
Good point! I think I agree. Maybe we should not propose a raw-ptr-based version of these functions quite yet. ;) |
The function
allocate_for_ptr
is passed a*const [T]
, which it turns into a&RcBox<[T]>
. Unfortunately, the latter has an alignment of 8 even ifT
has smaller alignment. This leads to UB because we have a not-sufficiently-aligned reference.This got introduced in #42565.
Found by miri (in my branch that verified the validity invariants).
Cc @murarth @Centril @aturon
The text was updated successfully, but these errors were encountered: