diff --git a/src/librustc/mir/mod.rs b/src/librustc/mir/mod.rs index 39940dca99dc7..53ef61f157665 100644 --- a/src/librustc/mir/mod.rs +++ b/src/librustc/mir/mod.rs @@ -2769,6 +2769,17 @@ pub struct BorrowCheckResult<'tcx> { pub used_mut_upvars: SmallVec<[Field; 8]>, } +/// The result of the `mir_const_qualif` query. +/// +/// Each field corresponds to an implementer of the `Qualif` trait in +/// `librustc_mir/transform/check_consts/qualifs.rs`. See that file for more information on each +/// `Qualif`. +#[derive(Clone, Copy, Debug, Default, RustcEncodable, RustcDecodable, HashStable)] +pub struct ConstQualifs { + pub has_mut_interior: bool, + pub needs_drop: bool, +} + /// After we borrow check a closure, we are left with various /// requirements that we have inferred between the free regions that /// appear in the closure's signature or on its field types. These diff --git a/src/librustc/query/mod.rs b/src/librustc/query/mod.rs index 9bd2a933c1c69..e07726bfa2aa1 100644 --- a/src/librustc/query/mod.rs +++ b/src/librustc/query/mod.rs @@ -91,9 +91,9 @@ rustc_queries! { } /// Maps DefId's that have an associated `mir::Body` to the result - /// of the MIR qualify_consts pass. The actual meaning of - /// the value isn't known except to the pass itself. - query mir_const_qualif(key: DefId) -> u8 { + /// of the MIR const-checking pass. This is the set of qualifs in + /// the final value of a `const`. + query mir_const_qualif(key: DefId) -> mir::ConstQualifs { desc { |tcx| "const checking `{}`", tcx.def_path_str(key) } cache_on_disk_if { key.is_local() } } diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index 4a14960aa69bd..4474b008c7949 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -1373,8 +1373,6 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "describes how to render the `rendered` field of json diagnostics"), unleash_the_miri_inside_of_you: bool = (false, parse_bool, [TRACKED], "take the breaks off const evaluation. NOTE: this is unsound"), - suppress_const_validation_back_compat_ice: bool = (false, parse_bool, [TRACKED], - "silence ICE triggered when the new const validator disagrees with the old"), osx_rpath_install_name: bool = (false, parse_bool, [TRACKED], "pass `-install_name @rpath/...` to the macOS linker"), sanitizer: Option = (None, parse_sanitizer, [TRACKED], diff --git a/src/librustc_metadata/rmeta/decoder.rs b/src/librustc_metadata/rmeta/decoder.rs index 7cebf2512d645..fc0a0010240ad 100644 --- a/src/librustc_metadata/rmeta/decoder.rs +++ b/src/librustc_metadata/rmeta/decoder.rs @@ -952,12 +952,12 @@ impl<'a, 'tcx> CrateMetadata { .decode((self, tcx)) } - fn mir_const_qualif(&self, id: DefIndex) -> u8 { + fn mir_const_qualif(&self, id: DefIndex) -> mir::ConstQualifs { match self.kind(id) { EntryKind::Const(qualif, _) | EntryKind::AssocConst(AssocContainer::ImplDefault, qualif, _) | EntryKind::AssocConst(AssocContainer::ImplFinal, qualif, _) => { - qualif.mir + qualif } _ => bug!(), } diff --git a/src/librustc_metadata/rmeta/encoder.rs b/src/librustc_metadata/rmeta/encoder.rs index 26055d329bcbd..483915f654ddd 100644 --- a/src/librustc_metadata/rmeta/encoder.rs +++ b/src/librustc_metadata/rmeta/encoder.rs @@ -875,7 +875,11 @@ impl EncodeContext<'tcx> { hir::print::to_string(self.tcx.hir(), |s| s.print_trait_item(ast_item)); let rendered_const = self.lazy(RenderedConst(rendered)); - EntryKind::AssocConst(container, ConstQualif { mir: 0 }, rendered_const) + EntryKind::AssocConst( + container, + Default::default(), + rendered_const, + ) } ty::AssocKind::Method => { let fn_data = if let hir::TraitItemKind::Method(m_sig, m) = &ast_item.kind { @@ -955,10 +959,11 @@ impl EncodeContext<'tcx> { record!(self.per_def.kind[def_id] <- match impl_item.kind { ty::AssocKind::Const => { if let hir::ImplItemKind::Const(_, body_id) = ast_item.kind { - let mir = self.tcx.at(ast_item.span).mir_const_qualif(def_id); + let qualifs = self.tcx.at(ast_item.span).mir_const_qualif(def_id); - EntryKind::AssocConst(container, - ConstQualif { mir }, + EntryKind::AssocConst( + container, + qualifs, self.encode_rendered_const_for_body(body_id)) } else { bug!() @@ -1089,9 +1094,9 @@ impl EncodeContext<'tcx> { hir::ItemKind::Static(_, hir::Mutability::Mutable, _) => EntryKind::MutStatic, hir::ItemKind::Static(_, hir::Mutability::Immutable, _) => EntryKind::ImmStatic, hir::ItemKind::Const(_, body_id) => { - let mir = self.tcx.at(item.span).mir_const_qualif(def_id); + let qualifs = self.tcx.at(item.span).mir_const_qualif(def_id); EntryKind::Const( - ConstQualif { mir }, + qualifs, self.encode_rendered_const_for_body(body_id) ) } @@ -1368,9 +1373,9 @@ impl EncodeContext<'tcx> { let id = self.tcx.hir().as_local_hir_id(def_id).unwrap(); let body_id = self.tcx.hir().body_owned_by(id); let const_data = self.encode_rendered_const_for_body(body_id); - let mir = self.tcx.mir_const_qualif(def_id); + let qualifs = self.tcx.mir_const_qualif(def_id); - record!(self.per_def.kind[def_id] <- EntryKind::Const(ConstQualif { mir }, const_data)); + record!(self.per_def.kind[def_id] <- EntryKind::Const(qualifs, const_data)); record!(self.per_def.visibility[def_id] <- ty::Visibility::Public); record!(self.per_def.span[def_id] <- self.tcx.def_span(def_id)); self.encode_item_type(def_id); diff --git a/src/librustc_metadata/rmeta/mod.rs b/src/librustc_metadata/rmeta/mod.rs index 990a3d984b225..850ee5afbc808 100644 --- a/src/librustc_metadata/rmeta/mod.rs +++ b/src/librustc_metadata/rmeta/mod.rs @@ -260,7 +260,7 @@ crate struct LazyPerDefTables<'tcx> { #[derive(Copy, Clone, RustcEncodable, RustcDecodable)] enum EntryKind<'tcx> { - Const(ConstQualif, Lazy), + Const(mir::ConstQualifs, Lazy), ImmStatic, MutStatic, ForeignImmStatic, @@ -288,16 +288,10 @@ enum EntryKind<'tcx> { Method(Lazy), AssocType(AssocContainer), AssocOpaqueTy(AssocContainer), - AssocConst(AssocContainer, ConstQualif, Lazy), + AssocConst(AssocContainer, mir::ConstQualifs, Lazy), TraitAlias, } -/// Additional data for EntryKind::Const and EntryKind::AssocConst -#[derive(Clone, Copy, RustcEncodable, RustcDecodable)] -struct ConstQualif { - mir: u8, -} - /// Contains a constant which has been rendered to a String. /// Used by rustdoc. #[derive(RustcEncodable, RustcDecodable)] diff --git a/src/librustc_mir/lib.rs b/src/librustc_mir/lib.rs index 6408c896e9301..6d19cd63bc32e 100644 --- a/src/librustc_mir/lib.rs +++ b/src/librustc_mir/lib.rs @@ -32,7 +32,6 @@ Rust MIR: a lowered representation of Rust. Also: an experiment! #[macro_use] extern crate log; #[macro_use] extern crate rustc; -#[macro_use] extern crate rustc_data_structures; #[macro_use] extern crate syntax; mod borrow_check; diff --git a/src/librustc_mir/transform/check_consts/mod.rs b/src/librustc_mir/transform/check_consts/mod.rs index a5b711e75a603..4d00aaf7abc21 100644 --- a/src/librustc_mir/transform/check_consts/mod.rs +++ b/src/librustc_mir/transform/check_consts/mod.rs @@ -95,16 +95,6 @@ impl ConstKind { ConstKind::ConstFn | ConstKind::Const => false, } } - - /// Returns `true` if the value returned by this item must be `Sync`. - /// - /// This returns false for `StaticMut` since all accesses to one are `unsafe` anyway. - pub fn requires_sync(self) -> bool { - match self { - ConstKind::Static => true, - ConstKind::ConstFn | ConstKind::Const | ConstKind::StaticMut => false, - } - } } impl fmt::Display for ConstKind { diff --git a/src/librustc_mir/transform/check_consts/ops.rs b/src/librustc_mir/transform/check_consts/ops.rs index 303c3984f7c0f..80f2925193a81 100644 --- a/src/librustc_mir/transform/check_consts/ops.rs +++ b/src/librustc_mir/transform/check_consts/ops.rs @@ -138,7 +138,15 @@ impl NonConstOp for HeapAllocation { #[derive(Debug)] pub struct IfOrMatch; -impl NonConstOp for IfOrMatch {} +impl NonConstOp for IfOrMatch { + fn emit_error(&self, item: &Item<'_, '_>, span: Span) { + // This should be caught by the HIR const-checker. + item.tcx.sess.delay_span_bug( + span, + "complex control flow is forbidden in a const context", + ); + } +} #[derive(Debug)] pub struct LiveDrop; @@ -154,7 +162,15 @@ impl NonConstOp for LiveDrop { #[derive(Debug)] pub struct Loop; -impl NonConstOp for Loop {} +impl NonConstOp for Loop { + fn emit_error(&self, item: &Item<'_, '_>, span: Span) { + // This should be caught by the HIR const-checker. + item.tcx.sess.delay_span_bug( + span, + "complex control flow is forbidden in a const context", + ); + } +} #[derive(Debug)] pub struct MutBorrow(pub BorrowKind); diff --git a/src/librustc_mir/transform/check_consts/qualifs.rs b/src/librustc_mir/transform/check_consts/qualifs.rs index 595ef2aad49d9..aad14299c1d94 100644 --- a/src/librustc_mir/transform/check_consts/qualifs.rs +++ b/src/librustc_mir/transform/check_consts/qualifs.rs @@ -6,12 +6,10 @@ use syntax_pos::DUMMY_SP; use super::{ConstKind, Item as ConstCx}; -#[derive(Clone, Copy)] -pub struct QualifSet(u8); - -impl QualifSet { - fn contains(self) -> bool { - self.0 & (1 << Q::IDX) != 0 +pub fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> ConstQualifs { + ConstQualifs { + has_mut_interior: HasMutInterior::in_any_value_of_ty(cx, ty), + needs_drop: NeedsDrop::in_any_value_of_ty(cx, ty), } } @@ -22,14 +20,14 @@ impl QualifSet { /// /// The default implementations proceed structurally. pub trait Qualif { - const IDX: usize; - /// The name of the file used to debug the dataflow analysis that computes this qualif. const ANALYSIS_NAME: &'static str; /// Whether this `Qualif` is cleared when a local is moved from. const IS_CLEARED_ON_MOVE: bool = false; + fn in_qualifs(qualifs: &ConstQualifs) -> bool; + /// Return the qualification that is (conservatively) correct for any value /// of the type. fn in_any_value_of_ty(_cx: &ConstCx<'_, 'tcx>, _ty: Ty<'tcx>) -> bool; @@ -122,9 +120,8 @@ pub trait Qualif { if cx.tcx.trait_of_item(def_id).is_some() { Self::in_any_value_of_ty(cx, constant.literal.ty) } else { - let bits = cx.tcx.at(constant.span).mir_const_qualif(def_id); - - let qualif = QualifSet(bits).contains::(); + let qualifs = cx.tcx.at(constant.span).mir_const_qualif(def_id); + let qualif = Self::in_qualifs(&qualifs); // Just in case the type is more specific than // the definition, e.g., impl associated const @@ -210,9 +207,12 @@ pub trait Qualif { pub struct HasMutInterior; impl Qualif for HasMutInterior { - const IDX: usize = 0; const ANALYSIS_NAME: &'static str = "flow_has_mut_interior"; + fn in_qualifs(qualifs: &ConstQualifs) -> bool { + qualifs.has_mut_interior + } + fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool { !ty.is_freeze(cx.tcx, cx.param_env, DUMMY_SP) } @@ -275,10 +275,13 @@ impl Qualif for HasMutInterior { pub struct NeedsDrop; impl Qualif for NeedsDrop { - const IDX: usize = 1; const ANALYSIS_NAME: &'static str = "flow_needs_drop"; const IS_CLEARED_ON_MOVE: bool = true; + fn in_qualifs(qualifs: &ConstQualifs) -> bool { + qualifs.needs_drop + } + fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool { ty.needs_drop(cx.tcx, cx.param_env) } diff --git a/src/librustc_mir/transform/check_consts/validation.rs b/src/librustc_mir/transform/check_consts/validation.rs index 74b22d8e14366..21e7c9ce565f0 100644 --- a/src/librustc_mir/transform/check_consts/validation.rs +++ b/src/librustc_mir/transform/check_consts/validation.rs @@ -1,21 +1,25 @@ //! 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::fmt; +use std::borrow::Cow; use std::ops::Deref; use crate::dataflow::{self as old_dataflow, generic as dataflow}; use self::old_dataflow::IndirectlyMutableLocals; use super::ops::{self, NonConstOp}; -use super::qualifs::{HasMutInterior, NeedsDrop}; +use super::qualifs::{self, HasMutInterior, NeedsDrop}; use super::resolver::FlowSensitiveAnalysis; use super::{ConstKind, Item, Qualif, is_lang_panic_fn}; @@ -85,6 +89,19 @@ impl Qualifs<'a, 'mir, 'tcx> { || self.indirectly_mutable(local, location) } + /// Returns `true` if `local` is `HasMutInterior` at the given `Location`. + /// + /// Only updates the cursor if absolutely necessary. + fn has_mut_interior_lazy_seek(&mut self, local: Local, location: Location) -> bool { + if !self.has_mut_interior.in_any_value_of_ty.contains(local) { + return false; + } + + self.has_mut_interior.cursor.seek_before(location); + self.has_mut_interior.cursor.get().contains(local) + || self.indirectly_mutable(local, location) + } + /// Returns `true` if `local` is `HasMutInterior`, but requires the `has_mut_interior` and /// `indirectly_mutable` cursors to be updated beforehand. fn has_mut_interior_eager_seek(&self, local: Local) -> bool { @@ -95,6 +112,35 @@ impl Qualifs<'a, 'mir, 'tcx> { self.has_mut_interior.cursor.get().contains(local) || self.indirectly_mutable.get().contains(local) } + + fn in_return_place(&mut self, item: &Item<'_, 'tcx>) -> ConstQualifs { + // Find the `Return` terminator if one exists. + // + // If no `Return` terminator exists, this MIR is divergent. Just return the conservative + // qualifs for the return type. + let return_block = item.body + .basic_blocks() + .iter_enumerated() + .find(|(_, block)| { + match block.terminator().kind { + TerminatorKind::Return => true, + _ => false, + } + }) + .map(|(bb, _)| bb); + + let return_block = match return_block { + None => return qualifs::in_any_value_of_ty(item, item.body.return_ty()), + Some(bb) => bb, + }; + + let return_loc = item.body.terminator_loc(return_block); + + ConstQualifs { + needs_drop: self.needs_drop_lazy_seek(RETURN_PLACE, return_loc), + has_mut_interior: self.has_mut_interior_lazy_seek(RETURN_PLACE, return_loc), + } + } } pub struct Validator<'a, 'mir, 'tcx> { @@ -114,11 +160,6 @@ pub struct Validator<'a, 'mir, 'tcx> { /// this set is empty. Note that if we start removing locals from /// `derived_from_illegal_borrow`, just checking at the end won't be enough. derived_from_illegal_borrow: BitSet, - - errors: Vec<(Span, String)>, - - /// Whether to actually emit errors or just store them in `errors`. - pub(crate) suppress_errors: bool, } impl Deref for Validator<'_, 'mir, 'tcx> { @@ -172,21 +213,55 @@ impl Validator<'a, 'mir, 'tcx> { span: item.body.span, item, qualifs, - errors: vec![], derived_from_illegal_borrow: BitSet::new_empty(item.body.local_decls.len()), - suppress_errors: false, } } - pub fn take_errors(&mut self) -> Vec<(Span, String)> { - std::mem::replace(&mut self.errors, vec![]) + 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); + + if body.is_cfg_cyclic() { + // We can't provide a good span for the error here, but this should be caught by the + // HIR const-checker anyways. + self.check_op_spanned(ops::Loop, body.span); + } + + 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) -> ConstQualifs { + self.qualifs.in_return_place(self.item) } /// Emits an error at the given `span` if an expression cannot be evaluated in the current /// context. Returns `Forbidden` if an error was emitted. pub fn check_op_spanned(&mut self, op: O, span: Span) -> CheckOpResult where - O: NonConstOp + fmt::Debug + O: NonConstOp { trace!("check_op: op={:?}", op); @@ -204,22 +279,37 @@ impl Validator<'a, 'mir, 'tcx> { return CheckOpResult::Unleashed; } - if !self.suppress_errors { - op.emit_error(self, span); - } - - self.errors.push((span, format!("{:?}", op))); + op.emit_error(self, span); CheckOpResult::Forbidden } /// Emits an error if an expression cannot be evaluated in the current context. - pub fn check_op(&mut self, op: impl NonConstOp + fmt::Debug) -> CheckOpResult { + pub fn check_op(&mut self, op: impl NonConstOp) -> CheckOpResult { let span = self.span; self.check_op_spanned(op, span) } } 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); @@ -451,14 +541,7 @@ impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> { self.super_statement(statement, location); } StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _) => { - // FIXME: make this the `emit_error` impl of `ops::IfOrMatch` 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.check_op(ops::IfOrMatch); } // FIXME(eddyb) should these really do nothing? StatementKind::FakeRead(..) | @@ -564,3 +647,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); + } + }); +} diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index 02ed12eda67a2..897e37858a68e 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -1,8 +1,8 @@ use crate::{build, shim}; use rustc_index::vec::IndexVec; use rustc::hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; -use rustc::mir::{Body, MirPhase, Promoted}; -use rustc::ty::{TyCtxt, InstanceDef}; +use rustc::mir::{Body, MirPhase, Promoted, ConstQualifs}; +use rustc::ty::{TyCtxt, InstanceDef, TypeFoldable}; use rustc::ty::query::Providers; use rustc::ty::steal::Steal; use rustc::hir; @@ -25,7 +25,6 @@ pub mod rustc_peek; pub mod elaborate_drops; pub mod add_call_guards; pub mod promote_consts; -pub mod qualify_consts; pub mod qualify_min_const_fn; pub mod remove_noop_landing_pads; pub mod dump_mir; @@ -39,12 +38,12 @@ pub mod uniform_array_move_out; pub mod uninhabited_enum_branching; pub(crate) fn provide(providers: &mut Providers<'_>) { - self::qualify_consts::provide(providers); self::check_unsafety::provide(providers); *providers = Providers { mir_keys, mir_built, mir_const, + mir_const_qualif, mir_validated, optimized_mir, is_mir_available, @@ -185,6 +184,41 @@ pub fn run_passes( body.phase = mir_phase; } +fn mir_const_qualif(tcx: TyCtxt<'_>, def_id: DefId) -> ConstQualifs { + let const_kind = check_consts::ConstKind::for_item(tcx, def_id); + + // No need to const-check a non-const `fn`. + if const_kind.is_none() { + return Default::default(); + } + + // N.B., this `borrow()` is guaranteed to be valid (i.e., the value + // cannot yet be stolen), because `mir_validated()`, which steals + // from `mir_const(), forces this query to execute before + // performing the steal. + let body = &tcx.mir_const(def_id).borrow(); + + if body.return_ty().references_error() { + tcx.sess.delay_span_bug(body.span, "mir_const_qualif: MIR had errors"); + return Default::default(); + } + + let item = check_consts::Item { + body, + tcx, + def_id, + const_kind, + param_env: tcx.param_env(def_id), + }; + + let mut validator = check_consts::validation::Validator::new(&item); + validator.check_body(); + + // We return the qualifs in the return place for every MIR body, even though it is only used + // when deciding to promote a reference to a `const` for now. + validator.qualifs_in_return_place().into() +} + fn mir_const(tcx: TyCtxt<'_>, def_id: DefId) -> &Steal> { // Unsafety check uses the raw mir, so make sure it is run let _ = tcx.unsafety_check_result(def_id); @@ -203,18 +237,14 @@ fn mir_validated( tcx: TyCtxt<'tcx>, def_id: DefId, ) -> (&'tcx Steal>, &'tcx Steal>>) { - let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap(); - if let hir::BodyOwnerKind::Const = tcx.hir().body_owner_kind(hir_id) { - // Ensure that we compute the `mir_const_qualif` for constants at - // this point, before we steal the mir-const result. - let _ = tcx.mir_const_qualif(def_id); - } + // Ensure that we compute the `mir_const_qualif` for constants at + // this point, before we steal the mir-const result. + let _ = tcx.mir_const_qualif(def_id); let mut body = tcx.mir_const(def_id).steal(); let promote_pass = promote_consts::PromoteTemps::default(); run_passes(tcx, &mut body, InstanceDef::Item(def_id), None, MirPhase::Validated, &[ // What we need to run borrowck etc. - &qualify_consts::QualifyAndPromoteConstants::default(), &promote_pass, &simplify::SimplifyCfg::new("qualify-consts"), ]); diff --git a/src/librustc_mir/transform/promote_consts.rs b/src/librustc_mir/transform/promote_consts.rs index b4da437279db1..c79d382a37480 100644 --- a/src/librustc_mir/transform/promote_consts.rs +++ b/src/librustc_mir/transform/promote_consts.rs @@ -582,30 +582,9 @@ impl<'tcx> Validator<'_, 'tcx> { Operand::Copy(place) | Operand::Move(place) => self.validate_place(place.as_ref()), - Operand::Constant(constant) => { - if let ty::ConstKind::Unevaluated(def_id, _) = constant.literal.val { - if self.tcx.trait_of_item(def_id).is_some() { - // Don't peek inside trait associated constants. - // (see below what we do for other consts, for now) - } else { - // HACK(eddyb) ensure that errors propagate correctly. - // FIXME(eddyb) remove this once the old promotion logic - // is gone - we can always promote constants even if they - // fail to pass const-checking, as compilation would've - // errored independently and promotion can't change that. - let bits = self.tcx.at(constant.span).mir_const_qualif(def_id); - if bits == super::qualify_consts::QUALIF_ERROR_BIT { - self.tcx.sess.delay_span_bug( - constant.span, - "promote_consts: MIR had errors", - ); - return Err(Unpromotable); - } - } - } - - Ok(()) - } + // The qualifs for a constant (e.g. `HasMutInterior`) are checked in + // `validate_rvalue` upon access. + Operand::Constant(_) => Ok(()), } } diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs deleted file mode 100644 index 964efdec2b9d2..0000000000000 --- a/src/librustc_mir/transform/qualify_consts.rs +++ /dev/null @@ -1,1479 +0,0 @@ -//! A pass that qualifies constness of temporaries in constants, -//! static initializers and functions and also drives promotion. -//! -//! The Qualif flags below can be used to also provide better -//! diagnostics as to why a constant rvalue wasn't promoted. - -use rustc_index::bit_set::BitSet; -use rustc_index::vec::IndexVec; -use rustc_target::spec::abi::Abi; -use rustc::hir; -use rustc::hir::def_id::DefId; -use rustc::traits::{self, TraitEngine}; -use rustc::ty::{self, TyCtxt, Ty, TypeFoldable}; -use rustc::ty::cast::CastTy; -use rustc::ty::query::Providers; -use rustc::mir::*; -use rustc::mir::visit::{PlaceContext, Visitor, MutatingUseContext, NonMutatingUseContext}; -use rustc::middle::lang_items; -use rustc::session::config::nightly_options; -use syntax::feature_gate::{emit_feature_err, GateIssue}; -use syntax::symbol::sym; -use syntax_pos::{Span, DUMMY_SP}; - -use std::borrow::Cow; -use std::cell::Cell; -use std::fmt; -use std::ops::{Deref, Index, IndexMut}; -use std::usize; - -use rustc::hir::HirId; -use crate::transform::{MirPass, MirSource}; -use crate::transform::check_consts::ops::{self, NonConstOp}; - -use rustc_error_codes::*; - -/// What kind of item we are in. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -enum Mode { - /// A `static` item. - Static, - /// A `static mut` item. - StaticMut, - /// A `const fn` item. - ConstFn, - /// A `const` item or an anonymous constant (e.g. in array lengths). - Const, - /// Other type of `fn`. - NonConstFn, -} - -impl Mode { - /// Determine whether we have to do full const-checking because syntactically, we - /// are required to be "const". - #[inline] - fn requires_const_checking(self) -> bool { - self != Mode::NonConstFn - } -} - -impl fmt::Display for Mode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Mode::Const => write!(f, "constant"), - Mode::Static | Mode::StaticMut => write!(f, "static"), - Mode::ConstFn => write!(f, "constant function"), - Mode::NonConstFn => write!(f, "function") - } - } -} - -const QUALIF_COUNT: usize = 2; - -// FIXME(eddyb) once we can use const generics, replace this array with -// something like `IndexVec` but for fixed-size arrays (`IndexArray`?). -#[derive(Copy, Clone, Default)] -struct PerQualif([T; QUALIF_COUNT]); - -impl PerQualif { - fn new(x: T) -> Self { - PerQualif([x.clone(), x]) - } -} - -impl PerQualif { - fn as_mut(&mut self) -> PerQualif<&mut T> { - let [x0, x1] = &mut self.0; - PerQualif([x0, x1]) - } - - fn zip(self, other: PerQualif) -> PerQualif<(T, U)> { - let [x0, x1] = self.0; - let [y0, y1] = other.0; - PerQualif([(x0, y0), (x1, y1)]) - } -} - -impl PerQualif { - fn encode_to_bits(self) -> u8 { - self.0.iter().enumerate().fold(0, |bits, (i, &qualif)| { - bits | ((qualif as u8) << i) - }) - } - - fn decode_from_bits(bits: u8) -> Self { - let mut qualifs = Self::default(); - for (i, qualif) in qualifs.0.iter_mut().enumerate() { - *qualif = (bits & (1 << i)) != 0; - } - qualifs - } -} - -impl Index for PerQualif { - type Output = T; - - fn index(&self, _: Q) -> &T { - &self.0[Q::IDX] - } -} - -impl IndexMut for PerQualif { - fn index_mut(&mut self, _: Q) -> &mut T { - &mut self.0[Q::IDX] - } -} - -struct ConstCx<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - mode: Mode, - body: &'a Body<'tcx>, - - per_local: PerQualif>, -} - -impl<'a, 'tcx> ConstCx<'a, 'tcx> { - fn is_const_panic_fn(&self, def_id: DefId) -> bool { - Some(def_id) == self.tcx.lang_items().panic_fn() || - Some(def_id) == self.tcx.lang_items().begin_panic_fn() - } -} - -#[derive(Copy, Clone, Debug)] -enum ValueSource<'a, 'tcx> { - Rvalue(&'a Rvalue<'tcx>), - DropAndReplace(&'a Operand<'tcx>), - Call { - callee: &'a Operand<'tcx>, - args: &'a [Operand<'tcx>], - return_ty: Ty<'tcx>, - }, -} - -/// A "qualif"(-ication) is a way to look for something "bad" in the MIR that would disqualify some -/// code for promotion or prevent it from evaluating at compile time. So `return true` means -/// "I found something bad, no reason to go on searching". `false` is only returned if we -/// definitely cannot find anything bad anywhere. -/// -/// The default implementations proceed structurally. -trait Qualif { - const IDX: usize; - - /// Return the qualification that is (conservatively) correct for any value - /// of the type, or `None` if the qualification is not value/type-based. - fn in_any_value_of_ty(_cx: &ConstCx<'_, 'tcx>, _ty: Ty<'tcx>) -> Option { - None - } - - /// Return a mask for the qualification, given a type. This is `false` iff - /// no value of that type can have the qualification. - fn mask_for_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool { - Self::in_any_value_of_ty(cx, ty).unwrap_or(true) - } - - fn in_local(cx: &ConstCx<'_, '_>, local: Local) -> bool { - cx.per_local.0[Self::IDX].contains(local) - } - - fn in_static(_cx: &ConstCx<'_, 'tcx>, _static: &Static<'tcx>) -> bool { - // FIXME(eddyb) should we do anything here for value properties? - false - } - - fn in_projection_structurally( - cx: &ConstCx<'_, 'tcx>, - place: PlaceRef<'_, 'tcx>, - ) -> bool { - if let [proj_base @ .., elem] = place.projection { - let base_qualif = Self::in_place(cx, PlaceRef { - base: place.base, - projection: proj_base, - }); - let qualif = base_qualif && Self::mask_for_ty( - cx, - Place::ty_from(place.base, proj_base, cx.body, cx.tcx) - .projection_ty(cx.tcx, elem) - .ty, - ); - match elem { - ProjectionElem::Deref | - ProjectionElem::Subslice { .. } | - ProjectionElem::Field(..) | - ProjectionElem::ConstantIndex { .. } | - ProjectionElem::Downcast(..) => qualif, - - // FIXME(eddyb) shouldn't this be masked *after* including the - // index local? Then again, it's `usize` which is neither - // `HasMutInterior` nor `NeedsDrop`. - ProjectionElem::Index(local) => qualif || Self::in_local(cx, *local), - } - } else { - bug!("This should be called if projection is not empty"); - } - } - - fn in_projection( - cx: &ConstCx<'_, 'tcx>, - place: PlaceRef<'_, 'tcx>, - ) -> bool { - Self::in_projection_structurally(cx, place) - } - - fn in_place(cx: &ConstCx<'_, 'tcx>, place: PlaceRef<'_, 'tcx>) -> bool { - match place { - PlaceRef { - base: PlaceBase::Local(local), - projection: [], - } => Self::in_local(cx, *local), - PlaceRef { - base: PlaceBase::Static(box Static { - kind: StaticKind::Promoted(..), - .. - }), - projection: [], - } => bug!("qualifying already promoted MIR"), - PlaceRef { - base: PlaceBase::Static(static_), - projection: [], - } => { - Self::in_static(cx, static_) - }, - PlaceRef { - base: _, - projection: [.., _], - } => Self::in_projection(cx, place), - } - } - - fn in_operand(cx: &ConstCx<'_, 'tcx>, operand: &Operand<'tcx>) -> bool { - match *operand { - Operand::Copy(ref place) | - Operand::Move(ref place) => Self::in_place(cx, place.as_ref()), - - Operand::Constant(ref constant) => { - if let ty::ConstKind::Unevaluated(def_id, _) = constant.literal.val { - // Don't peek inside trait associated constants. - if cx.tcx.trait_of_item(def_id).is_some() { - Self::in_any_value_of_ty(cx, constant.literal.ty).unwrap_or(false) - } else { - let bits = cx.tcx.at(constant.span).mir_const_qualif(def_id); - - let qualif = PerQualif::decode_from_bits(bits).0[Self::IDX]; - - // Just in case the type is more specific than - // the definition, e.g., impl associated const - // with type parameters, take it into account. - qualif && Self::mask_for_ty(cx, constant.literal.ty) - } - } else { - false - } - } - } - } - - fn in_rvalue_structurally(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { - match *rvalue { - Rvalue::NullaryOp(..) => false, - - Rvalue::Discriminant(ref place) | - Rvalue::Len(ref place) => Self::in_place(cx, place.as_ref()), - - Rvalue::Use(ref operand) | - Rvalue::Repeat(ref operand, _) | - Rvalue::UnaryOp(_, ref operand) | - Rvalue::Cast(_, ref operand, _) => Self::in_operand(cx, operand), - - Rvalue::BinaryOp(_, ref lhs, ref rhs) | - Rvalue::CheckedBinaryOp(_, ref lhs, ref rhs) => { - Self::in_operand(cx, lhs) || Self::in_operand(cx, rhs) - } - - Rvalue::Ref(_, _, ref place) => { - // Special-case reborrows to be more like a copy of the reference. - if let &[ref proj_base @ .., elem] = place.projection.as_ref() { - if ProjectionElem::Deref == elem { - let base_ty = Place::ty_from(&place.base, proj_base, cx.body, cx.tcx).ty; - if let ty::Ref(..) = base_ty.kind { - return Self::in_place(cx, PlaceRef { - base: &place.base, - projection: proj_base, - }); - } - } - } - - Self::in_place(cx, place.as_ref()) - } - - Rvalue::Aggregate(_, ref operands) => { - operands.iter().any(|o| Self::in_operand(cx, o)) - } - } - } - - fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { - Self::in_rvalue_structurally(cx, rvalue) - } - - fn in_call( - cx: &ConstCx<'_, 'tcx>, - _callee: &Operand<'tcx>, - _args: &[Operand<'tcx>], - return_ty: Ty<'tcx>, - ) -> bool { - // Be conservative about the returned value of a const fn. - Self::in_any_value_of_ty(cx, return_ty).unwrap_or(false) - } - - fn in_value(cx: &ConstCx<'_, 'tcx>, source: ValueSource<'_, 'tcx>) -> bool { - match source { - ValueSource::Rvalue(rvalue) => Self::in_rvalue(cx, rvalue), - ValueSource::DropAndReplace(source) => Self::in_operand(cx, source), - ValueSource::Call { callee, args, return_ty } => { - Self::in_call(cx, callee, args, return_ty) - } - } - } -} - -/// Constant containing interior mutability (`UnsafeCell`). -/// This must be ruled out to make sure that evaluating the constant at compile-time -/// and at *any point* during the run-time would produce the same result. In particular, -/// promotion of temporaries must not change program behavior; if the promoted could be -/// written to, that would be a problem. -struct HasMutInterior; - -impl Qualif for HasMutInterior { - const IDX: usize = 0; - - fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> Option { - Some(!ty.is_freeze(cx.tcx, cx.param_env, DUMMY_SP)) - } - - fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { - match *rvalue { - // Returning `true` for `Rvalue::Ref` indicates the borrow isn't - // allowed in constants (and the `Checker` will error), and/or it - // won't be promoted, due to `&mut ...` or interior mutability. - Rvalue::Ref(_, kind, ref place) => { - let ty = place.ty(cx.body, cx.tcx).ty; - - if let BorrowKind::Mut { .. } = kind { - // In theory, any zero-sized value could be borrowed - // mutably without consequences. However, only &mut [] - // is allowed right now, and only in functions. - if cx.mode == Mode::StaticMut { - // Inside a `static mut`, &mut [...] is also allowed. - match ty.kind { - ty::Array(..) | ty::Slice(_) => {} - _ => return true, - } - } else if let ty::Array(_, len) = ty.kind { - // FIXME(eddyb) the `cx.mode == Mode::NonConstFn` condition - // seems unnecessary, given that this is merely a ZST. - match len.try_eval_usize(cx.tcx, cx.param_env) { - Some(0) if cx.mode == Mode::NonConstFn => {}, - _ => return true, - } - } else { - return true; - } - } - } - - Rvalue::Aggregate(ref kind, _) => { - if let AggregateKind::Adt(def, ..) = **kind { - if Some(def.did) == cx.tcx.lang_items().unsafe_cell_type() { - let ty = rvalue.ty(cx.body, cx.tcx); - assert_eq!(Self::in_any_value_of_ty(cx, ty), Some(true)); - return true; - } - } - } - - _ => {} - } - - Self::in_rvalue_structurally(cx, rvalue) - } -} - -/// Constant containing an ADT that implements `Drop`. -/// This must be ruled out (a) because we cannot run `Drop` during compile-time -/// as that might not be a `const fn`, and (b) because implicit promotion would -/// remove side-effects that occur as part of dropping that value. -struct NeedsDrop; - -impl Qualif for NeedsDrop { - const IDX: usize = 1; - - fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> Option { - Some(ty.needs_drop(cx.tcx, cx.param_env)) - } - - fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { - if let Rvalue::Aggregate(ref kind, _) = *rvalue { - if let AggregateKind::Adt(def, ..) = **kind { - if def.has_dtor(cx.tcx) { - return true; - } - } - } - - Self::in_rvalue_structurally(cx, rvalue) - } -} - -// Ensure the `IDX` values are sequential (`0..QUALIF_COUNT`). -macro_rules! static_assert_seq_qualifs { - ($i:expr => $first:ident $(, $rest:ident)*) => { - static_assert!({ - static_assert_seq_qualifs!($i + 1 => $($rest),*); - - $first::IDX == $i - }); - }; - ($i:expr =>) => { - static_assert!(QUALIF_COUNT == $i); - }; -} -static_assert_seq_qualifs!( - 0 => HasMutInterior, NeedsDrop -); - -impl ConstCx<'_, 'tcx> { - fn qualifs_in_any_value_of_ty(&self, ty: Ty<'tcx>) -> PerQualif { - let mut qualifs = PerQualif::default(); - qualifs[HasMutInterior] = HasMutInterior::in_any_value_of_ty(self, ty).unwrap_or(false); - qualifs[NeedsDrop] = NeedsDrop::in_any_value_of_ty(self, ty).unwrap_or(false); - qualifs - } - - fn qualifs_in_local(&self, local: Local) -> PerQualif { - let mut qualifs = PerQualif::default(); - qualifs[HasMutInterior] = HasMutInterior::in_local(self, local); - qualifs[NeedsDrop] = NeedsDrop::in_local(self, local); - qualifs - } - - fn qualifs_in_value(&self, source: ValueSource<'_, 'tcx>) -> PerQualif { - let mut qualifs = PerQualif::default(); - qualifs[HasMutInterior] = HasMutInterior::in_value(self, source); - qualifs[NeedsDrop] = NeedsDrop::in_value(self, source); - qualifs - } -} - -/// Checks MIR for being admissible as a compile-time constant, using `ConstCx` -/// for value qualifications, and accumulates writes of -/// rvalue/call results to locals, in `local_qualif`. -/// It also records candidates for promotion in `promotion_candidates`, -/// both in functions and const/static items. -struct Checker<'a, 'tcx> { - cx: ConstCx<'a, 'tcx>, - - span: Span, - def_id: DefId, - - /// If `true`, do not emit errors to the user, merely collect them in `errors`. - suppress_errors: bool, - errors: Vec<(Span, String)>, -} - -macro_rules! unleash_miri { - ($this:expr) => {{ - if $this.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { - if $this.mode.requires_const_checking() && !$this.suppress_errors { - $this.tcx.sess.span_warn($this.span, "skipping const checks"); - } - return; - } - }} -} - -impl Deref for Checker<'a, 'tcx> { - type Target = ConstCx<'a, 'tcx>; - - fn deref(&self) -> &Self::Target { - &self.cx - } -} - -impl<'a, 'tcx> Checker<'a, 'tcx> { - fn new(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>, mode: Mode) -> Self { - assert!(def_id.is_local()); - - let param_env = tcx.param_env(def_id); - - let mut cx = ConstCx { - tcx, - param_env, - mode, - body, - per_local: PerQualif::new(BitSet::new_empty(body.local_decls.len())), - }; - - for (local, decl) in body.local_decls.iter_enumerated() { - if let LocalKind::Arg = body.local_kind(local) { - let qualifs = cx.qualifs_in_any_value_of_ty(decl.ty); - for (per_local, qualif) in &mut cx.per_local.as_mut().zip(qualifs).0 { - if *qualif { - per_local.insert(local); - } - } - } - } - - Checker { - cx, - span: body.span, - def_id, - errors: vec![], - suppress_errors: false, - } - } - - // FIXME(eddyb) we could split the errors into meaningful - // categories, but enabling full miri would make that - // slightly pointless (even with feature-gating). - fn not_const(&mut self, op: impl NonConstOp) { - unleash_miri!(self); - if self.mode.requires_const_checking() && !self.suppress_errors { - self.record_error(op); - let mut err = struct_span_err!( - self.tcx.sess, - self.span, - E0019, - "{} contains unimplemented expression type", - self.mode - ); - if self.tcx.sess.teach(&err.get_code().unwrap()) { - err.note("A function call isn't allowed in the const's initialization expression \ - because the expression's value must be known at compile-time."); - err.note("Remember: you can't use a function call inside a const's initialization \ - expression! However, you can use it anywhere else."); - } - err.emit(); - } - } - - fn record_error(&mut self, op: impl NonConstOp) { - self.record_error_spanned(op, self.span); - } - - fn record_error_spanned(&mut self, op: impl NonConstOp, span: Span) { - self.errors.push((span, format!("{:?}", op))); - } - - /// Assigns an rvalue/call qualification to the given destination. - fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location: Location) { - trace!("assign: {:?} <- {:?}", dest, source); - - let mut qualifs = self.qualifs_in_value(source); - - match source { - ValueSource::Rvalue(&Rvalue::Ref(_, kind, _)) => { - // Getting `true` from `HasMutInterior::in_rvalue` means - // the borrowed place is disallowed from being borrowed, - // due to either a mutable borrow (with some exceptions), - // or an shared borrow of a value with interior mutability. - // Then `HasMutInterior` is cleared - // to avoid duplicate errors (e.g. from reborrowing). - if qualifs[HasMutInterior] { - qualifs[HasMutInterior] = false; - - debug!("suppress_errors: {}", self.suppress_errors); - if self.mode.requires_const_checking() && !self.suppress_errors { - if !self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { - self.record_error(ops::MutBorrow(kind)); - if let BorrowKind::Mut { .. } = kind { - let mut err = struct_span_err!(self.tcx.sess, self.span, E0017, - "references in {}s may only refer \ - to immutable values", self.mode); - err.span_label(self.span, format!("{}s require immutable values", - self.mode)); - if self.tcx.sess.teach(&err.get_code().unwrap()) { - err.note("References in statics and constants may only refer \ - to immutable values.\n\n\ - Statics are shared everywhere, and if they refer to \ - mutable data one might violate memory safety since \ - holding multiple mutable references to shared data \ - is not allowed.\n\n\ - If you really want global mutable state, try using \ - static mut or a global UnsafeCell."); - } - err.emit(); - } else { - span_err!(self.tcx.sess, self.span, E0492, - "cannot borrow a constant which may contain \ - interior mutability, create a static instead"); - } - } - } - } - }, - _ => {}, - } - - let mut dest_projection = &dest.projection[..]; - let index = loop { - match (&dest.base, dest_projection) { - // We treat all locals equal in constants - (&PlaceBase::Local(index), []) => break index, - // projections are transparent for assignments - // we qualify the entire destination at once, even if just a field would have - // stricter qualification - (base, [proj_base @ .., _]) => { - // Catch more errors in the destination. `visit_place` also checks various - // projection rules like union field access and raw pointer deref - let context = PlaceContext::MutatingUse(MutatingUseContext::Store); - self.visit_place_base(base, context, location); - self.visit_projection(base, dest_projection, context, location); - dest_projection = proj_base; - }, - (&PlaceBase::Static(box Static { - kind: StaticKind::Promoted(..), - .. - }), []) => bug!("promoteds don't exist yet during promotion"), - (&PlaceBase::Static(box Static{ kind: _, .. }), []) => { - // Catch more errors in the destination. `visit_place` also checks that we - // do not try to access statics from constants or try to mutate statics - let context = PlaceContext::MutatingUse(MutatingUseContext::Store); - self.visit_place_base(&dest.base, context, location); - return; - } - } - }; - - let kind = self.body.local_kind(index); - debug!("store to {:?} {:?}", kind, index); - - // this is overly restrictive, because even full assignments do not clear the qualif - // While we could special case full assignments, this would be inconsistent with - // aggregates where we overwrite all fields via assignments, which would not get - // that feature. - for (per_local, qualif) in &mut self.cx.per_local.as_mut().zip(qualifs).0 { - if *qualif { - per_local.insert(index); - } - } - } - - /// Check a whole const, static initializer or const fn. - fn check_const(&mut self) -> u8 { - use crate::transform::check_consts as new_checker; - - debug!("const-checking {} {:?}", self.mode, self.def_id); - - // FIXME: Also use the new validator when features that require it (e.g. `const_if`) are - // enabled. - let use_new_validator = self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you; - if use_new_validator { - debug!("Using dataflow-based const validator"); - } - - let item = new_checker::Item::new(self.tcx, self.def_id, self.body); - let mut validator = new_checker::validation::Validator::new(&item); - - validator.suppress_errors = !use_new_validator; - self.suppress_errors = use_new_validator; - - let body = self.body; - - let mut seen_blocks = BitSet::new_empty(body.basic_blocks().len()); - let mut bb = START_BLOCK; - loop { - seen_blocks.insert(bb.index()); - - self.visit_basic_block_data(bb, &body[bb]); - validator.visit_basic_block_data(bb, &body[bb]); - - let target = match body[bb].terminator().kind { - TerminatorKind::Goto { target } | - TerminatorKind::FalseUnwind { real_target: target, .. } | - TerminatorKind::Drop { target, .. } | - TerminatorKind::DropAndReplace { target, .. } | - TerminatorKind::Assert { target, .. } | - TerminatorKind::Call { destination: Some((_, target)), .. } => { - Some(target) - } - - // Non-terminating calls cannot produce any value. - TerminatorKind::Call { destination: None, .. } => { - break; - } - - TerminatorKind::SwitchInt {..} | - TerminatorKind::Resume | - TerminatorKind::Abort | - TerminatorKind::GeneratorDrop | - TerminatorKind::Yield { .. } | - TerminatorKind::Unreachable | - TerminatorKind::FalseEdges { .. } => None, - - TerminatorKind::Return => { - break; - } - }; - - match target { - // No loops allowed. - Some(target) if !seen_blocks.contains(target.index()) => { - bb = target; - } - _ => { - 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", - ); - } - break; - } - } - } - - // The new validation pass should agree with the old when running on simple const bodies - // (e.g. no `if` or `loop`). - if !use_new_validator { - let mut new_errors = validator.take_errors(); - - // FIXME: each checker sometimes emits the same error with the same span twice in a row. - self.errors.dedup(); - new_errors.dedup(); - - if self.errors != new_errors { - validator_mismatch( - self.tcx, - body, - std::mem::replace(&mut self.errors, vec![]), - new_errors, - ); - } - } - - self.qualifs_in_local(RETURN_PLACE).encode_to_bits() - } -} - -impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { - fn visit_place_base( - &mut self, - place_base: &PlaceBase<'tcx>, - context: PlaceContext, - location: Location, - ) { - self.super_place_base(place_base, context, location); - match place_base { - PlaceBase::Local(_) => {} - PlaceBase::Static(box Static{ kind: StaticKind::Promoted(_, _), .. }) => { - unreachable!() - } - PlaceBase::Static(box Static{ kind: StaticKind::Static, def_id, .. }) => { - if self.tcx - .get_attrs(*def_id) - .iter() - .any(|attr| attr.check_name(sym::thread_local)) { - if self.mode.requires_const_checking() && !self.suppress_errors { - self.record_error(ops::ThreadLocalAccess); - span_err!(self.tcx.sess, self.span, E0625, - "thread-local statics cannot be \ - accessed at compile-time"); - } - return; - } - - // Only allow statics (not consts) to refer to other statics. - if self.mode == Mode::Static || self.mode == Mode::StaticMut { - return; - } - unleash_miri!(self); - - if self.mode.requires_const_checking() && !self.suppress_errors { - self.record_error(ops::StaticAccess); - let mut err = struct_span_err!(self.tcx.sess, self.span, E0013, - "{}s cannot refer to statics, use \ - a constant instead", self.mode); - if self.tcx.sess.teach(&err.get_code().unwrap()) { - err.note( - "Static and const variables can refer to other const variables. \ - But a const variable cannot refer to a static variable." - ); - err.help( - "To fix this, the value can be extracted as a const and then used." - ); - } - err.emit() - } - } - } - } - - fn visit_projection_elem( - &mut self, - place_base: &PlaceBase<'tcx>, - proj_base: &[PlaceElem<'tcx>], - elem: &PlaceElem<'tcx>, - context: PlaceContext, - location: Location, - ) { - debug!( - "visit_projection_elem: place_base={:?} proj_base={:?} elem={:?} \ - context={:?} location={:?}", - place_base, - proj_base, - elem, - context, - location, - ); - - self.super_projection_elem(place_base, proj_base, elem, context, location); - - match elem { - ProjectionElem::Deref => { - if context.is_mutating_use() { - // `not_const` errors out in const contexts - self.not_const(ops::MutDeref) - } - let base_ty = Place::ty_from(place_base, proj_base, self.body, self.tcx).ty; - match self.mode { - Mode::NonConstFn => {} - _ if self.suppress_errors => {} - _ => { - if let ty::RawPtr(_) = base_ty.kind { - if !self.tcx.features().const_raw_ptr_deref { - self.record_error(ops::RawPtrDeref); - emit_feature_err( - &self.tcx.sess.parse_sess, sym::const_raw_ptr_deref, - self.span, GateIssue::Language, - &format!( - "dereferencing raw pointers in {}s is unstable", - self.mode, - ), - ); - } - } - } - } - } - - ProjectionElem::ConstantIndex {..} | - ProjectionElem::Subslice {..} | - ProjectionElem::Field(..) | - ProjectionElem::Index(_) => { - let base_ty = Place::ty_from(place_base, proj_base, self.body, self.tcx).ty; - if let Some(def) = base_ty.ty_adt_def() { - if def.is_union() { - match self.mode { - Mode::ConstFn => { - if !self.tcx.features().const_fn_union - && !self.suppress_errors - { - self.record_error(ops::UnionAccess); - emit_feature_err( - &self.tcx.sess.parse_sess, sym::const_fn_union, - self.span, GateIssue::Language, - "unions in const fn are unstable", - ); - } - }, - - | Mode::NonConstFn - | Mode::Static - | Mode::StaticMut - | Mode::Const - => {}, - } - } - } - } - - ProjectionElem::Downcast(..) => { - self.not_const(ops::Downcast) - } - } - } - - fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { - debug!("visit_operand: operand={:?} location={:?}", operand, location); - self.super_operand(operand, location); - - match *operand { - Operand::Move(ref place) => { - // Mark the consumed locals to indicate later drops are noops. - if let Some(local) = place.as_local() { - self.cx.per_local[NeedsDrop].remove(local); - } - } - Operand::Copy(_) | - Operand::Constant(_) => {} - } - } - - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { - debug!("visit_rvalue: rvalue={:?} location={:?}", rvalue, location); - - // Check nested operands and places. - if let Rvalue::Ref(_, kind, ref place) = *rvalue { - // Special-case reborrows. - let mut reborrow_place = None; - if let &[ref proj_base @ .., elem] = place.projection.as_ref() { - if elem == ProjectionElem::Deref { - let base_ty = Place::ty_from(&place.base, proj_base, self.body, self.tcx).ty; - if let ty::Ref(..) = base_ty.kind { - reborrow_place = Some(proj_base); - } - } - } - - if let Some(proj) = reborrow_place { - let ctx = match kind { - BorrowKind::Shared => PlaceContext::NonMutatingUse( - NonMutatingUseContext::SharedBorrow, - ), - BorrowKind::Shallow => PlaceContext::NonMutatingUse( - NonMutatingUseContext::ShallowBorrow, - ), - BorrowKind::Unique => PlaceContext::NonMutatingUse( - NonMutatingUseContext::UniqueBorrow, - ), - BorrowKind::Mut { .. } => PlaceContext::MutatingUse( - MutatingUseContext::Borrow, - ), - }; - self.visit_place_base(&place.base, ctx, location); - self.visit_projection(&place.base, proj, ctx, location); - } else { - self.super_rvalue(rvalue, location); - } - } else { - self.super_rvalue(rvalue, location); - } - - match *rvalue { - Rvalue::Use(_) | - Rvalue::Repeat(..) | - Rvalue::UnaryOp(UnOp::Neg, _) | - Rvalue::UnaryOp(UnOp::Not, _) | - Rvalue::NullaryOp(NullOp::SizeOf, _) | - Rvalue::CheckedBinaryOp(..) | - Rvalue::Cast(CastKind::Pointer(_), ..) | - Rvalue::Discriminant(..) | - Rvalue::Len(_) | - Rvalue::Ref(..) | - Rvalue::Aggregate(..) => {} - - Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => { - let operand_ty = operand.ty(self.body, self.tcx); - let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast"); - let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast"); - match (cast_in, cast_out) { - (CastTy::Ptr(_), CastTy::Int(_)) | - (CastTy::FnPtr, CastTy::Int(_)) if self.mode != Mode::NonConstFn => { - unleash_miri!(self); - if !self.tcx.features().const_raw_ptr_to_usize_cast - && !self.suppress_errors - { - // in const fn and constants require the feature gate - // FIXME: make it unsafe inside const fn and constants - self.record_error(ops::RawPtrToIntCast); - emit_feature_err( - &self.tcx.sess.parse_sess, sym::const_raw_ptr_to_usize_cast, - self.span, GateIssue::Language, - &format!( - "casting pointers to integers in {}s is unstable", - self.mode, - ), - ); - } - } - _ => {} - } - } - - Rvalue::BinaryOp(op, ref lhs, _) => { - if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).kind { - assert!(op == BinOp::Eq || op == BinOp::Ne || - op == BinOp::Le || op == BinOp::Lt || - op == BinOp::Ge || op == BinOp::Gt || - op == BinOp::Offset); - - unleash_miri!(self); - if self.mode.requires_const_checking() && - !self.tcx.features().const_compare_raw_pointers && - !self.suppress_errors - { - self.record_error(ops::RawPtrComparison); - // require the feature gate inside constants and const fn - // FIXME: make it unsafe to use these operations - emit_feature_err( - &self.tcx.sess.parse_sess, - sym::const_compare_raw_pointers, - self.span, - GateIssue::Language, - &format!("comparing raw pointers inside {}", self.mode), - ); - } - } - } - - Rvalue::NullaryOp(NullOp::Box, _) => { - unleash_miri!(self); - if self.mode.requires_const_checking() && !self.suppress_errors { - self.record_error(ops::HeapAllocation); - let mut err = struct_span_err!(self.tcx.sess, self.span, E0010, - "allocations are not allowed in {}s", self.mode); - err.span_label(self.span, format!("allocation not allowed in {}s", self.mode)); - if self.tcx.sess.teach(&err.get_code().unwrap()) { - err.note( - "The value of statics and constants must be known at compile time, \ - and they live for the entire lifetime of a program. Creating a boxed \ - value allocates memory on the heap at runtime, and therefore cannot \ - be done at compile time." - ); - } - err.emit(); - } - } - } - } - - fn visit_terminator_kind(&mut self, - kind: &TerminatorKind<'tcx>, - location: Location) { - debug!("visit_terminator_kind: kind={:?} location={:?}", kind, location); - if let TerminatorKind::Call { ref func, ref args, ref destination, .. } = *kind { - if let Some((ref dest, _)) = *destination { - self.assign(dest, ValueSource::Call { - callee: func, - args, - return_ty: dest.ty(self.body, self.tcx).ty, - }, location); - } - - let fn_ty = func.ty(self.body, self.tcx); - match fn_ty.kind { - ty::FnDef(def_id, _) => { - match self.tcx.fn_sig(def_id).abi() { - Abi::RustIntrinsic | - Abi::PlatformIntrinsic => { - assert!(!self.tcx.is_const_fn(def_id)); - match &*self.tcx.item_name(def_id).as_str() { - // special intrinsic that can be called diretly without an intrinsic - // feature gate needs a language feature gate - "transmute" => { - if self.mode.requires_const_checking() - && !self.suppress_errors - { - // const eval transmute calls only with the feature gate - if !self.tcx.features().const_transmute { - self.record_error(ops::Transmute); - emit_feature_err( - &self.tcx.sess.parse_sess, sym::const_transmute, - self.span, GateIssue::Language, - &format!("The use of std::mem::transmute() \ - is gated in {}s", self.mode)); - } - } - } - - // no need to check feature gates, intrinsics are only callable - // from the libstd or with forever unstable feature gates - _ => {} - } - } - _ => { - // In normal functions no calls are feature-gated. - if self.mode.requires_const_checking() { - let unleash_miri = self - .tcx - .sess - .opts - .debugging_opts - .unleash_the_miri_inside_of_you; - if self.tcx.is_const_fn(def_id) - || unleash_miri - || self.suppress_errors - { - // stable const fns or unstable const fns - // with their feature gate active - // FIXME(eddyb) move stability checks from `is_const_fn` here. - } else if self.is_const_panic_fn(def_id) { - // Check the const_panic feature gate. - // FIXME: cannot allow this inside `allow_internal_unstable` - // because that would make `panic!` insta stable in constants, - // since the macro is marked with the attribute. - if !self.tcx.features().const_panic { - // Don't allow panics in constants without the feature gate. - self.record_error(ops::Panic); - emit_feature_err( - &self.tcx.sess.parse_sess, - sym::const_panic, - self.span, - GateIssue::Language, - &format!("panicking in {}s is unstable", self.mode), - ); - } - } else if let Some(feature) - = self.tcx.is_unstable_const_fn(def_id) { - // Check `#[unstable]` const fns or `#[rustc_const_unstable]` - // functions without the feature gate active in this crate in - // order to report a better error message than the one below. - if !self.span.allows_unstable(feature) { - self.record_error(ops::FnCallUnstable(def_id, feature)); - let mut err = self.tcx.sess.struct_span_err(self.span, - &format!("`{}` is not yet stable as a const fn", - self.tcx.def_path_str(def_id))); - if nightly_options::is_nightly_build() { - help!(&mut err, - "add `#![feature({})]` to the \ - crate attributes to enable", - feature); - } - err.emit(); - } - } else { - self.record_error(ops::FnCallNonConst(def_id)); - let mut err = struct_span_err!( - self.tcx.sess, - self.span, - E0015, - "calls in {}s are limited to constant functions, \ - tuple structs and tuple variants", - self.mode, - ); - err.emit(); - } - } - } - } - } - ty::FnPtr(_) => { - unleash_miri!(self); - if self.mode.requires_const_checking() && !self.suppress_errors { - self.record_error(ops::FnCallIndirect); - let mut err = self.tcx.sess.struct_span_err( - self.span, - "function pointers are not allowed in const fn" - ); - err.emit(); - } - } - _ => { - self.not_const(ops::FnCallOther); - } - } - - // Check callee and argument operands. - self.visit_operand(func, location); - for arg in args { - self.visit_operand(arg, location); - } - } else if let TerminatorKind::Drop { - location: ref place, .. - } | TerminatorKind::DropAndReplace { - location: ref place, .. - } = *kind { - match *kind { - TerminatorKind::DropAndReplace { .. } => {} - _ => self.super_terminator_kind(kind, location), - } - - // Deny *any* live drops anywhere other than functions. - if self.mode.requires_const_checking() && !self.suppress_errors { - unleash_miri!(self); - // HACK(eddyb): emulate a bit of dataflow analysis, - // conservatively, that drop elaboration will do. - let needs_drop = if let Some(local) = place.as_local() { - if NeedsDrop::in_local(self, local) { - Some(self.body.local_decls[local].source_info.span) - } else { - None - } - } else { - Some(self.span) - }; - - if let Some(span) = needs_drop { - // Double-check the type being dropped, to minimize false positives. - let ty = place.ty(self.body, self.tcx).ty; - if ty.needs_drop(self.tcx, self.param_env) { - self.record_error_spanned(ops::LiveDrop, span); - struct_span_err!(self.tcx.sess, span, E0493, - "destructors cannot be evaluated at compile-time") - .span_label(span, format!("{}s cannot evaluate destructors", - self.mode)) - .emit(); - } - } - } - - match *kind { - TerminatorKind::DropAndReplace { ref value, .. } => { - self.assign(place, ValueSource::DropAndReplace(value), location); - self.visit_operand(value, location); - } - _ => {} - } - } else { - // Qualify any operands inside other terminators. - self.super_terminator_kind(kind, location); - } - } - - fn visit_assign(&mut self, - dest: &Place<'tcx>, - rvalue: &Rvalue<'tcx>, - location: Location) { - debug!("visit_assign: dest={:?} rvalue={:?} location={:?}", dest, rvalue, location); - self.assign(dest, ValueSource::Rvalue(rvalue), location); - - self.visit_rvalue(rvalue, location); - } - - fn visit_source_info(&mut self, source_info: &SourceInfo) { - debug!("visit_source_info: source_info={:?}", source_info); - self.span = source_info.span; - } - - fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { - debug!("visit_statement: statement={:?} location={:?}", statement, location); - match statement.kind { - StatementKind::Assign(..) => { - self.super_statement(statement, location); - } - StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _) => { - 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", - ); - } - } - // FIXME(eddyb) should these really do nothing? - StatementKind::FakeRead(..) | - StatementKind::SetDiscriminant { .. } | - StatementKind::StorageLive(_) | - StatementKind::StorageDead(_) | - StatementKind::InlineAsm {..} | - StatementKind::Retag { .. } | - StatementKind::AscribeUserType(..) | - StatementKind::Nop => {} - } - } -} - -pub fn provide(providers: &mut Providers<'_>) { - *providers = Providers { - mir_const_qualif, - ..*providers - }; -} - -// FIXME(eddyb) this is only left around for the validation logic -// in `promote_consts`, see the comment in `validate_operand`. -pub(super) const QUALIF_ERROR_BIT: u8 = 1 << 2; - -fn mir_const_qualif(tcx: TyCtxt<'_>, def_id: DefId) -> u8 { - // N.B., this `borrow()` is guaranteed to be valid (i.e., the value - // cannot yet be stolen), because `mir_validated()`, which steals - // from `mir_const(), forces this query to execute before - // performing the steal. - let body = &tcx.mir_const(def_id).borrow(); - - if body.return_ty().references_error() { - tcx.sess.delay_span_bug(body.span, "mir_const_qualif: MIR had errors"); - return QUALIF_ERROR_BIT; - } - - Checker::new(tcx, def_id, body, Mode::Const).check_const() -} - -pub struct QualifyAndPromoteConstants<'tcx> { - pub promoted: Cell>>, -} - -impl<'tcx> Default for QualifyAndPromoteConstants<'tcx> { - fn default() -> Self { - QualifyAndPromoteConstants { - promoted: Cell::new(IndexVec::new()), - } - } -} - -impl<'tcx> MirPass<'tcx> for QualifyAndPromoteConstants<'tcx> { - fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, body: &mut Body<'tcx>) { - // There's not really any point in promoting errorful MIR. - if body.return_ty().references_error() { - tcx.sess.delay_span_bug(body.span, "QualifyAndPromoteConstants: MIR had errors"); - return; - } - - if src.promoted.is_some() { - return; - } - - let def_id = src.def_id(); - let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap(); - - let mode = determine_mode(tcx, hir_id, def_id); - - debug!("run_pass: mode={:?}", mode); - if let Mode::NonConstFn = mode { - // No need to const-check a non-const `fn` now that we don't do promotion here. - return; - } else if let Mode::ConstFn = mode { - let mut checker = Checker::new(tcx, def_id, body, mode); - let use_min_const_fn_checks = - !tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you && - tcx.is_min_const_fn(def_id); - if use_min_const_fn_checks { - // Enforce `min_const_fn` for stable `const fn`s. - use super::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_const` should not produce any errors, but better safe than sorry - // FIXME(#53819) - // Enforce a constant-like CFG for `const fn`. - checker.check_const(); - } else { - check_short_circuiting_in_const_local(tcx, body, mode); - - match mode { - Mode::Const => tcx.mir_const_qualif(def_id), - _ => Checker::new(tcx, def_id, body, mode).check_const(), - }; - } - - if mode == Mode::Static && !tcx.has_attr(def_id, sym::thread_local) { - // `static`s (not `static mut`s) which are not `#[thread_local]` must be `Sync`. - check_static_is_sync(tcx, body, hir_id); - } - } -} - -fn determine_mode(tcx: TyCtxt<'_>, hir_id: HirId, def_id: DefId) -> Mode { - match tcx.hir().body_owner_kind(hir_id) { - hir::BodyOwnerKind::Closure => Mode::NonConstFn, - hir::BodyOwnerKind::Fn if tcx.is_const_fn(def_id) => Mode::ConstFn, - hir::BodyOwnerKind::Fn => Mode::NonConstFn, - hir::BodyOwnerKind::Const => Mode::Const, - hir::BodyOwnerKind::Static(hir::Mutability::Immutable) => Mode::Static, - hir::BodyOwnerKind::Static(hir::Mutability::Mutable) => Mode::StaticMut, - } -} - -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(tcx: TyCtxt<'_>, body: &mut Body<'tcx>, mode: Mode) { - 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 = tcx.sess.struct_span_err( - span, - &format!( - "new features like let bindings are not permitted in {}s \ - which also use short circuiting operators", - mode, - ), - ); - 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_static_is_sync(tcx: TyCtxt<'tcx>, body: &mut 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); - } - }); -} - -fn validator_mismatch( - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - mut old_errors: Vec<(Span, String)>, - mut new_errors: Vec<(Span, String)>, -) { - error!("old validator: {:?}", old_errors); - error!("new validator: {:?}", new_errors); - - // ICE on nightly if the validators do not emit exactly the same errors. - // Users can supress this panic with an unstable compiler flag (hopefully after - // filing an issue). - let opts = &tcx.sess.opts; - let strict_validation_enabled = opts.unstable_features.is_nightly_build() - && !opts.debugging_opts.suppress_const_validation_back_compat_ice; - - if !strict_validation_enabled { - return; - } - - // If this difference would cause a regression from the old to the new or vice versa, trigger - // the ICE. - if old_errors.is_empty() || new_errors.is_empty() { - span_bug!(body.span, "{}", VALIDATOR_MISMATCH_ERR); - } - - // HACK: Borrows that would allow mutation are forbidden in const contexts, but they cause the - // new validator to be more conservative about when a dropped local has been moved out of. - // - // Supress the mismatch ICE in cases where the validators disagree only on the number of - // `LiveDrop` errors and both observe the same sequence of `MutBorrow`s. - - let is_live_drop = |(_, s): &mut (_, String)| s.starts_with("LiveDrop"); - let is_mut_borrow = |(_, s): &&(_, String)| s.starts_with("MutBorrow"); - - let old_live_drops: Vec<_> = old_errors.drain_filter(is_live_drop).collect(); - let new_live_drops: Vec<_> = new_errors.drain_filter(is_live_drop).collect(); - - let only_live_drops_differ = old_live_drops != new_live_drops && old_errors == new_errors; - - let old_mut_borrows = old_errors.iter().filter(is_mut_borrow); - let new_mut_borrows = new_errors.iter().filter(is_mut_borrow); - - let at_least_one_mut_borrow = old_mut_borrows.clone().next().is_some(); - - if only_live_drops_differ && at_least_one_mut_borrow && old_mut_borrows.eq(new_mut_borrows) { - return; - } - - span_bug!(body.span, "{}", VALIDATOR_MISMATCH_ERR); -} - -const VALIDATOR_MISMATCH_ERR: &str = - r"Disagreement between legacy and dataflow-based const validators. - After filing an issue, use `-Zsuppress-const-validation-back-compat-ice` to compile your code."; diff --git a/src/test/compile-fail/consts/const-fn-error.rs b/src/test/compile-fail/consts/const-fn-error.rs index 4adad16a57010..1a4fc72e81786 100644 --- a/src/test/compile-fail/consts/const-fn-error.rs +++ b/src/test/compile-fail/consts/const-fn-error.rs @@ -9,6 +9,7 @@ const fn f(x: usize) -> usize { //~| ERROR E0017 //~| ERROR E0080 //~| ERROR E0744 + //~| ERROR E0019 sum += i; } sum diff --git a/src/test/compile-fail/issue-52443.rs b/src/test/compile-fail/issue-52443.rs index 04eecb5687fd9..ee37aaa5e13b9 100644 --- a/src/test/compile-fail/issue-52443.rs +++ b/src/test/compile-fail/issue-52443.rs @@ -11,4 +11,5 @@ fn main() { //~| ERROR `for` is not allowed in a `const` //~| ERROR references in constants may only refer to immutable values //~| ERROR evaluation of constant value failed + //~| ERROR constant contains unimplemented expression type } diff --git a/src/test/ui/consts/const-eval/issue-65394.rs b/src/test/ui/consts/const-eval/issue-65394.rs index 8cf527f0429f0..b1c058eac9e4b 100644 --- a/src/test/ui/consts/const-eval/issue-65394.rs +++ b/src/test/ui/consts/const-eval/issue-65394.rs @@ -1,7 +1,10 @@ -// Test for absence of validation mismatch ICE in #65394 +// This test originated from #65394. We conservatively assume that `x` is still `LiveDrop` even +// after it has been moved because a mutable reference to it exists at some point in the const body. +// +// We will likely have to change this behavior before we allow `&mut` in a `const`. const _: Vec = { - let mut x = Vec::::new(); + let mut x = Vec::::new(); //~ ERROR destructors cannot be evaluated at compile-time let r = &mut x; //~ ERROR references in constants may only refer to immutable values let y = x; y diff --git a/src/test/ui/consts/const-eval/issue-65394.stderr b/src/test/ui/consts/const-eval/issue-65394.stderr index 15df813836e5b..acf5cbaede665 100644 --- a/src/test/ui/consts/const-eval/issue-65394.stderr +++ b/src/test/ui/consts/const-eval/issue-65394.stderr @@ -1,11 +1,16 @@ error[E0017]: references in constants may only refer to immutable values - --> $DIR/issue-65394.rs:5:13 + --> $DIR/issue-65394.rs:8:13 | LL | let r = &mut x; | ^^^^^^ constants require immutable values -[ERROR rustc_mir::transform::qualify_consts] old validator: [($DIR/issue-65394.rs:5:13: 5:19, "MutBorrow(Mut { allow_two_phase_borrow: false })")] -[ERROR rustc_mir::transform::qualify_consts] new validator: [($DIR/issue-65394.rs:5:13: 5:19, "MutBorrow(Mut { allow_two_phase_borrow: false })"), ($DIR/issue-65394.rs:4:9: 4:14, "LiveDrop")] -error: aborting due to previous error +error[E0493]: destructors cannot be evaluated at compile-time + --> $DIR/issue-65394.rs:7:9 + | +LL | let mut x = Vec::::new(); + | ^^^^^ constants cannot evaluate destructors + +error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0017`. +Some errors have detailed explanations: E0017, E0493. +For more information about an error, try `rustc --explain E0017`. diff --git a/src/test/ui/consts/miri_unleashed/enum_discriminants.rs b/src/test/ui/consts/miri_unleashed/enum_discriminants.rs index d7cdb0babc5c5..76d62f069f378 100644 --- a/src/test/ui/consts/miri_unleashed/enum_discriminants.rs +++ b/src/test/ui/consts/miri_unleashed/enum_discriminants.rs @@ -1,6 +1,9 @@ -// compile-flags: -Zunleash-the-miri-inside-of-you +// compile-flags: -Zunleash-the-miri-inside-of-you -Awarnings // run-pass +// miri unleashed warnings are not useful and change frequently, so they are silenced above. +#![feature(const_panic)] + //! Make sure that we read and write enum discriminants correctly for corner cases caused //! by layout optimizations. @@ -21,7 +24,7 @@ const OVERFLOW: usize = { } let x = Foo::B; - match x { //~ WARNING skipping const checks + match x { Foo::B => 0, _ => panic!(), } @@ -86,21 +89,17 @@ const MORE_OVERFLOW: usize = { } if let E1::V2 { .. } = (E1::V1 { f: true }) { - //~^ WARNING skipping const checks unreachable!() } if let E1::V1 { .. } = (E1::V1 { f: true }) { - //~^ WARNING skipping const checks } else { unreachable!() } if let E2::V1 { .. } = E2::V3:: { - //~^ WARNING skipping const checks unreachable!() } if let E2::V3 { .. } = E2::V3:: { - //~^ WARNING skipping const checks } else { unreachable!() } diff --git a/src/test/ui/consts/miri_unleashed/enum_discriminants.stderr b/src/test/ui/consts/miri_unleashed/enum_discriminants.stderr deleted file mode 100644 index b7fce223af80a..0000000000000 --- a/src/test/ui/consts/miri_unleashed/enum_discriminants.stderr +++ /dev/null @@ -1,47 +0,0 @@ -warning: skipping const checks - --> $DIR/enum_discriminants.rs:24:5 - | -LL | / match x { -LL | | Foo::B => 0, -LL | | _ => panic!(), -LL | | } - | |_____^ - -warning: skipping const checks - --> $DIR/enum_discriminants.rs:88:5 - | -LL | / if let E1::V2 { .. } = (E1::V1 { f: true }) { -LL | | -LL | | unreachable!() -LL | | } - | |_____^ - -warning: skipping const checks - --> $DIR/enum_discriminants.rs:92:5 - | -LL | / if let E1::V1 { .. } = (E1::V1 { f: true }) { -LL | | -LL | | } else { -LL | | unreachable!() -LL | | } - | |_____^ - -warning: skipping const checks - --> $DIR/enum_discriminants.rs:98:5 - | -LL | / if let E2::V1 { .. } = E2::V3:: { -LL | | -LL | | unreachable!() -LL | | } - | |_____^ - -warning: skipping const checks - --> $DIR/enum_discriminants.rs:102:5 - | -LL | / if let E2::V3 { .. } = E2::V3:: { -LL | | -LL | | } else { -LL | | unreachable!() -LL | | } - | |_____^ -