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,