Skip to content

const-eval: error when initializing a static writes to that static #143084

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 15 additions & 7 deletions compiler/rustc_const_eval/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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(())
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_const_eval/src/interpret/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down
30 changes: 20 additions & 10 deletions compiler/rustc_const_eval/src/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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`.

Expand All @@ -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,
Expand All @@ -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))?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another drive-by fix, I don't think it makes any sense to use the dest_alloc_id here -- the operation was invoked on the src_alloc, after all.

// 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),
Expand Down
24 changes: 24 additions & 0 deletions tests/ui/consts/recursive-static-write.rs
Original file line number Diff line number Diff line change
@@ -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() {}
15 changes: 15 additions & 0 deletions tests/ui/consts/recursive-static-write.stderr
Original file line number Diff line number Diff line change
@@ -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`.
10 changes: 5 additions & 5 deletions tests/ui/consts/recursive-zst-static.default.stderr
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/consts/recursive-zst-static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions tests/ui/consts/recursive-zst-static.unleash.stderr
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 2 additions & 1 deletion tests/ui/consts/write-to-static-mut-in-static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
12 changes: 9 additions & 3 deletions tests/ui/consts/write-to-static-mut-in-static.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
4 changes: 2 additions & 2 deletions tests/ui/recursion/recursive-static-definition.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
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 {
x: u32,
}

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() {}
4 changes: 2 additions & 2 deletions tests/ui/recursion/recursive-static-definition.stderr
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
10 changes: 6 additions & 4 deletions tests/ui/statics/read_before_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
28 changes: 21 additions & 7 deletions tests/ui/statics/read_before_init.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading