Description
liballoc's test_drain
fails when run in Miri. The error occurs in the drain(...).collect()
call:
collect
is called with avec_deque::Drain<usize>
as argument.Drain
containsIter
contains a shared reference to a slice; that slice is thus marked as "must not be mutated for the entire duration of this function call".collect
callsfrom_iter
callsextend
callsfor_each
callsfold
, which eventually drops theDrain
.Drain::drop
callssource_deque.wrap_copy
to re-arrange stuff (I have not fully understood this yet), and in some cases this will end up writing to memory that the slice inIter
points to.
I am not sure what the best way to fix this is. We have to fix Drain
holding (indirectly) a shared reference to something that it'll mutate during its Drop
. The mutation is likely there for a reason, so I guess the shared reference has to go. (FWIW, this shared reference already caused other trouble, but that was fixed by #56161.)
Activity
Gankra commentedon Apr 18, 2019
yeah I guess just change it to
*const [T]
and toss in a PhantomData for the borrow. shrugRalfJung commentedon Apr 18, 2019
You mean, change this in
Iter
as well? Sure, that should work. In principle this reduces the precision of compiler analyses onVecDeque::iter()
.Gankra commentedon Apr 18, 2019
ahh misread. maybe would just not use Iter then. depends on what the impl looks like now
cuviper commentedon Apr 18, 2019
But the first thing it does is
self.for_each(drop);
which should exhaust theIter
such that itsring
is an empty slice. I suppose its pointer will still be somewhere in the deque's memory, but with length 0 it can't actually read anything. Is that not good enough to appease Miri?RalfJung commentedon Apr 19, 2019
@cuviper
No. This code still matches the pattern
There is no notion of "exhausting" or "consuming" a shared reference. When you create a shared reference with a certain lifetime, you promise that until that lifetime ends, no mutation happens.
x
here is mutable so you can change what the slice points to, but that has no effect at all on the guarantees that have been made about the value that was initially passed tofoo
.Now, lifetimes are an entirely static concept, so for Stacked Borrows a "lifetime" generally is "until the pointer is used for the last time". However, for the special case of a reference being passed to a function, Stacked Borrows asserts that the "lifetime" will last at least as long as the function the reference is pointed to. (This is enforced by the "barriers" explained in this blog post.) This is extremely useful for optimizations.
RalfJung commentedon Apr 19, 2019
Drain::{next, next_back, size_hint}
forward toIter
. I wouldn't want to copy those.cuviper commentedon Apr 19, 2019
Oh, I also didn't realize that
Iter
doesn't actually shrink down to an empty slice -- it only updates its indexes. It would otherwise have to use two slices for the wrapped head and tail. So that full shared reference does still exist after the iterator is exhausted. (This also raises the validity question of having references to uninitialized data.)Could we base
Drain
onIterMut
instead? We could arrangewrap_copy
to work through that mutable reference too, so there's no question of mutable aliasing.RalfJung commentedon Apr 19, 2019
That might work. However,
Drain::drop
must not accessdeque.buf
in that case -- that would be in conflict with the unique reference inIterMut
.cuviper commentedon Apr 19, 2019
Hmm, does
Drain
need to be covariant overT
? I guessIterMut
would break that.RalfJung commentedon Apr 19, 2019
Actually this idea of "consuming a shared ref" is far from unique to
VecDeque
... the following code also errors in Miri:Julian-Wollersberger commentedon Apr 20, 2019
As I understand it, Miri and stacked borrows should be "more powerful" than the Rust borrow checker, right?
So, is this an example where Miri does not match what Rust allows or is this a bug in Rust that could lead to undefined behaviour in safe code?
(Your example in the playground)
15 remaining items
-Zmiri-retag-fields
libstd test failure insideVecDeque::drain
#997015225225 commentedon Aug 13, 2022
Smaller reproduction that I found in #99701 (a now closed dupe of this issue) that doesn't rely on VecDeque internals, that fails now with
-Zmiri-retag-fields
RalfJung commentedon Aug 13, 2022
-Zmiri-retag-fields
re-enables the part of Stacked Borrows that got disabled (in parts) to work around the issue, before I knew that we actually havenoalias
here and are risking real miscompilations.Auto merge of #2523 - saethlin:protector-test, r=RalfJung
saethlin commentedon Sep 2, 2022
@rustbot claim
Auto merge of rust-lang#101299 - saethlin:vecdeque-drain-drop, r=thomcc
Auto merge of #101299 - saethlin:vecdeque-drain-drop, r=thomcc