On Thu, Jul 25, 2024 at 4:48 PM Manos Pitsidianakis <manos.pitsidiana...@linaro.org> wrote: > > pl011_receive (called by qemu_chr_fe_accept_input) creates a mutable > > reference that *overlaps* the lifetime of the outer reference created > > by pl011_read. This is undefined behavior. You're effectively writing: > > There is no overlap there, sorry. Once qemu_chr_fe_accept_input > returns, any references it created do not exist anymore.
read |-------------| receive |-----| That's the overlap. Maybe you're thinking that the outer &mut "goes to sleep" and is reborn when qemu_chr_fe_accept_input() returns, but there's no such thing in the language. You can do it within a function: fn main() { let mut x = 42u32; let a = &mut x; let b = &mut *a; *b = 0; *a = 43; // *b = 42; } But that's because the compiler _knows_ the provenance of b. So it says "ok, 'b' is basically the same as 'a' and 'a' won't be accessed while 'b' is live". This doesn't happen in any other case. In fact miri points out that, the moment you create a &mut in pl011_read, the chardev's opaque becomes effectively unusable[1]. In other words, you're not even allowed to turn it from a *mut into a &mut! Again: it's exactly the same as the example above fn main() { let mut foo = String::from("foo"); let x = &mut foo; // pl011_read has a reference let x_ptr: *mut String = x as *mut _; // pl011_receive starts let y = &mut foo; // pl011_receive creates a reference // at this point, x becomes invalid print!("{}", y); // pl011_receive returns // this is undefined behavior (shown by miri) print!("{}", unsafe { &mut *x_ptr }); } Honestly I don't see how I can provide a proof or explanation that is more definitive than miri. If some specific documentation or discussions gives you the perception that there is no undefined behavior, I'm happy to check them out and provide an explanation in that context. But otherwise, I don't think it's useful to keep debating *whether* it is undefined behavior. Paolo [1] this is the other example I gave a few hours ago, which I'll copy here for reference: // MIRIFLAGS=-Zmiri-ignore-leaks cargo +nightly miri run use std::mem::MaybeUninit; struct S { me: *mut S, them: *mut S } impl S { pub fn chardev_receive(&mut self) { println!("in chardev_receive with &mut"); } pub fn memory_write_good(&mut self) { println!("in memory_write_good, calling qemu_chr_fe_accept_input()"); qemu_chr_fe_accept_input_good(self); } pub fn memory_write_bad(&mut self) { println!("in memory_write_bad, calling qemu_chr_fe_accept_input()"); qemu_chr_fe_accept_input_bad(self); } } fn qemu_chr_fe_accept_input_good(c: &S) { // you can still go from *mut to &mut in _another_ instance of struct S (unsafe { &mut *c.them }).chardev_receive(); } fn qemu_chr_fe_accept_input_bad(c: &S) { // you cannot go from *mut to &mut when it points to _this_ instance; // creating the &mut that is passed to memory_write_bad() has // effectively made that *mut unusable! (unsafe { &mut *c.me }).chardev_receive(); } fn main() { let p: &mut MaybeUninit<S> = Box::leak(Box::new(MaybeUninit::uninit())); let q: &mut MaybeUninit<S> = Box::leak(Box::new(MaybeUninit::uninit())); unsafe { let p_mut_ptr = p.as_mut_ptr(); let q_mut_ptr = q.as_mut_ptr(); *(&mut *p_mut_ptr) = S { me: p_mut_ptr, them: q_mut_ptr }; (&mut *p_mut_ptr).memory_write_bad(); (&mut *p_mut_ptr).memory_write_good(); } }