Add tests for virtual dispatch argument propagation
Change-Id: Ib74eda42dd1318d4ba3b86ebe2827e628a89d90f
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationToClassesOutsideLowerBoundTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationToClassesOutsideLowerBoundTest.java
new file mode 100644
index 0000000..1e2140c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagationToClassesOutsideLowerBoundTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2021, 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.optimize.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArgumentPropagationToClassesOutsideLowerBoundTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject aMethodSubject = inspector.clazz(A.class).uniqueMethodWithName("m");
+ assertThat(aMethodSubject, isPresent());
+ assertTrue(
+ aMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("A: Not null")));
+ assertTrue(
+ aMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("A: Null")));
+
+ // TODO(b/190154391): B.m() is always called with non-null.
+ MethodSubject bMethodSubject = inspector.clazz(B.class).uniqueMethodWithName("m");
+ assertThat(bMethodSubject, isPresent());
+ assertTrue(
+ bMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("B: Not null")));
+ assertTrue(
+ bMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("B: Null")));
+
+ // TODO(b/190154391): C.m() is always called with null.
+ MethodSubject cMethodSubject = inspector.clazz(C.class).uniqueMethodWithName("m");
+ assertThat(cMethodSubject, isPresent());
+ assertTrue(
+ cMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("C: Not null")));
+ assertTrue(
+ cMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("C: Null")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A: Not null", "A: Null");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A aOrB = System.currentTimeMillis() > 0 ? new A() : new B();
+ aOrB.m(new Object());
+
+ A aOrC = System.currentTimeMillis() > 0 ? new A() : new C();
+ aOrC.m(null);
+ }
+ }
+
+ static class A {
+
+ void m(Object o) {
+ if (o != null) {
+ System.out.println("A: Not null");
+ } else {
+ System.out.println("A: Null");
+ }
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class B extends A {
+
+ void m(Object o) {
+ if (o != null) {
+ System.out.println("B: Not null");
+ } else {
+ System.out.println("B: Null");
+ }
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class C extends A {
+
+ void m(Object o) {
+ if (o != null) {
+ System.out.println("C: Not null");
+ } else {
+ System.out.println("C: Null");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToUnrelatedMethodTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToUnrelatedMethodTest.java
new file mode 100644
index 0000000..03d63d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/UpwardsInterfacePropagationToUnrelatedMethodTest.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2021, 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.optimize.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class UpwardsInterfacePropagationToUnrelatedMethodTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection parameters() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options ->
+ options
+ .callSiteOptimizationOptions()
+ .setEnableExperimentalArgumentPropagation(true))
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ .addVerticallyMergedClassesInspector(
+ VerticallyMergedClassesInspector::assertNoClassesMerged)
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject aMethodSubject = inspector.clazz(A.class).uniqueMethodWithName("m");
+ assertThat(aMethodSubject, isPresent());
+ assertTrue(
+ aMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("A: Not null")));
+ assertTrue(
+ aMethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("A: Null")));
+
+ MethodSubject b2MethodSubject = inspector.clazz(B2.class).uniqueMethodWithName("m");
+ assertThat(b2MethodSubject, isPresent());
+ assertTrue(
+ b2MethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("B2: Not null")));
+ assertTrue(
+ b2MethodSubject
+ .streamInstructions()
+ .noneMatch(instruction -> instruction.isConstString("B2: Null")));
+
+ MethodSubject b3MethodSubject = inspector.clazz(B3.class).uniqueMethodWithName("m");
+ assertThat(b3MethodSubject, isPresent());
+ assertTrue(
+ b3MethodSubject
+ .streamInstructions()
+ .noneMatch(instruction -> instruction.isConstString("B3: Not null")));
+ assertTrue(
+ b3MethodSubject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isConstString("B3: Null")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A: Not null", "A: Null");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ // Call A.m() or B2.m() with a non-null object.
+ A a = System.currentTimeMillis() >= 0 ? new B() : new B2();
+ a.m(new Object());
+
+ // Call A.m() or B3.m() with a null object.
+ I i = System.currentTimeMillis() >= 0 ? new B() : new B3();
+ i.m(null);
+ }
+ }
+
+ interface I {
+
+ void m(Object o);
+ }
+
+ static class A {
+
+ public void m(Object o) {
+ if (o != null) {
+ System.out.println("A: Not null");
+ } else {
+ System.out.println("A: Null");
+ }
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class B extends A implements I {}
+
+ @NoHorizontalClassMerging
+ static class B2 extends A {
+
+ @Override
+ public void m(Object o) {
+ if (o != null) {
+ System.out.println("B2: Not null");
+ } else {
+ System.out.println("B2: Null");
+ }
+ }
+ }
+
+ static class B3 implements I {
+
+ @Override
+ public void m(Object o) {
+ if (o != null) {
+ System.out.println("B3: Not null");
+ } else {
+ System.out.println("B3: Null");
+ }
+ }
+ }
+}