-
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: fix for stacked borrows #56161
Conversation
r? @KodrAus (rust_highfive has picked a reviewer for you, use r? to override) |
Turns out just not using a mutable buffer, as suggested by @gnzlbg, is enough :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
Since this is super tricky stuff, it would be awesome if the PR description was updated to correspond to the final state of the PR . |
@bluss Good point, done. |
src/libcore/ptr.rs
Outdated
@@ -2808,14 +2808,14 @@ impl<T: ?Sized> fmt::Pointer for Unique<T> { | |||
#[unstable(feature = "ptr_internals", issue = "0")] | |||
impl<'a, T: ?Sized> From<&'a mut T> for Unique<T> { | |||
fn from(reference: &'a mut T) -> Self { | |||
Unique { pointer: NonZero(reference as _), _marker: PhantomData } | |||
Unique { pointer: NonZero(reference as *mut T as *const T), _marker: PhantomData } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand you correctly, the _
here was filled in with &'a T
, and that's no good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
Ideally we could make it infer *mut T
for the _
, that would be the safer choice.
3a3502d
to
8223fc1
Compare
Rebased (because I want to test this together with #56165) |
This comment has been minimized.
This comment has been minimized.
By going through a shared reference, we share the destination as read-only, meaning we can read but not write with the raw pointers
8223fc1
to
feb775c
Compare
This PR is now awaiting review for 12 days. Ping @KodrAus |
r? me |
r? @gnzlbg |
These are really subtle rules, and I’m worried about what this means for This combination feels very dangerous:
|
I think I feel strongly enough about this that I’m opposed to landing this PR as-is. Making soundness rely on the non-existence of a shared reference is not acceptable IMO as long as such a shared reference can be created "invisibly" like this. |
@SimonSapin I am not sure that I understand your argument correctly. Are you arguing that it is easy to assert immutability accidentally and therefore introduce undefined behavior ? If so, I agree. But this code was doing so explicitly with There are a couple of ideas being floated around about how to prevent "asserting immutability by accident" (e.g. making |
Yes, this is what I’m arguing. We have here a combination of three things:
It’s only with all three combined that there’s a problem. So we can fix the problem in any of three ways: A. Not accepting this proposed model I am opposed to doing only C. because that doen’t help with other potential instances of the same problem. This is made worse by 2. being, in my opinion, very unexpected. I claim to be fairly proficient at Rust, and I had no idea this was happening. So I think we should fix |
From my understanding, this was an explicit design goal from the start. Cc @eddyb @gankro @nikomatsakis Not doing this kills a lot of useful optimizations, because it means that creating a shared reference gives license to anyone to mutate data (until a barrier is added). In particular, the change I am making here is a necessary requirement to ever solve #16515 for shared references.
This was one of the first rules I ever learned about mutability in Rust: Though shall not mutate through shared references. Of course this has to include raw pointers created from shared references, otherwise the rule is useless (the compiler syntactically prevents direct assignment to a shared reference). This rule is the reason we have
That, I think, is a problem. We should change the coercion logic to only coerce to The reason I would like to land this PR is that it is needed to move forward with experimenting with this model in miri. I do not see this as acknowledgement that these will ultimately be the rules. But as long as a core data structure like When I previously talked about this with @nikomatsakis, he was open to changing the coercion rules in principle, but also said that coercion is a mess and in its current state maybe should not be touched, but rewritten. This PR is a way forward for experimentation with miri that is not blocked on fixing coercion. But I absolutely agree that we should block accepting this model and exploiting it in the compiler on fixing coercions. I am not sure what would be the best place to track this. |
So is the problem here that
I hope/suspect "rewritten" here doesn't mean just rewriting the |
The problem is that coercion chains are not atomic. I have no idea what |
FWIW, I'm fine to changing this to |
Are |
I opened #56604 for the coercion problem, let's continue the discussion there. |
@SimonSapin Beyond the issue above, I have added a not-yet-working test with a FIXME to miri and recorded this in the "missing validation tracking issue" at rust-lang/miri#551. Is that enough to remedy your concerns about this change?
I could change miri to be less strict about the "writing through raw pointers obtained from shared references" rule, but it seems strange to accept code that we definitely know we do not want to accept. |
Turns out that Still, seems likely that other code (besides |
Ok, landing this PR to unblock other work sounds acceptable, with the understanding that it is not "the" way to fix this kind of unsoundess (#56604 is) and that unsafe code outside of std does not need to be audited for this pattern. |
@SimonSapin As a process note (not an opinion on #56604 or rust-lang/rfcs#2582), such an understanding depends on consensus within T-lang; Since this is a soundness fix, we should just land it with a |
It’s not actually a soundness fix since nothing in codegen relies on the model this code violates |
Agreed. Okay so can I get someone to r+ this? :D |
@bors r+ |
📌 Commit feb775c has been approved by |
…r=SimonSapin VecDeque: fix for stacked borrows `VecDeque` violates a version of stacked borrows where creating a shared reference is not enough to make a location *mutably accessible* from raw pointers (and I think that is the version we want). There are two problems: * Creating a `NonNull<T>` from `&mut T` goes through `&T` (inferred for a `_`), then `*const T`, then `NonNull<T>`. That means in this stricter version of Stacked Borrows, we cannot actually write to such a `NonNull` because it was created from a shared reference! This PR fixes that by going from `&mut T` to `*mut T` to `*const T`. * `VecDeque::drain` creates the `Drain` struct by *first* creating a `NonNull` from `self` (which is an `&mut VecDeque`), and *then* calling `self.buffer_as_mut_slice()`. The latter reborrows `self`, asserting that `self` is currently the unique pointer to access this `VecDeque`, and hence invalidating the `NonNull` that was created earlier. This PR fixes that by instead using `self.buffer_as_slice()`, which only performs read accesses and creates only shared references, meaning the raw pointer (`NonNull`) remains valid. It is possible that other methods on `VecDeque` do something similar, miri's test coverage of `VecDeque` is sparse to say the least. Cc @nikomatsakis @gankro
VecDeque: fix for stacked borrows `VecDeque` violates a version of stacked borrows where creating a shared reference is not enough to make a location *mutably accessible* from raw pointers (and I think that is the version we want). There are two problems: * Creating a `NonNull<T>` from `&mut T` goes through `&T` (inferred for a `_`), then `*const T`, then `NonNull<T>`. That means in this stricter version of Stacked Borrows, we cannot actually write to such a `NonNull` because it was created from a shared reference! This PR fixes that by going from `&mut T` to `*mut T` to `*const T`. * `VecDeque::drain` creates the `Drain` struct by *first* creating a `NonNull` from `self` (which is an `&mut VecDeque`), and *then* calling `self.buffer_as_mut_slice()`. The latter reborrows `self`, asserting that `self` is currently the unique pointer to access this `VecDeque`, and hence invalidating the `NonNull` that was created earlier. This PR fixes that by instead using `self.buffer_as_slice()`, which only performs read accesses and creates only shared references, meaning the raw pointer (`NonNull`) remains valid. It is possible that other methods on `VecDeque` do something similar, miri's test coverage of `VecDeque` is sparse to say the least. Cc @nikomatsakis @gankro
☀️ Test successful - status-appveyor, status-travis |
Tested on commit rust-lang/rust@9fe5cb5. Direct link to PR: <rust-lang/rust#56161> 💔 rls on linux: test-pass → test-fail (cc @nrc @Xanewok, @rust-lang/infra).
@alexcrichton @kennytm this is intermittent - any possibility to retrigger the (On a similar note, why do newest nightlies don't contain RLS or rustfmt? The toolstate was ok and the manifest tool was fixed from what I've seen - anything else blocking these from distribution?) |
That would be #56667. There simply wasn't a new nightly since the tools were fixed. |
Ah, so it's not because the nightlies are blocked on broken tools but there's a problem with distributing nightlies itself! Makes sense, thanks for the heads up. EDIT: Or maybe not (it seems clippy managed to break just before the distribution). In any case, hopefully the next nightlies are okay. |
VecDeque
violates a version of stacked borrows where creating a shared reference is not enough to make a location mutably accessible from raw pointers (and I think that is the version we want). There are two problems:NonNull<T>
from&mut T
goes through&T
(inferred for a_
), then*const T
, thenNonNull<T>
. That means in this stricter version of Stacked Borrows, we cannot actually write to such aNonNull
because it was created from a shared reference! This PR fixes that by going from&mut T
to*mut T
to*const T
.VecDeque::drain
creates theDrain
struct by first creating aNonNull
fromself
(which is an&mut VecDeque
), and then callingself.buffer_as_mut_slice()
. The latter reborrowsself
, asserting thatself
is currently the unique pointer to access thisVecDeque
, and hence invalidating theNonNull
that was created earlier. This PR fixes that by instead usingself.buffer_as_slice()
, which only performs read accesses and creates only shared references, meaning the raw pointer (NonNull
) remains valid.It is possible that other methods on
VecDeque
do something similar, miri's test coverage ofVecDeque
is sparse to say the least.Cc @nikomatsakis @gankro