Add tests for interface initialization
Bug: 172050082
Bug: 172049649
Bug: 144266257
Change-Id: I82099d8606ceb7c1b06e5dbdc87f4156568e4c7c
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index f18a09f..1b417b3 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -8,7 +8,6 @@
import static com.google.common.collect.Lists.cartesianProduct;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.DataResourceProvider.Visitor;
@@ -77,7 +76,6 @@
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
-import com.android.tools.r8.utils.ThrowingAction;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -584,6 +582,12 @@
return builder;
}
+ protected static AndroidApp.Builder buildInnerClasses(Class<?> clazz) throws IOException {
+ AndroidApp.Builder builder = AndroidApp.builder();
+ builder.addProgramFiles(ToolHelper.getClassFilesForInnerClasses(clazz));
+ return builder;
+ }
+
protected static AndroidApp readClassesAndRuntimeJar(
List<Class<?>> programClasses, Backend backend) throws IOException {
AndroidApp.Builder builder = AndroidApp.builder();
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassMayHaveInitializationSideEffectsTestBase.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassMayHaveInitializationSideEffectsTestBase.java
new file mode 100644
index 0000000..fc1a01e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassMayHaveInitializationSideEffectsTestBase.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ClassMayHaveInitializationSideEffectsTestBase extends TestBase {
+
+ public void assertMayHaveClassInitializationSideEffects(
+ AppView<AppInfoWithLiveness> appView, Class<?> clazz) {
+ assertMayHaveClassInitializationSideEffectsEquals(appView, clazz, true);
+ }
+
+ public void assertNoClassInitializationSideEffects(
+ AppView<AppInfoWithLiveness> appView, Class<?> clazz) {
+ assertMayHaveClassInitializationSideEffectsEquals(appView, clazz, false);
+ }
+
+ public void assertMayHaveClassInitializationSideEffectsEquals(
+ AppView<AppInfoWithLiveness> appView, Class<?> clazz, boolean expected) {
+ DexType type = toDexType(clazz, appView.dexItemFactory());
+ DexProgramClass programClass = asProgramClassOrNull(appView.appInfo().definitionFor(type));
+ assertNotNull(programClass);
+ assertEquals(expected, programClass.classInitializationMayHaveSideEffects(appView));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java
new file mode 100644
index 0000000..2e0f754
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticOnSubInterfaceTest.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByInvokeStaticOnSubInterfaceTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public InterfaceInitializedByInvokeStaticOnSubInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForDesugaring(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ DesugarTestConfiguration::isJavac,
+ runResult -> runResult.assertSuccessWithOutputLines("J"))
+ .applyIf(
+ DesugarTestConfiguration::isNotJavac,
+ runResult -> {
+ if (parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
+ runResult.assertSuccessWithOutputLines("J");
+ } else {
+ // TODO(b/172050082): Calling greet() on the companion class of J should trigger J's
+ // class initializer.
+ runResult.assertSuccessWithEmptyOutput();
+ }
+ });
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("J");
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("J");
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ assertMayHaveClassInitializationSideEffects(appView, J.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ J.greet();
+ }
+ }
+
+ interface I {
+
+ Greeter iGreeter = new Greeter("I");
+ }
+
+ interface J extends I {
+
+ Greeter jGreeter = new Greeter("J");
+
+ static void greet() {}
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java
new file mode 100644
index 0000000..4f2eb00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByInvokeStaticTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByInvokeStaticTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public InterfaceInitializedByInvokeStaticTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForDesugaring(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ DesugarTestConfiguration::isJavac,
+ runResult -> runResult.assertSuccessWithOutputLines("I"))
+ .applyIf(
+ DesugarTestConfiguration::isNotJavac,
+ runResult -> {
+ if (parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
+ runResult.assertSuccessWithOutputLines("I");
+ } else {
+ // TODO(b/172050082): Calling greet() on the companion class of I should trigger I's
+ // class initializer.
+ runResult.assertSuccessWithEmptyOutput();
+ }
+ });
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("I");
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("I");
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ assertMayHaveClassInitializationSideEffects(appView, I.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ I.greet();
+ }
+ }
+
+ interface I {
+
+ Greeter greeter = new Greeter("I");
+
+ static void greet() {}
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java
new file mode 100644
index 0000000..d20f13a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubClassTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByStaticGetOnSubClassTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceInitializedByStaticGetOnSubClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("A");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("A");
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("A");
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ assertMayHaveClassInitializationSideEffects(appView, A.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Greeter greeter = A.aGreeter;
+ }
+ }
+
+ interface I {
+
+ Greeter iGreeter = new Greeter("I");
+ }
+
+ static class A implements I {
+
+ static Greeter aGreeter = new Greeter("A");
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java
new file mode 100644
index 0000000..bb59d8c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetOnSubInterfaceTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByStaticGetOnSubInterfaceTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceInitializedByStaticGetOnSubInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("J");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("J");
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("J");
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ assertMayHaveClassInitializationSideEffects(appView, J.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Greeter greeter = J.jGreeter;
+ }
+ }
+
+ interface I {
+
+ Greeter iGreeter = new Greeter("I");
+ }
+
+ interface J extends I {
+
+ Greeter jGreeter = new Greeter("J");
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java
new file mode 100644
index 0000000..a3e93ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceInitializedByStaticGetTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceInitializedByStaticGetTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceInitializedByStaticGetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("I");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("I");
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("I");
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ assertMayHaveClassInitializationSideEffects(appView, I.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Greeter greeter = I.greeter;
+ }
+ }
+
+ interface I {
+
+ Greeter greeter = new Greeter("I");
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java
new file mode 100644
index 0000000..88c1089
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceNotInitializedByInvokeStaticOnSubClassTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceNotInitializedByInvokeStaticOnSubClassTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceNotInitializedByInvokeStaticOnSubClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ // TODO(b/172049649): Initialization of A does not have side effects.
+ assertMayHaveClassInitializationSideEffects(appView, A.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A.greet();
+ }
+ }
+
+ interface I {
+
+ Greeter iGreeter = new Greeter("I");
+ }
+
+ static class A implements I {
+
+ static void greet() {}
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java
new file mode 100644
index 0000000..c69a6d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceWithDefaultMethodInitializedByInvokeStaticOnSubClassTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(
+ runResult -> {
+ if (parameters.isCfRuntime()
+ || parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
+ runResult.assertSuccessWithOutputLines("I");
+ } else {
+ // On older Android runtimes there is no default interface methods and therefore the
+ // semantics is different.
+ runResult.assertSuccessWithEmptyOutput();
+ }
+ });
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ // TODO(b/144266257): This should succeed with "I" when default interface methods are
+ // supported, but we remove the default method I.m() because it is unused, which changes
+ // the behavior.
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("I");
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ assertMayHaveClassInitializationSideEffects(appView, A.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A.greet();
+ }
+ }
+
+ interface I {
+
+ Greeter iGreeter = new Greeter("I");
+
+ default void m() {}
+ }
+
+ static class A implements I {
+
+ static void greet() {}
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java
new file mode 100644
index 0000000..a31b2ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceWithDefaultMethodInitializedByStaticGetOnSubClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(
+ runResult -> {
+ if (parameters.isCfRuntime()
+ || parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithStaticInterfaceMethodsSupport())) {
+ runResult.assertSuccessWithOutputLines("I", "A");
+ } else {
+ // On older Android runtimes there is no default interface methods and therefore the
+ // semantics is different.
+ runResult.assertSuccessWithOutputLines("A");
+ }
+ });
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ // TODO(b/144266257): This should succeed with "I\nA" when default interface methods are
+ // supported, but we remove the default method I.m() because it is unused, which changes
+ // the behavior.
+ .assertSuccessWithOutputLines("A");
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("I", "A");
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ assertMayHaveClassInitializationSideEffects(appView, A.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Greeter greeter = A.aGreeter;
+ }
+ }
+
+ interface I {
+
+ Greeter iGreeter = new Greeter("I");
+
+ default void m() {}
+ }
+
+ static class A implements I {
+
+ static Greeter aGreeter = new Greeter("A");
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java
new file mode 100644
index 0000000..1977be8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceWithDefaultMethodInitializedByStaticGetOnSubInterfaceTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("J");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("J");
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("J");
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ assertMayHaveClassInitializationSideEffects(appView, J.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Greeter greeter = J.jGreeter;
+ }
+ }
+
+ interface I {
+
+ Greeter iGreeter = new Greeter("I");
+
+ default void m() {}
+ }
+
+ interface J extends I {
+
+ Greeter jGreeter = new Greeter("J");
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java
new file mode 100644
index 0000000..115d6e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2020, 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.shaking.clinit;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest
+ extends ClassMayHaveInitializationSideEffectsTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InterfaceWithDefaultMethodNotInitializedByInvokeStaticOnSubInterfaceTest(
+ TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .allowStdoutMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testClassInitializationMayHaveSideEffects() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(buildInnerClasses(getClass()).build(), TestClass.class);
+ // TODO(b/172049649): The initialization of J does not trigger the <clinit> of I.
+ assertMayHaveClassInitializationSideEffects(appView, J.class);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ J.greet();
+ }
+ }
+
+ interface I {
+
+ Greeter iGreeter = new Greeter("I");
+
+ default void m() {}
+ }
+
+ interface J extends I {
+
+ static void greet() {}
+ }
+
+ static class Greeter {
+
+ Greeter(String greeting) {
+ System.out.println(greeting);
+ }
+ }
+}