rkyv: UnsafeCell in Archived type incompatible with miri

I’m attempting to create a mutex type that can be used in its Archived form, to facilitate safe mutation of archived data mmap’d into multiple processes.

I have it implemented, and some basic tests working, but noticed that rkyv::archived_root results in an error in miri. rkyv::archived_root_mut works ok. Maybe there’s a fundamental reason for that, and the answer is “just use rkyv::archived_root_mut”, but I think it might be worth digging into a bit more.

I created a smaller test case that seems to show the problem is more fundamentally around UnsafeCell, which would be needed for interior mutability in any Archive type.

#![deny(unsafe_op_in_unsafe_fn)]

use std::cell::UnsafeCell;

#[repr(transparent)]
struct UnsafeU32 { 
    val: UnsafeCell<u32>,
}

impl<S> rkyv::Serialize<S> for UnsafeU32 
where 
    S: rkyv::Fallible + ?Sized,
{
    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
        let val = unsafe { &*self.val.get() };
        val.serialize(serializer)
    }
}

impl rkyv::Archive for UnsafeU32 {
    type Archived = Self;
    type Resolver = <u32 as rkyv::Archive>::Resolver;

    unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
        unsafe { (&*self.val.get()).resolve(pos, resolver, out as *mut u32) };
    }
}

#[test]
fn test_rkyv_miri() {
    type T = UnsafeU32;
    let original: T = UnsafeU32 {
        val: UnsafeCell::new(10),
    };
    let bytes = rkyv::to_bytes::<_, 256>(&original).unwrap();

    // The archived mutex can be used to mutate the data in place.
    let archived = unsafe { rkyv::archived_root::<T>(&bytes[..]) };
    assert_eq!(unsafe { *archived.val.get() }, 10);
}

The test passes natively, but under miri fails:

$ MIRIFLAGS=-Zmiri-backtrace=full cargo +nightly miri test
Preparing a sysroot for Miri (target: x86_64-unknown-linux-gnu)... done
    Finished test [unoptimized + debuginfo] target(s) in 0.01s
     Running unittests src/lib.rs (target/miri/x86_64-unknown-linux-gnu/debug/deps/repro-982c58b2cf8c397a)

running 1 test
test test_rkyv_miri ... error: Undefined Behavior: trying to reborrow from <185489> for SharedReadWrite permission at alloc77718[0x0], but that tag only grants SharedReadOnly permission for this location
  --> /home/jnewsome/.cargo/registry/src/github.com-1ecc6299db9ec823/rkyv-0.7.39/src/util/mod.rs:67:5
   |
67 |     &*bytes.as_ptr().add(pos).cast()
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |     |
   |     trying to reborrow from <185489> for SharedReadWrite permission at alloc77718[0x0], but that tag only grants SharedReadOnly permission for this location
   |     this error occurs as part of a reborrow at alloc77718[0x0..0x4]
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows 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
help: <185489> was created by a retag at offsets [0x0..0x4]
  --> src/lib.rs:38:29
   |
38 |     let archived = unsafe { rkyv::archived_root::<T>(&bytes[..]) };
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: backtrace:
   = note: inside `rkyv::archived_value::<UnsafeU32>` at /home/jnewsome/.cargo/registry/src/github.com-1ecc6299db9ec823/rkyv-0.7.39/src/util/mod.rs:67:5
   = note: inside `rkyv::archived_root::<UnsafeU32>` at /home/jnewsome/.cargo/registry/src/github.com-1ecc6299db9ec823/rkyv-0.7.39/src/util/mod.rs:148:5
note: inside `test_rkyv_miri` at src/lib.rs:38:29
  --> src/lib.rs:38:29
   |
38 |     let archived = unsafe { rkyv::archived_root::<T>(&bytes[..]) };
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside closure at src/lib.rs:30:1
  --> src/lib.rs:30:1
   |
29 |   #[test]
   |   ------- in this procedural macro expansion
30 | / fn test_rkyv_miri() {
31 | |     type T = UnsafeU32;
32 | |     let original: T = UnsafeU32 {
33 | |         val: UnsafeCell::new(10),
...  |
39 | |     assert_eq!(unsafe { *archived.val.get() }, 10);
40 | | }
   | |_^
   = note: inside `<[closure@src/lib.rs:30:1: 40:2] as std::ops::FnOnce<()>>::call_once - shim` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:248:5
   = note: inside `<fn() as std::ops::FnOnce<()>>::call_once - shim(fn())` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:248:5
   = note: inside `test::__rust_begin_short_backtrace::<fn()>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:572:5
   = note: inside closure at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:563:30
   = note: inside `<[closure@test::run_test::{closure#1}] as std::ops::FnOnce<()>>::call_once - shim(vtable)` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:248:5
   = note: inside `<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send> as std::ops::FnOnce<()>>::call_once` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:1935:9
   = note: inside `<std::panic::AssertUnwindSafe<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send>> as std::ops::FnOnce<()>>::call_once` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:271:9
   = note: inside `std::panicking::r#try::do_call::<std::panic::AssertUnwindSafe<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send>>, ()>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:492:40
   = note: inside `std::panicking::r#try::<(), std::panic::AssertUnwindSafe<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send>>>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:456:19
   = note: inside `std::panic::catch_unwind::<std::panic::AssertUnwindSafe<std::boxed::Box<dyn std::ops::FnOnce() + std::marker::Send>>, ()>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:137:14
   = note: inside `test::run_test_in_process` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:595:18
   = note: inside closure at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:489:39
   = note: inside `test::run_test::run_test_inner` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:527:13
   = note: inside `test::run_test` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:559:28
   = note: inside `test::run_tests::<[closure@test::run_tests_console::{closure#2}]>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:302:17
   = note: inside `test::run_tests_console` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/console.rs:293:5
   = note: inside `test::test_main` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:113:15
   = note: inside `test::test_main_static` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/test/src/lib.rs:132:5
   = note: inside `main`
   = note: inside `<fn() as std::ops::FnOnce<()>>::call_once - shim(fn())` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:248:5
   = note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::<fn(), ()>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys_common/backtrace.rs:122:18
   = note: inside closure at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:145: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 /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:280:13
   = note: inside `std::panicking::r#try::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:492:40
   = note: inside `std::panicking::r#try::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:456:19
   = note: inside `std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:137:14
   = note: inside closure at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:128:48
   = note: inside `std::panicking::r#try::do_call::<[closure@std::rt::lang_start_internal::{closure#2}], isize>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:492:40
   = note: inside `std::panicking::r#try::<isize, [closure@std::rt::lang_start_internal::{closure#2}]>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:456:19
   = note: inside `std::panic::catch_unwind::<[closure@std::rt::lang_start_internal::{closure#2}], isize>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:137:14
   = note: inside `std::rt::lang_start_internal` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:128:20
   = note: inside `std::rt::lang_start::<()>` at /home/jnewsome/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:144:17
   = note: this error originates in the attribute macro `test` (in Nightly builds, run with -Z macro-backtrace for more info)

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 15 (10 by maintainers)

Most upvoted comments

If I get rid of the intermediate slice and cast the pointer directly, miri seems to think it’s ok:

Actually this is starting to make sense. In the current helpers, there’s a point in time where the same underlying data has both a slice of &[u8] and a &T referring to it. In this case, since &T contains UnsafeCell, miri knows that the underlying data has interior mutability, which is incompatible with the &[u8] alias.