From a5e4d1ad7dee869ff9647f1c6f16fa1e13953dd6 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Sat, 29 Sep 2018 11:56:26 +0300 Subject: [PATCH 01/10] rustc_mir: rename qualify_consts::Qualifier to Checker. --- src/librustc_mir/transform/qualify_consts.rs | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index c593be4e67f45..2cbe42721a3b1 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -94,7 +94,7 @@ impl fmt::Display for Mode { } } -struct Qualifier<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { +struct Checker<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { mode: Mode, span: Span, def_id: DefId, @@ -117,12 +117,12 @@ macro_rules! unleash_miri { }} } -impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { +impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId, mir: &'a Mir<'tcx>, mode: Mode) - -> Qualifier<'a, 'tcx, 'tcx> { + -> Checker<'a, 'tcx, 'tcx> { assert!(def_id.is_local()); let mut rpo = traversal::reverse_postorder(mir); let temps = promote_consts::collect_temps(mir, &mut rpo); @@ -137,7 +137,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { local_qualif[arg] = Some(qualif); } - Qualifier { + Checker { mode, span: mir.span, def_id, @@ -265,8 +265,8 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { } /// Qualify a whole const, static initializer or const fn. - fn qualify_const(&mut self) -> (Qualif, Lrc>) { - debug!("qualifying {} {:?}", self.mode, self.def_id); + fn check_const(&mut self) -> (Qualif, Lrc>) { + debug!("const-checking {} {:?}", self.mode, self.def_id); let mir = self.mir; @@ -358,7 +358,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { /// Accumulates an Rvalue or Call's effects in self.qualif. /// For functions (constant or not), it also records /// candidates for promotion in promotion_candidates. -impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> { +impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { fn visit_local(&mut self, &local: &Local, _: PlaceContext<'tcx>, @@ -1151,8 +1151,8 @@ fn mir_const_qualif<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, return (Qualif::NOT_CONST.bits(), Lrc::new(BitSet::new_empty(0))); } - let mut qualifier = Qualifier::new(tcx, def_id, mir, Mode::Const); - let (qualif, promoted_temps) = qualifier.qualify_const(); + let mut checker = Checker::new(tcx, def_id, mir, Mode::Const); + let (qualif, promoted_temps) = checker.check_const(); (qualif.bits(), promoted_temps) } @@ -1195,13 +1195,13 @@ impl MirPass for QualifyAndPromoteConstants { debug!("run_pass: mode={:?}", mode); if mode == Mode::Fn || mode == Mode::ConstFn { - // This is ugly because Qualifier holds onto mir, + // This is ugly because Checker holds onto mir, // which can't be mutated until its scope ends. let (temps, candidates) = { - let mut qualifier = Qualifier::new(tcx, def_id, mir, mode); + let mut checker = Checker::new(tcx, def_id, mir, mode); if mode == Mode::ConstFn { if tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { - qualifier.qualify_const(); + checker.check_const(); } else if tcx.is_min_const_fn(def_id) { // enforce `min_const_fn` for stable const fns use super::qualify_min_const_fn::is_min_const_fn; @@ -1210,19 +1210,19 @@ impl MirPass for QualifyAndPromoteConstants { } else { // this should not produce any errors, but better safe than sorry // FIXME(#53819) - qualifier.qualify_const(); + checker.check_const(); } } else { // Enforce a constant-like CFG for `const fn`. - qualifier.qualify_const(); + checker.check_const(); } } else { - while let Some((bb, data)) = qualifier.rpo.next() { - qualifier.visit_basic_block_data(bb, data); + while let Some((bb, data)) = checker.rpo.next() { + checker.visit_basic_block_data(bb, data); } } - (qualifier.temp_promotion_state, qualifier.promotion_candidates) + (checker.temp_promotion_state, checker.promotion_candidates) }; // Do the actual promotion, now that we know what's viable. @@ -1263,7 +1263,7 @@ impl MirPass for QualifyAndPromoteConstants { // Already computed by `mir_const_qualif`. const_promoted_temps.unwrap() } else { - Qualifier::new(tcx, def_id, mir, mode).qualify_const().1 + Checker::new(tcx, def_id, mir, mode).check_const().1 }; // In `const` and `static` everything without `StorageDead` From 732a2dc09577f93c5eb9e4f037f7ce6723d8d7eb Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Mon, 4 Feb 2019 23:24:20 +0200 Subject: [PATCH 02/10] rustc_mir: split off some qualify_consts::Checker fields into a State struct. --- src/librustc_mir/transform/qualify_consts.rs | 134 ++++++++++--------- 1 file changed, 73 insertions(+), 61 deletions(-) diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 2cbe42721a3b1..99b825b97a05a 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -94,18 +94,31 @@ impl fmt::Display for Mode { } } -struct Checker<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { +struct State { + local_qualif: IndexVec>, + + qualif: Qualif, +} + +impl State { + /// Add the given qualification to self.qualif. + fn add(&mut self, qualif: Qualif) { + self.qualif = self.qualif | qualif; + } +} + +struct Checker<'a, 'gcx, 'tcx> { + tcx: TyCtxt<'a, 'gcx, 'tcx>, + param_env: ty::ParamEnv<'tcx>, mode: Mode, span: Span, def_id: DefId, mir: &'a Mir<'tcx>, rpo: ReversePostorder<'a, 'tcx>, - tcx: TyCtxt<'a, 'gcx, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - local_qualif: IndexVec>, - qualif: Qualif, + + state: State, temp_promotion_state: IndexVec, - promotion_candidates: Vec + promotion_candidates: Vec, } macro_rules! unleash_miri { @@ -145,8 +158,10 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { rpo, tcx, param_env, - local_qualif, - qualif: Qualif::empty(), + state: State { + local_qualif, + qualif: Qualif::empty(), + }, temp_promotion_state: temps, promotion_candidates: vec![] } @@ -157,7 +172,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { // slightly pointless (even with feature-gating). fn not_const(&mut self) { unleash_miri!(self); - self.add(Qualif::NOT_CONST); + self.state.add(Qualif::NOT_CONST); if self.mode != Mode::Fn { let mut err = struct_span_err!( self.tcx.sess, @@ -176,31 +191,26 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { } } - /// Adds the given qualification to `self.qualif`. - fn add(&mut self, qualif: Qualif) { - self.qualif = self.qualif | qualif; - } - - /// Adds the given type's qualification to `self.qualif`. + /// Adds the given type's qualification to self.state.qualif. fn add_type(&mut self, ty: Ty<'tcx>) { - self.add(Qualif::MUTABLE_INTERIOR | Qualif::NEEDS_DROP); - self.qualif.restrict(ty, self.tcx, self.param_env); + self.state.add(Qualif::MUTABLE_INTERIOR | Qualif::NEEDS_DROP); + self.state.qualif.restrict(ty, self.tcx, self.param_env); } - /// Within the provided closure, `self.qualif` will start + /// Within the provided closure, `self.state.qualif` will start /// out empty, and its value after the closure returns will /// be combined with the value before the call to nest. fn nest(&mut self, f: F) { - let original = self.qualif; - self.qualif = Qualif::empty(); + let original = self.state.qualif; + self.state.qualif = Qualif::empty(); f(self); - self.add(original); + self.state.add(original); } /// Assign the current qualification to the given destination. fn assign(&mut self, dest: &Place<'tcx>, location: Location) { trace!("assign: {:?}", dest); - let qualif = self.qualif; + let qualif = self.state.qualif; let span = self.span; let store = |slot: &mut Option| { if slot.is_some() { @@ -215,7 +225,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { if self.mir.local_kind(index) == LocalKind::Temp && self.temp_promotion_state[index].is_promotable() { debug!("store to promotable temp {:?} ({:?})", index, qualif); - store(&mut self.local_qualif[index]); + store(&mut self.state.local_qualif[index]); } } return; @@ -253,14 +263,14 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { } }; debug!("store to var {:?}", index); - match &mut self.local_qualif[index] { + match &mut self.state.local_qualif[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. - Some(ref mut qualif) => *qualif = *qualif | self.qualif, + Some(ref mut qualif) => *qualif = *qualif | self.state.qualif, // insert new qualification - qualif @ None => *qualif = Some(self.qualif), + qualif @ None => *qualif = Some(self.state.qualif), } } @@ -317,12 +327,12 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { } } - self.qualif = self.local_qualif[RETURN_PLACE].unwrap_or(Qualif::NOT_CONST); + self.state.qualif = self.state.local_qualif[RETURN_PLACE].unwrap_or(Qualif::NOT_CONST); // Account for errors in consts by using the // conservative type qualification instead. - if self.qualif.intersects(Qualif::CONST_ERROR) { - self.qualif = Qualif::empty(); + if self.state.qualif.intersects(Qualif::CONST_ERROR) { + self.state.qualif = Qualif::empty(); let return_ty = mir.return_ty(); self.add_type(return_ty); } @@ -346,7 +356,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { } } - (self.qualif, Lrc::new(promoted_temps)) + (self.state.qualif, Lrc::new(promoted_temps)) } fn is_const_panic_fn(&self, def_id: DefId) -> bool { @@ -355,7 +365,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { } } -/// Accumulates an Rvalue or Call's effects in self.qualif. +/// Accumulates an Rvalue or Call's effects in self.state.qualif. /// For functions (constant or not), it also records /// candidates for promotion in promotion_candidates. impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { @@ -370,22 +380,22 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { self.not_const(); } LocalKind::Var if self.mode == Mode::Fn => { - self.add(Qualif::NOT_CONST); + self.state.add(Qualif::NOT_CONST); } LocalKind::Var | LocalKind::Arg | LocalKind::Temp => { if let LocalKind::Arg = kind { - self.add(Qualif::FN_ARGUMENT); + self.state.add(Qualif::FN_ARGUMENT); } if !self.temp_promotion_state[local].is_promotable() { debug!("visit_local: (not promotable) local={:?}", local); - self.add(Qualif::NOT_PROMOTABLE); + self.state.add(Qualif::NOT_PROMOTABLE); } - if let Some(qualif) = self.local_qualif[local] { - self.add(qualif); + if let Some(qualif) = self.state.local_qualif[local] { + self.state.add(qualif); } else { self.not_const(); } @@ -411,7 +421,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { "thread-local statics cannot be \ accessed at compile-time"); } - self.add(Qualif::NOT_CONST); + self.state.add(Qualif::NOT_CONST); return; } @@ -430,7 +440,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { return; } unleash_miri!(self); - self.add(Qualif::NOT_CONST); + self.state.add(Qualif::NOT_CONST); if self.mode != Mode::Fn { let mut err = struct_span_err!(self.tcx.sess, self.span, E0013, @@ -458,7 +468,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { this.not_const() } else { // just make sure this doesn't get promoted - this.add(Qualif::NOT_CONST); + this.state.add(Qualif::NOT_CONST); } let base_ty = proj.base.ty(this.mir, this.tcx).to_ty(this.tcx); match this.mode { @@ -508,7 +518,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { } let ty = place.ty(this.mir, this.tcx).to_ty(this.tcx); - this.qualif.restrict(ty, this.tcx, this.param_env); + this.state.qualif.restrict(ty, this.tcx, this.param_env); } ProjectionElem::Downcast(..) => { @@ -529,7 +539,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { Operand::Move(_) => { // Mark the consumed locals to indicate later drops are noops. if let Operand::Move(Place::Local(local)) = *operand { - self.local_qualif[local] = self.local_qualif[local].map(|q| + self.state.local_qualif[local] = self.state.local_qualif[local].map(|q| q - Qualif::NEEDS_DROP ); } @@ -543,12 +553,12 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { let (bits, _) = self.tcx.at(constant.span).mir_const_qualif(*def_id); let qualif = Qualif::from_bits(bits).expect("invalid mir_const_qualif"); - self.add(qualif); + self.state.add(qualif); // Just in case the type is more specific than // the definition, e.g., impl associated const // with type parameters, take it into account. - self.qualif.restrict(constant.ty, self.tcx, self.param_env); + self.state.qualif.restrict(constant.ty, self.tcx, self.param_env); } } } @@ -630,7 +640,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { if forbidden_mut { unleash_miri!(self); - self.add(Qualif::NOT_CONST); + self.state.add(Qualif::NOT_CONST); if self.mode != Mode::Fn { let mut err = struct_span_err!(self.tcx.sess, self.span, E0017, "references in {}s may only refer \ @@ -654,11 +664,11 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { // Constants cannot be borrowed if they contain interior mutability as // it means that our "silent insertion of statics" could change // initializer values (very bad). - if self.qualif.contains(Qualif::MUTABLE_INTERIOR) { + if self.state.qualif.contains(Qualif::MUTABLE_INTERIOR) { // A reference of a MUTABLE_INTERIOR place is instead // NOT_CONST (see `if forbidden_mut` below), to avoid // duplicate errors (from reborrowing, for example). - self.qualif = self.qualif - Qualif::MUTABLE_INTERIOR; + self.state.qualif = self.state.qualif - Qualif::MUTABLE_INTERIOR; if self.mode != Mode::Fn { span_err!(self.tcx.sess, self.span, E0492, "cannot borrow a constant which may contain \ @@ -673,7 +683,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { debug!("visit_rvalue: forbidden_mut={:?}", forbidden_mut); if forbidden_mut { unleash_miri!(self); - self.add(Qualif::NOT_CONST); + self.state.add(Qualif::NOT_CONST); } else { // We might have a candidate for promotion. let candidate = Candidate::Ref(location); @@ -689,7 +699,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { if let Place::Local(local) = *place { if self.mir.local_kind(local) == LocalKind::Temp { debug!("visit_rvalue: local={:?}", local); - if let Some(qualif) = self.local_qualif[local] { + if let Some(qualif) = self.state.local_qualif[local] { // `forbidden_mut` is false, so we can safely ignore // `MUTABLE_INTERIOR` from the local's qualifications. // This allows borrowing fields which don't have @@ -716,7 +726,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { unleash_miri!(self); if let Mode::Fn = self.mode { // in normal functions, mark such casts as not promotable - self.add(Qualif::NOT_CONST); + self.state.add(Qualif::NOT_CONST); } else if !self.tcx.features().const_raw_ptr_to_usize_cast { // in const fn and constants require the feature gate // FIXME: make it unsafe inside const fn and constants @@ -744,7 +754,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { unleash_miri!(self); if let Mode::Fn = self.mode { // raw pointer operations are not allowed inside promoteds - self.add(Qualif::NOT_CONST); + self.state.add(Qualif::NOT_CONST); } else if !self.tcx.features().const_compare_raw_pointers { // require the feature gate inside constants and const fn // FIXME: make it unsafe to use these operations @@ -761,7 +771,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { Rvalue::NullaryOp(NullOp::Box, _) => { unleash_miri!(self); - self.add(Qualif::NOT_CONST); + self.state.add(Qualif::NOT_CONST); if self.mode != Mode::Fn { let mut err = struct_span_err!(self.tcx.sess, self.span, E0010, "allocations are not allowed in {}s", self.mode); @@ -781,13 +791,13 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { Rvalue::Aggregate(ref kind, _) => { if let AggregateKind::Adt(def, ..) = **kind { if def.has_dtor(self.tcx) { - self.add(Qualif::NEEDS_DROP); + self.state.add(Qualif::NEEDS_DROP); } if Some(def.did) == self.tcx.lang_items().unsafe_cell_type() { let ty = rvalue.ty(self.mir, self.tcx); self.add_type(ty); - assert!(self.qualif.contains(Qualif::MUTABLE_INTERIOR)); + assert!(self.state.qualif.contains(Qualif::MUTABLE_INTERIOR)); } } } @@ -983,7 +993,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { } let candidate = Candidate::Argument { bb, index: i }; if is_shuffle && i == 2 { - if this.qualif.is_empty() { + if this.state.qualif.is_empty() { debug!("visit_terminator_kind: candidate={:?}", candidate); this.promotion_candidates.push(candidate); } else { @@ -1010,7 +1020,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { // which happens even without the user requesting it. // We can error out with a hard error if the argument is not // constant here. - if (this.qualif - Qualif::NOT_PROMOTABLE).is_empty() { + if (this.state.qualif - Qualif::NOT_PROMOTABLE).is_empty() { debug!("visit_terminator_kind: candidate={:?}", candidate); this.promotion_candidates.push(candidate); } else { @@ -1023,7 +1033,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { // non-const fn calls if !is_const_fn { - self.qualif = Qualif::NOT_CONST; + self.state.qualif = Qualif::NOT_CONST; if self.mode != Mode::Fn { self.tcx.sess.delay_span_bug( self.span, @@ -1034,16 +1044,16 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { if let Some((ref dest, _)) = *destination { // Avoid propagating irrelevant callee/argument qualifications. - if self.qualif.intersects(Qualif::CONST_ERROR) { - self.qualif = Qualif::NOT_CONST; + if self.state.qualif.intersects(Qualif::CONST_ERROR) { + self.state.qualif = Qualif::NOT_CONST; } else { // Be conservative about the returned value of a const fn. let tcx = self.tcx; let ty = dest.ty(self.mir, tcx).to_ty(tcx); if is_const_fn && !is_promotable_const_fn && self.mode == Mode::Fn { - self.qualif = Qualif::NOT_PROMOTABLE; + self.state.qualif = Qualif::NOT_PROMOTABLE; } else { - self.qualif = Qualif::empty(); + self.state.qualif = Qualif::empty(); } self.add_type(ty); } @@ -1058,7 +1068,9 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { // HACK(eddyb): emulate a bit of dataflow analysis, // conservatively, that drop elaboration will do. let needs_drop = if let Place::Local(local) = *place { - if self.local_qualif[local].map_or(true, |q| q.contains(Qualif::NEEDS_DROP)) { + let local_needs_drop = self.state.local_qualif[local] + .map_or(true, |q| q.contains(Qualif::NEEDS_DROP)); + if local_needs_drop { Some(self.mir.local_decls[local].source_info.span) } else { None From f6d1e9790c2d6d8922dc25d03817b93893bfcff8 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Tue, 5 Feb 2019 10:37:28 +0200 Subject: [PATCH 03/10] rustc_mir: split qualify_consts' checking and value qualification. --- src/librustc_mir/transform/qualify_consts.rs | 1059 ++++++++++------- src/test/ui/error-codes/E0017.nll.stderr | 8 +- src/test/ui/error-codes/E0017.stderr | 8 +- src/test/ui/error-codes/E0388.nll.stderr | 8 +- src/test/ui/error-codes/E0388.stderr | 8 +- .../issue-17718-const-bad-values.stderr | 8 +- 6 files changed, 628 insertions(+), 471 deletions(-) diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 99b825b97a05a..e1d0ead130c02 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -60,14 +60,29 @@ bitflags::bitflags! { } impl<'a, 'tcx> Qualif { + /// Compute the qualifications for the given type. + fn for_ty(ty: Ty<'tcx>, + tcx: TyCtxt<'a, 'tcx, 'tcx>, + param_env: ty::ParamEnv<'tcx>) -> Self { + let mut qualif = Self::empty(); + if !ty.is_freeze(tcx, param_env, DUMMY_SP) { + qualif = qualif | Qualif::MUTABLE_INTERIOR; + } + if ty.needs_drop(tcx, param_env) { + qualif = qualif | Qualif::NEEDS_DROP; + } + qualif + } + /// Remove flags which are impossible for the given type. fn restrict(&mut self, ty: Ty<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>) { - if ty.is_freeze(tcx, param_env, DUMMY_SP) { + let ty_qualif = Self::for_ty(ty, tcx, param_env); + if !ty_qualif.contains(Qualif::MUTABLE_INTERIOR) { *self = *self - Qualif::MUTABLE_INTERIOR; } - if !ty.needs_drop(tcx, param_env) { + if !ty_qualif.contains(Qualif::NEEDS_DROP) { *self = *self - Qualif::NEEDS_DROP; } } @@ -94,21 +109,359 @@ impl fmt::Display for Mode { } } -struct State { - local_qualif: IndexVec>, +struct Qualifier<'a, 'tcx> { + tcx: TyCtxt<'a, 'tcx, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + mode: Mode, + mir: &'a Mir<'tcx>, - qualif: Qualif, + local_qualif: &'a IndexVec>, + temp_promotion_state: &'a IndexVec, } -impl State { - /// Add the given qualification to self.qualif. - fn add(&mut self, qualif: Qualif) { - self.qualif = self.qualif | qualif; +impl<'a, 'tcx> Qualifier<'a, 'tcx> { + fn qualif_for_ty(&self, ty: Ty<'tcx>) -> Qualif { + Qualif::for_ty(ty, self.tcx, self.param_env) + } + + fn qualify_local(&self, local: Local) -> Qualif { + let kind = self.mir.local_kind(local); + match kind { + LocalKind::ReturnPointer => Qualif::NOT_CONST, + LocalKind::Var if self.mode == Mode::Fn => Qualif::NOT_CONST, + + LocalKind::Var | + LocalKind::Arg | + LocalKind::Temp => { + let mut qualif = self.local_qualif[local] + .unwrap_or(Qualif::NOT_CONST); + + if let LocalKind::Arg = kind { + qualif = qualif | Qualif::FN_ARGUMENT; + } + + if !self.temp_promotion_state[local].is_promotable() { + qualif = qualif | Qualif::NOT_PROMOTABLE; + } + + qualif + } + } + } + + fn qualify_projection_elem(&self, proj: &PlaceElem<'tcx>) -> Qualif { + match *proj { + ProjectionElem::Deref | + ProjectionElem::Subslice { .. } | + ProjectionElem::Field(..) | + ProjectionElem::ConstantIndex { .. } | + ProjectionElem::Downcast(..) => Qualif::empty(), + + ProjectionElem::Index(local) => self.qualify_local(local), + } + } + + fn qualify_place(&self, place: &Place<'tcx>) -> Qualif { + match *place { + Place::Local(local) => self.qualify_local(local), + Place::Promoted(_) => bug!("qualifying already promoted MIR"), + Place::Static(ref global) => { + if self.tcx + .get_attrs(global.def_id) + .iter() + .any(|attr| attr.check_name("thread_local")) { + return Qualif::NOT_CONST; + } + + // Only allow statics (not consts) to refer to other statics. + if self.mode == Mode::Static || self.mode == Mode::StaticMut { + Qualif::empty() + } else { + Qualif::NOT_CONST + } + } + Place::Projection(ref proj) => { + let mut qualif = + self.qualify_place(&proj.base) | + self.qualify_projection_elem(&proj.elem); + match proj.elem { + ProjectionElem::Deref | + ProjectionElem::Downcast(..) => qualif | Qualif::NOT_CONST, + + ProjectionElem::ConstantIndex {..} | + ProjectionElem::Subslice {..} | + ProjectionElem::Field(..) | + ProjectionElem::Index(_) => { + let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx); + if let Some(def) = base_ty.ty_adt_def() { + if def.is_union() { + match self.mode { + Mode::Fn => qualif = qualif | Qualif::NOT_CONST, + + Mode::ConstFn | + Mode::Static | + Mode::StaticMut | + Mode::Const => {} + } + } + } + + let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx); + qualif.restrict(ty, self.tcx, self.param_env); + qualif + } + } + } + } + } + + fn qualify_operand(&self, operand: &Operand<'tcx>) -> Qualif { + match *operand { + Operand::Copy(ref place) | + Operand::Move(ref place) => self.qualify_place(place), + + Operand::Constant(ref constant) => { + if let ty::LazyConst::Unevaluated(def_id, _) = constant.literal { + // Don't peek inside trait associated constants. + if self.tcx.trait_of_item(*def_id).is_some() { + self.qualif_for_ty(constant.ty) + } else { + let (bits, _) = self.tcx.at(constant.span).mir_const_qualif(*def_id); + + let mut qualif = Qualif::from_bits(bits).expect("invalid mir_const_qualif"); + + // Just in case the type is more specific than + // the definition, e.g., impl associated const + // with type parameters, take it into account. + qualif.restrict(constant.ty, self.tcx, self.param_env); + qualif + } + } else { + Qualif::empty() + } + } + } + } + + fn qualify_rvalue(&self, rvalue: &Rvalue<'tcx>) -> Qualif { + match *rvalue { + Rvalue::NullaryOp(NullOp::SizeOf, _) => Qualif::empty(), + + Rvalue::Use(ref operand) | + Rvalue::Repeat(ref operand, _) | + Rvalue::UnaryOp(UnOp::Neg, ref operand) | + Rvalue::UnaryOp(UnOp::Not, ref operand) | + Rvalue::Cast(CastKind::ReifyFnPointer, ref operand, _) | + Rvalue::Cast(CastKind::UnsafeFnPointer, ref operand, _) | + Rvalue::Cast(CastKind::ClosureFnPointer, ref operand, _) | + Rvalue::Cast(CastKind::Unsize, ref operand, _) => { + self.qualify_operand(operand) + } + + Rvalue::CheckedBinaryOp(_, ref lhs, ref rhs) => { + self.qualify_operand(lhs) | self.qualify_operand(rhs) + } + + Rvalue::Discriminant(ref place) | + Rvalue::Len(ref place) => self.qualify_place(place), + + Rvalue::Ref(_, kind, ref place) => { + let mut reborrow_qualif = None; + if let Place::Projection(ref proj) = *place { + if let ProjectionElem::Deref = proj.elem { + let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx); + if let ty::Ref(..) = base_ty.sty { + reborrow_qualif = Some(self.qualify_place(&proj.base)); + } + } + } + + let mut qualif = reborrow_qualif.unwrap_or_else(|| { + self.qualify_place(place) + }); + + let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx); + + 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. + let allowed = if self.mode == Mode::StaticMut { + // Inside a `static mut`, &mut [...] is also allowed. + match ty.sty { + ty::Array(..) | ty::Slice(_) => true, + _ => false + } + } else if let ty::Array(_, len) = ty.sty { + // FIXME(eddyb) the `self.mode == Mode::Fn` condition + // seems unnecessary, given that this is merely a ZST. + len.unwrap_usize(self.tcx) == 0 && self.mode == Mode::Fn + } else { + false + }; + + if !allowed { + qualif = qualif | Qualif::MUTABLE_INTERIOR; + } + } + + qualif + } + + Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => { + let mut qualif = self.qualify_operand(operand); + + let operand_ty = operand.ty(self.mir, 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 let Mode::Fn = self.mode { + // in normal functions, mark such casts as not promotable + qualif = qualif | Qualif::NOT_CONST; + } + } + _ => {} + } + + qualif + } + + Rvalue::BinaryOp(op, ref lhs, ref rhs) => { + let mut qualif = self.qualify_operand(lhs) | self.qualify_operand(rhs); + + if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.mir, self.tcx).sty { + assert!(op == BinOp::Eq || op == BinOp::Ne || + op == BinOp::Le || op == BinOp::Lt || + op == BinOp::Ge || op == BinOp::Gt || + op == BinOp::Offset); + + if let Mode::Fn = self.mode { + // raw pointer operations are not allowed inside promoteds + qualif = qualif | Qualif::NOT_CONST; + } + } + + qualif + } + + Rvalue::NullaryOp(NullOp::Box, _) => Qualif::NOT_CONST, + + Rvalue::Aggregate(ref kind, ref operands) => { + let mut qualif = operands.iter().map(|o| self.qualify_operand(o)) + .fold(Qualif::empty(), |a, b| a | b); + + if let AggregateKind::Adt(def, ..) = **kind { + if Some(def.did) == self.tcx.lang_items().unsafe_cell_type() { + let ty = rvalue.ty(self.mir, self.tcx); + qualif = qualif | self.qualif_for_ty(ty); + assert!(qualif.contains(Qualif::MUTABLE_INTERIOR)); + } + + if def.has_dtor(self.tcx) { + qualif = qualif | Qualif::NEEDS_DROP; + } + } + + qualif + } + } + } + + 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() + } + + fn qualify_call( + &self, + callee: &Operand<'tcx>, + args: &[Operand<'tcx>], + return_ty: Ty<'tcx>, + ) -> Qualif { + let fn_ty = callee.ty(self.mir, self.tcx); + let mut is_promotable_const_fn = false; + let is_const_fn = match fn_ty.sty { + 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()[..] { + | "size_of" + | "min_align_of" + | "needs_drop" + | "type_id" + | "bswap" + | "bitreverse" + | "ctpop" + | "cttz" + | "cttz_nonzero" + | "ctlz" + | "ctlz_nonzero" + | "overflowing_add" + | "overflowing_sub" + | "overflowing_mul" + | "unchecked_shl" + | "unchecked_shr" + | "rotate_left" + | "rotate_right" + | "add_with_overflow" + | "sub_with_overflow" + | "mul_with_overflow" + | "saturating_add" + | "saturating_sub" + | "transmute" + => true, + + _ => false, + } + } + _ => { + // Never promote runtime `const fn` calls of + // functions without `#[rustc_promotable]`. + if self.tcx.is_promotable_const_fn(def_id) { + is_promotable_const_fn = true; + } + + if self.mode == Mode::Fn { + self.tcx.is_const_fn(def_id) + } else { + self.tcx.is_const_fn(def_id) || + self.is_const_panic_fn(def_id) || + self.tcx.is_unstable_const_fn(def_id).is_some() + } + } + } + } + _ => false, + }; + + // Bail out on oon-`const fn` calls or if the callee had errors. + if !is_const_fn || self.qualify_operand(callee).intersects(Qualif::CONST_ERROR) { + return Qualif::NOT_CONST; + } + + // Bail out if any arguments had errors. + for arg in args { + if self.qualify_operand(arg).intersects(Qualif::CONST_ERROR) { + return Qualif::NOT_CONST; + } + } + + // Be conservative about the returned value of a const fn. + let qualif = self.qualif_for_ty(return_ty); + if !is_promotable_const_fn && self.mode == Mode::Fn { + qualif | Qualif::NOT_PROMOTABLE + } else { + qualif + } } } -struct Checker<'a, 'gcx, 'tcx> { - tcx: TyCtxt<'a, 'gcx, 'tcx>, +struct Checker<'a, 'tcx> { + tcx: TyCtxt<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>, mode: Mode, span: Span, @@ -116,7 +469,7 @@ struct Checker<'a, 'gcx, 'tcx> { mir: &'a Mir<'tcx>, rpo: ReversePostorder<'a, 'tcx>, - state: State, + local_qualif: IndexVec>, temp_promotion_state: IndexVec, promotion_candidates: Vec, } @@ -130,12 +483,12 @@ macro_rules! unleash_miri { }} } -impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { +impl<'a, 'tcx> Checker<'a, 'tcx> { fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId, mir: &'a Mir<'tcx>, mode: Mode) - -> Checker<'a, 'tcx, 'tcx> { + -> Self { assert!(def_id.is_local()); let mut rpo = traversal::reverse_postorder(mir); let temps = promote_consts::collect_temps(mir, &mut rpo); @@ -158,21 +511,28 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { rpo, tcx, param_env, - state: State { - local_qualif, - qualif: Qualif::empty(), - }, + local_qualif, temp_promotion_state: temps, promotion_candidates: vec![] } } + fn qualifier(&'a self) -> Qualifier<'a, 'tcx> { + Qualifier { + tcx: self.tcx, + param_env: self.param_env, + mode: self.mode, + mir: self.mir, + local_qualif: &self.local_qualif, + temp_promotion_state: &self.temp_promotion_state, + } + } + // 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) { unleash_miri!(self); - self.state.add(Qualif::NOT_CONST); if self.mode != Mode::Fn { let mut err = struct_span_err!( self.tcx.sess, @@ -191,33 +551,9 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { } } - /// Adds the given type's qualification to self.state.qualif. - fn add_type(&mut self, ty: Ty<'tcx>) { - self.state.add(Qualif::MUTABLE_INTERIOR | Qualif::NEEDS_DROP); - self.state.qualif.restrict(ty, self.tcx, self.param_env); - } - - /// Within the provided closure, `self.state.qualif` will start - /// out empty, and its value after the closure returns will - /// be combined with the value before the call to nest. - fn nest(&mut self, f: F) { - let original = self.state.qualif; - self.state.qualif = Qualif::empty(); - f(self); - self.state.add(original); - } - - /// Assign the current qualification to the given destination. - fn assign(&mut self, dest: &Place<'tcx>, location: Location) { - trace!("assign: {:?}", dest); - let qualif = self.state.qualif; - let span = self.span; - let store = |slot: &mut Option| { - if slot.is_some() { - span_bug!(span, "multiple assignments to {:?}", dest); - } - *slot = Some(qualif); - }; + /// Assigns an rvalue/call qualification to the given destination. + fn assign(&mut self, dest: &Place<'tcx>, qualif: Qualif, location: Location) { + trace!("assign: {:?} <- {:?}", dest, qualif); // Only handle promotable temps in non-const functions. if self.mode == Mode::Fn { @@ -225,7 +561,11 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { if self.mir.local_kind(index) == LocalKind::Temp && self.temp_promotion_state[index].is_promotable() { debug!("store to promotable temp {:?} ({:?})", index, qualif); - store(&mut self.state.local_qualif[index]); + let slot = &mut self.local_qualif[index]; + if slot.is_some() { + span_bug!(self.span, "multiple assignments to {:?}", dest); + } + *slot = Some(qualif); } } return; @@ -263,18 +603,15 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { } }; debug!("store to var {:?}", index); - match &mut self.state.local_qualif[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. - Some(ref mut qualif) => *qualif = *qualif | self.state.qualif, - // insert new qualification - qualif @ None => *qualif = Some(self.state.qualif), - } + // 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. + let slot = &mut self.local_qualif[index]; + *slot = Some(slot.unwrap_or(Qualif::empty()) | qualif); } - /// Qualify a whole const, static initializer or const fn. + /// Check a whole const, static initializer or const fn. fn check_const(&mut self) -> (Qualif, Lrc>) { debug!("const-checking {} {:?}", self.mode, self.def_id); @@ -327,14 +664,12 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { } } - self.state.qualif = self.state.local_qualif[RETURN_PLACE].unwrap_or(Qualif::NOT_CONST); + let mut qualif = self.local_qualif[RETURN_PLACE].unwrap_or(Qualif::NOT_CONST); // Account for errors in consts by using the // conservative type qualification instead. - if self.state.qualif.intersects(Qualif::CONST_ERROR) { - self.state.qualif = Qualif::empty(); - let return_ty = mir.return_ty(); - self.add_type(return_ty); + if qualif.intersects(Qualif::CONST_ERROR) { + qualif = self.qualifier().qualif_for_ty(mir.return_ty()); } @@ -356,50 +691,23 @@ impl<'a, 'tcx> Checker<'a, 'tcx, 'tcx> { } } - (self.state.qualif, Lrc::new(promoted_temps)) - } - - 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() + (qualif, Lrc::new(promoted_temps)) } } -/// Accumulates an Rvalue or Call's effects in self.state.qualif. +/// Checks MIR for const-correctness, using `Qualifier` +/// for value qualifications, and accumulates writes of +/// rvalue/call results to locals, in `local_qualif`. /// For functions (constant or not), it also records -/// candidates for promotion in promotion_candidates. -impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { +/// candidates for promotion in `promotion_candidates`. +impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { fn visit_local(&mut self, &local: &Local, _: PlaceContext<'tcx>, _: Location) { debug!("visit_local: local={:?}", local); - let kind = self.mir.local_kind(local); - match kind { - LocalKind::ReturnPointer => { - self.not_const(); - } - LocalKind::Var if self.mode == Mode::Fn => { - self.state.add(Qualif::NOT_CONST); - } - LocalKind::Var | - LocalKind::Arg | - LocalKind::Temp => { - if let LocalKind::Arg = kind { - self.state.add(Qualif::FN_ARGUMENT); - } - - if !self.temp_promotion_state[local].is_promotable() { - debug!("visit_local: (not promotable) local={:?}", local); - self.state.add(Qualif::NOT_PROMOTABLE); - } - - if let Some(qualif) = self.state.local_qualif[local] { - self.state.add(qualif); - } else { - self.not_const(); - } - } + if self.local_qualif[local].is_none() { + self.not_const(); } } @@ -408,9 +716,10 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { context: PlaceContext<'tcx>, location: Location) { debug!("visit_place: place={:?} context={:?} location={:?}", place, context, location); + self.super_place(place, context, location); match *place { - Place::Local(ref local) => self.visit_local(local, context, location), - Place::Promoted(_) => bug!("promoting already promoted MIR"), + Place::Local(_) | + Place::Promoted(_) => {} Place::Static(ref global) => { if self.tcx .get_attrs(global.def_id) @@ -421,7 +730,6 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { "thread-local statics cannot be \ accessed at compile-time"); } - self.state.add(Qualif::NOT_CONST); return; } @@ -440,7 +748,6 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { return; } unleash_miri!(self); - self.state.add(Qualif::NOT_CONST); if self.mode != Mode::Fn { let mut err = struct_span_err!(self.tcx.sess, self.span, E0013, @@ -459,73 +766,64 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { } } Place::Projection(ref proj) => { - self.nest(|this| { - this.super_place(place, context, location); - match proj.elem { - ProjectionElem::Deref => { - if context.is_mutating_use() { - // `not_const` errors out in const contexts - this.not_const() - } else { - // just make sure this doesn't get promoted - this.state.add(Qualif::NOT_CONST); - } - let base_ty = proj.base.ty(this.mir, this.tcx).to_ty(this.tcx); - match this.mode { - Mode::Fn => {}, - _ => { - if let ty::RawPtr(_) = base_ty.sty { - if !this.tcx.features().const_raw_ptr_deref { - emit_feature_err( - &this.tcx.sess.parse_sess, "const_raw_ptr_deref", - this.span, GateIssue::Language, - &format!( - "dereferencing raw pointers in {}s is unstable", - this.mode, - ), - ); - } + match proj.elem { + ProjectionElem::Deref => { + if context.is_mutating_use() { + // `not_const` errors out in const contexts + self.not_const() + } + let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx); + match self.mode { + Mode::Fn => {}, + _ => { + if let ty::RawPtr(_) = base_ty.sty { + if !self.tcx.features().const_raw_ptr_deref { + emit_feature_err( + &self.tcx.sess.parse_sess, "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 = proj.base.ty(this.mir, this.tcx).to_ty(this.tcx); - if let Some(def) = base_ty.ty_adt_def() { - if def.is_union() { - match this.mode { - Mode::Fn => this.not_const(), - Mode::ConstFn => { - if !this.tcx.features().const_fn_union { - emit_feature_err( - &this.tcx.sess.parse_sess, "const_fn_union", - this.span, GateIssue::Language, - "unions in const fn are unstable", - ); - } - }, - - | Mode::Static - | Mode::StaticMut - | Mode::Const - => {}, - } + ProjectionElem::ConstantIndex {..} | + ProjectionElem::Subslice {..} | + ProjectionElem::Field(..) | + ProjectionElem::Index(_) => { + let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx); + 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 { + emit_feature_err( + &self.tcx.sess.parse_sess, "const_fn_union", + self.span, GateIssue::Language, + "unions in const fn are unstable", + ); + } + }, + + | Mode::Fn + | Mode::Static + | Mode::StaticMut + | Mode::Const + => {}, } } - - let ty = place.ty(this.mir, this.tcx).to_ty(this.tcx); - this.state.qualif.restrict(ty, this.tcx, this.param_env); } + } - ProjectionElem::Downcast(..) => { - this.not_const() - } + ProjectionElem::Downcast(..) => { + self.not_const() } - }); + } } } } @@ -535,40 +833,25 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { self.super_operand(operand, location); match *operand { - Operand::Copy(_) | - Operand::Move(_) => { + Operand::Move(ref place) => { // Mark the consumed locals to indicate later drops are noops. - if let Operand::Move(Place::Local(local)) = *operand { - self.state.local_qualif[local] = self.state.local_qualif[local].map(|q| + if let Place::Local(local) = *place { + self.local_qualif[local] = self.local_qualif[local].map(|q| q - Qualif::NEEDS_DROP ); } } - Operand::Constant(ref constant) => { - if let ty::LazyConst::Unevaluated(def_id, _) = constant.literal { - // Don't peek inside trait associated constants. - if self.tcx.trait_of_item(*def_id).is_some() { - self.add_type(constant.ty); - } else { - let (bits, _) = self.tcx.at(constant.span).mir_const_qualif(*def_id); - - let qualif = Qualif::from_bits(bits).expect("invalid mir_const_qualif"); - self.state.add(qualif); - - // Just in case the type is more specific than - // the definition, e.g., impl associated const - // with type parameters, take it into account. - self.state.qualif.restrict(constant.ty, self.tcx, self.param_env); - } - } - } + Operand::Copy(_) | + Operand::Constant(_) => {} } } fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { debug!("visit_rvalue: rvalue={:?} location={:?}", rvalue, location); - // Recurse through operands and places. + + // Check nested operands and places. if let Rvalue::Ref(region, kind, ref place) = *rvalue { + // Special-case reborrows. let mut is_reborrow = false; if let Place::Projection(ref proj) = *place { if let ProjectionElem::Deref = proj.elem { @@ -581,14 +864,18 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { if is_reborrow { let ctx = match kind { - BorrowKind::Shared => - PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow(region)), - BorrowKind::Shallow => - PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow(region)), - BorrowKind::Unique => - PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow(region)), - BorrowKind::Mut { .. } => - PlaceContext::MutatingUse(MutatingUseContext::Borrow(region)), + BorrowKind::Shared => PlaceContext::NonMutatingUse( + NonMutatingUseContext::SharedBorrow(region), + ), + BorrowKind::Shallow => PlaceContext::NonMutatingUse( + NonMutatingUseContext::ShallowBorrow(region), + ), + BorrowKind::Unique => PlaceContext::NonMutatingUse( + NonMutatingUseContext::UniqueBorrow(region), + ), + BorrowKind::Mut { .. } => PlaceContext::MutatingUse( + MutatingUseContext::Borrow(region), + ), }; self.super_place(place, ctx, location); } else { @@ -610,111 +897,9 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { Rvalue::Cast(CastKind::ClosureFnPointer, ..) | Rvalue::Cast(CastKind::Unsize, ..) | Rvalue::Discriminant(..) | - Rvalue::Len(_) => {} - - Rvalue::Ref(_, kind, ref place) => { - let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx); - - // Default to forbidding the borrow and/or its promotion, - // due to the potential for direct or interior mutability, - // and only proceed by setting `forbidden_mut` to `false`. - let mut forbidden_mut = true; - - 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 self.mode == Mode::StaticMut { - // Inside a `static mut`, &mut [...] is also allowed. - match ty.sty { - ty::Array(..) | ty::Slice(_) => forbidden_mut = false, - _ => {} - } - } else if let ty::Array(_, len) = ty.sty { - // FIXME(eddyb) the `self.mode == Mode::Fn` condition - // seems unnecessary, given that this is merely a ZST. - if len.unwrap_usize(self.tcx) == 0 && self.mode == Mode::Fn { - forbidden_mut = false; - } - } - - if forbidden_mut { - unleash_miri!(self); - self.state.add(Qualif::NOT_CONST); - if self.mode != Mode::Fn { - 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 { - // Constants cannot be borrowed if they contain interior mutability as - // it means that our "silent insertion of statics" could change - // initializer values (very bad). - if self.state.qualif.contains(Qualif::MUTABLE_INTERIOR) { - // A reference of a MUTABLE_INTERIOR place is instead - // NOT_CONST (see `if forbidden_mut` below), to avoid - // duplicate errors (from reborrowing, for example). - self.state.qualif = self.state.qualif - Qualif::MUTABLE_INTERIOR; - if self.mode != Mode::Fn { - span_err!(self.tcx.sess, self.span, E0492, - "cannot borrow a constant which may contain \ - interior mutability, create a static instead"); - } - } else { - // We allow immutable borrows of frozen data. - forbidden_mut = false; - } - } - - debug!("visit_rvalue: forbidden_mut={:?}", forbidden_mut); - if forbidden_mut { - unleash_miri!(self); - self.state.add(Qualif::NOT_CONST); - } else { - // We might have a candidate for promotion. - let candidate = Candidate::Ref(location); - // We can only promote interior borrows of promotable temps. - let mut place = place; - while let Place::Projection(ref proj) = *place { - if proj.elem == ProjectionElem::Deref { - break; - } - place = &proj.base; - } - debug!("visit_rvalue: place={:?}", place); - if let Place::Local(local) = *place { - if self.mir.local_kind(local) == LocalKind::Temp { - debug!("visit_rvalue: local={:?}", local); - if let Some(qualif) = self.state.local_qualif[local] { - // `forbidden_mut` is false, so we can safely ignore - // `MUTABLE_INTERIOR` from the local's qualifications. - // This allows borrowing fields which don't have - // `MUTABLE_INTERIOR`, from a type that does, e.g.: - // `let _: &'static _ = &(Cell::new(1), 2).1;` - debug!("visit_rvalue: qualif={:?}", qualif); - if (qualif - Qualif::MUTABLE_INTERIOR).is_empty() { - debug!("visit_rvalue: candidate={:?}", candidate); - self.promotion_candidates.push(candidate); - } - } - } - } - } - } + Rvalue::Len(_) | + Rvalue::Ref(..) | + Rvalue::Aggregate(..) => {} Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => { let operand_ty = operand.ty(self.mir, self.tcx); @@ -722,12 +907,9 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { 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(_)) => { + (CastTy::FnPtr, CastTy::Int(_)) if self.mode != Mode::Fn => { unleash_miri!(self); - if let Mode::Fn = self.mode { - // in normal functions, mark such casts as not promotable - self.state.add(Qualif::NOT_CONST); - } else if !self.tcx.features().const_raw_ptr_to_usize_cast { + if !self.tcx.features().const_raw_ptr_to_usize_cast { // in const fn and constants require the feature gate // FIXME: make it unsafe inside const fn and constants emit_feature_err( @@ -752,10 +934,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { op == BinOp::Offset); unleash_miri!(self); - if let Mode::Fn = self.mode { - // raw pointer operations are not allowed inside promoteds - self.state.add(Qualif::NOT_CONST); - } else if !self.tcx.features().const_compare_raw_pointers { + if self.mode != Mode::Fn && !self.tcx.features().const_compare_raw_pointers { // require the feature gate inside constants and const fn // FIXME: make it unsafe to use these operations emit_feature_err( @@ -771,7 +950,6 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { Rvalue::NullaryOp(NullOp::Box, _) => { unleash_miri!(self); - self.state.add(Qualif::NOT_CONST); if self.mode != Mode::Fn { let mut err = struct_span_err!(self.tcx.sess, self.span, E0010, "allocations are not allowed in {}s", self.mode); @@ -787,20 +965,6 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { err.emit(); } } - - Rvalue::Aggregate(ref kind, _) => { - if let AggregateKind::Adt(def, ..) = **kind { - if def.has_dtor(self.tcx) { - self.state.add(Qualif::NEEDS_DROP); - } - - if Some(def.did) == self.tcx.lang_items().unsafe_cell_type() { - let ty = rvalue.ty(self.mir, self.tcx); - self.add_type(ty); - assert!(self.state.qualif.contains(Qualif::MUTABLE_INTERIOR)); - } - } - } } } @@ -810,13 +974,15 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { location: Location) { debug!("visit_terminator_kind: bb={:?} kind={:?} location={:?}", bb, kind, location); if let TerminatorKind::Call { ref func, ref args, ref destination, .. } = *kind { - self.visit_operand(func, location); + if let Some((ref dest, _)) = *destination { + let ty = dest.ty(self.mir, self.tcx).to_ty(self.tcx); + let qualif = self.qualifier().qualify_call(func, args, ty); + self.assign(dest, qualif, location); + } let fn_ty = func.ty(self.mir, self.tcx); let mut callee_def_id = None; let mut is_shuffle = false; - let mut is_const_fn = false; - let mut is_promotable_const_fn = false; match fn_ty.sty { ty::FnDef(def_id, _) => { callee_def_id = Some(def_id); @@ -825,38 +991,11 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { Abi::PlatformIntrinsic => { assert!(!self.tcx.is_const_fn(def_id)); match &self.tcx.item_name(def_id).as_str()[..] { - | "size_of" - | "min_align_of" - | "needs_drop" - | "type_id" - | "bswap" - | "bitreverse" - | "ctpop" - | "cttz" - | "cttz_nonzero" - | "ctlz" - | "ctlz_nonzero" - | "overflowing_add" - | "overflowing_sub" - | "overflowing_mul" - | "unchecked_shl" - | "unchecked_shr" - | "rotate_left" - | "rotate_right" - | "add_with_overflow" - | "sub_with_overflow" - | "mul_with_overflow" - | "saturating_add" - | "saturating_sub" - // no need to check feature gates, intrinsics are only callable - // from the libstd or with forever unstable feature gates - => is_const_fn = true, // special intrinsic that can be called diretly without an intrinsic // feature gate needs a language feature gate "transmute" => { // never promote transmute calls if self.mode != Mode::Fn { - is_const_fn = true; // const eval transmute calls only with the feature gate if !self.tcx.features().const_transmute { emit_feature_err( @@ -872,23 +1011,14 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { is_shuffle = true; } + // no need to check feature gates, intrinsics are only callable + // from the libstd or with forever unstable feature gates _ => {} } } _ => { - // In normal functions we only care about promotion. - if self.mode == Mode::Fn { - // Never promote const fn calls of - // functions without `#[rustc_promotable]`. - if self.tcx.is_promotable_const_fn(def_id) { - is_const_fn = true; - is_promotable_const_fn = true; - } else if self.tcx.is_const_fn(def_id) { - is_const_fn = true; - } - } else { - // stable const fns or unstable const fns with their feature gate - // active + // In normal functions no calls are feature-gated. + if self.mode != Mode::Fn { let unleash_miri = self .tcx .sess @@ -896,15 +1026,15 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { .debugging_opts .unleash_the_miri_inside_of_you; if self.tcx.is_const_fn(def_id) || unleash_miri { - is_const_fn = true; - } else if self.is_const_panic_fn(def_id) { + // 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.qualifier().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 { - is_const_fn = true; - } else { + if !self.tcx.features().const_panic { // Don't allow panics in constants without the feature gate. emit_feature_err( &self.tcx.sess.parse_sess, @@ -919,10 +1049,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { // 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.as_str()) { - // `allow_internal_unstable` can make such calls stable. - is_const_fn = true; - } else { + if !self.span.allows_unstable(&feature.as_str()) { let mut err = self.tcx.sess.struct_span_err(self.span, &format!("`{}` is not yet stable as a const fn", self.tcx.item_path_str(def_id))); @@ -966,7 +1093,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { } } } - }, + } ty::FnPtr(_) => { if self.mode != Mode::Fn { let mut err = self.tcx.sess.struct_span_err( @@ -974,42 +1101,22 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { &format!("function pointers are not allowed in const fn")); err.emit(); } - }, + } _ => { self.not_const(); - return } } - - let constant_arguments = callee_def_id.and_then(|id| { - args_required_const(self.tcx, id) - }); - for (i, arg) in args.iter().enumerate() { - self.nest(|this| { - this.visit_operand(arg, location); - if this.mode != Mode::Fn { - return - } - let candidate = Candidate::Argument { bb, index: i }; - if is_shuffle && i == 2 { - if this.state.qualif.is_empty() { - debug!("visit_terminator_kind: candidate={:?}", candidate); - this.promotion_candidates.push(candidate); - } else { - span_err!(this.tcx.sess, this.span, E0526, - "shuffle indices are not constant"); - } - return + if self.mode == Mode::Fn { + let constant_args = callee_def_id.and_then(|id| { + args_required_const(self.tcx, id) + }).unwrap_or_default(); + for (i, arg) in args.iter().enumerate() { + if !(is_shuffle && i == 2 || constant_args.contains(&i)) { + continue; } - let constant_arguments = match constant_arguments.as_ref() { - Some(s) => s, - None => return, - }; - if !constant_arguments.contains(&i) { - return - } + let candidate = Candidate::Argument { bb, index: i }; // Since the argument is required to be constant, // we care about constness, not promotability. // If we checked for promotability, we'd miss out on @@ -1020,44 +1127,27 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { // which happens even without the user requesting it. // We can error out with a hard error if the argument is not // constant here. - if (this.state.qualif - Qualif::NOT_PROMOTABLE).is_empty() { + let arg_qualif = self.qualifier().qualify_operand(arg); + if (arg_qualif - Qualif::NOT_PROMOTABLE).is_empty() { debug!("visit_terminator_kind: candidate={:?}", candidate); - this.promotion_candidates.push(candidate); + self.promotion_candidates.push(candidate); } else { - this.tcx.sess.span_err(this.span, - &format!("argument {} is required to be a constant", - i + 1)); + if is_shuffle { + span_err!(self.tcx.sess, self.span, E0526, + "shuffle indices are not constant"); + } else { + self.tcx.sess.span_err(self.span, + &format!("argument {} is required to be a constant", + i + 1)); + } } - }); - } - - // non-const fn calls - if !is_const_fn { - self.state.qualif = Qualif::NOT_CONST; - if self.mode != Mode::Fn { - self.tcx.sess.delay_span_bug( - self.span, - "should have reported an error about non-const fn calls in constants", - ) } } - if let Some((ref dest, _)) = *destination { - // Avoid propagating irrelevant callee/argument qualifications. - if self.state.qualif.intersects(Qualif::CONST_ERROR) { - self.state.qualif = Qualif::NOT_CONST; - } else { - // Be conservative about the returned value of a const fn. - let tcx = self.tcx; - let ty = dest.ty(self.mir, tcx).to_ty(tcx); - if is_const_fn && !is_promotable_const_fn && self.mode == Mode::Fn { - self.state.qualif = Qualif::NOT_PROMOTABLE; - } else { - self.state.qualif = Qualif::empty(); - } - self.add_type(ty); - } - self.assign(dest, location); + // 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, .. } = *kind { self.super_terminator_kind(bb, kind, location); @@ -1068,7 +1158,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { // HACK(eddyb): emulate a bit of dataflow analysis, // conservatively, that drop elaboration will do. let needs_drop = if let Place::Local(local) = *place { - let local_needs_drop = self.state.local_qualif[local] + let local_needs_drop = self.local_qualif[local] .map_or(true, |q| q.contains(Qualif::NEEDS_DROP)); if local_needs_drop { Some(self.mir.local_decls[local].source_info.span) @@ -1103,9 +1193,78 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { rvalue: &Rvalue<'tcx>, location: Location) { debug!("visit_assign: dest={:?} rvalue={:?} location={:?}", dest, rvalue, location); - self.visit_rvalue(rvalue, location); + let mut qualif = self.qualifier().qualify_rvalue(rvalue); + + if let Rvalue::Ref(_, kind, ref place) = *rvalue { + // Getting `MUTABLE_INTERIOR` from `qualify_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 `MUTABLE_INTERIOR` is replaced with `NOT_CONST`, + // to avoid duplicate errors (e.g. from reborrowing). + if qualif.contains(Qualif::MUTABLE_INTERIOR) { + qualif = (qualif - Qualif::MUTABLE_INTERIOR) | Qualif::NOT_CONST; + + if self.mode != Mode::Fn { + 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"); + } + } + } else { + // We might have a candidate for promotion. + let candidate = Candidate::Ref(location); + // We can only promote interior borrows of promotable temps. + let mut place = place; + while let Place::Projection(ref proj) = *place { + if proj.elem == ProjectionElem::Deref { + break; + } + place = &proj.base; + } + debug!("qualify_consts: promotion candidate: place={:?}", place); + if let Place::Local(local) = *place { + if self.mir.local_kind(local) == LocalKind::Temp { + debug!("qualify_consts: promotion candidate: local={:?}", local); + if let Some(qualif) = self.local_qualif[local] { + // The borrowed place doesn't have `MUTABLE_INTERIOR` + // (from `qualify_rvalue`), so we can safely ignore + // `MUTABLE_INTERIOR` from the local's qualifications. + // This allows borrowing fields which don't have + // `MUTABLE_INTERIOR`, from a type that does, e.g.: + // `let _: &'static _ = &(Cell::new(1), 2).1;` + debug!("qualify_consts: promotion candidate: qualif={:?}", qualif); + if (qualif - Qualif::MUTABLE_INTERIOR).is_empty() { + debug!("qualify_consts: promotion candidate: {:?}", candidate); + self.promotion_candidates.push(candidate); + } + } + } + } + } + } + + self.assign(dest, qualif, location); - self.assign(dest, location); + self.visit_rvalue(rvalue, location); } fn visit_source_info(&mut self, source_info: &SourceInfo) { @@ -1115,22 +1274,20 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { fn visit_statement(&mut self, bb: BasicBlock, statement: &Statement<'tcx>, location: Location) { debug!("visit_statement: bb={:?} statement={:?} location={:?}", bb, statement, location); - self.nest(|this| { - this.visit_source_info(&statement.source_info); - match statement.kind { - StatementKind::Assign(ref place, ref rvalue) => { - this.visit_assign(bb, place, rvalue, location); - } - StatementKind::FakeRead(..) | - StatementKind::SetDiscriminant { .. } | - StatementKind::StorageLive(_) | - StatementKind::StorageDead(_) | - StatementKind::InlineAsm {..} | - StatementKind::Retag { .. } | - StatementKind::AscribeUserType(..) | - StatementKind::Nop => {} + match statement.kind { + StatementKind::Assign(..) => { + self.super_statement(bb, statement, location); } - }); + // FIXME(eddyb) should these really do nothing? + StatementKind::FakeRead(..) | + StatementKind::SetDiscriminant { .. } | + StatementKind::StorageLive(_) | + StatementKind::StorageDead(_) | + StatementKind::InlineAsm {..} | + StatementKind::Retag { .. } | + StatementKind::AscribeUserType(..) | + StatementKind::Nop => {} + } } fn visit_terminator(&mut self, @@ -1138,7 +1295,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx, 'tcx> { terminator: &Terminator<'tcx>, location: Location) { debug!("visit_terminator: bb={:?} terminator={:?} location={:?}", bb, terminator, location); - self.nest(|this| this.super_terminator(bb, terminator, location)); + self.super_terminator(bb, terminator, location); } } diff --git a/src/test/ui/error-codes/E0017.nll.stderr b/src/test/ui/error-codes/E0017.nll.stderr index 305e85563ebc5..0477f06010b02 100644 --- a/src/test/ui/error-codes/E0017.nll.stderr +++ b/src/test/ui/error-codes/E0017.nll.stderr @@ -4,17 +4,17 @@ error[E0017]: references in constants may only refer to immutable values LL | const CR: &'static mut i32 = &mut C; //~ ERROR E0017 | ^^^^^^ constants require immutable values -error: cannot mutate statics in the initializer of another static +error[E0017]: references in statics may only refer to immutable values --> $DIR/E0017.rs:5:39 | LL | static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0017 - | ^^^^^^ + | ^^^^^^ statics require immutable values -error[E0017]: references in statics may only refer to immutable values +error: cannot mutate statics in the initializer of another static --> $DIR/E0017.rs:5:39 | LL | static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0017 - | ^^^^^^ statics require immutable values + | ^^^^^^ error[E0596]: cannot borrow immutable static item `X` as mutable --> $DIR/E0017.rs:5:39 diff --git a/src/test/ui/error-codes/E0017.stderr b/src/test/ui/error-codes/E0017.stderr index 93f5ca5545544..cc202ec912e96 100644 --- a/src/test/ui/error-codes/E0017.stderr +++ b/src/test/ui/error-codes/E0017.stderr @@ -4,17 +4,17 @@ error[E0017]: references in constants may only refer to immutable values LL | const CR: &'static mut i32 = &mut C; //~ ERROR E0017 | ^^^^^^ constants require immutable values -error: cannot mutate statics in the initializer of another static +error[E0017]: references in statics may only refer to immutable values --> $DIR/E0017.rs:5:39 | LL | static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0017 - | ^^^^^^ + | ^^^^^^ statics require immutable values -error[E0017]: references in statics may only refer to immutable values +error: cannot mutate statics in the initializer of another static --> $DIR/E0017.rs:5:39 | LL | static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0017 - | ^^^^^^ statics require immutable values + | ^^^^^^ error[E0596]: cannot borrow immutable static item as mutable --> $DIR/E0017.rs:5:44 diff --git a/src/test/ui/error-codes/E0388.nll.stderr b/src/test/ui/error-codes/E0388.nll.stderr index 2bcda2ba8fb17..a898d60a98595 100644 --- a/src/test/ui/error-codes/E0388.nll.stderr +++ b/src/test/ui/error-codes/E0388.nll.stderr @@ -4,17 +4,17 @@ error[E0017]: references in constants may only refer to immutable values LL | const CR: &'static mut i32 = &mut C; //~ ERROR E0017 | ^^^^^^ constants require immutable values -error: cannot mutate statics in the initializer of another static +error[E0017]: references in statics may only refer to immutable values --> $DIR/E0388.rs:5:39 | LL | static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0017 - | ^^^^^^ + | ^^^^^^ statics require immutable values -error[E0017]: references in statics may only refer to immutable values +error: cannot mutate statics in the initializer of another static --> $DIR/E0388.rs:5:39 | LL | static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0017 - | ^^^^^^ statics require immutable values + | ^^^^^^ error[E0596]: cannot borrow immutable static item `X` as mutable --> $DIR/E0388.rs:5:39 diff --git a/src/test/ui/error-codes/E0388.stderr b/src/test/ui/error-codes/E0388.stderr index f35d39c7bb246..f641830ae9ff2 100644 --- a/src/test/ui/error-codes/E0388.stderr +++ b/src/test/ui/error-codes/E0388.stderr @@ -4,17 +4,17 @@ error[E0017]: references in constants may only refer to immutable values LL | const CR: &'static mut i32 = &mut C; //~ ERROR E0017 | ^^^^^^ constants require immutable values -error: cannot mutate statics in the initializer of another static +error[E0017]: references in statics may only refer to immutable values --> $DIR/E0388.rs:5:39 | LL | static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0017 - | ^^^^^^ + | ^^^^^^ statics require immutable values -error[E0017]: references in statics may only refer to immutable values +error: cannot mutate statics in the initializer of another static --> $DIR/E0388.rs:5:39 | LL | static STATIC_REF: &'static mut i32 = &mut X; //~ ERROR E0017 - | ^^^^^^ statics require immutable values + | ^^^^^^ error[E0596]: cannot borrow immutable static item as mutable --> $DIR/E0388.rs:5:44 diff --git a/src/test/ui/issues/issue-17718-const-bad-values.stderr b/src/test/ui/issues/issue-17718-const-bad-values.stderr index 25b1cfb0ffb00..95ef2b1b01008 100644 --- a/src/test/ui/issues/issue-17718-const-bad-values.stderr +++ b/src/test/ui/issues/issue-17718-const-bad-values.stderr @@ -4,17 +4,17 @@ error[E0017]: references in constants may only refer to immutable values LL | const C1: &'static mut [usize] = &mut []; | ^^^^^^^ constants require immutable values -error[E0013]: constants cannot refer to statics, use a constant instead +error[E0017]: references in constants may only refer to immutable values --> $DIR/issue-17718-const-bad-values.rs:5:41 | LL | const C2: &'static mut usize = unsafe { &mut S }; - | ^^^^^^ + | ^^^^^^ constants require immutable values -error[E0017]: references in constants may only refer to immutable values +error[E0013]: constants cannot refer to statics, use a constant instead --> $DIR/issue-17718-const-bad-values.rs:5:41 | LL | const C2: &'static mut usize = unsafe { &mut S }; - | ^^^^^^ constants require immutable values + | ^^^^^^ error: aborting due to 3 previous errors From b4fbebb636d0f31658354f9cecee52d265602d2e Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Tue, 5 Feb 2019 12:45:15 +0200 Subject: [PATCH 04/10] rustc_mir: remove outdated `const fn` stability diagnostic. --- src/librustc_mir/transform/qualify_consts.rs | 26 +++----------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index e1d0ead130c02..44c7327044fda 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -21,7 +21,7 @@ use rustc::mir::visit::{PlaceContext, Visitor, MutatingUseContext, NonMutatingUs use rustc::middle::lang_items; use rustc::session::config::nightly_options; use syntax::ast::LitKind; -use syntax::feature_gate::{UnstableFeatures, emit_feature_err, GateIssue}; +use syntax::feature_gate::{emit_feature_err, GateIssue}; use syntax_pos::{Span, DUMMY_SP}; use std::fmt; @@ -1062,32 +1062,14 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { err.emit(); } } else { - // FIXME(#57563): remove this check when const fn stabilizes. - let (msg, note) = if let UnstableFeatures::Disallow = - self.tcx.sess.opts.unstable_features { - (format!("calls in {}s are limited to \ - tuple structs and tuple variants", - self.mode), - Some("a limited form of compile-time function \ - evaluation is available on a nightly \ - compiler via `const fn`")) - } else { - (format!("calls in {}s are limited \ - to constant functions, \ - tuple structs and tuple variants", - self.mode), - None) - }; let mut err = struct_span_err!( self.tcx.sess, self.span, E0015, - "{}", - msg, + "calls in {}s are limited to constant functions, \ + tuple structs and tuple variants", + self.mode, ); - if let Some(note) = note { - err.span_note(self.span, note); - } err.emit(); } } From af131d55b6bb0e6824cf5921d04cbb5fc02c47d9 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Tue, 5 Feb 2019 13:24:49 +0200 Subject: [PATCH 05/10] rustc_mir: remove qualify_consts::Qualif::FN_ARGUMENT. --- src/librustc_mir/transform/qualify_consts.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 44c7327044fda..0d3080d3f22ac 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -41,16 +41,13 @@ bitflags::bitflags! { // Constant containing an ADT that implements Drop. const NEEDS_DROP = 1 << 1; - // Function argument. - const FN_ARGUMENT = 1 << 2; - // Not constant at all - non-`const fn` calls, asm!, // pointer comparisons, ptr-to-int casts, etc. - const NOT_CONST = 1 << 3; + const NOT_CONST = 1 << 2; // Refers to temporaries which cannot be promoted as // promote_consts decided they weren't simple enough. - const NOT_PROMOTABLE = 1 << 4; + const NOT_PROMOTABLE = 1 << 3; // Const items can only have MUTABLE_INTERIOR // and NOT_PROMOTABLE without producing an error. @@ -136,10 +133,6 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx> { let mut qualif = self.local_qualif[local] .unwrap_or(Qualif::NOT_CONST); - if let LocalKind::Arg = kind { - qualif = qualif | Qualif::FN_ARGUMENT; - } - if !self.temp_promotion_state[local].is_promotable() { qualif = qualif | Qualif::NOT_PROMOTABLE; } @@ -498,9 +491,8 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { let mut local_qualif = IndexVec::from_elem(None, &mir.local_decls); for arg in mir.args_iter() { - let mut qualif = Qualif::NEEDS_DROP; - qualif.restrict(mir.local_decls[arg].ty, tcx, param_env); - local_qualif[arg] = Some(qualif); + let qualif = Qualif::for_ty(mir.local_decls[arg].ty, tcx, param_env); + local_qualif[arg] = Some(Qualif::NOT_PROMOTABLE | qualif); } Checker { From 2de5b389435d97fbb547c7393f400702f109594e Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Tue, 5 Feb 2019 19:46:43 +0200 Subject: [PATCH 06/10] rustc_mir: rename `Qualif::for_ty to `Qualif::any_value_of_ty`. --- src/librustc_mir/transform/qualify_consts.rs | 33 +++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 0d3080d3f22ac..f0fd598d31be4 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -58,9 +58,11 @@ bitflags::bitflags! { impl<'a, 'tcx> Qualif { /// Compute the qualifications for the given type. - fn for_ty(ty: Ty<'tcx>, - tcx: TyCtxt<'a, 'tcx, 'tcx>, - param_env: ty::ParamEnv<'tcx>) -> Self { + fn any_value_of_ty( + ty: Ty<'tcx>, + tcx: TyCtxt<'a, 'tcx, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) -> Self { let mut qualif = Self::empty(); if !ty.is_freeze(tcx, param_env, DUMMY_SP) { qualif = qualif | Qualif::MUTABLE_INTERIOR; @@ -72,10 +74,13 @@ impl<'a, 'tcx> Qualif { } /// Remove flags which are impossible for the given type. - fn restrict(&mut self, ty: Ty<'tcx>, - tcx: TyCtxt<'a, 'tcx, 'tcx>, - param_env: ty::ParamEnv<'tcx>) { - let ty_qualif = Self::for_ty(ty, tcx, param_env); + fn restrict( + &mut self, + ty: Ty<'tcx>, + tcx: TyCtxt<'a, 'tcx, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) { + let ty_qualif = Self::any_value_of_ty(ty, tcx, param_env); if !ty_qualif.contains(Qualif::MUTABLE_INTERIOR) { *self = *self - Qualif::MUTABLE_INTERIOR; } @@ -117,8 +122,8 @@ struct Qualifier<'a, 'tcx> { } impl<'a, 'tcx> Qualifier<'a, 'tcx> { - fn qualif_for_ty(&self, ty: Ty<'tcx>) -> Qualif { - Qualif::for_ty(ty, self.tcx, self.param_env) + fn qualify_any_value_of_ty(&self, ty: Ty<'tcx>) -> Qualif { + Qualif::any_value_of_ty(ty, self.tcx, self.param_env) } fn qualify_local(&self, local: Local) -> Qualif { @@ -217,7 +222,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx> { if let ty::LazyConst::Unevaluated(def_id, _) = constant.literal { // Don't peek inside trait associated constants. if self.tcx.trait_of_item(*def_id).is_some() { - self.qualif_for_ty(constant.ty) + self.qualify_any_value_of_ty(constant.ty) } else { let (bits, _) = self.tcx.at(constant.span).mir_const_qualif(*def_id); @@ -348,7 +353,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx> { if let AggregateKind::Adt(def, ..) = **kind { if Some(def.did) == self.tcx.lang_items().unsafe_cell_type() { let ty = rvalue.ty(self.mir, self.tcx); - qualif = qualif | self.qualif_for_ty(ty); + qualif = qualif | self.qualify_any_value_of_ty(ty); assert!(qualif.contains(Qualif::MUTABLE_INTERIOR)); } @@ -444,7 +449,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx> { } // Be conservative about the returned value of a const fn. - let qualif = self.qualif_for_ty(return_ty); + let qualif = self.qualify_any_value_of_ty(return_ty); if !is_promotable_const_fn && self.mode == Mode::Fn { qualif | Qualif::NOT_PROMOTABLE } else { @@ -491,7 +496,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { let mut local_qualif = IndexVec::from_elem(None, &mir.local_decls); for arg in mir.args_iter() { - let qualif = Qualif::for_ty(mir.local_decls[arg].ty, tcx, param_env); + let qualif = Qualif::any_value_of_ty(mir.local_decls[arg].ty, tcx, param_env); local_qualif[arg] = Some(Qualif::NOT_PROMOTABLE | qualif); } @@ -661,7 +666,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { // Account for errors in consts by using the // conservative type qualification instead. if qualif.intersects(Qualif::CONST_ERROR) { - qualif = self.qualifier().qualif_for_ty(mir.return_ty()); + qualif = self.qualifier().qualify_any_value_of_ty(mir.return_ty()); } From 1d6332def3c3091ba3287e5d473f80443dc0a882 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Wed, 6 Feb 2019 03:08:43 +0200 Subject: [PATCH 07/10] rustc_mir: avoid having an Option around each local qualification in qualify_consts. --- src/librustc_mir/transform/qualify_consts.rs | 107 +++++++++---------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index f0fd598d31be4..25d201c3452d9 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -117,8 +117,7 @@ struct Qualifier<'a, 'tcx> { mode: Mode, mir: &'a Mir<'tcx>, - local_qualif: &'a IndexVec>, - temp_promotion_state: &'a IndexVec, + local_qualif: &'a IndexVec, } impl<'a, 'tcx> Qualifier<'a, 'tcx> { @@ -127,24 +126,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx> { } fn qualify_local(&self, local: Local) -> Qualif { - let kind = self.mir.local_kind(local); - match kind { - LocalKind::ReturnPointer => Qualif::NOT_CONST, - LocalKind::Var if self.mode == Mode::Fn => Qualif::NOT_CONST, - - LocalKind::Var | - LocalKind::Arg | - LocalKind::Temp => { - let mut qualif = self.local_qualif[local] - .unwrap_or(Qualif::NOT_CONST); - - if !self.temp_promotion_state[local].is_promotable() { - qualif = qualif | Qualif::NOT_PROMOTABLE; - } - - qualif - } - } + self.local_qualif[local] } fn qualify_projection_elem(&self, proj: &PlaceElem<'tcx>) -> Qualif { @@ -467,7 +449,7 @@ struct Checker<'a, 'tcx> { mir: &'a Mir<'tcx>, rpo: ReversePostorder<'a, 'tcx>, - local_qualif: IndexVec>, + local_qualif: IndexVec, temp_promotion_state: IndexVec, promotion_candidates: Vec, } @@ -494,11 +476,22 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { let param_env = tcx.param_env(def_id); - let mut local_qualif = IndexVec::from_elem(None, &mir.local_decls); - for arg in mir.args_iter() { - let qualif = Qualif::any_value_of_ty(mir.local_decls[arg].ty, tcx, param_env); - local_qualif[arg] = Some(Qualif::NOT_PROMOTABLE | qualif); - } + let local_qualif = mir.local_decls.iter_enumerated().map(|(local, decl)| { + match mir.local_kind(local) { + LocalKind::Arg => { + Qualif::any_value_of_ty(decl.ty, tcx, param_env) | + Qualif::NOT_PROMOTABLE + } + + LocalKind::Var if mode == Mode::Fn => Qualif::NOT_CONST, + + LocalKind::Temp if !temps[local].is_promotable() => { + Qualif::NOT_PROMOTABLE + } + + _ => Qualif::empty(), + } + }).collect(); Checker { mode, @@ -521,7 +514,6 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { mode: self.mode, mir: self.mir, local_qualif: &self.local_qualif, - temp_promotion_state: &self.temp_promotion_state, } } @@ -559,10 +551,10 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { && self.temp_promotion_state[index].is_promotable() { debug!("store to promotable temp {:?} ({:?})", index, qualif); let slot = &mut self.local_qualif[index]; - if slot.is_some() { + if !slot.is_empty() { span_bug!(self.span, "multiple assignments to {:?}", dest); } - *slot = Some(qualif); + *slot = qualif; } } return; @@ -605,7 +597,18 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { // aggregates where we overwrite all fields via assignments, which would not get // that feature. let slot = &mut self.local_qualif[index]; - *slot = Some(slot.unwrap_or(Qualif::empty()) | qualif); + *slot = *slot | qualif; + + // Ensure we keep the `NOT_PROMOTABLE` flag is preserved. + // NOTE(eddyb) this is actually unnecessary right now, as + // we never replace the local's qualif (but we might in + // the future) - also, if `NOT_PROMOTABLE` only matters + // for `Mode::Fn`, then this is also pointless. + if self.mir.local_kind(index) == LocalKind::Temp { + if !self.temp_promotion_state[index].is_promotable() { + *slot = *slot | Qualif::NOT_PROMOTABLE; + } + } } /// Check a whole const, static initializer or const fn. @@ -661,7 +664,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { } } - let mut qualif = self.local_qualif[RETURN_PLACE].unwrap_or(Qualif::NOT_CONST); + let mut qualif = self.local_qualif[RETURN_PLACE]; // Account for errors in consts by using the // conservative type qualification instead. @@ -698,16 +701,6 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { /// For functions (constant or not), it also records /// candidates for promotion in `promotion_candidates`. impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { - fn visit_local(&mut self, - &local: &Local, - _: PlaceContext<'tcx>, - _: Location) { - debug!("visit_local: local={:?}", local); - if self.local_qualif[local].is_none() { - self.not_const(); - } - } - fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext<'tcx>, @@ -833,9 +826,8 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { Operand::Move(ref place) => { // Mark the consumed locals to indicate later drops are noops. if let Place::Local(local) = *place { - self.local_qualif[local] = self.local_qualif[local].map(|q| - q - Qualif::NEEDS_DROP - ); + let slot = &mut self.local_qualif[local]; + *slot = *slot - Qualif::NEEDS_DROP; } } Operand::Copy(_) | @@ -1137,9 +1129,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { // HACK(eddyb): emulate a bit of dataflow analysis, // conservatively, that drop elaboration will do. let needs_drop = if let Place::Local(local) = *place { - let local_needs_drop = self.local_qualif[local] - .map_or(true, |q| q.contains(Qualif::NEEDS_DROP)); - if local_needs_drop { + if self.local_qualif[local].contains(Qualif::NEEDS_DROP) { Some(self.mir.local_decls[local].source_info.span) } else { None @@ -1223,18 +1213,17 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { if let Place::Local(local) = *place { if self.mir.local_kind(local) == LocalKind::Temp { debug!("qualify_consts: promotion candidate: local={:?}", local); - if let Some(qualif) = self.local_qualif[local] { - // The borrowed place doesn't have `MUTABLE_INTERIOR` - // (from `qualify_rvalue`), so we can safely ignore - // `MUTABLE_INTERIOR` from the local's qualifications. - // This allows borrowing fields which don't have - // `MUTABLE_INTERIOR`, from a type that does, e.g.: - // `let _: &'static _ = &(Cell::new(1), 2).1;` - debug!("qualify_consts: promotion candidate: qualif={:?}", qualif); - if (qualif - Qualif::MUTABLE_INTERIOR).is_empty() { - debug!("qualify_consts: promotion candidate: {:?}", candidate); - self.promotion_candidates.push(candidate); - } + let qualif = self.local_qualif[local]; + // The borrowed place doesn't have `MUTABLE_INTERIOR` + // (from `qualify_rvalue`), so we can safely ignore + // `MUTABLE_INTERIOR` from the local's qualifications. + // This allows borrowing fields which don't have + // `MUTABLE_INTERIOR`, from a type that does, e.g.: + // `let _: &'static _ = &(Cell::new(1), 2).1;` + debug!("qualify_consts: promotion candidate: qualif={:?}", qualif); + if (qualif - Qualif::MUTABLE_INTERIOR).is_empty() { + debug!("qualify_consts: promotion candidate: {:?}", candidate); + self.promotion_candidates.push(candidate); } } } From 99fc3eff5795024b6d1bb510b06dfd4cb2049b72 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Wed, 6 Feb 2019 18:11:48 +0200 Subject: [PATCH 08/10] rustc_mir: do not treat NEEDS_DROP as unexpected in qualify_consts. --- src/librustc_mir/transform/qualify_consts.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 25d201c3452d9..960146651cfdd 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -48,11 +48,6 @@ bitflags::bitflags! { // Refers to temporaries which cannot be promoted as // promote_consts decided they weren't simple enough. const NOT_PROMOTABLE = 1 << 3; - - // Const items can only have MUTABLE_INTERIOR - // and NOT_PROMOTABLE without producing an error. - const CONST_ERROR = !Qualif::MUTABLE_INTERIOR.bits & - !Qualif::NOT_PROMOTABLE.bits; } } @@ -419,13 +414,13 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx> { }; // Bail out on oon-`const fn` calls or if the callee had errors. - if !is_const_fn || self.qualify_operand(callee).intersects(Qualif::CONST_ERROR) { + if !is_const_fn || self.qualify_operand(callee).intersects(Qualif::NOT_CONST) { return Qualif::NOT_CONST; } // Bail out if any arguments had errors. for arg in args { - if self.qualify_operand(arg).intersects(Qualif::CONST_ERROR) { + if self.qualify_operand(arg).intersects(Qualif::NOT_CONST) { return Qualif::NOT_CONST; } } @@ -668,7 +663,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { // Account for errors in consts by using the // conservative type qualification instead. - if qualif.intersects(Qualif::CONST_ERROR) { + if qualif.intersects(Qualif::NOT_CONST) { qualif = self.qualifier().qualify_any_value_of_ty(mir.return_ty()); } From 81648a0a6e6aaf66913dc92187613d8cffff3fd4 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Thu, 7 Feb 2019 03:45:37 +0200 Subject: [PATCH 09/10] rustc_mir: rename qualify_consts::Qualifier to ConstCx and keep in Checker as a field. --- src/librustc_mir/transform/qualify_consts.rs | 69 ++++++++++---------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 960146651cfdd..3a103e62297c4 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -25,6 +25,7 @@ use syntax::feature_gate::{emit_feature_err, GateIssue}; use syntax_pos::{Span, DUMMY_SP}; use std::fmt; +use std::ops::Deref; use std::usize; use crate::transform::{MirPass, MirSource}; @@ -106,16 +107,16 @@ impl fmt::Display for Mode { } } -struct Qualifier<'a, 'tcx> { +struct ConstCx<'a, 'tcx> { tcx: TyCtxt<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>, mode: Mode, mir: &'a Mir<'tcx>, - local_qualif: &'a IndexVec, + local_qualif: IndexVec, } -impl<'a, 'tcx> Qualifier<'a, 'tcx> { +impl<'a, 'tcx> ConstCx<'a, 'tcx> { fn qualify_any_value_of_ty(&self, ty: Ty<'tcx>) -> Qualif { Qualif::any_value_of_ty(ty, self.tcx, self.param_env) } @@ -436,15 +437,12 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx> { } struct Checker<'a, 'tcx> { - tcx: TyCtxt<'a, 'tcx, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - mode: Mode, + cx: ConstCx<'a, 'tcx>, + span: Span, def_id: DefId, - mir: &'a Mir<'tcx>, rpo: ReversePostorder<'a, 'tcx>, - local_qualif: IndexVec, temp_promotion_state: IndexVec, promotion_candidates: Vec, } @@ -458,6 +456,14 @@ macro_rules! unleash_miri { }} } +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<'a, 'tcx, 'tcx>, def_id: DefId, @@ -489,29 +495,21 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { }).collect(); Checker { - mode, + cx: ConstCx { + tcx, + param_env, + mode, + mir, + local_qualif, + }, span: mir.span, def_id, - mir, rpo, - tcx, - param_env, - local_qualif, temp_promotion_state: temps, promotion_candidates: vec![] } } - fn qualifier(&'a self) -> Qualifier<'a, 'tcx> { - Qualifier { - tcx: self.tcx, - param_env: self.param_env, - mode: self.mode, - mir: self.mir, - local_qualif: &self.local_qualif, - } - } - // FIXME(eddyb) we could split the errors into meaningful // categories, but enabling full miri would make that // slightly pointless (even with feature-gating). @@ -545,7 +543,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { if self.mir.local_kind(index) == LocalKind::Temp && self.temp_promotion_state[index].is_promotable() { debug!("store to promotable temp {:?} ({:?})", index, qualif); - let slot = &mut self.local_qualif[index]; + let slot = &mut self.cx.local_qualif[index]; if !slot.is_empty() { span_bug!(self.span, "multiple assignments to {:?}", dest); } @@ -586,12 +584,15 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { } } }; - debug!("store to var {:?}", index); + + let kind = self.mir.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. - let slot = &mut self.local_qualif[index]; + let slot = &mut self.cx.local_qualif[index]; *slot = *slot | qualif; // Ensure we keep the `NOT_PROMOTABLE` flag is preserved. @@ -599,7 +600,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { // we never replace the local's qualif (but we might in // the future) - also, if `NOT_PROMOTABLE` only matters // for `Mode::Fn`, then this is also pointless. - if self.mir.local_kind(index) == LocalKind::Temp { + if kind == LocalKind::Temp { if !self.temp_promotion_state[index].is_promotable() { *slot = *slot | Qualif::NOT_PROMOTABLE; } @@ -664,7 +665,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { // Account for errors in consts by using the // conservative type qualification instead. if qualif.intersects(Qualif::NOT_CONST) { - qualif = self.qualifier().qualify_any_value_of_ty(mir.return_ty()); + qualif = self.qualify_any_value_of_ty(mir.return_ty()); } @@ -690,7 +691,7 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { } } -/// Checks MIR for const-correctness, using `Qualifier` +/// Checks MIR for const-correctness, using `ConstCx` /// for value qualifications, and accumulates writes of /// rvalue/call results to locals, in `local_qualif`. /// For functions (constant or not), it also records @@ -821,7 +822,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { Operand::Move(ref place) => { // Mark the consumed locals to indicate later drops are noops. if let Place::Local(local) = *place { - let slot = &mut self.local_qualif[local]; + let slot = &mut self.cx.local_qualif[local]; *slot = *slot - Qualif::NEEDS_DROP; } } @@ -960,7 +961,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { if let TerminatorKind::Call { ref func, ref args, ref destination, .. } = *kind { if let Some((ref dest, _)) = *destination { let ty = dest.ty(self.mir, self.tcx).to_ty(self.tcx); - let qualif = self.qualifier().qualify_call(func, args, ty); + let qualif = self.qualify_call(func, args, ty); self.assign(dest, qualif, location); } @@ -1013,7 +1014,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { // 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.qualifier().is_const_panic_fn(def_id) { + } 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, @@ -1093,7 +1094,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { // which happens even without the user requesting it. // We can error out with a hard error if the argument is not // constant here. - let arg_qualif = self.qualifier().qualify_operand(arg); + let arg_qualif = self.qualify_operand(arg); if (arg_qualif - Qualif::NOT_PROMOTABLE).is_empty() { debug!("visit_terminator_kind: candidate={:?}", candidate); self.promotion_candidates.push(candidate); @@ -1157,7 +1158,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { rvalue: &Rvalue<'tcx>, location: Location) { debug!("visit_assign: dest={:?} rvalue={:?} location={:?}", dest, rvalue, location); - let mut qualif = self.qualifier().qualify_rvalue(rvalue); + let mut qualif = self.qualify_rvalue(rvalue); if let Rvalue::Ref(_, kind, ref place) = *rvalue { // Getting `MUTABLE_INTERIOR` from `qualify_rvalue` means From f04424acd1bf894d1dc930c2a347871ea8b96dfa Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Wed, 6 Feb 2019 17:18:42 +0200 Subject: [PATCH 10/10] rustc_mir: compute all the qualification bits separately in qualify_consts. --- Cargo.lock | 1 - src/librustc_mir/Cargo.toml | 1 - src/librustc_mir/transform/qualify_consts.rs | 867 +++++++++++-------- 3 files changed, 504 insertions(+), 365 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a36df24359df7..963bbcf0f0429 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2822,7 +2822,6 @@ name = "rustc_mir" version = "0.0.0" dependencies = [ "arena 0.0.0", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "graphviz 0.0.0", diff --git a/src/librustc_mir/Cargo.toml b/src/librustc_mir/Cargo.toml index 44a6b41cdfe45..c32bafa99205f 100644 --- a/src/librustc_mir/Cargo.toml +++ b/src/librustc_mir/Cargo.toml @@ -11,7 +11,6 @@ crate-type = ["dylib"] [dependencies] arena = { path = "../libarena" } -bitflags = "1.0" either = "1.5.0" dot = { path = "../libgraphviz", package = "graphviz" } log = "0.4" diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 3a103e62297c4..51168f650aeb7 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -25,67 +25,12 @@ use syntax::feature_gate::{emit_feature_err, GateIssue}; use syntax_pos::{Span, DUMMY_SP}; use std::fmt; -use std::ops::Deref; +use std::ops::{Deref, Index, IndexMut}; use std::usize; use crate::transform::{MirPass, MirSource}; use super::promote_consts::{self, Candidate, TempState}; -bitflags::bitflags! { - // Borrows of temporaries can be promoted only if - // they have none of these qualifications, with - // the exception of `STATIC_REF` (in statics only). - struct Qualif: u8 { - // Constant containing interior mutability (UnsafeCell). - const MUTABLE_INTERIOR = 1 << 0; - - // Constant containing an ADT that implements Drop. - const NEEDS_DROP = 1 << 1; - - // Not constant at all - non-`const fn` calls, asm!, - // pointer comparisons, ptr-to-int casts, etc. - const NOT_CONST = 1 << 2; - - // Refers to temporaries which cannot be promoted as - // promote_consts decided they weren't simple enough. - const NOT_PROMOTABLE = 1 << 3; - } -} - -impl<'a, 'tcx> Qualif { - /// Compute the qualifications for the given type. - fn any_value_of_ty( - ty: Ty<'tcx>, - tcx: TyCtxt<'a, 'tcx, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - ) -> Self { - let mut qualif = Self::empty(); - if !ty.is_freeze(tcx, param_env, DUMMY_SP) { - qualif = qualif | Qualif::MUTABLE_INTERIOR; - } - if ty.needs_drop(tcx, param_env) { - qualif = qualif | Qualif::NEEDS_DROP; - } - qualif - } - - /// Remove flags which are impossible for the given type. - fn restrict( - &mut self, - ty: Ty<'tcx>, - tcx: TyCtxt<'a, 'tcx, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - ) { - let ty_qualif = Self::any_value_of_ty(ty, tcx, param_env); - if !ty_qualif.contains(Qualif::MUTABLE_INTERIOR) { - *self = *self - Qualif::MUTABLE_INTERIOR; - } - if !ty_qualif.contains(Qualif::NEEDS_DROP) { - *self = *self - Qualif::NEEDS_DROP; - } - } -} - /// What kind of item we are in. #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum Mode { @@ -107,264 +52,404 @@ impl fmt::Display for Mode { } } +const QUALIF_COUNT: usize = 4; + +// 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.clone(), x.clone(), x]) + } +} + +impl PerQualif { + fn as_mut(&mut self) -> PerQualif<&mut T> { + let [x0, x1, x2, x3] = &mut self.0; + PerQualif([x0, x1, x2, x3]) + } + + fn zip(self, other: PerQualif) -> PerQualif<(T, U)> { + let [x0, x1, x2, x3] = self.0; + let [y0, y1, y2, y3] = other.0; + PerQualif([(x0, y0), (x1, y1), (x2, y2), (x3, y3)]) + } +} + +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<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>, mode: Mode, mir: &'a Mir<'tcx>, - local_qualif: IndexVec, + per_local: PerQualif>, } impl<'a, 'tcx> ConstCx<'a, 'tcx> { - fn qualify_any_value_of_ty(&self, ty: Ty<'tcx>) -> Qualif { - Qualif::any_value_of_ty(ty, self.tcx, self.param_env) + 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() } +} - fn qualify_local(&self, local: Local) -> Qualif { - self.local_qualif[local] +#[derive(Copy, Clone, Debug)] +enum ValueSource<'a, 'tcx> { + Rvalue(&'a Rvalue<'tcx>), + Call { + callee: &'a Operand<'tcx>, + args: &'a [Operand<'tcx>], + return_ty: Ty<'tcx>, + }, +} + +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 qualify_projection_elem(&self, proj: &PlaceElem<'tcx>) -> Qualif { - match *proj { + 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>, + proj: &PlaceProjection<'tcx>, + ) -> bool { + let base_qualif = Self::in_place(cx, &proj.base); + let qualif = base_qualif && Self::mask_for_ty( + cx, + proj.base.ty(cx.mir, cx.tcx) + .projection_ty(cx.tcx, &proj.elem) + .to_ty(cx.tcx), + ); + match proj.elem { ProjectionElem::Deref | ProjectionElem::Subslice { .. } | ProjectionElem::Field(..) | ProjectionElem::ConstantIndex { .. } | - ProjectionElem::Downcast(..) => Qualif::empty(), + ProjectionElem::Downcast(..) => qualif, - ProjectionElem::Index(local) => self.qualify_local(local), + ProjectionElem::Index(local) => qualif || Self::in_local(cx, local), } } - fn qualify_place(&self, place: &Place<'tcx>) -> Qualif { + fn in_projection(cx: &ConstCx<'_, 'tcx>, proj: &PlaceProjection<'tcx>) -> bool { + Self::in_projection_structurally(cx, proj) + } + + fn in_place(cx: &ConstCx<'_, 'tcx>, place: &Place<'tcx>) -> bool { match *place { - Place::Local(local) => self.qualify_local(local), + Place::Local(local) => Self::in_local(cx, local), Place::Promoted(_) => bug!("qualifying already promoted MIR"), - Place::Static(ref global) => { - if self.tcx - .get_attrs(global.def_id) - .iter() - .any(|attr| attr.check_name("thread_local")) { - return Qualif::NOT_CONST; - } - - // Only allow statics (not consts) to refer to other statics. - if self.mode == Mode::Static || self.mode == Mode::StaticMut { - Qualif::empty() - } else { - Qualif::NOT_CONST - } - } - Place::Projection(ref proj) => { - let mut qualif = - self.qualify_place(&proj.base) | - self.qualify_projection_elem(&proj.elem); - match proj.elem { - ProjectionElem::Deref | - ProjectionElem::Downcast(..) => qualif | Qualif::NOT_CONST, - - ProjectionElem::ConstantIndex {..} | - ProjectionElem::Subslice {..} | - ProjectionElem::Field(..) | - ProjectionElem::Index(_) => { - let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx); - if let Some(def) = base_ty.ty_adt_def() { - if def.is_union() { - match self.mode { - Mode::Fn => qualif = qualif | Qualif::NOT_CONST, - - Mode::ConstFn | - Mode::Static | - Mode::StaticMut | - Mode::Const => {} - } - } - } - - let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx); - qualif.restrict(ty, self.tcx, self.param_env); - qualif - } - } - } + Place::Static(ref static_) => Self::in_static(cx, static_), + Place::Projection(ref proj) => Self::in_projection(cx, proj), } } - fn qualify_operand(&self, operand: &Operand<'tcx>) -> Qualif { + fn in_operand(cx: &ConstCx<'_, 'tcx>, operand: &Operand<'tcx>) -> bool { match *operand { Operand::Copy(ref place) | - Operand::Move(ref place) => self.qualify_place(place), + Operand::Move(ref place) => Self::in_place(cx, place), Operand::Constant(ref constant) => { if let ty::LazyConst::Unevaluated(def_id, _) = constant.literal { // Don't peek inside trait associated constants. - if self.tcx.trait_of_item(*def_id).is_some() { - self.qualify_any_value_of_ty(constant.ty) + if cx.tcx.trait_of_item(*def_id).is_some() { + Self::in_any_value_of_ty(cx, constant.ty).unwrap_or(false) } else { - let (bits, _) = self.tcx.at(constant.span).mir_const_qualif(*def_id); + let (bits, _) = cx.tcx.at(constant.span).mir_const_qualif(*def_id); - let mut qualif = Qualif::from_bits(bits).expect("invalid mir_const_qualif"); + 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.restrict(constant.ty, self.tcx, self.param_env); - qualif + qualif && Self::mask_for_ty(cx, constant.ty) } } else { - Qualif::empty() + false } } } } - fn qualify_rvalue(&self, rvalue: &Rvalue<'tcx>) -> Qualif { + fn in_rvalue_structurally(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { match *rvalue { - Rvalue::NullaryOp(NullOp::SizeOf, _) => Qualif::empty(), + Rvalue::NullaryOp(..) => false, + + Rvalue::Discriminant(ref place) | + Rvalue::Len(ref place) => Self::in_place(cx, place), Rvalue::Use(ref operand) | Rvalue::Repeat(ref operand, _) | - Rvalue::UnaryOp(UnOp::Neg, ref operand) | - Rvalue::UnaryOp(UnOp::Not, ref operand) | - Rvalue::Cast(CastKind::ReifyFnPointer, ref operand, _) | - Rvalue::Cast(CastKind::UnsafeFnPointer, ref operand, _) | - Rvalue::Cast(CastKind::ClosureFnPointer, ref operand, _) | - Rvalue::Cast(CastKind::Unsize, ref operand, _) => { - self.qualify_operand(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.qualify_operand(lhs) | self.qualify_operand(rhs) + Self::in_operand(cx, lhs) || Self::in_operand(cx, rhs) } - Rvalue::Discriminant(ref place) | - Rvalue::Len(ref place) => self.qualify_place(place), - - Rvalue::Ref(_, kind, ref place) => { - let mut reborrow_qualif = None; + Rvalue::Ref(_, _, ref place) => { + // Special-case reborrows to be more like a copy of the reference. if let Place::Projection(ref proj) = *place { if let ProjectionElem::Deref = proj.elem { - let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx); + let base_ty = proj.base.ty(cx.mir, cx.tcx).to_ty(cx.tcx); if let ty::Ref(..) = base_ty.sty { - reborrow_qualif = Some(self.qualify_place(&proj.base)); + return Self::in_place(cx, &proj.base); } } } - let mut qualif = reborrow_qualif.unwrap_or_else(|| { - self.qualify_place(place) - }); + Self::in_place(cx, place) + } - let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx); + 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::Call { callee, args, return_ty } => { + Self::in_call(cx, callee, args, return_ty) + } + } + } +} + +// Constant containing interior mutability (UnsafeCell). +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.mir, cx.tcx).to_ty(cx.tcx); 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. - let allowed = if self.mode == Mode::StaticMut { + if cx.mode == Mode::StaticMut { // Inside a `static mut`, &mut [...] is also allowed. match ty.sty { - ty::Array(..) | ty::Slice(_) => true, - _ => false + ty::Array(..) | ty::Slice(_) => {} + _ => return true, } } else if let ty::Array(_, len) = ty.sty { - // FIXME(eddyb) the `self.mode == Mode::Fn` condition + // FIXME(eddyb) the `cx.mode == Mode::Fn` condition // seems unnecessary, given that this is merely a ZST. - len.unwrap_usize(self.tcx) == 0 && self.mode == Mode::Fn + if !(len.unwrap_usize(cx.tcx) == 0 && cx.mode == Mode::Fn) { + return true; + } } else { - false - }; + return true; + } + } + } - if !allowed { - qualif = qualif | Qualif::MUTABLE_INTERIOR; + 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.mir, 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. +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; + } + } + } - qualif + Self::in_rvalue_structurally(cx, rvalue) + } +} + +// Not constant at all - non-`const fn` calls, asm!, +// pointer comparisons, ptr-to-int casts, etc. +struct IsNotConst; + +impl Qualif for IsNotConst { + const IDX: usize = 2; + + fn in_static(cx: &ConstCx<'_, 'tcx>, static_: &Static<'tcx>) -> bool { + // Only allow statics (not consts) to refer to other statics. + let allowed = cx.mode == Mode::Static || cx.mode == Mode::StaticMut; + + !allowed || + cx.tcx.get_attrs(static_.def_id).iter().any(|attr| attr.check_name("thread_local")) + } + + fn in_projection(cx: &ConstCx<'_, 'tcx>, proj: &PlaceProjection<'tcx>) -> bool { + match proj.elem { + ProjectionElem::Deref | + ProjectionElem::Downcast(..) => return true, + + ProjectionElem::ConstantIndex {..} | + ProjectionElem::Subslice {..} | + ProjectionElem::Index(_) => {} + + ProjectionElem::Field(..) => { + if cx.mode == Mode::Fn { + let base_ty = proj.base.ty(cx.mir, cx.tcx).to_ty(cx.tcx); + if let Some(def) = base_ty.ty_adt_def() { + if def.is_union() { + return true; + } + } + } } + } - Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => { - let mut qualif = self.qualify_operand(operand); + Self::in_projection_structurally(cx, proj) + } - let operand_ty = operand.ty(self.mir, self.tcx); + fn in_rvalue(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool { + match *rvalue { + Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) if cx.mode == Mode::Fn => { + let operand_ty = operand.ty(cx.mir, cx.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 let Mode::Fn = self.mode { - // in normal functions, mark such casts as not promotable - qualif = qualif | Qualif::NOT_CONST; - } + // in normal functions, mark such casts as not promotable + return true; } _ => {} } - - qualif } - Rvalue::BinaryOp(op, ref lhs, ref rhs) => { - let mut qualif = self.qualify_operand(lhs) | self.qualify_operand(rhs); - - if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.mir, self.tcx).sty { + Rvalue::BinaryOp(op, ref lhs, _) if cx.mode == Mode::Fn => { + if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(cx.mir, cx.tcx).sty { assert!(op == BinOp::Eq || op == BinOp::Ne || op == BinOp::Le || op == BinOp::Lt || op == BinOp::Ge || op == BinOp::Gt || op == BinOp::Offset); - if let Mode::Fn = self.mode { - // raw pointer operations are not allowed inside promoteds - qualif = qualif | Qualif::NOT_CONST; - } + // raw pointer operations are not allowed inside promoteds + return true; } - - qualif } - Rvalue::NullaryOp(NullOp::Box, _) => Qualif::NOT_CONST, - - Rvalue::Aggregate(ref kind, ref operands) => { - let mut qualif = operands.iter().map(|o| self.qualify_operand(o)) - .fold(Qualif::empty(), |a, b| a | b); - - if let AggregateKind::Adt(def, ..) = **kind { - if Some(def.did) == self.tcx.lang_items().unsafe_cell_type() { - let ty = rvalue.ty(self.mir, self.tcx); - qualif = qualif | self.qualify_any_value_of_ty(ty); - assert!(qualif.contains(Qualif::MUTABLE_INTERIOR)); - } - - if def.has_dtor(self.tcx) { - qualif = qualif | Qualif::NEEDS_DROP; - } - } + Rvalue::NullaryOp(NullOp::Box, _) => return true, - qualif - } + _ => {} } - } - 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() + Self::in_rvalue_structurally(cx, rvalue) } - fn qualify_call( - &self, + fn in_call( + cx: &ConstCx<'_, 'tcx>, callee: &Operand<'tcx>, args: &[Operand<'tcx>], - return_ty: Ty<'tcx>, - ) -> Qualif { - let fn_ty = callee.ty(self.mir, self.tcx); - let mut is_promotable_const_fn = false; - let is_const_fn = match fn_ty.sty { + _return_ty: Ty<'tcx>, + ) -> bool { + let fn_ty = callee.ty(cx.mir, cx.tcx); + match fn_ty.sty { ty::FnDef(def_id, _) => { - match self.tcx.fn_sig(def_id).abi() { + match cx.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()[..] { + assert!(!cx.tcx.is_const_fn(def_id)); + match &cx.tcx.item_name(def_id).as_str()[..] { | "size_of" | "min_align_of" | "needs_drop" @@ -389,50 +474,100 @@ impl<'a, 'tcx> ConstCx<'a, 'tcx> { | "saturating_add" | "saturating_sub" | "transmute" - => true, + => return true, - _ => false, + _ => {} } } _ => { - // Never promote runtime `const fn` calls of - // functions without `#[rustc_promotable]`. - if self.tcx.is_promotable_const_fn(def_id) { - is_promotable_const_fn = true; - } - - if self.mode == Mode::Fn { - self.tcx.is_const_fn(def_id) - } else { - self.tcx.is_const_fn(def_id) || - self.is_const_panic_fn(def_id) || - self.tcx.is_unstable_const_fn(def_id).is_some() + let is_const_fn = + cx.tcx.is_const_fn(def_id) || + cx.tcx.is_unstable_const_fn(def_id).is_some() || + cx.is_const_panic_fn(def_id); + if !is_const_fn { + return true; } } } } - _ => false, - }; - - // Bail out on oon-`const fn` calls or if the callee had errors. - if !is_const_fn || self.qualify_operand(callee).intersects(Qualif::NOT_CONST) { - return Qualif::NOT_CONST; + _ => return true, } - // Bail out if any arguments had errors. - for arg in args { - if self.qualify_operand(arg).intersects(Qualif::NOT_CONST) { - return Qualif::NOT_CONST; + Self::in_operand(cx, callee) || args.iter().any(|arg| Self::in_operand(cx, arg)) + } +} + +// Refers to temporaries which cannot be promoted as +// promote_consts decided they weren't simple enough. +struct IsNotPromotable; + +impl Qualif for IsNotPromotable { + const IDX: usize = 3; + + fn in_call( + cx: &ConstCx<'_, 'tcx>, + callee: &Operand<'tcx>, + _args: &[Operand<'tcx>], + _return_ty: Ty<'tcx>, + ) -> bool { + if cx.mode == Mode::Fn { + if let ty::FnDef(def_id, _) = callee.ty(cx.mir, cx.tcx).sty { + // Never promote runtime `const fn` calls of + // functions without `#[rustc_promotable]`. + if !cx.tcx.is_promotable_const_fn(def_id) { + return true; + } } } - // Be conservative about the returned value of a const fn. - let qualif = self.qualify_any_value_of_ty(return_ty); - if !is_promotable_const_fn && self.mode == Mode::Fn { - qualif | Qualif::NOT_PROMOTABLE - } else { - qualif - } + // FIXME(eddyb) do we need "not promotable" in anything + // other than `Mode::Fn` by any chance? + + false + } +} + +// Ensure the `IDX` values are sequential (`0..QUALIF_COUNT`). +macro_rules! static_assert_seq_qualifs { + ($i:expr => $first:ident $(, $rest:ident)*) => { + static_assert!(SEQ_QUALIFS: { + static_assert_seq_qualifs!($i + 1 => $($rest),*); + + $first::IDX == $i + }); + }; + ($i:expr =>) => { + static_assert!(SEQ_QUALIFS: QUALIF_COUNT == $i); + }; +} +static_assert_seq_qualifs!(0 => HasMutInterior, NeedsDrop, IsNotConst, IsNotPromotable); + +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[IsNotConst] = IsNotConst::in_any_value_of_ty(self, ty).unwrap_or(false); + qualifs[IsNotPromotable] = IsNotPromotable::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[IsNotConst] = IsNotConst::in_local(self, local); + qualifs[IsNotPromotable] = IsNotPromotable::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[IsNotConst] = IsNotConst::in_value(self, source); + qualifs[IsNotPromotable] = IsNotPromotable::in_value(self, source); + qualifs } } @@ -477,31 +612,40 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { let param_env = tcx.param_env(def_id); - let local_qualif = mir.local_decls.iter_enumerated().map(|(local, decl)| { + let mut cx = ConstCx { + tcx, + param_env, + mode, + mir, + per_local: PerQualif::new(BitSet::new_empty(mir.local_decls.len())), + }; + + for (local, decl) in mir.local_decls.iter_enumerated() { match mir.local_kind(local) { LocalKind::Arg => { - Qualif::any_value_of_ty(decl.ty, tcx, param_env) | - Qualif::NOT_PROMOTABLE + 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); + } + } + cx.per_local[IsNotPromotable].insert(local); } - LocalKind::Var if mode == Mode::Fn => Qualif::NOT_CONST, + LocalKind::Var if mode == Mode::Fn => { + cx.per_local[IsNotConst].insert(local); + } LocalKind::Temp if !temps[local].is_promotable() => { - Qualif::NOT_PROMOTABLE + cx.per_local[IsNotPromotable].insert(local); } - _ => Qualif::empty(), + _ => {} } - }).collect(); + } Checker { - cx: ConstCx { - tcx, - param_env, - mode, - mir, - local_qualif, - }, + cx, span: mir.span, def_id, rpo, @@ -534,23 +678,76 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { } /// Assigns an rvalue/call qualification to the given destination. - fn assign(&mut self, dest: &Place<'tcx>, qualif: Qualif, location: Location) { - trace!("assign: {:?} <- {:?}", dest, qualif); + fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location: Location) { + trace!("assign: {:?} <- {:?}", dest, source); - // Only handle promotable temps in non-const functions. - if self.mode == Mode::Fn { - if let Place::Local(index) = *dest { - if self.mir.local_kind(index) == LocalKind::Temp - && self.temp_promotion_state[index].is_promotable() { - debug!("store to promotable temp {:?} ({:?})", index, qualif); - let slot = &mut self.cx.local_qualif[index]; - if !slot.is_empty() { - span_bug!(self.span, "multiple assignments to {:?}", dest); + let mut qualifs = self.qualifs_in_value(source); + + if let ValueSource::Rvalue(&Rvalue::Ref(_, kind, ref place)) = source { + // 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 replaced with `IsNotConst`, + // to avoid duplicate errors (e.g. from reborrowing). + if qualifs[HasMutInterior] { + qualifs[HasMutInterior] = false; + qualifs[IsNotConst] = true; + + if self.mode != Mode::Fn { + 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"); + } + } + } else { + // We might have a candidate for promotion. + let candidate = Candidate::Ref(location); + // We can only promote interior borrows of promotable temps. + let mut place = place; + while let Place::Projection(ref proj) = *place { + if proj.elem == ProjectionElem::Deref { + break; + } + place = &proj.base; + } + debug!("qualify_consts: promotion candidate: place={:?}", place); + if let Place::Local(local) = *place { + if self.mir.local_kind(local) == LocalKind::Temp { + debug!("qualify_consts: promotion candidate: local={:?}", local); + // The borrowed place doesn't have `HasMutInterior` + // (from `in_rvalue`), so we can safely ignore + // `HasMutInterior` from the local's qualifications. + // This allows borrowing fields which don't have + // `HasMutInterior`, from a type that does, e.g.: + // `let _: &'static _ = &(Cell::new(1), 2).1;` + let mut local_qualifs = self.qualifs_in_local(local); + local_qualifs[HasMutInterior] = false; + if !local_qualifs.0.iter().any(|&qualif| qualif) { + debug!("qualify_consts: promotion candidate: {:?}", candidate); + self.promotion_candidates.push(candidate); + } } - *slot = qualif; } } - return; } let mut dest = dest; @@ -588,27 +785,39 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { let kind = self.mir.local_kind(index); debug!("store to {:?} {:?}", kind, index); + // Only handle promotable temps in non-const functions. + if self.mode == Mode::Fn { + if kind != LocalKind::Temp || + !self.temp_promotion_state[index].is_promotable() { + return; + } + } + // 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. - let slot = &mut self.cx.local_qualif[index]; - *slot = *slot | qualif; + for (per_local, qualif) in &mut self.cx.per_local.as_mut().zip(qualifs).0 { + if *qualif { + per_local.insert(index); + } + } - // Ensure we keep the `NOT_PROMOTABLE` flag is preserved. + // Ensure the `IsNotPromotable` qualification is preserved. // NOTE(eddyb) this is actually unnecessary right now, as - // we never replace the local's qualif (but we might in - // the future) - also, if `NOT_PROMOTABLE` only matters - // for `Mode::Fn`, then this is also pointless. + // we never replace the local's qualif, but we might in + // the future, and so it serves to catch changes that unset + // important bits (in which case, asserting `contains` could + // be replaced with calling `insert` to re-set the bit). if kind == LocalKind::Temp { if !self.temp_promotion_state[index].is_promotable() { - *slot = *slot | Qualif::NOT_PROMOTABLE; + assert!(self.cx.per_local[IsNotPromotable].contains(index)); } } } /// Check a whole const, static initializer or const fn. - fn check_const(&mut self) -> (Qualif, Lrc>) { + fn check_const(&mut self) -> (u8, Lrc>) { debug!("const-checking {} {:?}", self.mode, self.def_id); let mir = self.mir; @@ -660,14 +869,6 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { } } - let mut qualif = self.local_qualif[RETURN_PLACE]; - - // Account for errors in consts by using the - // conservative type qualification instead. - if qualif.intersects(Qualif::NOT_CONST) { - qualif = self.qualify_any_value_of_ty(mir.return_ty()); - } - // Collect all the temps we need to promote. let mut promoted_temps = BitSet::new_empty(self.temp_promotion_state.len()); @@ -687,7 +888,17 @@ impl<'a, 'tcx> Checker<'a, 'tcx> { } } - (qualif, Lrc::new(promoted_temps)) + let promoted_temps = Lrc::new(promoted_temps); + + let mut qualifs = self.qualifs_in_local(RETURN_PLACE); + + // Account for errors in consts by using the + // conservative type qualification instead. + if qualifs[IsNotConst] { + qualifs = self.qualifs_in_any_value_of_ty(mir.return_ty()); + } + + (qualifs.encode_to_bits(), promoted_temps) } } @@ -822,8 +1033,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { Operand::Move(ref place) => { // Mark the consumed locals to indicate later drops are noops. if let Place::Local(local) = *place { - let slot = &mut self.cx.local_qualif[local]; - *slot = *slot - Qualif::NEEDS_DROP; + self.cx.per_local[NeedsDrop].remove(local); } } Operand::Copy(_) | @@ -960,9 +1170,11 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { debug!("visit_terminator_kind: bb={:?} kind={:?} location={:?}", bb, kind, location); if let TerminatorKind::Call { ref func, ref args, ref destination, .. } = *kind { if let Some((ref dest, _)) = *destination { - let ty = dest.ty(self.mir, self.tcx).to_ty(self.tcx); - let qualif = self.qualify_call(func, args, ty); - self.assign(dest, qualif, location); + self.assign(dest, ValueSource::Call { + callee: func, + args, + return_ty: dest.ty(self.mir, self.tcx).to_ty(self.tcx), + }, location); } let fn_ty = func.ty(self.mir, self.tcx); @@ -1094,8 +1306,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { // which happens even without the user requesting it. // We can error out with a hard error if the argument is not // constant here. - let arg_qualif = self.qualify_operand(arg); - if (arg_qualif - Qualif::NOT_PROMOTABLE).is_empty() { + if !IsNotConst::in_operand(self, arg) { debug!("visit_terminator_kind: candidate={:?}", candidate); self.promotion_candidates.push(candidate); } else { @@ -1125,7 +1336,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { // HACK(eddyb): emulate a bit of dataflow analysis, // conservatively, that drop elaboration will do. let needs_drop = if let Place::Local(local) = *place { - if self.local_qualif[local].contains(Qualif::NEEDS_DROP) { + if NeedsDrop::in_local(self, local) { Some(self.mir.local_decls[local].source_info.span) } else { None @@ -1158,75 +1369,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> { rvalue: &Rvalue<'tcx>, location: Location) { debug!("visit_assign: dest={:?} rvalue={:?} location={:?}", dest, rvalue, location); - let mut qualif = self.qualify_rvalue(rvalue); - - if let Rvalue::Ref(_, kind, ref place) = *rvalue { - // Getting `MUTABLE_INTERIOR` from `qualify_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 `MUTABLE_INTERIOR` is replaced with `NOT_CONST`, - // to avoid duplicate errors (e.g. from reborrowing). - if qualif.contains(Qualif::MUTABLE_INTERIOR) { - qualif = (qualif - Qualif::MUTABLE_INTERIOR) | Qualif::NOT_CONST; - - if self.mode != Mode::Fn { - 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"); - } - } - } else { - // We might have a candidate for promotion. - let candidate = Candidate::Ref(location); - // We can only promote interior borrows of promotable temps. - let mut place = place; - while let Place::Projection(ref proj) = *place { - if proj.elem == ProjectionElem::Deref { - break; - } - place = &proj.base; - } - debug!("qualify_consts: promotion candidate: place={:?}", place); - if let Place::Local(local) = *place { - if self.mir.local_kind(local) == LocalKind::Temp { - debug!("qualify_consts: promotion candidate: local={:?}", local); - let qualif = self.local_qualif[local]; - // The borrowed place doesn't have `MUTABLE_INTERIOR` - // (from `qualify_rvalue`), so we can safely ignore - // `MUTABLE_INTERIOR` from the local's qualifications. - // This allows borrowing fields which don't have - // `MUTABLE_INTERIOR`, from a type that does, e.g.: - // `let _: &'static _ = &(Cell::new(1), 2).1;` - debug!("qualify_consts: promotion candidate: qualif={:?}", qualif); - if (qualif - Qualif::MUTABLE_INTERIOR).is_empty() { - debug!("qualify_consts: promotion candidate: {:?}", candidate); - self.promotion_candidates.push(candidate); - } - } - } - } - } - - self.assign(dest, qualif, location); + self.assign(dest, ValueSource::Rvalue(rvalue), location); self.visit_rvalue(rvalue, location); } @@ -1281,12 +1424,10 @@ fn mir_const_qualif<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, if mir.return_ty().references_error() { tcx.sess.delay_span_bug(mir.span, "mir_const_qualif: Mir had errors"); - return (Qualif::NOT_CONST.bits(), Lrc::new(BitSet::new_empty(0))); + return (1 << IsNotConst::IDX, Lrc::new(BitSet::new_empty(0))); } - let mut checker = Checker::new(tcx, def_id, mir, Mode::Const); - let (qualif, promoted_temps) = checker.check_const(); - (qualif.bits(), promoted_temps) + Checker::new(tcx, def_id, mir, Mode::Const).check_const() } pub struct QualifyAndPromoteConstants;