Reproduce b/135210786: wrong side-effect analysis of const-class.
Bug: 135210786
Change-Id: Ib2a7caf2238456fde275f8c078931264b9672602
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalAccessConstClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalAccessConstClassTest.java
new file mode 100644
index 0000000..6f4bb79
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalAccessConstClassTest.java
@@ -0,0 +1,116 @@
+// 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.canonicalization;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+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 com.google.common.collect.Streams;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// Will be changed to different package.
+class PackagePrivateClass {
+}
+
+// Will be changed to use the above class in a different package.
+class FakePackagePrivateClassConsumer {
+ public static void main(String... args) {
+ if (System.currentTimeMillis() < -2) {
+ System.out.println(PackagePrivateClass.class.getName());
+ } else if (System.currentTimeMillis() < -1) {
+ System.out.println(PackagePrivateClass.class.getName());
+ } else {
+ System.out.println("No need to load any classes");
+ }
+ }
+}
+
+@RunWith(Parameterized.class)
+public class IllegalAccessConstClassTest extends TestBase {
+ private static final Class<?> MAIN = FakePackagePrivateClassConsumer.class;
+ private static final String JAVA_OUTPUT = StringUtils.lines(
+ "No need to load any classes"
+ );
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ private final TestParameters parameters;
+
+ public IllegalAccessConstClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJVMOutput() throws Exception {
+ assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+
+ testForD8()
+ .release()
+ .addProgramClassFileData(
+ IllegalAccessConstClassTestDump.PackagePrivateClassDump.dump())
+ .addProgramClassFileData(
+ IllegalAccessConstClassTestDump.FakePackagePrivateClassConsumerDump.dump())
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ @Ignore("b/135210786")
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ IllegalAccessConstClassTestDump.PackagePrivateClassDump.dump())
+ .addProgramClassFileData(
+ IllegalAccessConstClassTestDump.FakePackagePrivateClassConsumerDump.dump())
+ .addKeepMainRule(MAIN)
+ .noMinification()
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject prvClass = inspector.clazz("PackagePrivateClass");
+ assertThat(prvClass, isPresent());
+
+ ClassSubject mainClass = inspector.clazz(MAIN);
+ assertThat(mainClass, isPresent());
+ MethodSubject mainMethod = mainClass.mainMethod();
+ assertThat(mainMethod, isPresent());
+ // No canonicalization of const-class instructions.
+ assertEquals(
+ 2,
+ Streams.stream(mainMethod.iterateInstructions(InstructionSubject::isConstClass)).count());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalAccessConstClassTestDump.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalAccessConstClassTestDump.java
new file mode 100644
index 0000000..077750c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/IllegalAccessConstClassTestDump.java
@@ -0,0 +1,169 @@
+// 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.canonicalization;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+class IllegalAccessConstClassTestDump {
+
+ // Originated from the following code snippet:
+ //
+ // class ...PackagePrivateClass {}
+ //
+ // then repackaged to the top-level.
+ static class PackagePrivateClassDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "PackagePrivateClass", null, "java/lang/Object", null);
+
+ classWriter.visitSource("IllegalAccessConstClassTest.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(25, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LPackagePrivateClass;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+
+ // Originated from the following code snippet:
+ //
+ // class FakePackagePrivateClassConsumer {
+ // public static void main(String... args) {
+ // if (System.currentTimeMillis() < -2) {
+ // System.out.println(PackagePrivateClass.class.getName());
+ // } else if (System.currentTimeMillis() < -1) {
+ // System.out.println(PackagePrivateClass.class.getName());
+ // } else {
+ // System.out.println("No need to load any classes");
+ // }
+ // }
+ // }
+ //
+ // then rewritten to use the repackaged PackagePrivateClass instead.
+ static class FakePackagePrivateClassConsumerDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_SUPER,
+ "com/android/tools/r8/ir/optimize/canonicalization/FakePackagePrivateClassConsumer",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitSource("IllegalAccessConstClassTest.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(28, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable(
+ "this",
+ "Lcom/android/tools/r8/ir/optimize/canonicalization/FakePackagePrivateClassConsumer;",
+ null,
+ label0,
+ label1,
+ 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC | ACC_VARARGS, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(30, label0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
+ methodVisitor.visitLdcInsn(new Long(-2L));
+ methodVisitor.visitInsn(LCMP);
+ Label label1 = new Label();
+ methodVisitor.visitJumpInsn(IFGE, label1);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(31, label2);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn(Type.getType("LPackagePrivateClass;"));
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label3 = new Label();
+ methodVisitor.visitJumpInsn(GOTO, label3);
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(32, label1);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
+ methodVisitor.visitLdcInsn(new Long(-1L));
+ methodVisitor.visitInsn(LCMP);
+ Label label4 = new Label();
+ methodVisitor.visitJumpInsn(IFGE, label4);
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(33, label5);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn(Type.getType("LPackagePrivateClass;"));
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitJumpInsn(GOTO, label3);
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(35, label4);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("No need to load any classes");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(37, label3);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ Label label6 = new Label();
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label6, 0);
+ methodVisitor.visitMaxs(4, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/UnresolvableLibraryConstClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/UnresolvableLibraryConstClassTest.java
new file mode 100644
index 0000000..a054c1b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/UnresolvableLibraryConstClassTest.java
@@ -0,0 +1,143 @@
+// 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.canonicalization;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+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.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+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 com.google.common.collect.Streams;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class LibraryClass {
+}
+
+@NeverMerge
+class ProgramClass1 extends LibraryClass {
+}
+
+class ProgramSubClass extends ProgramClass1 {
+}
+
+interface LibraryInterface {
+ void foo();
+}
+
+class ProgramClass2 implements LibraryInterface {
+ @Override
+ public void foo() {
+ System.out.println("ProgramClass2::foo");
+ }
+}
+
+class TestMain {
+ public static void main(String... args) {
+ if (System.currentTimeMillis() < -6) {
+ System.out.println(ProgramSubClass.class.getName());
+ } else if (System.currentTimeMillis() < -5) {
+ System.out.println(ProgramSubClass.class.getName());
+ } else if (System.currentTimeMillis() < -4) {
+ System.out.println(ProgramClass1.class.getName());
+ } else if (System.currentTimeMillis() < -3) {
+ System.out.println(ProgramClass1.class.getName());
+ } else if (System.currentTimeMillis() < -2) {
+ System.out.println(ProgramClass2.class.getName());
+ } else if (System.currentTimeMillis() < -1) {
+ System.out.println(ProgramClass2.class.getName());
+ } else {
+ System.out.println("No need to load any classes");
+ }
+ }
+}
+
+@RunWith(Parameterized.class)
+public class UnresolvableLibraryConstClassTest extends TestBase {
+ private static final Class<?> MAIN = TestMain.class;
+ private static final String JAVA_OUTPUT = StringUtils.lines(
+ "No need to load any classes"
+ );
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ private final TestParameters parameters;
+
+ public UnresolvableLibraryConstClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJVMOutput() throws Exception {
+ assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime());
+ testForJvm()
+ .addTestClasspath()
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+
+ testForD8()
+ .release()
+ .addProgramClasses(ProgramClass1.class, ProgramClass2.class, ProgramSubClass.class, MAIN)
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ @Ignore("b/135210786")
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addLibraryClasses(LibraryClass.class, LibraryInterface.class)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addProgramClasses(ProgramClass1.class, ProgramClass2.class, ProgramSubClass.class, MAIN)
+ .addKeepMainRule(MAIN)
+ .noMinification()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject programClass1 = inspector.clazz(ProgramClass1.class);
+ assertThat(programClass1, isPresent());
+ ClassSubject programSubClass = inspector.clazz(ProgramSubClass.class);
+ assertThat(programSubClass, isPresent());
+ ClassSubject programClass2 = inspector.clazz(ProgramClass2.class);
+ assertThat(programClass2, isPresent());
+
+ ClassSubject mainClass = inspector.clazz(MAIN);
+ assertThat(mainClass, isPresent());
+ MethodSubject mainMethod = mainClass.mainMethod();
+ assertThat(mainMethod, isPresent());
+ // No canonicalization of const-class instructions.
+ assertEquals(
+ 6,
+ Streams.stream(mainMethod.iterateInstructions(InstructionSubject::isConstClass)).count());
+ }
+}