Description
This example comes from this URLO thread, which in turn was inspired by this zerocopy issue.
The addr_of!
and addr_of_mut!
docs imply that they can handle unaligned pointers so long as a reference is never materialized from that pointer (which would be insta-UB). However, running the following program under Miri fails:
fn main() {
#[repr(C, packed)]
struct Unalign<T>(T);
#[repr(C)]
struct Foo {
a: u8,
b: u16,
}
// Has alignment `align_of::<T>()`, and the `Unalign<T>`
// is at byte offset 1; so long as `align_of::<T>() > 1`,
// the contained `T` is misaligned.
#[repr(C)]
struct Misalign<T>(u8, Unalign<T>, [T; 0]);
let u = Misalign(0, Unalign(Foo{ a: 1, b: 2 }), []);
let u_ptr: *const Unalign<Foo> = &u.1;
// Sound because `Unalign` contains a `T` and nothing else.
let f_ptr: *const Foo = u_ptr.cast();
// Should be sound because we never construct a reference.
let addr_of_b: *const u16 = unsafe { core::ptr::addr_of!((*f_ptr).b) };
println!("{:?}", addr_of_b);
}
Here's the failure:
error: Undefined Behavior: accessing memory with alignment 1, but alignment 2 is required
--> src/main.rs:22:42
|
22 | let addr_of_b: *const u16 = unsafe { core::ptr::addr_of!((*f_ptr).b) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment 1, but alignment 2 is required
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `main` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:1980:5: 1980:22
= note: this error originates in the macro `core::ptr::addr_of` (in Nightly builds, run with -Z macro-backtrace for more info)
@cuviper pointed out my issue:
The example shows an unaligned field (since it's packed), but that doesn't excuse everything. "Note, however, that the
expr
inaddr_of!(expr)
is still subject to all the usual rules." -- I think this would include alignment for(*f_ptr)
, although the docs only call out null pointers.Miri is happy with
addr_of!((*u_ptr).0.b)
.
The docs strongly imply that this would be sound. It does this in two ways. First, when contrasting references and raw pointers, it specifically calls out that references need to be aligned:
Creating a reference with
&
/&mut
is only allowed if the pointer is properly aligned and points to initialized data. For cases where those requirements do not hold, raw pointers should be used instead.
Second, it seems to enumerate the cases in which addr_of!
would be unsound, and operating on unaligned pointers isn't on the list:
Note, however, that the
expr
inaddr_of!(expr)
is still subject to all the usual rules. In particular,addr_of!(*ptr::null())
is Undefined Behavior because it dereferences a null pointer.
IMO the docs as currently written are both vague and misleading with respect to what's allowed. It would be good if the docs were more precise on exactly what is and isn't allowed. I was pretty confident in the soundness of the code I'd written, and only discovered the issue thanks to Miri.