Skip to content

Commit 73b38c6

Browse files
committed
Do not allocate a second "background" alloc id for the main allocation of a static.
Instead we re-use the static's alloc id within the interpreter for its initializer to refer to the `Allocation` that only exists within the interpreter.
1 parent e238627 commit 73b38c6

19 files changed

+261
-101
lines changed

compiler/rustc_const_eval/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ const_eval_realloc_or_alloc_with_offset =
313313
*[other] {""}
314314
} {$ptr} which does not point to the beginning of an object
315315
316+
const_eval_recursive_static = encountered static that tried to initialize itself with itself
317+
316318
const_eval_remainder_by_zero =
317319
calculating the remainder with a divisor of zero
318320
const_eval_remainder_overflow =

compiler/rustc_const_eval/src/const_eval/error.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, MachineStopTy
1919
pub enum ConstEvalErrKind {
2020
ConstAccessesMutGlobal,
2121
ModifiedGlobal,
22+
RecursiveStatic,
2223
AssertFailure(AssertKind<ConstInt>),
2324
Panic { msg: Symbol, line: u32, col: u32, file: Symbol },
2425
}
@@ -31,13 +32,14 @@ impl MachineStopType for ConstEvalErrKind {
3132
ConstAccessesMutGlobal => const_eval_const_accesses_mut_global,
3233
ModifiedGlobal => const_eval_modified_global,
3334
Panic { .. } => const_eval_panic,
35+
RecursiveStatic => const_eval_recursive_static,
3436
AssertFailure(x) => x.diagnostic_message(),
3537
}
3638
}
3739
fn add_args(self: Box<Self>, adder: &mut dyn FnMut(DiagnosticArgName, DiagnosticArgValue)) {
3840
use ConstEvalErrKind::*;
3941
match *self {
40-
ConstAccessesMutGlobal | ModifiedGlobal => {}
42+
RecursiveStatic | ConstAccessesMutGlobal | ModifiedGlobal => {}
4143
AssertFailure(kind) => kind.add_args(adder),
4244
Panic { msg, line, col, file } => {
4345
adder("msg".into(), msg.into_diagnostic_arg());

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

+33-29
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use either::{Left, Right};
22

33
use rustc_hir::def::DefKind;
44
use rustc_middle::mir::interpret::{AllocId, ErrorHandled, InterpErrorInfo};
5-
use rustc_middle::mir::pretty::write_allocation_bytes;
65
use rustc_middle::mir::{self, ConstAlloc, ConstValue};
76
use rustc_middle::traits::Reveal;
87
use rustc_middle::ty::layout::LayoutOf;
@@ -18,8 +17,9 @@ use crate::errors;
1817
use crate::errors::ConstEvalError;
1918
use crate::interpret::eval_nullary_intrinsic;
2019
use crate::interpret::{
21-
intern_const_alloc_recursive, CtfeValidationMode, GlobalId, Immediate, InternKind, InterpCx,
22-
InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking, StackPopCleanup,
20+
create_static_alloc, intern_const_alloc_recursive, take_static_root_alloc, CtfeValidationMode,
21+
GlobalId, Immediate, InternKind, InterpCx, InterpError, InterpResult, MPlaceTy, MemoryKind,
22+
OpTy, RefTracking, StackPopCleanup,
2323
};
2424

2525
// Returns a pointer to where the result lives
@@ -47,7 +47,21 @@ fn eval_body_using_ecx<'mir, 'tcx>(
4747
);
4848
let layout = ecx.layout_of(body.bound_return_ty().instantiate(tcx, cid.instance.args))?;
4949
assert!(layout.is_sized());
50-
let ret = ecx.allocate(layout, MemoryKind::Stack)?;
50+
51+
let intern_kind = if cid.promoted.is_some() {
52+
InternKind::Promoted
53+
} else {
54+
match tcx.static_mutability(cid.instance.def_id()) {
55+
Some(m) => InternKind::Static(m),
56+
None => InternKind::Constant,
57+
}
58+
};
59+
60+
let ret = if let InternKind::Static(_) = intern_kind {
61+
create_static_alloc(ecx, cid.instance.def_id(), layout)?
62+
} else {
63+
ecx.allocate(layout, MemoryKind::Stack)?
64+
};
5165

5266
trace!(
5367
"eval_body_using_ecx: pushing stack frame for global: {}{}",
@@ -67,14 +81,6 @@ fn eval_body_using_ecx<'mir, 'tcx>(
6781
while ecx.step()? {}
6882

6983
// Intern the result
70-
let intern_kind = if cid.promoted.is_some() {
71-
InternKind::Promoted
72-
} else {
73-
match tcx.static_mutability(cid.instance.def_id()) {
74-
Some(m) => InternKind::Static(m),
75-
None => InternKind::Constant,
76-
}
77-
};
7884
intern_const_alloc_recursive(ecx, intern_kind, &ret)?;
7985

8086
Ok(ret)
@@ -259,16 +265,17 @@ pub fn eval_static_initializer_provider<'tcx>(
259265

260266
let instance = ty::Instance::mono(tcx, def_id.to_def_id());
261267
let cid = rustc_middle::mir::interpret::GlobalId { instance, promoted: None };
262-
let ecx = InterpCx::new(
268+
let mut ecx = InterpCx::new(
263269
tcx,
264270
tcx.def_span(def_id),
265271
ty::ParamEnv::reveal_all(),
266272
// Statics (and promoteds inside statics) may access other statics, because unlike consts
267273
// they do not have to behave "as if" they were evaluated at runtime.
268274
CompileTimeInterpreter::new(CanAccessMutGlobal::Yes, CheckAlignment::Error),
269275
);
270-
let alloc_id = eval_in_interpreter(ecx, cid, true)?.alloc_id;
271-
let alloc = tcx.global_alloc(alloc_id).unwrap_memory();
276+
let alloc_id = eval_in_interpreter(&mut ecx, cid, true)?.alloc_id;
277+
let alloc = take_static_root_alloc(&mut ecx, alloc_id);
278+
let alloc = tcx.mk_const_alloc(alloc);
272279
Ok(alloc)
273280
}
274281

@@ -299,7 +306,7 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
299306
let def = cid.instance.def.def_id();
300307
let is_static = tcx.is_static(def);
301308

302-
let ecx = InterpCx::new(
309+
let mut ecx = InterpCx::new(
303310
tcx,
304311
tcx.def_span(def),
305312
key.param_env,
@@ -309,19 +316,19 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
309316
// so we have to reject reading mutable global memory.
310317
CompileTimeInterpreter::new(CanAccessMutGlobal::from(is_static), CheckAlignment::Error),
311318
);
312-
eval_in_interpreter(ecx, cid, is_static)
319+
eval_in_interpreter(&mut ecx, cid, is_static)
313320
}
314321

315322
pub fn eval_in_interpreter<'mir, 'tcx>(
316-
mut ecx: InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
323+
ecx: &mut InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
317324
cid: GlobalId<'tcx>,
318325
is_static: bool,
319326
) -> ::rustc_middle::mir::interpret::EvalToAllocationRawResult<'tcx> {
320327
// `is_static` just means "in static", it could still be a promoted!
321328
debug_assert_eq!(is_static, ecx.tcx.static_mutability(cid.instance.def_id()).is_some());
322329

323330
let res = ecx.load_mir(cid.instance.def, cid.promoted);
324-
match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body)) {
331+
match res.and_then(|body| eval_body_using_ecx(ecx, cid, body)) {
325332
Err(error) => {
326333
let (error, backtrace) = error.into_parts();
327334
backtrace.print_backtrace();
@@ -356,8 +363,11 @@ pub fn eval_in_interpreter<'mir, 'tcx>(
356363
}
357364
Ok(mplace) => {
358365
// Since evaluation had no errors, validate the resulting constant.
359-
// This is a separate `try` block to provide more targeted error reporting.
366+
367+
// Temporarily allow access to the static_root_alloc_id for the purpose of validation.
368+
let static_root_alloc_id = ecx.machine.static_root_alloc_id.take();
360369
let validation = const_validate_mplace(&ecx, &mplace, cid);
370+
ecx.machine.static_root_alloc_id = static_root_alloc_id;
361371

362372
let alloc_id = mplace.ptr().provenance.unwrap().alloc_id();
363373

@@ -409,15 +419,9 @@ pub fn const_report_error<'mir, 'tcx>(
409419

410420
let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {});
411421

412-
let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
413-
let mut bytes = String::new();
414-
if alloc.size() != abi::Size::ZERO {
415-
bytes = "\n".into();
416-
// FIXME(translation) there might be pieces that are translatable.
417-
write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, " ").unwrap();
418-
}
419-
let raw_bytes =
420-
errors::RawBytesNote { size: alloc.size().bytes(), align: alloc.align.bytes(), bytes };
422+
let bytes = ecx.print_alloc_bytes_for_diagnostics(alloc_id);
423+
let (size, align, _) = ecx.get_alloc_info(alloc_id);
424+
let raw_bytes = errors::RawBytesNote { size: size.bytes(), align: align.bytes(), bytes };
421425

422426
crate::const_eval::report(
423427
*ecx.tcx,

compiler/rustc_const_eval/src/const_eval/machine.rs

+15
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> {
5858

5959
/// Whether to check alignment during evaluation.
6060
pub(super) check_alignment: CheckAlignment,
61+
62+
/// Used to prevent reads from a static's base allocation, as that may allow for self-initialization.
63+
pub(crate) static_root_alloc_id: Option<AllocId>,
6164
}
6265

6366
#[derive(Copy, Clone)]
@@ -91,6 +94,7 @@ impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> {
9194
stack: Vec::new(),
9295
can_access_mut_global,
9396
check_alignment,
97+
static_root_alloc_id: None,
9498
}
9599
}
96100
}
@@ -746,6 +750,17 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
746750
// Everything else is fine.
747751
Ok(())
748752
}
753+
754+
fn before_alloc_read(
755+
ecx: &InterpCx<'mir, 'tcx, Self>,
756+
alloc_id: AllocId,
757+
) -> InterpResult<'tcx> {
758+
if Some(alloc_id) == ecx.machine.static_root_alloc_id {
759+
Err(ConstEvalErrKind::RecursiveStatic.into())
760+
} else {
761+
Ok(())
762+
}
763+
}
749764
}
750765

751766
// Please do not add any code below the above `Machine` trait impl. I (oli-obk) plan more cleanups

compiler/rustc_const_eval/src/interpret/eval_context.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,19 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
899899
.local_to_op(self.frame(), mir::RETURN_PLACE, None)
900900
.expect("return place should always be live");
901901
let dest = self.frame().return_place.clone();
902-
let err = self.copy_op_allow_transmute(&op, &dest);
902+
let err = if self.stack().len() == 1 {
903+
// The initializer of constants and statics will get validated separately
904+
// after the constant has been fully evaluated. While we could fall back to the default
905+
// code path, that will cause -Zenforce-validity to cycle on static initializers.
906+
// Reading from a static's memory is not allowed during its evaluation, and will always
907+
// trigger a cycle error. Validation must read from the memory of the current item.
908+
// For Miri this means we do not validate the root frame return value,
909+
// but Miri anyway calls `read_target_isize` on that so separate validation
910+
// is not needed.
911+
self.copy_op_no_dest_validation(&op, &dest)
912+
} else {
913+
self.copy_op_allow_transmute(&op, &dest)
914+
};
903915
trace!("return value: {:?}", self.dump_place(&dest));
904916
// We delay actually short-circuiting on this error until *after* the stack frame is
905917
// popped, since we want this error to be attributed to the caller, whose type defines

compiler/rustc_const_eval/src/interpret/intern.rs

+27-5
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ pub enum InternKind {
8585
///
8686
/// This *cannot raise an interpreter error*. Doing so is left to validation, which
8787
/// tracks where in the value we are and thus can show much better error messages.
88+
///
89+
/// For `InternKind::Static` the root allocation will not be interned, but must be handled by the caller.
8890
#[instrument(level = "debug", skip(ecx))]
8991
pub fn intern_const_alloc_recursive<
9092
'mir,
@@ -97,12 +99,12 @@ pub fn intern_const_alloc_recursive<
9799
) -> Result<(), ErrorGuaranteed> {
98100
// We are interning recursively, and for mutability we are distinguishing the "root" allocation
99101
// that we are starting in, and all other allocations that we are encountering recursively.
100-
let (base_mutability, inner_mutability) = match intern_kind {
102+
let (base_mutability, inner_mutability, is_static) = match intern_kind {
101103
InternKind::Constant | InternKind::Promoted => {
102104
// Completely immutable. Interning anything mutably here can only lead to unsoundness,
103105
// since all consts are conceptually independent values but share the same underlying
104106
// memory.
105-
(Mutability::Not, Mutability::Not)
107+
(Mutability::Not, Mutability::Not, false)
106108
}
107109
InternKind::Static(Mutability::Not) => {
108110
(
@@ -115,22 +117,31 @@ pub fn intern_const_alloc_recursive<
115117
// Inner allocations are never mutable. They can only arise via the "tail
116118
// expression" / "outer scope" rule, and we treat them consistently with `const`.
117119
Mutability::Not,
120+
true,
118121
)
119122
}
120123
InternKind::Static(Mutability::Mut) => {
121124
// Just make everything mutable. We accept code like
122125
// `static mut X = &mut [42]`, so even inner allocations need to be mutable.
123-
(Mutability::Mut, Mutability::Mut)
126+
(Mutability::Mut, Mutability::Mut, true)
124127
}
125128
};
126129

127130
// Intern the base allocation, and initialize todo list for recursive interning.
128131
let base_alloc_id = ret.ptr().provenance.unwrap().alloc_id();
132+
trace!(?base_alloc_id, ?base_mutability);
129133
// First we intern the base allocation, as it requires a different mutability.
130134
// This gives us the initial set of nested allocations, which will then all be processed
131135
// recursively in the loop below.
132-
let mut todo: Vec<_> =
133-
intern_shallow(ecx, base_alloc_id, base_mutability).unwrap().map(|prov| prov).collect();
136+
let mut todo: Vec<_> = if is_static {
137+
// Do not steal the root allocation, we need it later for `take_static_root_alloc`
138+
// But still change its mutability to match the requested one.
139+
let alloc = ecx.memory.alloc_map.get_mut(&base_alloc_id).unwrap();
140+
alloc.1.mutability = base_mutability;
141+
alloc.1.provenance().ptrs().iter().map(|&(_, prov)| prov).collect()
142+
} else {
143+
intern_shallow(ecx, base_alloc_id, base_mutability).unwrap().map(|prov| prov).collect()
144+
};
134145
// We need to distinguish "has just been interned" from "was already in `tcx`",
135146
// so we track this in a separate set.
136147
let mut just_interned: FxHashSet<_> = std::iter::once(base_alloc_id).collect();
@@ -148,7 +159,17 @@ pub fn intern_const_alloc_recursive<
148159
// before validation, and interning doesn't know the type of anything, this means we can't show
149160
// better errors. Maybe we should consider doing validation before interning in the future.
150161
while let Some(prov) = todo.pop() {
162+
trace!(?prov);
151163
let alloc_id = prov.alloc_id();
164+
165+
if base_alloc_id == alloc_id && is_static {
166+
// This is a pointer to the static itself. It's ok for a static to refer to itself,
167+
// even mutably. Whether that mutable pointer is legal at all is checked in validation.
168+
// See tests/ui/statics/recursive_interior_mut.rs for how such a situation can occur.
169+
// We also already collected all the nested allocations, so there's no need to do that again.
170+
continue;
171+
}
172+
152173
// Crucially, we check this *before* checking whether the `alloc_id`
153174
// has already been interned. The point of this check is to ensure that when
154175
// there are multiple pointers to the same allocation, they are *all* immutable.
@@ -176,6 +197,7 @@ pub fn intern_const_alloc_recursive<
176197
// `&None::<Cell<i32>>` lead to promotion that can produce mutable pointers. We rely
177198
// on the promotion analysis not screwing up to ensure that it is sound to intern
178199
// promoteds as immutable.
200+
trace!("found bad mutable pointer");
179201
found_bad_mutable_pointer = true;
180202
}
181203
if ecx.tcx.try_get_global_alloc(alloc_id).is_some() {

compiler/rustc_const_eval/src/interpret/machine.rs

+15
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
388388
/// Takes read-only access to the allocation so we can keep all the memory read
389389
/// operations take `&self`. Use a `RefCell` in `AllocExtra` if you
390390
/// need to mutate.
391+
///
392+
/// This is not invoked for ZST accesses, as no read actually happens.
391393
#[inline(always)]
392394
fn before_memory_read(
393395
_tcx: TyCtxtAt<'tcx>,
@@ -399,7 +401,20 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
399401
Ok(())
400402
}
401403

404+
/// Hook for performing extra checks on any memory read access,
405+
/// that involves an allocation, even ZST reads.
406+
///
407+
/// Used to prevent statics from self-initializing by reading from their own memory
408+
/// as it is being initialized.
409+
fn before_alloc_read(
410+
_ecx: &InterpCx<'mir, 'tcx, Self>,
411+
_alloc_id: AllocId,
412+
) -> InterpResult<'tcx> {
413+
Ok(())
414+
}
415+
402416
/// Hook for performing extra checks on a memory write access.
417+
/// This is not invoked for ZST accesses, as no write actually happens.
403418
#[inline(always)]
404419
fn before_memory_write(
405420
_tcx: TyCtxtAt<'tcx>,

compiler/rustc_const_eval/src/interpret/memory.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -624,19 +624,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
624624
size,
625625
CheckInAllocMsg::MemoryAccessTest,
626626
|alloc_id, offset, prov| {
627+
// We want to call the hook on *all* accesses that involve an AllocId,
628+
// including zero-sized accesses. That means we have to do it here
629+
// rather than below in the `Some` branch.
630+
M::before_alloc_read(self, alloc_id)?;
627631
let alloc = self.get_alloc_raw(alloc_id)?;
628632
Ok((alloc.size(), alloc.align, (alloc_id, offset, prov, alloc)))
629633
},
630634
)?;
635+
631636
if let Some((alloc_id, offset, prov, alloc)) = ptr_and_alloc {
632637
let range = alloc_range(offset, size);
633638
M::before_memory_read(self.tcx, &self.machine, &alloc.extra, (alloc_id, prov), range)?;
634639
Ok(Some(AllocRef { alloc, range, tcx: *self.tcx, alloc_id }))
635640
} else {
636-
// Even in this branch we have to be sure that we actually access the allocation, in
637-
// order to ensure that `static FOO: Type = FOO;` causes a cycle error instead of
638-
// magically pulling *any* ZST value from the ether. However, the `get_raw` above is
639-
// always called when `ptr` has an `AllocId`.
640641
Ok(None)
641642
}
642643
}
@@ -855,6 +856,21 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
855856
DumpAllocs { ecx: self, allocs }
856857
}
857858

859+
/// Print the allocation's bytes, without any nested allocations.
860+
pub fn print_alloc_bytes_for_diagnostics(&self, id: AllocId) -> String {
861+
// Using the "raw" access to avoid the `before_alloc_read` hook, we specifically
862+
// want to be able to read all memory for diagnostics, even if that is cyclic.
863+
let alloc = self.get_alloc_raw(id).unwrap();
864+
let mut bytes = String::new();
865+
if alloc.size() != Size::ZERO {
866+
bytes = "\n".into();
867+
// FIXME(translation) there might be pieces that are translatable.
868+
rustc_middle::mir::pretty::write_allocation_bytes(*self.tcx, alloc, &mut bytes, " ")
869+
.unwrap();
870+
}
871+
bytes
872+
}
873+
858874
/// Find leaked allocations. Allocations reachable from `static_roots` or a `Global` allocation
859875
/// are not considered leaked, as well as leaks whose kind's `may_leak()` returns true.
860876
pub fn find_leaked_allocations(

0 commit comments

Comments
 (0)