Skip to content

Constrain maximum lifetime of stack closures #7455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/librustc/middle/trans/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

// 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};
}

let llbox = PointerCast(bcx, llbox, type_of(ccx, cboxptr_ty));
// 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.
Expand Down Expand Up @@ -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;

Expand Down
111 changes: 99 additions & 12 deletions src/librustc/middle/typeck/check/regionck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) |
Expand All @@ -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, _) => {
Expand Down Expand Up @@ -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) => {
Expand All @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions src/librustc/middle/typeck/infer/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/librustc/middle/typeck/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down Expand Up @@ -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,
Expand All @@ -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)),
Expand Down
76 changes: 76 additions & 0 deletions src/test/compile-fail/regionck-closure-lifetimes.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Loading