Skip to content

Commit

Permalink
Enable standalone const-checking with Validator
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
ecstatic-morse committed Nov 15, 2019
1 parent 4881104 commit 973b16a
Showing 1 changed file with 126 additions and 1 deletion.
127 changes: 126 additions & 1 deletion src/librustc_mir/transform/check_consts/validation.rs
@@ -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;

Expand Down Expand Up @@ -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![])
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
});
}

0 comments on commit 973b16a

Please sign in to comment.