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();
+    }
+  }
+}