diff options
| author | bors <bors@rust-lang.org> | 2017-08-30 20:12:25 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2017-08-30 20:12:25 +0000 |
| commit | 7eeac1b81446c6327f1827ef334eca2db7fe28f7 (patch) | |
| tree | 5bef3331d201a6eb12d723d6f4c825a8206b3bb5 /src/librustc_mir/transform | |
| parent | 51a54b6dc0b51d270d46d9d388e627201f0986db (diff) | |
| parent | c76a024121d8d92af8c5c44651bc72a177565281 (diff) | |
| download | rust-7eeac1b81446c6327f1827ef334eca2db7fe28f7.tar.gz rust-7eeac1b81446c6327f1827ef334eca2db7fe28f7.zip | |
Auto merge of #43932 - eddyb:const-scoping, r=nikomatsakis
Forward-compatibly deny drops in constants if they *could* actually run.
This is part of #40036, specifically the checks for user-defined destructor invocations on locals which *may not* have been moved away, the motivating example being:
```rust
const FOO: i32 = (HasDrop {...}, 0).1;
```
The evaluation of constant MIR will continue to create `'static` slots for more locals than is necessary (if `Storage{Live,Dead}` statements are ignored), but it shouldn't be misusable.
r? @nikomatsakis
Diffstat (limited to 'src/librustc_mir/transform')
| -rw-r--r-- | src/librustc_mir/transform/qualify_consts.rs | 71 |
1 files changed, 63 insertions, 8 deletions
diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 9126949156c..2198b533ea0 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -120,6 +120,7 @@ struct Qualifier<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { return_qualif: Option<Qualif>, qualif: Qualif, const_fn_arg_vars: BitVector, + local_needs_drop: IndexVec<Local, Option<Span>>, temp_promotion_state: IndexVec<Local, TempState>, promotion_candidates: Vec<Candidate> } @@ -146,6 +147,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { return_qualif: None, qualif: Qualif::empty(), const_fn_arg_vars: BitVector::new(mir.local_decls.len()), + local_needs_drop: IndexVec::from_elem(None, &mir.local_decls), temp_promotion_state: temps, promotion_candidates: vec![] } @@ -194,15 +196,25 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { } /// Check for NEEDS_DROP (from an ADT or const fn call) and + /// error, unless we're in a function. + fn always_deny_drop(&self) { + self.deny_drop_with_feature_gate_override(false); + } + + /// Check for NEEDS_DROP (from an ADT or const fn call) and /// error, unless we're in a function, or the feature-gate /// for globals with destructors is enabled. fn deny_drop(&self) { + self.deny_drop_with_feature_gate_override(true); + } + + fn deny_drop_with_feature_gate_override(&self, allow_gate: bool) { if self.mode == Mode::Fn || !self.qualif.intersects(Qualif::NEEDS_DROP) { return; } // Static and const fn's allow destructors, but they're feature-gated. - let msg = if self.mode != Mode::Const { + let msg = if allow_gate && self.mode != Mode::Const { // Feature-gate for globals with destructors is enabled. if self.tcx.sess.features.borrow().drop_types_in_const { return; @@ -223,7 +235,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { let mut err = struct_span_err!(self.tcx.sess, self.span, E0493, "{}", msg); - if self.mode != Mode::Const { + if allow_gate && self.mode != Mode::Const { help!(&mut err, "in Nightly builds, add `#![feature(drop_types_in_const)]` \ to the crate attributes to enable"); @@ -231,7 +243,8 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { self.find_drop_implementation_method_span() .map(|span| err.span_label(span, "destructor defined here")); - err.span_label(self.span, "constants cannot have destructors"); + err.span_label(self.span, + format!("{}s cannot have destructors", self.mode)); } err.emit(); @@ -314,6 +327,15 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { return; } + // When initializing a local, record whether the *value* being + // stored in it needs dropping, which it may not, even if its + // type does, e.g. `None::<String>`. + if let Lvalue::Local(local) = *dest { + if qualif.intersects(Qualif::NEEDS_DROP) { + self.local_needs_drop[local] = Some(self.span); + } + } + match *dest { Lvalue::Local(index) if self.mir.local_kind(index) == LocalKind::Temp => { debug!("store to temp {:?}", index); @@ -360,7 +382,6 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> { let target = match mir[bb].terminator().kind { TerminatorKind::Goto { target } | - // Drops are considered noops. TerminatorKind::Drop { target, .. } | TerminatorKind::Assert { target, .. } | TerminatorKind::Call { destination: Some((_, target)), .. } => { @@ -560,22 +581,32 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> { fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { match *operand { - Operand::Consume(_) => { + Operand::Consume(ref lvalue) => { self.nest(|this| { this.super_operand(operand, location); this.try_consume(); }); + + // Mark the consumed locals to indicate later drops are noops. + if let Lvalue::Local(local) = *lvalue { + self.local_needs_drop[local] = None; + } } Operand::Constant(ref constant) => { - if let Literal::Item { def_id, substs } = constant.literal { - // Don't peek inside generic (associated) constants. - if substs.types().next().is_some() { + if let Literal::Item { def_id, substs: _ } = 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.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); } // Let `const fn` transitively have destructors, @@ -866,6 +897,30 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> { } self.assign(dest, location); } + } else if let TerminatorKind::Drop { location: ref lvalue, .. } = *kind { + self.super_terminator_kind(bb, kind, location); + + // Deny *any* live drops anywhere other than functions. + if self.mode != Mode::Fn { + // HACK(eddyb) Emulate a bit of dataflow analysis, + // conservatively, that drop elaboration will do. + let needs_drop = if let Lvalue::Local(local) = *lvalue { + self.local_needs_drop[local] + } else { + None + }; + + if let Some(span) = needs_drop { + let ty = lvalue.ty(self.mir, self.tcx).to_ty(self.tcx); + self.add_type(ty); + + // Use the original assignment span to be more precise. + let old_span = self.span; + self.span = span; + self.always_deny_drop(); + self.span = old_span; + } + } } else { // Qualify any operands inside other terminators. self.super_terminator_kind(bb, kind, location); |
