Test for "R8/CF does not merge identical catch handlers"

Also add ToolHelper.runR8WithConsumer(AndroidApp, ProgramConsumer) to
support compiling an AndroidApp with the CF backend.

Unfortunately the name runR8(AndroidApp, ProgramConsumer) clashes with
runR8(AndroidApp, Consumer) because both Consumer and ProgramConsumer
are functional interfaces, and runR8(AndroidApp, Consumer) is used in
many places with a lambda as second argument.

Bug: 80514966
Change-Id: I15e88849527285ea45248f8c0ab8fac1e30e84fb
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 68d87c1..0a4416a 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -793,12 +793,22 @@
   }
 
   public static R8Command.Builder prepareR8CommandBuilder(AndroidApp app) {
-    return R8Command.builder(app).setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    return prepareR8CommandBuilder(app, DexIndexedConsumer.emptyConsumer());
+  }
+
+  public static R8Command.Builder prepareR8CommandBuilder(
+      AndroidApp app, ProgramConsumer programConsumer) {
+    return R8Command.builder(app).setProgramConsumer(programConsumer);
   }
 
   public static AndroidApp runR8(AndroidApp app) throws IOException {
+    return runR8WithProgramConsumer(app, DexIndexedConsumer.emptyConsumer());
+  }
+
+  public static AndroidApp runR8WithProgramConsumer(AndroidApp app, ProgramConsumer programConsumer)
+      throws IOException {
     try {
-      return runR8(prepareR8CommandBuilder(app).build());
+      return runR8(prepareR8CommandBuilder(app, programConsumer).build());
     } catch (CompilationFailedException e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
new file mode 100644
index 0000000..fd696bc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -0,0 +1,88 @@
+// 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.assertNotEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import java.nio.file.Paths;
+import java.util.Set;
+import org.junit.Test;
+
+public class IdenticalCatchHandlerTest extends TestBase {
+
+  private static class TestClass {
+    public void foo(Object a, Object b, Object c) {
+      if (a == null) {
+        try {
+          System.out.println(b.toString());
+        } catch (RuntimeException e) {
+        }
+      } else {
+        try {
+          System.out.println(c.toString());
+        } catch (RuntimeException e) {
+        }
+      }
+      System.out.println("Hello");
+    }
+  }
+
+  @Test
+  public void test() throws Exception {
+    byte[] inputBytes = ToolHelper.getClassAsBytes(TestClass.class);
+    AndroidApp inputApp =
+        AndroidApp.builder()
+            .addClassProgramData(inputBytes, Origin.unknown())
+            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .build();
+    assertEquals(2, countCatchHandlers(inputApp));
+    AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
+    assertEquals(1, countCatchHandlers(outputDexApp));
+    AndroidApp outputCfApp =
+        ToolHelper.runR8WithProgramConsumer(inputApp, ClassFileConsumer.emptyConsumer());
+    // TODO(b/80514966): Change to assertEquals when fixed.
+    assertNotEquals(1, countCatchHandlers(outputCfApp));
+  }
+
+  private int countCatchHandlers(AndroidApp inputApp) throws Exception {
+    DexInspector inspector = new DexInspector(inputApp, o -> o.enableCfFrontend = true);
+    DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
+    Code code = dexClass.virtualMethods()[0].getCode();
+    if (code.isCfCode()) {
+      CfCode cfCode = code.asCfCode();
+      Set<CfLabel> targets = Sets.newIdentityHashSet();
+      for (CfTryCatch tryCatch : cfCode.getTryCatchRanges()) {
+        targets.addAll(tryCatch.targets);
+      }
+      return targets.size();
+    } else if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      IntSet targets = new IntOpenHashSet();
+      for (Try aTry : dexCode.tries) {
+        targets.add(aTry.handlerOffset);
+      }
+      return targets.size();
+    } else {
+      throw new Unimplemented(code.getClass().getName());
+    }
+  }
+}