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