-
Notifications
You must be signed in to change notification settings - Fork 60
Description
Consider the following code:
use zerocopy::{Immutable, IntoBytes};
fn as_bytes<T: ?Sized + IntoBytes + Immutable>(t: &T) -> &[u8] {
let len = size_of_val(t);
let ptr: *const T = t;
// SAFETY:
// - By `T: IntoBytes`, all of the bytes in `*t` are initialized to valid `u8`s
// - By `T: Immutable`, `T` does not permit interior mutation, and neither does `[u8]`
// - The constructed pointer's referent has the same bytes as `*t`. By invariant on `&T`,
// this means that the referent is either zero-sized or lives in a single allocation which
// lives for at least as long as the lifetime of the input/output references to this function.
unsafe { core::ptr::slice_from_raw_parts(ptr.cast::<u8>(), len) }
}This safety comment implicitly assumes that *t is contiguous. If it isn't (e.g., if it contains bytes at addresses 1 and 3 but not 2), then the safety comment is wrong, and the code is unsound since the returned reference refers to bytes which may not be allocated, may not be initialized, may be subject to interior mutation, etc.
This makes me think that we basically need to guarantee that all pointers refer to contiguous sets of bytes, or else tons of unsafe code is unsound, and it would be very difficult to write sound unsafe code. Every time you did a transmute, you'd need to reason about whether the referents of the source and destination types had the same "holes".
Should we just bite the bullet and guarantee that all referents are contiguous in memory? Have we already guaranteed this somewhere that I'm not aware of?
cc @jswrenn