Merge "CF backend: Don't read stack frames in with ASM"
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index c0c2e27..9c3e9a5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -71,6 +71,14 @@
     return false;
   }
 
+  /**
+   * Returns true if the other method has the same name and prototype (including signature and
+   * return type), false otherwise.
+   */
+  public boolean hasSameProtoAndName(DexMethod other) {
+    return name == other.name && proto == other.proto;
+  }
+
   @Override
   public int compareTo(DexMethod other) {
     return sortedCompareTo(other.getSortedIndex());
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index 3379864..6c9f075 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -129,16 +129,24 @@
     BasicBlock phiBlock = phi.getBlock();
     int phiBlockNumber = phiBlock.getNumber();
     LatticeElement element = Top.getInstance();
-    for (int i = 0; i < phiBlock.getPredecessors().size(); i++) {
-      BasicBlock predecessor = phiBlock.getPredecessors().get(i);
+    List<BasicBlock> predecessors = phiBlock.getPredecessors();
+    int size = predecessors.size();
+    for (int i = 0; i < size; i++) {
+      BasicBlock predecessor = predecessors.get(i);
       if (isExecutableEdge(predecessor.getNumber(), phiBlockNumber)) {
         element = element.meet(getLatticeElement(phi.getOperand(i)));
+        // bottom lattice can no longer be changed, thus no need to continue
+        if (element.isBottom()) {
+          break;
+        }
       }
     }
-    LatticeElement currentPhiElement = getLatticeElement(phi);
-    if (!element.isTop() && currentPhiElement.meet(element) != currentPhiElement) {
-      ssaEdges.add(phi);
-      setLatticeElement(phi, element);
+    if (!element.isTop()) {
+      LatticeElement currentPhiElement = getLatticeElement(phi);
+      if (currentPhiElement.meet(element) != currentPhiElement) {
+        ssaEdges.add(phi);
+        setLatticeElement(phi, element);
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 228f594..9c6bb86 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -172,7 +172,9 @@
   }
 
   @Override
-  public void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException {
+  public void buildInstruction(
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
+      throws ApiLevelException {
     updateCurrentCatchHandlers(instructionIndex);
     updateDebugPosition(instructionIndex, builder);
     currentDexInstruction = code.instructions[instructionIndex];
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 5bea64e..13d66c2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -504,7 +504,7 @@
           addToWorklist(info.block, i);
           break;
         }
-        source.buildInstruction(this, i);
+        source.buildInstruction(this, i, i == item.firstInstructionIndex);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 4dd5656..b5dfdbb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -488,7 +488,9 @@
   }
 
   @Override
-  public void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException {
+  public void buildInstruction(
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
+      throws ApiLevelException {
     if (instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
       buildExceptionalPostlude(builder);
       return;
@@ -498,8 +500,17 @@
     assert verifyExceptionEdgesAreRecorded(insn);
 
     // If a new block is starting here, we restore the computed JarState.
-    if (builder.getCFG().containsKey(instructionIndex) || instructionIndex == 0) {
+    // current position needs to be compute only for the first instruction of a block, thereafter
+    // current position will be updated by LineNumberNode into this block.
+    if (firstBlockInstruction || instructionIndex == 0) {
       state.restoreState(instructionIndex);
+      // Don't include line changes when processing a label. Doing so will end up emitting local
+      // writes after the line has changed and thus causing locals to become visible too late.
+      currentPosition =
+          getDebugPositionAtOffset(
+              ((instructionIndex > 0) && (insn instanceof LabelNode))
+                  ? instructionIndex - 1
+                  : instructionIndex);
     }
 
     String preInstructionState;
@@ -507,14 +518,6 @@
       preInstructionState = state.toString();
     }
 
-    // Don't include line changes when processing a label. Doing so will end up emitting local
-    // writes after the line has changed and thus causing locals to become visible too late.
-    currentPosition =
-        getDebugPositionAtOffset(
-            ((instructionIndex > 0) && (insn instanceof LabelNode))
-                ? instructionIndex - 1
-                : instructionIndex);
-
     build(insn, builder);
 
     if (Log.ENABLED && !(insn instanceof LineNumberNode)) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index 13d1551..be1c670 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -47,7 +47,9 @@
 
   // Delegates for IR building.
   void buildPrelude(IRBuilder builder);
-  void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException;
+
+  void buildInstruction(IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
+      throws ApiLevelException;
   void buildPostlude(IRBuilder builder);
 
   // Helper to resolve switch payloads and build switch instructions (dex code only).
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index dba6183..126b127 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -859,7 +859,8 @@
     }
 
     @Override
-    public void buildInstruction(IRBuilder builder, int instructionIndex) {
+    public void buildInstruction(
+        IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
       if (instructionIndex == outline.templateInstructions.size()) {
         if (outline.returnType == dexItemFactory.voidType) {
           builder.addReturn();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 5d03dfd..5a6cc2d 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -188,7 +188,8 @@
   }
 
   @Override
-  public final void buildInstruction(IRBuilder builder, int instructionIndex)
+  public final void buildInstruction(
+      IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
       throws ApiLevelException {
     constructors.get(instructionIndex).accept(builder);
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index c815cf8..5847a80 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -33,8 +33,9 @@
       method.getCode().registerCodeReferences(targetExtractor);
       DexMethod target = targetExtractor.getTarget();
       InvokeKind kind = targetExtractor.getKind();
-      if (target != null &&
-          target.proto == method.method.proto) {
+      // javac-generated visibility forward bridge method has same descriptor (name, signature and
+      // return type).
+      if (target != null && target.hasSameProtoAndName(method.method)) {
         assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
         if (kind == InvokeKind.SUPER) {
           // This is a visibility forward, so check for the direct target.
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index dc0cd0f..42775d2 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -100,6 +101,8 @@
   public boolean verbose = false;
   // Silencing output.
   public boolean quiet = false;
+  // Throw exception if there is a warning about invalid debug info.
+  public boolean invalidDebugInfoFatal = false;
 
   // Hidden marker for classes.dex
   private boolean hasMarker = false;
@@ -268,6 +271,9 @@
 
   public void warningInvalidDebugInfo(
       DexEncodedMethod method, Origin origin, InvalidDebugInfoException e) {
+    if (invalidDebugInfoFatal) {
+      throw new CompilationError("Fatal warning: Invalid debug info", e);
+    }
     synchronized (warningInvalidDebugInfo) {
       warningInvalidDebugInfo.computeIfAbsent(
           origin, k -> new ArrayList<>()).add(new Pair<>(method, e.getMessage()));
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d7d8b99..a29d673 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -398,6 +398,13 @@
         + (obfuscate ? "-printmapping\n" : "-dontobfuscate\n");
   }
 
+  public static String keepMainProguardConfiguration(
+      String clazz, boolean allowaccessmodification, boolean obfuscate) {
+    return keepMainProguardConfiguration(clazz)
+        + (allowaccessmodification ? "-allowaccessmodification\n" : "")
+        + (obfuscate ? "-printmapping\n" : "-dontobfuscate\n");
+  }
+
   /**
    * Run application on the specified version of Art with the specified main class.
    */
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 6e3c114..35a7613 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -4,14 +4,26 @@
 
 package com.android.tools.r8.bridgeremoval;
 
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.bridgeremoval.bridgestoremove.Main;
 import com.android.tools.r8.bridgeremoval.bridgestoremove.Outer;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
 
@@ -40,4 +52,81 @@
   public void testWithoutObfuscation() throws Exception {
     run(false);
   }
+
+  @Test
+  public void regressionTest_b76383728_WithObfuscation() throws Exception {
+    runRegressionTest_b76383728(true);
+  }
+
+  @Test
+  public void regressionTest_b76383728_WithoutObfuscation() throws Exception {
+    runRegressionTest_b76383728(false);
+  }
+
+  /**
+   * Regression test for b76383728 to make sure we correctly identify and remove real visibility
+   * forward bridge methods synthesized by javac.
+   */
+  private void runRegressionTest_b76383728(boolean obfuscate) throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+
+    ClassBuilder superClass = jasminBuilder.addClass("SuperClass");
+    superClass.addDefaultConstructor();
+    superClass.addVirtualMethod("method", Collections.emptyList(), "Ljava/lang/String;",
+        ".limit stack 1",
+        "ldc \"Hello World\"",
+        "areturn");
+
+    // Generate a subclass with a method with same
+    ClassBuilder subclass = jasminBuilder.addClass("SubClass", superClass.name);
+    subclass.addBridgeMethod("getMethod", Collections.emptyList(), "Ljava/lang/String;",
+        ".limit stack 1",
+        "aload_0",
+        "invokespecial " + superClass.name + "/method()Ljava/lang/String;",
+        "areturn");
+
+    ClassBuilder mainClass = jasminBuilder.addClass("Main");
+    mainClass.addMainMethod(
+        ".limit stack 3",
+        ".limit locals 2",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "new " + subclass.name,
+        "dup",
+        "invokespecial " + subclass.name + "/<init>()V",
+        "invokevirtual " + subclass.name + "/getMethod()Ljava/lang/String;",
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "return"
+    );
+
+    final String mainClassName = mainClass.name;
+
+    String proguardConfig = keepMainProguardConfiguration(mainClass.name, true, obfuscate);
+
+    // Run input program on java.
+    Path outputDirectory = temp.newFolder().toPath();
+    jasminBuilder.writeClassFiles(outputDirectory);
+    ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
+    assertEquals(0, javaResult.exitCode);
+
+    AndroidApp optimizedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
+        // Disable inlining to avoid the (short) tested method from being inlined then removed.
+        internalOptions -> internalOptions.enableInlining = false);
+
+    // Run optimized (output) program on ART
+    String artResult = runOnArt(optimizedApp, mainClassName);
+    assertEquals(javaResult.stdout, artResult);
+
+    DexInspector inspector = new DexInspector(optimizedApp);
+
+    ClassSubject classSubject = inspector.clazz(superClass.name);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject
+        .method("java.lang.String", "method", Collections.emptyList());
+    assertThat(methodSubject, isPresent());
+
+    classSubject = inspector.clazz(subclass.name);
+    assertThat(classSubject, isPresent());
+    methodSubject = classSubject.method("java.lang.String", "getMethod", Collections.emptyList());
+    assertThat(methodSubject, isPresent());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTest.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTest.java
new file mode 100644
index 0000000..6e009e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTest.java
@@ -0,0 +1,27 @@
+// 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 DebugInfoTest {
+
+  public static void main(String[] args) {
+    if (args.length > 0) {
+      arg = args.length % 2 == 0;
+      DebugInfoTest.method();
+    }
+  }
+
+  private static boolean arg;
+
+  private static void method() {
+    int intVar;
+    if (arg) {
+      float floatVar1 = 0f;
+      intVar = (int) floatVar1;
+    } else {
+      float floatVar2 = 0f;
+      intVar = (int) floatVar2;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
new file mode 100644
index 0000000..fed7637
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
@@ -0,0 +1,68 @@
+// 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import java.nio.file.Path;
+import org.junit.Test;
+
+public class DebugInfoTestRunner extends TestBase {
+  static final Class CLASS = DebugInfoTest.class;
+
+  @Test
+  public void test() throws Exception {
+    ProcessResult runInput =
+        ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+    assertEquals(0, runInput.exitCode);
+    Path out1 = temp.getRoot().toPath().resolve("out1.zip");
+    build(
+        builder -> builder.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown()),
+        new ClassFileConsumer.ArchiveConsumer(out1));
+    ProcessResult run1 = ToolHelper.runJava(out1, CLASS.getCanonicalName());
+    assertEquals(runInput.toString(), run1.toString());
+    Path out2 = temp.getRoot().toPath().resolve("out2.zip");
+    boolean invalidDebugInfo = false;
+    try {
+      build(builder -> builder.addProgramFiles(out1), new ClassFileConsumer.ArchiveConsumer(out2));
+    } catch (CompilationError e) {
+      invalidDebugInfo = e.getCause() instanceof InvalidDebugInfoException;
+    }
+    // TODO(b/77522100): Change to assertFalse when fixed.
+    assertTrue(invalidDebugInfo);
+    if (!invalidDebugInfo) {
+      ProcessResult run2 = ToolHelper.runJava(out2, CLASS.getCanonicalName());
+      assertEquals(runInput.toString(), run2.toString());
+    }
+  }
+
+  private void build(ThrowingConsumer<Builder, Exception> input, ProgramConsumer consumer)
+      throws Exception {
+    Builder builder =
+        R8Command.builder()
+            .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(consumer);
+    input.accept(builder);
+    ToolHelper.runR8(
+        builder.build(),
+        o -> {
+          o.invalidDebugInfoFatal = true;
+          o.enableInlining = false;
+        });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 64d092b..ff3f81a 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -120,6 +120,15 @@
       return addMethod("public", name, argumentTypes, returnType, lines);
     }
 
+    public MethodSignature addBridgeMethod(
+        String name,
+        List<String> argumentTypes,
+        String returnType,
+        String... lines) {
+      makeInit = true;
+      return addMethod("public bridge", name, argumentTypes, returnType, lines);
+    }
+
     public MethodSignature addPrivateVirtualMethod(
         String name,
         List<String> argumentTypes,
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 35df33e..a6d0177 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -707,7 +707,8 @@
     }
 
     @Override
-    public void buildInstruction(IRBuilder builder, int instructionIndex) {
+    public void buildInstruction(
+        IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
       assert instructionIndex == 0;
       builder.addReturn();
     }