Skip to content

Commit f70f13a

Browse files
authored
Rollup merge of #120932 - RalfJung:mut-ptr-to-static, r=oli-obk
const_mut_refs: allow mutable pointers to statics Fixes #118447 Writing this PR became a bit messy, see [Zulip](https://rust-lang.zulipchat.com/#narrow/stream/146212-t-compiler.2Fconst-eval/topic/Statics.20pointing.20to.20interior.20mutable.20statics) for some of my journey.^^ Turns out there was a long-standing bug in our qualif logic that led to us incorrectly classifying certain places as "no interior mut" when they actually had interior mut. Due to that the `const_refs_to_cell` feature gate was required far less often than it otherwise would, e.g. in [this code](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=9e0c042c451b3d11d64dd6263679a164). Fixing this however would be a massive breaking change all over libcore and likely the wider ecosystem. So I also changed the const-checking logic to just not require the feature gate for the affected cases. While doing so I discovered a bunch of logic that is not explained and that I could not explain. However I think stabilizing some const-eval feature will make most of that logic inconsequential so I just added some FIXMEs and left it at that. r? `@oli-obk`
2 parents 5f21609 + 340f8aa commit f70f13a

13 files changed

+173
-62
lines changed

compiler/rustc_const_eval/src/transform/check_consts/check.rs

+33-4
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
344344
visitor.visit_ty(ty);
345345
}
346346

347-
fn check_mut_borrow(&mut self, local: Local, kind: hir::BorrowKind) {
347+
fn check_mut_borrow(&mut self, place: &Place<'_>, kind: hir::BorrowKind) {
348348
match self.const_kind() {
349349
// In a const fn all borrows are transient or point to the places given via
350350
// references in the arguments (so we already checked them with
@@ -355,10 +355,19 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
355355
// to mutable memory.
356356
hir::ConstContext::ConstFn => self.check_op(ops::TransientMutBorrow(kind)),
357357
_ => {
358+
// For indirect places, we are not creating a new permanent borrow, it's just as
359+
// transient as the already existing one. For reborrowing references this is handled
360+
// at the top of `visit_rvalue`, but for raw pointers we handle it here.
361+
// Pointers/references to `static mut` and cases where the `*` is not the first
362+
// projection also end up here.
358363
// Locals with StorageDead do not live beyond the evaluation and can
359364
// thus safely be borrowed without being able to be leaked to the final
360365
// value of the constant.
361-
if self.local_has_storage_dead(local) {
366+
// Note: This is only sound if every local that has a `StorageDead` has a
367+
// `StorageDead` in every control flow path leading to a `return` terminator.
368+
// The good news is that interning will detect if any unexpected mutable
369+
// pointer slips through.
370+
if place.is_indirect() || self.local_has_storage_dead(place.local) {
362371
self.check_op(ops::TransientMutBorrow(kind));
363372
} else {
364373
self.check_op(ops::MutBorrow(kind));
@@ -390,6 +399,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
390399
trace!("visit_rvalue: rvalue={:?} location={:?}", rvalue, location);
391400

392401
// Special-case reborrows to be more like a copy of a reference.
402+
// FIXME: this does not actually handle all reborrows. It only detects cases where `*` is the outermost
403+
// projection of the borrowed place, it skips deref'ing raw pointers and it skips `static`.
404+
// All those cases are handled below with shared/mutable borrows.
405+
// Once `const_mut_refs` is stable, we should be able to entirely remove this special case.
406+
// (`const_refs_to_cell` is not needed, we already allow all borrows of indirect places anyway.)
393407
match *rvalue {
394408
Rvalue::Ref(_, kind, place) => {
395409
if let Some(reborrowed_place_ref) = place_as_reborrow(self.tcx, self.body, place) {
@@ -460,7 +474,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
460474

461475
if !is_allowed {
462476
self.check_mut_borrow(
463-
place.local,
477+
place,
464478
if matches!(rvalue, Rvalue::Ref(..)) {
465479
hir::BorrowKind::Ref
466480
} else {
@@ -478,7 +492,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
478492
place.as_ref(),
479493
);
480494

481-
if borrowed_place_has_mut_interior {
495+
// If the place is indirect, this is basically a reborrow. We have a reborrow
496+
// special case above, but for raw pointers and pointers/references to `static` and
497+
// when the `*` is not the first projection, `place_as_reborrow` does not recognize
498+
// them as such, so we end up here. This should probably be considered a
499+
// `TransientCellBorrow` (we consider the equivalent mutable case a
500+
// `TransientMutBorrow`), but such reborrows got accidentally stabilized already and
501+
// it is too much of a breaking change to take back.
502+
if borrowed_place_has_mut_interior && !place.is_indirect() {
482503
match self.const_kind() {
483504
// In a const fn all borrows are transient or point to the places given via
484505
// references in the arguments (so we already checked them with
@@ -495,6 +516,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
495516
// final value.
496517
// Note: This is only sound if every local that has a `StorageDead` has a
497518
// `StorageDead` in every control flow path leading to a `return` terminator.
519+
// The good news is that interning will detect if any unexpected mutable
520+
// pointer slips through.
498521
if self.local_has_storage_dead(place.local) {
499522
self.check_op(ops::TransientCellBorrow);
500523
} else {
@@ -948,6 +971,12 @@ fn place_as_reborrow<'tcx>(
948971
) -> Option<PlaceRef<'tcx>> {
949972
match place.as_ref().last_projection() {
950973
Some((place_base, ProjectionElem::Deref)) => {
974+
// FIXME: why do statics and raw pointers get excluded here? This makes
975+
// some code involving mutable pointers unstable, but it is unclear
976+
// why that code is treated differently from mutable references.
977+
// Once TransientMutBorrow and TransientCellBorrow are stable,
978+
// this can probably be cleaned up without any behavioral changes.
979+
951980
// A borrow of a `static` also looks like `&(*_1)` in the MIR, but `_1` is a `const`
952981
// that points to the allocation for the static. Don't treat these as reborrows.
953982
if body.local_decls[place_base.local].is_ref_to_static() {

compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs

+24
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ pub trait Qualif {
7474
adt: AdtDef<'tcx>,
7575
args: GenericArgsRef<'tcx>,
7676
) -> bool;
77+
78+
/// Returns `true` if this `Qualif` behaves sructurally for pointers and references:
79+
/// the pointer/reference qualifies if and only if the pointee qualifies.
80+
///
81+
/// (This is currently `false` for all our instances, but that may change in the future. Also,
82+
/// by keeping it abstract, the handling of `Deref` in `in_place` becomes more clear.)
83+
fn deref_structural<'tcx>(cx: &ConstCx<'_, 'tcx>) -> bool;
7784
}
7885

7986
/// Constant containing interior mutability (`UnsafeCell<T>`).
@@ -103,6 +110,10 @@ impl Qualif for HasMutInterior {
103110
// It arises structurally for all other types.
104111
adt.is_unsafe_cell()
105112
}
113+
114+
fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool {
115+
false
116+
}
106117
}
107118

108119
/// Constant containing an ADT that implements `Drop`.
@@ -131,6 +142,10 @@ impl Qualif for NeedsDrop {
131142
) -> bool {
132143
adt.has_dtor(cx.tcx)
133144
}
145+
146+
fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool {
147+
false
148+
}
134149
}
135150

136151
/// Constant containing an ADT that implements non-const `Drop`.
@@ -210,6 +225,10 @@ impl Qualif for NeedsNonConstDrop {
210225
) -> bool {
211226
adt.has_non_const_dtor(cx.tcx)
212227
}
228+
229+
fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool {
230+
false
231+
}
213232
}
214233

215234
// FIXME: Use `mir::visit::Visitor` for the `in_*` functions if/when it supports early return.
@@ -303,6 +322,11 @@ where
303322
return false;
304323
}
305324

325+
if matches!(elem, ProjectionElem::Deref) && !Q::deref_structural(cx) {
326+
// We have to assume that this qualifies.
327+
return true;
328+
}
329+
306330
place = place_base;
307331
}
308332

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//@check-pass
2+
//! This is the reduced version of the "Linux kernel vtable" use-case.
3+
#![feature(const_mut_refs, const_refs_to_static)]
4+
use std::ptr::addr_of_mut;
5+
6+
#[repr(C)]
7+
struct ThisModule(i32);
8+
9+
trait Module {
10+
const THIS_MODULE_PTR: *mut ThisModule;
11+
}
12+
13+
struct MyModule;
14+
15+
// Generated by a macro.
16+
extern "C" {
17+
static mut THIS_MODULE: ThisModule;
18+
}
19+
20+
// Generated by a macro.
21+
impl Module for MyModule {
22+
const THIS_MODULE_PTR: *mut ThisModule = unsafe { addr_of_mut!(THIS_MODULE) };
23+
}
24+
25+
struct Vtable {
26+
module: *mut ThisModule,
27+
foo_fn: fn(*mut ()) -> i32,
28+
}
29+
30+
trait Foo {
31+
type Mod: Module;
32+
33+
fn foo(&mut self) -> i32;
34+
}
35+
36+
fn generate_vtable<T: Foo>() -> &'static Vtable {
37+
&Vtable {
38+
module: T::Mod::THIS_MODULE_PTR,
39+
foo_fn: |ptr| unsafe { &mut *ptr.cast::<T>() }.foo(),
40+
}
41+
}
42+
43+
fn main() {}

tests/ui/consts/issue-17718-const-bad-values.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ const C1: &'static mut [usize] = &mut [];
66
static mut S: usize = 3;
77
const C2: &'static mut usize = unsafe { &mut S };
88
//~^ ERROR: referencing statics in constants
9+
//~| ERROR: mutable references are not allowed
910

1011
fn main() {}

tests/ui/consts/issue-17718-const-bad-values.stderr

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@ LL | const C2: &'static mut usize = unsafe { &mut S };
1616
= note: `static` and `const` variables can refer to other `const` variables. A `const` variable, however, cannot refer to a `static` variable.
1717
= help: to fix this, the value can be extracted to a `const` and then used.
1818

19-
error: aborting due to 2 previous errors
19+
error[E0658]: mutable references are not allowed in constants
20+
--> $DIR/issue-17718-const-bad-values.rs:7:41
21+
|
22+
LL | const C2: &'static mut usize = unsafe { &mut S };
23+
| ^^^^^^
24+
|
25+
= note: see issue #57349 <https://github.com/rust-lang/rust/issues/57349> for more information
26+
= help: add `#![feature(const_mut_refs)]` to the crate attributes to enable
27+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
28+
29+
error: aborting due to 3 previous errors
2030

2131
Some errors have detailed explanations: E0658, E0764.
2232
For more information about an error, try `rustc --explain E0658`.

tests/ui/consts/miri_unleashed/box.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ help: skipping check for `const_mut_refs` feature
1616
|
1717
LL | &mut *(Box::new(0))
1818
| ^^^^^^^^^^^^^^^^^^^
19-
help: skipping check that does not even have a feature gate
19+
help: skipping check for `const_mut_refs` feature
2020
--> $DIR/box.rs:8:5
2121
|
2222
LL | &mut *(Box::new(0))

tests/ui/consts/miri_unleashed/mutable_references_err.32bit.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ help: skipping check for `const_refs_to_static` feature
114114
|
115115
LL | const SUBTLE: &mut i32 = unsafe { &mut FOO };
116116
| ^^^
117-
help: skipping check that does not even have a feature gate
117+
help: skipping check for `const_mut_refs` feature
118118
--> $DIR/mutable_references_err.rs:32:35
119119
|
120120
LL | const SUBTLE: &mut i32 = unsafe { &mut FOO };

tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ help: skipping check for `const_refs_to_static` feature
114114
|
115115
LL | const SUBTLE: &mut i32 = unsafe { &mut FOO };
116116
| ^^^
117-
help: skipping check that does not even have a feature gate
117+
help: skipping check for `const_mut_refs` feature
118118
--> $DIR/mutable_references_err.rs:32:35
119119
|
120120
LL | const SUBTLE: &mut i32 = unsafe { &mut FOO };

tests/ui/consts/mut-ptr-to-static.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@run-pass
2+
#![feature(const_mut_refs)]
3+
#![feature(sync_unsafe_cell)]
4+
5+
use std::cell::SyncUnsafeCell;
6+
use std::ptr;
7+
8+
#[repr(C)]
9+
struct SyncPtr {
10+
foo: *mut u32,
11+
}
12+
unsafe impl Sync for SyncPtr {}
13+
14+
static mut STATIC: u32 = 42;
15+
16+
static INTERIOR_MUTABLE_STATIC: SyncUnsafeCell<u32> = SyncUnsafeCell::new(42);
17+
18+
// A static that mutably points to STATIC.
19+
static PTR: SyncPtr = SyncPtr {
20+
foo: unsafe { ptr::addr_of_mut!(STATIC) },
21+
};
22+
static INTERIOR_MUTABLE_PTR: SyncPtr = SyncPtr {
23+
foo: ptr::addr_of!(INTERIOR_MUTABLE_STATIC) as *mut u32,
24+
};
25+
26+
fn main() {
27+
let ptr = PTR.foo;
28+
unsafe {
29+
assert_eq!(*ptr, 42);
30+
*ptr = 0;
31+
assert_eq!(*PTR.foo, 0);
32+
}
33+
34+
let ptr = INTERIOR_MUTABLE_PTR.foo;
35+
unsafe {
36+
assert_eq!(*ptr, 42);
37+
*ptr = 0;
38+
assert_eq!(*INTERIOR_MUTABLE_PTR.foo, 0);
39+
}
40+
}

tests/ui/error-codes/E0017.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1+
#![feature(const_mut_refs)]
2+
13
static X: i32 = 1;
24
const C: i32 = 2;
35
static mut M: i32 = 3;
46

57
const CR: &'static mut i32 = &mut C; //~ ERROR mutable references are not allowed
68
//~| WARN taking a mutable
79

8-
static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0658
9-
//~| ERROR cannot borrow
10-
//~| ERROR mutable references are not allowed
10+
static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR cannot borrow immutable static item `X` as mutable
1111

1212
static CONST_REF: &'static mut i32 = &mut C; //~ ERROR mutable references are not allowed
1313
//~| WARN taking a mutable
1414

15-
static STATIC_MUT_REF: &'static mut i32 = unsafe { &mut M }; //~ ERROR mutable references are not
15+
static STATIC_MUT_REF: &'static mut i32 = unsafe { &mut M };
1616
//~^ WARN mutable reference of mutable static is discouraged [static_mut_ref]
1717

1818
fn main() {}

tests/ui/error-codes/E0017.stderr

+7-29
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,28 @@ LL | static STATIC_MUT_REF: &'static mut i32 = unsafe { addr_of_mut!(M) };
1414
| ~~~~~~~~~~~~~~~
1515

1616
warning: taking a mutable reference to a `const` item
17-
--> $DIR/E0017.rs:5:30
17+
--> $DIR/E0017.rs:7:30
1818
|
1919
LL | const CR: &'static mut i32 = &mut C;
2020
| ^^^^^^
2121
|
2222
= note: each usage of a `const` item creates a new temporary
2323
= note: the mutable reference will refer to this temporary, not the original `const` item
2424
note: `const` item defined here
25-
--> $DIR/E0017.rs:2:1
25+
--> $DIR/E0017.rs:4:1
2626
|
2727
LL | const C: i32 = 2;
2828
| ^^^^^^^^^^^^
2929
= note: `#[warn(const_item_mutation)]` on by default
3030

3131
error[E0764]: mutable references are not allowed in the final value of constants
32-
--> $DIR/E0017.rs:5:30
32+
--> $DIR/E0017.rs:7:30
3333
|
3434
LL | const CR: &'static mut i32 = &mut C;
3535
| ^^^^^^
3636

37-
error[E0658]: mutation through a reference is not allowed in statics
38-
--> $DIR/E0017.rs:8:39
39-
|
40-
LL | static STATIC_REF: &'static mut i32 = &mut X;
41-
| ^^^^^^
42-
|
43-
= note: see issue #57349 <https://github.com/rust-lang/rust/issues/57349> for more information
44-
= help: add `#![feature(const_mut_refs)]` to the crate attributes to enable
45-
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
46-
47-
error[E0764]: mutable references are not allowed in the final value of statics
48-
--> $DIR/E0017.rs:8:39
49-
|
50-
LL | static STATIC_REF: &'static mut i32 = &mut X;
51-
| ^^^^^^
52-
5337
error[E0596]: cannot borrow immutable static item `X` as mutable
54-
--> $DIR/E0017.rs:8:39
38+
--> $DIR/E0017.rs:10:39
5539
|
5640
LL | static STATIC_REF: &'static mut i32 = &mut X;
5741
| ^^^^^^ cannot borrow as mutable
@@ -65,7 +49,7 @@ LL | static CONST_REF: &'static mut i32 = &mut C;
6549
= note: each usage of a `const` item creates a new temporary
6650
= note: the mutable reference will refer to this temporary, not the original `const` item
6751
note: `const` item defined here
68-
--> $DIR/E0017.rs:2:1
52+
--> $DIR/E0017.rs:4:1
6953
|
7054
LL | const C: i32 = 2;
7155
| ^^^^^^^^^^^^
@@ -76,13 +60,7 @@ error[E0764]: mutable references are not allowed in the final value of statics
7660
LL | static CONST_REF: &'static mut i32 = &mut C;
7761
| ^^^^^^
7862

79-
error[E0764]: mutable references are not allowed in the final value of statics
80-
--> $DIR/E0017.rs:15:52
81-
|
82-
LL | static STATIC_MUT_REF: &'static mut i32 = unsafe { &mut M };
83-
| ^^^^^^
84-
85-
error: aborting due to 6 previous errors; 3 warnings emitted
63+
error: aborting due to 3 previous errors; 3 warnings emitted
8664

87-
Some errors have detailed explanations: E0596, E0658, E0764.
65+
Some errors have detailed explanations: E0596, E0764.
8866
For more information about an error, try `rustc --explain E0596`.

tests/ui/error-codes/E0388.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ const C: i32 = 2;
33

44
const CR: &'static mut i32 = &mut C; //~ ERROR mutable references are not allowed
55
//~| WARN taking a mutable
6-
static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR cannot borrow
7-
//~| ERROR E0658
8-
//~| ERROR mutable references are not allowed
6+
static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0658
97

108
static CONST_REF: &'static mut i32 = &mut C; //~ ERROR mutable references are not allowed
119
//~| WARN taking a mutable

0 commit comments

Comments
 (0)