Fix handling of unlabeled break and continue in gflow.

This fixes issues:

http://code.google.com/p/google-web-toolkit/issues/detail?id=6272

http://code.google.com/p/google-web-toolkit/issues/detail?id=6429

(was issue 1453809)

Review at http://gwt-code-reviews.appspot.com/1447824

Review by: jbrosenberg@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10341 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java
index 6e135c2..aeb0cf7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilder.java
@@ -452,6 +452,7 @@
 
     @Override
     public boolean visit(JDoStatement x, Context ctx) {
+      List<Exit> unlabeledExits = removeUnlabeledExits();
       pushNode(new CfgStatementNode<JStatement>(parent, x));
       int pos = nodes.size();
 
@@ -473,6 +474,7 @@
       addNormalExit(node, CfgConditionalNode.ELSE);
 
       popNode();
+      addExits(unlabeledExits);
       return false;
     }
 
@@ -486,6 +488,7 @@
 
     @Override
     public boolean visit(JForStatement x, Context ctx) {
+      List<Exit> unlabeledExits = removeUnlabeledExits();
       pushNode(new CfgStatementNode<JStatement>(parent, x));
       accept(x.getInitializers());
 
@@ -519,6 +522,7 @@
       }
 
       popNode();
+      addExits(unlabeledExits);
       return false;
     }
 
@@ -970,6 +974,7 @@
 
     @Override
     public boolean visit(JWhileStatement x, Context ctx) {
+      List<Exit> unlabeledExits = removeUnlabeledExits();
       pushNode(new CfgStatementNode<JStatement>(parent, x));
       int pos = nodes.size();
       accept(x.getTestExpr());
@@ -992,6 +997,7 @@
       addNormalExit(node, CfgConditionalNode.ELSE);
 
       popNode();
+      addExits(unlabeledExits);
       return false;
     }
 
@@ -1014,7 +1020,7 @@
     }
 
     /**
-     * Transform all brake exits into normal exits, thus making sure that
+     * Transform all break exits into normal exits, thus making sure that
      * next node will get edges from them.
      */
     private void addBreakExits(String label) {
@@ -1163,6 +1169,21 @@
       addExits(labeledBreaks);
       return breakExits;
     }
+
+    private List<Exit> removeUnlabeledExits() {
+      List<Exit> unlabeledExits = new ArrayList<Exit>();
+      Exit.Reason reasons[] = { Exit.Reason.BREAK, Exit.Reason.CONTINUE };
+      for (Exit.Reason reason : reasons) {
+        for (Iterator<Exit> i = currentExitsByReason.get(reason).iterator(); i.hasNext();) {
+          Exit exit = i.next();
+          if (exit.getLabel() == null) {
+            i.remove();
+            unlabeledExits.add(exit);
+          }
+        }
+      }
+      return unlabeledExits;
+    }
   }
 
   /**
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizerTest.java
index ef88dd3..060bc4e 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizerTest.java
@@ -262,6 +262,36 @@
        );
   }
 
+  public void testComplexCode4() throws Exception {
+    addSnippetClassDecl("static boolean confirm() { return true; }");
+
+    optimize("int",
+       "int n = 0;",
+       "for (; ; ) {",
+       "  if (confirm()) {",
+       "    break;",
+       "  } else {",
+       "    for (int i = 0; i < 2; i++) {",
+       "      n = i;",
+       "    }",
+       "  }",
+       "}",
+       "return n;"
+   ).into(
+       "int n = 0;",
+       "for (; ; ) {",
+       "  if (confirm()) {",
+       "    break;",
+       "  } else {",
+       "    for (int i = 0; i < 2; i++) {",
+       "      n = i;",
+       "    }",
+       "  }",
+       "}",
+      "return n;"
+      );
+  }
+
   public void testImplicitConversion() throws Exception {
     optimize("long", 
         "int bar = 0x12345678;",
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java
index 8e2d5c8..6448f7c 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java
@@ -51,7 +51,7 @@
     addSnippetClassDecl("static class Foo { int i; int j; int k; }");
     addSnippetClassDecl("static Foo createFoo() {return null;}");
   }
-  
+
   public void testConstantAssignment() throws Exception {
     assertCfg("void", "i = 1;").is(
         "BLOCK -> [*]",
@@ -260,6 +260,52 @@
         "END");
   }
   
+  public void testDoStatementBreakNoLabel() throws Exception {
+    assertCfg("void", "do { if (b1) { break; } else { do { j = 2; } while (b2); } } while (i == 1);").is(
+        "BLOCK -> [*]",
+        "STMT -> [*]",
+        "1: BLOCK -> [*]",
+        "STMT -> [*]",
+        "READ(b1) -> [*]",
+        "COND (EntryPoint.b1) -> [THEN=*, ELSE=2]",
+        "BLOCK -> [*]",
+        "STMT -> [*]",
+        "GOTO -> [4]",
+        "2: BLOCK -> [*]",
+        "STMT -> [*]",
+        "3: BLOCK -> [*]",
+        "STMT -> [*]",
+        "WRITE(j, 2) -> [*]",
+        "READ(b2) -> [*]",
+        "COND (EntryPoint.b2) -> [THEN=3, ELSE=*]",
+        "READ(i) -> [*]",
+        "COND (EntryPoint.i == 1) -> [THEN=1, ELSE=*]",
+        "4: END");
+  }
+  
+  public void testDoStatementContinueNoLabel() throws Exception {
+    assertCfg("void", "do { if (b1) { continue; } else { do { j = 2; } while (b2); } } while (i == 1);").is(
+        "BLOCK -> [*]",
+        "STMT -> [*]",
+        "1: BLOCK -> [*]",
+        "STMT -> [*]",
+        "READ(b1) -> [*]",
+        "COND (EntryPoint.b1) -> [THEN=*, ELSE=2]",
+        "BLOCK -> [*]",
+        "STMT -> [*]",
+        "GOTO -> [1]",
+        "2: BLOCK -> [*]",
+        "STMT -> [*]",
+        "3: BLOCK -> [*]",
+        "STMT -> [*]",
+        "WRITE(j, 2) -> [*]",
+        "READ(b2) -> [*]",
+        "COND (EntryPoint.b2) -> [THEN=3, ELSE=*]",
+        "READ(i) -> [*]",
+        "COND (EntryPoint.i == 1) -> [THEN=1, ELSE=*]",
+        "END");
+  }
+
   public void testReturn1() throws Exception {
     assertCfg("void", "return;").is(
         "BLOCK -> [*]",
@@ -479,6 +525,29 @@
         "3: END");
   }
 
+  public void testWhileBreakNoLabel2() throws Exception {
+    assertCfg("void", "while (b1) { if (b2) { break; } else { while (i < 10) { i++; } } }").is(
+        "BLOCK -> [*]",
+        "STMT -> [*]",
+        "1: READ(b1) -> [*]",
+        "COND (EntryPoint.b1) -> [THEN=*, ELSE=4]",
+        "BLOCK -> [*]",
+        "STMT -> [*]",
+        "READ(b2) -> [*]",
+        "COND (EntryPoint.b2) -> [THEN=*, ELSE=2]",
+        "BLOCK -> [*]",
+        "STMT -> [*]",
+        "GOTO -> [4]",
+        "2: BLOCK -> [*]",
+        "STMT -> [*]",
+        "3: READ(i) -> [*]",
+        "COND (EntryPoint.i < 10) -> [THEN=*, ELSE=1]",
+        "BLOCK -> [*]",
+        "STMT -> [*]",
+        "READWRITE(i, null) -> [3]",
+        "4: END");
+  }
+
   public void testWhileBreakWithLabel1() throws Exception {
     assertCfg("void", 
         "nextLoop: while(b3)", 
@@ -550,6 +619,29 @@
             "3: END");
   }
   
+  public void testForBreakNoLabel() throws Exception {
+    assertCfg("void", 
+        "for(int i = 0; i < 10; i++) { if (b2) { break; } i++; }").is(
+            "BLOCK -> [*]",
+            "STMT -> [*]",
+            "STMT -> [*]",
+            "WRITE(i, 0) -> [*]",
+            "1: READ(i) -> [*]",
+            "COND (i < 10) -> [THEN=*, ELSE=3]",
+            "BLOCK -> [*]",
+            "STMT -> [*]",
+            "READ(b2) -> [*]",
+            "COND (EntryPoint.b2) -> [THEN=*, ELSE=2]",
+            "BLOCK -> [*]",
+            "STMT -> [*]",
+            "GOTO -> [3]",
+            "2: STMT -> [*]",
+            "READWRITE(i, null) -> [*]",
+            "STMT -> [*]",
+            "READWRITE(i, null) -> [1]",
+            "3: END");
+  }
+  
   public void testForContinueNoLabel() throws Exception {
     assertCfg("void", 
         "for(int i = 0; i < 10; i++) { if (b2) { continue; } i++; }").is(
@@ -572,7 +664,71 @@
             "READWRITE(i, null) -> [1]",
             "4: END");
   }
-  
+
+  public void testForBreakNestedForWithLabel() throws Exception {
+    assertCfg("int",
+        "int j = 0; a: for(; ; ) { if (b2) { break a; } else { for (int i = 0; i < 1; i++) { j = i; } } } return j;").is(
+            "BLOCK -> [*]",
+            "STMT -> [*]",
+            "WRITE(j, 0) -> [*]",
+            "STMT -> [*]",
+            "1: BLOCK -> [*]",
+            "STMT -> [*]",
+            "READ(b2) -> [*]",
+            "COND (EntryPoint.b2) -> [THEN=*, ELSE=2]",
+            "BLOCK -> [*]",
+            "STMT -> [*]",
+            "GOTO -> [4]",
+            "2: BLOCK -> [*]",
+            "STMT -> [*]",
+            "STMT -> [*]",
+            "WRITE(i, 0) -> [*]",
+            "3: READ(i) -> [*]",
+            "COND (i < 1) -> [THEN=*, ELSE=1]",
+            "BLOCK -> [*]",
+            "STMT -> [*]",
+            "READ(i) -> [*]",
+            "WRITE(j, i) -> [*]",
+            "STMT -> [*]",
+            "READWRITE(i, null) -> [3]",
+            "4: STMT -> [*]",
+            "READ(j) -> [*]",
+            "GOTO -> [*]",
+            "END");
+  }
+
+  public void testForBreakNestedForNoLabel() throws Exception {
+    assertCfg("int",
+        "int j = 0; for(; ; ) { if (b2) { break; } else { for (int i = 0; i < 1; i++) { j = i; } } } return j;").is(
+            "BLOCK -> [*]",
+            "STMT -> [*]",
+            "WRITE(j, 0) -> [*]",
+            "STMT -> [*]",
+            "1: BLOCK -> [*]",
+            "STMT -> [*]",
+            "READ(b2) -> [*]",
+            "COND (EntryPoint.b2) -> [THEN=*, ELSE=2]",
+            "BLOCK -> [*]",
+            "STMT -> [*]",
+            "GOTO -> [4]",
+            "2: BLOCK -> [*]",
+            "STMT -> [*]",
+            "STMT -> [*]",
+            "WRITE(i, 0) -> [*]",
+            "3: READ(i) -> [*]",
+            "COND (i < 1) -> [THEN=*, ELSE=1]",
+            "BLOCK -> [*]",
+            "STMT -> [*]",
+            "READ(i) -> [*]",
+            "WRITE(j, i) -> [*]",
+            "STMT -> [*]",
+            "READWRITE(i, null) -> [3]",
+            "4: STMT -> [*]",
+            "READ(j) -> [*]",
+            "GOTO -> [*]",
+            "END");
+  }
+
   public void testCatchThrowException1() throws Exception {
     assertCfg("void", 
         "try {",
@@ -1484,7 +1640,7 @@
         "READWRITE(k, null) -> [*]",
         "8: END");
   }
-  
+
   private CfgBuilderResult assertCfg(String returnType, String ...codeSnippet)
       throws UnableToCompleteException {
     JProgram program = compileSnippet(returnType,