rustc_mir: conservatively deny non-noop drops in constant contexts.

This commit is contained in:
Eduard-Mihai Burtescu 2017-07-22 15:18:52 +03:00
parent b8c05fe90b
commit 9b61771ea8
3 changed files with 83 additions and 6 deletions

View file

@ -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![]
}
@ -193,16 +195,26 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
self.add(original);
}
/// 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)), .. } => {
@ -558,11 +579,16 @@ 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 {
@ -864,6 +890,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);

View file

@ -86,8 +86,9 @@ static STATIC8: SafeStruct = SafeStruct{field1: SafeEnum::Variant1,
// This example should fail because field1 in the base struct is not safe
static STATIC9: SafeStruct = SafeStruct{field1: SafeEnum::Variant1,
..SafeStruct{field1: SafeEnum::Variant3(WithDtor),
//~^ ERROR destructors in statics are an unstable feature
//~| ERROR statics are not allowed to have destructors
field2: SafeEnum::Variant1}};
//~^^ ERROR destructors in statics are an unstable feature
struct UnsafeStruct;

View file

@ -0,0 +1,26 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(drop_types_in_const)]
struct WithDtor;
impl Drop for WithDtor {
fn drop(&mut self) {}
}
static FOO: Option<&'static WithDtor> = Some(&WithDtor);
//~^ ERROR statics are not allowed to have destructors
//~| ERROR borrowed value does not live long enoug
static BAR: i32 = (WithDtor, 0).1;
//~^ ERROR statics are not allowed to have destructors
fn main () {}