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".
+ }
+ }
+}