-
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
Vec::push invalidates interior references even when it does not reallocate #60847
Comments
Specifically, code for pub fn push(&mut self, value: T) {
if self.len == self.buf.cap() {
self.reserve(1);
}
unsafe {
let end = self.as_mut_ptr().add(self.len);
ptr::write(end, value);
self.len += 1;
}
} and it is There's probably a bunch of other methods with similar problems. For example, |
Good point, I forgot to mention that the call is implicit. I amended my original post. |
|
We already do. :) Specifically, this runs the |
I just noticed fn main() { unsafe {
let x: &[u8] = &[1,2,3];
let x_01 = &x[0..2];
let raw = x_01.as_ptr();
let _val = raw.add(2).read(); // pointer is beyond the bounds of the slice it was created from!
} } This does not currently get caught by Miri because of imprecision around our handling of raw pointers. |
Vec: avoid creating slices to the elements Instead of `self.deref_mut().as_mut_ptr()` to get a raw pointer to the buffer, use `self.buf.ptr_mut()`. This (a) avoids creating a unique reference to all existing elements without any need, and (b) creates a pointer that can actually be used for the *entire* buffer, and not just for the part of it covered by `self.deref_mut()`. I also got worried about `RawVec::ptr` returning a `*mut T` from an `&self`, so I added both a mutable and an immutable version. Cc @gankro in particular for the `assume` changes -- I don't know why that is not in `Unique`, but I moved it up from `Vec::deref` to `RawVec::ptr` to avoid having to repeat it everywhere. Fixes rust-lang#60847
Vec: avoid creating slices to the elements Instead of `self.deref_mut().as_mut_ptr()` to get a raw pointer to the buffer, use `self.buf.ptr_mut()`. This (a) avoids creating a unique reference to all existing elements without any need, and (b) creates a pointer that can actually be used for the *entire* buffer, and not just for the part of it covered by `self.deref_mut()`. I also got worried about `RawVec::ptr` returning a `*mut T` from an `&self`, so I added both a mutable and an immutable version. Cc @gankro in particular for the `assume` changes -- I don't know why that is not in `Unique`, but I moved it up from `Vec::deref` to `RawVec::ptr` to avoid having to repeat it everywhere. Fixes rust-lang#60847
Making the motivating example legal seems to also make the following example legal (playground): fn main() {
let mut v = vec![1, 2, 3, 4, 5];
v.pop();
let ptr = v.as_mut_ptr();
let ptr = unsafe { &mut *ptr };
test(&mut v, ptr);
println!("{:?}", v); // [8, 2, 3, 4, 7]
}
fn test(v: &mut Vec<u32>, x: &mut u32) {
v.push(7);
*x = 8;
} This looks very wrong for me. It also completely breaks Prusti… Isn't there another way to fix whatever code depends on this? |
What do you mean by "it breaks Prusti"? |
Our current encoding assumes that the parameters |
I think that model is incomplete, but that seems fine to me (unless you want to claim your tool is total). After all, one could also easily write code like fn test(v: &mut *mut u32, x: mut u32) {
unsafe { *v.add(1) = 1; }
*x = 2;
} and call this function with let mut arr = [1u32, 2];
let v = arr.as_mut_ptr();
test(v, unsafe { &mut *v }); This is, conceptually, what happens in your example as well. |
Put differently, in your example, Essentially, (My example uses raw ptrs so it does not have a "super-safe" function; maybe that makes it a bad example.) |
Just to avoid potential misunderstanding: here I am interested in the semantics of
My mental image of this sentence is that I should treat the following struct struct Foo {
f: Unique<u32>,
} as if it was: struct Foo {
f: u32,
} And, therefore, be able to conclude that fn bar(x: &mut Foo, y: &mut u32) { ... } Does this make sense to you? Or maybe you see a flaw in my reasoning? |
If I understand you correctly, super safe functions are the functions whose callers are allowed to call them with arguments that have broken invariants, right? My gut feeling says that such functions could result in all type invariants becoming public (I remember seeing this problem popping up with C++ friend functions), which makes reasoning really hard (not only for automatic verification but also for humans). Do you know whether anyone tried to incorporate visibility into the RustBelt proofs (prove that all code that needs to open a type invariant has access to all fields that are mentioned in it)? By the way, I have a feeling that examples like the ones that motivated this issue are coming from the fact that programmers are (mis)using |
This is a good point. However, I think your
I would phrase this slightly differently, namely -- these examples exploit deep knowledge of the invariant of For most types, breaking the abstraction barrier like this would be considered a bug. The exact invariant is an internal implementation detail.
I think |
Btw, discussions in closed issues are generally discouraged and easily get lost. You are raising some interesting questions though. Could you open a new issue and provide a short summary? Then we could invite some t-libs people to determine whether this is considered within the intended usage of |
Sure. I will do that. Edit: the explanation of why this is needed and allowed can be found in this comment. |
To my knowledge, the following code is intended to be legal:
However, Miri currently flags this as UB. The reason is that
Vec::push
implicitly (through auto-deref) callsderef_mut
to create a&mut [T]
covering all elements in the vector, which overlaps with our referencev0
in the example code above. Overlapping shared and mutable references are prohibited by Stacked Borrows.The text was updated successfully, but these errors were encountered: