]> git.lizzy.rs Git - rust.git/commitdiff
Enable standalone const-checking with `Validator`
authorDylan MacKenzie <ecstaticmorse@gmail.com>
Wed, 30 Oct 2019 00:19:58 +0000 (17:19 -0700)
committerDylan MacKenzie <ecstaticmorse@gmail.com>
Fri, 15 Nov 2019 18:33:32 +0000 (10:33 -0800)
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`.

src/librustc_mir/transform/check_consts/validation.rs

index 53fb0bab80f83425dda94733906d145cb46cf50d..6687618b7485949854e772a5b3ba90f9d4f715c1 100644 (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 @@ pub fn new(
         }
     }
 
+    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 @@ pub fn check_op(&mut self, op: impl NonConstOp + fmt::Debug) -> CheckOpResult {
 }
 
 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 @@ fn visit_terminator_kind(&mut self, kind: &TerminatorKind<'tcx>, location: Locat
         }
     }
 }
+
+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);
+        }
+    });
+}