Add basic tests about propagating constants from call sites.

Bug: 69963623, 139246447
Change-Id: Id8575e0df2c10841231ce03afeacbd8bb7e4e939
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
new file mode 100644
index 0000000..0c464f2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2019, 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.ir.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeDirectNegativeTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeDirectNegativeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeDirectNegativeTest.class)
+        .addKeepMainRule(MAIN)
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "non-null")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject main = inspector.clazz(MAIN);
+    assertThat(main, isPresent());
+    MethodSubject test = main.uniqueMethodWithName("test");
+    assertThat(test, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  @NeverClassInline
+  static class Main {
+    public static void main(String... args) {
+      Main obj = new Main();
+      obj.test("null"); // calls test with "null".
+      obj.test("nul");  // calls test with "nul".
+    }
+
+    @NeverInline
+    private void test(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
new file mode 100644
index 0000000..c0ec39b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2019, 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.ir.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeDirectPositiveTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeDirectPositiveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeDirectPositiveTest.class)
+        .addKeepMainRule(MAIN)
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("non-null")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject main = inspector.clazz(MAIN);
+    assertThat(main, isPresent());
+    MethodSubject test = main.uniqueMethodWithName("test");
+    assertThat(test, isPresent());
+    // TODO(b/69963623):
+    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  @NeverClassInline
+  static class Main {
+    public static void main(String... args) {
+      Main obj = new Main();
+      obj.test("nul"); // calls test with "nul".
+    }
+
+    @NeverInline
+    private void test(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
new file mode 100644
index 0000000..2410a3a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2019, 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.ir.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfaceNegativeTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeInterfaceNegativeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeInterfaceNegativeTest.class)
+        .addKeepMainRule(MAIN)
+        .enableMergeAnnotations()
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
+          o.enableDevirtualization = false;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "non-null")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject i = inspector.clazz(I.class);
+    assertThat(i, isPresent());
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+    MethodSubject a_m = a.uniqueMethodWithName("m");
+    assertThat(a_m, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  @NeverMerge
+  interface I {
+    void m(String arg);
+  }
+
+  @NeverClassInline
+  static class A implements I {
+    @NeverInline
+    @Override
+    public void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      I i = new A();
+      i.m("null");  // calls A.m() with "null".
+      i.m("nul");   // calls A.m() with "nul".
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
new file mode 100644
index 0000000..5372bf3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2019, 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.ir.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfacePositiveTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeInterfacePositiveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeInterfacePositiveTest.class)
+        .addKeepMainRule(MAIN)
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addOptionsModification(o -> {
+          // To prevent invoke-interface from being rewritten to invoke-virtual w/ a single target.
+          o.enableDevirtualization = false;
+        })
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("non-null")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject i = inspector.clazz(I.class);
+    assertThat(i, isPresent());
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+    MethodSubject a_m = a.uniqueMethodWithName("m");
+    assertThat(a_m, isPresent());
+    // TODO(b/69963623):
+    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+    ClassSubject b = inspector.clazz(B.class);
+    assertThat(b, isPresent());
+    MethodSubject b_m = b.uniqueMethodWithName("m");
+    assertThat(b_m, isPresent());
+    // TODO(b/69963623):
+    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  interface I {
+    void m(String arg);
+  }
+
+  @NeverClassInline
+  static class A implements I {
+    @NeverInline
+    @Override
+    public void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  @NeverClassInline
+  static class B implements I {
+    @NeverInline
+    @Override
+    public void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      I i = System.currentTimeMillis() > 0 ? new A() : new B();
+      i.m("nul");  // calls A.m() with "nul".
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
new file mode 100644
index 0000000..491c26e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2019, 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.ir.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticNegativeTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeStaticNegativeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeStaticNegativeTest.class)
+        .addKeepMainRule(MAIN)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "non-null")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject main = inspector.clazz(MAIN);
+    assertThat(main, isPresent());
+    MethodSubject test = main.uniqueMethodWithName("test");
+    assertThat(test, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      test("null"); // calls test with "null".
+      test("nul");  // calls test with "nul".
+    }
+
+    @NeverInline
+    static void test(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
new file mode 100644
index 0000000..f12d80d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2019, 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.ir.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeStaticPositiveTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeStaticPositiveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeStaticPositiveTest.class)
+        .addKeepMainRule(MAIN)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("non-null")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject main = inspector.clazz(MAIN);
+    assertThat(main, isPresent());
+    MethodSubject test = main.uniqueMethodWithName("test");
+    assertThat(test, isPresent());
+    // TODO(b/69963623):
+    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(test.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      test("nul"); // calls test with "nul".
+    }
+
+    @NeverInline
+    static void test(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
new file mode 100644
index 0000000..c908411
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2019, 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.ir.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualNegativeTest extends TestBase {
+
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeVirtualNegativeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeVirtualNegativeTest.class)
+        .addKeepMainRule(MAIN)
+        .enableMergeAnnotations()
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("null", "non-null", "null", "non-null")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+
+    MethodSubject a_m = a.uniqueMethodWithName("m");
+    assertThat(a_m, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+
+    ClassSubject b = inspector.clazz(B.class);
+    assertThat(b, isPresent());
+
+    MethodSubject b_m = b.uniqueMethodWithName("m");
+    assertThat(b_m, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  static class A {
+    @NeverInline
+    void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+    @NeverInline
+    @Override
+    void m(String arg) {
+      // Same as A#m.
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+
+    @NeverInline
+    @Override
+    public String toString() {
+      return "B";
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      A a = new A();
+      test(a);    // calls A.m() with "null".
+      a.m("nul"); // calls A.m() with "nul".
+      B b = new B();
+      test(b);    // calls B.m() with "null".
+      b.m("nul"); // calls B.m() with "nul".
+    }
+
+    @NeverInline
+    static void test(A arg) {
+      arg.m("null");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
new file mode 100644
index 0000000..eaf5924
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2019, 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.ir.optimize.callsites.constants;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeVirtualPositiveTest extends TestBase {
+  private static final Class<?> MAIN = Main.class;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeVirtualPositiveTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(InvokeVirtualPositiveTest.class)
+        .addKeepMainRule(MAIN)
+        .enableMergeAnnotations()
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines("non-null", "null")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject a = inspector.clazz(A.class);
+    assertThat(a, isPresent());
+
+    MethodSubject a_m = a.uniqueMethodWithName("m");
+    assertThat(a_m, isPresent());
+    // TODO(b/69963623):
+    //   Can optimize branches since `arg` is definitely "nul", i.e., not containing "null".
+    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+
+    ClassSubject b = inspector.clazz(B.class);
+    assertThat(b, isPresent());
+
+    MethodSubject b_m = b.uniqueMethodWithName("m");
+    assertThat(b_m, isPresent());
+    // Should not optimize branches since the value of `arg` is unsure.
+    assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  static class A {
+    @NeverInline
+    void m(String arg) {
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  @NeverClassInline
+  static class B extends A {
+    @NeverInline
+    @Override
+    void m(String arg) {
+      // Same as A#m.
+      if (arg.contains("null")) {
+        System.out.println("null");
+      } else {
+        System.out.println("non-null");
+      }
+    }
+  }
+
+  static class Main {
+    public static void main(String... args) {
+      A a = System.currentTimeMillis() > 0 ? new A() : new B();
+      a.m("nul");  // calls A.m() with "nul".
+
+      A b = new B();  // with the exact type:
+      b.m("null");    // calls B.m() with "null".
+    }
+  }
+}