Implement simple class merger.

BUG=

Change-Id: Ib22cf7a7d10797b0ba7ed6ef8fb5aa5fda9aaa60

diff --git a/src/test/examples/classmerging/ClassWithConflictingMethod.java b/src/test/examples/classmerging/ClassWithConflictingMethod.java
new file mode 100644
index 0000000..b4bf9d7
--- /dev/null
+++ b/src/test/examples/classmerging/ClassWithConflictingMethod.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class ClassWithConflictingMethod {
+
+  public static int conflict(ConflictingInterface item) {
+    return 123;
+  }
+}
diff --git a/src/test/examples/classmerging/ConflictingInterface.java b/src/test/examples/classmerging/ConflictingInterface.java
new file mode 100644
index 0000000..6202e3c
--- /dev/null
+++ b/src/test/examples/classmerging/ConflictingInterface.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public interface ConflictingInterface {
+
+  public String method();
+}
diff --git a/src/test/examples/classmerging/ConflictingInterfaceImpl.java b/src/test/examples/classmerging/ConflictingInterfaceImpl.java
new file mode 100644
index 0000000..e46edaf
--- /dev/null
+++ b/src/test/examples/classmerging/ConflictingInterfaceImpl.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class ConflictingInterfaceImpl implements ConflictingInterface {
+
+  @Override
+  public String method() {
+    return "ConflictingInterfaceImpl::method";
+  }
+}
diff --git a/src/test/examples/classmerging/GenericAbstractClass.java b/src/test/examples/classmerging/GenericAbstractClass.java
new file mode 100644
index 0000000..42620f6
--- /dev/null
+++ b/src/test/examples/classmerging/GenericAbstractClass.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public abstract class GenericAbstractClass<T> {
+
+  public abstract T method();
+
+  public T otherMethod() {
+    return null;
+  }
+}
diff --git a/src/test/examples/classmerging/GenericAbstractClassImpl.java b/src/test/examples/classmerging/GenericAbstractClassImpl.java
new file mode 100644
index 0000000..931465e
--- /dev/null
+++ b/src/test/examples/classmerging/GenericAbstractClassImpl.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class GenericAbstractClassImpl extends GenericAbstractClass<String> {
+
+  @Override
+  public String method() {
+    return "Hello from GenericAbstractClassImpl";
+  }
+
+  @Override
+  public String otherMethod() {
+    return "otherMethod";
+  }
+}
diff --git a/src/test/examples/classmerging/GenericInterface.java b/src/test/examples/classmerging/GenericInterface.java
new file mode 100644
index 0000000..cda0b32
--- /dev/null
+++ b/src/test/examples/classmerging/GenericInterface.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public interface GenericInterface<T> {
+
+  T method();
+}
diff --git a/src/test/examples/classmerging/GenericInterfaceImpl.java b/src/test/examples/classmerging/GenericInterfaceImpl.java
new file mode 100644
index 0000000..6a14107
--- /dev/null
+++ b/src/test/examples/classmerging/GenericInterfaceImpl.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class GenericInterfaceImpl implements GenericInterface<String> {
+
+  @Override
+  public String method() {
+    return "method";
+  }
+}
diff --git a/src/test/examples/classmerging/OtherClassWithConflictingMethod.java b/src/test/examples/classmerging/OtherClassWithConflictingMethod.java
new file mode 100644
index 0000000..cd7efd1
--- /dev/null
+++ b/src/test/examples/classmerging/OtherClassWithConflictingMethod.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class OtherClassWithConflictingMethod {
+
+  public static int conflict(ConflictingInterfaceImpl item) {
+    return 321;
+  }
+}
diff --git a/src/test/examples/classmerging/Outer.java b/src/test/examples/classmerging/Outer.java
new file mode 100644
index 0000000..652f794
--- /dev/null
+++ b/src/test/examples/classmerging/Outer.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2017, 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 classmerging;
+
+class Outer {
+
+  /**
+   * This class is package private to trigger the generation of bridge methods
+   * for the visibility change of methods from public subtypes.
+   */
+  class SuperClass {
+
+    public String method() {
+      return "Method in SuperClass.";
+    }
+  }
+
+  public class SubClass extends SuperClass {
+    // Intentionally left empty.
+  }
+
+  public SubClass getInstance() {
+    return new SubClass();
+  }
+}
diff --git a/src/test/examples/classmerging/SubClass.java b/src/test/examples/classmerging/SubClass.java
new file mode 100644
index 0000000..b6a9054
--- /dev/null
+++ b/src/test/examples/classmerging/SubClass.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class SubClass extends SuperClass {
+
+  private int field;
+
+  public SubClass(int field) {
+    this(field, field + 100);
+  }
+
+  public SubClass(int one, int other) {
+    super(one);
+    field = other;
+  }
+
+  public String toString() {
+    return "is " + field + " " + getField();
+  }
+}
diff --git a/src/test/examples/classmerging/SubClassThatReferencesSuperMethod.java b/src/test/examples/classmerging/SubClassThatReferencesSuperMethod.java
new file mode 100644
index 0000000..c12804c
--- /dev/null
+++ b/src/test/examples/classmerging/SubClassThatReferencesSuperMethod.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class SubClassThatReferencesSuperMethod extends SuperClassWithReferencedMethod {
+
+  @Override
+  public String referencedMethod() {
+    return "From sub: " + super.referencedMethod();
+  }
+}
diff --git a/src/test/examples/classmerging/SuperClass.java b/src/test/examples/classmerging/SuperClass.java
new file mode 100644
index 0000000..b7c9207
--- /dev/null
+++ b/src/test/examples/classmerging/SuperClass.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class SuperClass {
+
+  private final int field;
+
+  public SuperClass(int field) {
+    this.field = field;
+  }
+
+  public int getField() {
+    return field;
+  }
+}
diff --git a/src/test/examples/classmerging/SuperClassWithReferencedMethod.java b/src/test/examples/classmerging/SuperClassWithReferencedMethod.java
new file mode 100644
index 0000000..8d4e7b5
--- /dev/null
+++ b/src/test/examples/classmerging/SuperClassWithReferencedMethod.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class SuperClassWithReferencedMethod {
+
+  public String referencedMethod() {
+    return "From Super";
+  }
+}
diff --git a/src/test/examples/classmerging/Test.java b/src/test/examples/classmerging/Test.java
new file mode 100644
index 0000000..6d5c51f
--- /dev/null
+++ b/src/test/examples/classmerging/Test.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2017, 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 classmerging;
+
+public class Test {
+
+  public static void main(String... args) {
+    GenericInterface iface = new GenericInterfaceImpl();
+    callMethodOnIface(iface);
+    GenericAbstractClass clazz = new GenericAbstractClassImpl();
+    callMethodOnAbstractClass(clazz);
+    ConflictingInterfaceImpl impl = new ConflictingInterfaceImpl();
+    callMethodOnIface(impl);
+    System.out.println(new SubClassThatReferencesSuperMethod().referencedMethod());
+    System.out.println(new Outer().getInstance().method());
+    System.out.println(new SubClass(42));
+  }
+
+  private static void callMethodOnIface(GenericInterface iface) {
+    System.out.println(iface.method());
+  }
+
+  private static void callMethodOnAbstractClass(GenericAbstractClass clazz) {
+    System.out.println(clazz.method());
+    System.out.println(clazz.otherMethod());
+  }
+
+  private static void callMethodOnIface(ConflictingInterface iface) {
+    System.out.println(iface.method());
+    System.out.println(ClassWithConflictingMethod.conflict(null));
+    System.out.println(OtherClassWithConflictingMethod.conflict(null));
+  }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
new file mode 100644
index 0000000..58b262b
--- /dev/null
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -0,0 +1,12 @@
+# Copyright (c) 2016, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class classmerging.Test {
+  public static void main(...);
+}
+
+# allow access modification to enable minification
+-allowaccessmodification
diff --git a/src/test/examplesAndroidO/invokecustom/keep-rules.txt b/src/test/examplesAndroidO/invokecustom/keep-rules.txt
index 52fa2a7..bcefc4b 100644
--- a/src/test/examplesAndroidO/invokecustom/keep-rules.txt
+++ b/src/test/examplesAndroidO/invokecustom/keep-rules.txt
@@ -8,7 +8,7 @@
   public static void main(...);
 }
 
--keepclassmembers  class * {
+-keepclasseswithmembers  class * {
   *** targetMethodTest*(...);
 }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
new file mode 100644
index 0000000..2326eb5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -0,0 +1,72 @@
+package com.android.tools.r8.classmerging;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.DexInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class ClassMergingTest {
+
+  private static final Path EXAMPLE_JAR = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
+      .resolve("classmerging.jar");
+  private static final Path EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_DIR)
+      .resolve("classmerging").resolve("keep-rules.txt");
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Before
+  public void runR8()
+      throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+    // Disable access modification, as it otherwise is difficult to test visibility bridge methods.
+    ToolHelper.runR8(
+        R8Command.builder()
+            .setOutputPath(Paths.get(temp.getRoot().getCanonicalPath()))
+            .addProgramFiles(EXAMPLE_JAR)
+            .addProguardConfigurationFiles(EXAMPLE_KEEP)
+            .setMinification(false)
+            .build(), o -> o.allowAccessModification = false);
+    inspector = new DexInspector(
+        Paths.get(temp.getRoot().getCanonicalPath()).resolve("classes.dex"));
+  }
+
+  private DexInspector inspector;
+
+  @Test
+  public void testClassesHaveBeenMerged() throws IOException, ExecutionException {
+    // GenericInterface should be merged into GenericInterfaceImpl.
+    Assert.assertFalse(inspector.clazz("classmerging.GenericInterface").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
+    Assert.assertFalse(inspector.clazz("classmerging.GenericAbstractClass").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
+    Assert.assertFalse(inspector.clazz("classmerging.Outer$SuperClass").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.Outer$SubClass").isPresent());
+    Assert.assertFalse(inspector.clazz("classmerging.SuperClass").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.SubClass").isPresent());
+  }
+
+
+  @Test
+  public void testConflictWasDetected() throws IOException, ExecutionException {
+    Assert.assertTrue(inspector.clazz("classmerging.ConflictingInterface").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.ConflictingInterfaceImpl").isPresent());
+  }
+
+  @Test
+  public void testSuperCallWasDetected() throws IOException, ExecutionException {
+    Assert.assertTrue(inspector.clazz("classmerging.SuperClassWithReferencedMethod").isPresent());
+    Assert
+        .assertTrue(inspector.clazz("classmerging.SubClassThatReferencesSuperMethod").isPresent());
+  }
+
+}