Merge "CfBuilder: Fix missing label and stack frame for entry block"
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 5664f8d..83995bd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -201,6 +201,7 @@
     CfLabel tryCatchStart = null;
     CatchHandlers<BasicBlock> tryCatchHandlers = CatchHandlers.EMPTY_BASIC_BLOCK;
     BasicBlock pendingFrame = null;
+    boolean previousFallthrough = false;
     do {
       CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
       if (!tryCatchHandlers.equals(handlers)) {
@@ -218,6 +219,24 @@
         tryCatchHandlers = handlers;
       }
       BasicBlock nextBlock = blockIterator.hasNext() ? blockIterator.next() : null;
+      // If previousBlock is fallthrough, then it is counted in getPredecessors().size(), but
+      // we only want to set a pendingFrame if we have a predecessor which is not previousBlock.
+      if (block.getPredecessors().size() > (previousFallthrough ? 1 : 0)) {
+        assert stack.isEmpty();
+        pendingFrame = block;
+        emitLabel(getLabel(block));
+      }
+      if (pendingFrame != null) {
+        boolean advancesPC = hasMaterializingInstructions(block, nextBlock);
+        // If block has no materializing instructions, then we postpone emitting the frame
+        // until the next block. In this case, nextBlock must be non-null
+        // (or we would fall off the edge of the method).
+        assert advancesPC || nextBlock != null;
+        if (advancesPC) {
+          addFrame(pendingFrame, Collections.emptyList());
+          pendingFrame = null;
+        }
+      }
       JumpInstruction exit = block.exit();
       boolean fallthrough =
           (exit.isGoto() && exit.asGoto().getTarget() == nextBlock)
@@ -230,29 +249,8 @@
         pendingLocalChanges = true;
       }
       buildCfInstructions(block, fallthrough, stack);
-      if (nextBlock != null) {
-        if (!fallthrough || nextBlock.getPredecessors().size() > 1) {
-          assert stack.isEmpty();
-          pendingFrame = nextBlock;
-          emitLabel(getLabel(nextBlock));
-        }
-        if (pendingFrame != null) {
-          BasicBlock nextNextBlock = null;
-          if (blockIterator.hasNext()) {
-            nextNextBlock = blockIterator.next();
-            blockIterator.previous();
-          }
-          boolean advancesPC = hasMaterializingInstructions(nextBlock, nextNextBlock);
-          // If nextBlock has no materializing instructions, then nextNextBlock must be non-null
-          // (or we would fall off the edge of the method).
-          assert advancesPC || nextNextBlock != null;
-          if (advancesPC) {
-            addFrame(pendingFrame, Collections.emptyList());
-            pendingFrame = null;
-          }
-        }
-      }
       block = nextBlock;
+      previousFallthrough = fallthrough;
     } while (block != null);
     assert stack.isEmpty();
     CfLabel endLabel = ensureLabel();
diff --git a/src/test/java/com/android/tools/r8/cf/CallLoopTest.java b/src/test/java/com/android/tools/r8/cf/CallLoopTest.java
new file mode 100644
index 0000000..6ccbdd1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CallLoopTest.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf;
+
+public class CallLoopTest {
+  private static boolean doThrow = false;
+  private static boolean doLoop = true;
+
+  public static void main(String[] args) {
+    if (args.length % 2 == 0) doThrow = true;
+    if (args.length % 3 == 0) doLoop = false;
+    loop1();
+    try {
+      loop2();
+    } catch (RuntimeException e) {
+      // OK
+    }
+  }
+
+  private static void loop1() {
+    while (doLoop) {}
+  }
+
+  private static void loop2() {
+    while (true) {
+      maybeThrow();
+    }
+  }
+
+  private static void maybeThrow() {
+    if (doThrow) {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java
new file mode 100644
index 0000000..b4daa84
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf;
+
+import com.android.tools.r8.ClassFileConsumer.DirectoryConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class CallLoopTestRunner {
+  static final Class CLASS = CallLoopTest.class;
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void test() throws Exception {
+    Path out = temp.getRoot().toPath();
+    R8.run(
+        R8Command.builder()
+            .setMode(CompilationMode.DEBUG)
+            .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(new DirectoryConsumer(out))
+            .build());
+    assert ToolHelper.runJava(out, CLASS.getCanonicalName()).exitCode == 0;
+  }
+}