Skip to content

Commit 8f21f22

Browse files
committed
Delay interning errors to after validation
1 parent a42873e commit 8f21f22

21 files changed

+283
-619
lines changed

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

+25-3
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@ use rustc_middle::traits::Reveal;
88
use rustc_middle::ty::layout::LayoutOf;
99
use rustc_middle::ty::print::with_no_trimmed_paths;
1010
use rustc_middle::ty::{self, Ty, TyCtxt};
11+
use rustc_session::lint;
1112
use rustc_span::def_id::LocalDefId;
1213
use rustc_span::Span;
1314
use rustc_target::abi::{self, Abi};
1415

1516
use super::{CanAccessMutGlobal, CompileTimeEvalContext, CompileTimeInterpreter};
1617
use crate::const_eval::CheckAlignment;
17-
use crate::errors;
1818
use crate::errors::ConstEvalError;
19-
use crate::interpret::eval_nullary_intrinsic;
19+
use crate::errors::{self, DanglingPtrInFinal};
2020
use crate::interpret::{
2121
create_static_alloc, intern_const_alloc_recursive, CtfeValidationMode, GlobalId, Immediate,
2222
InternKind, InterpCx, InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking,
2323
StackPopCleanup,
2424
};
25+
use crate::interpret::{eval_nullary_intrinsic, InternResult};
2526

2627
// Returns a pointer to where the result lives
2728
#[instrument(level = "trace", skip(ecx, body))]
@@ -82,11 +83,32 @@ fn eval_body_using_ecx<'mir, 'tcx, R: InterpretationResult<'tcx>>(
8283
while ecx.step()? {}
8384

8485
// Intern the result
85-
intern_const_alloc_recursive(ecx, intern_kind, &ret)?;
86+
let InternResult { found_bad_mutable_pointer, found_dangling_pointer } =
87+
intern_const_alloc_recursive(ecx, intern_kind, &ret);
8688

8789
// Since evaluation had no errors, validate the resulting constant.
8890
const_validate_mplace(&ecx, &ret, cid)?;
8991

92+
// Only report this after validation, as validaiton produces much better diagnostics.
93+
// FIXME: ensure validation always reports this and stop making interning care about it.
94+
95+
if found_dangling_pointer {
96+
return Err(ecx
97+
.tcx
98+
.dcx()
99+
.emit_err(DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind })
100+
.into());
101+
} else if found_bad_mutable_pointer {
102+
// only report mutable pointers if there were no dangling pointers
103+
let err_diag = errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind };
104+
ecx.tcx.emit_node_span_lint(
105+
lint::builtin::CONST_EVAL_MUTABLE_PTR_IN_FINAL_VALUE,
106+
ecx.best_lint_scope(),
107+
err_diag.span,
108+
err_diag,
109+
)
110+
}
111+
90112
Ok(R::make_result(ret, ecx))
91113
}
92114

compiler/rustc_const_eval/src/const_eval/valtrees.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,9 @@ pub fn valtree_to_const_value<'tcx>(
323323

324324
valtree_into_mplace(&mut ecx, &place, valtree);
325325
dump_place(&ecx, &place);
326-
intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &place).unwrap();
326+
assert!(
327+
intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &place).no_errors()
328+
);
327329

328330
op_to_const(&ecx, &place.into(), /* for diagnostics */ false)
329331
}
@@ -359,7 +361,7 @@ fn valtree_to_ref<'tcx>(
359361

360362
valtree_into_mplace(ecx, &pointee_place, valtree);
361363
dump_place(ecx, &pointee_place);
362-
intern_const_alloc_recursive(ecx, InternKind::Constant, &pointee_place).unwrap();
364+
assert!(intern_const_alloc_recursive(ecx, InternKind::Constant, &pointee_place).no_errors());
363365

364366
pointee_place.to_ref(&ecx.tcx)
365367
}

compiler/rustc_const_eval/src/interpret/intern.rs

+21-20
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,15 @@
1616
use hir::def::DefKind;
1717
use rustc_ast::Mutability;
1818
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
19-
use rustc_errors::ErrorGuaranteed;
2019
use rustc_hir as hir;
2120
use rustc_middle::mir::interpret::{ConstAllocation, CtfeProvenance, InterpResult};
2221
use rustc_middle::query::TyCtxtAt;
2322
use rustc_middle::ty::layout::TyAndLayout;
24-
use rustc_session::lint;
2523
use rustc_span::def_id::LocalDefId;
2624
use rustc_span::sym;
2725

2826
use super::{AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy};
2927
use crate::const_eval;
30-
use crate::errors::{DanglingPtrInFinal, MutablePtrInFinal};
3128

3229
pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine<
3330
'mir,
@@ -123,6 +120,19 @@ pub enum InternKind {
123120
Promoted,
124121
}
125122

123+
#[derive(Default)]
124+
pub struct InternResult {
125+
pub found_bad_mutable_pointer: bool,
126+
pub found_dangling_pointer: bool,
127+
}
128+
129+
impl InternResult {
130+
pub fn no_errors(self) -> bool {
131+
let Self { found_bad_mutable_pointer, found_dangling_pointer } = self;
132+
!(found_bad_mutable_pointer && found_dangling_pointer)
133+
}
134+
}
135+
126136
/// Intern `ret` and everything it references.
127137
///
128138
/// This *cannot raise an interpreter error*. Doing so is left to validation, which
@@ -138,7 +148,7 @@ pub fn intern_const_alloc_recursive<
138148
ecx: &mut InterpCx<'mir, 'tcx, M>,
139149
intern_kind: InternKind,
140150
ret: &MPlaceTy<'tcx>,
141-
) -> Result<(), ErrorGuaranteed> {
151+
) -> InternResult {
142152
// We are interning recursively, and for mutability we are distinguishing the "root" allocation
143153
// that we are starting in, and all other allocations that we are encountering recursively.
144154
let (base_mutability, inner_mutability, is_static) = match intern_kind {
@@ -190,7 +200,7 @@ pub fn intern_const_alloc_recursive<
190200
// Whether we encountered a bad mutable pointer.
191201
// We want to first report "dangling" and then "mutable", so we need to delay reporting these
192202
// errors.
193-
let mut found_bad_mutable_pointer = false;
203+
let mut result = InternResult::default();
194204

195205
// Keep interning as long as there are things to intern.
196206
// We show errors if there are dangling pointers, or mutable pointers in immutable contexts
@@ -240,7 +250,7 @@ pub fn intern_const_alloc_recursive<
240250
// on the promotion analysis not screwing up to ensure that it is sound to intern
241251
// promoteds as immutable.
242252
trace!("found bad mutable pointer");
243-
found_bad_mutable_pointer = true;
253+
result.found_bad_mutable_pointer = true;
244254
}
245255
if ecx.tcx.try_get_global_alloc(alloc_id).is_some() {
246256
// Already interned.
@@ -258,21 +268,12 @@ pub fn intern_const_alloc_recursive<
258268
// pointers before deciding which allocations can be made immutable; but for now we are
259269
// okay with losing some potential for immutability here. This can anyway only affect
260270
// `static mut`.
261-
todo.extend(intern_shallow(ecx, alloc_id, inner_mutability).map_err(|()| {
262-
ecx.tcx.dcx().emit_err(DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind })
263-
})?);
264-
}
265-
if found_bad_mutable_pointer {
266-
let err_diag = MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind };
267-
ecx.tcx.emit_node_span_lint(
268-
lint::builtin::CONST_EVAL_MUTABLE_PTR_IN_FINAL_VALUE,
269-
ecx.best_lint_scope(),
270-
err_diag.span,
271-
err_diag,
272-
)
271+
match intern_shallow(ecx, alloc_id, inner_mutability) {
272+
Ok(nested) => todo.extend(nested),
273+
Err(()) => result.found_dangling_pointer = true,
274+
}
273275
}
274-
275-
Ok(())
276+
result
276277
}
277278

278279
/// Intern `ret`. This function assumes that `ret` references no other allocation.

compiler/rustc_const_eval/src/interpret/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub use rustc_middle::mir::interpret::*; // have all the `interpret` symbols in
2323
pub use self::eval_context::{format_interp_error, Frame, FrameInfo, InterpCx, StackPopCleanup};
2424
pub use self::intern::{
2525
intern_const_alloc_for_constprop, intern_const_alloc_recursive, HasStaticRootDefId, InternKind,
26+
InternResult,
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

+40-31
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::num::NonZero;
1010
use either::{Left, Right};
1111

1212
use hir::def::DefKind;
13+
use hir::def_id::DefId;
1314
use rustc_ast::Mutability;
1415
use rustc_data_structures::fx::FxHashSet;
1516
use rustc_hir as hir;
@@ -27,9 +28,9 @@ use rustc_target::abi::{
2728
use std::hash::Hash;
2829

2930
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,
31+
format_interp_error, AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx,
32+
InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Pointer, Projectable, Scalar,
33+
ValueVisitor,
3334
};
3435

3536
// for the validation errors
@@ -433,7 +434,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
433434
throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
434435
}
435436
// Recursive checking
436-
if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
437+
if self.ref_tracking.is_some() {
437438
// Determine whether this pointer expects to be pointing to something mutable.
438439
let ptr_expected_mutbl = match ptr_kind {
439440
PointerKind::Box => Mutability::Mut,
@@ -457,6 +458,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
457458
// Special handling for pointers to statics (irrespective of their type).
458459
assert!(!self.ecx.tcx.is_thread_local_static(did));
459460
assert!(self.ecx.tcx.is_static(did));
461+
// Return alloc mutability. For "root" statics we look at the type to account for interior
462+
// mutability; for nested statics we have no type and directly use the annotated mutability.
463+
let DefKind::Static { mutability, nested } = self.ecx.tcx.def_kind(did)
464+
else {
465+
bug!()
466+
};
460467
// Mode-specific checks
461468
match self.ctfe_mode {
462469
Some(
@@ -471,7 +478,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
471478
// trigger cycle errors if we try to compute the value of the other static
472479
// and that static refers back to us (potentially through a promoted).
473480
// This could miss some UB, but that's fine.
474-
skip_recursive_check = true;
481+
// We still walk nested allocations, as they are fundamentally part of this validation run.
482+
skip_recursive_check = !nested;
475483
}
476484
Some(CtfeValidationMode::Const { .. }) => {
477485
// We can't recursively validate `extern static`, so we better reject them.
@@ -481,28 +489,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
481489
}
482490
None => {}
483491
}
484-
// Return alloc mutability. For "root" statics we look at the type to account for interior
485-
// mutability; for nested statics we have no type and directly use the annotated mutability.
486-
let DefKind::Static { mutability, nested } = self.ecx.tcx.def_kind(did)
487-
else {
488-
bug!()
489-
};
490-
match (mutability, nested) {
491-
(Mutability::Mut, _) => Mutability::Mut,
492-
(Mutability::Not, true) => Mutability::Not,
493-
(Mutability::Not, false)
494-
if !self
495-
.ecx
496-
.tcx
497-
.type_of(did)
498-
.no_bound_vars()
499-
.expect("statics should not have generic parameters")
500-
.is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all()) =>
501-
{
502-
Mutability::Mut
503-
}
504-
(Mutability::Not, false) => Mutability::Not,
505-
}
492+
self.static_mutability(mutability, nested, did)
506493
}
507494
GlobalAlloc::Memory(alloc) => alloc.inner().mutability,
508495
GlobalAlloc::Function(..) | GlobalAlloc::VTable(..) => {
@@ -535,7 +522,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
535522
}
536523
}
537524
let path = &self.path;
538-
ref_tracking.track(place, || {
525+
self.ref_tracking.as_deref_mut().unwrap().track(place, || {
539526
// We need to clone the path anyway, make sure it gets created
540527
// with enough space for the additional `Deref`.
541528
let mut new_path = Vec::with_capacity(path.len() + 1);
@@ -547,6 +534,25 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
547534
Ok(())
548535
}
549536

537+
fn static_mutability(&self, mutability: Mutability, nested: bool, did: DefId) -> Mutability {
538+
match (mutability, nested) {
539+
(Mutability::Mut, _) => Mutability::Mut,
540+
(Mutability::Not, true) => Mutability::Not,
541+
(Mutability::Not, false)
542+
if !self
543+
.ecx
544+
.tcx
545+
.type_of(did)
546+
.no_bound_vars()
547+
.expect("statics should not have generic parameters")
548+
.is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all()) =>
549+
{
550+
Mutability::Mut
551+
}
552+
(Mutability::Not, false) => Mutability::Not,
553+
}
554+
}
555+
550556
/// Check if this is a value of primitive type, and if yes check the validity of the value
551557
/// at that type. Return `true` if the type is indeed primitive.
552558
///
@@ -708,9 +714,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
708714
if let Some(mplace) = op.as_mplace_or_imm().left() {
709715
if let Some(alloc_id) = mplace.ptr().provenance.and_then(|p| p.get_alloc_id()) {
710716
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-
}
717+
GlobalAlloc::Static(id) => match self.ecx.tcx.def_kind(id) {
718+
DefKind::Static { nested, mutability } => {
719+
self.static_mutability(mutability, nested, id)
720+
}
721+
_ => bug!(),
722+
},
714723
GlobalAlloc::Memory(alloc) => alloc.inner().mutability,
715724
_ => span_bug!(self.ecx.tcx.span, "not a memory allocation"),
716725
};

compiler/rustc_const_eval/src/util/caller_location.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ 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+
assert!(intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &loc_place).no_errors());
7068
mir::ConstValue::Scalar(Scalar::from_maybe_pointer(loc_place.ptr(), &tcx))
7169
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
#![feature(const_heap)]
33
#![feature(const_mut_refs)]
44

5+
// Strip out raw byte dumps to make comparison platform-independent:
6+
//@ normalize-stderr-test "(the raw bytes of the constant) \(size: [0-9]*, align: [0-9]*\)" -> "$1 (size: $$SIZE, align: $$ALIGN)"
7+
//@ normalize-stderr-test "([0-9a-f][0-9a-f] |╾─*A(LLOC)?[0-9]+(\+[a-z0-9]+)?(<imm>)?─*╼ )+ *│.*" -> "HEX_DUMP"
8+
//@ normalize-stderr-test "HEX_DUMP\s*\n\s*HEX_DUMP" -> "HEX_DUMP"
9+
510
use std::intrinsics;
611

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

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

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
error: encountered dangling pointer in final value of constant
2-
--> $DIR/dealloc_intrinsic_dangling.rs:7:1
1+
error[E0080]: it is undefined behavior to use this value
2+
--> $DIR/dealloc_intrinsic_dangling.rs:12: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: $SIZE, align: $ALIGN) {
9+
HEX_DUMP
10+
}
611

712
error[E0080]: evaluation of constant value failed
8-
--> $DIR/dealloc_intrinsic_dangling.rs:18:5
13+
--> $DIR/dealloc_intrinsic_dangling.rs:23: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

+2-2
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)