Enable standalone const-checking with Validator

Unlike the original pass, we check *every* non-cleanup basic block
instead of stopping at `SwitchInt`. We use the `is_cfg_cyclic` function
to check for loops unlike the original checker which could not differentiate
between true cycles and basic blocks with more than two predecessors.

The last three functions are all copied verbatim from `qualify_consts`.
This commit is contained in:
Dylan MacKenzie 2019-10-29 17:19:58 -07:00
parent 48811048f8
commit 973b16ab4b

View File

@ -1,14 +1,19 @@
//! The `Visitor` responsible for actually checking a `mir::Body` for invalid operations.
use rustc::hir::HirId;
use rustc::middle::lang_items;
use rustc::mir::visit::{PlaceContext, Visitor, MutatingUseContext, NonMutatingUseContext};
use rustc::mir::*;
use rustc::traits::{self, TraitEngine};
use rustc::ty::cast::CastTy;
use rustc::ty;
use rustc::ty::{self, TyCtxt};
use rustc_index::bit_set::BitSet;
use rustc_target::spec::abi::Abi;
use rustc_error_codes::*;
use syntax::symbol::sym;
use syntax_pos::Span;
use std::borrow::Cow;
use std::fmt;
use std::ops::Deref;
@ -222,6 +227,52 @@ impl Validator<'a, 'mir, 'tcx> {
}
}
pub fn check_body(&mut self) {
let Item { tcx, body, def_id, const_kind, .. } = *self.item;
let use_min_const_fn_checks =
tcx.is_min_const_fn(def_id)
&& !tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you;
if use_min_const_fn_checks {
// Enforce `min_const_fn` for stable `const fn`s.
use crate::transform::qualify_min_const_fn::is_min_const_fn;
if let Err((span, err)) = is_min_const_fn(tcx, def_id, body) {
error_min_const_fn_violation(tcx, span, err);
return;
}
}
check_short_circuiting_in_const_local(self.item);
// FIXME: give a span for the loop
if body.is_cfg_cyclic() {
// FIXME: make this the `emit_error` impl of `ops::Loop` once the const
// checker is no longer run in compatability mode.
if !self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
self.tcx.sess.delay_span_bug(
self.span,
"complex control flow is forbidden in a const context",
);
}
}
self.visit_body(body);
// Ensure that the end result is `Sync` in a non-thread local `static`.
let should_check_for_sync = const_kind == Some(ConstKind::Static)
&& !tcx.has_attr(def_id, sym::thread_local);
if should_check_for_sync {
let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
check_return_ty_is_sync(tcx, body, hir_id);
}
}
pub fn qualifs_in_return_place(&mut self) -> QualifSet {
self.qualifs.in_return_place(self.item)
}
pub fn take_errors(&mut self) -> Vec<(Span, String)> {
std::mem::replace(&mut self.errors, vec![])
}
@ -264,6 +315,25 @@ impl Validator<'a, 'mir, 'tcx> {
}
impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
fn visit_basic_block_data(
&mut self,
bb: BasicBlock,
block: &BasicBlockData<'tcx>,
) {
trace!("visit_basic_block_data: bb={:?} is_cleanup={:?}", bb, block.is_cleanup);
// Just as the old checker did, we skip const-checking basic blocks on the unwind path.
// These blocks often drop locals that would otherwise be returned from the function.
//
// FIXME: This shouldn't be unsound since a panic at compile time will cause a compiler
// error anyway, but maybe we should do more here?
if block.is_cleanup {
return;
}
self.super_basic_block_data(bb, block);
}
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
trace!("visit_rvalue: rvalue={:?} location={:?}", rvalue, location);
@ -608,3 +678,58 @@ impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
}
}
}
fn error_min_const_fn_violation(tcx: TyCtxt<'_>, span: Span, msg: Cow<'_, str>) {
struct_span_err!(tcx.sess, span, E0723, "{}", msg)
.note("for more information, see issue https://github.com/rust-lang/rust/issues/57563")
.help("add `#![feature(const_fn)]` to the crate attributes to enable")
.emit();
}
fn check_short_circuiting_in_const_local(item: &Item<'_, 'tcx>) {
let body = item.body;
if body.control_flow_destroyed.is_empty() {
return;
}
let mut locals = body.vars_iter();
if let Some(local) = locals.next() {
let span = body.local_decls[local].source_info.span;
let mut error = item.tcx.sess.struct_span_err(
span,
&format!(
"new features like let bindings are not permitted in {}s \
which also use short circuiting operators",
item.const_kind(),
),
);
for (span, kind) in body.control_flow_destroyed.iter() {
error.span_note(
*span,
&format!("use of {} here does not actually short circuit due to \
the const evaluator presently not being able to do control flow. \
See https://github.com/rust-lang/rust/issues/49146 for more \
information.", kind),
);
}
for local in locals {
let span = body.local_decls[local].source_info.span;
error.span_note(span, "more locals defined here");
}
error.emit();
}
}
fn check_return_ty_is_sync(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, hir_id: HirId) {
let ty = body.return_ty();
tcx.infer_ctxt().enter(|infcx| {
let cause = traits::ObligationCause::new(body.span, hir_id, traits::SharedStatic);
let mut fulfillment_cx = traits::FulfillmentContext::new();
let sync_def_id = tcx.require_lang_item(lang_items::SyncTraitLangItem, Some(body.span));
fulfillment_cx.register_bound(&infcx, ty::ParamEnv::empty(), ty, sync_def_id, cause);
if let Err(err) = fulfillment_cx.select_all_or_error(&infcx) {
infcx.report_fulfillment_errors(&err, None, false);
}
});
}