Open
Description
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:
fn direct_mut_to_const_raw() {
let x = &mut 0;
let y: *const i32 = x;
unsafe { *(y as *mut i32) = 1; }
assert_eq!(*x, 1);
}
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
, meaning y
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.
Metadata
Metadata
Assignees
Labels
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
RalfJung commentedon Dec 7, 2018
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.
eddyb commentedon Dec 7, 2018
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
&*
explicitly.eddyb commentedon Dec 7, 2018
@nikomatsakis I believe the comment above
if is_ref {
has been outdated for quite a while (and makes no sense for NLL), can you confirm?RalfJung commentedon Dec 7, 2018
@eddyb that code generates "adjustments". I have no idea what that means. I found no use of
AutoBorrow::RawPtr
that would turn this into casts or reborrows or so.eddyb commentedon Dec 7, 2018
Oh I got confused as to what this is doing, I missed the
AutoBorrow::RawPtr
bit, looks like "borrow" and "cast reference to raw pointer" are fused into one adjustment. The lowering is here, btw:rust/src/librustc_mir/hair/cx/expr.rs
Lines 152 to 180 in 1c3236a
So looking again at the testcase,
y as *mut i32
still goes through the reference->raw pointer coercion logic, but it does so for coercing&mut T
to*mut T
which does a mutable reborrow instead of an immutable reborrow (and then there's a separate*mut T
to*const T
coercion).Changing
AutoBorrow::RawPtr(mutbl_b)
toAutoBorrow::RawPtr(mt_a.mutbl)
would work, but you then also need to pushAdjust::MutToConstPointer
to thatvec![...]
, ifmt_a.mutbl != mutbl_b
.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.
RalfJung commentedon Dec 7, 2018
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.
eddyb commentedon Dec 7, 2018
Without
Adjust::MutToConstPointer
the MIR may end up malformed (using*mut T
where*const T
is expected), I'm surprised you don't get any errors!RalfJung commentedon Dec 7, 2018
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 think my inclination is that we don't want to reborrow on a cast-to-raw. That would also make testing Stacked Borrows in miri easier, these implicit unavoidable reborrows are a pain. :P We'd have to make sure though that the borrow checker understands that after a
(x: &mut T) as *mut T
,x
is still there -- it hasn't been moved away.eddyb commentedon Dec 7, 2018
I guess the tricky bit with making it a cast is that what we have to work with is
Operand::{Move,Copy}
.Instead, we could just add to the MIR a sort of "borrow to raw pointer" (a lot like
AutoBorrow::RawPtr
, really), and then we just need to make sure the old borrowck doesn't treat even a mutableAutoBorrow::RawPtr
like a real borrow.RalfJung commentedon Dec 8, 2018
You mean like rust-lang/rfcs#2582? :D
eddyb commentedon Dec 8, 2018
@RalfJung Yupp, that's what I mean, that seems like the perfect solution here.
92 remaining items