Reproduce NPE from bridge hoisting of non-live bridges
Bug: 195037294
Change-Id: I92ab8cc255209179409b8c8af7b10106938deaf5
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index 69c59b5..f6082ca 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMember;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -34,6 +35,7 @@
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -120,7 +122,7 @@
private void processClass(DexProgramClass clazz, SubtypingInfo subtypingInfo) {
Set<DexType> subtypes = subtypingInfo.allImmediateSubtypes(clazz.type);
- Set<DexProgramClass> subclasses = new TreeSet<>((x, y) -> x.type.compareTo(y.type));
+ Set<DexProgramClass> subclasses = new TreeSet<>(Comparator.comparing(DexClass::getType));
for (DexType subtype : subtypes) {
DexProgramClass subclass = asProgramClassOrNull(appView.definitionFor(subtype));
if (subclass == null) {
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/AbstractAfterTreeShakingBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/AbstractAfterTreeShakingBridgeHoistingTest.java
new file mode 100644
index 0000000..c249684
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/AbstractAfterTreeShakingBridgeHoistingTest.java
@@ -0,0 +1,121 @@
+// 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.bridgeremoval.hoisting;
+
+import com.android.tools.r8.CompilationFailedException;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/** Regression test for b/195037294. */
+@RunWith(Parameterized.class)
+public class AbstractAfterTreeShakingBridgeHoistingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public AbstractAfterTreeShakingBridgeHoistingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, A.class)
+ .addProgramClassFileData(
+ transformer(B1.class)
+ .setBridge(B1.class.getDeclaredMethod("virtualBridge", Object.class))
+ .transform(),
+ transformer(B2.class)
+ .setBridge(B2.class.getDeclaredMethod("virtualBridge", Object.class))
+ .transform())
+ .addKeepMainRule(TestClass.class)
+ // Keep test() method to disable call site optimization for that method.
+ .addKeepRules(
+ "-keepclassmembers class " + TestClass.class.getTypeName() + " {",
+ " void test(...);",
+ "}")
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput("Hello");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ B1 b1 = null;
+ B2 b2 = null;
+ // Dead instantiations of B1 and B2. This way, R8 considers B1 and B2 to be instantiated until
+ // the second round of tree shaking. After the second round of tree shaking, the
+ // virtualBridge() methods on B1 and B2 are made abstract, since they are not instantiated.
+ // This should disable bridge hoisting.
+ if (alwaysFalse()) {
+ b1 = B1.create();
+ b2 = B2.create();
+ }
+ test(new A(), b1, b2);
+ }
+
+ static boolean alwaysFalse() {
+ return false;
+ }
+
+ @NeverInline
+ private static void test(A a, B1 b1, B2 b2) {
+ System.out.print(a.m("Hello"));
+ if (b1 != null) {
+ System.out.print(b1.virtualBridge(" "));
+ }
+ if (b2 != null) {
+ System.out.println(b2.virtualBridge("world!"));
+ }
+ }
+ }
+
+ static class A {
+
+ @NeverInline
+ public Object m(String arg) {
+ return System.currentTimeMillis() >= 0 ? arg : null;
+ }
+ }
+
+ @NeverClassInline
+ static class B1 extends A {
+
+ static B1 create() {
+ return new B1();
+ }
+
+ @NeverInline
+ public /*bridge*/ String virtualBridge(Object o) {
+ return (String) m((String) o);
+ }
+ }
+
+ @NeverClassInline
+ static class B2 extends A {
+
+ static B2 create() {
+ return new B2();
+ }
+
+ @NeverInline
+ public /*bridge*/ String virtualBridge(Object o) {
+ return (String) m((String) o);
+ }
+ }
+}