Skip to content

Commit 7eeac1b

Browse files
committed
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
2 parents 51a54b6 + c76a024 commit 7eeac1b

File tree

6 files changed

+177
-54
lines changed

6 files changed

+177
-54
lines changed

src/librustc/middle/region.rs

+50-26
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ pub struct RegionMaps {
223223
/// table, the appropriate cleanup scope is the innermost
224224
/// enclosing statement, conditional expression, or repeating
225225
/// block (see `terminating_scopes`).
226-
rvalue_scopes: NodeMap<CodeExtent>,
226+
/// In constants, None is used to indicate that certain expressions
227+
/// escape into 'static and should have no local cleanup scope.
228+
rvalue_scopes: NodeMap<Option<CodeExtent>>,
227229

228230
/// Encodes the hierarchy of fn bodies. Every fn body (including
229231
/// closures) forms its own distinct region hierarchy, rooted in
@@ -358,9 +360,11 @@ impl<'tcx> RegionMaps {
358360
self.var_map.insert(var, lifetime);
359361
}
360362

361-
fn record_rvalue_scope(&mut self, var: ast::NodeId, lifetime: CodeExtent) {
363+
fn record_rvalue_scope(&mut self, var: ast::NodeId, lifetime: Option<CodeExtent>) {
362364
debug!("record_rvalue_scope(sub={:?}, sup={:?})", var, lifetime);
363-
assert!(var != lifetime.node_id());
365+
if let Some(lifetime) = lifetime {
366+
assert!(var != lifetime.node_id());
367+
}
364368
self.rvalue_scopes.insert(var, lifetime);
365369
}
366370

@@ -389,7 +393,7 @@ impl<'tcx> RegionMaps {
389393
// check for a designated rvalue scope
390394
if let Some(&s) = self.rvalue_scopes.get(&expr_id) {
391395
debug!("temporary_scope({:?}) = {:?} [custom]", expr_id, s);
392-
return Some(s);
396+
return s;
393397
}
394398

395399
// else, locate the innermost terminating scope
@@ -803,16 +807,11 @@ fn resolve_expr<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>, expr:
803807
}
804808

805809
fn resolve_local<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>,
806-
local: &'tcx hir::Local) {
807-
debug!("resolve_local(local.id={:?},local.init={:?})",
808-
local.id,local.init.is_some());
810+
pat: Option<&'tcx hir::Pat>,
811+
init: Option<&'tcx hir::Expr>) {
812+
debug!("resolve_local(pat={:?}, init={:?})", pat, init);
809813

810-
// For convenience in trans, associate with the local-id the var
811-
// scope that will be used for any bindings declared in this
812-
// pattern.
813814
let blk_scope = visitor.cx.var_parent;
814-
let blk_scope = blk_scope.expect("locals must be within a block");
815-
visitor.region_maps.record_var_scope(local.id, blk_scope);
816815

817816
// As an exception to the normal rules governing temporary
818817
// lifetimes, initializers in a let have a temporary lifetime
@@ -872,15 +871,22 @@ fn resolve_local<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>,
872871
//
873872
// FIXME(#6308) -- Note that `[]` patterns work more smoothly post-DST.
874873

875-
if let Some(ref expr) = local.init {
874+
if let Some(expr) = init {
876875
record_rvalue_scope_if_borrow_expr(visitor, &expr, blk_scope);
877876

878-
if is_binding_pat(&local.pat) {
879-
record_rvalue_scope(visitor, &expr, blk_scope);
877+
if let Some(pat) = pat {
878+
if is_binding_pat(pat) {
879+
record_rvalue_scope(visitor, &expr, blk_scope);
880+
}
880881
}
881882
}
882883

883-
intravisit::walk_local(visitor, local);
884+
if let Some(pat) = pat {
885+
visitor.visit_pat(pat);
886+
}
887+
if let Some(expr) = init {
888+
visitor.visit_expr(expr);
889+
}
884890

885891
/// True if `pat` match the `P&` nonterminal:
886892
///
@@ -954,7 +960,7 @@ fn resolve_local<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>,
954960
fn record_rvalue_scope_if_borrow_expr<'a, 'tcx>(
955961
visitor: &mut RegionResolutionVisitor<'a, 'tcx>,
956962
expr: &hir::Expr,
957-
blk_id: CodeExtent)
963+
blk_id: Option<CodeExtent>)
958964
{
959965
match expr.node {
960966
hir::ExprAddrOf(_, ref subexpr) => {
@@ -1004,7 +1010,7 @@ fn resolve_local<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>,
10041010
/// Note: ET is intended to match "rvalues or lvalues based on rvalues".
10051011
fn record_rvalue_scope<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>,
10061012
expr: &hir::Expr,
1007-
blk_scope: CodeExtent) {
1013+
blk_scope: Option<CodeExtent>) {
10081014
let mut expr = expr;
10091015
loop {
10101016
// Note: give all the expressions matching `ET` with the
@@ -1077,12 +1083,7 @@ impl<'a, 'tcx> Visitor<'tcx> for RegionResolutionVisitor<'a, 'tcx> {
10771083

10781084
let outer_cx = self.cx;
10791085
let outer_ts = mem::replace(&mut self.terminating_scopes, NodeSet());
1080-
1081-
// Only functions have an outer terminating (drop) scope,
1082-
// while temporaries in constant initializers are 'static.
1083-
if let MirSource::Fn(_) = MirSource::from_node(self.tcx, owner_id) {
1084-
self.terminating_scopes.insert(body_id.node_id);
1085-
}
1086+
self.terminating_scopes.insert(body_id.node_id);
10861087

10871088
if let Some(root_id) = self.cx.root_id {
10881089
self.region_maps.record_fn_parent(body_id.node_id, root_id);
@@ -1100,7 +1101,30 @@ impl<'a, 'tcx> Visitor<'tcx> for RegionResolutionVisitor<'a, 'tcx> {
11001101

11011102
// The body of the every fn is a root scope.
11021103
self.cx.parent = self.cx.var_parent;
1103-
self.visit_expr(&body.value);
1104+
if let MirSource::Fn(_) = MirSource::from_node(self.tcx, owner_id) {
1105+
self.visit_expr(&body.value);
1106+
} else {
1107+
// Only functions have an outer terminating (drop) scope, while
1108+
// temporaries in constant initializers may be 'static, but only
1109+
// according to rvalue lifetime semantics, using the same
1110+
// syntactical rules used for let initializers.
1111+
//
1112+
// E.g. in `let x = &f();`, the temporary holding the result from
1113+
// the `f()` call lives for the entirety of the surrounding block.
1114+
//
1115+
// Similarly, `const X: ... = &f();` would have the result of `f()`
1116+
// live for `'static`, implying (if Drop restrictions on constants
1117+
// ever get lifted) that the value *could* have a destructor, but
1118+
// it'd get leaked instead of the destructor running during the
1119+
// evaluation of `X` (if at all allowed by CTFE).
1120+
//
1121+
// However, `const Y: ... = g(&f());`, like `let y = g(&f());`,
1122+
// would *not* let the `f()` temporary escape into an outer scope
1123+
// (i.e. `'static`), which means that after `g` returns, it drops,
1124+
// and all the associated destruction scope rules apply.
1125+
self.cx.var_parent = None;
1126+
resolve_local(self, None, Some(&body.value));
1127+
}
11041128

11051129
// Restore context we had at the start.
11061130
self.cx = outer_cx;
@@ -1120,7 +1144,7 @@ impl<'a, 'tcx> Visitor<'tcx> for RegionResolutionVisitor<'a, 'tcx> {
11201144
resolve_expr(self, ex);
11211145
}
11221146
fn visit_local(&mut self, l: &'tcx Local) {
1123-
resolve_local(self, l);
1147+
resolve_local(self, Some(&l.pat), l.init.as_ref().map(|e| &**e));
11241148
}
11251149
}
11261150

src/librustc_mir/transform/qualify_consts.rs

+63-8
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ struct Qualifier<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
120120
return_qualif: Option<Qualif>,
121121
qualif: Qualif,
122122
const_fn_arg_vars: BitVector,
123+
local_needs_drop: IndexVec<Local, Option<Span>>,
123124
temp_promotion_state: IndexVec<Local, TempState>,
124125
promotion_candidates: Vec<Candidate>
125126
}
@@ -146,6 +147,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
146147
return_qualif: None,
147148
qualif: Qualif::empty(),
148149
const_fn_arg_vars: BitVector::new(mir.local_decls.len()),
150+
local_needs_drop: IndexVec::from_elem(None, &mir.local_decls),
149151
temp_promotion_state: temps,
150152
promotion_candidates: vec![]
151153
}
@@ -193,16 +195,26 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
193195
self.add(original);
194196
}
195197

198+
/// Check for NEEDS_DROP (from an ADT or const fn call) and
199+
/// error, unless we're in a function.
200+
fn always_deny_drop(&self) {
201+
self.deny_drop_with_feature_gate_override(false);
202+
}
203+
196204
/// Check for NEEDS_DROP (from an ADT or const fn call) and
197205
/// error, unless we're in a function, or the feature-gate
198206
/// for globals with destructors is enabled.
199207
fn deny_drop(&self) {
208+
self.deny_drop_with_feature_gate_override(true);
209+
}
210+
211+
fn deny_drop_with_feature_gate_override(&self, allow_gate: bool) {
200212
if self.mode == Mode::Fn || !self.qualif.intersects(Qualif::NEEDS_DROP) {
201213
return;
202214
}
203215

204216
// Static and const fn's allow destructors, but they're feature-gated.
205-
let msg = if self.mode != Mode::Const {
217+
let msg = if allow_gate && self.mode != Mode::Const {
206218
// Feature-gate for globals with destructors is enabled.
207219
if self.tcx.sess.features.borrow().drop_types_in_const {
208220
return;
@@ -223,15 +235,16 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
223235
let mut err =
224236
struct_span_err!(self.tcx.sess, self.span, E0493, "{}", msg);
225237

226-
if self.mode != Mode::Const {
238+
if allow_gate && self.mode != Mode::Const {
227239
help!(&mut err,
228240
"in Nightly builds, add `#![feature(drop_types_in_const)]` \
229241
to the crate attributes to enable");
230242
} else {
231243
self.find_drop_implementation_method_span()
232244
.map(|span| err.span_label(span, "destructor defined here"));
233245

234-
err.span_label(self.span, "constants cannot have destructors");
246+
err.span_label(self.span,
247+
format!("{}s cannot have destructors", self.mode));
235248
}
236249

237250
err.emit();
@@ -314,6 +327,15 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
314327
return;
315328
}
316329

330+
// When initializing a local, record whether the *value* being
331+
// stored in it needs dropping, which it may not, even if its
332+
// type does, e.g. `None::<String>`.
333+
if let Lvalue::Local(local) = *dest {
334+
if qualif.intersects(Qualif::NEEDS_DROP) {
335+
self.local_needs_drop[local] = Some(self.span);
336+
}
337+
}
338+
317339
match *dest {
318340
Lvalue::Local(index) if self.mir.local_kind(index) == LocalKind::Temp => {
319341
debug!("store to temp {:?}", index);
@@ -360,7 +382,6 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
360382

361383
let target = match mir[bb].terminator().kind {
362384
TerminatorKind::Goto { target } |
363-
// Drops are considered noops.
364385
TerminatorKind::Drop { target, .. } |
365386
TerminatorKind::Assert { target, .. } |
366387
TerminatorKind::Call { destination: Some((_, target)), .. } => {
@@ -560,22 +581,32 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
560581

561582
fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
562583
match *operand {
563-
Operand::Consume(_) => {
584+
Operand::Consume(ref lvalue) => {
564585
self.nest(|this| {
565586
this.super_operand(operand, location);
566587
this.try_consume();
567588
});
589+
590+
// Mark the consumed locals to indicate later drops are noops.
591+
if let Lvalue::Local(local) = *lvalue {
592+
self.local_needs_drop[local] = None;
593+
}
568594
}
569595
Operand::Constant(ref constant) => {
570-
if let Literal::Item { def_id, substs } = constant.literal {
571-
// Don't peek inside generic (associated) constants.
572-
if substs.types().next().is_some() {
596+
if let Literal::Item { def_id, substs: _ } = constant.literal {
597+
// Don't peek inside trait associated constants.
598+
if self.tcx.trait_of_item(def_id).is_some() {
573599
self.add_type(constant.ty);
574600
} else {
575601
let bits = self.tcx.at(constant.span).mir_const_qualif(def_id);
576602

577603
let qualif = Qualif::from_bits(bits).expect("invalid mir_const_qualif");
578604
self.add(qualif);
605+
606+
// Just in case the type is more specific than
607+
// the definition, e.g. impl associated const
608+
// with type parameters, take it into account.
609+
self.qualif.restrict(constant.ty, self.tcx, self.param_env);
579610
}
580611

581612
// Let `const fn` transitively have destructors,
@@ -866,6 +897,30 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
866897
}
867898
self.assign(dest, location);
868899
}
900+
} else if let TerminatorKind::Drop { location: ref lvalue, .. } = *kind {
901+
self.super_terminator_kind(bb, kind, location);
902+
903+
// Deny *any* live drops anywhere other than functions.
904+
if self.mode != Mode::Fn {
905+
// HACK(eddyb) Emulate a bit of dataflow analysis,
906+
// conservatively, that drop elaboration will do.
907+
let needs_drop = if let Lvalue::Local(local) = *lvalue {
908+
self.local_needs_drop[local]
909+
} else {
910+
None
911+
};
912+
913+
if let Some(span) = needs_drop {
914+
let ty = lvalue.ty(self.mir, self.tcx).to_ty(self.tcx);
915+
self.add_type(ty);
916+
917+
// Use the original assignment span to be more precise.
918+
let old_span = self.span;
919+
self.span = span;
920+
self.always_deny_drop();
921+
self.span = old_span;
922+
}
923+
}
869924
} else {
870925
// Qualify any operands inside other terminators.
871926
self.super_terminator_kind(bb, kind, location);

src/librustc_passes/consts.rs

+21-16
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,14 @@ impl<'a, 'gcx> CheckCrateVisitor<'a, 'gcx> {
8787
}
8888
}
8989

90-
// Adds the worst effect out of all the values of one type.
91-
fn add_type(&mut self, ty: Ty<'gcx>) {
92-
if !ty.is_freeze(self.tcx, self.param_env, DUMMY_SP) {
93-
self.promotable = false;
94-
}
95-
96-
if ty.needs_drop(self.tcx, self.param_env) {
97-
self.promotable = false;
98-
}
90+
// Returns true iff all the values of the type are promotable.
91+
fn type_has_only_promotable_values(&mut self, ty: Ty<'gcx>) -> bool {
92+
ty.is_freeze(self.tcx, self.param_env, DUMMY_SP) &&
93+
!ty.needs_drop(self.tcx, self.param_env)
9994
}
10095

10196
fn handle_const_fn_call(&mut self, def_id: DefId, ret_ty: Ty<'gcx>) {
102-
self.add_type(ret_ty);
97+
self.promotable &= self.type_has_only_promotable_values(ret_ty);
10398

10499
self.promotable &= if let Some(fn_id) = self.tcx.hir.as_local_node_id(def_id) {
105100
FnLikeNode::from_node(self.tcx.hir.get(fn_id)).map_or(false, |fn_like| {
@@ -333,20 +328,30 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, e: &hir::Expr, node
333328
match def {
334329
Def::VariantCtor(..) | Def::StructCtor(..) |
335330
Def::Fn(..) | Def::Method(..) => {}
336-
Def::AssociatedConst(_) => v.add_type(node_ty),
337-
Def::Const(did) => {
338-
v.promotable &= if let Some(node_id) = v.tcx.hir.as_local_node_id(did) {
339-
match v.tcx.hir.expect_item(node_id).node {
340-
hir::ItemConst(_, body) => {
331+
332+
Def::Const(did) |
333+
Def::AssociatedConst(did) => {
334+
let promotable = if v.tcx.trait_of_item(did).is_some() {
335+
// Don't peek inside trait associated constants.
336+
false
337+
} else if let Some(node_id) = v.tcx.hir.as_local_node_id(did) {
338+
match v.tcx.hir.maybe_body_owned_by(node_id) {
339+
Some(body) => {
341340
v.visit_nested_body(body);
342341
v.tcx.rvalue_promotable_to_static.borrow()[&body.node_id]
343342
}
344-
_ => false
343+
None => false
345344
}
346345
} else {
347346
v.tcx.const_is_rvalue_promotable_to_static(did)
348347
};
348+
349+
// Just in case the type is more specific than the definition,
350+
// e.g. impl associated const with type parameters, check it.
351+
// Also, trait associated consts are relaxed by this.
352+
v.promotable &= promotable || v.type_has_only_promotable_values(node_ty);
349353
}
354+
350355
_ => {
351356
v.promotable = false;
352357
}

src/test/compile-fail/check-static-values-constraints.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ static STATIC8: SafeStruct = SafeStruct{field1: SafeEnum::Variant1,
8686
// This example should fail because field1 in the base struct is not safe
8787
static STATIC9: SafeStruct = SafeStruct{field1: SafeEnum::Variant1,
8888
..SafeStruct{field1: SafeEnum::Variant3(WithDtor),
89+
//~^ ERROR destructors in statics are an unstable feature
90+
//~| ERROR statics are not allowed to have destructors
8991
field2: SafeEnum::Variant1}};
90-
//~^^ ERROR destructors in statics are an unstable feature
9192

9293
struct UnsafeStruct;
9394

0 commit comments

Comments
 (0)