Exploit that type information is exact for newly-created instances

This CL improves the mechanism for finding single virtual targets, by exploiting the fact that values that have been defined directly by a new-instance instruction have exact type information.

Bug: 140234782, 140233505
Change-Id: I1613c8c9c8100d6d918dddc8cc8e5cf1ff7ab74f
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 6399a1c..c2fdaaf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -40,16 +40,16 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.DefaultCallSiteOptimizationInfo;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.info.MutableCallSiteOptimizationInfo;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.NestUtils;
+import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultCallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MutableCallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.ir.synthetic.CfEmulateInterfaceSyntheticSourceCodeProvider;
@@ -1281,6 +1281,11 @@
   }
 
   @Override
+  public boolean isValidVirtualTargetForDynamicDispatch() {
+    return isVirtualMethod();
+  }
+
+  @Override
   public DexEncodedMethod asResultOfResolve() {
     checkIfObsolete();
     return this;
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 83a4132..253d014 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -26,6 +26,8 @@
 
   boolean isValidVirtualTarget(InternalOptions options);
 
+  boolean isValidVirtualTargetForDynamicDispatch();
+
   default Set<DexEncodedMethod> lookupVirtualDispatchTargets(
       boolean isInterface, AppInfoWithSubtyping appInfo) {
     return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo);
@@ -141,6 +143,16 @@
     }
 
     @Override
+    public boolean isValidVirtualTargetForDynamicDispatch() {
+      for (DexEncodedMethod method : methods) {
+        if (!method.isVirtualMethod()) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    @Override
     public DexEncodedMethod asResultOfResolve() {
       // Resolution may return any of the targets that were found.
       return methods.get(0);
@@ -218,6 +230,11 @@
     public boolean isValidVirtualTarget(InternalOptions options) {
       return true;
     }
+
+    @Override
+    public boolean isValidVirtualTargetForDynamicDispatch() {
+      return true;
+    }
   }
 
   abstract class FailedResolutionResult extends EmptyResult {
@@ -226,6 +243,11 @@
     public boolean isValidVirtualTarget(InternalOptions options) {
       return false;
     }
+
+    @Override
+    public boolean isValidVirtualTargetForDynamicDispatch() {
+      return false;
+    }
   }
 
   class ClassNotFoundResult extends FailedResolutionResult {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 45716d0..dc74f64 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -90,11 +90,12 @@
   @Override
   public DexEncodedMethod lookupSingleTarget(
       AppView<AppInfoWithLiveness> appView, DexType invocationContext) {
-    DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView, this);
-    DexMethod method = getInvokedMethod();
-    return appView
-        .appInfo()
-        .lookupSingleInterfaceTarget(method, invocationContext, refinedReceiverType);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    return appInfo.lookupSingleInterfaceTarget(
+        getInvokedMethod(),
+        invocationContext,
+        TypeAnalysis.getRefinedReceiverType(appView, this),
+        getReceiver().getExactDynamicType());
   }
 
   @Override
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 2e94e3f..9f5dbe6 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
@@ -6,7 +6,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -59,8 +61,20 @@
   public boolean verifyTypes(AppView<?> appView) {
     assert super.verifyTypes(appView);
 
-    TypeLatticeElement receiverType = getReceiver().getTypeLattice();
+    Value receiver = getReceiver();
+    TypeLatticeElement receiverType = receiver.getTypeLattice();
     assert receiverType.isPreciseType();
+
+    if (appView.appInfo().hasSubtyping()) {
+      DexType exactReceiverType = receiver.getExactDynamicType();
+      if (exactReceiverType != null) {
+        DexType refinedReceiverType =
+            TypeAnalysis.getRefinedReceiverType(appView.withSubtyping(), this);
+        assert exactReceiverType == refinedReceiverType
+            || appView.appInfo().withSubtyping().isMissingOrHasMissingSuperType(exactReceiverType);
+      }
+    }
+
     return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 8a9caba..1d1b2a8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -93,11 +93,12 @@
   @Override
   public DexEncodedMethod lookupSingleTarget(
       AppView<AppInfoWithLiveness> appView, DexType invocationContext) {
-    DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView, this);
-    DexMethod method = getInvokedMethod();
-    return appView
-        .appInfo()
-        .lookupSingleVirtualTarget(method, invocationContext, refinedReceiverType);
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    return appInfo.lookupSingleVirtualTarget(
+        getInvokedMethod(),
+        invocationContext,
+        TypeAnalysis.getRefinedReceiverType(appView, this),
+        getReceiver().getExactDynamicType());
   }
 
   @Override
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 46e8ef7..7eb14d7 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
@@ -1164,4 +1164,17 @@
   public TypeLatticeElement getTypeLattice() {
     return typeLattice;
   }
+
+  public DexType getExactDynamicType() {
+    Value root = getAliasedValue();
+    if (root.isPhi()) {
+      return null;
+    }
+    Instruction definition = root.definition;
+    if (definition.isNewInstance()) {
+      NewInstance newInstance = definition.asNewInstance();
+      return newInstance.clazz;
+    }
+    return null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 9841fff..dddeb6c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -849,16 +849,40 @@
   /** For mapping invoke virtual instruction to single target method. */
   public DexEncodedMethod lookupSingleVirtualTarget(DexMethod method, DexType invocationContext) {
     assert checkIfObsolete();
-    return lookupSingleVirtualTarget(method, invocationContext, method.holder);
+    return lookupSingleVirtualTarget(method, invocationContext, method.holder, null);
   }
 
   public DexEncodedMethod lookupSingleVirtualTarget(
-      DexMethod method, DexType invocationContext, DexType refinedReceiverType) {
+      DexMethod method,
+      DexType invocationContext,
+      DexType refinedReceiverType,
+      DexType receiverLowerBoundType) {
     assert checkIfObsolete();
     DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
     if (directResult != null) {
       return directResult;
     }
+
+    // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
+    // runtime type information. In this case, the invoke will dispatch to the resolution result
+    // from the runtime type of the receiver.
+    if (receiverLowerBoundType != null) {
+      if (receiverLowerBoundType == refinedReceiverType) {
+        ResolutionResult resolutionResult = resolveMethod(method.holder, method, false);
+        if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+          ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
+          if (refinedResolutionResult.hasSingleTarget()
+              && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+            return refinedResolutionResult.asSingleTarget();
+          }
+        }
+        return null;
+      } else {
+        // We should never hit the case at the moment, but if we start tracking more precise lower-
+        // bound type information, we should handle this case as well.
+      }
+    }
+
     // This implements the logic from
     // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
     assert method != null;
@@ -1018,11 +1042,14 @@
 
   public DexEncodedMethod lookupSingleInterfaceTarget(DexMethod method, DexType invocationContext) {
     assert checkIfObsolete();
-    return lookupSingleInterfaceTarget(method, invocationContext, method.holder);
+    return lookupSingleInterfaceTarget(method, invocationContext, method.holder, null);
   }
 
   public DexEncodedMethod lookupSingleInterfaceTarget(
-      DexMethod method, DexType invocationContext, DexType refinedReceiverType) {
+      DexMethod method,
+      DexType invocationContext,
+      DexType refinedReceiverType,
+      DexType receiverLowerBoundType) {
     assert checkIfObsolete();
     DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
     if (directResult != null) {
@@ -1031,6 +1058,27 @@
     if (instantiatedLambdas.contains(method.holder)) {
       return null;
     }
+
+    // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
+    // runtime type information. In this case, the invoke will dispatch to the resolution result
+    // from the runtime type of the receiver.
+    if (receiverLowerBoundType != null) {
+      if (receiverLowerBoundType == refinedReceiverType) {
+        ResolutionResult resolutionResult = resolveMethod(method.holder, method, true);
+        if (resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+          ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
+          if (refinedResolutionResult.hasSingleTarget()
+              && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
+            return refinedResolutionResult.asSingleTarget();
+          }
+        }
+        return null;
+      } else {
+        // We should never hit the case at the moment, but if we start tracking more precise lower-
+        // bound type information, we should handle this case as well.
+      }
+    }
+
     DexClass holder = definitionFor(method.holder);
     if ((holder == null) || holder.isNotProgramClass() || !holder.accessFlags.isInterface()) {
       return null;
diff --git a/src/test/examples/memberrebinding/Memberrebinding.java b/src/test/examples/memberrebinding/Memberrebinding.java
index 74c99fd..1e782ae 100644
--- a/src/test/examples/memberrebinding/Memberrebinding.java
+++ b/src/test/examples/memberrebinding/Memberrebinding.java
@@ -29,9 +29,15 @@
     System.out.println(classExtendsOther.aMethodThatReturnsTwo());
     System.out.println(classExtendsOther.aMethodThatReturnsThree());
     System.out.println(classExtendsOther.aMethodThatReturnsFour());
-    AnIndependentInterface iface = classExtendsOther;
+    AnIndependentInterface iface =
+        System.currentTimeMillis() >= 0
+            ? new ClassExtendsOtherLibraryClass()
+            : new OtherClassExtendsOtherLibraryClass();
     System.out.println(iface.aMethodThatReturnsTwo());
-    SuperClassOfClassExtendsOtherLibraryClass superClass = classExtendsOther;
+    SuperClassOfClassExtendsOtherLibraryClass superClass =
+        System.currentTimeMillis() >= 0
+            ? new ClassExtendsOtherLibraryClass()
+            : new OtherClassExtendsOtherLibraryClass();
     System.out.println(superClass.aMethodThatReturnsTrue());
     System.out.println(superClass.aMethodThatReturnsFalse());
   }
diff --git a/src/test/examples/memberrebinding/OtherClassExtendsOtherLibraryClass.java b/src/test/examples/memberrebinding/OtherClassExtendsOtherLibraryClass.java
new file mode 100644
index 0000000..e88af28
--- /dev/null
+++ b/src/test/examples/memberrebinding/OtherClassExtendsOtherLibraryClass.java
@@ -0,0 +1,20 @@
+// 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 memberrebinding;
+
+import memberrebindinglib.AnIndependentInterface;
+
+public class OtherClassExtendsOtherLibraryClass extends SuperClassOfClassExtendsOtherLibraryClass
+    implements AnIndependentInterface {
+
+  @Override
+  public boolean aMethodThatReturnsTrue() {
+    return true;
+  }
+
+  @Override
+  public boolean aMethodThatReturnsFalse() {
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
index e832c3f..36ea940 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/CustomCollectionTest.java
@@ -83,6 +83,11 @@
             .addInnerClasses(CustomCollectionTest.class)
             .setMinApi(parameters.getApiLevel())
             .addKeepClassAndMembersRules(Executor.class)
+            .addOptionsModification(
+                options -> {
+                  // TODO(b/140233505): Allow devirtualization once fixed.
+                  options.enableDevirtualization = false;
+                })
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
             .inspect(inspector -> this.assertCustomCollectionCallsCorrect(inspector, true))
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/ProgramRewritingTest.java
index 5f27e8d..f9778df 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/ProgramRewritingTest.java
@@ -103,6 +103,11 @@
               .addKeepMainRule(TEST_CLASS)
               .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "stream.jar"))
               .setMinApi(parameters.getApiLevel())
+              .addOptionsModification(
+                  options -> {
+                    // TODO(b/140233505): Allow devirtualization once fixed.
+                    options.enableDevirtualization = false;
+                  })
               .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
               .compile()
               .inspect(this::checkRewrittenInvokes)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
index 695cf0a..a75cc4c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.optimize.devirtualize;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -18,7 +19,6 @@
 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.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -60,17 +60,15 @@
             .inspector();
 
     ClassSubject clazz = inspector.clazz(Main.class);
-    MethodSubject m = clazz.method(CodeInspector.MAIN);
-    long numOfInvokeInterface =
-        Streams.stream(m.iterateInstructions(InstructionSubject::isInvokeInterface)).count();
-    // List#add, List#get
-    assertEquals(2, numOfInvokeInterface);
+    MethodSubject m = clazz.mainMethod();
+    // List#add and List#get get devirtualized into ArrayList#add and ArrayList#get.
+    assertTrue(m.streamInstructions().noneMatch(InstructionSubject::isInvokeInterface));
+    // System.out.println, List#add ~> ArrayList#add, List#get ~> ArrayList#get, I#get ~> A0#get.
     long numOfInvokeVirtual =
-        Streams.stream(m.iterateInstructions(InstructionSubject::isInvokeVirtual)).count();
-    // System.out.println, I#get ~> A0#get
-    assertEquals(2, numOfInvokeVirtual);
-    long numOfCast = Streams.stream(m.iterateInstructions(InstructionSubject::isCheckCast)).count();
-    // check-cast I ~> check-cast A0
+        m.streamInstructions().filter(InstructionSubject::isInvokeVirtual).count();
+    assertEquals(4, numOfInvokeVirtual);
+    // check-cast I ~> check-cast A0.
+    long numOfCast = m.streamInstructions().filter(InstructionSubject::isCheckCast).count();
     assertEquals(1, numOfCast);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
index d26719b..67d312f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/invokeinterface/Main.java
@@ -10,7 +10,7 @@
   private static final int COUNT = 8;
 
   public static void main(String[] args) {
-    I a0 = new A0();
+    I a0 = System.currentTimeMillis() >= 0 ? new A0() : new A1();
     List<I> l = new ArrayList<>();
     for (int i = 0; i < COUNT; i++) {
       l.add(a0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java
new file mode 100644
index 0000000..ced497f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SingleTargetFromExactReceiverTypeTest.java
@@ -0,0 +1,136 @@
+// 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.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 SingleTargetFromExactReceiverTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public SingleTargetFromExactReceiverTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(SingleTargetFromExactReceiverTypeTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(
+            "-keepclassmembers class " + A.class.getTypeName() + " {",
+            "  void cannotBeInlinedDueToKeepRule();",
+            "}")
+        .enableClassInliningAnnotations()
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(this::verifyOnlyCanBeInlinedHasBeenInlined)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines(
+            "A.canBeInlined()",
+            "B.canBeInlined()",
+            "A.cannotBeInlinedDueToDynamicDispatch()",
+            "A.cannotBeInlinedDueToKeepRule()");
+  }
+
+  private void verifyOnlyCanBeInlinedHasBeenInlined(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("canBeInlined"), not(isPresent()));
+    assertThat(
+        aClassSubject.uniqueMethodWithName("cannotBeInlinedDueToDynamicDispatch"), isPresent());
+    assertThat(aClassSubject.uniqueMethodWithName("cannotBeInlinedDueToKeepRule"), isPresent());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+    assertThat(bClassSubject.uniqueMethodWithName("canBeInlined"), not(isPresent()));
+    assertThat(
+        bClassSubject.uniqueMethodWithName("cannotBeInlinedDueToDynamicDispatch"), isPresent());
+    assertThat(bClassSubject.uniqueMethodWithName("cannotBeInlinedDueToKeepRule"), isPresent());
+
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertTrue(
+        mainMethodSubject
+            .streamInstructions()
+            .anyMatch(x -> x.isConstString("A.canBeInlined()", JumboStringMode.ALLOW)));
+    assertThat(
+        mainMethodSubject,
+        invokesMethod(aClassSubject.uniqueMethodWithName("cannotBeInlinedDueToDynamicDispatch")));
+    assertThat(
+        mainMethodSubject,
+        invokesMethod(aClassSubject.uniqueMethodWithName("cannotBeInlinedDueToKeepRule")));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A().canBeInlined();
+      new B().canBeInlined();
+      (System.currentTimeMillis() >= 0 ? new A() : new B()).cannotBeInlinedDueToDynamicDispatch();
+      new A().cannotBeInlinedDueToKeepRule();
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    public void canBeInlined() {
+      System.out.println("A.canBeInlined()");
+    }
+
+    public void cannotBeInlinedDueToDynamicDispatch() {
+      System.out.println("A.cannotBeInlinedDueToDynamicDispatch()");
+    }
+
+    public void cannotBeInlinedDueToKeepRule() {
+      System.out.println("A.cannotBeInlinedDueToKeepRule()");
+    }
+  }
+
+  static class B extends A {
+
+    @Override
+    public void canBeInlined() {
+      System.out.println("B.canBeInlined()");
+    }
+
+    @Override
+    public void cannotBeInlinedDueToDynamicDispatch() {
+      System.out.println("B.cannotBeInlinedDueToDynamicDispatch()");
+    }
+
+    @Override
+    public void cannotBeInlinedDueToKeepRule() {
+      System.out.println("B.cannotBeInlinedDueToKeepRule()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index a3cde5c..4996911 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -139,7 +139,9 @@
     assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
     assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
     assertTrue(iterator.next().holder().is("memberrebinding.ClassExtendsOtherLibraryClass"));
+    assertTrue(iterator.next().holder().is("java.lang.System"));
     assertTrue(iterator.next().holder().is("memberrebindinglib.AnIndependentInterface"));
+    assertTrue(iterator.next().holder().is("java.lang.System"));
     assertTrue(
         iterator.next().holder().is("memberrebinding.SuperClassOfClassExtendsOtherLibraryClass"));
     assertTrue(
@@ -172,7 +174,10 @@
     assertTrue(
         iterator.next().holder().is("memberrebinding.SuperClassOfClassExtendsOtherLibraryClass"));
     // Some dispatches on interfaces.
+    assertTrue(iterator.next().holder().is("java.lang.System"));
     assertTrue(iterator.next().holder().is("memberrebindinglib.AnIndependentInterface"));
+    // Some dispatches on classes.
+    assertTrue(iterator.next().holder().is("java.lang.System"));
     assertTrue(iterator.next().holder().is("memberrebindinglib.SubClass"));
     assertTrue(iterator.next().holder().is("memberrebindinglib.ImplementedInProgramClass"));
     assertFalse(iterator.hasNext());
@@ -369,7 +374,7 @@
   }
 
   @Test
-  public void memberRebindingTest() throws IOException, InterruptedException, ExecutionException {
+  public void memberRebindingTest() throws IOException, ExecutionException {
     Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
 
     Path out = Paths.get(temp.getRoot().getCanonicalPath());
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index b2e44c3..3cd46e12 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -85,14 +85,6 @@
     assertFalse(method.isAbstract());
   }
 
-  private void defaultMethodAbstract(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
-    assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method("int", "method", ImmutableList.of());
-    assertTrue(method.isPresent());
-    assertTrue(method.isAbstract());
-  }
-
   @Test
   public void test() throws Exception {
     runTest(ImmutableList.of(), this::interfaceNotKept);
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/OtherClassImplementingInterface.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/OtherClassImplementingInterface.java
new file mode 100644
index 0000000..b6a39bb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/OtherClassImplementingInterface.java
@@ -0,0 +1,11 @@
+// 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.shaking.defaultmethods;
+
+public class OtherClassImplementingInterface implements InterfaceWithDefaultMethods {
+  public int method() {
+    return 41;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/TestClass.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/TestClass.java
index 5e46681..be7b509 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/TestClass.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/TestClass.java
@@ -7,7 +7,10 @@
 public class TestClass {
 
   public void useInterfaceMethod() {
-    InterfaceWithDefaultMethods iface = new ClassImplementingInterface();
+    InterfaceWithDefaultMethods iface =
+        System.currentTimeMillis() >= 0
+            ? new ClassImplementingInterface()
+            : new OtherClassImplementingInterface();
     System.out.println(iface.method());
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/OtherClassImplementingInterface.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/OtherClassImplementingInterface.java
new file mode 100644
index 0000000..0c3f8f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/OtherClassImplementingInterface.java
@@ -0,0 +1,13 @@
+// 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.shaking.forceproguardcompatibility.defaultmethods;
+
+public class OtherClassImplementingInterface implements InterfaceWithDefaultMethods {
+  public int method() {
+    return 41;
+  }
+
+  public void method2(String x, int y) {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/TestClass.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/TestClass.java
index 35ba3c5..c661852 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/TestClass.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultmethods/TestClass.java
@@ -7,12 +7,18 @@
 public class TestClass {
 
   public void useInterfaceMethod() {
-    InterfaceWithDefaultMethods iface = new ClassImplementingInterface();
+    InterfaceWithDefaultMethods iface =
+        System.currentTimeMillis() >= 0
+            ? new ClassImplementingInterface()
+            : new OtherClassImplementingInterface();
     System.out.println(iface.method());
   }
 
   public void useInterfaceMethod2() {
-    InterfaceWithDefaultMethods iface = new ClassImplementingInterface();
+    InterfaceWithDefaultMethods iface =
+        System.currentTimeMillis() >= 0
+            ? new ClassImplementingInterface()
+            : new OtherClassImplementingInterface();
     iface.method2("a", 1);
   }