Add tests for class init change when adding visibility bridges
Bug: 182129249
Bug: 220668540
Bug: 220667525
Change-Id: I8a12a600068fca5e1e9215722ccccd2b2dd98035
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 433349e..f24cac2 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -80,6 +80,9 @@
public void run(ExecutorService executorService) throws ExecutionException {
// Collect all visibility bridges to remove.
+ if (!appView.options().enableVisibilityBridgeRemoval) {
+ return;
+ }
ConcurrentHashMap<DexProgramClass, Set<DexEncodedMethod>> visibilityBridgesToRemove =
new ConcurrentHashMap<>();
processItems(
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 3fec28a..05f1434 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -298,6 +298,7 @@
public boolean readDebugSetFileEvent = false;
public boolean disableL8AnnotationRemoval =
System.getProperty("com.android.tools.r8.disableL8AnnotationRemoval") != null;
+ public boolean enableVisibilityBridgeRemoval = true;
public int callGraphLikelySpuriousCallEdgeThreshold = 50;
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/StaticFieldClassInitMemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/StaticFieldClassInitMemberRebindingTest.java
new file mode 100644
index 0000000..c31c93d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/StaticFieldClassInitMemberRebindingTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2022, 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.memberrebinding;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticFieldClassInitMemberRebindingTest extends TestBase {
+
+ private static final String EXPECTED = "World!";
+ private static final String R8_EXPECTED = "Hello World!";
+
+ private final String NEW_A_DESCRIPTOR = "Lfoo/A;";
+ private final String NEW_B_DESCRIPTOR = "Lfoo/B;";
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClassFileData(
+ getMainWithRewrittenDescriptors(), getAWithPackageFoo(), getBWithRewrittenDescriptors())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ getMainWithRewrittenDescriptors(), getAWithPackageFoo(), getBWithRewrittenDescriptors())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .addOptionsModification(options -> options.enableVisibilityBridgeRemoval = false)
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/220668540): R8 should not change class loading semantics.
+ .assertSuccessWithOutputLines(R8_EXPECTED);
+ }
+
+ private byte[] getAWithPackageFoo() throws Exception {
+ return transformer(A.class).setClassDescriptor(NEW_A_DESCRIPTOR).transform();
+ }
+
+ private byte[] getBWithRewrittenDescriptors() throws Exception {
+ return transformer(B.class)
+ .setClassDescriptor(NEW_B_DESCRIPTOR)
+ .setSuper(NEW_A_DESCRIPTOR)
+ .replaceClassDescriptorInMethodInstructions(descriptor(A.class), NEW_A_DESCRIPTOR)
+ .transform();
+ }
+
+ private byte[] getMainWithRewrittenDescriptors() throws Exception {
+ return transformer(Main.class)
+ .replaceClassDescriptorInMethodInstructions(descriptor(B.class), NEW_B_DESCRIPTOR)
+ .transform();
+ }
+
+ static class /* foo. */ A {
+
+ @NeverInline
+ public static void foo() {
+ System.out.println("World!");
+ }
+ }
+
+ public static class /* foo */ B extends A {
+
+ static {
+ System.out.print("Hello ");
+ }
+ }
+
+ public static class Main {
+
+ @NeverInline
+ public static void test() {
+ B.foo(); // Resolves to A.foo(), hence does not trigger B.<clinit>().
+ }
+
+ public static void main(String[] args) {
+ test();
+ if (System.currentTimeMillis() == 0) {
+ // Needed to ensure we do not remove B.<clinit>() in first round of treeshaking before
+ // running member rebinding analysis.
+ new B();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/RemoveCallToStaticInitTest.java b/src/test/java/com/android/tools/r8/shaking/RemoveCallToStaticInitTest.java
new file mode 100644
index 0000000..0d1fb3d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/RemoveCallToStaticInitTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2022, 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.graph.MethodAccessFlags;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RemoveCallToStaticInitTest extends TestBase {
+
+ private static final String EXPECTED = "Hello World!";
+ private static final String R8_EXPECTED = "World!";
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getBWithBridge())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, Main.class)
+ .addProgramClassFileData(getBWithBridge())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/220667525): R8 should emit EXPECTED
+ .assertSuccessWithOutputLines(R8_EXPECTED)
+ .inspect(
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(B.class);
+ assertThat(clazz, isPresent());
+ // TODO(b/220667525): Should not remove bridge due to class init.
+ assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
+ });
+ }
+
+ private byte[] getBWithBridge() throws Exception {
+ return transformer(B.class)
+ .setAccessFlags(B.class.getMethod("foo"), MethodAccessFlags::setBridge)
+ .transform();
+ }
+
+ public static class A {
+
+ @NeverInline
+ public static void foo() {
+ System.out.println("World!");
+ }
+ }
+
+ public static class B extends A {
+
+ static {
+ System.out.print("Hello ");
+ }
+
+ @NeverInline
+ public static /* bridge */ void foo() {
+ A.foo();
+ }
+ }
+
+ public static class Main {
+
+ @NeverInline
+ public static void test() {
+ B.foo();
+ }
+
+ public static void main(String[] args) {
+ test();
+ }
+ }
+}