diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index fccb6b171b1c2..ead081ea5cdb5 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -861,6 +861,36 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { interp_ok(()) } + fn before_static_ref_eval( + tcx: TyCtxtAt<'tcx>, + machine: &Self, + ptr: Pointer, + static_def_id: DefId, + ) -> InterpResult<'tcx> { + // We are only interested in immediate references to statics here. + // Indirect references should not be checked because: + // - they may be references to some other legitimate static reference + // (e.g. via a raw pointer), and + // - if they originate from an illegal static reference, that illegal + // reference must already appear in the body and will be checked there. + if ptr.provenance.shared_ref() { + return interp_ok(()); + } + + // Check if this is the currently evaluated static. + if Some(static_def_id) == machine.static_root_ids.map(|(_, def_id)| def_id.into()) { + return Err(ConstEvalErrKind::RecursiveStatic).into(); + } + + if !tcx.is_foreign_item(static_def_id) { + // Fire the query to detect cycles. We cannot restrict this to only when + // evaluating statics, since static reference cycles can also be formed + // through consts, especially promoted ones. + tcx.eval_static_initializer(static_def_id)?; + } + interp_ok(()) + } + fn cached_union_data_range<'e>( ecx: &'e mut InterpCx<'tcx, Self>, ty: Ty<'tcx>, diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 753b2dd3b8ea1..517536443b2a1 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -447,6 +447,20 @@ pub trait Machine<'tcx>: Sized { interp_ok(()) } + /// Hook for performing extra checks on any reference to static. + /// This is not invoked for raw pointers such as `unsafe { &*&raw const STATIC }`. + /// + /// Used to prevent statics from constructing uninhabitated values by referencing themselves + /// as it is being initialized. + fn before_static_ref_eval( + _tcx: TyCtxtAt<'tcx>, + _machine: &Self, + _ptr: Pointer, + _static_def_id: DefId, + ) -> InterpResult<'tcx> { + interp_ok(()) + } + /// Hook for performing extra checks on a memory write access. /// This is not invoked for ZST accesses, as no write actually happens. /// `ptr` will always be a pointer with the provenance in `prov` pointing to the beginning of diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 88a1160947583..bda2b909314a6 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -3,11 +3,13 @@ //! The main entry point is the `step` method. use std::iter; +use std::ops::Deref; use either::Either; use rustc_abi::{FIRST_VARIANT, FieldIdx}; use rustc_data_structures::fx::FxHashSet; use rustc_index::IndexSlice; +use rustc_middle::mir::interpret::{GlobalAlloc, Provenance, Scalar}; use rustc_middle::ty::{self, Instance, Ty}; use rustc_middle::{bug, mir, span_bug}; use rustc_span::source_map::Spanned; @@ -217,9 +219,30 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } Ref(_, borrow_kind, place) => { + let is_reborrow_of_ref = if let Some(local) = place.local_or_deref_local() + && place.is_indirect() + { + self.layout_of_local(self.frame(), local, None)?.ty.is_ref() + } else { + false + }; + let src = self.eval_place(place)?; let place = self.force_allocation(&src)?; let val = ImmTy::from_immediate(place.to_ref(self), dest.layout); + + // Check whether this forms a cycle involving a static reference. + // Ensure the place is not a reborrow from a raw pointer, since we do not want to + // forbid static reference cycles that go through raw pointers. + if is_reborrow_of_ref + && let Immediate::Scalar(Scalar::Ptr(ptr, _)) = val.deref() + && let Some(alloc_id) = ptr.provenance.get_alloc_id() + && let Some(GlobalAlloc::Static(def_id)) = + self.tcx.try_get_global_alloc(alloc_id) + { + M::before_static_ref_eval(self.tcx, &self.machine, *ptr, def_id)?; + } + // A fresh reference was created, make sure it gets retagged. let val = M::retag_ptr_value( self, diff --git a/tests/ui/consts/const-eval/issue-47971.rs b/tests/ui/consts/const-eval/issue-47971.rs index 74eac963408ca..8bbb4ac9fcac2 100644 --- a/tests/ui/consts/const-eval/issue-47971.rs +++ b/tests/ui/consts/const-eval/issue-47971.rs @@ -1,9 +1,8 @@ -//@ check-pass - struct S(pub &'static u32, pub u32); const fn g(ss: &S) -> &u32 { &ss.1 } static T: S = S(g(&T), 0); +//~^ ERROR: encountered static that tried to access itself during initialization fn main () { } diff --git a/tests/ui/consts/const-eval/issue-47971.stderr b/tests/ui/consts/const-eval/issue-47971.stderr new file mode 100644 index 0000000000000..75a9f1f154240 --- /dev/null +++ b/tests/ui/consts/const-eval/issue-47971.stderr @@ -0,0 +1,9 @@ +error[E0080]: encountered static that tried to access itself during initialization + --> $DIR/issue-47971.rs:5:19 + | +LL | static T: S = S(g(&T), 0); + | ^^ evaluation of `T` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/cycle-static-promoted.rs b/tests/ui/consts/cycle-static-promoted.rs index d648d04861189..9a227b05356ed 100644 --- a/tests/ui/consts/cycle-static-promoted.rs +++ b/tests/ui/consts/cycle-static-promoted.rs @@ -1,5 +1,3 @@ -//@ check-pass - struct Value { values: &'static [&'static Value], } @@ -7,6 +5,7 @@ struct Value { // This `static` recursively points to itself through a promoted (the slice). static VALUE: Value = Value { values: &[&VALUE], + //~^ ERROR: cycle detected when evaluating initializer of static `VALUE` }; fn main() {} diff --git a/tests/ui/consts/cycle-static-promoted.stderr b/tests/ui/consts/cycle-static-promoted.stderr new file mode 100644 index 0000000000000..dd227f3182bf2 --- /dev/null +++ b/tests/ui/consts/cycle-static-promoted.stderr @@ -0,0 +1,23 @@ +error[E0391]: cycle detected when evaluating initializer of static `VALUE` + --> $DIR/cycle-static-promoted.rs:7:13 + | +LL | values: &[&VALUE], + | ^^^^^^^^^ + | +note: ...which requires simplifying constant for the type system `VALUE::promoted[0]`... + --> $DIR/cycle-static-promoted.rs:6:1 + | +LL | static VALUE: Value = Value { + | ^^^^^^^^^^^^^^^^^^^ +note: ...which requires const-evaluating + checking `VALUE::promoted[0]`... + --> $DIR/cycle-static-promoted.rs:6:1 + | +LL | static VALUE: Value = Value { + | ^^^^^^^^^^^^^^^^^^^ + = note: ...which again requires evaluating initializer of static `VALUE`, completing the cycle + = note: cycle used when running analysis passes on crate `cycle_static_promoted` + = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0391`. diff --git a/tests/ui/consts/static-cycle-error.rs b/tests/ui/consts/static-cycle-error.rs index b23872ed509f5..13e0d20938257 100644 --- a/tests/ui/consts/static-cycle-error.rs +++ b/tests/ui/consts/static-cycle-error.rs @@ -1,11 +1,10 @@ -//@ check-pass - struct Foo { foo: Option<&'static Foo> } static FOO: Foo = Foo { foo: Some(&FOO), + //~^ ERROR: encountered static that tried to access itself during initialization }; fn main() {} diff --git a/tests/ui/consts/static-cycle-error.stderr b/tests/ui/consts/static-cycle-error.stderr new file mode 100644 index 0000000000000..faa8e6d841313 --- /dev/null +++ b/tests/ui/consts/static-cycle-error.stderr @@ -0,0 +1,9 @@ +error[E0080]: encountered static that tried to access itself during initialization + --> $DIR/static-cycle-error.rs:6:15 + | +LL | foo: Some(&FOO), + | ^^^^ evaluation of `FOO` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/statics/recursive_interior_mut.rs b/tests/ui/statics/recursive_interior_mut.rs index 43e9d0c50913c..d900ac828d4e7 100644 --- a/tests/ui/statics/recursive_interior_mut.rs +++ b/tests/ui/statics/recursive_interior_mut.rs @@ -13,7 +13,7 @@ unsafe impl Sync for EmptyChunkFooter {} static EMPTY_CHUNK: EmptyChunkFooter = EmptyChunkFooter(ChunkFooter { prev: Cell::new(unsafe { - NonNull::new_unchecked(&EMPTY_CHUNK as *const EmptyChunkFooter as *mut ChunkFooter) + NonNull::new_unchecked(&raw const EMPTY_CHUNK as *mut ChunkFooter) }), }); diff --git a/tests/ui/statics/static-recursive.rs b/tests/ui/statics/static-recursive.rs index dab60dd8641bd..22b7c19894658 100644 --- a/tests/ui/statics/static-recursive.rs +++ b/tests/ui/statics/static-recursive.rs @@ -1,7 +1,4 @@ -//@ run-pass - -static mut S: *const u8 = unsafe { &S as *const *const u8 as *const u8 }; -//~^ WARN shared reference to mutable static [static_mut_refs] +static mut S: *const u8 = &raw const S as *const u8; struct StaticDoubleLinked { prev: &'static StaticDoubleLinked, @@ -11,13 +8,13 @@ struct StaticDoubleLinked { } static L1: StaticDoubleLinked = StaticDoubleLinked { prev: &L3, next: &L2, data: 1, head: true }; +//~^ ERROR: cycle detected when evaluating initializer of static `L1` static L2: StaticDoubleLinked = StaticDoubleLinked { prev: &L1, next: &L3, data: 2, head: false }; static L3: StaticDoubleLinked = StaticDoubleLinked { prev: &L2, next: &L1, data: 3, head: false }; pub fn main() { unsafe { assert_eq!(S, *(S as *const *const u8)); - //~^ WARN creating a shared reference to mutable static [static_mut_refs] } let mut test_vec = Vec::new(); diff --git a/tests/ui/statics/static-recursive.stderr b/tests/ui/statics/static-recursive.stderr index 16d5e183ccbd7..70bce8e5ab775 100644 --- a/tests/ui/statics/static-recursive.stderr +++ b/tests/ui/statics/static-recursive.stderr @@ -1,25 +1,23 @@ -warning: creating a shared reference to mutable static - --> $DIR/static-recursive.rs:3:36 +error[E0391]: cycle detected when evaluating initializer of static `L1` + --> $DIR/static-recursive.rs:10:1 | -LL | static mut S: *const u8 = unsafe { &S as *const *const u8 as *const u8 }; - | ^^ shared reference to mutable static +LL | static L1: StaticDoubleLinked = StaticDoubleLinked { prev: &L3, next: &L2, data: 1, head: true }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: for more information, see - = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives - = note: `#[warn(static_mut_refs)]` (part of `#[warn(rust_2024_compatibility)]`) on by default -help: use `&raw const` instead to create a raw pointer +note: ...which requires evaluating initializer of static `L3`... + --> $DIR/static-recursive.rs:13:1 | -LL | static mut S: *const u8 = unsafe { &raw const S as *const *const u8 as *const u8 }; - | +++++++++ - -warning: creating a shared reference to mutable static - --> $DIR/static-recursive.rs:19:20 - | -LL | assert_eq!(S, *(S as *const *const u8)); - | ^ shared reference to mutable static +LL | static L3: StaticDoubleLinked = StaticDoubleLinked { prev: &L2, next: &L1, data: 3, head: false }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: ...which requires evaluating initializer of static `L2`... + --> $DIR/static-recursive.rs:12:1 | - = note: for more information, see - = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives +LL | static L2: StaticDoubleLinked = StaticDoubleLinked { prev: &L1, next: &L3, data: 2, head: false }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: ...which again requires evaluating initializer of static `L1`, completing the cycle + = note: cycle used when running analysis passes on crate `static_recursive` + = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information -warning: 2 warnings emitted +error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0391`. diff --git a/tests/ui/statics/static-ref-cycle-issue-143047.rs b/tests/ui/statics/static-ref-cycle-issue-143047.rs new file mode 100644 index 0000000000000..5bb7be948f72b --- /dev/null +++ b/tests/ui/statics/static-ref-cycle-issue-143047.rs @@ -0,0 +1,48 @@ +// Regression test for https://github.com/rust-lang/rust/issues/143047 + +#[allow(static_mut_refs)] + +static FOO: u8 = { + let x = &FOO; + //~^ ERROR: encountered static that tried to access itself during initialization + 0 +}; + +static mut BAR: u8 = { + let x = unsafe { &mut BAR }; + //~^ ERROR: encountered static that tried to access itself during initialization + 0 +}; + +static mut BAZ: u8 = { + let x: &u8 = unsafe { &*&raw const BAR }; + let y = &raw mut BAR; + let z: &mut u8 = unsafe { &mut *y }; + let w = &z; + let v = &w; + 0 +}; + +static QUX: u8 = { + //~^ ERROR: cycle detected when evaluating initializer of static `QUX` + let x = &QUUX; + 0 +}; + +static QUUX: u8 = { + let x = &QUUUX; + 0 +}; + +static QUUUX: u8 = { + let x = &QUX; + 0 +}; + +static PROMOTED: u8 = { + let x = &&&PROMOTED; + //~^ ERROR: cycle detected when evaluating initializer of static `PROMOTED` + 0 +}; + +fn main() {} diff --git a/tests/ui/statics/static-ref-cycle-issue-143047.stderr b/tests/ui/statics/static-ref-cycle-issue-143047.stderr new file mode 100644 index 0000000000000..8d7fdd00b0e94 --- /dev/null +++ b/tests/ui/statics/static-ref-cycle-issue-143047.stderr @@ -0,0 +1,56 @@ +error[E0080]: encountered static that tried to access itself during initialization + --> $DIR/static-ref-cycle-issue-143047.rs:6:13 + | +LL | let x = &FOO; + | ^^^^ evaluation of `FOO` failed here + +error[E0080]: encountered static that tried to access itself during initialization + --> $DIR/static-ref-cycle-issue-143047.rs:12:22 + | +LL | let x = unsafe { &mut BAR }; + | ^^^^^^^^ evaluation of `BAR` failed here + +error[E0391]: cycle detected when evaluating initializer of static `QUX` + --> $DIR/static-ref-cycle-issue-143047.rs:26:1 + | +LL | static QUX: u8 = { + | ^^^^^^^^^^^^^^ + | +note: ...which requires evaluating initializer of static `QUUX`... + --> $DIR/static-ref-cycle-issue-143047.rs:32:1 + | +LL | static QUUX: u8 = { + | ^^^^^^^^^^^^^^^ +note: ...which requires evaluating initializer of static `QUUUX`... + --> $DIR/static-ref-cycle-issue-143047.rs:37:1 + | +LL | static QUUUX: u8 = { + | ^^^^^^^^^^^^^^^^ + = note: ...which again requires evaluating initializer of static `QUX`, completing the cycle + = note: cycle used when running analysis passes on crate `static_ref_cycle_issue_143047` + = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information + +error[E0391]: cycle detected when evaluating initializer of static `PROMOTED` + --> $DIR/static-ref-cycle-issue-143047.rs:43:13 + | +LL | let x = &&&PROMOTED; + | ^^^^^^^^^^^ + | +note: ...which requires simplifying constant for the type system `PROMOTED::promoted[0]`... + --> $DIR/static-ref-cycle-issue-143047.rs:42:1 + | +LL | static PROMOTED: u8 = { + | ^^^^^^^^^^^^^^^^^^^ +note: ...which requires const-evaluating + checking `PROMOTED::promoted[0]`... + --> $DIR/static-ref-cycle-issue-143047.rs:42:1 + | +LL | static PROMOTED: u8 = { + | ^^^^^^^^^^^^^^^^^^^ + = note: ...which again requires evaluating initializer of static `PROMOTED`, completing the cycle + = note: cycle used when running analysis passes on crate `static_ref_cycle_issue_143047` + = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0080, E0391. +For more information about an error, try `rustc --explain E0080`. diff --git a/tests/ui/statics/static-ref-cycle-lib-invariants-issue-143047.rs b/tests/ui/statics/static-ref-cycle-lib-invariants-issue-143047.rs new file mode 100644 index 0000000000000..6c6386f4492e0 --- /dev/null +++ b/tests/ui/statics/static-ref-cycle-lib-invariants-issue-143047.rs @@ -0,0 +1,28 @@ +// Regression test for https://github.com/rust-lang/rust/issues/143047 + +mod mrow { + pub struct InBoundsIndex(()); + + impl InBoundsIndex { + pub const fn new() -> Option> { + if N < 32 { Some(Self(())) } else { None } + } + } +} + +use mrow::InBoundsIndex; + +static IDX: InBoundsIndex<64> = { + // The following line should cause a compilation error, otherwise it results in an + // undefined behavior. + index([0; 32], &IDX); + //~^ ERROR: encountered static that tried to access itself during initialization + InBoundsIndex::<64>::new().unwrap() +}; + +const fn index(arr: [u8; 32], _: &InBoundsIndex) -> u8 { + // SAFETY: InBoundsIndex can only be created by its new, which ensures N is < 32 + unsafe { arr.as_ptr().add(N).read() } +} + +fn main() {} diff --git a/tests/ui/statics/static-ref-cycle-lib-invariants-issue-143047.stderr b/tests/ui/statics/static-ref-cycle-lib-invariants-issue-143047.stderr new file mode 100644 index 0000000000000..a53191ea2c10f --- /dev/null +++ b/tests/ui/statics/static-ref-cycle-lib-invariants-issue-143047.stderr @@ -0,0 +1,9 @@ +error[E0080]: encountered static that tried to access itself during initialization + --> $DIR/static-ref-cycle-lib-invariants-issue-143047.rs:18:20 + | +LL | index([0; 32], &IDX); + | ^^^^ evaluation of `IDX` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/statics/static-ref-cycle-uninhabited-init-issue-143047.rs b/tests/ui/statics/static-ref-cycle-uninhabited-init-issue-143047.rs new file mode 100644 index 0000000000000..1f358c962a903 --- /dev/null +++ b/tests/ui/statics/static-ref-cycle-uninhabited-init-issue-143047.rs @@ -0,0 +1,14 @@ +// Regression test for https://github.com/rust-lang/rust/issues/143047 + +enum Never {} + +static X: &Never = weird(&X); +//~^ ERROR: encountered static that tried to access itself during initialization + +const fn weird(a: &&Never) -> &'static Never { + // SAFETY: our argument type has an unsatisfiable + // library invariant; therefore, this code is unreachable. + unsafe { std::hint::unreachable_unchecked() }; +} + +fn main() {} diff --git a/tests/ui/statics/static-ref-cycle-uninhabited-init-issue-143047.stderr b/tests/ui/statics/static-ref-cycle-uninhabited-init-issue-143047.stderr new file mode 100644 index 0000000000000..8a6ab107621cc --- /dev/null +++ b/tests/ui/statics/static-ref-cycle-uninhabited-init-issue-143047.stderr @@ -0,0 +1,9 @@ +error[E0080]: encountered static that tried to access itself during initialization + --> $DIR/static-ref-cycle-uninhabited-init-issue-143047.rs:5:26 + | +LL | static X: &Never = weird(&X); + | ^^ evaluation of `X` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`.