Ensure dataflow of a proc never looks at blocks from closed-over context.

Details: in a program like:
```
type T = proc(int) -> int; /* 4 */

pub fn outer(captured /* pat 16 */: T) -> T {
    (proc(x /* pat 23 */) {
        ((captured /* 29 */).foo((x /* 30 */)) /* 28 */)
    } /* block 27 */ /* 20 */)
} /* block 19 */ /* 12 */
```
the `captured` arg is moved from the outer fn into the inner proc (id=20).

The old dataflow analysis for flowed_move_data_moves, when looking at
the inner proc, would attempt to add a kill bit for `captured` at the
end of its scope; the problem is that it thought the end of the
`captured` arg's scope was the outer fn (id=12), even though at that
point in the analysis, the `captured` arg's scope should now be
restricted to the proc itself (id=20).

This patch fixes handling of upvars so that dataflow of a fn/proc
should never attempts to add a gen or kill bit to any NodeId outside
of the current fn/proc.  It accomplishes this by adding an `LpUpvar`
variant to `borrowck::LoanPath`, so for cases like `captured` above
will carry both their original `var_id`, as before, as well as the
`NodeId` for the closure that is capturing them.

As a drive-by fix to another occurrence of a similar bug that
nikomatsakis pointed out to me earlier, this also fixes
`gather_loans::compute_kill_scope` so that it computes the kill scope
of the `captured` arg to be block 27; that is, the block for the proc
itself (id=20).

(This is an updated version that generalizes the new loan path variant
to cover all upvars, and thus renamed the variant from `LpCopiedUpvar`
to just `LpUpvar`.)
This commit is contained in:
Felix S. Klock II 2014-06-13 17:19:29 +02:00
parent 4d82456f69
commit 263a433f19
7 changed files with 82 additions and 42 deletions

View file

@ -242,7 +242,7 @@ impl<'a> CheckLoanCtxt<'a> {
let mut loan_path = loan_path; let mut loan_path = loan_path;
loop { loop {
match *loan_path { match *loan_path {
LpVar(_) => { LpVar(_) | LpUpvar(_) => {
break; break;
} }
LpExtend(ref lp_base, _, _) => { LpExtend(ref lp_base, _, _) => {
@ -632,7 +632,7 @@ impl<'a> CheckLoanCtxt<'a> {
*/ */
match **lp { match **lp {
LpVar(_) => { LpVar(_) | LpUpvar(_) => {
// assigning to `x` does not require that `x` is initialized // assigning to `x` does not require that `x` is initialized
} }
LpExtend(ref lp_base, _, LpInterior(_)) => { LpExtend(ref lp_base, _, LpInterior(_)) => {

View file

@ -395,7 +395,8 @@ impl<'a> GatherLoanCtxt<'a> {
//! from a local variable, mark the mutability decl as necessary. //! from a local variable, mark the mutability decl as necessary.
match *loan_path { match *loan_path {
LpVar(local_id) => { LpVar(local_id) |
LpUpvar(ty::UpvarId{ var_id: local_id, closure_expr_id: _ }) => {
self.tcx().used_mut_nodes.borrow_mut().insert(local_id); self.tcx().used_mut_nodes.borrow_mut().insert(local_id);
} }
LpExtend(ref base, mc::McInherited, _) => { LpExtend(ref base, mc::McInherited, _) => {
@ -445,8 +446,8 @@ impl<'a> GatherLoanCtxt<'a> {
//! with immutable `&` pointers, because borrows of such pointers //! with immutable `&` pointers, because borrows of such pointers
//! do not require restrictions and hence do not cause a loan. //! do not require restrictions and hence do not cause a loan.
let lexical_scope = lp.kill_scope(self.bccx.tcx);
let rm = &self.bccx.tcx.region_maps; let rm = &self.bccx.tcx.region_maps;
let lexical_scope = rm.var_scope(lp.node_id());
if rm.is_subscope_of(lexical_scope, loan_scope) { if rm.is_subscope_of(lexical_scope, loan_scope) {
lexical_scope lexical_scope
} else { } else {

View file

@ -67,13 +67,23 @@ impl<'a> RestrictionsContext<'a> {
} }
mc::cat_local(local_id) | mc::cat_local(local_id) |
mc::cat_arg(local_id) | mc::cat_arg(local_id) => {
mc::cat_upvar(ty::UpvarId {var_id: local_id, ..}, _) => { // R-Variable, locally declared
// R-Variable
let lp = Rc::new(LpVar(local_id)); let lp = Rc::new(LpVar(local_id));
SafeIf(lp.clone(), vec!(lp)) SafeIf(lp.clone(), vec!(lp))
} }
mc::cat_upvar(upvar_id, _) => {
// R-Variable, captured into closure
let lp = Rc::new(LpUpvar(upvar_id));
SafeIf(lp.clone(), vec!(lp))
}
mc::cat_copied_upvar(..) => {
// FIXME(#2152) allow mutation of upvars
Safe
}
mc::cat_downcast(cmt_base) => { mc::cat_downcast(cmt_base) => {
// When we borrow the interior of an enum, we have to // When we borrow the interior of an enum, we have to
// ensure the enum itself is not mutated, because that // ensure the enum itself is not mutated, because that
@ -107,7 +117,6 @@ impl<'a> RestrictionsContext<'a> {
self.extend(result, cmt.mutbl, LpDeref(pk)) self.extend(result, cmt.mutbl, LpDeref(pk))
} }
mc::cat_copied_upvar(..) | // FIXME(#2152) allow mutation of upvars
mc::cat_static_item(..) => { mc::cat_static_item(..) => {
Safe Safe
} }

View file

@ -201,6 +201,7 @@ pub struct Loan {
#[deriving(PartialEq, Eq, Hash)] #[deriving(PartialEq, Eq, Hash)]
pub enum LoanPath { pub enum LoanPath {
LpVar(ast::NodeId), // `x` in doc.rs LpVar(ast::NodeId), // `x` in doc.rs
LpUpvar(ty::UpvarId), // `x` captured by-value into closure
LpExtend(Rc<LoanPath>, mc::MutabilityCategory, LoanPathElem) LpExtend(Rc<LoanPath>, mc::MutabilityCategory, LoanPathElem)
} }
@ -210,11 +211,25 @@ pub enum LoanPathElem {
LpInterior(mc::InteriorKind) // `LV.f` in doc.rs LpInterior(mc::InteriorKind) // `LV.f` in doc.rs
} }
pub fn closure_to_block(closure_id: ast::NodeId,
tcx: &ty::ctxt) -> ast::NodeId {
match tcx.map.get(closure_id) {
ast_map::NodeExpr(expr) => match expr.node {
ast::ExprProc(_decl, block) |
ast::ExprFnBlock(_decl, block) => { block.id }
_ => fail!("encountered non-closure id: {}", closure_id)
},
_ => fail!("encountered non-expr id: {}", closure_id)
}
}
impl LoanPath { impl LoanPath {
pub fn node_id(&self) -> ast::NodeId { pub fn kill_scope(&self, tcx: &ty::ctxt) -> ast::NodeId {
match *self { match *self {
LpVar(local_id) => local_id, LpVar(local_id) => tcx.region_maps.var_scope(local_id),
LpExtend(ref base, _, _) => base.node_id() LpUpvar(upvar_id) =>
closure_to_block(upvar_id.closure_expr_id, tcx),
LpExtend(ref base, _, _) => base.kill_scope(tcx),
} }
} }
} }
@ -234,12 +249,18 @@ pub fn opt_loan_path(cmt: &mc::cmt) -> Option<Rc<LoanPath>> {
} }
mc::cat_local(id) | mc::cat_local(id) |
mc::cat_arg(id) | mc::cat_arg(id) => {
mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: id, .. }) |
mc::cat_upvar(ty::UpvarId {var_id: id, ..}, _) => {
Some(Rc::new(LpVar(id))) Some(Rc::new(LpVar(id)))
} }
mc::cat_upvar(ty::UpvarId {var_id: id, closure_expr_id: proc_id}, _) |
mc::cat_copied_upvar(mc::CopiedUpvar { upvar_id: id,
onceness: _,
capturing_proc: proc_id }) => {
let upvar_id = ty::UpvarId{ var_id: id, closure_expr_id: proc_id };
Some(Rc::new(LpUpvar(upvar_id)))
}
mc::cat_deref(ref cmt_base, _, pk) => { mc::cat_deref(ref cmt_base, _, pk) => {
opt_loan_path(cmt_base).map(|lp| { opt_loan_path(cmt_base).map(|lp| {
Rc::new(LpExtend(lp, cmt.mutbl, LpDeref(pk))) Rc::new(LpExtend(lp, cmt.mutbl, LpDeref(pk)))
@ -693,6 +714,7 @@ impl<'a> BorrowckCtxt<'a> {
loan_path: &LoanPath, loan_path: &LoanPath,
out: &mut String) { out: &mut String) {
match *loan_path { match *loan_path {
LpUpvar(ty::UpvarId{ var_id: id, closure_expr_id: _ }) |
LpVar(id) => { LpVar(id) => {
out.push_str(ty::local_var_name_str(self.tcx, id).get()); out.push_str(ty::local_var_name_str(self.tcx, id).get());
} }
@ -734,7 +756,7 @@ impl<'a> BorrowckCtxt<'a> {
self.append_autoderefd_loan_path_to_str(&**lp_base, out) self.append_autoderefd_loan_path_to_str(&**lp_base, out)
} }
LpVar(..) | LpExtend(_, _, LpInterior(..)) => { LpVar(..) | LpUpvar(..) | LpExtend(_, _, LpInterior(..)) => {
self.append_loan_path_to_str(loan_path, out) self.append_loan_path_to_str(loan_path, out)
} }
} }
@ -796,6 +818,12 @@ impl Repr for LoanPath {
(format!("$({})", tcx.map.node_to_str(id))).to_string() (format!("$({})", tcx.map.node_to_str(id))).to_string()
} }
&LpUpvar(ty::UpvarId{ var_id, closure_expr_id }) => {
let s = tcx.map.node_to_str(var_id);
let s = format!("$({} captured by id={})", s, closure_expr_id);
s.to_string()
}
&LpExtend(ref lp, _, LpDeref(_)) => { &LpExtend(ref lp, _, LpDeref(_)) => {
(format!("{}.*", lp.repr(tcx))).to_string() (format!("{}.*", lp.repr(tcx))).to_string()
} }

View file

@ -231,7 +231,7 @@ impl MoveData {
} }
let index = match *lp { let index = match *lp {
LpVar(..) => { LpVar(..) | LpUpvar(..) => {
let index = MovePathIndex(self.paths.borrow().len()); let index = MovePathIndex(self.paths.borrow().len());
self.paths.borrow_mut().push(MovePath { self.paths.borrow_mut().push(MovePath {
@ -302,7 +302,7 @@ impl MoveData {
} }
None => { None => {
match **lp { match **lp {
LpVar(..) => { } LpVar(..) | LpUpvar(..) => { }
LpExtend(ref b, _, _) => { LpExtend(ref b, _, _) => {
self.add_existing_base_paths(b, result); self.add_existing_base_paths(b, result);
} }
@ -418,6 +418,11 @@ impl MoveData {
let path = *self.path_map.borrow().get(&path.loan_path); let path = *self.path_map.borrow().get(&path.loan_path);
self.kill_moves(path, kill_id, dfcx_moves); self.kill_moves(path, kill_id, dfcx_moves);
} }
LpUpvar(ty::UpvarId { var_id: _, closure_expr_id }) => {
let kill_id = closure_to_block(closure_expr_id, tcx);
let path = *self.path_map.borrow().get(&path.loan_path);
self.kill_moves(path, kill_id, dfcx_moves);
}
LpExtend(..) => {} LpExtend(..) => {}
} }
} }
@ -430,6 +435,10 @@ impl MoveData {
let kill_id = tcx.region_maps.var_scope(id); let kill_id = tcx.region_maps.var_scope(id);
dfcx_assign.add_kill(kill_id, assignment_index); dfcx_assign.add_kill(kill_id, assignment_index);
} }
LpUpvar(ty::UpvarId { var_id: _, closure_expr_id }) => {
let kill_id = closure_to_block(closure_expr_id, tcx);
dfcx_assign.add_kill(kill_id, assignment_index);
}
LpExtend(..) => { LpExtend(..) => {
tcx.sess.bug("var assignment for non var path"); tcx.sess.bug("var assignment for non var path");
} }

View file

@ -266,34 +266,24 @@ impl<'a, O:DataFlowOperator> DataFlowContext<'a, O> {
pub fn add_gen(&mut self, id: ast::NodeId, bit: uint) { pub fn add_gen(&mut self, id: ast::NodeId, bit: uint) {
//! Indicates that `id` generates `bit` //! Indicates that `id` generates `bit`
if self.nodeid_to_index.contains_key(&id) { debug!("{:s} add_gen(id={:?}, bit={:?})",
debug!("add_gen(id={:?}, bit={:?})", id, bit); self.analysis_name, id, bit);
let cfgidx = to_cfgidx_or_die(id, &self.nodeid_to_index); assert!(self.nodeid_to_index.contains_key(&id));
let (start, end) = self.compute_id_range(cfgidx); let cfgidx = to_cfgidx_or_die(id, &self.nodeid_to_index);
{ let (start, end) = self.compute_id_range(cfgidx);
let gens = self.gens.mut_slice(start, end); let gens = self.gens.mut_slice(start, end);
set_bit(gens, bit); set_bit(gens, bit);
}
} else {
debug!("{:s} add_gen skip (id={:?}, bit={:?}); id not in current fn",
self.analysis_name, id, bit);
}
} }
pub fn add_kill(&mut self, id: ast::NodeId, bit: uint) { pub fn add_kill(&mut self, id: ast::NodeId, bit: uint) {
//! Indicates that `id` kills `bit` //! Indicates that `id` kills `bit`
if self.nodeid_to_index.contains_key(&id) { debug!("{:s} add_kill(id={:?}, bit={:?})",
debug!("add_kill(id={:?}, bit={:?})", id, bit); self.analysis_name, id, bit);
let cfgidx = to_cfgidx_or_die(id, &self.nodeid_to_index); assert!(self.nodeid_to_index.contains_key(&id));
let (start, end) = self.compute_id_range(cfgidx); let cfgidx = to_cfgidx_or_die(id, &self.nodeid_to_index);
{ let (start, end) = self.compute_id_range(cfgidx);
let kills = self.kills.mut_slice(start, end); let kills = self.kills.mut_slice(start, end);
set_bit(kills, bit); set_bit(kills, bit);
}
} else {
debug!("{:s} add_kill skip (id={:?}, bit={:?}); id not in current fn",
self.analysis_name, id, bit);
}
} }
fn apply_gen_kill(&mut self, cfgidx: CFGIndex, bits: &mut [uint]) { fn apply_gen_kill(&mut self, cfgidx: CFGIndex, bits: &mut [uint]) {

View file

@ -97,6 +97,7 @@ pub enum categorization {
pub struct CopiedUpvar { pub struct CopiedUpvar {
pub upvar_id: ast::NodeId, pub upvar_id: ast::NodeId,
pub onceness: ast::Onceness, pub onceness: ast::Onceness,
pub capturing_proc: ast::NodeId,
} }
// different kinds of pointers: // different kinds of pointers:
@ -559,7 +560,9 @@ impl<'t,TYPER:Typer> MemCategorizationContext<'t,TYPER> {
span:span, span:span,
cat:cat_copied_upvar(CopiedUpvar { cat:cat_copied_upvar(CopiedUpvar {
upvar_id: var_id, upvar_id: var_id,
onceness: closure_ty.onceness}), onceness: closure_ty.onceness,
capturing_proc: fn_node_id,
}),
mutbl:McImmutable, mutbl:McImmutable,
ty:expr_ty ty:expr_ty
})) }))