Description
The following code:
#[derive(Debug)]
#[repr(C)] // for easier computation of the field offsets (no need to `offset_of!`)
struct S {
a: u8,
b: u8,
}
let s = S { a: 42, b: 27 };
let at_b = ::core::ptr::raw_const!(s.b);
unsafe {
dbg!(
&*at_b.cast::<u8>().offset(-1 /* offset of `.b` in `S` */).cast::<S>()
);
}
triggers the following Miri error:
error: Undefined Behavior: trying to reborrow for SharedReadOnly at alloc1414+0x1, but parent tag <untagged> does not have an appropriate item in the borrow stack
--> src/main.rs:15:13
|
15 | &*at_b.cast::<u8>().offset(-1 /* offset of `.b` in `S` */).cast::<S>()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to reborrow for SharedReadOnly at alloc1414+0x1, but parent tag <untagged> does not have an appropriate item in the borrow stack
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
= note: inside `main` at src/main.rs:15:13
= note: inside `<fn() as std::ops::FnOnce<()>>::call_once - shim(fn())` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227:5
= note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::<fn(), ()>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys_common/backtrace.rs:125:18
= note: inside closure at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:66:18
= note: inside `std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:259:13
= note: inside `std::panicking::r#try::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:379:40
= note: inside `std::panicking::r#try::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:343:19
= note: inside `std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:396:14
= note: inside `std::rt::lang_start_internal` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:51:25
= note: inside `std::rt::lang_start::<()>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:65:5
Which means that ptr::raw_const!(s.b)
is interpreted as having provenance over s.b
only; i.e., that it is not equivalent to doing ptr::raw_const!(s).cast::<u8>().offset( offset_of!(S, b) )
More surprisingly, if at_b
is computed as:
let at_b = ::core::ptr::raw_const!((*&s).b);
then Miri does not complain.
This looks like a bug (Miri false positive), although I can imagine that having the raw_ref!
macros "guess" the right provenance might be hard to implement, if not impossible; but these differences in semantics with changes that subtle look error-prone and footgunny to me; at the very least the docs of the raw_ref
macros ought to mention that extra care should be taken when dealing with provenance (that is, to recommend using the explicit at_s.cast::<u8>.offset(offset_of!(S, field)).cast::<FieldTy>()
, with offset_of!
being the one that uses raw_ref!
macros under the hood).
This might be an important thing to clarify / to fix now that the macros are about to be stabilized (rust-lang/rust#80886)