interpret: sanity-check that required_consts captures all consts that can fail
This commit is contained in:
parent
b2b617a88e
commit
bf021ea625
7 changed files with 62 additions and 34 deletions
|
@ -46,6 +46,12 @@ impl<'mir, 'tcx: 'mir> interpret::Machine<'mir, 'tcx> for DummyMachine {
|
||||||
type MemoryKind = !;
|
type MemoryKind = !;
|
||||||
const PANIC_ON_ALLOC_FAIL: bool = true;
|
const PANIC_ON_ALLOC_FAIL: bool = true;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn all_required_consts_are_checked(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
|
||||||
|
// We want to just eval random consts in the program, so `eval_mir_const` can fail.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
|
fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
|
||||||
false // no reason to enforce alignment
|
false // no reason to enforce alignment
|
||||||
|
|
|
@ -375,6 +375,20 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
||||||
|
|
||||||
const PANIC_ON_ALLOC_FAIL: bool = false; // will be raised as a proper error
|
const PANIC_ON_ALLOC_FAIL: bool = false; // will be raised as a proper error
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn all_required_consts_are_checked(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
|
||||||
|
// Generally we expect required_consts to be properly filled, except for promoteds where
|
||||||
|
// storing these consts shows up negatively in benchmarks. A promoted can only be relevant
|
||||||
|
// if its parent MIR is relevant, and the consts in the promoted will be in the parent's
|
||||||
|
// `required_consts`, so we are still sure to catch any const-eval bugs, just a bit less
|
||||||
|
// directly.
|
||||||
|
if ecx.frame_idx() == 0 && ecx.frame().body.source.promoted.is_some() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
|
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
|
||||||
matches!(ecx.machine.check_alignment, CheckAlignment::Error)
|
matches!(ecx.machine.check_alignment, CheckAlignment::Error)
|
||||||
|
|
|
@ -822,16 +822,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
self.stack_mut().push(frame);
|
self.stack_mut().push(frame);
|
||||||
|
|
||||||
// Make sure all the constants required by this frame evaluate successfully (post-monomorphization check).
|
// Make sure all the constants required by this frame evaluate successfully (post-monomorphization check).
|
||||||
if M::POST_MONO_CHECKS {
|
|
||||||
for &const_ in &body.required_consts {
|
for &const_ in &body.required_consts {
|
||||||
let c = self
|
let c =
|
||||||
.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?;
|
self.instantiate_from_current_frame_and_normalize_erasing_regions(const_.const_)?;
|
||||||
c.eval(*self.tcx, self.param_env, const_.span).map_err(|err| {
|
c.eval(*self.tcx, self.param_env, const_.span).map_err(|err| {
|
||||||
err.emit_note(*self.tcx);
|
err.emit_note(*self.tcx);
|
||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// done
|
// done
|
||||||
M::after_stack_push(self)?;
|
M::after_stack_push(self)?;
|
||||||
|
@ -1181,8 +1179,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||||
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
|
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> {
|
||||||
M::eval_mir_constant(self, *val, span, layout, |ecx, val, span, layout| {
|
M::eval_mir_constant(self, *val, span, layout, |ecx, val, span, layout| {
|
||||||
let const_val = val.eval(*ecx.tcx, ecx.param_env, span).map_err(|err| {
|
let const_val = val.eval(*ecx.tcx, ecx.param_env, span).map_err(|err| {
|
||||||
// FIXME: somehow this is reachable even when POST_MONO_CHECKS is on.
|
if M::all_required_consts_are_checked(self)
|
||||||
// Are we not always populating `required_consts`?
|
&& !matches!(err, ErrorHandled::TooGeneric(..))
|
||||||
|
{
|
||||||
|
// Looks like the const is not captued by `required_consts`, that's bad.
|
||||||
|
bug!("interpret const eval failure of {val:?} which is not in required_consts");
|
||||||
|
}
|
||||||
err.emit_note(*ecx.tcx);
|
err.emit_note(*ecx.tcx);
|
||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -140,8 +140,9 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
|
||||||
/// Should the machine panic on allocation failures?
|
/// Should the machine panic on allocation failures?
|
||||||
const PANIC_ON_ALLOC_FAIL: bool;
|
const PANIC_ON_ALLOC_FAIL: bool;
|
||||||
|
|
||||||
/// Should post-monomorphization checks be run when a stack frame is pushed?
|
/// Determines whether `eval_mir_constant` can never fail because all required consts have
|
||||||
const POST_MONO_CHECKS: bool = true;
|
/// already been checked before.
|
||||||
|
fn all_required_consts_are_checked(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
|
||||||
|
|
||||||
/// Whether memory accesses should be alignment-checked.
|
/// Whether memory accesses should be alignment-checked.
|
||||||
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
|
fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool;
|
||||||
|
|
|
@ -720,18 +720,8 @@ impl<'tcx> Inliner<'tcx> {
|
||||||
kind: TerminatorKind::Goto { target: integrator.map_block(START_BLOCK) },
|
kind: TerminatorKind::Goto { target: integrator.map_block(START_BLOCK) },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy only unevaluated constants from the callee_body into the caller_body.
|
// Copy required constants from the callee_body into the caller_body.
|
||||||
// Although we are only pushing `ConstKind::Unevaluated` consts to
|
caller_body.required_consts.extend(callee_body.required_consts);
|
||||||
// `required_consts`, here we may not only have `ConstKind::Unevaluated`
|
|
||||||
// because we are calling `instantiate_and_normalize_erasing_regions`.
|
|
||||||
caller_body.required_consts.extend(callee_body.required_consts.iter().copied().filter(
|
|
||||||
|&ct| match ct.const_ {
|
|
||||||
Const::Ty(_) => {
|
|
||||||
bug!("should never encounter ty::UnevaluatedConst in `required_consts`")
|
|
||||||
}
|
|
||||||
Const::Val(..) | Const::Unevaluated(..) => true,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
// Now that we incorporated the callee's `required_consts`, we can remove the callee from
|
// Now that we incorporated the callee's `required_consts`, we can remove the callee from
|
||||||
// `mentioned_items` -- but we have to take their `mentioned_items` in return. This does
|
// `mentioned_items` -- but we have to take their `mentioned_items` in return. This does
|
||||||
// some extra work here to save the monomorphization collector work later. It helps a lot,
|
// some extra work here to save the monomorphization collector work later. It helps a lot,
|
||||||
|
@ -747,8 +737,9 @@ impl<'tcx> Inliner<'tcx> {
|
||||||
caller_body.mentioned_items.remove(idx);
|
caller_body.mentioned_items.remove(idx);
|
||||||
caller_body.mentioned_items.extend(callee_body.mentioned_items);
|
caller_body.mentioned_items.extend(callee_body.mentioned_items);
|
||||||
} else {
|
} else {
|
||||||
// If we can't find the callee, there's no point in adding its items.
|
// If we can't find the callee, there's no point in adding its items. Probably it
|
||||||
// Probably it already got removed by being inlined elsewhere in the same function.
|
// already got removed by being inlined elsewhere in the same function, so we already
|
||||||
|
// took its items.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use rustc_middle::mir::visit::Visitor;
|
use rustc_middle::mir::visit::Visitor;
|
||||||
use rustc_middle::mir::{Const, ConstOperand, Location};
|
use rustc_middle::mir::{Const, ConstOperand, Location};
|
||||||
use rustc_middle::ty::ConstKind;
|
use rustc_middle::ty;
|
||||||
|
|
||||||
pub struct RequiredConstsVisitor<'a, 'tcx> {
|
pub struct RequiredConstsVisitor<'a, 'tcx> {
|
||||||
required_consts: &'a mut Vec<ConstOperand<'tcx>>,
|
required_consts: &'a mut Vec<ConstOperand<'tcx>>,
|
||||||
|
@ -14,14 +14,22 @@ impl<'a, 'tcx> RequiredConstsVisitor<'a, 'tcx> {
|
||||||
|
|
||||||
impl<'tcx> Visitor<'tcx> for RequiredConstsVisitor<'_, 'tcx> {
|
impl<'tcx> Visitor<'tcx> for RequiredConstsVisitor<'_, 'tcx> {
|
||||||
fn visit_constant(&mut self, constant: &ConstOperand<'tcx>, _: Location) {
|
fn visit_constant(&mut self, constant: &ConstOperand<'tcx>, _: Location) {
|
||||||
let const_ = constant.const_;
|
// Only unevaluated consts have to be added to `required_consts` as only those can possibly
|
||||||
match const_ {
|
// still have latent const-eval errors.
|
||||||
|
let is_required = match constant.const_ {
|
||||||
Const::Ty(c) => match c.kind() {
|
Const::Ty(c) => match c.kind() {
|
||||||
ConstKind::Param(_) | ConstKind::Error(_) | ConstKind::Value(_) => {}
|
ty::ConstKind::Value(_) => false, // already a value, cannot error
|
||||||
_ => bug!("only ConstKind::Param/Value should be encountered here, got {:#?}", c),
|
ty::ConstKind::Param(_) | ty::ConstKind::Error(_) => true, // these are errors or could be replaced by errors
|
||||||
|
_ => bug!(
|
||||||
|
"only ConstKind::Param/Value/Error should be encountered here, got {:#?}",
|
||||||
|
c
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Const::Unevaluated(..) => self.required_consts.push(*constant),
|
Const::Unevaluated(..) => true,
|
||||||
Const::Val(..) => {}
|
Const::Val(..) => false, // already a value, cannot error
|
||||||
|
};
|
||||||
|
if is_required {
|
||||||
|
self.required_consts.push(*constant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -872,6 +872,12 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||||
|
|
||||||
const PANIC_ON_ALLOC_FAIL: bool = false;
|
const PANIC_ON_ALLOC_FAIL: bool = false;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn all_required_consts_are_checked(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
|
||||||
|
// We want to catch any cases of missing required_consts
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn enforce_alignment(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool {
|
fn enforce_alignment(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool {
|
||||||
ecx.machine.check_alignment != AlignmentCheck::None
|
ecx.machine.check_alignment != AlignmentCheck::None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue