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::codemap::span; import middle::ty::{expr_ty, type_is_bot}; import util::common::*; 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: This could be a typestate constraint 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); /* log_err("join_then_else:"); log_expr_err(*antec); log_bitv_err(fcx, expr_prestate(fcx.ccx, antec)); log_bitv_err(fcx, expr_poststate(fcx.ccx, antec)); log_block_err(conseq); log_bitv_err(fcx, block_prestate(fcx.ccx, conseq)); log_bitv_err(fcx, block_poststate(fcx.ccx, conseq)); log_err("****"); log_bitv_err(fcx, expr_precond(fcx.ccx, antec)); log_bitv_err(fcx, expr_postcond(fcx.ccx, antec)); log_bitv_err(fcx, block_precond(fcx.ccx, conseq)); log_bitv_err(fcx, block_postcond(fcx.ccx, conseq)); */ 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"); log_expr(*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_bind(operator, maybe_args) { let mut args = []; let callee_ops = callee_arg_init_ops(fcx, operator.id); let mut ops = []; let mut i = 0; for maybe_args.each {|a_opt| 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, ops, args, return_val); } 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) { /* #error("in a while loop:"); log_expr_err(*e); aux::log_tritv_err(fcx, block_poststate(fcx.ccx, body)); aux::log_tritv_err(fcx, pres); */ 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_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) { // FIXME Should factor out this code, // which also appears in find_pre_post_state_exprs /* 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_cont { 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); log(debug, "[" + fcx.name + "]"); #debug("*At beginning: stmt = "); log_stmt(*s); #debug("*prestate = "); log(debug, tritv::to_str(stmt_ann.states.prestate)); #debug("*poststate ="); log(debug, 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 = "); log_stmt(*s); #debug("prestate = "); log(debug, tritv::to_str(stmt_ann.states.prestate)); #debug("poststate ="); log(debug, tritv::to_str(stmt_ann.states.prestate)); #debug("changed ="); log(debug, 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:"); log_stmt(*s); log(debug, "prestate = "); log(debug, tritv::to_str(stmt_ann.states.prestate)); #debug("poststate ="); log(debug, (tritv::to_str(stmt_ann.states.poststate))); #debug("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); /* #error("For block:"); log_block_err(b); #error("poststate = "); log_states_err(block_states(fcx.ccx, b)); #error("pres0:"); log_tritv_err(fcx, pres0); #error("post:"); log_tritv_err(fcx, post); #error("changed = "); log(error, changed); */ 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: //