-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
VecDeque's Drain::drop writes to memory that a shared reference points to #60076
Comments
yeah I guess just change it to |
You mean, change this in |
ahh misread. maybe would just not use Iter then. depends on what the impl looks like now |
But the first thing it does is |
No. This code still matches the pattern fn foo(mut x: (T, &[U])) {
let ptr = &x.1[0] as *const U;
// do some stuff with x that "semantically" "exhausts" the slice.
x.1 = &[];
// then write to where `x.1` used to point to.
*(ptr as *mut U) = ...;
} 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. 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. |
|
Oh, I also didn't realize that Could we base |
That might work. However, |
Hmm, does |
Actually this idea of "consuming a shared ref" is far from unique to use std::cell::{RefCell, Ref};
fn break_it(rc: &RefCell<i32>, r: Ref<'_, i32>) {
// `r` has a shared reference, it is passed in as argument and hence
// a barrier is added that marks this memory as read-only for the entire
// duration of this function.
drop(r);
// *oops* here we can mutate that memory.
*rc.borrow_mut() = 2;
}
fn main() {
let rc = RefCell::new(0);
break_it(&rc, rc.borrow())
} |
As I understand it, Miri and stacked borrows should be "more powerful" than the Rust borrow checker, right? |
@Julius-Beides -- I believe @RalfJung is admitting that |
This is up for us all to decide. I can't tell you. ;)
Not necessary. I have argued that |
FWIW here is a stand-alone version of the testcase to reproduce this with Miri (but it needs access to some private use std::collections::VecDeque;
fn main() {
let mut tester: VecDeque<usize> = VecDeque::with_capacity(3);
let cap = tester.capacity();
for len in 0..=cap {
for tail in 0..=cap {
for drain_start in 0..=len {
for drain_end in drain_start..=len {
tester.tail = tail;
tester.head = tail;
for i in 0..len {
tester.push_back(i);
}
// Check that we drain the correct values
println!("Testing {} {} {} {}", len, tail, drain_start, drain_end);
let drained: VecDeque<_> = tester.drain(drain_start..drain_end).collect();
let drained_expected: VecDeque<_> = (drain_start..drain_end).collect();
assert_eq!(drained, drained_expected);
// We shouldn't have changed the capacity or made the
// head or tail out of bounds
assert_eq!(tester.capacity(), cap);
assert!(tester.tail < tester.cap());
assert!(tester.head < tester.cap());
// We should see the correct values in the VecDeque
let expected: VecDeque<_> = (0..drain_start)
.chain(drain_end..len)
.collect();
assert_eq!(expected, tester);
}
}
}
}
} |
See rust-lang/unsafe-code-guidelines#125 for the issue about whether Stacked Borrows should allow such code or not. For now, with rust-lang/miri#872, Miri accepts this code again. So I am going to close the issue here. It still represents an interesting data point to inform what Stacked Borrows rules should be. |
nods wisely just gotta stand still long enough for the spec to bend around you |
@gankro ;-) I don't think the decision is made either way, but for now with several counterexamples in libstd, it is not helpful for Miri to complain about this. Plus, this matches what we formalized in Coq; the basic optimizations that we considered so far are all fine with this change. |
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 use std::collections::VecDeque;
fn main() {
let mut tester: VecDeque<usize> = VecDeque::with_capacity(3);
for i in 0..3 {
tester.push_back(i);
}
let _: VecDeque<_> = tester.drain(1..2).collect();
} |
|
Add a protector test that demonstrates the base tag diagnostic Per #2519 (comment), this demonstrates this case for protector diagnostics: ``` help: <3131> was created here, as a base tag for alloc1623 --> tests/fail/stacked_borrows/invalidate_against_protector3.rs:10:19 | 10 | let ptr = std::alloc::alloc(std::alloc::Layout::for_value(&0i32)) as *mut i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``` This diagnostic is inspired by what Miri used to do with rust-lang/rust#60076 (comment)
@rustbot claim |
Remove &[T] from vec_deque::Drain Fixes rust-lang/rust#60076 I don't know what the right approach is here. There were a few suggestions in the issue, and they all seem a bit thorny to implement. So I just picked one that was kind of familiar.
liballoc's
test_drain
fails when run in Miri. The error occurs in thedrain(...).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 itsDrop
. 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.)Cc @nikomatsakis @gankro
The text was updated successfully, but these errors were encountered: