Stacked Borrows violation in BufWriter::into_parts
#127584
Description
I tried this code: [playground]
use std::io::{Cursor, BufWriter};
fn main() {
let mut v = vec![0; 1024];
let c = Cursor::new(&mut v);
let w = BufWriter::new(Box::new(c));
let _ = w.into_parts();
}
I expected to see this happen: it runs cleanly under Miri.
Instead, this happened:
error: Undefined Behavior: trying to retag from <2686> for Unique permission at alloc1164[0x0], but that tag does not exist in the borrow stack for this location
--> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/buffered/bufwriter.rs:175:10
|
175 | (inner, buf)
| ^^^^^
| |
| trying to retag from <2686> for Unique permission at alloc1164[0x0], but that tag does not exist in the borrow stack for this location
| this error occurs as part of retag at alloc1164[0x0..0x10]
|
= 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: <2686> was created by a Unique retag at offsets [0x0..0x10]
--> src/main.rs:7:13
|
7 | let _ = w.into_parts();
| ^^^^^^^^^^^^^^
help: <2686> was later invalidated at offsets [0x0..0x10] by a Unique retag (of a reference/box inside this compound value)
--> src/main.rs:7:13
|
7 | let _ = w.into_parts();
| ^^^^^^^^^^^^^^
= note: BACKTRACE (of the first span):
= note: inside `std::io::BufWriter::<std::boxed::Box<std::io::Cursor<&mut std::vec::Vec<u8>>>>::into_parts` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/buffered/bufwriter.rs:175:10: 175:15
note: inside `main`
--> src/main.rs:7:13
|
7 | let _ = w.into_parts();
| ^^^^^^^^^^^^^^
The issue is in this construct:
By the SB model, moving a (struct directly containing) Box
retags that Box
as the live unique owner of the referenced allocation. Thus passing self
to forget
invalidates the duplicated Box
(W
) we just retrieved. The correct way to write this is with ManuallyDrop
:
// inner is the only remaining field with nontrivial drop glue
let this = ManuallyDrop::new(self);
// SAFETY: this takes logical ownership of inner
let inner = unsafe { ptr::read(&this.inner) };
Meta
Tested against 1.81.0-nightly (2024-07-09 6be96e3).
This also catches BufWriter::into_inner
, which is implemented using into_parts
.
IIRC the LLVM backend is currently not informed about this instance of this UB, as the relevant attributes are only added when the argument passing ABI is Scalar
or ScalarPair
, which it is not in this case. Additionally, Miri's SB implementation does not complain when the box is Box<dyn Write>
, (conjecture: since dyn Write
is not Unpin
).
cc @RalfJung — another case of read; forget
instead of ManuallyDrop::new; read
in std.
This was randomly spotted. It might be worth it to do a grep for forget
to try to spot further such mistakes. forget
's docs call this out as incorrect, but it's still quite easy to overlook.
Activity