Skip to content

Commit 9d7a214

Browse files
committed
Move validation before interning
1 parent a3fa705 commit 9d7a214

18 files changed

+215
-113
lines changed

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ use super::{CanAccessMutGlobal, CompileTimeEvalContext, CompileTimeInterpreter};
1616
use crate::const_eval::CheckAlignment;
1717
use crate::errors;
1818
use crate::errors::ConstEvalError;
19-
use crate::interpret::eval_nullary_intrinsic;
2019
use crate::interpret::{
2120
create_static_alloc, intern_const_alloc_recursive, CtfeValidationMode, GlobalId, Immediate,
2221
InternKind, InterpCx, InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking,
2322
StackPopCleanup,
2423
};
24+
use crate::interpret::{eval_nullary_intrinsic, patch_mutability_of_allocs};
2525

2626
// Returns a pointer to where the result lives
2727
#[instrument(level = "trace", skip(ecx, body))]
@@ -81,11 +81,13 @@ fn eval_body_using_ecx<'mir, 'tcx, R: InterpretationResult<'tcx>>(
8181
// The main interpreter loop.
8282
while ecx.step()? {}
8383

84-
// Intern the result
85-
intern_const_alloc_recursive(ecx, intern_kind, &ret)?;
84+
patch_mutability_of_allocs(ecx, intern_kind, &ret)?;
8685

8786
// Since evaluation had no errors, validate the resulting constant.
88-
const_validate_mplace(&ecx, &ret, cid)?;
87+
const_validate_mplace(ecx, &ret, cid)?;
88+
89+
// Intern the result
90+
intern_const_alloc_recursive(ecx, intern_kind, &ret)?;
8991

9092
Ok(R::make_result(ret, ecx))
9193
}

compiler/rustc_const_eval/src/const_eval/valtrees.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ use super::machine::CompileTimeEvalContext;
1010
use super::{ValTreeCreationError, ValTreeCreationResult, VALTREE_MAX_NODES};
1111
use crate::const_eval::CanAccessMutGlobal;
1212
use crate::errors::MaxNumNodesInConstErr;
13-
use crate::interpret::MPlaceTy;
1413
use crate::interpret::{
1514
intern_const_alloc_recursive, ImmTy, Immediate, InternKind, MemPlaceMeta, MemoryKind, PlaceTy,
1615
Projectable, Scalar,
1716
};
17+
use crate::interpret::{patch_mutability_of_allocs, MPlaceTy};
1818

1919
#[instrument(skip(ecx), level = "debug")]
2020
fn branches<'tcx>(
@@ -323,6 +323,7 @@ pub fn valtree_to_const_value<'tcx>(
323323

324324
valtree_into_mplace(&mut ecx, &place, valtree);
325325
dump_place(&ecx, &place);
326+
patch_mutability_of_allocs(&mut ecx, InternKind::Constant, &place).unwrap();
326327
intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &place).unwrap();
327328

328329
op_to_const(&ecx, &place.into(), /* for diagnostics */ false)
@@ -359,6 +360,7 @@ fn valtree_to_ref<'tcx>(
359360

360361
valtree_into_mplace(ecx, &pointee_place, valtree);
361362
dump_place(ecx, &pointee_place);
363+
patch_mutability_of_allocs(ecx, InternKind::Constant, &pointee_place).unwrap();
362364
intern_const_alloc_recursive(ecx, InternKind::Constant, &pointee_place).unwrap();
363365

364366
pointee_place.to_ref(&ecx.tcx)

compiler/rustc_const_eval/src/interpret/intern.rs

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,13 @@ impl HasStaticRootDefId for const_eval::CompileTimeInterpreter<'_, '_> {
6161
fn intern_shallow<'rt, 'mir, 'tcx, T, M: CompileTimeMachine<'mir, 'tcx, T>>(
6262
ecx: &'rt mut InterpCx<'mir, 'tcx, M>,
6363
alloc_id: AllocId,
64-
mutability: Mutability,
6564
) -> Result<impl Iterator<Item = CtfeProvenance> + 'tcx, ()> {
6665
trace!("intern_shallow {:?}", alloc_id);
6766
// remove allocation
6867
// FIXME(#120456) - is `swap_remove` correct?
69-
let Some((_kind, mut alloc)) = ecx.memory.alloc_map.swap_remove(&alloc_id) else {
68+
let Some((_kind, alloc)) = ecx.memory.alloc_map.swap_remove(&alloc_id) else {
7069
return Err(());
7170
};
72-
// Set allocation mutability as appropriate. This is used by LLVM to put things into
73-
// read-only memory, and also by Miri when evaluating other globals that
74-
// access this one.
75-
match mutability {
76-
Mutability::Not => {
77-
alloc.mutability = Mutability::Not;
78-
}
79-
Mutability::Mut => {
80-
// This must be already mutable, we won't "un-freeze" allocations ever.
81-
assert_eq!(alloc.mutability, Mutability::Mut);
82-
}
83-
}
8471
// link the alloc id to the actual allocation
8572
let alloc = ecx.tcx.mk_const_alloc(alloc);
8673
if let Some(static_id) = ecx.machine.static_def_id() {
@@ -122,14 +109,9 @@ pub enum InternKind {
122109
Promoted,
123110
}
124111

125-
/// Intern `ret` and everything it references.
126-
///
127-
/// This *cannot raise an interpreter error*. Doing so is left to validation, which
128-
/// tracks where in the value we are and thus can show much better error messages.
129-
///
130-
/// For `InternKind::Static` the root allocation will not be interned, but must be handled by the caller.
131-
#[instrument(level = "debug", skip(ecx))]
132-
pub fn intern_const_alloc_recursive<
112+
/// Now that evaluation is finished, and we are not going to modify allocations anymore,
113+
/// recursively mark all allocations as immutable if the item kind calls for it (const/promoted/immut static).
114+
pub fn patch_mutability_of_allocs<
133115
'mir,
134116
'tcx: 'mir,
135117
M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>,
@@ -140,12 +122,12 @@ pub fn intern_const_alloc_recursive<
140122
) -> Result<(), ErrorGuaranteed> {
141123
// We are interning recursively, and for mutability we are distinguishing the "root" allocation
142124
// that we are starting in, and all other allocations that we are encountering recursively.
143-
let (base_mutability, inner_mutability, is_static) = match intern_kind {
125+
let (base_mutability, inner_mutability) = match intern_kind {
144126
InternKind::Constant | InternKind::Promoted => {
145127
// Completely immutable. Interning anything mutably here can only lead to unsoundness,
146128
// since all consts are conceptually independent values but share the same underlying
147129
// memory.
148-
(Mutability::Not, Mutability::Not, false)
130+
(Mutability::Not, Mutability::Not)
149131
}
150132
InternKind::Static(Mutability::Not) => {
151133
(
@@ -158,30 +140,79 @@ pub fn intern_const_alloc_recursive<
158140
// Inner allocations are never mutable. They can only arise via the "tail
159141
// expression" / "outer scope" rule, and we treat them consistently with `const`.
160142
Mutability::Not,
161-
true,
162143
)
163144
}
164145
InternKind::Static(Mutability::Mut) => {
165146
// Just make everything mutable. We accept code like
166147
// `static mut X = &mut [42]`, so even inner allocations need to be mutable.
167-
(Mutability::Mut, Mutability::Mut, true)
148+
(Mutability::Mut, Mutability::Mut)
168149
}
169150
};
151+
let base_alloc_id = ret.ptr().provenance.unwrap().alloc_id();
152+
let mut todo: Vec<_> = {
153+
let base_alloc = &mut ecx.memory.alloc_map.get_mut(&base_alloc_id).unwrap().1;
154+
base_alloc.mutability = base_mutability;
155+
base_alloc.provenance().ptrs().iter().copied().collect()
156+
};
157+
let mut seen = FxHashSet::default();
158+
seen.insert(base_alloc_id);
159+
while let Some((_, prov)) = todo.pop() {
160+
if !seen.insert(prov.alloc_id()) {
161+
// Already processed
162+
continue;
163+
}
164+
let Some((_, alloc)) = &mut ecx.memory.alloc_map.get_mut(&prov.alloc_id()) else {
165+
continue;
166+
};
167+
// We always intern with `inner_mutability`, and furthermore we ensured above that if
168+
// that is "immutable", then there are *no* mutable pointers anywhere in the newly
169+
// interned memory -- justifying that we can indeed intern immutably. However this also
170+
// means we can *not* easily intern immutably here if `prov.immutable()` is true and
171+
// `inner_mutability` is `Mut`: there might be other pointers to that allocation, and
172+
// we'd have to somehow check that they are *all* immutable before deciding that this
173+
// allocation can be made immutable. In the future we could consider analyzing all
174+
// pointers before deciding which allocations can be made immutable; but for now we are
175+
// okay with losing some potential for immutability here. This can anyway only affect
176+
// `static mut`.
177+
alloc.mutability = inner_mutability;
178+
todo.extend(alloc.provenance().ptrs().iter().copied());
179+
}
180+
Ok(())
181+
}
182+
183+
/// Intern `ret` and everything it references.
184+
///
185+
/// This *cannot raise an interpreter error*. Doing so is left to validation, which
186+
/// tracks where in the value we are and thus can show much better error messages.
187+
///
188+
/// For `InternKind::Static` the root allocation will not be interned, but must be handled by the caller.
189+
#[instrument(level = "debug", skip(ecx))]
190+
pub fn intern_const_alloc_recursive<
191+
'mir,
192+
'tcx: 'mir,
193+
M: CompileTimeMachine<'mir, 'tcx, const_eval::MemoryKind>,
194+
>(
195+
ecx: &mut InterpCx<'mir, 'tcx, M>,
196+
intern_kind: InternKind,
197+
ret: &MPlaceTy<'tcx>,
198+
) -> Result<(), ErrorGuaranteed> {
199+
let (inner_mutability, is_static) = match intern_kind {
200+
InternKind::Constant | InternKind::Promoted => (Mutability::Not, false),
201+
InternKind::Static(mutability) => (mutability, true),
202+
};
170203

171204
// Intern the base allocation, and initialize todo list for recursive interning.
172205
let base_alloc_id = ret.ptr().provenance.unwrap().alloc_id();
173-
trace!(?base_alloc_id, ?base_mutability);
206+
trace!(?base_alloc_id);
174207
// First we intern the base allocation, as it requires a different mutability.
175208
// This gives us the initial set of nested allocations, which will then all be processed
176209
// recursively in the loop below.
177210
let mut todo: Vec<_> = if is_static {
178211
// Do not steal the root allocation, we need it later to create the return value of `eval_static_initializer`.
179-
// But still change its mutability to match the requested one.
180-
let alloc = ecx.memory.alloc_map.get_mut(&base_alloc_id).unwrap();
181-
alloc.1.mutability = base_mutability;
212+
let alloc = ecx.memory.alloc_map.get(&base_alloc_id).unwrap();
182213
alloc.1.provenance().ptrs().iter().map(|&(_, prov)| prov).collect()
183214
} else {
184-
intern_shallow(ecx, base_alloc_id, base_mutability).unwrap().collect()
215+
intern_shallow(ecx, base_alloc_id).unwrap().collect()
185216
};
186217
// We need to distinguish "has just been interned" from "was already in `tcx`",
187218
// so we track this in a separate set.
@@ -247,17 +278,7 @@ pub fn intern_const_alloc_recursive<
247278
continue;
248279
}
249280
just_interned.insert(alloc_id);
250-
// We always intern with `inner_mutability`, and furthermore we ensured above that if
251-
// that is "immutable", then there are *no* mutable pointers anywhere in the newly
252-
// interned memory -- justifying that we can indeed intern immutably. However this also
253-
// means we can *not* easily intern immutably here if `prov.immutable()` is true and
254-
// `inner_mutability` is `Mut`: there might be other pointers to that allocation, and
255-
// we'd have to somehow check that they are *all* immutable before deciding that this
256-
// allocation can be made immutable. In the future we could consider analyzing all
257-
// pointers before deciding which allocations can be made immutable; but for now we are
258-
// okay with losing some potential for immutability here. This can anyway only affect
259-
// `static mut`.
260-
todo.extend(intern_shallow(ecx, alloc_id, inner_mutability).map_err(|()| {
281+
todo.extend(intern_shallow(ecx, alloc_id).map_err(|()| {
261282
ecx.tcx.dcx().emit_err(DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind })
262283
})?);
263284
}
@@ -287,7 +308,8 @@ pub fn intern_const_alloc_for_constprop<
287308
return Ok(());
288309
}
289310
// Move allocation to `tcx`.
290-
for _ in intern_shallow(ecx, alloc_id, Mutability::Not).map_err(|()| err_ub!(DeadLocal))? {
311+
ecx.memory.alloc_map.get_mut(&alloc_id).unwrap().1.mutability = Mutability::Not;
312+
for _ in intern_shallow(ecx, alloc_id).map_err(|()| err_ub!(DeadLocal))? {
291313
// We are not doing recursive interning, so we don't currently support provenance.
292314
// (If this assertion ever triggers, we should just implement a
293315
// proper recursive interning loop -- or just call `intern_const_alloc_recursive`.
@@ -314,7 +336,8 @@ impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>>
314336
let dest = self.allocate(layout, MemoryKind::Stack)?;
315337
f(self, &dest.clone().into())?;
316338
let alloc_id = dest.ptr().provenance.unwrap().alloc_id(); // this was just allocated, it must have provenance
317-
for prov in intern_shallow(self, alloc_id, Mutability::Not).unwrap() {
339+
self.memory.alloc_map.get_mut(&alloc_id).unwrap().1.mutability = Mutability::Not;
340+
for prov in intern_shallow(self, alloc_id).unwrap() {
318341
// We are not doing recursive interning, so we don't currently support provenance.
319342
// (If this assertion ever triggers, we should just implement a
320343
// proper recursive interning loop -- or just call `intern_const_alloc_recursive`.

compiler/rustc_const_eval/src/interpret/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ pub use rustc_middle::mir::interpret::*; // have all the `interpret` symbols in
2222

2323
pub use self::eval_context::{format_interp_error, Frame, FrameInfo, InterpCx, StackPopCleanup};
2424
pub use self::intern::{
25-
intern_const_alloc_for_constprop, intern_const_alloc_recursive, HasStaticRootDefId, InternKind,
25+
intern_const_alloc_for_constprop, intern_const_alloc_recursive, patch_mutability_of_allocs,
26+
HasStaticRootDefId, InternKind,
2627
};
2728
pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump};
2829
pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};

compiler/rustc_const_eval/src/interpret/validity.rs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ use rustc_target::abi::{
2626

2727
use std::hash::Hash;
2828

29+
use crate::interpret::AllocKind;
30+
2931
use super::{
30-
format_interp_error, machine::AllocMap, AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy,
31-
Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Pointer, Projectable,
32-
Scalar, ValueVisitor,
32+
format_interp_error, AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx,
33+
InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Pointer, Projectable, Scalar,
34+
ValueVisitor,
3335
};
3436

3537
// for the validation errors
@@ -450,10 +452,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
450452
if let Ok((alloc_id, _offset, _prov)) = self.ecx.ptr_try_get_alloc_id(place.ptr()) {
451453
let mut skip_recursive_check = false;
452454
// Let's see what kind of memory this points to.
453-
// `unwrap` since dangling pointers have already been handled.
454-
let alloc_kind = self.ecx.tcx.try_get_global_alloc(alloc_id).unwrap();
455-
let alloc_actual_mutbl = match alloc_kind {
456-
GlobalAlloc::Static(did) => {
455+
let alloc_actual_mutbl = match self.ecx.tcx.try_get_global_alloc(alloc_id) {
456+
Some(GlobalAlloc::Static(did)) => {
457457
// Special handling for pointers to statics (irrespective of their type).
458458
assert!(!self.ecx.tcx.is_thread_local_static(did));
459459
assert!(self.ecx.tcx.is_static(did));
@@ -504,11 +504,19 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
504504
(Mutability::Not, false) => Mutability::Not,
505505
}
506506
}
507-
GlobalAlloc::Memory(alloc) => alloc.inner().mutability,
508-
GlobalAlloc::Function(..) | GlobalAlloc::VTable(..) => {
507+
Some(GlobalAlloc::Memory(alloc)) => alloc.inner().mutability,
508+
Some(GlobalAlloc::Function(..) | GlobalAlloc::VTable(..)) => {
509509
// These are immutable, we better don't allow mutable pointers here.
510510
Mutability::Not
511511
}
512+
None => match self.ecx.get_alloc_info(alloc_id).2 {
513+
AllocKind::LiveData => self.ecx.get_alloc_mutability(alloc_id).unwrap(),
514+
AllocKind::Function | AllocKind::VTable => Mutability::Not,
515+
AllocKind::Dead => throw_validation_failure!(
516+
self.path,
517+
DanglingPtrUseAfterFree { ptr_kind }
518+
),
519+
},
512520
};
513521
// Mutability check.
514522
// If this allocation has size zero, there is no actual mutability here.
@@ -707,13 +715,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
707715
fn in_mutable_memory(&self, op: &OpTy<'tcx, M::Provenance>) -> bool {
708716
if let Some(mplace) = op.as_mplace_or_imm().left() {
709717
if let Some(alloc_id) = mplace.ptr().provenance.and_then(|p| p.get_alloc_id()) {
710-
let mutability = match self.ecx.tcx.global_alloc(alloc_id) {
711-
GlobalAlloc::Static(_) => {
712-
self.ecx.memory.alloc_map.get(alloc_id).unwrap().1.mutability
713-
}
714-
GlobalAlloc::Memory(alloc) => alloc.inner().mutability,
715-
_ => span_bug!(self.ecx.tcx.span, "not a memory allocation"),
716-
};
718+
let mutability = self.ecx.get_alloc_mutability(alloc_id).unwrap();
717719
return mutability == Mutability::Mut;
718720
}
719721
}

compiler/rustc_const_eval/src/util/caller_location.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ pub(crate) fn const_caller_location_provider(
6464
);
6565

6666
let loc_place = alloc_caller_location(&mut ecx, file, line, col);
67-
if intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &loc_place).is_err() {
68-
bug!("intern_const_alloc_recursive should not error in this case")
69-
}
67+
patch_mutability_of_allocs(&mut ecx, InternKind::Constant, &loc_place).unwrap();
68+
intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &loc_place).unwrap();
7069
mir::ConstValue::Scalar(Scalar::from_maybe_pointer(loc_place.ptr(), &tcx))
7170
}

tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use std::intrinsics;
66

77
const _X: &'static u8 = unsafe {
8-
//~^ error: dangling pointer in final value of constant
8+
//~^ error: it is undefined behavior to use this value
99
let ptr = intrinsics::const_allocate(4, 4);
1010
intrinsics::const_deallocate(ptr, 4, 4);
1111
&*ptr

tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.stderr

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
error: encountered dangling pointer in final value of constant
1+
error[E0080]: it is undefined behavior to use this value
22
--> $DIR/dealloc_intrinsic_dangling.rs:7:1
33
|
44
LL | const _X: &'static u8 = unsafe {
5-
| ^^^^^^^^^^^^^^^^^^^^^
5+
| ^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (use-after-free)
6+
|
7+
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
8+
= note: the raw bytes of the constant (size: 8, align: 8) {
9+
╾ALLOC0<imm>╼ │ ╾──────╼
10+
}
611

712
error[E0080]: evaluation of constant value failed
813
--> $DIR/dealloc_intrinsic_dangling.rs:18:5
914
|
1015
LL | *reference
11-
| ^^^^^^^^^^ memory access failed: ALLOC0 has been freed, so this pointer is dangling
16+
| ^^^^^^^^^^ memory access failed: ALLOC1 has been freed, so this pointer is dangling
1217

1318
error: aborting due to 2 previous errors
1419

tests/ui/consts/const-mut-refs/mut_ref_in_final_dynamic_check.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ const fn helper_dangling() -> Option<&'static mut i32> { unsafe {
3333
// Undefined behaviour (dangling pointer), who doesn't love tests like this.
3434
Some(&mut *(&mut 42 as *mut i32))
3535
} }
36-
const DANGLING: Option<&mut i32> = helper_dangling(); //~ ERROR encountered dangling pointer
37-
static DANGLING_STATIC: Option<&mut i32> = helper_dangling(); //~ ERROR encountered dangling pointer
36+
const DANGLING: Option<&mut i32> = helper_dangling(); //~ ERROR it is undefined behavior to use this value
37+
static DANGLING_STATIC: Option<&mut i32> = helper_dangling(); //~ ERROR it is undefined behavior to use this value
3838

3939
// These are fine! Just statics pointing to mutable statics, nothing fundamentally wrong with this.
4040
static MUT_STATIC: Option<&mut i32> = helper();

0 commit comments

Comments
 (0)