From b2cac5afa36d55818f741f48029d350da35be511 Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Mon, 8 Aug 2011 19:38:19 -0700 Subject: [PATCH] Implement typestate checking for move-mode args. Un-XFAIL compile-fail/move-arg.rs. --- src/comp/middle/tstate/auxiliary.rs | 45 +++++++++++-- src/comp/middle/tstate/pre_post_conditions.rs | 29 ++++++++- src/comp/middle/tstate/states.rs | 64 +++++++++++++------ src/test/compile-fail/move-arg.rs | 4 -- 4 files changed, 110 insertions(+), 32 deletions(-) diff --git a/src/comp/middle/tstate/auxiliary.rs b/src/comp/middle/tstate/auxiliary.rs index 2b08dde75c6..92a6cf9ae74 100644 --- a/src/comp/middle/tstate/auxiliary.rs +++ b/src/comp/middle/tstate/auxiliary.rs @@ -1093,14 +1093,45 @@ fn locals_to_bindings(locals : &(@local)[]) -> binding[] { ivec::map(local_to_bindings, locals) } -fn anon_bindings(es : &(@expr)[]) -> binding[] { - fn expr_to_initializer(e : &@expr) -> initializer { - {op: init_assign, expr: e} +fn callee_modes(fcx: &fn_ctxt, callee: node_id) -> ty::mode[] { + let ty = ty::type_autoderef(fcx.ccx.tcx, + ty::node_id_to_type(fcx.ccx.tcx, callee)); + alt ty::struct(fcx.ccx.tcx, ty) { + ty::ty_fn(_, args, _, _, _) + | ty::ty_native_fn(_, args, _) { + let modes = ~[]; + for arg: ty::arg in args { + modes += ~[arg.mode]; + } + ret modes; + } + _ { + // Shouldn't happen; callee should be ty_fn. + fcx.ccx.tcx.sess.bug("non-fn callee type in callee_modes: " + + util::ppaux::ty_to_str(fcx.ccx.tcx, ty)); + } + } +} + +fn callee_arg_init_ops(fcx: &fn_ctxt, callee: node_id) -> init_op[] { + fn mode_to_op(m: &ty::mode) -> init_op { + alt m { + ty::mo_move. { init_move } + _ { init_assign } + } } - ret ivec::map(fn (e : &@expr) -> binding { - {lhs: ~[], - rhs: some(expr_to_initializer(e)) } }, - es); + ivec::map(mode_to_op, callee_modes(fcx, callee)) +} + +fn anon_bindings(ops: &init_op[], es : &(@expr)[]) -> binding[] { + let bindings: binding[] = ~[]; + let i = 0; + for op: init_op in ops { + bindings += ~[{lhs: ~[], + rhs: some({op:op, expr: es.(i)})}]; + i += 1; + } + ret bindings; } // diff --git a/src/comp/middle/tstate/pre_post_conditions.rs b/src/comp/middle/tstate/pre_post_conditions.rs index 473e1cfbf2b..bade71525e3 100644 --- a/src/comp/middle/tstate/pre_post_conditions.rs +++ b/src/comp/middle/tstate/pre_post_conditions.rs @@ -316,6 +316,18 @@ fn handle_var(fcx: &fn_ctxt, rslt: &pre_and_post, id: node_id, name: ident) { } } +fn forget_args_moved_in(fcx: &fn_ctxt, parent: &@expr, + modes: &ty::mode[], + operands: &(@expr)[]) { + let i = 0; + for mode: ty::mode in modes { + if mode == ty::mo_move { + forget_in_postcond(fcx, parent.id, operands.(i).id); + } + i += 1; + } +} + /* Fills in annotations as a side effect. Does not rebuild the expr */ fn find_pre_post_expr(fcx: &fn_ctxt, e: @expr) { let enclosing = fcx.enclosing; @@ -336,6 +348,8 @@ fn find_pre_post_expr(fcx: &fn_ctxt, e: @expr) { require(i, expr_pp(fcx.ccx, e)); } + forget_args_moved_in(fcx, e, callee_modes(fcx, operator.id), + operands); /* if this is a failing call, its postcondition sets everything */ alt controlflow_expr(fcx.ccx, operator) { @@ -347,6 +361,8 @@ fn find_pre_post_expr(fcx: &fn_ctxt, e: @expr) { let /* copy */args = operands; args += ~[operator]; find_pre_post_exprs(fcx, args, e.id); + forget_args_moved_in(fcx, e, callee_modes(fcx, operator.id), + operands); } expr_vec(args, _, _) { find_pre_post_exprs(fcx, args, e.id); } expr_path(p) { @@ -544,14 +560,21 @@ fn find_pre_post_expr(fcx: &fn_ctxt, e: @expr) { expr_bind(operator, maybe_args) { let args = ~[]; - for expr_opt: option::t[@expr] in maybe_args { + let cmodes = callee_modes(fcx, operator.id); + let modes = ~[]; + let i = 0; + for expr_opt: option::t[@expr] in maybe_args { alt expr_opt { none. {/* no-op */ } - some(expr) { args += ~[expr]; } + some(expr) { + modes += ~[cmodes.(i)]; + args += ~[expr]; + } } + i += 1; } args += ~[operator]; /* ??? order of eval? */ - + forget_args_moved_in(fcx, e, modes, args); find_pre_post_exprs(fcx, args, e.id); } expr_break. { clear_pp(expr_pp(fcx.ccx, e)); } diff --git a/src/comp/middle/tstate/states.rs b/src/comp/middle/tstate/states.rs index 0f7d920e6ce..420ea4cad8c 100644 --- a/src/comp/middle/tstate/states.rs +++ b/src/comp/middle/tstate/states.rs @@ -45,9 +45,6 @@ fn handle_move_or_copy(fcx: &fn_ctxt, post: &poststate, rhs_path: &path, // not a local -- do nothing } } - if (init_op == init_move) { - forget_in_poststate(fcx, post, rhs_id); - } } fn seq_states(fcx: &fn_ctxt, pres: &prestate, bindings: &binding[]) @@ -67,10 +64,14 @@ fn seq_states(fcx: &fn_ctxt, pres: &prestate, bindings: &binding[]) handle_move_or_copy(fcx, post, p, an_init.expr.id, i, an_init.op); } - _ {} + _ { } } set_in_poststate_ident(fcx, i.node, i.ident, post); } + // Forget the RHS if we just moved it. + if an_init.op == init_move { + forget_in_poststate(fcx, post, an_init.expr.id); + } } none { for i: inst in b.lhs { @@ -162,16 +163,24 @@ fn find_pre_post_state_two(fcx: &fn_ctxt, pres: &prestate, lhs: &@expr, } fn find_pre_post_state_call(fcx: &fn_ctxt, pres: &prestate, a: &@expr, - id: node_id, bs: &(@expr)[], cf: controlflow) -> - bool { + id: node_id, ops: &init_op[], bs: &(@expr)[], + cf: controlflow) -> bool { let changed = find_pre_post_state_expr(fcx, pres, a); - ret find_pre_post_state_exprs(fcx, expr_poststate(fcx.ccx, a), id, bs, cf) + if ivec::len(bs) != ivec::len(ops) { + fcx.ccx.tcx.sess.span_bug(a.span, + #fmt("mismatched arg lengths: \ + %u exprs vs. %u ops", + ivec::len(bs), ivec::len(ops))); + } + ret find_pre_post_state_exprs(fcx, expr_poststate(fcx.ccx, a), id, + ops, bs, cf) || changed; } fn find_pre_post_state_exprs(fcx: &fn_ctxt, pres: &prestate, id: node_id, - es: &(@expr)[], cf: controlflow) -> bool { - let rs = seq_states(fcx, pres, anon_bindings(es)); + ops: &init_op[], es: &(@expr)[], + cf: controlflow) -> bool { + let rs = seq_states(fcx, pres, anon_bindings(ops, es)); let changed = rs.changed | set_prestate_ann(fcx.ccx, id, pres); /* if this is a failing call, it sets everything as initialized */ alt cf { @@ -304,23 +313,39 @@ fn find_pre_post_state_expr(fcx: &fn_ctxt, pres: &prestate, e: @expr) -> alt e.node { expr_vec(elts, _, _) { - ret find_pre_post_state_exprs(fcx, pres, e.id, elts, return); + ret find_pre_post_state_exprs(fcx, pres, e.id, + ivec::init_elt(init_assign, + ivec::len(elts)), + elts, return); } expr_call(operator, operands) { - ret find_pre_post_state_call(fcx, pres, operator, e.id, operands, + ret find_pre_post_state_call(fcx, pres, operator, e.id, + callee_arg_init_ops(fcx, operator.id), + operands, controlflow_expr(fcx.ccx, operator)); } expr_spawn(_, _, operator, operands) { - ret find_pre_post_state_call(fcx, pres, operator, e.id, operands, - return); + ret find_pre_post_state_call(fcx, pres, operator, e.id, + callee_arg_init_ops(fcx, operator.id), + operands, return); } expr_bind(operator, maybe_args) { let args = ~[]; - for a_opt: option::t[@expr] in maybe_args { - alt a_opt { none. {/* no-op */ } some(a) { args += ~[a]; } } + let callee_ops = callee_arg_init_ops(fcx, operator.id); + let ops = ~[]; + let i = 0; + for a_opt: option::t[@expr] in maybe_args { + alt a_opt { + none. {/* no-op */ } + some(a) { + ops += ~[callee_ops.(i)]; + args += ~[a]; + } + } + i += 1; } - - ret find_pre_post_state_call(fcx, pres, operator, e.id, args, return); + ret find_pre_post_state_call(fcx, pres, operator, e.id, ops, args, + return); } expr_path(_) { ret pure_exp(fcx.ccx, e.id, pres); } expr_log(_, ex) { @@ -347,7 +372,10 @@ fn find_pre_post_state_expr(fcx: &fn_ctxt, pres: &prestate, e: @expr) -> } expr_rec(fields, maybe_base) { let changed = - find_pre_post_state_exprs(fcx, pres, e.id, field_exprs(fields), + find_pre_post_state_exprs(fcx, pres, e.id, + ivec::init_elt(init_assign, + ivec::len(fields)), + field_exprs(fields), return); alt maybe_base { none. {/* do nothing */ } diff --git a/src/test/compile-fail/move-arg.rs b/src/test/compile-fail/move-arg.rs index 1b174ea64c6..cd348231d91 100644 --- a/src/test/compile-fail/move-arg.rs +++ b/src/test/compile-fail/move-arg.rs @@ -1,7 +1,3 @@ -// xfail-stage0 -// xfail-stage1 -// xfail-stage2 -// xfail-stage3 // error-pattern: Unsatisfied precondition constraint fn test(foo: -int) { assert (foo == 10);