Reland "Add a test and fix missing lower bound for effectively-final classes"
This reverts commit a70f8356bc4c111149f64708be6a2e1338c6379e.
Bug: 154752443
Change-Id: I48a565682ef296d776e8a293e1d0cd915e1b317e
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 689bf8f..070c9ee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -4,8 +4,10 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -82,10 +84,50 @@
TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this);
assert receiverLowerBoundType.getClassType() == refinedReceiverType
|| receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness)
+ || upperBoundAssumedByCallSiteOptimizationAndNoLongerInstantiated(
+ appViewWithLiveness, refinedReceiverType, receiverLowerBoundType.getClassType())
: "The receiver lower bound does not match the receiver type";
}
}
return true;
}
+
+ private boolean upperBoundAssumedByCallSiteOptimizationAndNoLongerInstantiated(
+ AppView<AppInfoWithLiveness> appViewWithLiveness,
+ DexType upperBoundType,
+ DexType lowerBoundType) {
+ // Check that information came from the CallSiteOptimization.
+ if (!getReceiver().getAliasedValue().isArgument()) {
+ return false;
+ }
+ // Check that the receiver information comes from a dynamic type.
+ if (!getReceiver().definition.isAssumeDynamicType()) {
+ return false;
+ }
+ // Now, it can be that the upper bound is more precise than the lower:
+ // class A { }
+ // class B extends A { }
+ //
+ // class Main {
+ // new B();
+ // }
+ //
+ // Above, the callsite optimization will register that A.<init> will be called with an argument
+ // of type B and put B in as the dynamic upper bound type. However, we can also class-inline B,
+ // thereby removing the instantiation, making A effectively final.
+ // TODO(b/154822960): Perhaps we should not process this code at all?
+ DexProgramClass upperBound = appViewWithLiveness.definitionForProgramType(upperBoundType);
+ if (upperBound == null) {
+ return false;
+ }
+ if (appViewWithLiveness.appInfo().isInstantiatedDirectlyOrIndirectly(upperBound)) {
+ return false;
+ }
+ DexClass lowerBound = appViewWithLiveness.definitionFor(lowerBoundType);
+ if (lowerBound == null || !lowerBound.isEffectivelyFinal(appViewWithLiveness)) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index ca2cdd4..87ee96f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -1171,6 +1171,16 @@
}
public ClassTypeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
+ // If it is a final or effectively-final class type, then we know the lower bound.
+ if (getType().isClassType()) {
+ ClassTypeElement classType = getType().asClassType();
+ DexType type = classType.getClassType();
+ DexClass clazz = appView.definitionFor(type);
+ if (clazz != null && clazz.isEffectivelyFinal(appView)) {
+ return classType;
+ }
+ }
+
Value root = getAliasedValue();
if (root.isPhi()) {
return null;
@@ -1197,16 +1207,6 @@
: lattice;
}
- // If it is a final or effectively-final class type, then we know the lower bound.
- if (getType().isClassType()) {
- ClassTypeElement classType = getType().asClassType();
- DexType type = classType.getClassType();
- DexClass clazz = appView.definitionFor(type);
- if (clazz != null && clazz.isEffectivelyFinal(appView)) {
- return classType;
- }
- }
-
return null;
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicUpperBoundWithEffectivelyFinalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicUpperBoundWithEffectivelyFinalTest.java
new file mode 100644
index 0000000..5393409
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicUpperBoundWithEffectivelyFinalTest.java
@@ -0,0 +1,83 @@
+// 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.ir.optimize.dynamictype;
+
+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 java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DynamicUpperBoundWithEffectivelyFinalTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DynamicUpperBoundWithEffectivelyFinalTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws ExecutionException, CompilationFailedException, IOException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, Base.class, Final.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(Final.class)
+ .addKeepClassAndMembersRules(Base.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @NeverClassInline
+ public static class A {
+
+ @Override
+ public String toString() {
+ return "Hello World!";
+ }
+ }
+
+ public abstract static class Base {
+
+ abstract A run();
+ }
+
+ @NeverClassInline
+ public static final class Final extends Base {
+
+ @Override
+ @NeverInline
+ A run() {
+ return new A();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(runFinal(new Final()));
+ }
+
+ @NeverInline
+ public static A runFinal(Final fin) {
+ return fin.run();
+ }
+ }
+}