Skip to content

Commit

Permalink
Delay interning errors to after validation
Browse files Browse the repository at this point in the history
  • Loading branch information
oli-obk committed Mar 19, 2024
1 parent a42873e commit 592f674
Show file tree
Hide file tree
Showing 21 changed files with 284 additions and 621 deletions.
28 changes: 25 additions & 3 deletions compiler/rustc_const_eval/src/const_eval/eval_queries.rs
Expand Up @@ -8,20 +8,21 @@ use rustc_middle::traits::Reveal;
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::lint;
use rustc_span::def_id::LocalDefId;
use rustc_span::Span;
use rustc_target::abi::{self, Abi};

use super::{CanAccessMutGlobal, CompileTimeEvalContext, CompileTimeInterpreter};
use crate::const_eval::CheckAlignment;
use crate::errors;
use crate::errors::ConstEvalError;
use crate::interpret::eval_nullary_intrinsic;
use crate::errors::{self, DanglingPtrInFinal};
use crate::interpret::{
create_static_alloc, intern_const_alloc_recursive, CtfeValidationMode, GlobalId, Immediate,
InternKind, InterpCx, InterpError, InterpResult, MPlaceTy, MemoryKind, OpTy, RefTracking,
StackPopCleanup,
};
use crate::interpret::{eval_nullary_intrinsic, InternResult};

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

// Intern the result
intern_const_alloc_recursive(ecx, intern_kind, &ret)?;
let InternResult { found_bad_mutable_pointer, found_dangling_pointer } =
intern_const_alloc_recursive(ecx, intern_kind, &ret);

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

// Only report this after validation, as validaiton produces much better diagnostics.
// FIXME: ensure validation always reports this and stop making interning care about it.

if found_dangling_pointer {
return Err(ecx
.tcx
.dcx()
.emit_err(DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind })
.into());
} else if found_bad_mutable_pointer {
// only report mutable pointers if there were no dangling pointers
let err_diag = errors::MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind };
ecx.tcx.emit_node_span_lint(
lint::builtin::CONST_EVAL_MUTABLE_PTR_IN_FINAL_VALUE,
ecx.best_lint_scope(),
err_diag.span,
err_diag,
)
}

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

Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_const_eval/src/const_eval/valtrees.rs
Expand Up @@ -323,7 +323,9 @@ pub fn valtree_to_const_value<'tcx>(

valtree_into_mplace(&mut ecx, &place, valtree);
dump_place(&ecx, &place);
intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &place).unwrap();
assert!(
intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &place).no_errors()
);

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

valtree_into_mplace(ecx, &pointee_place, valtree);
dump_place(ecx, &pointee_place);
intern_const_alloc_recursive(ecx, InternKind::Constant, &pointee_place).unwrap();
assert!(intern_const_alloc_recursive(ecx, InternKind::Constant, &pointee_place).no_errors());

pointee_place.to_ref(&ecx.tcx)
}
Expand Down
41 changes: 21 additions & 20 deletions compiler/rustc_const_eval/src/interpret/intern.rs
Expand Up @@ -16,18 +16,15 @@
use hir::def::DefKind;
use rustc_ast::Mutability;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::ErrorGuaranteed;
use rustc_hir as hir;
use rustc_middle::mir::interpret::{ConstAllocation, CtfeProvenance, InterpResult};
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_session::lint;
use rustc_span::def_id::LocalDefId;
use rustc_span::sym;

use super::{AllocId, Allocation, InterpCx, MPlaceTy, Machine, MemoryKind, PlaceTy};
use crate::const_eval;
use crate::errors::{DanglingPtrInFinal, MutablePtrInFinal};

pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine<
'mir,
Expand Down Expand Up @@ -123,6 +120,19 @@ pub enum InternKind {
Promoted,
}

#[derive(Default)]
pub struct InternResult {
pub found_bad_mutable_pointer: bool,
pub found_dangling_pointer: bool,
}

impl InternResult {
pub fn no_errors(self) -> bool {
let Self { found_bad_mutable_pointer, found_dangling_pointer } = self;
!(found_bad_mutable_pointer && found_dangling_pointer)
}
}

/// Intern `ret` and everything it references.
///
/// This *cannot raise an interpreter error*. Doing so is left to validation, which
Expand All @@ -138,7 +148,7 @@ pub fn intern_const_alloc_recursive<
ecx: &mut InterpCx<'mir, 'tcx, M>,
intern_kind: InternKind,
ret: &MPlaceTy<'tcx>,
) -> Result<(), ErrorGuaranteed> {
) -> InternResult {
// We are interning recursively, and for mutability we are distinguishing the "root" allocation
// that we are starting in, and all other allocations that we are encountering recursively.
let (base_mutability, inner_mutability, is_static) = match intern_kind {
Expand Down Expand Up @@ -190,7 +200,7 @@ pub fn intern_const_alloc_recursive<
// Whether we encountered a bad mutable pointer.
// We want to first report "dangling" and then "mutable", so we need to delay reporting these
// errors.
let mut found_bad_mutable_pointer = false;
let mut result = InternResult::default();

// Keep interning as long as there are things to intern.
// We show errors if there are dangling pointers, or mutable pointers in immutable contexts
Expand Down Expand Up @@ -240,7 +250,7 @@ pub fn intern_const_alloc_recursive<
// on the promotion analysis not screwing up to ensure that it is sound to intern
// promoteds as immutable.
trace!("found bad mutable pointer");
found_bad_mutable_pointer = true;
result.found_bad_mutable_pointer = true;
}
if ecx.tcx.try_get_global_alloc(alloc_id).is_some() {
// Already interned.
Expand All @@ -258,21 +268,12 @@ pub fn intern_const_alloc_recursive<
// pointers before deciding which allocations can be made immutable; but for now we are
// okay with losing some potential for immutability here. This can anyway only affect
// `static mut`.
todo.extend(intern_shallow(ecx, alloc_id, inner_mutability).map_err(|()| {
ecx.tcx.dcx().emit_err(DanglingPtrInFinal { span: ecx.tcx.span, kind: intern_kind })
})?);
}
if found_bad_mutable_pointer {
let err_diag = MutablePtrInFinal { span: ecx.tcx.span, kind: intern_kind };
ecx.tcx.emit_node_span_lint(
lint::builtin::CONST_EVAL_MUTABLE_PTR_IN_FINAL_VALUE,
ecx.best_lint_scope(),
err_diag.span,
err_diag,
)
match intern_shallow(ecx, alloc_id, inner_mutability) {
Ok(nested) => todo.extend(nested),
Err(()) => result.found_dangling_pointer = true,
}
}

Ok(())
result
}

/// Intern `ret`. This function assumes that `ret` references no other allocation.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_const_eval/src/interpret/mod.rs
Expand Up @@ -23,6 +23,7 @@ pub use rustc_middle::mir::interpret::*; // have all the `interpret` symbols in
pub use self::eval_context::{format_interp_error, Frame, FrameInfo, InterpCx, StackPopCleanup};
pub use self::intern::{
intern_const_alloc_for_constprop, intern_const_alloc_recursive, HasStaticRootDefId, InternKind,
InternResult,
};
pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump};
pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};
Expand Down
71 changes: 40 additions & 31 deletions compiler/rustc_const_eval/src/interpret/validity.rs
Expand Up @@ -10,6 +10,7 @@ use std::num::NonZero;
use either::{Left, Right};

use hir::def::DefKind;
use hir::def_id::DefId;
use rustc_ast::Mutability;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
Expand All @@ -27,9 +28,9 @@ use rustc_target::abi::{
use std::hash::Hash;

use super::{
format_interp_error, machine::AllocMap, AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy,
Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Pointer, Projectable,
Scalar, ValueVisitor,
format_interp_error, AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx,
InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Pointer, Projectable, Scalar,
ValueVisitor,
};

// for the validation errors
Expand Down Expand Up @@ -433,7 +434,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty })
}
// Recursive checking
if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() {
if self.ref_tracking.is_some() {
// Determine whether this pointer expects to be pointing to something mutable.
let ptr_expected_mutbl = match ptr_kind {
PointerKind::Box => Mutability::Mut,
Expand All @@ -457,6 +458,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
// Special handling for pointers to statics (irrespective of their type).
assert!(!self.ecx.tcx.is_thread_local_static(did));
assert!(self.ecx.tcx.is_static(did));
// Return alloc mutability. For "root" statics we look at the type to account for interior
// mutability; for nested statics we have no type and directly use the annotated mutability.
let DefKind::Static { mutability, nested } = self.ecx.tcx.def_kind(did)
else {
bug!()
};
// Mode-specific checks
match self.ctfe_mode {
Some(
Expand All @@ -471,7 +478,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
// trigger cycle errors if we try to compute the value of the other static
// and that static refers back to us (potentially through a promoted).
// This could miss some UB, but that's fine.
skip_recursive_check = true;
// We still walk nested allocations, as they are fundamentally part of this validation run.
skip_recursive_check = !nested;
}
Some(CtfeValidationMode::Const { .. }) => {
// We can't recursively validate `extern static`, so we better reject them.
Expand All @@ -481,28 +489,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
}
None => {}
}
// Return alloc mutability. For "root" statics we look at the type to account for interior
// mutability; for nested statics we have no type and directly use the annotated mutability.
let DefKind::Static { mutability, nested } = self.ecx.tcx.def_kind(did)
else {
bug!()
};
match (mutability, nested) {
(Mutability::Mut, _) => Mutability::Mut,
(Mutability::Not, true) => Mutability::Not,
(Mutability::Not, false)
if !self
.ecx
.tcx
.type_of(did)
.no_bound_vars()
.expect("statics should not have generic parameters")
.is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all()) =>
{
Mutability::Mut
}
(Mutability::Not, false) => Mutability::Not,
}
self.static_mutability(mutability, nested, did)
}
GlobalAlloc::Memory(alloc) => alloc.inner().mutability,
GlobalAlloc::Function(..) | GlobalAlloc::VTable(..) => {
Expand Down Expand Up @@ -535,7 +522,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
}
}
let path = &self.path;
ref_tracking.track(place, || {
self.ref_tracking.as_deref_mut().unwrap().track(place, || {
// We need to clone the path anyway, make sure it gets created
// with enough space for the additional `Deref`.
let mut new_path = Vec::with_capacity(path.len() + 1);
Expand All @@ -547,6 +534,25 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
Ok(())
}

fn static_mutability(&self, mutability: Mutability, nested: bool, did: DefId) -> Mutability {
match (mutability, nested) {
(Mutability::Mut, _) => Mutability::Mut,
(Mutability::Not, true) => Mutability::Not,
(Mutability::Not, false)
if !self
.ecx
.tcx
.type_of(did)
.no_bound_vars()
.expect("statics should not have generic parameters")
.is_freeze(*self.ecx.tcx, ty::ParamEnv::reveal_all()) =>
{
Mutability::Mut
}
(Mutability::Not, false) => Mutability::Not,
}
}

/// Check if this is a value of primitive type, and if yes check the validity of the value
/// at that type. Return `true` if the type is indeed primitive.
///
Expand Down Expand Up @@ -708,9 +714,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
if let Some(mplace) = op.as_mplace_or_imm().left() {
if let Some(alloc_id) = mplace.ptr().provenance.and_then(|p| p.get_alloc_id()) {
let mutability = match self.ecx.tcx.global_alloc(alloc_id) {
GlobalAlloc::Static(_) => {
self.ecx.memory.alloc_map.get(alloc_id).unwrap().1.mutability
}
GlobalAlloc::Static(id) => match self.ecx.tcx.def_kind(id) {
DefKind::Static { nested, mutability } => {
self.static_mutability(mutability, nested, id)
}
_ => bug!(),
},
GlobalAlloc::Memory(alloc) => alloc.inner().mutability,
_ => span_bug!(self.ecx.tcx.span, "not a memory allocation"),
};
Expand Down
4 changes: 1 addition & 3 deletions compiler/rustc_const_eval/src/util/caller_location.rs
Expand Up @@ -64,8 +64,6 @@ pub(crate) fn const_caller_location_provider(
);

let loc_place = alloc_caller_location(&mut ecx, file, line, col);
if intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &loc_place).is_err() {
bug!("intern_const_alloc_recursive should not error in this case")
}
assert!(intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &loc_place).no_errors());
mir::ConstValue::Scalar(Scalar::from_maybe_pointer(loc_place.ptr(), &tcx))
}
Expand Up @@ -2,10 +2,15 @@
#![feature(const_heap)]
#![feature(const_mut_refs)]

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

use std::intrinsics;

const _X: &'static u8 = unsafe {
//~^ error: dangling pointer in final value of constant
//~^ error: it is undefined behavior to use this value
let ptr = intrinsics::const_allocate(4, 4);
intrinsics::const_deallocate(ptr, 4, 4);
&*ptr
Expand Down
15 changes: 10 additions & 5 deletions tests/ui/consts/const-eval/heap/dealloc_intrinsic_dangling.stderr
@@ -1,14 +1,19 @@
error: encountered dangling pointer in final value of constant
--> $DIR/dealloc_intrinsic_dangling.rs:7:1
error[E0080]: it is undefined behavior to use this value
--> $DIR/dealloc_intrinsic_dangling.rs:12:1
|
LL | const _X: &'static u8 = unsafe {
| ^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (use-after-free)
|
= 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.
= note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) {
HEX_DUMP
}

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

error: aborting due to 2 previous errors

Expand Down
Expand Up @@ -33,8 +33,8 @@ const fn helper_dangling() -> Option<&'static mut i32> { unsafe {
// Undefined behaviour (dangling pointer), who doesn't love tests like this.
Some(&mut *(&mut 42 as *mut i32))
} }
const DANGLING: Option<&mut i32> = helper_dangling(); //~ ERROR encountered dangling pointer
static DANGLING_STATIC: Option<&mut i32> = helper_dangling(); //~ ERROR encountered dangling pointer
const DANGLING: Option<&mut i32> = helper_dangling(); //~ ERROR it is undefined behavior to use this value
static DANGLING_STATIC: Option<&mut i32> = helper_dangling(); //~ ERROR it is undefined behavior to use this value

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

0 comments on commit 592f674

Please sign in to comment.