about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2025-06-27 15:04:57 +0200
committerGitHub <noreply@github.com>2025-06-27 15:04:57 +0200
commit0bbeeffe57378b9460ec6df4532b8a569003f7f9 (patch)
tree81375f28340e900d1b616751535294f4c3e4062a
parent8e7b0b57ea97afd31b05709b3da525036269df57 (diff)
parented4f01ed2e033098f6c5e10a4a0cc740dc04f958 (diff)
downloadrust-0bbeeffe57378b9460ec6df4532b8a569003f7f9.tar.gz
rust-0bbeeffe57378b9460ec6df4532b8a569003f7f9.zip
Rollup merge of #143084 - RalfJung:const-eval-recursive-static-write, r=oli-obk
const-eval: error when initializing a static writes to that static

Fixes https://github.com/rust-lang/rust/issues/142404 by also calling the relevant hook for writes, not just reads. To avoid erroring during the actual write of the initial value, we neuter the hook when popping the final stack frame.

Calling the hook during writes requires changing its signature since we cannot pass in the entire interpreter any more.

While doing this I also realized a gap in https://github.com/rust-lang/rust/pull/142575 for zero-sized copies on the read side, so I fixed that and added a test.

r? `@oli-obk`
-rw-r--r--compiler/rustc_const_eval/messages.ftl2
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs22
-rw-r--r--compiler/rustc_const_eval/src/interpret/machine.rs6
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs30
-rw-r--r--tests/ui/consts/recursive-static-write.rs24
-rw-r--r--tests/ui/consts/recursive-static-write.stderr15
-rw-r--r--tests/ui/consts/recursive-zst-static.default.stderr10
-rw-r--r--tests/ui/consts/recursive-zst-static.rs2
-rw-r--r--tests/ui/consts/recursive-zst-static.unleash.stderr10
-rw-r--r--tests/ui/consts/write-to-static-mut-in-static.rs3
-rw-r--r--tests/ui/consts/write-to-static-mut-in-static.stderr12
-rw-r--r--tests/ui/recursion/recursive-static-definition.rs4
-rw-r--r--tests/ui/recursion/recursive-static-definition.stderr4
-rw-r--r--tests/ui/statics/read_before_init.rs10
-rw-r--r--tests/ui/statics/read_before_init.stderr28
15 files changed, 133 insertions, 49 deletions
diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl
index 97b154ad142..671ae5975a7 100644
--- a/compiler/rustc_const_eval/messages.ftl
+++ b/compiler/rustc_const_eval/messages.ftl
@@ -352,7 +352,7 @@ const_eval_realloc_or_alloc_with_offset =
         *[other] {""}
     } {$ptr} which does not point to the beginning of an object
 
-const_eval_recursive_static = encountered static that tried to initialize itself with itself
+const_eval_recursive_static = encountered static that tried to access itself during initialization
 
 const_eval_remainder_by_zero =
     calculating the remainder with a divisor of zero
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index a68dcf29988..2ec3f8432c7 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -62,7 +62,7 @@ pub struct CompileTimeMachine<'tcx> {
 
     /// If `Some`, we are evaluating the initializer of the static with the given `LocalDefId`,
     /// storing the result in the given `AllocId`.
-    /// Used to prevent reads from a static's base allocation, as that may allow for self-initialization loops.
+    /// Used to prevent accesses to a static's base allocation, as that may allow for self-initialization loops.
     pub(crate) static_root_ids: Option<(AllocId, LocalDefId)>,
 
     /// A cache of "data range" computations for unions (i.e., the offsets of non-padding bytes).
@@ -705,19 +705,27 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
         interp_ok(())
     }
 
-    fn before_alloc_read(ecx: &InterpCx<'tcx, Self>, alloc_id: AllocId) -> InterpResult<'tcx> {
+    fn before_alloc_access(
+        tcx: TyCtxtAt<'tcx>,
+        machine: &Self,
+        alloc_id: AllocId,
+    ) -> InterpResult<'tcx> {
+        if machine.stack.is_empty() {
+            // Get out of the way for the final copy.
+            return interp_ok(());
+        }
         // Check if this is the currently evaluated static.
-        if Some(alloc_id) == ecx.machine.static_root_ids.map(|(id, _)| id) {
+        if Some(alloc_id) == machine.static_root_ids.map(|(id, _)| id) {
             return Err(ConstEvalErrKind::RecursiveStatic).into();
         }
         // If this is another static, make sure we fire off the query to detect cycles.
         // But only do that when checks for static recursion are enabled.
-        if ecx.machine.static_root_ids.is_some() {
-            if let Some(GlobalAlloc::Static(def_id)) = ecx.tcx.try_get_global_alloc(alloc_id) {
-                if ecx.tcx.is_foreign_item(def_id) {
+        if machine.static_root_ids.is_some() {
+            if let Some(GlobalAlloc::Static(def_id)) = tcx.try_get_global_alloc(alloc_id) {
+                if tcx.is_foreign_item(def_id) {
                     throw_unsup!(ExternStatic(def_id));
                 }
-                ecx.ctfe_query(|tcx| tcx.eval_static_initializer(def_id))?;
+                tcx.eval_static_initializer(def_id)?;
             }
         }
         interp_ok(())
diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs
index b9e022c9604..d6d230fbd17 100644
--- a/compiler/rustc_const_eval/src/interpret/machine.rs
+++ b/compiler/rustc_const_eval/src/interpret/machine.rs
@@ -443,7 +443,11 @@ pub trait Machine<'tcx>: Sized {
     ///
     /// Used to prevent statics from self-initializing by reading from their own memory
     /// as it is being initialized.
-    fn before_alloc_read(_ecx: &InterpCx<'tcx, Self>, _alloc_id: AllocId) -> InterpResult<'tcx> {
+    fn before_alloc_access(
+        _tcx: TyCtxtAt<'tcx>,
+        _machine: &Self,
+        _alloc_id: AllocId,
+    ) -> InterpResult<'tcx> {
         interp_ok(())
     }
 
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index 57bf867e389..69fceb02ff9 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -720,7 +720,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // do this after `check_and_deref_ptr` to ensure some basic sanity has already been checked.
         if !self.memory.validation_in_progress.get() {
             if let Ok((alloc_id, ..)) = self.ptr_try_get_alloc_id(ptr, size_i64) {
-                M::before_alloc_read(self, alloc_id)?;
+                M::before_alloc_access(self.tcx, &self.machine, alloc_id)?;
             }
         }
 
@@ -821,6 +821,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         if let Some((alloc_id, offset, prov, alloc, machine)) = ptr_and_alloc {
             let range = alloc_range(offset, size);
             if !validation_in_progress {
+                // For writes, it's okay to only call those when there actually is a non-zero
+                // amount of bytes to be written: a zero-sized write doesn't manifest anything.
+                M::before_alloc_access(tcx, machine, alloc_id)?;
                 M::before_memory_write(
                     tcx,
                     machine,
@@ -1396,6 +1399,14 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let src_parts = self.get_ptr_access(src, size)?;
         let dest_parts = self.get_ptr_access(dest, size * num_copies)?; // `Size` multiplication
 
+        // Similar to `get_ptr_alloc`, we need to call `before_alloc_access` even for zero-sized
+        // reads. However, just like in `get_ptr_alloc_mut`, the write part is okay to skip for
+        // zero-sized writes.
+        if let Ok((alloc_id, ..)) = self.ptr_try_get_alloc_id(src, size.bytes().try_into().unwrap())
+        {
+            M::before_alloc_access(tcx, &self.machine, alloc_id)?;
+        }
+
         // FIXME: we look up both allocations twice here, once before for the `check_ptr_access`
         // and once below to get the underlying `&[mut] Allocation`.
 
@@ -1408,12 +1419,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let src_range = alloc_range(src_offset, size);
         assert!(!self.memory.validation_in_progress.get(), "we can't be copying during validation");
 
-        // Trigger read hooks.
-        // For the overlapping case, it is crucial that we trigger the read hooks
+        // Trigger read hook.
+        // For the overlapping case, it is crucial that we trigger the read hook
         // before the write hook -- the aliasing model cares about the order.
-        if let Ok((alloc_id, ..)) = self.ptr_try_get_alloc_id(src, size.bytes() as i64) {
-            M::before_alloc_read(self, alloc_id)?;
-        }
         M::before_memory_read(
             tcx,
             &self.machine,
@@ -1438,16 +1446,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let provenance = src_alloc
             .provenance()
             .prepare_copy(src_range, dest_offset, num_copies, self)
-            .map_err(|e| e.to_interp_error(dest_alloc_id))?;
+            .map_err(|e| e.to_interp_error(src_alloc_id))?;
         // Prepare a copy of the initialization mask.
         let init = src_alloc.init_mask().prepare_copy(src_range);
 
-        // Destination alloc preparations and access hooks.
-        let (dest_alloc, extra) = self.get_alloc_raw_mut(dest_alloc_id)?;
+        // Destination alloc preparations...
+        let (dest_alloc, machine) = self.get_alloc_raw_mut(dest_alloc_id)?;
         let dest_range = alloc_range(dest_offset, size * num_copies);
+        // ...and access hooks.
+        M::before_alloc_access(tcx, machine, dest_alloc_id)?;
         M::before_memory_write(
             tcx,
-            extra,
+            machine,
             &mut dest_alloc.extra,
             dest,
             (dest_alloc_id, dest_prov),
diff --git a/tests/ui/consts/recursive-static-write.rs b/tests/ui/consts/recursive-static-write.rs
new file mode 100644
index 00000000000..dc5813d8c78
--- /dev/null
+++ b/tests/ui/consts/recursive-static-write.rs
@@ -0,0 +1,24 @@
+//! Ensure that writing to `S` while initializing `S` errors.
+//! Regression test for <https://github.com/rust-lang/rust/issues/142404>.
+#![allow(dead_code)]
+
+struct Foo {
+    x: i32,
+    y: (),
+}
+
+static S: Foo = Foo {
+    x: 0,
+    y: unsafe {
+        (&raw const S.x).cast_mut().write(1); //~ERROR access itself during initialization
+    },
+};
+
+static mut S2: Foo = Foo {
+    x: 0,
+    y: unsafe {
+        S2.x = 1; //~ERROR access itself during initialization
+    },
+};
+
+fn main() {}
diff --git a/tests/ui/consts/recursive-static-write.stderr b/tests/ui/consts/recursive-static-write.stderr
new file mode 100644
index 00000000000..f5b5c49317c
--- /dev/null
+++ b/tests/ui/consts/recursive-static-write.stderr
@@ -0,0 +1,15 @@
+error[E0080]: encountered static that tried to access itself during initialization
+  --> $DIR/recursive-static-write.rs:13:9
+   |
+LL |         (&raw const S.x).cast_mut().write(1);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `S` failed here
+
+error[E0080]: encountered static that tried to access itself during initialization
+  --> $DIR/recursive-static-write.rs:20:9
+   |
+LL |         S2.x = 1;
+   |         ^^^^^^^^ evaluation of `S2` failed here
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/recursive-zst-static.default.stderr b/tests/ui/consts/recursive-zst-static.default.stderr
index fee33a892d0..c814576dfd5 100644
--- a/tests/ui/consts/recursive-zst-static.default.stderr
+++ b/tests/ui/consts/recursive-zst-static.default.stderr
@@ -1,20 +1,20 @@
-error[E0080]: encountered static that tried to initialize itself with itself
+error[E0080]: encountered static that tried to access itself during initialization
   --> $DIR/recursive-zst-static.rs:10:18
    |
 LL | static FOO: () = FOO;
    |                  ^^^ evaluation of `FOO` failed here
 
 error[E0391]: cycle detected when evaluating initializer of static `A`
-  --> $DIR/recursive-zst-static.rs:13:16
+  --> $DIR/recursive-zst-static.rs:13:1
    |
 LL | static A: () = B;
-   |                ^
+   | ^^^^^^^^^^^^
    |
 note: ...which requires evaluating initializer of static `B`...
-  --> $DIR/recursive-zst-static.rs:14:16
+  --> $DIR/recursive-zst-static.rs:14:1
    |
 LL | static B: () = A;
-   |                ^
+   | ^^^^^^^^^^^^
    = note: ...which again requires evaluating initializer of static `A`, completing the cycle
    = note: cycle used when running analysis passes on this crate
    = 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
diff --git a/tests/ui/consts/recursive-zst-static.rs b/tests/ui/consts/recursive-zst-static.rs
index 852caae9493..853af6d70eb 100644
--- a/tests/ui/consts/recursive-zst-static.rs
+++ b/tests/ui/consts/recursive-zst-static.rs
@@ -8,7 +8,7 @@
 // See https://github.com/rust-lang/rust/issues/71078 for more details.
 
 static FOO: () = FOO;
-//~^ ERROR encountered static that tried to initialize itself with itself
+//~^ ERROR encountered static that tried to access itself during initialization
 
 static A: () = B; //~ ERROR cycle detected when evaluating initializer of static `A`
 static B: () = A;
diff --git a/tests/ui/consts/recursive-zst-static.unleash.stderr b/tests/ui/consts/recursive-zst-static.unleash.stderr
index fee33a892d0..c814576dfd5 100644
--- a/tests/ui/consts/recursive-zst-static.unleash.stderr
+++ b/tests/ui/consts/recursive-zst-static.unleash.stderr
@@ -1,20 +1,20 @@
-error[E0080]: encountered static that tried to initialize itself with itself
+error[E0080]: encountered static that tried to access itself during initialization
   --> $DIR/recursive-zst-static.rs:10:18
    |
 LL | static FOO: () = FOO;
    |                  ^^^ evaluation of `FOO` failed here
 
 error[E0391]: cycle detected when evaluating initializer of static `A`
-  --> $DIR/recursive-zst-static.rs:13:16
+  --> $DIR/recursive-zst-static.rs:13:1
    |
 LL | static A: () = B;
-   |                ^
+   | ^^^^^^^^^^^^
    |
 note: ...which requires evaluating initializer of static `B`...
-  --> $DIR/recursive-zst-static.rs:14:16
+  --> $DIR/recursive-zst-static.rs:14:1
    |
 LL | static B: () = A;
-   |                ^
+   | ^^^^^^^^^^^^
    = note: ...which again requires evaluating initializer of static `A`, completing the cycle
    = note: cycle used when running analysis passes on this crate
    = 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
diff --git a/tests/ui/consts/write-to-static-mut-in-static.rs b/tests/ui/consts/write-to-static-mut-in-static.rs
index ce15d9e912b..016bfb06ccf 100644
--- a/tests/ui/consts/write-to-static-mut-in-static.rs
+++ b/tests/ui/consts/write-to-static-mut-in-static.rs
@@ -3,8 +3,9 @@ pub static mut B: () = unsafe { A = 1; };
 //~^ ERROR modifying a static's initial value
 
 pub static mut C: u32 = unsafe { C = 1; 0 };
+//~^ ERROR static that tried to access itself during initialization
 
 pub static D: u32 = D;
-//~^ ERROR static that tried to initialize itself with itself
+//~^ ERROR static that tried to access itself during initialization
 
 fn main() {}
diff --git a/tests/ui/consts/write-to-static-mut-in-static.stderr b/tests/ui/consts/write-to-static-mut-in-static.stderr
index bb5e217afb9..4180bb49339 100644
--- a/tests/ui/consts/write-to-static-mut-in-static.stderr
+++ b/tests/ui/consts/write-to-static-mut-in-static.stderr
@@ -4,12 +4,18 @@ error[E0080]: modifying a static's initial value from another static's initializ
 LL | pub static mut B: () = unsafe { A = 1; };
    |                                 ^^^^^ evaluation of `B` failed here
 
-error[E0080]: encountered static that tried to initialize itself with itself
-  --> $DIR/write-to-static-mut-in-static.rs:7:21
+error[E0080]: encountered static that tried to access itself during initialization
+  --> $DIR/write-to-static-mut-in-static.rs:5:34
+   |
+LL | pub static mut C: u32 = unsafe { C = 1; 0 };
+   |                                  ^^^^^ evaluation of `C` failed here
+
+error[E0080]: encountered static that tried to access itself during initialization
+  --> $DIR/write-to-static-mut-in-static.rs:8:21
    |
 LL | pub static D: u32 = D;
    |                     ^ evaluation of `D` failed here
 
-error: aborting due to 2 previous errors
+error: aborting due to 3 previous errors
 
 For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/recursion/recursive-static-definition.rs b/tests/ui/recursion/recursive-static-definition.rs
index 55db6a86bf1..4f0624eb162 100644
--- a/tests/ui/recursion/recursive-static-definition.rs
+++ b/tests/ui/recursion/recursive-static-definition.rs
@@ -1,5 +1,5 @@
 pub static FOO: u32 = FOO;
-//~^ ERROR encountered static that tried to initialize itself with itself
+//~^ ERROR encountered static that tried to access itself during initialization
 
 #[derive(Copy, Clone)]
 pub union Foo {
@@ -7,6 +7,6 @@ pub union Foo {
 }
 
 pub static BAR: Foo = BAR;
-//~^ ERROR encountered static that tried to initialize itself with itself
+//~^ ERROR encountered static that tried to access itself during initialization
 
 fn main() {}
diff --git a/tests/ui/recursion/recursive-static-definition.stderr b/tests/ui/recursion/recursive-static-definition.stderr
index ce93c41bc67..1e4005832cb 100644
--- a/tests/ui/recursion/recursive-static-definition.stderr
+++ b/tests/ui/recursion/recursive-static-definition.stderr
@@ -1,10 +1,10 @@
-error[E0080]: encountered static that tried to initialize itself with itself
+error[E0080]: encountered static that tried to access itself during initialization
   --> $DIR/recursive-static-definition.rs:1:23
    |
 LL | pub static FOO: u32 = FOO;
    |                       ^^^ evaluation of `FOO` failed here
 
-error[E0080]: encountered static that tried to initialize itself with itself
+error[E0080]: encountered static that tried to access itself during initialization
   --> $DIR/recursive-static-definition.rs:9:23
    |
 LL | pub static BAR: Foo = BAR;
diff --git a/tests/ui/statics/read_before_init.rs b/tests/ui/statics/read_before_init.rs
index d779ef6dffa..32cc2554e1a 100644
--- a/tests/ui/statics/read_before_init.rs
+++ b/tests/ui/statics/read_before_init.rs
@@ -8,13 +8,15 @@
 
 use std::mem::MaybeUninit;
 
-pub static X: (i32, MaybeUninit<i32>) = (1, foo(&X.0));
-//~^ ERROR: encountered static that tried to initialize itself with itself
+pub static X: (i32, MaybeUninit<i32>) = (1, foo(&X.0, 1));
+//~^ ERROR: encountered static that tried to access itself during initialization
+pub static Y: (i32, MaybeUninit<i32>) = (1, foo(&Y.0, 0));
+//~^ ERROR: encountered static that tried to access itself during initialization
 
-const fn foo(x: &i32) -> MaybeUninit<i32> {
+const fn foo(x: &i32, num: usize) -> MaybeUninit<i32> {
     let mut temp = MaybeUninit::<i32>::uninit();
     unsafe {
-        std::ptr::copy(x, temp.as_mut_ptr(), 1);
+        std::ptr::copy(x, temp.as_mut_ptr(), num);
     }
     temp
 }
diff --git a/tests/ui/statics/read_before_init.stderr b/tests/ui/statics/read_before_init.stderr
index aeebcf7d9ce..239568c1205 100644
--- a/tests/ui/statics/read_before_init.stderr
+++ b/tests/ui/statics/read_before_init.stderr
@@ -1,17 +1,31 @@
-error[E0080]: encountered static that tried to initialize itself with itself
+error[E0080]: encountered static that tried to access itself during initialization
   --> $DIR/read_before_init.rs:11:45
    |
-LL | pub static X: (i32, MaybeUninit<i32>) = (1, foo(&X.0));
-   |                                             ^^^^^^^^^ evaluation of `X` failed inside this call
+LL | pub static X: (i32, MaybeUninit<i32>) = (1, foo(&X.0, 1));
+   |                                             ^^^^^^^^^^^^ evaluation of `X` failed inside this call
    |
 note: inside `foo`
-  --> $DIR/read_before_init.rs:17:9
+  --> $DIR/read_before_init.rs:19:9
    |
-LL |         std::ptr::copy(x, temp.as_mut_ptr(), 1);
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |         std::ptr::copy(x, temp.as_mut_ptr(), num);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 note: inside `std::ptr::copy::<i32>`
   --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
 
-error: aborting due to 1 previous error
+error[E0080]: encountered static that tried to access itself during initialization
+  --> $DIR/read_before_init.rs:13:45
+   |
+LL | pub static Y: (i32, MaybeUninit<i32>) = (1, foo(&Y.0, 0));
+   |                                             ^^^^^^^^^^^^ evaluation of `Y` failed inside this call
+   |
+note: inside `foo`
+  --> $DIR/read_before_init.rs:19:9
+   |
+LL |         std::ptr::copy(x, temp.as_mut_ptr(), num);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+note: inside `std::ptr::copy::<i32>`
+  --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
+
+error: aborting due to 2 previous errors
 
 For more information about this error, try `rustc --explain E0080`.