From b5989eeb68865e32775105acc8435ba9071232d8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 11 Feb 2024 14:27:08 +0100 Subject: [PATCH] const_mut_refs: allow mutable refs to statics --- .../src/transform/check_consts/check.rs | 36 ++++++++++++++-- .../src/transform/check_consts/qualifs.rs | 21 +++++++++ .../const-ref-to-static-linux-vtable.rs | 43 +++++++++++++++++++ .../ui/consts/issue-17718-const-bad-values.rs | 1 + .../issue-17718-const-bad-values.stderr | 12 +++++- tests/ui/consts/miri_unleashed/box.stderr | 2 +- .../mutable_references_err.64bit.stderr | 2 +- tests/ui/consts/mut-ptr-to-static.rs | 40 +++++++++++++++++ 8 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 tests/ui/consts/const-ref-to-static-linux-vtable.rs create mode 100644 tests/ui/consts/mut-ptr-to-static.rs diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs index 28dc69859fd77..9d4236689fc50 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs @@ -344,7 +344,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { visitor.visit_ty(ty); } - fn check_mut_borrow(&mut self, local: Local, kind: hir::BorrowKind) { + fn check_mut_borrow(&mut self, place: &Place<'_>, kind: hir::BorrowKind) { match self.const_kind() { // In a const fn all borrows are transient or point to the places given via // references in the arguments (so we already checked them with @@ -355,10 +355,19 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { // to mutable memory. hir::ConstContext::ConstFn => self.check_op(ops::TransientMutBorrow(kind)), _ => { + // For indirect places, we are not creating a new permanent borrow, it's + // just as transient as the already existing one. For reborrowing references + // this is handled at the top of `visit_rvalue`, but for raw pointers we handle it + // here. Pointers/references to `static mut` also end up here. // Locals with StorageDead do not live beyond the evaluation and can // thus safely be borrowed without being able to be leaked to the final // value of the constant. - if self.local_has_storage_dead(local) { + // Note: This is only sound if every local that has a `StorageDead` has a + // `StorageDead` in every control flow path leading to a `return` terminator. + // The good news is that interning will detect if any unexpected mutable + // pointer slips through. + if place.is_indirect_first_projection() || self.local_has_storage_dead(place.local) + { self.check_op(ops::TransientMutBorrow(kind)); } else { self.check_op(ops::MutBorrow(kind)); @@ -460,7 +469,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { if !is_allowed { self.check_mut_borrow( - place.local, + place, if matches!(rvalue, Rvalue::Ref(..)) { hir::BorrowKind::Ref } else { @@ -478,7 +487,13 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { place.as_ref(), ); - if borrowed_place_has_mut_interior { + // If the place is indirect, this is basically a reborrow. We have a reborrow + // special case above, but for raw pointers and pointers/references to `static`, + // `place_as_reborrow` does not recognize them as such, so we end up here. This + // should probably be considered a `TransientCellBorrow` (we consider the equivalent + // mutable case a `TransientMutBorrow`), but such reborrows got accidentally + // stabilized already and it is too much of a breaking change to take back. + if borrowed_place_has_mut_interior && !place.is_indirect_first_projection() { match self.const_kind() { // In a const fn all borrows are transient or point to the places given via // references in the arguments (so we already checked them with @@ -495,6 +510,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // final value. // Note: This is only sound if every local that has a `StorageDead` has a // `StorageDead` in every control flow path leading to a `return` terminator. + // The good news is that interning will detect if any unexpected mutable + // pointer slips through. if self.local_has_storage_dead(place.local) { self.check_op(ops::TransientCellBorrow); } else { @@ -619,6 +636,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { if base_ty.is_unsafe_ptr() { if place_ref.projection.is_empty() { let decl = &self.body.local_decls[place_ref.local]; + // FIXME: Why is this only checked for unsafe pointers? + // That will check `static mut` but not `static`. + // Crucially it then skips the other checks below! + // Probably, this can be removed entirely once `MutDeref` + // and `RawMutPtrDeref` are stable. if let LocalInfo::StaticRef { def_id, .. } = *decl.local_info() { let span = decl.source_info.span; self.check_static(def_id, span); @@ -946,6 +968,12 @@ fn place_as_reborrow<'tcx>( ) -> Option> { match place.as_ref().last_projection() { Some((place_base, ProjectionElem::Deref)) => { + // FIXME: why do statics and raw pointers get excluded here? This makes + // some code involving mutable pointers unstable, but it is unclear + // why that code is treated differently from mutable references. + // Once TransientMutBorrow and TransientCellBorrow are stable, + // this can probably be cleaned up without any behavioral changes. + // A borrow of a `static` also looks like `&(*_1)` in the MIR, but `_1` is a `const` // that points to the allocation for the static. Don't treat these as reborrows. if body.local_decls[place_base.local].is_ref_to_static() { diff --git a/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs index 67fef20807910..081aa097afd47 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs @@ -74,6 +74,10 @@ pub trait Qualif { adt: AdtDef<'tcx>, args: GenericArgsRef<'tcx>, ) -> bool; + + /// Returns `true` if this `Qualif` behaves sructurally for pointers and references: + /// the pointer/reference qualifies if and only if the pointee qualifies. + fn deref_structural<'tcx>(cx: &ConstCx<'_, 'tcx>) -> bool; } /// Constant containing interior mutability (`UnsafeCell`). @@ -103,6 +107,10 @@ impl Qualif for HasMutInterior { // It arises structurally for all other types. adt.is_unsafe_cell() } + + fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool { + false + } } /// Constant containing an ADT that implements `Drop`. @@ -131,6 +139,10 @@ impl Qualif for NeedsDrop { ) -> bool { adt.has_dtor(cx.tcx) } + + fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool { + false + } } /// Constant containing an ADT that implements non-const `Drop`. @@ -210,6 +222,10 @@ impl Qualif for NeedsNonConstDrop { ) -> bool { adt.has_non_const_dtor(cx.tcx) } + + fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool { + false + } } // FIXME: Use `mir::visit::Visitor` for the `in_*` functions if/when it supports early return. @@ -303,6 +319,11 @@ where return false; } + if matches!(elem, ProjectionElem::Deref) && !Q::deref_structural(cx) { + // We have to assume that this qualifies. + return true; + } + place = place_base; } diff --git a/tests/ui/consts/const-ref-to-static-linux-vtable.rs b/tests/ui/consts/const-ref-to-static-linux-vtable.rs new file mode 100644 index 0000000000000..1e2c3bcfefae1 --- /dev/null +++ b/tests/ui/consts/const-ref-to-static-linux-vtable.rs @@ -0,0 +1,43 @@ +// check-pass +//! This is the reduced version of the "Linux kernel vtable" use-case. +#![feature(const_mut_refs, const_refs_to_static)] +use std::ptr::addr_of_mut; + +#[repr(C)] +struct ThisModule(i32); + +trait Module { + const THIS_MODULE_PTR: *mut ThisModule; +} + +struct MyModule; + +// Generated by a macro. +extern "C" { + static mut THIS_MODULE: ThisModule; +} + +// Generated by a macro. +impl Module for MyModule { + const THIS_MODULE_PTR: *mut ThisModule = unsafe { addr_of_mut!(THIS_MODULE) }; +} + +struct Vtable { + module: *mut ThisModule, + foo_fn: fn(*mut ()) -> i32, +} + +trait Foo { + type Mod: Module; + + fn foo(&mut self) -> i32; +} + +fn generate_vtable() -> &'static Vtable { + &Vtable { + module: T::Mod::THIS_MODULE_PTR, + foo_fn: |ptr| unsafe { &mut *ptr.cast::() }.foo(), + } +} + +fn main() {} diff --git a/tests/ui/consts/issue-17718-const-bad-values.rs b/tests/ui/consts/issue-17718-const-bad-values.rs index af50fed972df8..8fb1e8fa11a28 100644 --- a/tests/ui/consts/issue-17718-const-bad-values.rs +++ b/tests/ui/consts/issue-17718-const-bad-values.rs @@ -5,6 +5,7 @@ static mut S: usize = 3; const C2: &'static mut usize = unsafe { &mut S }; //~^ ERROR: referencing statics in constants //~| ERROR: referencing statics in constants +//~| ERROR: mutable references are not allowed //~| WARN mutable reference of mutable static is discouraged [static_mut_ref] fn main() {} diff --git a/tests/ui/consts/issue-17718-const-bad-values.stderr b/tests/ui/consts/issue-17718-const-bad-values.stderr index cda94490155c0..df053cfd885e6 100644 --- a/tests/ui/consts/issue-17718-const-bad-values.stderr +++ b/tests/ui/consts/issue-17718-const-bad-values.stderr @@ -44,7 +44,17 @@ LL | const C2: &'static mut usize = unsafe { &mut S }; = help: to fix this, the value can be extracted to a `const` and then used. = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error: aborting due to 3 previous errors; 1 warning emitted +error[E0658]: mutable references are not allowed in constants + --> $DIR/issue-17718-const-bad-values.rs:5:41 + | +LL | const C2: &'static mut usize = unsafe { &mut S }; + | ^^^^^^ + | + = note: see issue #57349 for more information + = help: add `#![feature(const_mut_refs)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 4 previous errors; 1 warning emitted Some errors have detailed explanations: E0658, E0764. For more information about an error, try `rustc --explain E0658`. diff --git a/tests/ui/consts/miri_unleashed/box.stderr b/tests/ui/consts/miri_unleashed/box.stderr index 5229f1e50cd44..a0518c99cda56 100644 --- a/tests/ui/consts/miri_unleashed/box.stderr +++ b/tests/ui/consts/miri_unleashed/box.stderr @@ -16,7 +16,7 @@ help: skipping check for `const_mut_refs` feature | LL | &mut *(Box::new(0)) | ^^^^^^^^^^^^^^^^^^^ -help: skipping check that does not even have a feature gate +help: skipping check for `const_mut_refs` feature --> $DIR/box.rs:8:5 | LL | &mut *(Box::new(0)) diff --git a/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr b/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr index 1e5d4bd890b99..a7de7fe137e77 100644 --- a/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr +++ b/tests/ui/consts/miri_unleashed/mutable_references_err.64bit.stderr @@ -119,7 +119,7 @@ help: skipping check for `const_refs_to_static` feature | LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; | ^^^ -help: skipping check that does not even have a feature gate +help: skipping check for `const_mut_refs` feature --> $DIR/mutable_references_err.rs:32:35 | LL | const SUBTLE: &mut i32 = unsafe { &mut FOO }; diff --git a/tests/ui/consts/mut-ptr-to-static.rs b/tests/ui/consts/mut-ptr-to-static.rs new file mode 100644 index 0000000000000..0efe9fa0ded50 --- /dev/null +++ b/tests/ui/consts/mut-ptr-to-static.rs @@ -0,0 +1,40 @@ +// run-pass +#![feature(const_mut_refs)] +#![feature(sync_unsafe_cell)] + +use std::cell::SyncUnsafeCell; +use std::ptr; + +#[repr(C)] +struct SyncPtr { + foo: *mut u32, +} +unsafe impl Sync for SyncPtr {} + +static mut STATIC: u32 = 42; + +static INTERIOR_MUTABLE_STATIC: SyncUnsafeCell = SyncUnsafeCell::new(42); + +// A static that mutably points to STATIC. +static PTR: SyncPtr = SyncPtr { + foo: unsafe { ptr::addr_of_mut!(STATIC) }, +}; +static INTERIOR_MUTABLE_PTR: SyncPtr = SyncPtr { + foo: ptr::addr_of!(INTERIOR_MUTABLE_STATIC) as *mut u32, +}; + +fn main() { + let ptr = PTR.foo; + unsafe { + assert_eq!(*ptr, 42); + *ptr = 0; + assert_eq!(*PTR.foo, 0); + } + + let ptr = INTERIOR_MUTABLE_PTR.foo; + unsafe { + assert_eq!(*ptr, 42); + *ptr = 0; + assert_eq!(*INTERIOR_MUTABLE_PTR.foo, 0); + } +}