diff --git a/src/librustsyntax/ast.rs b/src/librustsyntax/ast.rs index 05a5a17b2f7..3404f277e8d 100644 --- a/src/librustsyntax/ast.rs +++ b/src/librustsyntax/ast.rs @@ -351,7 +351,7 @@ enum expr_ { } #[auto_serialize] -type capture_item = { +type capture_item = @{ id: int, is_move: bool, name: ident, // Currently, can only capture a local var. diff --git a/src/librustsyntax/parse/parser.rs b/src/librustsyntax/parse/parser.rs index c81d9ace376..51b26373c36 100644 --- a/src/librustsyntax/parse/parser.rs +++ b/src/librustsyntax/parse/parser.rs @@ -434,7 +434,7 @@ class parser { fn parse_capture_item(p:parser, is_move: bool) -> capture_item { let sp = mk_sp(p.span.lo, p.span.hi); let ident = parse_ident(p); - {id: p.get_id(), is_move: is_move, name: ident, span: sp} + @{id: p.get_id(), is_move: is_move, name: ident, span: sp} } if eat_keyword(self, "move") { @@ -1710,7 +1710,7 @@ class parser { let id = p.get_id(); let sp = mk_sp(p.span.lo, p.span.hi); let ident = parse_ident(p); - res += [{id:id, is_move: is_move, name:ident, span:sp}]; + res += [@{id:id, is_move: is_move, name:ident, span:sp}]; if !eat(p, token::COMMA) { ret res; } diff --git a/src/rustc/driver/driver.rs b/src/rustc/driver/driver.rs index 82a42700ea1..0cb99af2141 100644 --- a/src/rustc/driver/driver.rs +++ b/src/rustc/driver/driver.rs @@ -192,6 +192,9 @@ fn compile_upto(sess: session, cfg: ast::crate_cfg, bind middle::check_loop::check_crate(ty_cx, crate)); time(time_passes, "alt checking", bind middle::check_alt::check_crate(ty_cx, crate)); + let _last_use_map = + time(time_passes, "liveness checking", + bind middle::liveness::check_crate(ty_cx, method_map, crate)); time(time_passes, "typestate checking", bind middle::tstate::ck::check_crate(ty_cx, crate)); let (root_map, mutbl_map) = time( diff --git a/src/rustc/middle/borrowck.rs b/src/rustc/middle/borrowck.rs index 85ee5b1c957..8cf6671a3d4 100644 --- a/src/rustc/middle/borrowck.rs +++ b/src/rustc/middle/borrowck.rs @@ -541,6 +541,15 @@ enum assignment_type { } impl methods for assignment_type { + fn checked_by_liveness() -> bool { + // the liveness pass guarantees that immutable local variables + // are only assigned once; but it doesn't consider &mut + alt self { + at_straight_up {true} + at_swap {true} + at_mutbl_ref {false} + } + } fn ing_form(desc: str) -> str { alt self { at_straight_up { "assigning to " + desc } @@ -717,6 +726,13 @@ impl methods for check_loan_ctxt { } } + fn is_local_variable(cmt: cmt) -> bool { + alt cmt.cat { + cat_local(_) {true} + _ {false} + } + } + fn is_self_field(cmt: cmt) -> bool { alt cmt.cat { cat_comp(cmt_base, comp_field(_)) { @@ -735,9 +751,13 @@ impl methods for check_loan_ctxt { #debug["check_assignment(cmt=%s)", self.bccx.cmt_to_repr(cmt)]; - // check that the lvalue `ex` is assignable, but be careful - // because assigning to self.foo in a ctor is always allowed. - if !self.in_ctor || !self.is_self_field(cmt) { + if self.in_ctor && self.is_self_field(cmt) + && at.checked_by_liveness() { + // assigning to self.foo in a ctor is always allowed. + } else if self.is_local_variable(cmt) && at.checked_by_liveness() { + // liveness guarantees that immutable local variables + // are only assigned once + } else { alt cmt.mutbl { m_mutbl { /*ok*/ } m_const | m_imm { diff --git a/src/rustc/middle/capture.rs b/src/rustc/middle/capture.rs index 84da4ee2136..d6edd2b935d 100644 --- a/src/rustc/middle/capture.rs +++ b/src/rustc/middle/capture.rs @@ -1,5 +1,6 @@ import syntax::{ast, ast_util}; import driver::session::session; +import syntax::codemap::span; import std::map; import std::map::hashmap; @@ -14,15 +15,17 @@ export cap_drop; export cap_ref; enum capture_mode { - cap_copy, //< Copy the value into the closure. - cap_move, //< Move the value into the closure. - cap_drop, //< Drop value after creating closure. - cap_ref, //< Reference directly from parent stack frame (block fn). + cap_copy, // Copy the value into the closure. + cap_move, // Move the value into the closure. + cap_drop, // Drop value after creating closure. + cap_ref, // Reference directly from parent stack frame (block fn). } type capture_var = { - def: ast::def, //< The variable being accessed free. - mode: capture_mode //< How is the variable being accessed. + def: ast::def, // Variable being accessed free + span: span, // Location of access or cap item + cap_item: option, // Capture item, if any + mode: capture_mode // How variable is being accessed }; type capture_map = map::hashmap; @@ -70,15 +73,24 @@ fn compute_capture_vars(tcx: ty::ctxt, // if we are moving the value in, but it's not actually used, // must drop it. if vec::any(*freevars, {|fv| fv.def == cap_def}) { - cap_map.insert(cap_def_id, { def:cap_def, mode:cap_move }); + cap_map.insert(cap_def_id, {def:cap_def, + span: cap_item.span, + cap_item: some(cap_item), + mode:cap_move}); } else { - cap_map.insert(cap_def_id, { def:cap_def, mode:cap_drop }); + cap_map.insert(cap_def_id, {def:cap_def, + span: cap_item.span, + cap_item: some(cap_item), + mode:cap_drop}); } } else { // if we are copying the value in, but it's not actually used, // just ignore it. if vec::any(*freevars, {|fv| fv.def == cap_def}) { - cap_map.insert(cap_def_id, { def:cap_def, mode:cap_copy }); + cap_map.insert(cap_def_id, {def:cap_def, + span: cap_item.span, + cap_item: some(cap_item), + mode:cap_copy}); } } } @@ -96,7 +108,10 @@ fn compute_capture_vars(tcx: ty::ctxt, alt cap_map.find(fvar_def_id) { option::some(_) { /* was explicitly named, do nothing */ } option::none { - cap_map.insert(fvar_def_id, {def:fvar.def, mode:implicit_mode}); + cap_map.insert(fvar_def_id, {def:fvar.def, + span: fvar.span, + cap_item: none, + mode:implicit_mode}); } } } diff --git a/src/rustc/middle/liveness.rs b/src/rustc/middle/liveness.rs new file mode 100644 index 00000000000..d95c58c15c8 --- /dev/null +++ b/src/rustc/middle/liveness.rs @@ -0,0 +1,1502 @@ +/* + +A classic liveness analysis based on dataflow over the AST. Computes, +for each local variable in a function, whether that variable is live +at a given point. Program execution points are identified by their +id. + +# Basic idea + +The basic model is that each local variable is assigned an index. We +represent sets of local variables using a vector indexed by this +index. The value in the vector is either 0, indicating the variable +is dead, or the id of an expression that uses the variable. + +We conceptually walk over the AST in reverse execution order. If we +find a use of a variable, we add it to the set of live variables. If +we find an assignment to a variable, we remove it from the set of live +variables. When we have to merge two flows, we take the union of +those two flows---if the variable is live on both paths, we simply +pick one id. In the event of loops, we continue doing this until a +fixed point is reached. + +## Checking initialization + +At the function entry point, all variables must be dead. If this is +not the case, we can report an error using the id found in the set of +live variables, which identifies a use of the variable which is not +dominated by an assignment. + +## Checking moves + +After each explicit move, the variable must be dead. + +## Computing last uses + +Any use of the variable where the variable is dead afterwards is a +last use. + +# Extension to handle constructors + +Each field is assigned an index just as with local variables. A use of +`self` is considered a use of all fields. A use of `self.f` is just a use +of `f`. + +*/ + +import dvec::{dvec, extensions}; +import std::map::{hashmap, int_hash, str_hash}; +import syntax::{visit, ast_util}; +import syntax::print::pprust::{expr_to_str}; +import visit::vt; +import syntax::codemap::span; +import syntax::ast::*; +import driver::session::session; +import io::writer_util; +import capture::{cap_move, cap_drop, cap_copy, cap_ref}; + +export check_crate; +export last_use_map; + +type last_use_map = hashmap>; + +enum variable = uint; +enum live_node = uint; + +enum live_node_kind { + lnk_freevar(span), + lnk_expr(span), + lnk_vdef(span), + lnk_exit +} + +fn check_crate(tcx: ty::ctxt, + method_map: typeck::method_map, + crate: @crate) -> last_use_map { + let visitor = visit::mk_vt(@{ + visit_fn: visit_fn, + visit_local: visit_local, + visit_expr: visit_expr + with *visit::default_visitor() + }); + + let last_use_map = int_hash(); + let initial_maps = @ir_maps(tcx, method_map, last_use_map); + visit::visit_crate(*crate, initial_maps, visitor); + tcx.sess.abort_if_errors(); + ret last_use_map; +} + +impl of to_str::to_str for live_node { + fn to_str() -> str { #fmt["ln(%u)", *self] } +} + +impl of to_str::to_str for variable { + fn to_str() -> str { #fmt["v(%u)", *self] } +} + +// ______________________________________________________________________ +// Creating ir_maps +// +// This is the first pass and the one that drives the main +// computation. It walks up and down the IR once. On the way down, +// we count for each function the number of variables as well as +// liveness nodes. A liveness node is basically an expression or +// capture clause that does something of interest: either it has +// interesting control flow or it uses/defines a local variable. +// +// On the way back up, at each function node we create liveness sets +// (we now know precisely how big to make our various vectors and so +// forth) and then do the data-flow propagation to compute the set +// of live variables at each program point. +// +// Finally, we run back over the IR one last time and, using the +// computed liveness, check various safety conditions. For example, +// there must be no live nodes at the definition site for a variable +// unless it has an initializer. Similarly, each non-mutable local +// variable must not be assigned if there is some successor +// assignment. And so forth. + +impl methods for live_node { + fn is_valid() -> bool { *self != uint::max_value } +} + +fn invalid_node() -> live_node { live_node(uint::max_value) } + +enum relevant_def { rdef_var(node_id), rdef_self } + +type capture_info = {ln: live_node, is_move: bool, rv: relevant_def}; + +type var_info = {id: node_id, name: str}; + +fn relevant_def(def: def) -> option { + alt def { + def_self(_) {some(rdef_self)} + def_arg(nid, _) | def_local(nid, _) {some(rdef_var(nid))} + _ {none} + } +} + +class ir_maps { + let tcx: ty::ctxt; + let method_map: typeck::method_map; + let last_use_map: last_use_map; + + let mut num_live_nodes: uint; + let mut num_vars: uint; + let live_node_map: hashmap; + let variable_map: hashmap; + let field_map: hashmap; + let capture_map: hashmap; + let mut var_infos: [var_info]; + let mut lnks: [live_node_kind]; + + new(tcx: ty::ctxt, method_map: typeck::method_map, + last_use_map: hashmap>) { + self.tcx = tcx; + self.method_map = method_map; + self.last_use_map = last_use_map; + + self.num_live_nodes = 0u; + self.num_vars = 0u; + self.live_node_map = int_hash(); + self.variable_map = int_hash(); + self.capture_map = int_hash(); + self.field_map = str_hash(); + self.var_infos = []; + self.lnks = []; + } + + fn add_live_node(lnk: live_node_kind) -> live_node { + let ln = live_node(self.num_live_nodes); + self.lnks += [lnk]; + self.num_live_nodes += 1u; + + #debug["%s is of kind %?", ln.to_str(), lnk]; + + ln + } + + fn add_live_node_for_node(node_id: node_id, lnk: live_node_kind) { + let ln = self.add_live_node(lnk); + self.live_node_map.insert(node_id, ln); + + #debug["%s is node %d", ln.to_str(), node_id]; + } + + fn add_variable(node_id: node_id, name: str) -> variable { + let v = variable(self.num_vars); + self.variable_map.insert(node_id, v); + self.var_infos += [{id:node_id, name:name}]; + self.num_vars += 1u; + + #debug["%s is node %d", v.to_str(), node_id]; + + v + } + + fn variable(node_id: node_id, span: span) -> variable { + alt self.variable_map.find(node_id) { + some(var) {var} + none { + self.tcx.sess.span_bug( + span, "No variable registered for this id"); + } + } + } + + fn set_captures(node_id: node_id, +cs: [capture_info]) { + self.capture_map.insert(node_id, @cs); + } + + fn captures(expr: @expr) -> @[capture_info] { + alt self.capture_map.find(expr.id) { + some(caps) {caps} + none { + self.tcx.sess.span_bug(expr.span, "no registered caps"); + } + } + } + + fn lnk(ln: live_node) -> live_node_kind { + self.lnks[*ln] + } + + fn add_last_use(expr_id: node_id, var: variable) { + let v = alt self.last_use_map.find(expr_id) { + some(v) { v } + none { + let v = @dvec(); + self.last_use_map.insert(expr_id, v); + v + } + }; + let {id, name} = self.var_infos[*var]; + #debug["Node %d is a last use of variable %d / %s", + expr_id, id, name]; + (*v).push(id); + } +} + +fn visit_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, + sp: span, id: node_id, &&self: @ir_maps, v: vt<@ir_maps>) { + #debug["visit_fn: id=%d", id]; + let _i = util::common::indenter(); + + // swap in a new set of IR maps for this function body: + let fn_maps = @ir_maps(self.tcx, self.method_map, + self.last_use_map); + + #debug["creating fn_maps: %x", ptr::addr_of(*fn_maps) as uint]; + + for decl.inputs.each { |arg| + #debug["adding argument %d", arg.id]; + (*fn_maps).add_variable(arg.id, arg.ident); + } + + // gather up the various local variables, significant expressions, + // and so forth: + visit::visit_fn(fk, decl, body, sp, id, fn_maps, v); + + alt fk { + visit::fk_ctor(_, _, _, class_did) { + add_class_fields(fn_maps, class_did); + } + _ {} + } + + // Special nodes and variables: + // - exit_ln represents the end of the fn, either by ret or fail + // - implicit_ret_var is a pseudo-variable that represents + // an implicit return + let specials = { + exit_ln: (*fn_maps).add_live_node(lnk_exit), + fallthrough_ln: (*fn_maps).add_live_node(lnk_exit), + no_ret_var: (*fn_maps).add_variable(0, ""), + self_var: (*fn_maps).add_variable(0, "self") + }; + + // compute liveness + let lsets = @liveness(fn_maps, specials); + let entry_ln = (*lsets).compute(body); + + // check for various error conditions + let check_vt = visit::mk_vt(@{ + visit_fn: check_fn, + visit_local: check_local, + visit_expr: check_expr + with *visit::default_visitor() + }); + check_vt.visit_block(body, lsets, check_vt); + lsets.check_ret(id, sp, fk, entry_ln); + lsets.check_fields(sp, entry_ln); +} + +fn add_class_fields(self: @ir_maps, did: def_id) { + for ty::lookup_class_fields(self.tcx, did).each { |field_ty| + assert field_ty.id.crate == local_crate; + let var = (*self).add_variable( + field_ty.id.node, #fmt["self.%s", field_ty.ident]); + self.field_map.insert(field_ty.ident, var); + } +} + +fn visit_local(local: @local, &&self: @ir_maps, vt: vt<@ir_maps>) { + let def_map = self.tcx.def_map; + pat_util::pat_bindings(def_map, local.node.pat) { |p_id, sp, path| + #debug["adding local variable %d", p_id]; + let name = ast_util::path_to_ident(path); + (*self).add_live_node_for_node(p_id, lnk_vdef(sp)); + (*self).add_variable(p_id, name); + } + visit::visit_local(local, self, vt); +} + +fn visit_expr(expr: @expr, &&self: @ir_maps, vt: vt<@ir_maps>) { + alt expr.node { + // live nodes required for uses or definitions of variables: + expr_path(_) { + let def = self.tcx.def_map.get(expr.id); + #debug["expr %d: path that leads to %?", expr.id, def]; + if relevant_def(def).is_some() { + (*self).add_live_node_for_node(expr.id, lnk_expr(expr.span)); + } + visit::visit_expr(expr, self, vt); + } + expr_fn(_, _, _, cap_clause) | + expr_fn_block(_, _, cap_clause) { + // Make a live_node for each captured variable, with the span + // being the location that the variable is used. This results + // in better error messages than just pointing at the closure + // construction site. + let proto = ty::ty_fn_proto(ty::expr_ty(self.tcx, expr)); + let cvs = capture::compute_capture_vars(self.tcx, expr.id, + proto, cap_clause); + let mut call_caps = []; + for cvs.each { |cv| + alt relevant_def(cv.def) { + some(rv) { + let cv_ln = (*self).add_live_node(lnk_freevar(cv.span)); + let is_move = alt cv.mode { + cap_move | cap_drop {true} // var must be dead afterwards + cap_copy | cap_ref {false} // var can still be used + }; + call_caps += [{ln: cv_ln, is_move: is_move, rv: rv}]; + } + none {} + } + } + (*self).set_captures(expr.id, call_caps); + + visit::visit_expr(expr, self, vt); + } + + // live nodes required for interesting control flow: + expr_if_check(*) | expr_if(*) | expr_alt(*) | + expr_while(*) | expr_loop(*) { + (*self).add_live_node_for_node(expr.id, lnk_expr(expr.span)); + visit::visit_expr(expr, self, vt); + } + expr_binary(op, _, _) if ast_util::lazy_binop(op) { + (*self).add_live_node_for_node(expr.id, lnk_expr(expr.span)); + visit::visit_expr(expr, self, vt); + } + + // otherwise, live nodes are not required: + expr_index(*) | expr_field(*) | expr_vstore(*) | + expr_vec(*) | expr_rec(*) | expr_call(*) | expr_tup(*) | + expr_bind(*) | expr_new(*) | expr_log(*) | expr_binary(*) | + expr_assert(*) | expr_check(*) | expr_addr_of(*) | expr_copy(*) | + expr_loop_body(*) | expr_cast(*) | expr_unary(*) | expr_fail(*) | + expr_break | expr_cont | expr_lit(_) | expr_ret(*) | + expr_block(*) | expr_move(*) | expr_assign(*) | expr_swap(*) | + expr_assign_op(*) | expr_mac(*) { + visit::visit_expr(expr, self, vt); + } + } +} + +// ______________________________________________________________________ +// Computing liveness sets +// +// Actually we compute just a bit more than just liveness, but we use +// the same basic propagation framework in all cases. + +type users = { + reader: live_node, + writer: live_node +}; + +fn invalid_users() -> users { + {reader: invalid_node(), writer: invalid_node()} +} + +type specials = { + exit_ln: live_node, + fallthrough_ln: live_node, + no_ret_var: variable, + self_var: variable +}; + +const ACC_READ: uint = 1u; +const ACC_WRITE: uint = 2u; + +class liveness { + let tcx: ty::ctxt; + let ir: @ir_maps; + let s: specials; + let successors: [mut live_node]; + let users: [mut users]; + let mut break_ln: live_node; + let mut cont_ln: live_node; + + new(ir: @ir_maps, specials: specials) { + self.ir = ir; + self.tcx = ir.tcx; + self.s = specials; + self.successors = + vec::to_mut( + vec::from_elem(self.ir.num_live_nodes, + invalid_node())); + self.users = + vec::to_mut( + vec::from_elem(self.ir.num_live_nodes * self.ir.num_vars, + invalid_users())); + self.break_ln = invalid_node(); + self.cont_ln = invalid_node(); + } + + // _______________________________________________________________________ + + fn live_node(node_id: node_id, span: span) -> live_node { + alt self.ir.live_node_map.find(node_id) { + some(ln) {ln} + none { + // This must be a mismatch between the ir_map construction + // above and the propagation code below; the two sets of + // code have to agree about which AST nodes are worth + // creating liveness nodes for. + self.tcx.sess.span_bug( + span, #fmt["No live node registered for node %d", + node_id]); + } + } + } + + fn variable_from_rdef(rv: relevant_def, span: span) -> variable { + alt rv { + rdef_self {self.s.self_var} + rdef_var(nid) {self.variable(nid, span)} + } + } + + fn variable(node_id: node_id, span: span) -> variable { + (*self.ir).variable(node_id, span) + } + + fn variable_from_def_map(node_id: node_id, + span: span) -> option { + alt self.tcx.def_map.find(node_id) { + some(def) { + relevant_def(def).map { |rdef| + self.variable_from_rdef(rdef, span) + } + } + none { + self.tcx.sess.span_bug( + span, "Not present in def map") + } + } + } + + fn idx(ln: live_node, var: variable) -> uint { + *ln * self.ir.num_vars + *var + } + + fn live_on_entry(ln: live_node, var: variable) + -> option { + + assert ln.is_valid(); + let reader = self.users[self.idx(ln, var)].reader; + if reader.is_valid() {some((*self.ir).lnk(reader))} else {none} + } + + fn live_on_exit(ln: live_node, var: variable) + -> option { + + self.live_on_entry(self.successors[*ln], var) + } + + fn assigned_on_entry(ln: live_node, var: variable) + -> option { + + assert ln.is_valid(); + let writer = self.users[self.idx(ln, var)].writer; + if writer.is_valid() {some((*self.ir).lnk(writer))} else {none} + } + + fn assigned_on_exit(ln: live_node, var: variable) + -> option { + + self.assigned_on_entry(self.successors[*ln], var) + } + + fn indices(ln: live_node, op: fn(uint)) { + let node_base_idx = self.idx(ln, variable(0u)); + uint::range(0u, self.ir.num_vars) { |var_idx| + op(node_base_idx + var_idx) + } + } + + fn indices2(ln: live_node, succ_ln: live_node, + op: fn(uint, uint)) { + let node_base_idx = self.idx(ln, variable(0u)); + let succ_base_idx = self.idx(succ_ln, variable(0u)); + uint::range(0u, self.ir.num_vars) { |var_idx| + op(node_base_idx + var_idx, succ_base_idx + var_idx); + } + } + + fn write_vars(wr: io::writer, + ln: live_node, + test: fn(uint) -> live_node) { + let node_base_idx = self.idx(ln, variable(0u)); + uint::range(0u, self.ir.num_vars) { |var_idx| + let idx = node_base_idx + var_idx; + if test(idx).is_valid() { + wr.write_str(" "); + wr.write_str(variable(var_idx).to_str()); + } + } + } + + fn ln_str(ln: live_node) -> str { + io::with_str_writer { |wr| + wr.write_str("[ln("); + wr.write_uint(*ln); + wr.write_str(") of kind "); + wr.write_str(#fmt["%?", self.ir.lnks[*ln]]); + wr.write_str(" reads"); + self.write_vars(wr, ln, {|idx| self.users[idx].reader}); + wr.write_str(" writes"); + self.write_vars(wr, ln, {|idx| self.users[idx].writer}); + wr.write_str(" "); + wr.write_str(" precedes "); + wr.write_str(self.successors[*ln].to_str()); + wr.write_str("]"); + } + } + + fn init_empty(ln: live_node, succ_ln: live_node) { + self.successors[*ln] = succ_ln; + + // It is not necessary to initialize the + // values to empty because this is the value + // they have when they are created, and the sets + // only grow during iterations. + // + // self.indices(ln) { |idx| + // self.users[idx] = invalid_users(); + // } + } + + fn init_from_succ(ln: live_node, succ_ln: live_node) { + // more efficient version of init_empty() / merge_from_succ() + self.successors[*ln] = succ_ln; + self.indices2(ln, succ_ln) { |idx, succ_idx| + self.users[idx] = self.users[succ_idx]; + } + #debug["init_from_succ(ln=%s, succ=%s)", + self.ln_str(ln), self.ln_str(succ_ln)]; + } + + fn merge_from_succ(ln: live_node, succ_ln: live_node, + first_merge: bool) -> bool { + if ln == succ_ln { ret false; } + + let mut changed = false; + self.indices2(ln, succ_ln) { |idx, succ_idx| + changed |= copy_if_invalid(self.users[succ_idx].reader, + self.users[idx].reader); + changed |= copy_if_invalid(self.users[succ_idx].writer, + self.users[idx].writer); + } + + #debug["merge_from_succ(ln=%s, succ=%s, first_merge=%b, changed=%b)", + ln.to_str(), self.ln_str(succ_ln), first_merge, changed]; + ret changed; + + fn copy_if_invalid(src: live_node, &dst: live_node) -> bool { + if src.is_valid() { + if !dst.is_valid() { + dst = src; + ret true; + } + } + ret false; + } + } + + // Indicates that a local variable was *defined*; we know that no + // uses of the variable can precede the definition (resolve checks + // this) so we just clear out all the data. + fn define(writer: live_node, var: variable) { + let idx = self.idx(writer, var); + self.users[idx].reader = invalid_node(); + self.users[idx].writer = invalid_node(); + + #debug["%s defines %s (idx=%u): %s", writer.to_str(), var.to_str(), + idx, self.ln_str(writer)]; + } + + // Indicates that a new value for the local variable was assigned. + fn write(writer: live_node, var: variable) { + let idx = self.idx(writer, var); + self.users[idx].reader = invalid_node(); + self.users[idx].writer = writer; + + #debug["%s writes %s (idx=%u): %s", writer.to_str(), var.to_str(), + idx, self.ln_str(writer)]; + } + + // Indicates that the current value of the local variable was used. + fn read(reader: live_node, var: variable) { + let idx = self.idx(reader, var); + self.users[idx].reader = reader; + + #debug["%s reads %s (idx=%u): %s", reader.to_str(), var.to_str(), + idx, self.ln_str(reader)]; + } + + // Either read, write, or both depending on the acc bitset + fn acc(ln: live_node, var: variable, acc: uint) { + if (acc & ACC_WRITE) != 0u { self.write(ln, var) } + + // Important: if we both read/write, must do read second + // or else the write will override. + if (acc & ACC_READ) != 0u { self.read(ln, var) } + } + + // _______________________________________________________________________ + + fn compute(body: blk) -> live_node { + // if there is a `break` or `cont` at the top level, then it's + // effectively a return---this only occurs in `for` loops, + // where the body is really a closure. + let entry_ln: live_node = + self.with_loop_nodes(self.s.exit_ln, self.s.exit_ln) {|| + self.propagate_through_fn_block(body) + }; + + // hack to skip the loop unless #debug is enabled: + #debug["^^ liveness computation results for body %d (entry=%s)", + { + uint::range(0u, self.ir.num_live_nodes) { |ln_idx| + #debug["%s", self.ln_str(live_node(ln_idx))]; + } + body.node.id + }, + entry_ln.to_str()]; + + entry_ln + } + + fn propagate_through_fn_block(blk: blk) -> live_node { + if blk.node.expr.is_none() { + self.read(self.s.fallthrough_ln, self.s.no_ret_var) + } + + // in a ctor, there is an implicit use of self.f for all fields f: + for self.ir.field_map.each_value { |var| + self.read(self.s.fallthrough_ln, var); + } + + self.propagate_through_block(blk, self.s.fallthrough_ln) + } + + fn propagate_through_block(blk: blk, succ: live_node) -> live_node { + let succ = self.propagate_through_opt_expr(blk.node.expr, succ); + blk.node.stmts.foldr(succ) { |stmt, succ| + self.propagate_through_stmt(stmt, succ) + } + } + + fn propagate_through_stmt(stmt: @stmt, succ: live_node) -> live_node { + alt stmt.node { + stmt_decl(decl, _) { + ret self.propagate_through_decl(decl, succ); + } + + stmt_expr(expr, _) | stmt_semi(expr, _) { + ret self.propagate_through_expr(expr, succ); + } + } + } + + fn propagate_through_decl(decl: @decl, succ: live_node) -> live_node { + alt decl.node { + decl_local(locals) { + locals.foldr(succ) { |local, succ| + self.propagate_through_local(local, succ) + } + } + decl_item(_) { + succ + } + } + } + + fn propagate_through_local(local: @local, succ: live_node) -> live_node { + // Note: we mark the variable as defined regardless of whether + // there is an initializer. Initially I had thought to only mark + // the live variable as defined if it was initialized, and then we + // could check for uninit variables just by scanning what is live + // at the start of the function. But that doesn't work so well for + // immutable variables defined in a loop: + // loop { let x; x = 5; } + // because the "assignment" loops back around and generates an error. + // + // So now we just check that variables defined w/o an + // initializer are not live at the point of their + // initialization, which is mildly more complex than checking + // once at the func header but otherwise equivalent. + + let opt_init = local.node.init.map { |i| i.expr }; + let mut succ = self.propagate_through_opt_expr(opt_init, succ); + let def_map = self.tcx.def_map; + pat_util::pat_bindings(def_map, local.node.pat) { |p_id, sp, _n| + let ln = self.live_node(p_id, sp); + let var = self.variable(p_id, sp); + self.init_from_succ(ln, succ); + self.define(ln, var); + succ = ln; + } + succ + } + + fn propagate_through_exprs(exprs: [@expr], succ: live_node) -> live_node { + exprs.foldr(succ) { |expr, succ| + self.propagate_through_expr(expr, succ) + } + } + + fn propagate_through_opt_expr(opt_expr: option<@expr>, + succ: live_node) -> live_node { + opt_expr.foldl(succ) { |succ, expr| + self.propagate_through_expr(expr, succ) + } + } + + fn propagate_through_expr(expr: @expr, succ: live_node) -> live_node { + alt expr.node { + // Interesting cases with control flow or which gen/kill + + expr_path(_) { + self.access_path(expr, succ, ACC_READ) + } + + expr_field(e, nm, _) { + // If this is a reference to `self.f` inside of a ctor, + // then we treat it as a read of that variable. + // Otherwise, we ignore it and just propagate down to + // process `e`. + alt self.as_self_field(e, nm) { + some((ln, var)) { + self.init_from_succ(ln, succ); + self.read(ln, var); + ln + } + none { + self.propagate_through_expr(e, succ) + } + } + } + + expr_fn(*) | expr_fn_block(*) { + // the construction of a closure itself is not important, + // but we have to consider the closed over variables. + let caps = (*self.ir).captures(expr); + (*caps).foldr(succ) { |cap, succ| + self.init_from_succ(cap.ln, succ); + let var = self.variable_from_rdef(cap.rv, expr.span); + self.read(cap.ln, var); + cap.ln + } + } + + expr_if_check(cond, then, els) | + expr_if(cond, then, els) { + // + // (cond) + // | + // v + // (expr) + // / \ + // | | + // v v + // (then)(els) + // | | + // v v + // ( succ ) + // + let else_ln = self.propagate_through_opt_expr(els, succ); + let then_ln = self.propagate_through_block(then, succ); + let ln = self.live_node(expr.id, expr.span); + self.init_from_succ(ln, else_ln); + self.merge_from_succ(ln, then_ln, false); + self.propagate_through_expr(cond, ln) + } + + expr_while(cond, blk) { + self.propagate_through_loop(expr, some(cond), blk, succ) + } + + expr_loop(blk) { + self.propagate_through_loop(expr, none, blk, succ) + } + + expr_alt(e, arms, _) { + // + // (e) + // | + // v + // (expr) + // / | \ + // | | | + // v v v + // (..arms..) + // | | | + // v v v + // ( succ ) + // + // + let ln = self.live_node(expr.id, expr.span); + self.init_empty(ln, succ); + let mut first_merge = true; + for arms.each { |arm| + let arm_succ = + self.propagate_through_opt_expr( + arm.guard, + self.propagate_through_block(arm.body, succ)); + self.merge_from_succ(ln, arm_succ, first_merge); + first_merge = false; + }; + self.propagate_through_expr(e, ln) + } + + expr_ret(o_e) | expr_fail(o_e) { // ignore succ and subst exit_ln: + self.propagate_through_opt_expr(o_e, self.s.exit_ln) + } + + expr_break { + if !self.break_ln.is_valid() { + self.tcx.sess.span_bug( + expr.span, "break with invalid break_ln"); + } + + self.break_ln + } + + expr_cont { + if !self.cont_ln.is_valid() { + self.tcx.sess.span_bug( + expr.span, "cont with invalid cont_ln"); + } + + self.cont_ln + } + + expr_move(l, r) | expr_assign(l, r) { + // see comment on lvalues in + // propagate_through_lvalue_components() + let succ = self.write_lvalue(l, succ, ACC_WRITE); + let succ = self.propagate_through_expr(r, succ); + self.propagate_through_lvalue_components(l, succ) + } + + expr_swap(l, r) { + // see comment on lvalues in + // propagate_through_lvalue_components() + let succ = self.write_lvalue(r, succ, ACC_WRITE|ACC_READ); + let succ = self.write_lvalue(l, succ, ACC_WRITE|ACC_READ); + let succ = self.propagate_through_lvalue_components(r, succ); + self.propagate_through_lvalue_components(l, succ) + } + + expr_assign_op(_, l, r) { + // see comment on lvalues in + // propagate_through_lvalue_components() + let succ = self.write_lvalue(l, succ, ACC_WRITE|ACC_READ); + let succ = self.propagate_through_expr(r, succ); + self.propagate_through_lvalue_components(l, succ) + } + + // Uninteresting cases: just propagate in rev exec order + + expr_vstore(expr, _) { + self.propagate_through_expr(expr, succ) + } + + expr_vec(exprs, _) { + self.propagate_through_exprs(exprs, succ) + } + + expr_rec(fields, with_expr) { + let succ = self.propagate_through_opt_expr(with_expr, succ); + fields.foldr(succ) { |field, succ| + self.propagate_through_expr(field.node.expr, succ) + } + } + + expr_call(f, args, _) { + // calling a fn with bot return type means that the fn + // will fail, and hence the successors can be ignored + let t_ret = ty::ty_fn_ret(ty::expr_ty(self.tcx, f)); + let succ = if ty::type_is_bot(t_ret) {self.s.exit_ln} + else {succ}; + let succ = self.propagate_through_exprs(args, succ); + self.propagate_through_expr(f, succ) + } + + expr_tup(exprs) { + self.propagate_through_exprs(exprs, succ) + } + + expr_bind(f, args) { + let succ = args.foldr(succ) { |arg, succ| + alt arg { + none {succ} + some(e) {self.propagate_through_expr(e, succ)} + } + }; + self.propagate_through_expr(f, succ) + } + + expr_binary(op, l, r) if ast_util::lazy_binop(op) { + let r_succ = self.propagate_through_expr(r, succ); + + let ln = self.live_node(expr.id, expr.span); + self.init_from_succ(ln, succ); + self.merge_from_succ(ln, r_succ, false); + + self.propagate_through_expr(l, ln) + } + + expr_new(l, _, r) | + expr_log(_, l, r) | + expr_index(l, r) | + expr_binary(_, l, r) { + self.propagate_through_exprs([l, r], succ) + } + + expr_assert(e) | + expr_check(_, e) | + expr_addr_of(_, e) | + expr_copy(e) | + expr_loop_body(e) | + expr_cast(e, _) | + expr_unary(_, e) { + self.propagate_through_expr(e, succ) + } + + expr_lit(*) { + succ + } + + expr_block(blk) { + self.propagate_through_block(blk, succ) + } + + expr_mac(*) { + self.tcx.sess.span_bug(expr.span, "unexpanded macro"); + } + } + } + + fn propagate_through_lvalue_components(expr: @expr, + succ: live_node) -> live_node { + // # Lvalues + // + // In general, the full flow graph structure for an + // assignment/move/etc can be handled in one of two ways, + // depending on whether what is being assigned is a "tracked + // value" or not. A tracked value is basically a local variable + // or argument, or a self-field (`self.f`) in a ctor. + // + // The two kinds of graphs are: + // + // Tracked lvalue Untracked lvalue + // ----------------------++----------------------- + // || + // | || | + // | || v + // | || (lvalue components) + // | || | + // v || v + // (rvalue) || (rvalue) + // | || | + // v || | + // (write of lvalue) || | + // | || | + // v || v + // (succ) || (succ) + // || + // ----------------------++----------------------- + // + // I will cover the two cases in turn: + // + // # Tracked lvalues + // + // A tracked lvalue is either a local variable/argument `x` or + // else it is a self-field `self.f` in a constructor. In + // these cases, the link_node where the write occurs is linked + // to node id of `x` or `self`, respectively. The + // `write_lvalue()` routine generates the contents of this + // node. There are no subcomponents to consider. + // + // # Non-tracked lvalues + // + // These are lvalues like `x[5]` or `x.f`. In that case, we + // basically ignore the value which is written to but generate + // reads for the components---`x` in these two examples. The + // components reads are generated by + // `propagate_through_lvalue_components()` (this fn). + // + // # Illegal lvalues + // + // It is still possible to observe assignments to non-lvalues; + // these errors are detected in the later pass borrowck. We + // just ignore such cases and treat them as reads. + + alt expr.node { + expr_path(_) { + succ + } + + expr_field(e, nm, _) { + alt self.as_self_field(e, nm) { + some(_) {succ} + none {self.propagate_through_expr(e, succ)} + } + } + + _ { + self.propagate_through_expr(expr, succ) + } + } + } + + // see comment on propagate_through_lvalue() + fn write_lvalue(expr: @expr, + succ: live_node, + acc: uint) -> live_node { + alt expr.node { + expr_path(_) { + self.access_path(expr, succ, acc) + } + + expr_field(e, nm, _) { + alt self.as_self_field(e, nm) { + some((ln, var)) { + self.init_from_succ(ln, succ); + self.acc(ln, var, acc); + ln + } + none { + succ + } + } + } + + // We do not track other lvalues, so just propagate through + // to their subcomponents. Also, it may happen that + // non-lvalues occur here, because those are detected in the + // later pass borrowck. + _ {succ} + } + } + + fn access_path(expr: @expr, succ: live_node, acc: uint) -> live_node { + let def = self.tcx.def_map.get(expr.id); + alt relevant_def(def) { + some(rdef_self) { + // Accessing `self` is like accessing every field of + // the current object. This allows something like + // `self = ...;` (it will be considered a write to + // every field, sensibly enough), though the borrowck + // pass will reject it later on. + // + // Also, note that, within a ctor at least, an + // expression like `self.f` is "shortcircuiting" + // before it reaches this point by the code for + // expr_field. + let ln = self.live_node(expr.id, expr.span); + if acc != 0u { + self.init_from_succ(ln, succ); + for self.ir.field_map.each_value { |var| + self.acc(ln, var, acc); + } + } + ln + } + some(rdef_var(nid)) { + let ln = self.live_node(expr.id, expr.span); + if acc != 0u { + self.init_from_succ(ln, succ); + let var = self.variable(nid, expr.span); + self.acc(ln, var, acc); + } + ln + } + none { + succ + } + } + } + + fn as_self_field(expr: @expr, fld: str) -> option<(live_node,variable)> { + // If we checking a constructor, then we treat self.f as a + // variable. we use the live_node id that will be assigned to + // the reference to self but the variable id for `f`. + alt expr.node { + expr_path(_) { + let def = self.tcx.def_map.get(expr.id); + alt def { + def_self(_) { + // Note: the field_map is empty unless we are in a ctor + ret self.ir.field_map.find(fld).map { |var| + let ln = self.live_node(expr.id, expr.span); + (ln, var) + }; + } + _ { ret none; } + } + } + _ { ret none; } + } + } + + fn propagate_through_loop(expr: @expr, + cond: option<@expr>, + body: blk, + succ: live_node) -> live_node { + + /* + + We model control flow like this: + + (cond) <--+ + | | + v | + +-- (expr) | + | | | + | v | + | (body) ---+ + | + | + v + (succ) + + */ + + // first iteration: + let mut first_merge = true; + let ln = self.live_node(expr.id, expr.span); + self.init_empty(ln, succ); + if cond.is_some() { + // if there is a condition, then it's possible we bypass + // the body altogether. otherwise, the only way is via a + // break in the loop body. + self.merge_from_succ(ln, succ, first_merge); + first_merge = false; + } + let cond_ln = self.propagate_through_opt_expr(cond, ln); + let body_ln = self.with_loop_nodes(succ, ln) {|| + self.propagate_through_block(body, cond_ln) + }; + + // repeat until fixed point is reached: + while self.merge_from_succ(ln, body_ln, first_merge) { + first_merge = false; + assert cond_ln == self.propagate_through_opt_expr(cond, ln); + assert body_ln == self.with_loop_nodes(succ, ln) {|| + self.propagate_through_block(body, cond_ln) + }; + } + + cond_ln + } + + fn with_loop_nodes(break_ln: live_node, + cont_ln: live_node, + f: fn() -> R) -> R { + let bl = self.break_ln, cl = self.cont_ln; + self.break_ln = break_ln; + self.cont_ln = cont_ln; + let r <- f(); + self.break_ln = bl; + self.cont_ln = cl; + ret r; + } +} + +// _______________________________________________________________________ +// Checking for error conditions + +fn check_local(local: @local, &&self: @liveness, vt: vt<@liveness>) { + alt local.node.init { + some({op: init_move, expr: expr}) { + // can never be accessed uninitialized, but the move might + // be invalid + #debug["check_local() with move initializer"]; + self.check_move_from_expr(expr, vt); + } + some({op: init_op, expr: _}) { + // can never be accessed uninitialized + #debug["check_local() with initializer"]; + } + none { + #debug["check_local() with no initializer"]; + let def_map = self.tcx.def_map; + pat_util::pat_bindings(def_map, local.node.pat) {|p_id, sp, _n| + let ln = (*self).live_node(p_id, sp); + let var = (*self).variable(p_id, sp); + alt (*self).live_on_exit(ln, var) { + none { /* not live: good */ } + some(lnk) { + self.report_illegal_read( + local.span, lnk, var, possibly_uninitialized_variable); + } + } + } + } + } + + visit::visit_local(local, self, vt); +} + +fn check_expr(expr: @expr, &&self: @liveness, vt: vt<@liveness>) { + alt expr.node { + expr_path(_) { + for (*self).variable_from_def_map(expr.id, expr.span).each { |var| + let ln = (*self).live_node(expr.id, expr.span); + self.consider_last_use(expr, ln, var); + } + + visit::visit_expr(expr, self, vt); + } + + expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) { + let caps = (*self.ir).captures(expr); + for (*caps).each { |cap| + let var = (*self).variable_from_rdef(cap.rv, expr.span); + self.consider_last_use(expr, cap.ln, var); + if cap.is_move { + self.check_move_from_var(expr.span, cap.ln, var); + } + } + + visit::visit_expr(expr, self, vt); + } + + expr_assign(l, r) { + self.check_lvalue(l, vt); + vt.visit_expr(r, self, vt); + } + + expr_move(l, r) { + self.check_lvalue(l, vt); + self.check_move_from_expr(r, vt); + } + + expr_call(f, args, _) { + let targs = ty::ty_fn_args(ty::expr_ty(self.tcx, f)); + vt.visit_expr(f, self, vt); + vec::iter2(args, targs) { |arg_expr, arg_ty| + alt ty::resolved_mode(self.tcx, arg_ty.mode) { + by_val | by_ref | by_mutbl_ref | by_copy { + vt.visit_expr(arg_expr, self, vt); + } + by_move { + self.check_move_from_expr(arg_expr, vt); + } + } + } + } + + // no correctness conditions related to liveness + expr_if_check(*) | expr_if(*) | expr_alt(*) | + expr_while(*) | expr_loop(*) | + expr_index(*) | expr_field(*) | expr_vstore(*) | + expr_vec(*) | expr_rec(*) | expr_tup(*) | + expr_bind(*) | expr_new(*) | expr_log(*) | expr_binary(*) | + expr_assert(*) | expr_check(*) | expr_addr_of(*) | expr_copy(*) | + expr_loop_body(*) | expr_cast(*) | expr_unary(*) | expr_fail(*) | + expr_ret(*) | expr_break | expr_cont | expr_lit(_) | + expr_block(*) | expr_swap(*) | expr_assign_op(*) | expr_mac(*) { + visit::visit_expr(expr, self, vt); + } + } +} + +fn check_fn(_fk: visit::fn_kind, _decl: fn_decl, + _body: blk, _sp: span, _id: node_id, + &&_self: @liveness, _v: vt<@liveness>) { + // do not check contents of nested fns +} + +enum read_kind { + possibly_uninitialized_variable, + possibly_uninitialized_field, + moved_variable +} + +impl check_methods for @liveness { + fn check_fields(sp: span, entry_ln: live_node) { + for self.ir.field_map.each { |nm, var| + alt (*self).live_on_entry(entry_ln, var) { + none { /* ok */ } + some(lnk_exit) { + self.tcx.sess.span_err( + sp, #fmt["field `self.%s` is never initialized", nm]); + } + some(lnk) { + self.report_illegal_read( + sp, lnk, var, possibly_uninitialized_field); + } + } + } + } + + fn check_ret(id: node_id, sp: span, fk: visit::fn_kind, + entry_ln: live_node) { + if (*self).live_on_entry(entry_ln, self.s.no_ret_var).is_some() { + // if no_ret_var is live, then we fall off the end of the + // function without any kind of return expression: + + let t_ret = ty::ty_fn_ret(ty::node_id_to_type(self.tcx, id)); + if ty::type_is_nil(t_ret) { + // for nil return types, it is ok to not return a value expl. + } else if ty::type_is_bot(t_ret) { + // for bot return types, not ok. Function should fail. + self.tcx.sess.span_err( + sp, "some control paths may return"); + } else { + alt fk { + visit::fk_ctor(*) { + // ctors are written as though they are unit. + } + _ { + self.tcx.sess.span_err( + sp, "not all control paths return a value"); + } + } + } + } + } + + fn check_move_from_var(span: span, ln: live_node, var: variable) { + #debug["check_move_from_var(%s, %s)", + ln.to_str(), var.to_str()]; + + alt (*self).live_on_exit(ln, var) { + none {} + some(lnk) { + self.report_illegal_read(span, lnk, var, moved_variable); + self.tcx.sess.span_note( + span, + "move of variable occurred here"); + } + } + } + + fn consider_last_use(expr: @expr, ln: live_node, var: variable) { + alt (*self).live_on_exit(ln, var) { + some(_) {} + none { + (*self.ir).add_last_use(expr.id, var); + } + } + } + + fn check_move_from_expr(expr: @expr, vt: vt<@liveness>) { + #debug["check_move_from_expr(node %d: %s)", + expr.id, expr_to_str(expr)]; + + if self.ir.method_map.contains_key(expr.id) { + // actually an rvalue, since this calls a method + ret vt.visit_expr(expr, self, vt); + } + + alt expr.node { + expr_path(_) { + let def = self.tcx.def_map.get(expr.id); + alt relevant_def(def) { + some(rdef) { + // Moving from a variable is allowed if is is not live. + let ln = (*self).live_node(expr.id, expr.span); + let var = (*self).variable_from_rdef(rdef, expr.span); + self.check_move_from_var(expr.span, ln, var); + } + none {} + } + } + + expr_field(base, _, _) { + // Moving from x.y is allowed if x is never used later. + // (Note that the borrowck guarantees that anything + // being moved from is uniquely tied to the stack frame) + self.check_move_from_expr(base, vt); + } + + expr_index(base, idx) { + // Moving from x[y] is allowed if x is never used later. + // (Note that the borrowck guarantees that anything + // being moved from is uniquely tied to the stack frame) + self.check_move_from_expr(base, vt); + vt.visit_expr(idx, self, vt); + } + + _ { + // For other kinds of lvalues, no checks are required, + // and any embedded expressions are actually rvalues + vt.visit_expr(expr, self, vt); + } + } + } + + fn check_lvalue(expr: @expr, vt: vt<@liveness>) { + alt expr.node { + expr_path(_) { + alt self.tcx.def_map.get(expr.id) { + def_local(nid, false) { + // Assignment to an immutable variable or argument: + // only legal if there is no later assignment. + let ln = (*self).live_node(expr.id, expr.span); + let var = (*self).variable(nid, expr.span); + alt (*self).assigned_on_exit(ln, var) { + some(lnk_expr(span)) { + self.tcx.sess.span_err( + span, + "re-assignment of immutable variable"); + + self.tcx.sess.span_note( + expr.span, + "prior assignment occurs here"); + } + some(lnk) { + self.tcx.sess.span_bug( + expr.span, + #fmt["illegal writer: %?", lnk]); + } + none {} + } + } + def_arg(*) | def_local(_, true) { + // Assignment to a mutable variable; no conditions + // req'd. In the case of arguments, the mutability is + // enforced by borrowck. + } + _ { + // Not a variable, don't care + } + } + } + + _ { + // For other kinds of lvalues, no checks are required, + // and any embedded expressions are actually rvalues + visit::visit_expr(expr, self, vt); + } + } + } + + fn report_illegal_read(chk_span: span, + lnk: live_node_kind, + var: variable, + rk: read_kind) { + let msg = alt rk { + possibly_uninitialized_variable {"possibly uninitialized variable"} + possibly_uninitialized_field {"possibly uninitialized field"} + moved_variable {"moved variable"} + }; + let name = self.ir.var_infos[*var].name; + alt lnk { + lnk_freevar(span) { + self.tcx.sess.span_err( + span, + #fmt["capture of %s: `%s`", msg, name]); + } + lnk_expr(span) { + self.tcx.sess.span_err( + span, + #fmt["use of %s: `%s`", msg, name]); + } + lnk_exit | lnk_vdef(_) { + self.tcx.sess.span_bug( + chk_span, + #fmt["illegal reader: %?", lnk]); + } + } + } +} \ No newline at end of file diff --git a/src/rustc/rustc.rc b/src/rustc/rustc.rc index b3956c2ab02..4ba01859cdd 100644 --- a/src/rustc/rustc.rc +++ b/src/rustc/rustc.rc @@ -72,6 +72,7 @@ mod middle { mod borrowck; mod alias; mod last_use; + mod liveness; mod block_use; mod kind; mod freevars; diff --git a/src/test/compile-fail/and-init.rs b/src/test/compile-fail/and-init.rs deleted file mode 100644 index c053180bf43..00000000000 --- a/src/test/compile-fail/and-init.rs +++ /dev/null @@ -1,8 +0,0 @@ -// error-pattern:unsatisfied precondition constraint (for example, init(i - -fn main() { - let i: int; - - log(debug, false && { i = 5; true }); - log(debug, i); -} diff --git a/src/test/compile-fail/assign-imm-local-twice.rs b/src/test/compile-fail/assign-imm-local-twice.rs new file mode 100644 index 00000000000..13ec17e0585 --- /dev/null +++ b/src/test/compile-fail/assign-imm-local-twice.rs @@ -0,0 +1,9 @@ +fn test(cond: bool) { + let v: int; + v = 1; //! NOTE prior assignment occurs here + v = 2; //! ERROR re-assignment of immutable variable +} + +fn main() { + test(true); +} diff --git a/src/test/compile-fail/block-uninit.rs b/src/test/compile-fail/block-uninit.rs deleted file mode 100644 index 99b8346166a..00000000000 --- a/src/test/compile-fail/block-uninit.rs +++ /dev/null @@ -1,4 +0,0 @@ -// error-pattern:unsatisfied precondition constraint - -fn force(f: fn()) { f(); } -fn main() { let x: int; force(fn&() { log(error, x); }); } diff --git a/src/test/compile-fail/break-uninit.rs b/src/test/compile-fail/break-uninit.rs deleted file mode 100644 index 8babc95f0dc..00000000000 --- a/src/test/compile-fail/break-uninit.rs +++ /dev/null @@ -1,14 +0,0 @@ -// error-pattern:unsatisfied precondition - -fn foo() -> int { - let x: int; - let i: int; - - loop { i = 0; break; x = 0; } - - log(debug, x); - - ret 17; -} - -fn main() { log(debug, foo()); } diff --git a/src/test/compile-fail/break-uninit2.rs b/src/test/compile-fail/break-uninit2.rs deleted file mode 100644 index 1229e0dc0ee..00000000000 --- a/src/test/compile-fail/break-uninit2.rs +++ /dev/null @@ -1,14 +0,0 @@ -// error-pattern:unsatisfied precondition - -fn foo() -> int { - let x: int; - let i: int; - - while 1 != 2 { i = 0; break; x = 0; } - - log(debug, x); - - ret 17; -} - -fn main() { log(debug, foo()); } diff --git a/src/test/compile-fail/fn-expr-type-state.rs b/src/test/compile-fail/fn-expr-type-state.rs deleted file mode 100644 index ab3f1dd1087..00000000000 --- a/src/test/compile-fail/fn-expr-type-state.rs +++ /dev/null @@ -1,7 +0,0 @@ -// error-pattern:unsatisfied precondition - -fn main() { - // Typestate should work even in a fn@. we should reject this program. - let f = fn@() -> int { let i: int; ret i; }; - log(error, f()); -} diff --git a/src/test/compile-fail/fn-expr-typestate-2.rs b/src/test/compile-fail/fn-expr-typestate-2.rs deleted file mode 100644 index 75682c8ddf8..00000000000 --- a/src/test/compile-fail/fn-expr-typestate-2.rs +++ /dev/null @@ -1,6 +0,0 @@ -// error-pattern:unsatisfied precondition - -fn main() { - let j = fn@() -> int { let i: int; ret i; }(); - log(error, j); -} diff --git a/src/test/compile-fail/fru-typestate.rs b/src/test/compile-fail/fru-typestate.rs deleted file mode 100644 index 447f6f44dc5..00000000000 --- a/src/test/compile-fail/fru-typestate.rs +++ /dev/null @@ -1,12 +0,0 @@ -// -*- rust -*- - -// error-pattern: precondition - -type point = {x: int, y: int}; - -fn main() { - let origin: point; - - let right: point = {x: 10 with origin}; - origin = {x: 0, y: 0}; -} diff --git a/src/test/compile-fail/issue-2163.rs b/src/test/compile-fail/issue-2163.rs deleted file mode 100644 index 15b3d6d96c7..00000000000 --- a/src/test/compile-fail/issue-2163.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main(s: [str]) { - let a: [int] = []; - vec::each(a) { |x| //! ERROR in function `anon`, not all control paths - } //! ERROR see function return type of `bool` -} diff --git a/src/test/compile-fail/liveness-and-init.rs b/src/test/compile-fail/liveness-and-init.rs new file mode 100644 index 00000000000..aea30e3465c --- /dev/null +++ b/src/test/compile-fail/liveness-and-init.rs @@ -0,0 +1,6 @@ +fn main() { + let i: int; + + log(debug, false && { i = 5; true }); + log(debug, i); //! ERROR use of possibly uninitialized variable: `i` +} diff --git a/src/test/compile-fail/liveness-assign-imm-local-in-loop.rs b/src/test/compile-fail/liveness-assign-imm-local-in-loop.rs new file mode 100644 index 00000000000..a68528418e6 --- /dev/null +++ b/src/test/compile-fail/liveness-assign-imm-local-in-loop.rs @@ -0,0 +1,11 @@ +fn test(cond: bool) { + let v: int; + loop { + v = 1; //! ERROR re-assignment of immutable variable + //!^ NOTE prior assignment occurs here + } +} + +fn main() { + test(true); +} diff --git a/src/test/compile-fail/liveness-assign-imm-local-in-op-eq.rs b/src/test/compile-fail/liveness-assign-imm-local-in-op-eq.rs new file mode 100644 index 00000000000..2f880e03c74 --- /dev/null +++ b/src/test/compile-fail/liveness-assign-imm-local-in-op-eq.rs @@ -0,0 +1,9 @@ +fn test(cond: bool) { + let v: int; + v = 2; //! NOTE prior assignment occurs here + v += 1; //! ERROR re-assignment of immutable variable +} + +fn main() { + test(true); +} diff --git a/src/test/compile-fail/liveness-assign-imm-local-in-swap.rs b/src/test/compile-fail/liveness-assign-imm-local-in-swap.rs new file mode 100644 index 00000000000..c432d03bed3 --- /dev/null +++ b/src/test/compile-fail/liveness-assign-imm-local-in-swap.rs @@ -0,0 +1,18 @@ +fn test1() { + let v: int; + let mut w: int; + v = 1; //! NOTE prior assignment occurs here + w = 2; + v <-> w; //! ERROR re-assignment of immutable variable +} + +fn test2() { + let v: int; + let mut w: int; + v = 1; //! NOTE prior assignment occurs here + w = 2; + w <-> v; //! ERROR re-assignment of immutable variable +} + +fn main() { +} diff --git a/src/test/compile-fail/bad-bang-ann-2.rs b/src/test/compile-fail/liveness-bad-bang-2.rs similarity index 100% rename from src/test/compile-fail/bad-bang-ann-2.rs rename to src/test/compile-fail/liveness-bad-bang-2.rs diff --git a/src/test/compile-fail/liveness-block-unint.rs b/src/test/compile-fail/liveness-block-unint.rs new file mode 100644 index 00000000000..698f9f7cf79 --- /dev/null +++ b/src/test/compile-fail/liveness-block-unint.rs @@ -0,0 +1,7 @@ +fn force(f: fn()) { f(); } +fn main() { + let x: int; + force(fn&() { + log(debug, x); //! ERROR capture of possibly uninitialized variable: `x` + }); +} diff --git a/src/test/compile-fail/liveness-break-uninit-2.rs b/src/test/compile-fail/liveness-break-uninit-2.rs new file mode 100644 index 00000000000..30038dbe96e --- /dev/null +++ b/src/test/compile-fail/liveness-break-uninit-2.rs @@ -0,0 +1,16 @@ +fn foo() -> int { + let x: int; + let i: int; + + while 1 != 2 { + i = 0; + break; + x = 0; //! WARNING unreachable statement + } + + log(debug, x); //! ERROR use of possibly uninitialized variable: `x` + + ret 17; +} + +fn main() { log(debug, foo()); } diff --git a/src/test/compile-fail/liveness-break-uninit.rs b/src/test/compile-fail/liveness-break-uninit.rs new file mode 100644 index 00000000000..55041daa7fb --- /dev/null +++ b/src/test/compile-fail/liveness-break-uninit.rs @@ -0,0 +1,16 @@ +fn foo() -> int { + let x: int; + let i: int; + + loop { + i = 0; + break; + x = 0; //! WARNING unreachable statement + } + + log(debug, x); //! ERROR use of possibly uninitialized variable: `x` + + ret 17; +} + +fn main() { log(debug, foo()); } diff --git a/src/test/compile-fail/block-require-return.rs b/src/test/compile-fail/liveness-closure-require-ret.rs similarity index 100% rename from src/test/compile-fail/block-require-return.rs rename to src/test/compile-fail/liveness-closure-require-ret.rs diff --git a/src/test/compile-fail/liveness-ctor-access-self-with-uninit-fields.rs b/src/test/compile-fail/liveness-ctor-access-self-with-uninit-fields.rs new file mode 100644 index 00000000000..9c982292816 --- /dev/null +++ b/src/test/compile-fail/liveness-ctor-access-self-with-uninit-fields.rs @@ -0,0 +1,11 @@ +class cat { + let how_hungry : int; + fn meow() {} + new() { + self.meow(); + //!^ ERROR use of possibly uninitialized field: `self.how_hungry` + } +} + +fn main() { +} diff --git a/src/test/compile-fail/liveness-ctor-field-never-init.rs b/src/test/compile-fail/liveness-ctor-field-never-init.rs new file mode 100644 index 00000000000..b979bdd9255 --- /dev/null +++ b/src/test/compile-fail/liveness-ctor-field-never-init.rs @@ -0,0 +1,7 @@ +class cat { + let how_hungry : int; + new() {} //! ERROR field `self.how_hungry` is never initialized +} + +fn main() { +} diff --git a/src/test/compile-fail/liveness-ctor-uninit-field.rs b/src/test/compile-fail/liveness-ctor-uninit-field.rs new file mode 100644 index 00000000000..080d4678a2f --- /dev/null +++ b/src/test/compile-fail/liveness-ctor-uninit-field.rs @@ -0,0 +1,14 @@ +class cat { + let mut a: int; + let mut b: int; + let mut c: int; + + new() { + self.a = 3; + self.b = self.a; + self.a += self.c; //! ERROR use of possibly uninitialized field: `self.c` + } +} + +fn main() { +} diff --git a/src/test/compile-fail/ctor-uninit-var.rs b/src/test/compile-fail/liveness-ctor-uninit-var.rs similarity index 76% rename from src/test/compile-fail/ctor-uninit-var.rs rename to src/test/compile-fail/liveness-ctor-uninit-var.rs index b6f94c11fe5..3bbccd62ae3 100644 --- a/src/test/compile-fail/ctor-uninit-var.rs +++ b/src/test/compile-fail/liveness-ctor-uninit-var.rs @@ -1,4 +1,3 @@ -// error-pattern:unsatisfied precondition class cat { priv { let mut meows : uint; @@ -13,7 +12,7 @@ class cat { new(in_x : uint, in_y : int) { let foo; self.meows = in_x + (in_y as uint); - self.how_hungry = foo; + self.how_hungry = foo; //! ERROR use of possibly uninitialized variable: `foo` } } diff --git a/src/test/compile-fail/forgot-ret.rs b/src/test/compile-fail/liveness-forgot-ret.rs similarity index 100% rename from src/test/compile-fail/forgot-ret.rs rename to src/test/compile-fail/liveness-forgot-ret.rs diff --git a/src/test/compile-fail/liveness-if-no-else.rs b/src/test/compile-fail/liveness-if-no-else.rs new file mode 100644 index 00000000000..5cbd24a6a5b --- /dev/null +++ b/src/test/compile-fail/liveness-if-no-else.rs @@ -0,0 +1,6 @@ +fn foo(x: int) { log(debug, x); } + +fn main() { + let x: int; if 1 > 2 { x = 10; } + foo(x); //! ERROR use of possibly uninitialized variable: `x` +} diff --git a/src/test/compile-fail/use-uninit-3.rs b/src/test/compile-fail/liveness-if-with-else.rs similarity index 68% rename from src/test/compile-fail/use-uninit-3.rs rename to src/test/compile-fail/liveness-if-with-else.rs index 2a6522ab407..dbb3e6b1645 100644 --- a/src/test/compile-fail/use-uninit-3.rs +++ b/src/test/compile-fail/liveness-if-with-else.rs @@ -1,5 +1,3 @@ -// error-pattern:unsatisfied precondition - fn foo(x: int) { log(debug, x); } fn main() { @@ -9,5 +7,5 @@ fn main() { } else { x = 10; } - foo(x); + foo(x); //! ERROR use of possibly uninitialized variable: `x` } diff --git a/src/test/compile-fail/liveness-init-in-called-fn-expr.rs b/src/test/compile-fail/liveness-init-in-called-fn-expr.rs new file mode 100644 index 00000000000..28351ceeb08 --- /dev/null +++ b/src/test/compile-fail/liveness-init-in-called-fn-expr.rs @@ -0,0 +1,7 @@ +fn main() { + let j = fn@() -> int { + let i: int; + ret i; //! ERROR use of possibly uninitialized variable: `i` + }; + j(); +} diff --git a/src/test/compile-fail/liveness-init-in-fn-expr.rs b/src/test/compile-fail/liveness-init-in-fn-expr.rs new file mode 100644 index 00000000000..8c68ba750a8 --- /dev/null +++ b/src/test/compile-fail/liveness-init-in-fn-expr.rs @@ -0,0 +1,7 @@ +fn main() { + let f = fn@() -> int { + let i: int; + ret i; //! ERROR use of possibly uninitialized variable: `i` + }; + log(error, f()); +} diff --git a/src/test/compile-fail/liveness-init-in-fru.rs b/src/test/compile-fail/liveness-init-in-fru.rs new file mode 100644 index 00000000000..1c17a2503dc --- /dev/null +++ b/src/test/compile-fail/liveness-init-in-fru.rs @@ -0,0 +1,8 @@ +// -*- rust -*- + +type point = {x: int, y: int}; + +fn main() { + let mut origin: point; + origin = {x: 10 with origin}; //! ERROR use of possibly uninitialized variable: `origin` +} diff --git a/src/test/compile-fail/liveness-init-op-equal.rs b/src/test/compile-fail/liveness-init-op-equal.rs new file mode 100644 index 00000000000..2f37dc5070f --- /dev/null +++ b/src/test/compile-fail/liveness-init-op-equal.rs @@ -0,0 +1,8 @@ +fn test(cond: bool) { + let v: int; + v += 1; //! ERROR use of possibly uninitialized variable: `v` +} + +fn main() { + test(true); +} diff --git a/src/test/compile-fail/liveness-init-plus-equal.rs b/src/test/compile-fail/liveness-init-plus-equal.rs new file mode 100644 index 00000000000..56c5b3b2717 --- /dev/null +++ b/src/test/compile-fail/liveness-init-plus-equal.rs @@ -0,0 +1,8 @@ +fn test(cond: bool) { + let mut v: int; + v = v + 1; //! ERROR use of possibly uninitialized variable: `v` +} + +fn main() { + test(true); +} diff --git a/src/test/compile-fail/liveness-issue-2163.rs b/src/test/compile-fail/liveness-issue-2163.rs new file mode 100644 index 00000000000..dea27e585ef --- /dev/null +++ b/src/test/compile-fail/liveness-issue-2163.rs @@ -0,0 +1,5 @@ +fn main(s: [str]) { + let a: [int] = []; + vec::each(a) { |x| //! ERROR not all control paths return a value + } +} diff --git a/src/test/compile-fail/missing-return2.rs b/src/test/compile-fail/liveness-missing-ret2.rs similarity index 100% rename from src/test/compile-fail/missing-return2.rs rename to src/test/compile-fail/liveness-missing-ret2.rs diff --git a/src/test/compile-fail/liveness-move-from-mode.rs b/src/test/compile-fail/liveness-move-from-mode.rs new file mode 100644 index 00000000000..01483f033eb --- /dev/null +++ b/src/test/compile-fail/liveness-move-from-mode.rs @@ -0,0 +1,10 @@ +fn take(-x: int) {} + +fn main() { + + let x: int = 25; + loop { + take(x); //! ERROR use of moved variable: `x` + //!^ NOTE move of variable occurred here + } +} diff --git a/src/test/compile-fail/liveness-move-in-loop.rs b/src/test/compile-fail/liveness-move-in-loop.rs new file mode 100644 index 00000000000..81467d8a2b4 --- /dev/null +++ b/src/test/compile-fail/liveness-move-in-loop.rs @@ -0,0 +1,16 @@ +fn main() { + + let y: int = 42; + let mut x: int; + loop { + log(debug, y); + loop { + loop { + loop { + x <- y; //! ERROR use of moved variable + //!^ NOTE move of variable occurred here + } + } + } + } +} diff --git a/src/test/compile-fail/while-constraints.rs b/src/test/compile-fail/liveness-move-in-while.rs similarity index 54% rename from src/test/compile-fail/while-constraints.rs rename to src/test/compile-fail/liveness-move-in-while.rs index ca8d0140108..6acbc5a9f35 100644 --- a/src/test/compile-fail/while-constraints.rs +++ b/src/test/compile-fail/liveness-move-in-while.rs @@ -1,10 +1,11 @@ -// error-pattern:unsatisfied precondition constraint (for example, init(y fn main() { let y: int = 42; - let x: int; + let mut x: int; loop { log(debug, y); while true { while true { while true { x <- y; } } } + //!^ ERROR use of moved variable: `y` + //!^^ NOTE move of variable occurred here } } diff --git a/src/test/compile-fail/liveness-or-init.rs b/src/test/compile-fail/liveness-or-init.rs new file mode 100644 index 00000000000..5912378cf42 --- /dev/null +++ b/src/test/compile-fail/liveness-or-init.rs @@ -0,0 +1,6 @@ +fn main() { + let i: int; + + log(debug, false || { i = 5; true }); + log(debug, i); //! ERROR use of possibly uninitialized variable: `i` +} diff --git a/src/test/compile-fail/liveness-return.rs b/src/test/compile-fail/liveness-return.rs new file mode 100644 index 00000000000..cee1444ca63 --- /dev/null +++ b/src/test/compile-fail/liveness-return.rs @@ -0,0 +1,6 @@ +fn f() -> int { + let x: int; + ret x; //! ERROR use of possibly uninitialized variable: `x` +} + +fn main() { f(); } diff --git a/src/test/compile-fail/liveness-swap-uninit.rs b/src/test/compile-fail/liveness-swap-uninit.rs new file mode 100644 index 00000000000..53714c1f740 --- /dev/null +++ b/src/test/compile-fail/liveness-swap-uninit.rs @@ -0,0 +1,5 @@ +fn main() { + let x = 3; + let y; + x <-> y; //! ERROR use of possibly uninitialized variable: `y` +} diff --git a/src/test/compile-fail/liveness-uninit-after-item.rs b/src/test/compile-fail/liveness-uninit-after-item.rs new file mode 100644 index 00000000000..7c619804c3c --- /dev/null +++ b/src/test/compile-fail/liveness-uninit-after-item.rs @@ -0,0 +1,6 @@ +fn main() { + let bar; + fn baz(x: int) { } + bind baz(bar); //! ERROR use of possibly uninitialized variable: `bar` +} + diff --git a/src/test/compile-fail/liveness-uninit.rs b/src/test/compile-fail/liveness-uninit.rs new file mode 100644 index 00000000000..1930a2e3352 --- /dev/null +++ b/src/test/compile-fail/liveness-uninit.rs @@ -0,0 +1,6 @@ +fn foo(x: int) { log(debug, x); } + +fn main() { + let x: int; + foo(x); //! ERROR use of possibly uninitialized variable: `x` +} diff --git a/src/test/compile-fail/liveness-use-after-move.rs b/src/test/compile-fail/liveness-use-after-move.rs new file mode 100644 index 00000000000..48d7a9a303e --- /dev/null +++ b/src/test/compile-fail/liveness-use-after-move.rs @@ -0,0 +1,5 @@ +fn main() { + let x = @5; + let y <- x; //! NOTE move of variable occurred here + log(debug, *x); //! ERROR use of moved variable: `x` +} diff --git a/src/test/compile-fail/use-after-send.rs b/src/test/compile-fail/liveness-use-after-send.rs similarity index 69% rename from src/test/compile-fail/use-after-send.rs rename to src/test/compile-fail/liveness-use-after-send.rs index 4fe164878a8..63e42bee3c9 100644 --- a/src/test/compile-fail/use-after-send.rs +++ b/src/test/compile-fail/liveness-use-after-send.rs @@ -1,4 +1,3 @@ -// error-pattern:unsatisfied precondition constraint fn send(ch: _chan, -data: T) { log(debug, ch); log(debug, data); @@ -10,8 +9,8 @@ enum _chan = int; // Tests that "log(debug, message);" is flagged as using // message after the send deinitializes it fn test00_start(ch: _chan, message: int, count: int) { - send(ch, message); - log(debug, message); + send(ch, message); //! NOTE move of variable occurred here + log(debug, message); //! ERROR use of moved variable: `message` } fn main() { fail; } diff --git a/src/test/compile-fail/liveness-use-in-index-lvalue.rs b/src/test/compile-fail/liveness-use-in-index-lvalue.rs new file mode 100644 index 00000000000..50d0662f803 --- /dev/null +++ b/src/test/compile-fail/liveness-use-in-index-lvalue.rs @@ -0,0 +1,6 @@ +fn test() { + let w: [int]; + w[5] = 0; //! ERROR use of possibly uninitialized variable: `w` +} + +fn main() { test(); } diff --git a/src/test/compile-fail/liveness-while-break.rs b/src/test/compile-fail/liveness-while-break.rs new file mode 100644 index 00000000000..755deb31fba --- /dev/null +++ b/src/test/compile-fail/liveness-while-break.rs @@ -0,0 +1,12 @@ +fn test(cond: bool) { + let v; + while cond { + v = 3; + break; + } + #debug["%d", v]; //! ERROR use of possibly uninitialized variable: `v` +} + +fn main() { + test(true); +} diff --git a/src/test/compile-fail/liveness-while-cond.rs b/src/test/compile-fail/liveness-while-cond.rs new file mode 100644 index 00000000000..a0a90e9550c --- /dev/null +++ b/src/test/compile-fail/liveness-while-cond.rs @@ -0,0 +1,4 @@ +fn main() { + let x: bool; + while x { } //! ERROR use of possibly uninitialized variable: `x` +} diff --git a/src/test/compile-fail/liveness-while.rs b/src/test/compile-fail/liveness-while.rs new file mode 100644 index 00000000000..b69012bc3f2 --- /dev/null +++ b/src/test/compile-fail/liveness-while.rs @@ -0,0 +1,7 @@ +fn f() -> int { + let mut x: int; + while 1 == 1 { x = 10; } + ret x; //! ERROR use of possibly uninitialized variable: `x` +} + +fn main() { f(); } diff --git a/src/test/compile-fail/move-arg.rs b/src/test/compile-fail/move-arg.rs deleted file mode 100644 index 512cd44e206..00000000000 --- a/src/test/compile-fail/move-arg.rs +++ /dev/null @@ -1,4 +0,0 @@ -// error-pattern:unsatisfied precondition constraint -fn test(-foo: int) { assert (foo == 10); } - -fn main() { let x = 10; test(x); log(debug, x); } diff --git a/src/test/compile-fail/or-init.rs b/src/test/compile-fail/or-init.rs deleted file mode 100644 index 0bc339904ba..00000000000 --- a/src/test/compile-fail/or-init.rs +++ /dev/null @@ -1,8 +0,0 @@ -// error-pattern:unsatisfied precondition constraint (for example, init(i - -fn main() { - let i: int; - - log(debug, false || { i = 5; true }); - log(debug, i); -} diff --git a/src/test/compile-fail/return-uninit.rs b/src/test/compile-fail/return-uninit.rs deleted file mode 100644 index 1978ec5f420..00000000000 --- a/src/test/compile-fail/return-uninit.rs +++ /dev/null @@ -1,5 +0,0 @@ -// error-pattern: precondition constraint - -fn f() -> int { let x: int; ret x; } - -fn main() { f(); } diff --git a/src/test/compile-fail/swap-uninit.rs b/src/test/compile-fail/swap-uninit.rs deleted file mode 100644 index fbf400db08a..00000000000 --- a/src/test/compile-fail/swap-uninit.rs +++ /dev/null @@ -1,3 +0,0 @@ -// error-pattern:unsatisfied precondition - -fn main() { let x = 3; let y; x <-> y; } diff --git a/src/test/compile-fail/tstate-and-init.rs b/src/test/compile-fail/tstate-and-init.rs new file mode 100644 index 00000000000..7b254fe2e19 --- /dev/null +++ b/src/test/compile-fail/tstate-and-init.rs @@ -0,0 +1,7 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } +fn main() { + let i: int = 4; + log(debug, false && { check is_even(i); true }); + even(i); //! ERROR unsatisfied precondition +} diff --git a/src/test/compile-fail/tstate-block-uninit.rs b/src/test/compile-fail/tstate-block-uninit.rs new file mode 100644 index 00000000000..3fbf812a9dc --- /dev/null +++ b/src/test/compile-fail/tstate-block-uninit.rs @@ -0,0 +1,11 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn force(f: fn()) { f(); } + +fn main() { + let x: int = 4; + force(fn&() { + even(x); //! ERROR unsatisfied precondition + }); +} diff --git a/src/test/compile-fail/tstate-break-uninit-2.rs b/src/test/compile-fail/tstate-break-uninit-2.rs new file mode 100644 index 00000000000..8ea5446c809 --- /dev/null +++ b/src/test/compile-fail/tstate-break-uninit-2.rs @@ -0,0 +1,16 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn foo() -> int { + let x: int = 4; + + while 1 != 2 { + break; + check is_even(x); //! WARNING unreachable statement + } + + even(x); //! ERROR unsatisfied precondition + ret 17; +} + +fn main() { log(debug, foo()); } diff --git a/src/test/compile-fail/tstate-break-uninit.rs b/src/test/compile-fail/tstate-break-uninit.rs new file mode 100644 index 00000000000..55146447b44 --- /dev/null +++ b/src/test/compile-fail/tstate-break-uninit.rs @@ -0,0 +1,16 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn foo() -> int { + let x: int = 4; + + loop { + break; + check is_even(x); //! WARNING unreachable statement + } + + even(x); //! ERROR unsatisfied precondition + ret 17; +} + +fn main() { log(debug, foo()); } diff --git a/src/test/compile-fail/tstate-ctor-unsat.rs b/src/test/compile-fail/tstate-ctor-unsat.rs new file mode 100644 index 00000000000..d249865cd49 --- /dev/null +++ b/src/test/compile-fail/tstate-ctor-unsat.rs @@ -0,0 +1,25 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +class cat { + priv { + let mut meows : uint; + } + + let how_hungry : int; + + fn eat() { + self.how_hungry -= 5; + } + + new(in_x : uint, in_y : int) { + let foo = 3; + self.meows = in_x + (in_y as uint); + self.how_hungry = even(foo); //! ERROR unsatisfied precondition + } +} + +fn main() { + let nyan : cat = cat(52u, 99); + nyan.eat(); +} diff --git a/src/test/compile-fail/tstate-fru.rs b/src/test/compile-fail/tstate-fru.rs new file mode 100644 index 00000000000..a4989d86ab4 --- /dev/null +++ b/src/test/compile-fail/tstate-fru.rs @@ -0,0 +1,13 @@ +// -*- rust -*- + +type point = {x: int, y: int}; + +pure fn test(_p: point) -> bool { true } +fn tested(p: point) : test(p) -> point { p } + +fn main() { + let origin: point; + origin = {x: 0, y: 0}; + let right: point = {x: 10 with tested(origin)}; + //!^ ERROR precondition +} diff --git a/src/test/compile-fail/tstate-if-no-else.rs b/src/test/compile-fail/tstate-if-no-else.rs new file mode 100644 index 00000000000..fbc02bf591a --- /dev/null +++ b/src/test/compile-fail/tstate-if-no-else.rs @@ -0,0 +1,10 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn foo(x: int) { log(debug, x); } + +fn main() { + let x: int = 10; + if 1 > 2 { check is_even(x); } + even(x); //! ERROR unsatisfied precondition +} diff --git a/src/test/compile-fail/tstate-if-with-else.rs b/src/test/compile-fail/tstate-if-with-else.rs new file mode 100644 index 00000000000..74edb4b8d8b --- /dev/null +++ b/src/test/compile-fail/tstate-if-with-else.rs @@ -0,0 +1,14 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn foo(x: int) { log(debug, x); } + +fn main() { + let x: int = 10; + if 1 > 2 { + #debug("whoops"); + } else { + check is_even(x); + } + even(x); //! ERROR unsatisfied precondition +} diff --git a/src/test/compile-fail/tstate-loop-constraints.rs b/src/test/compile-fail/tstate-loop-constraints.rs new file mode 100644 index 00000000000..34ff0753d86 --- /dev/null +++ b/src/test/compile-fail/tstate-loop-constraints.rs @@ -0,0 +1,19 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn main() { + + let mut x: int = 42; + loop { + loop { + loop { + check is_even(x); + even(x); // OK + loop { + even(x); //! ERROR unsatisfied precondition + x = 11; + } + } + } + } +} diff --git a/src/test/compile-fail/tstate-or-init.rs b/src/test/compile-fail/tstate-or-init.rs new file mode 100644 index 00000000000..c26925929a6 --- /dev/null +++ b/src/test/compile-fail/tstate-or-init.rs @@ -0,0 +1,7 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } +fn main() { + let i: int = 4; + log(debug, false || { check is_even(i); true }); + even(i); //! ERROR unsatisfied precondition +} diff --git a/src/test/compile-fail/tstate-return.rs b/src/test/compile-fail/tstate-return.rs new file mode 100644 index 00000000000..6d786bacd7b --- /dev/null +++ b/src/test/compile-fail/tstate-return.rs @@ -0,0 +1,9 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn f() -> int { + let x: int = 4; + ret even(x); //! ERROR unsatisfied precondition +} + +fn main() { f(); } diff --git a/src/test/compile-fail/tstate-unsat-after-item.rs b/src/test/compile-fail/tstate-unsat-after-item.rs new file mode 100644 index 00000000000..b82df3a8657 --- /dev/null +++ b/src/test/compile-fail/tstate-unsat-after-item.rs @@ -0,0 +1,9 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn main() { + let x = 4; + fn baz(_x: int) { } + bind baz(even(x)); //! ERROR unsatisfied precondition +} + diff --git a/src/test/compile-fail/tstate-unsat-in-called-fn-expr.rs b/src/test/compile-fail/tstate-unsat-in-called-fn-expr.rs new file mode 100644 index 00000000000..010ac92f7b0 --- /dev/null +++ b/src/test/compile-fail/tstate-unsat-in-called-fn-expr.rs @@ -0,0 +1,9 @@ +fn foo(v: [int]) : vec::is_empty(v) { #debug("%d", v[0]); } + +fn main() { + let f = fn@() { + let v = [1]; + foo(v); //! ERROR unsatisfied precondition constraint + }(); + log(error, f); +} diff --git a/src/test/compile-fail/tstate-unsat-in-fn-expr.rs b/src/test/compile-fail/tstate-unsat-in-fn-expr.rs new file mode 100644 index 00000000000..b9cd2582537 --- /dev/null +++ b/src/test/compile-fail/tstate-unsat-in-fn-expr.rs @@ -0,0 +1,9 @@ +fn foo(v: [int]) : vec::is_empty(v) { #debug("%d", v[0]); } + +fn main() { + let f = fn@() { + let v = [1]; + foo(v); //! ERROR unsatisfied precondition constraint + }; + log(error, f()); +} diff --git a/src/test/compile-fail/tstate-unsat.rs b/src/test/compile-fail/tstate-unsat.rs new file mode 100644 index 00000000000..44a3e88fdee --- /dev/null +++ b/src/test/compile-fail/tstate-unsat.rs @@ -0,0 +1,7 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn main() { + let x: int = 4; + even(x); //! ERROR unsatisfied precondition +} diff --git a/src/test/compile-fail/tstate-while-break.rs b/src/test/compile-fail/tstate-while-break.rs new file mode 100644 index 00000000000..6a25929a85b --- /dev/null +++ b/src/test/compile-fail/tstate-while-break.rs @@ -0,0 +1,15 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn test(cond: bool) { + let v = 4; + while cond { + check is_even(v); + break; + } + even(v); //! ERROR unsatisfied precondition +} + +fn main() { + test(true); +} diff --git a/src/test/compile-fail/tstate-while-cond.rs b/src/test/compile-fail/tstate-while-cond.rs new file mode 100644 index 00000000000..ae5436aa4e8 --- /dev/null +++ b/src/test/compile-fail/tstate-while-cond.rs @@ -0,0 +1,7 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn main() { + let x: int = 4; + while even(x) != 0 { } //! ERROR unsatisfied precondition +} diff --git a/src/test/compile-fail/while-loop-pred-constraints.rs b/src/test/compile-fail/tstate-while-loop-unsat-constriants.rs similarity index 100% rename from src/test/compile-fail/while-loop-pred-constraints.rs rename to src/test/compile-fail/tstate-while-loop-unsat-constriants.rs diff --git a/src/test/compile-fail/tstate-while.rs b/src/test/compile-fail/tstate-while.rs new file mode 100644 index 00000000000..6091a0237a2 --- /dev/null +++ b/src/test/compile-fail/tstate-while.rs @@ -0,0 +1,10 @@ +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn f() { + let mut x: int = 10; + while 1 == 1 { x = 10; } + even(x); //! ERROR unsatisfied precondition +} + +fn main() { f(); } diff --git a/src/test/compile-fail/uninit-after-item.rs b/src/test/compile-fail/uninit-after-item.rs deleted file mode 100644 index 560c855a7e6..00000000000 --- a/src/test/compile-fail/uninit-after-item.rs +++ /dev/null @@ -1,7 +0,0 @@ -// error-pattern:unsatisfied precondition constraint (for example, init(bar -fn main() { - let bar; - fn baz(x: int) { } - bind baz(bar); -} - diff --git a/src/test/compile-fail/use-after-move.rs b/src/test/compile-fail/use-after-move.rs deleted file mode 100644 index 177ad0010be..00000000000 --- a/src/test/compile-fail/use-after-move.rs +++ /dev/null @@ -1,2 +0,0 @@ -// error-pattern:unsatisfied precondition constraint (for example, init(x -fn main() { let x = @5; let y <- x; log(debug, *x); } diff --git a/src/test/compile-fail/use-uninit-2.rs b/src/test/compile-fail/use-uninit-2.rs deleted file mode 100644 index 82946cf022a..00000000000 --- a/src/test/compile-fail/use-uninit-2.rs +++ /dev/null @@ -1,5 +0,0 @@ -// error-pattern:unsatisfied precondition - -fn foo(x: int) { log(debug, x); } - -fn main() { let x: int; if 1 > 2 { x = 10; } foo(x); } diff --git a/src/test/compile-fail/use-uninit.rs b/src/test/compile-fail/use-uninit.rs deleted file mode 100644 index 3ad93fe0a08..00000000000 --- a/src/test/compile-fail/use-uninit.rs +++ /dev/null @@ -1,5 +0,0 @@ -// error-pattern:unsatisfied precondition - -fn foo(x: int) { log(debug, x); } - -fn main() { let x: int; foo(x); } diff --git a/src/test/compile-fail/while-bypass.rs b/src/test/compile-fail/while-bypass.rs deleted file mode 100644 index 231fb299898..00000000000 --- a/src/test/compile-fail/while-bypass.rs +++ /dev/null @@ -1,5 +0,0 @@ -// error-pattern: precondition constraint - -fn f() -> int { let x: int; while 1 == 1 { x = 10; } ret x; } - -fn main() { f(); } diff --git a/src/test/compile-fail/while-expr.rs b/src/test/compile-fail/while-expr.rs deleted file mode 100644 index 5ff3adf34c3..00000000000 --- a/src/test/compile-fail/while-expr.rs +++ /dev/null @@ -1,3 +0,0 @@ -// error-pattern: precondition constraint - -fn main() { let x: bool; while x { } } diff --git a/src/test/compile-fail/while-loop-constraints.rs b/src/test/compile-fail/while-loop-constraints.rs deleted file mode 100644 index 38c1d471593..00000000000 --- a/src/test/compile-fail/while-loop-constraints.rs +++ /dev/null @@ -1,14 +0,0 @@ -// error-pattern:unsatisfied precondition constraint (for example, init(y -fn main() { - - let y: int = 42; - let x: int; - loop { - log(debug, y); - loop { - loop { - loop { x <- y; } - } - } - } -} diff --git a/src/test/compile-fail/writing-through-uninit-vec.rs b/src/test/compile-fail/writing-through-uninit-vec.rs deleted file mode 100644 index 283e925e207..00000000000 --- a/src/test/compile-fail/writing-through-uninit-vec.rs +++ /dev/null @@ -1,5 +0,0 @@ -// error-pattern:unsatisfied precondition constraint - -fn test() { let w: [int]; w[5] = 0; } - -fn main() { test(); } diff --git a/src/test/run-pass/liveness-assign-imm-local-after-loop.rs b/src/test/run-pass/liveness-assign-imm-local-after-loop.rs new file mode 100644 index 00000000000..9fbf5fb447f --- /dev/null +++ b/src/test/run-pass/liveness-assign-imm-local-after-loop.rs @@ -0,0 +1,10 @@ +fn test(cond: bool) { + let v: int; + v = 1; + loop { } // loop never terminates, so no error is reported + v = 2; +} + +fn main() { + // note: don't call test()... :) +} diff --git a/src/test/run-pass/liveness-assign-imm-local-after-ret.rs b/src/test/run-pass/liveness-assign-imm-local-after-ret.rs new file mode 100644 index 00000000000..4b1cc591713 --- /dev/null +++ b/src/test/run-pass/liveness-assign-imm-local-after-ret.rs @@ -0,0 +1,9 @@ +fn test() { + let _v: int; + _v = 1; + ret; + _v = 2; //! WARNING: unreachable statement +} + +fn main() { +} diff --git a/src/test/run-pass/liveness-loop-break.rs b/src/test/run-pass/liveness-loop-break.rs new file mode 100644 index 00000000000..58274555202 --- /dev/null +++ b/src/test/run-pass/liveness-loop-break.rs @@ -0,0 +1,14 @@ +// xfail-test --- tstate incorrectly fails this + +fn test() { + let v; + loop { + v = 3; + break; + } + #debug["%d", v]; +} + +fn main() { + test(); +} diff --git a/src/test/run-pass/liveness-move-in-loop.rs b/src/test/run-pass/liveness-move-in-loop.rs new file mode 100644 index 00000000000..a7f9547c059 --- /dev/null +++ b/src/test/run-pass/liveness-move-in-loop.rs @@ -0,0 +1,15 @@ +fn take(-x: int) -> int {x} + +fn the_loop() { + let mut list = []; + loop { + let x = 5; + if x > 3 { + list += [take(x)]; + } else { + break; + } + } +} + +fn main() {} diff --git a/src/test/run-pass/tstate-loop-break.rs b/src/test/run-pass/tstate-loop-break.rs new file mode 100644 index 00000000000..14a276b316a --- /dev/null +++ b/src/test/run-pass/tstate-loop-break.rs @@ -0,0 +1,17 @@ +// xfail-test + +pure fn is_even(i: int) -> bool { (i%2) == 0 } +fn even(i: int) : is_even(i) -> int { i } + +fn test() { + let v = 4; + loop { + check is_even(v); + break; + } + even(v); +} + +fn main() { + test(); +}