Skip to content

Commit 278ed50

Browse files
committed
auto merge of #7455 : nikomatsakis/rust/issue-7336-constrain-closure-lifetimes, r=pnkfelix
Constrain maximum lifetime of stack closures that capture variables to be limited by the innermost repeating scope. Fixes #7336. r? whomever.
2 parents e95fcfa + 3b8c5a1 commit 278ed50

File tree

6 files changed

+209
-39
lines changed

6 files changed

+209
-39
lines changed

src/librustc/middle/trans/closure.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,16 +220,24 @@ pub fn store_environment(bcx: block,
220220
// compute the type of the closure
221221
let cdata_ty = mk_closure_tys(tcx, bound_values);
222222

223-
// allocate closure in the heap
224-
let Result {bcx: bcx, val: llbox} = allocate_cbox(bcx, sigil, cdata_ty);
225-
226223
// cbox_ty has the form of a tuple: (a, b, c) we want a ptr to a
227224
// tuple. This could be a ptr in uniq or a box or on stack,
228225
// whatever.
229226
let cbox_ty = tuplify_box_ty(tcx, cdata_ty);
230227
let cboxptr_ty = ty::mk_ptr(tcx, ty::mt {ty:cbox_ty, mutbl:ast::m_imm});
228+
let llboxptr_ty = type_of(ccx, cboxptr_ty);
229+
230+
// If there are no bound values, no point in allocating anything.
231+
if bound_values.is_empty() {
232+
return ClosureResult {llbox: C_null(llboxptr_ty),
233+
cdata_ty: cdata_ty,
234+
bcx: bcx};
235+
}
231236

232-
let llbox = PointerCast(bcx, llbox, type_of(ccx, cboxptr_ty));
237+
// allocate closure in the heap
238+
let Result {bcx: bcx, val: llbox} = allocate_cbox(bcx, sigil, cdata_ty);
239+
240+
let llbox = PointerCast(bcx, llbox, llboxptr_ty);
233241
debug!("tuplify_box_ty = %s", ty_to_str(tcx, cbox_ty));
234242

235243
// Copy expr values into boxed bindings.
@@ -268,6 +276,7 @@ pub fn build_closure(bcx0: block,
268276
sigil: ast::Sigil,
269277
include_ret_handle: Option<ValueRef>) -> ClosureResult {
270278
let _icx = push_ctxt("closure::build_closure");
279+
271280
// If we need to, package up the iterator body to call
272281
let bcx = bcx0;
273282

src/librustc/middle/typeck/check/regionck.rs

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ use syntax::visit;
4848

4949
pub struct Rcx {
5050
fcx: @mut FnCtxt,
51-
errors_reported: uint
51+
errors_reported: uint,
52+
53+
// id of innermost fn or loop
54+
repeating_scope: ast::node_id,
5255
}
5356

5457
pub type rvt = visit::vt<@mut Rcx>;
@@ -78,6 +81,12 @@ impl Rcx {
7881
self.fcx.ccx.tcx
7982
}
8083

84+
pub fn set_repeating_scope(&mut self, scope: ast::node_id) -> ast::node_id {
85+
let old_scope = self.repeating_scope;
86+
self.repeating_scope = scope;
87+
old_scope
88+
}
89+
8190
pub fn resolve_type(&mut self, unresolved_ty: ty::t) -> ty::t {
8291
/*!
8392
* Try to resolve the type for the given node, returning
@@ -134,7 +143,8 @@ impl Rcx {
134143
}
135144

136145
pub fn regionck_expr(fcx: @mut FnCtxt, e: @ast::expr) {
137-
let rcx = @mut Rcx { fcx: fcx, errors_reported: 0 };
146+
let rcx = @mut Rcx { fcx: fcx, errors_reported: 0,
147+
repeating_scope: e.id };
138148
if fcx.err_count_since_creation() == 0 {
139149
// regionck assumes typeck succeeded
140150
let v = regionck_visitor();
@@ -144,7 +154,8 @@ pub fn regionck_expr(fcx: @mut FnCtxt, e: @ast::expr) {
144154
}
145155

146156
pub fn regionck_fn(fcx: @mut FnCtxt, blk: &ast::blk) {
147-
let rcx = @mut Rcx { fcx: fcx, errors_reported: 0 };
157+
let rcx = @mut Rcx { fcx: fcx, errors_reported: 0,
158+
repeating_scope: blk.node.id };
148159
if fcx.err_count_since_creation() == 0 {
149160
// regionck assumes typeck succeeded
150161
let v = regionck_visitor();
@@ -231,7 +242,8 @@ fn constrain_bindings_in_pat(pat: @ast::pat, rcx: @mut Rcx) {
231242
}
232243

233244
fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
234-
debug!("regionck::visit_expr(e=?)");
245+
debug!("regionck::visit_expr(e=%s, repeating_scope=%?)",
246+
expr.repr(rcx.fcx.tcx()), rcx.repeating_scope);
235247

236248
let has_method_map = rcx.fcx.inh.method_map.contains_key(&expr.id);
237249

@@ -274,6 +286,9 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
274286
}
275287
}
276288
}
289+
ast::expr_loop(ref body, _) => {
290+
tcx.region_maps.record_cleanup_scope(body.node.id);
291+
}
277292
ast::expr_while(cond, ref body) => {
278293
tcx.region_maps.record_cleanup_scope(cond.id);
279294
tcx.region_maps.record_cleanup_scope(body.node.id);
@@ -313,10 +328,14 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
313328
ast::expr_call(callee, ref args, _) => {
314329
constrain_callee(rcx, callee.id, expr, callee);
315330
constrain_call(rcx, callee.id, expr, None, *args, false);
331+
332+
visit::visit_expr(expr, (rcx, v));
316333
}
317334

318335
ast::expr_method_call(callee_id, arg0, _, _, ref args, _) => {
319336
constrain_call(rcx, callee_id, expr, Some(arg0), *args, false);
337+
338+
visit::visit_expr(expr, (rcx, v));
320339
}
321340

322341
ast::expr_index(callee_id, lhs, rhs) |
@@ -327,23 +346,31 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
327346
// implicit "by ref" sort of passing style here. This
328347
// should be converted to an adjustment!
329348
constrain_call(rcx, callee_id, expr, Some(lhs), [rhs], true);
349+
350+
visit::visit_expr(expr, (rcx, v));
330351
}
331352

332353
ast::expr_unary(callee_id, _, lhs) if has_method_map => {
333354
// As above.
334355
constrain_call(rcx, callee_id, expr, Some(lhs), [], true);
356+
357+
visit::visit_expr(expr, (rcx, v));
335358
}
336359

337360
ast::expr_unary(_, ast::deref, base) => {
338361
// For *a, the lifetime of a must enclose the deref
339362
let base_ty = rcx.resolve_node_type(base.id);
340363
constrain_derefs(rcx, expr, 1, base_ty);
364+
365+
visit::visit_expr(expr, (rcx, v));
341366
}
342367

343368
ast::expr_index(_, vec_expr, _) => {
344369
// For a[b], the lifetime of a must enclose the deref
345370
let vec_type = rcx.resolve_expr_type_adjusted(vec_expr);
346371
constrain_index(rcx, expr, vec_type);
372+
373+
visit::visit_expr(expr, (rcx, v));
347374
}
348375

349376
ast::expr_cast(source, _) => {
@@ -372,6 +399,8 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
372399
}
373400
_ => ()
374401
}
402+
403+
visit::visit_expr(expr, (rcx, v));
375404
}
376405

377406
ast::expr_addr_of(_, base) => {
@@ -387,29 +416,87 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
387416
let ty0 = rcx.resolve_node_type(expr.id);
388417
constrain_regions_in_type(rcx, ty::re_scope(expr.id),
389418
infer::AddrOf(expr.span), ty0);
419+
visit::visit_expr(expr, (rcx, v));
390420
}
391421

392422
ast::expr_match(discr, ref arms) => {
393423
guarantor::for_match(rcx, discr, *arms);
424+
425+
visit::visit_expr(expr, (rcx, v));
426+
}
427+
428+
ast::expr_loop_body(subexpr) => {
429+
check_expr_fn_block(rcx, subexpr, v, true);
394430
}
395431

396432
ast::expr_fn_block(*) => {
397-
// The lifetime of a block fn must not outlive the variables
398-
// it closes over
433+
check_expr_fn_block(rcx, expr, v, false);
434+
}
435+
436+
ast::expr_loop(ref body, _) => {
437+
let repeating_scope = rcx.set_repeating_scope(body.node.id);
438+
visit::visit_expr(expr, (rcx, v));
439+
rcx.set_repeating_scope(repeating_scope);
440+
}
441+
442+
ast::expr_while(cond, ref body) => {
443+
let repeating_scope = rcx.set_repeating_scope(cond.id);
444+
(v.visit_expr)(cond, (rcx, v));
445+
446+
rcx.set_repeating_scope(body.node.id);
447+
(v.visit_block)(body, (rcx, v));
448+
449+
rcx.set_repeating_scope(repeating_scope);
450+
}
451+
452+
_ => {
453+
visit::visit_expr(expr, (rcx, v));
454+
}
455+
}
456+
}
457+
458+
fn check_expr_fn_block(rcx: @mut Rcx,
459+
expr: @ast::expr,
460+
v: rvt,
461+
is_loop_body: bool) {
462+
let tcx = rcx.fcx.tcx();
463+
match expr.node {
464+
ast::expr_fn_block(_, ref body) => {
399465
let function_type = rcx.resolve_node_type(expr.id);
400466
match ty::get(function_type).sty {
401-
ty::ty_closure(ty::ClosureTy {sigil: ast::BorrowedSigil,
402-
region: region, _}) => {
403-
constrain_free_variables(rcx, region, expr);
467+
ty::ty_closure(
468+
ty::ClosureTy {
469+
sigil: ast::BorrowedSigil, region: region, _}) => {
470+
if get_freevars(tcx, expr.id).is_empty() && !is_loop_body {
471+
// No free variables means that the environment
472+
// will be NULL at runtime and hence the closure
473+
// has static lifetime.
474+
} else {
475+
// Otherwise, the closure must not outlive the
476+
// variables it closes over, nor can it
477+
// outlive the innermost repeating scope
478+
// (since otherwise that would require
479+
// infinite stack).
480+
constrain_free_variables(rcx, region, expr);
481+
let repeating_scope = ty::re_scope(rcx.repeating_scope);
482+
rcx.fcx.mk_subr(true, infer::InfStackClosure(expr.span),
483+
region, repeating_scope);
484+
}
404485
}
405486
_ => ()
406487
}
488+
489+
let repeating_scope = rcx.set_repeating_scope(body.node.id);
490+
visit::visit_expr(expr, (rcx, v));
491+
rcx.set_repeating_scope(repeating_scope);
407492
}
408493

409-
_ => ()
494+
_ => {
495+
tcx.sess.span_bug(
496+
expr.span,
497+
"Expected expr_fn_block");
498+
}
410499
}
411-
412-
visit::visit_expr(expr, (rcx, v));
413500
}
414501

415502
fn constrain_callee(rcx: @mut Rcx,

src/librustc/middle/typeck/infer/error_reporting.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,21 @@ impl ErrorReporting for InferCtxt {
231231
sup,
232232
"");
233233
}
234+
infer::InfStackClosure(span) => {
235+
self.tcx.sess.span_err(
236+
span,
237+
"closure outlives stack frame");
238+
note_and_explain_region(
239+
self.tcx,
240+
"...the closure must be valid for ",
241+
sub,
242+
"...");
243+
note_and_explain_region(
244+
self.tcx,
245+
"...but the closure's stack frame is only valid for ",
246+
sup,
247+
"");
248+
}
234249
infer::InvokeClosure(span) => {
235250
self.tcx.sess.span_err(
236251
span,

src/librustc/middle/typeck/infer/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ pub enum SubregionOrigin {
141141
// Arose from a subtyping relation
142142
Subtype(TypeTrace),
143143

144+
// Stack-allocated closures cannot outlive innermost loop
145+
// or function so as to ensure we only require finite stack
146+
InfStackClosure(span),
147+
144148
// Invocation of closure must be within its lifetime
145149
InvokeClosure(span),
146150

@@ -829,6 +833,7 @@ impl SubregionOrigin {
829833
pub fn span(&self) -> span {
830834
match *self {
831835
Subtype(a) => a.span(),
836+
InfStackClosure(a) => a,
832837
InvokeClosure(a) => a,
833838
DerefPointer(a) => a,
834839
FreeVariable(a) => a,
@@ -850,6 +855,7 @@ impl Repr for SubregionOrigin {
850855
fn repr(&self, tcx: ty::ctxt) -> ~str {
851856
match *self {
852857
Subtype(a) => fmt!("Subtype(%s)", a.repr(tcx)),
858+
InfStackClosure(a) => fmt!("InfStackClosure(%s)", a.repr(tcx)),
853859
InvokeClosure(a) => fmt!("InvokeClosure(%s)", a.repr(tcx)),
854860
DerefPointer(a) => fmt!("DerefPointer(%s)", a.repr(tcx)),
855861
FreeVariable(a) => fmt!("FreeVariable(%s)", a.repr(tcx)),
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn env<'a>(_: &'a uint, blk: &fn(p: &'a fn())) {
12+
// Test that the closure here cannot be assigned
13+
// the lifetime `'a`, which outlives the current
14+
// block.
15+
//
16+
// FIXME(#4846): The `&'a uint` parameter is needed to ensure that `'a`
17+
// is a free and not bound region name.
18+
19+
let mut state = 0;
20+
let statep = &mut state;
21+
blk(|| *statep = 1); //~ ERROR cannot infer an appropriate lifetime
22+
}
23+
24+
fn no_env_no_for<'a>(_: &'a uint, blk: &fn(p: &'a fn())) {
25+
// Test that a closure with no free variables CAN
26+
// outlive the block in which it is created.
27+
//
28+
// FIXME(#4846): The `&'a uint` parameter is needed to ensure that `'a`
29+
// is a free and not bound region name.
30+
31+
blk(|| ())
32+
}
33+
34+
fn no_env_but_for<'a>(_: &'a uint, blk: &fn(p: &'a fn() -> bool) -> bool) {
35+
// Test that a `for` loop is considered to hvae
36+
// implicit free variables.
37+
//
38+
// FIXME(#4846): The `&'a uint` parameter is needed to ensure that `'a`
39+
// is a free and not bound region name.
40+
41+
for blk { } //~ ERROR cannot infer an appropriate lifetime
42+
}
43+
44+
fn repeating_loop() {
45+
// Test that the closure cannot be created within `loop` loop and
46+
// called without, even though the state that it closes over is
47+
// external to the loop.
48+
49+
let closure;
50+
let state = 0;
51+
52+
loop {
53+
closure = || state; //~ ERROR cannot infer an appropriate lifetime
54+
break;
55+
}
56+
57+
closure();
58+
}
59+
60+
fn repeating_while() {
61+
// Test that the closure cannot be created within `while` loop and
62+
// called without, even though the state that it closes over is
63+
// external to the loop.
64+
65+
let closure;
66+
let state = 0;
67+
68+
while true {
69+
closure = || state; //~ ERROR cannot infer an appropriate lifetime
70+
break;
71+
}
72+
73+
closure();
74+
}
75+
76+
fn main() {}

0 commit comments

Comments
 (0)