Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens if I call Vec::from_raw_parts with a smaller capacity than the pointer actually has?

Tags:

rust

I have a vector of u8 that I want to interpret as a vector of u32. It is assumed that the bytes are in the right order. I don't want to allocate new memory and copy bytes after casting. I got the following to work:

use std::mem;

fn reinterpret(mut v: Vec<u8>) -> Option<Vec<u32>> {
    let v_len = v.len();
    v.shrink_to_fit();
    if v_len % 4 != 0 {
        None
    } else {
        let v_cap = v.capacity();
        let v_ptr = v.as_mut_ptr();
        println!("{:?}|{:?}|{:?}", v_len, v_cap, v_ptr);
        let v_reinterpret = unsafe { Vec::from_raw_parts(v_ptr as *mut u32, v_len / 4, v_cap / 4) };
        println!("{:?}|{:?}|{:?}",
                 v_reinterpret.len(),
                 v_reinterpret.capacity(),
                 v_reinterpret.as_ptr());
        println!("{:?}", v_reinterpret);
        println!("{:?}", v); // v is still alive, but is same as rebuilt
        mem::forget(v);
        Some(v_reinterpret)
    }
}

fn main() {
    let mut v: Vec<u8> = vec![1, 1, 1, 1, 1, 1, 1, 1];
    let test = reinterpret(v);
    println!("{:?}", test);
}

However, there's an obvious problem here. From the shrink_to_fit documentation:

It will drop down as close as possible to the length but the allocator may still inform the vector that there is space for a few more elements.

Does this mean that my capacity may still not be a multiple of the size of u32 after calling shrink_to_fit? If in from_raw_parts I set capacity to v_len/4 with v.capacity() not an exact multiple of 4, do I leak those 1-3 bytes, or will they go back into the memory pool because of mem::forget on v?

Is there any other problem I am overlooking here?

I think moving v into reinterpret guarantees that it's not accessible from that point on, so there's only one owner from the mem::forget(v) call onwards.

like image 217
sgldiv Avatar asked Aug 09 '16 00:08

sgldiv


People also ask

What does VEC mean in Rust?

A contiguous growable array type, written as Vec<T> , short for 'vector'.

How do you clear a vector in Rust?

To remove all elements from a vector in Rust, use . retain() method to keep all elements the do not match. let mut v = vec![ "A", "warm", "fall", "warm", "day"]; let elem = "warm"; // element to remove v.

What is a VEC u8?

Vec<u8> is like Box<[u8]> , except it additionally stores a "capacity" count, making it three machine words wide. Separately stored capacity allows for efficient resizing of the underlying array. It's the basis for String .


1 Answers

This is an old question, and it looks like it has a working solution in the comments. I've just written up what exactly goes wrong here, and some solutions that one might create/use in today's Rust.

This is undefined behavior

Vec::from_raw_parts is an unsafe function, and thus you must satisfy its invariants, or you invoke undefined behavior.

Quoting from the documentation for Vec::from_raw_parts:

  • ptr needs to have been previously allocated via String/Vec (at least, it's highly likely to be incorrect if it wasn't).
  • T needs to have the same size and alignment as what ptr was allocated with. (T having a less strict alignment is not sufficient, the alignment really needs to be equal to satsify the dealloc requirement that memory must be allocated and deallocated with the same layout.)
  • length needs to be less than or equal to capacity.
  • capacity needs to be the capacity that the pointer was allocated with.

So, to answer your question, if capacity is not equal to the capacity of the original vec, then you've broken this invariant. This gives you undefined behavior.

Note that the requirement isn't on size_of::<T>() * capacity either, though, which brings us to the next topic.

Is there any other problem I am overlooking here?

Three things.

First, the function as written is disregarding another requirement of from_raw_parts. Specifically, T must have the same size as alignment as the original T. u32 is four times as big as u8, so this again breaks this requirement. Even if capacity*size remains the same, size isn't, and capacity isn't. This function will never be sound as implemented.

Second, even if all of the above was valid, you've also ignored the alignment. u32 must be aligned to 4-byte boundaries, while a Vec<u8> is only guaranteed to be aligned to a 1-byte boundary.

A comment on the OP mentions:

I think on x86_64, misalignment will have performance penalty

It's worth noting that while this may be true of machine language, it is not true for Rust. The rust reference explicitly states "A value of alignment n must only be stored at an address that is a multiple of n." This is a hard requirement.

Why the exact type requirement?

Vec::from_raw_parts seems like it's pretty strict, and that's for a reason. In Rust, the allocator API operates not only on allocation size, but on a Layout, which is the combination of size, number of things, and alignment of individual elements. In C with memalloc, all the allocator can rely upon is that the size is the same, and some minimum alignment. In Rust, though, it's allowed to rely on the entire Layout, and invoke undefined behavior if not.

So in order to correctly deallocate the memory, Vec needs to know the exact type that it was allocated with. By converting a Vec<u32> into Vec<u8>, it no longer knows this information, and so it can no longer properly deallocate this memory.

Alternative - Transforming slices

Vec::from_raw_parts's strictness comes from the fact that it needs to deallocate the memory. If we create a borrowing slice, &[u32] instead, we no longer need to deal with it! There is no capacity when turning a &[u8] into &[u32], so we should be all good, right?

Well, almost. You still have to deal with alignment. Primitives are generally aligned to their size, so a [u8] is only guaranteed to be aligned to 1-byte boundaries, while [u32] must be aligned to a 4-byte boundary.

If you want to chance it, though, and create a [u32] if possible, there's a function for that - <[T]>::align_to:

pub unsafe fn align_to<U>(&self) -> (&[T], &[U], &[T])

This will trim of any starting and ending misaligned values, and then give you a slice in the middle of your new type. It's unsafe, but the only invariant you need to satisfy is that the elements in the middle slice are valid.

It's sound to reinterpret 4 u8 values as a u32 value, so we're good.

Putting it all together, a sound version of the original function would look like this. This operates on borrowed rather than owned values, but given that reinterpreting an owned Vec is instant-undefined-behavior in any case, I think it's safe to say this is the closest sound function:

use std::mem;

fn reinterpret(v: &[u8]) -> Option<&[u32]> {
    let (trimmed_front, u32s, trimmed_back) = unsafe { v.align_to::<u32>() };
    if trimmed_front.is_empty() && trimmed_back.is_empty() {
        Some(u32s)
    } else {
        // either alignment % 4 != 0 or len % 4 != 0, so we can't do this op
        None
    }
}

fn main() {
    let mut v: Vec<u8> = vec![1, 1, 1, 1, 1, 1, 1, 1];
    let test = reinterpret(&v);
    println!("{:?}", test);
}

As a note, this could also be done with std::slice::from_raw_parts rather than align_to. However, that requires manually dealing with the alignment, and all it really gives is more things we need to ensure we're doing right. Well, that and compatibility with older compilers - align_to was introduced in 2018 in Rust 1.30.0, and wouldn't have existed when this question was asked.

Alternative - Copying

If you do need a Vec<u32> for long term data storage, I think the best option is to just allocate new memory. The old memory is allocated for u8s anyways, and wouldn't work.

This can be made fairly simple with some functional programming:

fn reinterpret(v: &[u8]) -> Option<Vec<u32>> {
    let v_len = v.len();
    if v_len % 4 != 0 {
        None
    } else {
        let result = v
            .chunks_exact(4)
            .map(|chunk: &[u8]| -> u32 {
                let chunk: [u8; 4] = chunk.try_into().unwrap();
                let value = u32::from_ne_bytes(chunk);
                value
            })
            .collect();
        Some(result)
    }
}

First, we use <[T]>::chunks_exact to iterate over chunks of 4 u8s. Next, try_into to convert from &[u8] to [u8; 4]. The &[u8] is guaranteed to be length 4, so this never fails.

We use u32::from_ne_bytes to convert the bytes into a u32 using native endianness. If interacting with a network protocol, or on-disk serialization, then using from_be_bytes or from_le_bytes may be preferable. And finally, we collect to turn our result back into a Vec<u32>.

As a last note, a truly general solution might use both of these techniques. If we change the return type to Cow<'_, [u32]>, we could return aligned, borrowed data if it works, and allocate a new array if it doesn't! Not quite the best of both worlds, but close.

like image 200
daboross Avatar answered Sep 30 '22 18:09

daboross