import ann::*; import aux::*; import tritv::{tritv_clone, tritv_set, ttrue}; import syntax::print::pprust::block_to_str; import bitvectors::*; import pat_util::*; import syntax::ast::*; import syntax::ast_util::*; import syntax::print::pprust::{expr_to_str, stmt_to_str}; import syntax::codemap::span; import middle::ty::{expr_ty, type_is_bot}; import util::common::{field_exprs, has_nonlocal_exits, may_break}; import driver::session::session; import std::map::hashmap; fn forbid_upvar(fcx: fn_ctxt, rhs_id: node_id, sp: span, t: oper_type) { alt t { oper_move { alt local_node_id_to_def(fcx, rhs_id) { some(def_upvar(_, _, _)) { fcx.ccx.tcx.sess.span_err(sp, "tried to deinitialize a variable \ declared in a different scope"); } _ { } } } _ {/* do nothing */ } } } fn handle_move_or_copy(fcx: fn_ctxt, post: poststate, rhs_path: @path, rhs_id: node_id, destlhs: dest, init_op: init_op) { forbid_upvar(fcx, rhs_id, rhs_path.span, op_to_oper_ty(init_op)); let rhs_d_id = local_node_id_to_def_id(fcx, rhs_id); alt rhs_d_id { some(rhsid) { // RHS is a local var let instrhs = {ident: path_to_ident(rhs_path), node: rhsid.node}; alt destlhs { local_dest(instlhs) { copy_in_poststate(fcx, post, instlhs, instrhs, op_to_oper_ty(init_op)); } _ {} } } _ { // not a local -- do nothing } } } fn seq_states(fcx: fn_ctxt, pres: prestate, bindings: ~[binding]) -> {changed: bool, post: poststate} { let mut changed = false; let mut post = tritv_clone(pres); for bindings.each |b| { alt b.rhs { some(an_init) { // an expression, with or without a destination changed |= find_pre_post_state_expr(fcx, post, an_init.expr) || changed; post = tritv_clone(expr_poststate(fcx.ccx, an_init.expr)); for b.lhs.each |d| { alt an_init.expr.node { expr_path(p) { handle_move_or_copy(fcx, post, p, an_init.expr.id, d, an_init.op); } _ { } } } // Forget the RHS if we just moved it. if an_init.op == init_move { forget_in_poststate(fcx, post, an_init.expr.id); } } none { } } } ret {changed: changed, post: post}; } fn find_pre_post_state_sub(fcx: fn_ctxt, pres: prestate, e: @expr, parent: node_id, c: option) -> bool { let mut changed = find_pre_post_state_expr(fcx, pres, e); changed = set_prestate_ann(fcx.ccx, parent, pres) || changed; let post = tritv_clone(expr_poststate(fcx.ccx, e)); alt c { none { } some(c1) { set_in_poststate_(bit_num(fcx, c1), post); } } changed = set_poststate_ann(fcx.ccx, parent, post) || changed; ret changed; } fn find_pre_post_state_two(fcx: fn_ctxt, pres: prestate, lhs: @expr, rhs: @expr, parent: node_id, ty: oper_type) -> bool { let mut changed = set_prestate_ann(fcx.ccx, parent, pres); changed = find_pre_post_state_expr(fcx, pres, lhs) || changed; changed = find_pre_post_state_expr(fcx, expr_poststate(fcx.ccx, lhs), rhs) || changed; forbid_upvar(fcx, rhs.id, rhs.span, ty); let post = tritv_clone(expr_poststate(fcx.ccx, rhs)); alt lhs.node { expr_path(p) { // for termination, need to make sure intermediate changes don't set // changed flag // tmp remembers "old" constraints we'd otherwise forget, // for substitution purposes let tmp = tritv_clone(post); alt ty { oper_move { if is_path(rhs) { forget_in_poststate(fcx, post, rhs.id); } forget_in_poststate(fcx, post, lhs.id); } oper_swap { forget_in_poststate(fcx, post, lhs.id); forget_in_poststate(fcx, post, rhs.id); } _ { forget_in_poststate(fcx, post, lhs.id); } } alt rhs.node { expr_path(p1) { let d = local_node_id_to_local_def_id(fcx, lhs.id); let d1 = local_node_id_to_local_def_id(fcx, rhs.id); alt d { some(id) { alt d1 { some(id1) { let instlhs = {ident: path_to_ident(p), node: id}; let instrhs = {ident: path_to_ident(p1), node: id1}; copy_in_poststate_two(fcx, tmp, post, instlhs, instrhs, ty); } _ { } } } _ { } } } _ {/* do nothing */ } } } _ { } } changed = set_poststate_ann(fcx.ccx, parent, post) || changed; ret changed; } fn find_pre_post_state_call(fcx: fn_ctxt, pres: prestate, a: @expr, id: node_id, ops: ~[init_op], bs: ~[@expr], cf: ret_style) -> bool { let mut changed = find_pre_post_state_expr(fcx, pres, a); // FIXME (#2178): This could be a typestate constraint (except we're // not using them inside the compiler, I guess... see discussion in // bug) if vec::len(bs) != vec::len(ops) { fcx.ccx.tcx.sess.span_bug(a.span, #fmt["mismatched arg lengths: \ %u exprs vs. %u ops", vec::len(bs), vec::len(ops)]); } ret find_pre_post_state_exprs(fcx, pres, id, ops, bs, cf) || changed; } fn find_pre_post_state_exprs(fcx: fn_ctxt, pres: prestate, id: node_id, ops: ~[init_op], es: ~[@expr], cf: ret_style) -> bool { let rs = seq_states(fcx, pres, arg_bindings(ops, es)); let mut changed = rs.changed | set_prestate_ann(fcx.ccx, id, pres); /* if this is a failing call, it sets everything as initialized */ alt cf { noreturn { let post = false_postcond(num_constraints(fcx.enclosing)); changed |= set_poststate_ann(fcx.ccx, id, post); } _ { changed |= set_poststate_ann(fcx.ccx, id, rs.post); } } ret changed; } fn join_then_else(fcx: fn_ctxt, antec: @expr, conseq: blk, maybe_alt: option<@expr>, id: node_id, chk: if_ty, pres: prestate) -> bool { let mut changed = set_prestate_ann(fcx.ccx, id, pres) | find_pre_post_state_expr(fcx, pres, antec); alt maybe_alt { none { alt chk { if_check { let c: sp_constr = expr_to_constr(fcx.ccx.tcx, antec); let conseq_prestate = tritv_clone(expr_poststate(fcx.ccx, antec)); tritv_set(bit_num(fcx, c.node), conseq_prestate, ttrue); changed |= find_pre_post_state_block(fcx, conseq_prestate, conseq) | set_poststate_ann(fcx.ccx, id, expr_poststate(fcx.ccx, antec)); } _ { changed |= find_pre_post_state_block(fcx, expr_poststate(fcx.ccx, antec), conseq) | set_poststate_ann(fcx.ccx, id, expr_poststate(fcx.ccx, antec)); } } } some(altern) { changed |= find_pre_post_state_expr(fcx, expr_poststate(fcx.ccx, antec), altern); let mut conseq_prestate = expr_poststate(fcx.ccx, antec); alt chk { if_check { let c: sp_constr = expr_to_constr(fcx.ccx.tcx, antec); conseq_prestate = tritv_clone(conseq_prestate); tritv_set(bit_num(fcx, c.node), conseq_prestate, ttrue); } _ { } } changed |= find_pre_post_state_block(fcx, conseq_prestate, conseq); let poststate_res = intersect_states(block_poststate(fcx.ccx, conseq), expr_poststate(fcx.ccx, altern)); /* fcx.ccx.tcx.sess.span_note(antec.span, "poststate_res = " + aux::tritv_to_str(fcx, poststate_res)); fcx.ccx.tcx.sess.span_note(antec.span, "altern poststate = " + aux::tritv_to_str(fcx, expr_poststate(fcx.ccx, altern))); fcx.ccx.tcx.sess.span_note(antec.span, "conseq poststate = " + aux::tritv_to_str(fcx, block_poststate(fcx.ccx, conseq))); */ changed |= set_poststate_ann(fcx.ccx, id, poststate_res); } } ret changed; } fn find_pre_post_state_cap_clause(fcx: fn_ctxt, e_id: node_id, pres: prestate, cap_clause: capture_clause) -> bool { let ccx = fcx.ccx; let pres_changed = set_prestate_ann(ccx, e_id, pres); let post = tritv_clone(pres); for (*cap_clause).each |cap_item| { if cap_item.is_move { forget_in_poststate(fcx, post, cap_item.id); } } ret set_poststate_ann(ccx, e_id, post) || pres_changed; } fn find_pre_post_state_expr(fcx: fn_ctxt, pres: prestate, e: @expr) -> bool { let num_constrs = num_constraints(fcx.enclosing); alt e.node { expr_new(p, _, v) { ret find_pre_post_state_two(fcx, pres, p, v, e.id, oper_pure); } expr_vstore(ee, _) { let mut changed = find_pre_post_state_expr(fcx, pres, ee); set_prestate_ann(fcx.ccx, e.id, expr_prestate(fcx.ccx, ee)); set_poststate_ann(fcx.ccx, e.id, expr_poststate(fcx.ccx, ee)); ret changed; } expr_vec(elts, _) { ret find_pre_post_state_exprs(fcx, pres, e.id, vec::from_elem(vec::len(elts), init_assign), elts, return_val); } expr_call(operator, operands, _) { #debug["hey it's a call: %s", expr_to_str(e)]; 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_path(_) { ret pure_exp(fcx.ccx, e.id, pres); } expr_log(_, lvl, ex) { ret find_pre_post_state_two(fcx, pres, lvl, ex, e.id, oper_pure); } expr_mac(_) { fcx.ccx.tcx.sess.bug("unexpanded macro"); } expr_lit(l) { ret pure_exp(fcx.ccx, e.id, pres); } expr_fn(_, _, _, cap_clause) { ret find_pre_post_state_cap_clause(fcx, e.id, pres, cap_clause); } expr_fn_block(_, _, cap_clause) { ret find_pre_post_state_cap_clause(fcx, e.id, pres, cap_clause); } expr_block(b) { ret find_pre_post_state_block(fcx, pres, b) | set_prestate_ann(fcx.ccx, e.id, pres) | set_poststate_ann(fcx.ccx, e.id, block_poststate(fcx.ccx, b)); } expr_rec(fields, maybe_base) { let exs = field_exprs(fields); let mut changed = find_pre_post_state_exprs(fcx, pres, e.id, vec::from_elem(vec::len(fields), init_assign), exs, return_val); let base_pres = alt vec::last_opt(exs) { none { pres } some(f) { expr_poststate(fcx.ccx, f) }}; option::iter(maybe_base, |base| { changed |= find_pre_post_state_expr(fcx, base_pres, base) | set_poststate_ann(fcx.ccx, e.id, expr_poststate(fcx.ccx, base)) }); ret changed; } expr_tup(elts) { ret find_pre_post_state_exprs(fcx, pres, e.id, vec::from_elem(vec::len(elts), init_assign), elts, return_val); } expr_move(lhs, rhs) { ret find_pre_post_state_two(fcx, pres, lhs, rhs, e.id, oper_move); } expr_assign(lhs, rhs) { ret find_pre_post_state_two(fcx, pres, lhs, rhs, e.id, oper_assign); } expr_swap(lhs, rhs) { ret find_pre_post_state_two(fcx, pres, lhs, rhs, e.id, oper_swap); // Could be more precise and actually swap the role of // lhs and rhs in constraints } expr_ret(maybe_ret_val) { let mut changed = set_prestate_ann(fcx.ccx, e.id, pres); /* everything is true if execution continues after a ret expression (since execution never continues locally after a ret expression */ let post = false_postcond(num_constrs); set_poststate_ann(fcx.ccx, e.id, post); alt maybe_ret_val { none {/* do nothing */ } some(ret_val) { changed |= find_pre_post_state_expr(fcx, pres, ret_val); } } ret changed; } expr_if(antec, conseq, maybe_alt) { ret join_then_else(fcx, antec, conseq, maybe_alt, e.id, plain_if, pres); } expr_binary(bop, l, r) { if lazy_binop(bop) { let mut changed = find_pre_post_state_expr(fcx, pres, l); changed |= find_pre_post_state_expr(fcx, expr_poststate(fcx.ccx, l), r); ret changed | set_prestate_ann(fcx.ccx, e.id, pres) | set_poststate_ann(fcx.ccx, e.id, expr_poststate(fcx.ccx, l)); } else { ret find_pre_post_state_two(fcx, pres, l, r, e.id, oper_pure); } } expr_assign_op(op, lhs, rhs) { ret find_pre_post_state_two(fcx, pres, lhs, rhs, e.id, oper_assign_op); } expr_while(test, body) { let loop_pres = intersect_states(block_poststate(fcx.ccx, body), pres); let mut changed = set_prestate_ann(fcx.ccx, e.id, loop_pres) | find_pre_post_state_expr(fcx, loop_pres, test) | find_pre_post_state_block(fcx, expr_poststate(fcx.ccx, test), body); /* conservative approximation: if a loop contains a break or cont, we assume nothing about the poststate */ /* which is still unsound -- see ~[Break-unsound] */ if has_nonlocal_exits(body) { ret changed | set_poststate_ann(fcx.ccx, e.id, pres); } else { let e_post = expr_poststate(fcx.ccx, test); let b_post = block_poststate(fcx.ccx, body); ret changed | set_poststate_ann(fcx.ccx, e.id, intersect_states(e_post, b_post)); } } expr_loop(body) { let loop_pres = intersect_states(block_poststate(fcx.ccx, body), pres); let mut changed = set_prestate_ann(fcx.ccx, e.id, loop_pres) | find_pre_post_state_block(fcx, loop_pres, body); /* conservative approximation: if a loop contains a break or cont, we assume nothing about the poststate (so, we set all predicates to "don't know" */ /* which is still unsound -- see ~[Break-unsound] */ if may_break(body) { /* Only do this if there are *breaks* not conts. An infinite loop with conts is still an infinite loop. We assume all preds are FALSE, not '?' -- because in the worst case, the body could invalidate all preds and deinitialize everything before breaking */ let post = empty_poststate(num_constrs); tritv::tritv_kill(post); ret changed | set_poststate_ann(fcx.ccx, e.id, post); } else { ret changed | set_poststate_ann(fcx.ccx, e.id, false_postcond(num_constrs)); } } expr_index(val, sub) { ret find_pre_post_state_two(fcx, pres, val, sub, e.id, oper_pure); } expr_alt(val, alts, _) { let mut changed = set_prestate_ann(fcx.ccx, e.id, pres) | find_pre_post_state_expr(fcx, pres, val); let e_post = expr_poststate(fcx.ccx, val); let mut a_post; if vec::len(alts) > 0u { a_post = false_postcond(num_constrs); for alts.each |an_alt| { alt an_alt.guard { some(e) { changed |= find_pre_post_state_expr(fcx, e_post, e); } _ {} } changed |= find_pre_post_state_block(fcx, e_post, an_alt.body); intersect(a_post, block_poststate(fcx.ccx, an_alt.body)); // We deliberately do *not* update changed here, because // we'd go into an infinite loop that way, and the change // gets made after the if expression. } } else { // No alts; poststate is the poststate of the test a_post = e_post; } ret changed | set_poststate_ann(fcx.ccx, e.id, a_post); } expr_field(x, _, _) | expr_loop_body(x) | expr_do_body(x) | expr_unary(_, x) | expr_addr_of(_, x) | expr_assert(x) | expr_cast(x, _) | expr_copy(x) { ret find_pre_post_state_sub(fcx, pres, x, e.id, none); } expr_fail(maybe_fail_val) { /* if execution continues after fail, then everything is true! woo! */ let post = false_postcond(num_constrs); ret set_prestate_ann(fcx.ccx, e.id, pres) | set_poststate_ann(fcx.ccx, e.id, post) | option::map_default( maybe_fail_val, false, |fail_val| find_pre_post_state_expr(fcx, pres, fail_val) ); } expr_check(_, p) { /* predicate p holds after this expression executes */ let c: sp_constr = expr_to_constr(fcx.ccx.tcx, p); ret find_pre_post_state_sub(fcx, pres, p, e.id, some(c.node)); } expr_if_check(p, conseq, maybe_alt) { ret join_then_else(fcx, p, conseq, maybe_alt, e.id, if_check, pres); } expr_break { ret pure_exp(fcx.ccx, e.id, pres); } expr_again { ret pure_exp(fcx.ccx, e.id, pres); } } } fn find_pre_post_state_stmt(fcx: fn_ctxt, pres: prestate, s: @stmt) -> bool { let stmt_ann = stmt_to_ann(fcx.ccx, *s); #debug["[ %s ]", *fcx.name]; #debug["*At beginning: stmt = %s", stmt_to_str(*s)]; #debug["*prestate = %s", tritv::to_str(stmt_ann.states.prestate)]; #debug["*poststate = %s", tritv::to_str(stmt_ann.states.prestate)]; alt s.node { stmt_decl(adecl, id) { alt adecl.node { decl_local(alocals) { set_prestate(stmt_ann, pres); let c_and_p = seq_states(fcx, pres, locals_to_bindings(fcx.ccx.tcx, alocals)); /* important to do this in one step to ensure termination (don't want to set changed to true for intermediate changes) */ let mut changed = set_poststate(stmt_ann, c_and_p.post) | c_and_p.changed; #debug["Summary: stmt = %s", stmt_to_str(*s)]; #debug["prestate = %s", tritv::to_str(stmt_ann.states.prestate)]; #debug["poststate = %s",tritv::to_str(stmt_ann.states.poststate)]; #debug["changed = %s", bool::to_str(changed)]; ret changed; } decl_item(an_item) { ret set_prestate(stmt_ann, pres) | set_poststate(stmt_ann, pres); /* the outer visitor will recurse into the item */ } } } stmt_expr(ex, _) | stmt_semi(ex, _) { let mut changed = find_pre_post_state_expr(fcx, pres, ex) | set_prestate(stmt_ann, expr_prestate(fcx.ccx, ex)) | set_poststate(stmt_ann, expr_poststate(fcx.ccx, ex)); #debug["Finally: %s", stmt_to_str(*s)]; #debug["prestate = %s", tritv::to_str(stmt_ann.states.prestate)]; #debug["poststate = %s", tritv::to_str(stmt_ann.states.poststate)]; #debug["changed = %s", bool::to_str(changed)]; ret changed; } } } /* Updates the pre- and post-states of statements in the block, returns a boolean flag saying whether any pre- or poststates changed */ fn find_pre_post_state_block(fcx: fn_ctxt, pres0: prestate, b: blk) -> bool { /* First, set the pre-states and post-states for every expression */ let mut pres = pres0; /* Iterate over each stmt. The new prestate is . The poststate consist of improving with whatever variables this stmt initializes. Then becomes the new poststate. */ let mut changed = false; for b.node.stmts.each |s| { changed |= find_pre_post_state_stmt(fcx, pres, s); pres = stmt_poststate(fcx.ccx, *s); } let mut post = pres; alt b.node.expr { none { } some(e) { changed |= find_pre_post_state_expr(fcx, pres, e); post = expr_poststate(fcx.ccx, e); } } set_prestate_ann(fcx.ccx, b.node.id, pres0); set_poststate_ann(fcx.ccx, b.node.id, post); ret changed; } fn find_pre_post_state_fn(fcx: fn_ctxt, f_decl: fn_decl, f_body: blk) -> bool { // All constraints are considered false until proven otherwise. // This ensures that intersect works correctly. kill_all_prestate(fcx, f_body.node.id); // Instantiate any constraints on the arguments so we can use them let block_pre = block_prestate(fcx.ccx, f_body); for f_decl.constraints.each |c| { let tsc = ast_constr_to_ts_constr(fcx.ccx.tcx, f_decl.inputs, c); set_in_prestate_constr(fcx, tsc, block_pre); } let mut changed = find_pre_post_state_block(fcx, block_pre, f_body); /* #error("find_pre_post_state_fn"); log(error, changed); fcx.ccx.tcx.sess.span_note(f_body.span, fcx.name); */ ret changed; } // // Local Variables: // mode: rust // fill-column: 78; // indent-tabs-mode: nil // c-basic-offset: 4 // buffer-file-coding-system: utf-8-unix // End: //