-
Notifications
You must be signed in to change notification settings - Fork 348
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
Why does Miri mark &mut
->&mut
reborrows as a read access?
#1878
Comments
Hm, yes that is a very interesting case; thanks for sharing! I will have to dig deeper to figure out why Miri accepts this. |
I think I know what is going on... when you call a function that takes an fn launder<T>(x: *mut T) -> &mut T {&mut *x}
let r3 = launder(r1 as *mut _);
*r3 = 42;
let _ = {&mut *r3};
let _ = {r3}; So basically what your code does is cast I hope future Rust aliasing models can properly account for two-phase borrows, but in Stacked Borrows I decided to give up on them so that I can at least get something finished that supports "regular" mutable and shared references. So, this is an instance of rust-lang/unsafe-code-guidelines#85. |
Ah. I think two-phase borrows aren't discussed in the paper, but your explanation makes sense. If I understand correctly, since all mut argument reborrows are two-phase borrows, this means that all mut argument reborrows are implicitly thrown through a mutable pointer first for purposes of aliasing semantics. That said, it's surprising. What do you want to do with this bug? Close in favor of rust-lang/unsafe-code-guidelines#85 ? |
I agree! I don't want this code to be accepted. But rejecting it without also rejecting safe code that does "funny" things with two-phase borrows is hard. Yeah let's close this issue; there is no Miri bug but an issue in the underlying operational model. Thanks for reporting it, it is a very good test case for potential fixes to the two-phase borrow situation! |
Here's a demo program I put up on the playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5c600b9de975a4c261f7442b77d4421e
If we perform a write access to
r1
, it will invalidatep
, but if we perform a read access, it will not. For example:I think I'm with this so far.
However, this also passes Miri:
It seems to treat reborrowing as a write operation, except when it's part of function call, in which case it's a read operation.
In which case, there's a trick to make all of the not-OK code turn acceptable according to Miri: launder the reference through a reborrow / function call, so that we get a retagged mut reference which we can do write operations onto:
It doesn't make sense to me that these operations would be banned unless retagged through a function call. Since it allows the same runtime behavior anyway, then they should have the same status as either both-UB or both-defined-behavior, right?
As for docs: my understanding from reading the paper and the UCG page is that when you reborrow a
mut
reference, this counts as a write access.The paper states that retagging in function call is e.g. equivalent to
let x = &mut *y;
, and&mut *y
follows the rules for new-mutable-ref. Emphasis mine:(use-2 is about pointers, so doesn't come into play.)
Similarly, the UCG page, emphasis mine:
So my uninformed, uneducated reading is pointing me towards
launder
not being a valid trick, and all of these things being UB.The text was updated successfully, but these errors were encountered: