-
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
Coercing &mut to *const should not create a shared reference #56604
Comments
I believe the relevant code might be rust/src/librustc_typeck/check/coercion.rs Lines 734 to 752 in f504d3f
but I am not sure because this is type checking. I have no idea where the code that decides about lowering of coercions to reborrows/casts lives. |
What do you mean? That code is generating a deref and a borrow, each of those gets turned into the equivalent MIR later, and the result is as if the user wrote |
@nikomatsakis I believe the comment above |
@eddyb that code generates "adjustments". I have no idea what that means. I found no use of |
Oh I got confused as to what this is doing, I missed the rust/src/librustc_mir/hair/cx/expr.rs Lines 152 to 180 in 1c3236a
So looking again at the testcase, Changing EDIT: this may be backwards incompatible if the mutable reference being coerced was also already immutably borrowed, since you'd be introducing a mutable reborrow. Maybe we can avoid reborrows altogether here? But I'd leave that to @nikomatsakis. |
I did the first but not the last and tests seem to still pass...^^ But yeah, there is a compatibility problem with outstanding shared references. Ouch. |
Without |
Yeah I figured. run-pass and ui tests all pass, so either the change did nothing or nothing checks the MIR^^ For the backwards compatibility issue: |
I guess the tricky bit with making it a cast is that what we have to work with is Instead, we could just add to the MIR a sort of "borrow to raw pointer" (a lot like |
You mean like rust-lang/rfcs#2582? :D |
@RalfJung Yupp, that's what I mean, that seems like the perfect solution here. |
(Sorry, that was the wrong button.) Notice that the lint discussed in rust-lang/rfcs#2582, at least when implemented on the MIR, would actually flag these reborrows that are in our way now: The new reference is created just to turn it into a raw pointer, it has no other use. |
Following this idea, I have updated miri to treat the case of "escaping a ptr to be usable for raw accesses" as just another reborrow: Both trigger a retag; the retag after a cast-to-raw is a bit special because it also retags raw pointers. So, I am convinced now that the best way forward is to entirely ditch ref-to-raw casts, and encode them all as reborrow-to-raw. |
I found another case of this pattern in the wild, see https://internals.rust-lang.org/t/writing-through-a-pointer-derived-from-a-shared-reference-after-that-reference-is-dead/9078 |
This is actually more subtle than I thought. The thing that I did not know when discovering this problem is that the borrow checker treats let x = &mut 0;
let shared = &*x;
let y = x as *const i32; // if we use *mut here instead, this stops compiling
let _val = *shared; IOW, the borrow checker considers a cast to a This means that if we pull through with the plan to make So, everybody in rust-lang/unsafe-code-guidelines#106 (including me and @nikomatsakis but also in particular @SimonSapin) seemed to feel that the following code should be allowed (that's from the OP here): // example 1
let x = &mut 0;
let y: *const i32 = x;
unsafe { *(y as *mut i32) = 1; } But how do we all feel about this code? // example 2
let x = &mut 0;
let shared = &*x;
let y: *const i32 = x;
let _val = *shared;
unsafe { *(y as *mut i32) = 1; } Here, we cannot possibly maintain the idea that Does that mean that we are fine with example 2 being UB? And if it does, does that change our position about example 1? My opinionPersonally I am less sure now about example 1, and feel maybe it should actually be UB, after all. The reason for this is that it leads to the cleanest model, with fewer cases to consider. I will go into several possible models for a bit here. Clean and strictThe IMO most clean model (with the usual caveat that we'll not know how clean this ends up being until it got implemented and tested) is that the behavior of cast-to-raw depends only on the type of the raw pointer ( Basically, However, this would rule out the example in the OP, and run counter the idea (that I repeated a lot) that Extrapolate from current behaviorCurrently, we accept example 1 if it gets changed to
This would allow example 1. disallow example 2, and it would also disallow the following: // example 2
let x = &mut 0;
let shared = &*x;
let y: *const i32 = x;
unsafe { *(y as *mut i32) = 1; }
Two-phase@matthewjasper suggested it might be possible to treat cast-to- |
No, I'm was saying that a model that accepts both example 1 and 2 would effectively be modelling two-phase borrows. I can't see any way to allow this without complicating the model significantly. |
I'd love to see this change. I really like the current status of rust that pointers are basically numbers. and they only matter when you dereference, and even then we have no strict alias rules etc. just alignment+size.(and data must be defined which might have implications with padding). |
That is not true at all I am afraid. Even if you are talking about raw pointers only. See this brief introduction to pointer provenance. Also, the fact that pointers have provenance is entirely off-topic here. "Pointers are just numbers" is plain impossible with LLVM as a backend.
So I am wondering now what you mean by "this". My assumption of course is that you mean this issue, i.e. specifically casting a mutable reference to a const raw pointer. But then you go on talking about things way broader than that (pointer provenance in general) and entirely out of scope for this issue, so I am confused. Also the reason this issue is open is that we agree it's a foot-gun, what we are looking for is a good solution. |
Will read. I heard that a lot so it was more of a quote for me, but interesting to understand. thanks
Yes, I mostly wanted to say this because I encountered this UB in the past, mostly in types similar to |
I've suggested adding a lint for this: rust-lang/rust-clippy#12791 |
|
ptr::from_ref(r) is documented as "This is equivalent to r as *const T" and ptr::from_ref takes a shared reference as its parameter. Thus we are free to write any r as *const T as ptr::from_ref(r).
This argument holds only if r has argument type &T.
|
There are various ways of reading the documentation. One way is to assume that |
Given that the statement contains Looks like we should clarify the docs then. |
As I mentioned in #125897, there are at multiple interpretations of
Maybe the solution here is to precisely define the precedence of the rules. |
That is indeed exactly the open question that is tracked here. |
from_ref, from_mut: clarify documentation This was brought up [here](rust-lang#56604 (comment)). The domain of quantification is generally always constrained by the type in the type signature, and I am not sure it's always worth spelling that out explicitly as that makes things exceedingly verbose. But since this was explicitly brought up, let's clarify.
Rollup merge of rust-lang#125897 - RalfJung:from-ref, r=Amanieu from_ref, from_mut: clarify documentation This was brought up [here](rust-lang#56604 (comment)). The domain of quantification is generally always constrained by the type in the type signature, and I am not sure it's always worth spelling that out explicitly as that makes things exceedingly verbose. But since this was explicitly brought up, let's clarify.
Nominating as a replacement for #129653 (comment) , to bring up the more general situation. |
…tmcm clarify that addr_of creates read-only pointers Stacked Borrows does make this UB, but Tree Borrows does not. This is tied up with rust-lang#56604 and other UCG discussions. Also see [this collection of links](Rust-for-Linux/linux#950 (comment)) where rustc treats `addr_of!` as a "non-mutating use". So, let's better be careful for now.
…tmcm clarify that addr_of creates read-only pointers Stacked Borrows does make this UB, but Tree Borrows does not. This is tied up with rust-lang#56604 and other UCG discussions. Also see [this collection of links](Rust-for-Linux/linux#950 (comment)) where rustc treats `addr_of!` as a "non-mutating use". So, let's better be careful for now.
…tmcm clarify that addr_of creates read-only pointers Stacked Borrows does make this UB, but Tree Borrows does not. This is tied up with rust-lang#56604 and other UCG discussions. Also see [this collection of links](Rust-for-Linux/linux#950 (comment)) where rustc treats `addr_of!` as a "non-mutating use". So, let's better be careful for now.
…tmcm clarify that addr_of creates read-only pointers Stacked Borrows does make this UB, but Tree Borrows does not. This is tied up with rust-lang#56604 and other UCG discussions. Also see [this collection of links](Rust-for-Linux/linux#950 (comment)) where rustc treats `addr_of!` as a "non-mutating use". So, let's better be careful for now.
…tmcm clarify that addr_of creates read-only pointers Stacked Borrows does make this UB, but Tree Borrows does not. This is tied up with rust-lang#56604 and other UCG discussions. Also see [this collection of links](Rust-for-Linux/linux#950 (comment)) where rustc treats `addr_of!` as a "non-mutating use". So, let's better be careful for now.
…tmcm clarify that addr_of creates read-only pointers Stacked Borrows does make this UB, but Tree Borrows does not. This is tied up with rust-lang#56604 and other UCG discussions. Also see [this collection of links](Rust-for-Linux/linux#950 (comment)) where rustc treats `addr_of!` as a "non-mutating use". So, let's better be careful for now.
…tmcm clarify that addr_of creates read-only pointers Stacked Borrows does make this UB, but Tree Borrows does not. This is tied up with rust-lang#56604 and other UCG discussions. Also see [this collection of links](Rust-for-Linux/linux#950 (comment)) where rustc treats `addr_of!` as a "non-mutating use". So, let's better be careful for now.
Rollup merge of rust-lang#129653 - RalfJung:addr-of-read-only, r=scottmcm clarify that addr_of creates read-only pointers Stacked Borrows does make this UB, but Tree Borrows does not. This is tied up with rust-lang#56604 and other UCG discussions. Also see [this collection of links](Rust-for-Linux/linux#950 (comment)) where rustc treats `addr_of!` as a "non-mutating use". So, let's better be careful for now.
clarify that addr_of creates read-only pointers Stacked Borrows does make this UB, but Tree Borrows does not. This is tied up with rust-lang/rust#56604 and other UCG discussions. Also see [this collection of links](Rust-for-Linux/linux#950 (comment)) where rustc treats `addr_of!` as a "non-mutating use". So, let's better be careful for now.
It has long been a rule in Rust that you must not mutate through a shared reference, or a raw pointer obtained from a shared reference.
Unfortunately, that rule currently forbids the following code:
The reason for this is that coercing
&mut T
to*const T
implicitly first creates a shared reference and then coerces that to*const T
, meaningy
in the example above is technically a raw pointer obtained from a shared reference.We should fix our coercion logic to no longer create this intermediate shared reference.
See #56161 for how we uncovered this problem.
Cc @eddyb @nikomatsakis
The text was updated successfully, but these errors were encountered: