From 6d4e2042f9dae20266292c45b2789962873b1d01 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 28 Oct 2019 21:25:51 -0700 Subject: [PATCH] Remove `qualify_consts` completely --- src/librustc_mir/lib.rs | 1 - src/librustc_mir/transform/mod.rs | 11 +- src/librustc_mir/transform/qualify_consts.rs | 1479 ------------------ 3 files changed, 3 insertions(+), 1488 deletions(-) delete mode 100644 src/librustc_mir/transform/qualify_consts.rs diff --git a/src/librustc_mir/lib.rs b/src/librustc_mir/lib.rs index 6408c896e93..6d19cd63bc3 100644 --- a/src/librustc_mir/lib.rs +++ b/src/librustc_mir/lib.rs @@ -32,7 +32,6 @@ #[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/mod.rs b/src/librustc_mir/transform/mod.rs index 64649e93481..5773d850fbc 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -25,7 +25,6 @@ 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; @@ -238,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/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs deleted file mode 100644 index ee5dfae6ce2..00000000000 --- 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 0; - } - - 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."; -- 2.44.0