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.
This commit is contained in:
bors 2013-07-11 09:34:40 -07:00
commit 278ed50e0a
6 changed files with 209 additions and 39 deletions

View File

@ -220,16 +220,24 @@ pub fn store_environment(bcx: block,
// compute the type of the closure
let cdata_ty = mk_closure_tys(tcx, bound_values);
// allocate closure in the heap
let Result {bcx: bcx, val: llbox} = allocate_cbox(bcx, sigil, cdata_ty);
// cbox_ty has the form of a tuple: (a, b, c) we want a ptr to a
// tuple. This could be a ptr in uniq or a box or on stack,
// whatever.
let cbox_ty = tuplify_box_ty(tcx, cdata_ty);
let cboxptr_ty = ty::mk_ptr(tcx, ty::mt {ty:cbox_ty, mutbl:ast::m_imm});
let llboxptr_ty = type_of(ccx, cboxptr_ty);
let llbox = PointerCast(bcx, llbox, type_of(ccx, cboxptr_ty));
// If there are no bound values, no point in allocating anything.
if bound_values.is_empty() {
return ClosureResult {llbox: C_null(llboxptr_ty),
cdata_ty: cdata_ty,
bcx: bcx};
}
// allocate closure in the heap
let Result {bcx: bcx, val: llbox} = allocate_cbox(bcx, sigil, cdata_ty);
let llbox = PointerCast(bcx, llbox, llboxptr_ty);
debug!("tuplify_box_ty = %s", ty_to_str(tcx, cbox_ty));
// Copy expr values into boxed bindings.
@ -268,6 +276,7 @@ pub fn build_closure(bcx0: block,
sigil: ast::Sigil,
include_ret_handle: Option<ValueRef>) -> ClosureResult {
let _icx = push_ctxt("closure::build_closure");
// If we need to, package up the iterator body to call
let bcx = bcx0;

View File

@ -48,7 +48,10 @@ use syntax::visit;
pub struct Rcx {
fcx: @mut FnCtxt,
errors_reported: uint
errors_reported: uint,
// id of innermost fn or loop
repeating_scope: ast::node_id,
}
pub type rvt = visit::vt<@mut Rcx>;
@ -78,6 +81,12 @@ impl Rcx {
self.fcx.ccx.tcx
}
pub fn set_repeating_scope(&mut self, scope: ast::node_id) -> ast::node_id {
let old_scope = self.repeating_scope;
self.repeating_scope = scope;
old_scope
}
pub fn resolve_type(&mut self, unresolved_ty: ty::t) -> ty::t {
/*!
* Try to resolve the type for the given node, returning
@ -134,7 +143,8 @@ impl Rcx {
}
pub fn regionck_expr(fcx: @mut FnCtxt, e: @ast::expr) {
let rcx = @mut Rcx { fcx: fcx, errors_reported: 0 };
let rcx = @mut Rcx { fcx: fcx, errors_reported: 0,
repeating_scope: e.id };
if fcx.err_count_since_creation() == 0 {
// regionck assumes typeck succeeded
let v = regionck_visitor();
@ -144,7 +154,8 @@ pub fn regionck_expr(fcx: @mut FnCtxt, e: @ast::expr) {
}
pub fn regionck_fn(fcx: @mut FnCtxt, blk: &ast::blk) {
let rcx = @mut Rcx { fcx: fcx, errors_reported: 0 };
let rcx = @mut Rcx { fcx: fcx, errors_reported: 0,
repeating_scope: blk.node.id };
if fcx.err_count_since_creation() == 0 {
// regionck assumes typeck succeeded
let v = regionck_visitor();
@ -231,7 +242,8 @@ fn constrain_bindings_in_pat(pat: @ast::pat, rcx: @mut Rcx) {
}
fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
debug!("regionck::visit_expr(e=?)");
debug!("regionck::visit_expr(e=%s, repeating_scope=%?)",
expr.repr(rcx.fcx.tcx()), rcx.repeating_scope);
let has_method_map = rcx.fcx.inh.method_map.contains_key(&expr.id);
@ -274,6 +286,9 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
}
}
}
ast::expr_loop(ref body, _) => {
tcx.region_maps.record_cleanup_scope(body.node.id);
}
ast::expr_while(cond, ref body) => {
tcx.region_maps.record_cleanup_scope(cond.id);
tcx.region_maps.record_cleanup_scope(body.node.id);
@ -313,10 +328,14 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
ast::expr_call(callee, ref args, _) => {
constrain_callee(rcx, callee.id, expr, callee);
constrain_call(rcx, callee.id, expr, None, *args, false);
visit::visit_expr(expr, (rcx, v));
}
ast::expr_method_call(callee_id, arg0, _, _, ref args, _) => {
constrain_call(rcx, callee_id, expr, Some(arg0), *args, false);
visit::visit_expr(expr, (rcx, v));
}
ast::expr_index(callee_id, lhs, rhs) |
@ -327,23 +346,31 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
// implicit "by ref" sort of passing style here. This
// should be converted to an adjustment!
constrain_call(rcx, callee_id, expr, Some(lhs), [rhs], true);
visit::visit_expr(expr, (rcx, v));
}
ast::expr_unary(callee_id, _, lhs) if has_method_map => {
// As above.
constrain_call(rcx, callee_id, expr, Some(lhs), [], true);
visit::visit_expr(expr, (rcx, v));
}
ast::expr_unary(_, ast::deref, base) => {
// For *a, the lifetime of a must enclose the deref
let base_ty = rcx.resolve_node_type(base.id);
constrain_derefs(rcx, expr, 1, base_ty);
visit::visit_expr(expr, (rcx, v));
}
ast::expr_index(_, vec_expr, _) => {
// For a[b], the lifetime of a must enclose the deref
let vec_type = rcx.resolve_expr_type_adjusted(vec_expr);
constrain_index(rcx, expr, vec_type);
visit::visit_expr(expr, (rcx, v));
}
ast::expr_cast(source, _) => {
@ -372,6 +399,8 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
}
_ => ()
}
visit::visit_expr(expr, (rcx, v));
}
ast::expr_addr_of(_, base) => {
@ -387,29 +416,87 @@ fn visit_expr(expr: @ast::expr, (rcx, v): (@mut Rcx, rvt)) {
let ty0 = rcx.resolve_node_type(expr.id);
constrain_regions_in_type(rcx, ty::re_scope(expr.id),
infer::AddrOf(expr.span), ty0);
visit::visit_expr(expr, (rcx, v));
}
ast::expr_match(discr, ref arms) => {
guarantor::for_match(rcx, discr, *arms);
visit::visit_expr(expr, (rcx, v));
}
ast::expr_loop_body(subexpr) => {
check_expr_fn_block(rcx, subexpr, v, true);
}
ast::expr_fn_block(*) => {
// The lifetime of a block fn must not outlive the variables
// it closes over
check_expr_fn_block(rcx, expr, v, false);
}
ast::expr_loop(ref body, _) => {
let repeating_scope = rcx.set_repeating_scope(body.node.id);
visit::visit_expr(expr, (rcx, v));
rcx.set_repeating_scope(repeating_scope);
}
ast::expr_while(cond, ref body) => {
let repeating_scope = rcx.set_repeating_scope(cond.id);
(v.visit_expr)(cond, (rcx, v));
rcx.set_repeating_scope(body.node.id);
(v.visit_block)(body, (rcx, v));
rcx.set_repeating_scope(repeating_scope);
}
_ => {
visit::visit_expr(expr, (rcx, v));
}
}
}
fn check_expr_fn_block(rcx: @mut Rcx,
expr: @ast::expr,
v: rvt,
is_loop_body: bool) {
let tcx = rcx.fcx.tcx();
match expr.node {
ast::expr_fn_block(_, ref body) => {
let function_type = rcx.resolve_node_type(expr.id);
match ty::get(function_type).sty {
ty::ty_closure(ty::ClosureTy {sigil: ast::BorrowedSigil,
region: region, _}) => {
constrain_free_variables(rcx, region, expr);
ty::ty_closure(
ty::ClosureTy {
sigil: ast::BorrowedSigil, region: region, _}) => {
if get_freevars(tcx, expr.id).is_empty() && !is_loop_body {
// No free variables means that the environment
// will be NULL at runtime and hence the closure
// has static lifetime.
} else {
// Otherwise, the closure must not outlive the
// variables it closes over, nor can it
// outlive the innermost repeating scope
// (since otherwise that would require
// infinite stack).
constrain_free_variables(rcx, region, expr);
let repeating_scope = ty::re_scope(rcx.repeating_scope);
rcx.fcx.mk_subr(true, infer::InfStackClosure(expr.span),
region, repeating_scope);
}
}
_ => ()
}
let repeating_scope = rcx.set_repeating_scope(body.node.id);
visit::visit_expr(expr, (rcx, v));
rcx.set_repeating_scope(repeating_scope);
}
_ => ()
_ => {
tcx.sess.span_bug(
expr.span,
"Expected expr_fn_block");
}
}
visit::visit_expr(expr, (rcx, v));
}
fn constrain_callee(rcx: @mut Rcx,

View File

@ -231,6 +231,21 @@ impl ErrorReporting for InferCtxt {
sup,
"");
}
infer::InfStackClosure(span) => {
self.tcx.sess.span_err(
span,
"closure outlives stack frame");
note_and_explain_region(
self.tcx,
"...the closure must be valid for ",
sub,
"...");
note_and_explain_region(
self.tcx,
"...but the closure's stack frame is only valid for ",
sup,
"");
}
infer::InvokeClosure(span) => {
self.tcx.sess.span_err(
span,

View File

@ -141,6 +141,10 @@ pub enum SubregionOrigin {
// Arose from a subtyping relation
Subtype(TypeTrace),
// Stack-allocated closures cannot outlive innermost loop
// or function so as to ensure we only require finite stack
InfStackClosure(span),
// Invocation of closure must be within its lifetime
InvokeClosure(span),
@ -829,6 +833,7 @@ impl SubregionOrigin {
pub fn span(&self) -> span {
match *self {
Subtype(a) => a.span(),
InfStackClosure(a) => a,
InvokeClosure(a) => a,
DerefPointer(a) => a,
FreeVariable(a) => a,
@ -850,6 +855,7 @@ impl Repr for SubregionOrigin {
fn repr(&self, tcx: ty::ctxt) -> ~str {
match *self {
Subtype(a) => fmt!("Subtype(%s)", a.repr(tcx)),
InfStackClosure(a) => fmt!("InfStackClosure(%s)", a.repr(tcx)),
InvokeClosure(a) => fmt!("InvokeClosure(%s)", a.repr(tcx)),
DerefPointer(a) => fmt!("DerefPointer(%s)", a.repr(tcx)),
FreeVariable(a) => fmt!("FreeVariable(%s)", a.repr(tcx)),

View File

@ -0,0 +1,76 @@
// Copyright 2013 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.
fn env<'a>(_: &'a uint, blk: &fn(p: &'a fn())) {
// Test that the closure here cannot be assigned
// the lifetime `'a`, which outlives the current
// block.
//
// FIXME(#4846): The `&'a uint` parameter is needed to ensure that `'a`
// is a free and not bound region name.
let mut state = 0;
let statep = &mut state;
blk(|| *statep = 1); //~ ERROR cannot infer an appropriate lifetime
}
fn no_env_no_for<'a>(_: &'a uint, blk: &fn(p: &'a fn())) {
// Test that a closure with no free variables CAN
// outlive the block in which it is created.
//
// FIXME(#4846): The `&'a uint` parameter is needed to ensure that `'a`
// is a free and not bound region name.
blk(|| ())
}
fn no_env_but_for<'a>(_: &'a uint, blk: &fn(p: &'a fn() -> bool) -> bool) {
// Test that a `for` loop is considered to hvae
// implicit free variables.
//
// FIXME(#4846): The `&'a uint` parameter is needed to ensure that `'a`
// is a free and not bound region name.
for blk { } //~ ERROR cannot infer an appropriate lifetime
}
fn repeating_loop() {
// Test that the closure cannot be created within `loop` loop and
// called without, even though the state that it closes over is
// external to the loop.
let closure;
let state = 0;
loop {
closure = || state; //~ ERROR cannot infer an appropriate lifetime
break;
}
closure();
}
fn repeating_while() {
// Test that the closure cannot be created within `while` loop and
// called without, even though the state that it closes over is
// external to the loop.
let closure;
let state = 0;
while true {
closure = || state; //~ ERROR cannot infer an appropriate lifetime
break;
}
closure();
}
fn main() {}

View File

@ -1,23 +0,0 @@
// Copyright 2013 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.
// xfail-test
// FIXME: #7336: codegen bug makes this segfault on Linux x86_64
fn foo<'a>(blk: &fn(p: &'a fn() -> &'a fn())) {
let mut state = 0;
let statep = &mut state;
do blk {
|| { *statep = 1; }
}
}
fn main() {
do foo |p| { p()() }
}