about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorTim Chevalier <chevalier@alum.wellesley.edu>2011-04-21 17:39:04 -0700
committerGraydon Hoare <graydon@mozilla.com>2011-04-28 13:26:19 -0700
commit0190ebfe0748f28c290eb4759a03ffbd416cbeac (patch)
treea419e87e4071017aa52399f74646865f3f2c22ae /src
parent7c4f8cb45924326e21547d19cbed683115657616 (diff)
downloadrust-0190ebfe0748f28c290eb4759a03ffbd416cbeac.tar.gz
rust-0190ebfe0748f28c290eb4759a03ffbd416cbeac.zip
Support all expression forms in typestate
Added support for self_method, cont, chan, port, recv, send, be,
do_while, spawn, and ext; handled break and cont correctly.
(However, there are no non-xfailed test cases for ext or spawn in
stage0 currently.)

Although the standard library compiles and all test cases pass with
typestate enabled, I left typestate checking disabled as rustc
terminates abnormally when building the standard library if so,
even though it does generate code correctly.
Diffstat (limited to 'src')
-rw-r--r--src/comp/middle/typestate_check.rs210
-rw-r--r--src/comp/util/common.rs32
-rw-r--r--src/test/compile-fail/break-uninit.rs22
-rw-r--r--src/test/compile-fail/break-uninit2.rs22
4 files changed, 274 insertions, 12 deletions
diff --git a/src/comp/middle/typestate_check.rs b/src/comp/middle/typestate_check.rs
index 04aeebdf37c..693521778dd 100644
--- a/src/comp/middle/typestate_check.rs
+++ b/src/comp/middle/typestate_check.rs
@@ -106,6 +106,7 @@ import util.common.log_stmt;
 import util.common.log_block;
 import util.common.log_stmt_err;
 import util.common.log_block_err;
+import util.common.has_nonlocal_exits;
 import util.common.decl_lhs;
 import util.typestate_ann;
 import util.typestate_ann.ts_ann;
@@ -466,6 +467,9 @@ fn expr_ann(&expr e) -> ann {
     case (expr_cont(?a)) {
         ret a;
     }
+    case (expr_self_method(_, ?a)) {
+        ret a;
+    }
   }
 }
 
@@ -896,6 +900,11 @@ fn find_pre_post_expr(&fn_info_map fm, &fn_info enclosing, &expr e) -> () {
             _vec.push[@expr](args, operator);
             find_pre_post_exprs(fm, enclosing, args, a);
         }
+        case (expr_spawn(_, _, ?operator, ?operands, ?a)) {
+            auto args = _vec.clone[@expr](operands);
+            _vec.push[@expr](args, operator);
+            find_pre_post_exprs(fm, enclosing, args, a);
+        }
         case (expr_vec(?args, _, ?a)) {
             find_pre_post_exprs(fm, enclosing, args, a);
         }
@@ -923,10 +932,19 @@ fn find_pre_post_expr(&fn_info_map fm, &fn_info enclosing, &expr e) -> () {
             // Otherwise, variable is global, so it must be initialized
             set_pre_and_post(a, res);
         }
+        case (expr_self_method(?v, ?a)) {
+            /* v is a method of the enclosing obj, so it must be
+               initialized, right? */
+            set_pre_and_post(a, empty_pre_post(num_local_vars));
+        }
         case(expr_log(_, ?arg, ?a)) {
             find_pre_post_expr(fm, enclosing, *arg);
             set_pre_and_post(a, expr_pp(*arg));
         }
+        case (expr_chan(?arg, ?a)) {
+            find_pre_post_expr(fm, enclosing, *arg);
+            set_pre_and_post(a, expr_pp(*arg));
+        }
         case(expr_put(?opt, ?a)) {
             alt (opt) {
                 case (some[@expr](?arg)) {
@@ -963,6 +981,22 @@ fn find_pre_post_expr(&fn_info_map fm, &fn_info enclosing, &expr e) -> () {
                 }
             }
         }
+        case (expr_recv(?lhs, ?rhs, ?a)) {
+            alt (lhs.node) {
+                case (expr_path(?p, some[def](def_local(?d_id)), ?a_lhs)) {
+                    find_pre_post_expr(fm, enclosing, *rhs);
+                    set_pre_and_post(a, expr_pp(*rhs));
+                    log("gen:");
+                    log_expr(e);
+                    gen(enclosing, a, d_id);
+                }
+                case (_) {
+                    // doesn't check that lhs is an lval, but
+                    // that's probably ok
+                    find_pre_post_exprs(fm, enclosing, vec(lhs, rhs), a);
+                }
+            }
+        }
         case (expr_assign_op(_, ?lhs, ?rhs, ?a)) {
             /* Different from expr_assign in that the lhs *must*
                already be initialized */
@@ -987,6 +1021,11 @@ fn find_pre_post_expr(&fn_info_map fm, &fn_info enclosing, &expr e) -> () {
                 }
             }
         }
+        case (expr_be(?e, ?a)) {
+            find_pre_post_expr(fm, enclosing, *e);
+            set_pre_and_post(a, rec(precondition=expr_prestate(*e),
+                          postcondition=false_postcond(num_local_vars)));
+        }
         case (expr_if(?antec, ?conseq, ?maybe_alt, ?a)) {
             find_pre_post_expr(fm, enclosing, *antec);
             find_pre_post_block(fm, enclosing, conseq);
@@ -1028,6 +1067,9 @@ fn find_pre_post_expr(&fn_info_map fm, &fn_info enclosing, &expr e) -> () {
              FIXME */
             find_pre_post_exprs(fm, enclosing, vec(l, r), a);
         }
+        case (expr_send(?l, ?r, ?a)) {
+            find_pre_post_exprs(fm, enclosing, vec(l, r), a);
+        }
         case (expr_unary(_,?operand,?a)) {
             find_pre_post_expr(fm, enclosing, *operand);
             set_pre_and_post(a, expr_pp(*operand));
@@ -1048,6 +1090,24 @@ fn find_pre_post_expr(&fn_info_map fm, &fn_info enclosing, &expr e) -> () {
                   intersect_postconds(vec(expr_postcond(*test),
                                           block_postcond(body)))));
         }
+        case (expr_do_while(?body, ?test, ?a)) {
+            find_pre_post_block(fm, enclosing, body);
+            find_pre_post_expr(fm, enclosing, *test);
+   
+            auto loop_postcond = union_postconds(num_local_vars,
+                            vec(block_postcond(body), expr_postcond(*test)));
+            /* conservative approximination: if the body
+               could break or cont, the test may never be executed */
+            if (has_nonlocal_exits(body)) {
+                loop_postcond = empty_poststate(num_local_vars);
+            }
+
+            set_pre_and_post(a, 
+               rec(precondition=seq_preconds(num_local_vars,
+                                             vec(block_pp(body),
+                                                 expr_pp(*test))),
+                   postcondition=loop_postcond));
+        }
         case (expr_for(?d, ?index, ?body, ?a)) {
             find_pre_post_loop(fm, enclosing, d, index, body, a);
         }
@@ -1104,10 +1164,15 @@ fn find_pre_post_expr(&fn_info_map fm, &fn_info enclosing, &expr e) -> () {
         case (expr_break(?a)) {
             set_pre_and_post(a, empty_pre_post(num_local_vars));
         }
-        case(_) {
-            log_err("this sort of expr isn't implemented!");
-            log_expr_err(e);
-            fail;
+        case (expr_cont(?a)) {
+            set_pre_and_post(a, empty_pre_post(num_local_vars));
+        }
+        case (expr_port(?a)) {
+            set_pre_and_post(a, empty_pre_post(num_local_vars));
+        }
+        case (expr_ext(_, _, _, ?expanded, ?a)) {
+            find_pre_post_expr(fm, enclosing, *expanded);
+            set_pre_and_post(a, expr_pp(*expanded));
         }
     }
 }
@@ -1181,6 +1246,22 @@ fn find_pre_post_stmt(fn_info_map fm, &fn_info enclosing, &ast.stmt s)
 
 fn find_pre_post_block(&fn_info_map fm, &fn_info enclosing, block b)
     -> () {
+    /* Want to say that if there is a break or cont in this
+     block, then that invalidates the poststate upheld by
+    any of the stmts after it. 
+    Given that the typechecker has run, we know any break will be in
+    a block that forms a loop body. So that's ok. There'll never be an
+    expr_break outside a loop body, therefore, no expr_break outside a block.
+    */
+
+    /* Conservative approximation for now: This says that if a block contains
+     *any* breaks or conts, then its postcondition doesn't promise anything.
+     This will mean that:
+     x = 0;
+     break;
+
+     won't have a postcondition that says x is initialized, but that's ok.
+     */
     auto nv = num_locals(enclosing);
 
     fn do_one_(fn_info_map fm, fn_info i, &@stmt s) -> () {
@@ -1214,7 +1295,11 @@ fn find_pre_post_block(&fn_info_map fm, &fn_info enclosing, block b)
     /* A block may be empty, so this next line ensures that the postconds
        vector is non-empty. */
     _vec.push[postcond](postconds, block_precond);
-    auto block_postcond = union_postconds(nv, postconds);
+    auto block_postcond = empty_poststate(nv);
+    /* conservative approximation */
+    if (! has_nonlocal_exits(b)) {
+        block_postcond = union_postconds(nv, postconds);
+    }
     set_pre_and_post(b.node.a, rec(precondition=block_precond,
                                    postcondition=block_postcond));
 }
@@ -1406,6 +1491,12 @@ fn find_pre_post_state_expr(&fn_info_map fm, &fn_info enclosing,
                                     expr_poststate(*operator), a, operands)
           || changed);
     }
+    case (expr_spawn(_, _, ?operator, ?operands, ?a)) {
+        changed = find_pre_post_state_expr(fm, enclosing, pres, operator);
+        ret(find_pre_post_state_exprs(fm, enclosing,
+                 expr_poststate(*operator), a, operands)
+          || changed);
+    }
     case (expr_bind(?operator, ?maybe_args, ?a)) {
         changed = find_pre_post_state_expr(fm, enclosing, pres, operator)
             || changed;
@@ -1423,6 +1514,19 @@ fn find_pre_post_state_expr(&fn_info_map fm, &fn_info enclosing,
         changed = extend_poststate_ann(a, expr_poststate(*e)) || changed;
         ret changed;
     }
+    case (expr_chan(?e, ?a)) {
+        changed = find_pre_post_state_expr(fm, enclosing, pres, e);
+        changed = extend_prestate_ann(a, pres) || changed;
+        changed = extend_poststate_ann(a, expr_poststate(*e)) || changed;
+        ret changed;
+    }
+    case (expr_ext(_, _, _, ?expanded, ?a)) {
+        changed = find_pre_post_state_expr(fm, enclosing, pres, expanded);
+        changed = extend_prestate_ann(a, pres) || changed;
+        changed = extend_poststate_ann(a, expr_poststate(*expanded))
+           || changed;
+        ret changed;
+    }
     case (expr_put(?maybe_e, ?a)) {
         alt (maybe_e) {
             case (some[@expr](?arg)) {
@@ -1486,6 +1590,32 @@ fn find_pre_post_state_expr(&fn_info_map fm, &fn_info enclosing,
         }
         ret changed;
     }
+    case (expr_recv(?lhs, ?rhs, ?a)) {
+        extend_prestate_ann(a, pres);
+
+        alt (lhs.node) {
+            case (expr_path(?p, some[def](def_local(?d_id)), ?a_lhs)) {
+                // receive to local var
+                changed = pure_exp(a_lhs, pres) || changed;
+                changed = find_pre_post_state_expr(fm, enclosing, pres, rhs)
+                    || changed;
+                changed = extend_poststate_ann(a, expr_poststate(*rhs))
+                    || changed;
+                changed = gen_poststate(enclosing, a, d_id) || changed;
+            }
+            case (_) {
+                // receive to something that must already have been init'd
+                changed = find_pre_post_state_expr(fm, enclosing, pres, lhs)
+                    || changed;
+                changed = find_pre_post_state_expr(fm, enclosing,
+                     expr_poststate(*lhs), rhs) || changed;
+                changed = extend_poststate_ann(a, expr_poststate(*rhs))
+                    || changed;
+            }
+        }
+        ret changed;
+    }
+
     case (expr_ret(?maybe_ret_val, ?a)) {
         changed = extend_prestate_ann(a, pres) || changed;
         set_poststate_ann(a, false_postcond(num_local_vars));
@@ -1498,6 +1628,12 @@ fn find_pre_post_state_expr(&fn_info_map fm, &fn_info enclosing,
         }
         ret changed;
     }
+    case (expr_be(?e, ?a)) {
+        changed = extend_prestate_ann(a, pres) || changed;
+        set_poststate_ann(a, false_postcond(num_local_vars));
+        changed = find_pre_post_state_expr(fm, enclosing, pres, e) || changed;
+        ret changed;
+    }
     case (expr_if(?antec, ?conseq, ?maybe_alt, ?a)) {
         changed = extend_prestate_ann(a, pres) || changed;
         changed = find_pre_post_state_expr(fm, enclosing, pres, antec)
@@ -1529,6 +1665,15 @@ fn find_pre_post_state_expr(&fn_info_map fm, &fn_info enclosing,
         changed = extend_poststate_ann(a, expr_poststate(*r)) || changed;
         ret changed;
     }
+    case (expr_send(?l, ?r, ?a)) {
+        changed = extend_prestate_ann(a, pres) || changed;
+        changed = find_pre_post_state_expr(fm, enclosing, pres, l)
+                    || changed;
+        changed = find_pre_post_state_expr(fm,
+                    enclosing, expr_poststate(*l), r) || changed;
+        changed = extend_poststate_ann(a, expr_poststate(*r)) || changed;
+        ret changed;
+    }
     case (expr_assign_op(?op, ?lhs, ?rhs, ?a)) {
         /* quite similar to binary -- should abstract this */
         changed = extend_prestate_ann(a, pres) || changed;
@@ -1558,6 +1703,27 @@ fn find_pre_post_state_expr(&fn_info_map fm, &fn_info enclosing,
                                         block_poststate(body)))) || changed;
         ret changed;
     }
+    case (expr_do_while(?body, ?test, ?a)) {
+        changed = extend_prestate_ann(a, pres) || changed;
+        changed = find_pre_post_state_block(fm, enclosing, pres, body)
+            || changed;
+        changed = find_pre_post_state_expr(fm, enclosing,
+                     block_poststate(body), test) || changed;
+
+        /* conservative approximination: if the body of the loop
+           could break or cont, we revert to the prestate
+           (TODO: could treat cont differently from break, since
+           if there's a cont, the test will execute) */
+        if (has_nonlocal_exits(body)) {
+            changed = set_poststate_ann(a, pres) || changed;
+        }
+        else {
+            changed = extend_poststate_ann(a, expr_poststate(*test))
+              || changed;
+        }
+
+        ret changed;
+    }
     case (expr_for(?d, ?index, ?body, ?a)) {
         ret find_pre_post_state_loop(fm, enclosing, pres, d, index, body, a);
     }
@@ -1634,13 +1800,16 @@ fn find_pre_post_state_expr(&fn_info_map fm, &fn_info enclosing,
     case (expr_break(?a)) {
         ret pure_exp(a, pres);
     }
-    case (_) {
-      log_err("find_pre_post_state_expr: implement this case!");
-      log_expr_err(*e);
-      fail;
+    case (expr_cont(?a)) {
+        ret pure_exp(a, pres);
+    }
+    case (expr_port(?a)) {
+        ret pure_exp(a, pres);
+    }
+    case (expr_self_method(_, ?a)) {
+        ret pure_exp(a, pres);
     }
   }
-
 }
 
 fn find_pre_post_state_stmt(&fn_info_map fm, &fn_info enclosing,
@@ -1733,6 +1902,8 @@ fn find_pre_post_state_block(&fn_info_map fm, &fn_info enclosing,
                              &prestate pres0, &block b)
   -> bool {
 
+    /* FIXME handle non-local exits */
+
   auto changed = false;
   auto num_local_vars = num_locals(enclosing);
 
@@ -1756,6 +1927,20 @@ fn find_pre_post_state_block(&fn_info_map fm, &fn_info enclosing,
       post = expr_poststate(*e);
     }
   }
+
+  /*
+  log_err("block:");
+  log_block_err(b);
+  log_err("has non-local exits?");
+  log_err(has_nonlocal_exits(b));
+  */
+
+  /* conservative approximation: if a block contains a break
+     or cont, we assume nothing about the poststate */
+  if (has_nonlocal_exits(b)) {
+      post = pres0;
+  }
+  
   set_prestate_ann(@b.node.a, pres0);
   set_poststate_ann(b.node.a, post);
 
@@ -1802,7 +1987,7 @@ fn check_states_expr(fn_info enclosing, &expr e) -> () {
   let prestate pres   = expr_prestate(e);
 
   if (!implies(pres, prec)) {
-      log_err("check_states_expr: unsatisfied precondition for ");
+      log_err("check_states_expr: Unsatisfied precondition constraint for ");
       log_expr_err(e);
       log_err("Precondition: ");
       log_bitv_err(enclosing, prec);
@@ -1831,7 +2016,8 @@ fn check_states_stmt(fn_info enclosing, &stmt s) -> () {
       */
 
       if (!implies(pres, prec)) {
-        log_err("check_states_stmt: unsatisfied precondition for ");
+        log_err("check_states_stmt: "
+              + "Unsatisfied precondition constraint for ");
         log_stmt_err(s);
         log_err("Precondition: ");
         log_bitv_err(enclosing, prec);
diff --git a/src/comp/util/common.rs b/src/comp/util/common.rs
index d2ed72a0ff3..fd0d688bab8 100644
--- a/src/comp/util/common.rs
+++ b/src/comp/util/common.rs
@@ -1,3 +1,5 @@
+import std.map;
+import std.map.hashmap;
 import std._uint;
 import std._int;
 import std._vec;
@@ -5,6 +7,9 @@ import std.option.none;
 import front.ast;
 import util.typestate_ann.ts_ann;
 
+import middle.fold;
+import middle.fold.respan;
+
 import std.io.stdout;
 import std.io.str_writer;
 import std.io.string_writer;
@@ -16,6 +21,7 @@ import pretty.pp.mkstate;
 type filename = str;
 type span = rec(uint lo, uint hi);
 type spanned[T] = rec(T node, span span);
+type flag = hashmap[str, ()];
 
 tag ty_mach {
     ty_i8;
@@ -222,6 +228,32 @@ fn decl_lhs(@ast.decl d) -> ast.def_id {
     }
 }
 
+fn has_nonlocal_exits(&ast.block b) -> bool {
+    /* overkill, but just passing around a mutable bool doesn't seem
+       to work in rustboot */
+    auto has_exits = new_str_hash[()]();
+ 
+   fn set_break(&flag f, &span sp, ast.ann a) -> @ast.expr {
+        f.insert("foo", ());
+        ret @respan(sp, ast.expr_break(a));
+    }
+    fn set_cont(&flag f, &span sp, ast.ann a) -> @ast.expr {
+        f.insert("foo", ());
+        ret @respan(sp, ast.expr_cont(a));
+    }
+    fn check_b(&flag f) -> bool {
+        ret (f.size() == 0u);
+    }
+
+    auto fld0 = fold.new_identity_fold[flag]();
+
+    fld0 = @rec(fold_expr_break = bind set_break(_,_,_),
+                fold_expr_cont  = bind set_cont(_,_,_),
+                keep_going      = bind check_b(_) with *fld0);
+    fold.fold_block[flag](has_exits, fld0, b);
+
+    ret (has_exits.size() > 0u);
+}
 //
 // Local Variables:
 // mode: rust
diff --git a/src/test/compile-fail/break-uninit.rs b/src/test/compile-fail/break-uninit.rs
new file mode 100644
index 00000000000..e9085dd25e8
--- /dev/null
+++ b/src/test/compile-fail/break-uninit.rs
@@ -0,0 +1,22 @@
+// xfail-boot
+// xfail-stage0
+// error-pattern:Unsatisfied precondition
+
+fn foo() -> int {
+  let int x;
+  let int i;
+
+  do {
+    i = 0;
+    break;
+    x = 0;
+  } while ((x = 0) != 0);
+
+  log(x);
+
+  ret 17;
+}
+
+fn main() {
+  log(foo());
+}
diff --git a/src/test/compile-fail/break-uninit2.rs b/src/test/compile-fail/break-uninit2.rs
new file mode 100644
index 00000000000..8ef83f08cf5
--- /dev/null
+++ b/src/test/compile-fail/break-uninit2.rs
@@ -0,0 +1,22 @@
+// xfail-boot
+// xfail-stage0
+// error-pattern:Unsatisfied precondition
+
+fn foo() -> int {
+  let int x;
+  let int i;
+
+  do {
+    i = 0;
+    break;
+    x = 0;
+  } while (1 != 2);
+
+  log(x);
+
+  ret 17;
+}
+
+fn main() {
+  log(foo());
+}