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()); + } + +}