Don't consider non-interface invokes for interface desugaring.

Bug: 199135051
Change-Id: I2a39d1510985feb81689ae88b19dc0a60fc1eadf
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 49ad7d3..5d05028 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1268,6 +1268,7 @@
     newFlags.setSynthetic();
     newFlags.unsetAbstract();
     // Holder is companion class, or retarget method, not an interface.
+    boolean isInterfaceMethodReference = false;
     return syntheticBuilder()
         .setMethod(newMethod)
         .setAccessFlags(newFlags)
@@ -1276,8 +1277,7 @@
         .setCode(
             ForwardMethodBuilder.builder(factory)
                 .setNonStaticSource(newMethod)
-                // Holder is companion class, or retarget method, not an interface.
-                .setStaticTarget(forwardMethod, false)
+                .setStaticTarget(forwardMethod, isInterfaceMethodReference)
                 .build())
         .setApiLevelForDefinition(target.getDefinition().getApiLevelForDefinition())
         .setApiLevelForCode(target.getDefinition().getApiLevelForCode())
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index d7b3d0a..f07a802 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -16,20 +16,21 @@
     DexMethod target = lambda.descriptor.implHandle.asMethod();
     ForwardMethodBuilder forwardMethodBuilder =
         ForwardMethodBuilder.builder(lambda.appView.dexItemFactory()).setStaticSource(accessor);
+    boolean isInterface = lambda.descriptor.implHandle.isInterface;
     switch (lambda.descriptor.implHandle.type) {
       case INVOKE_INSTANCE:
         {
-          forwardMethodBuilder.setVirtualTarget(target, false);
+          forwardMethodBuilder.setVirtualTarget(target, isInterface);
           break;
         }
       case INVOKE_STATIC:
         {
-          forwardMethodBuilder.setStaticTarget(target, false);
+          forwardMethodBuilder.setStaticTarget(target, isInterface);
           break;
         }
       case INVOKE_DIRECT:
         {
-          forwardMethodBuilder.setDirectTarget(target, false);
+          forwardMethodBuilder.setDirectTarget(target, isInterface);
           break;
         }
       case INVOKE_CONSTRUCTOR:
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index c9b9a70..1f737f2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ValueType;
@@ -292,33 +293,40 @@
   }
 
   private DesugarDescription computeDescription(CfInstruction instruction, ProgramMethod context) {
-    // Interface desugaring is only interested in invokes and should not be double processed.
+    // Interface desugaring is only interested in invokes.
     CfInvoke invoke = instruction.asInvoke();
-    if (invoke == null || isAlreadyDesugared(invoke, context)) {
+    if (invoke == null) {
+      return DesugarDescription.nothing();
+    }
+    // Don't desugar if the invoke is to be desugared by preceeding desugar tasks.
+    if (isAlreadyDesugared(invoke, context)) {
       return DesugarDescription.nothing();
     }
     // There should never be any calls to interface initializers.
     if (invoke.isInvokeSpecial() && invoke.isInvokeConstructor(factory)) {
       return DesugarDescription.nothing();
     }
+    // If the invoke is not an interface invoke, then there should generally not be any desugaring.
+    // However, there are some cases where the insertion of forwarding methods can change behavior
+    // so we need to identify them at the various call sites here.
+    if (!invoke.isInterface()) {
+      return computeNonInterfaceInvoke(invoke, context);
+    }
     // If the target holder does not resolve we may want to issue diagnostics.
     DexClass holder = appView.definitionForHolder(invoke.getMethod(), context);
     if (holder == null) {
       // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
       // exception but we can not report it as error since it can also be the intended
       // behavior.
-      if (invoke.isInterface()) {
-        return DesugarDescription.builder()
-            .addScanEffect(
-                () -> {
-                  if (invoke.isInvokeStatic()) {
-                    leavingStaticInvokeToInterface(context);
-                  }
-                  warnMissingType(context, invoke.getMethod().getHolderType());
-                })
-            .build();
-      }
-      return DesugarDescription.nothing();
+      return DesugarDescription.builder()
+          .addScanEffect(
+              () -> {
+                if (invoke.isInvokeStatic()) {
+                  leavingStaticInvokeToInterface(context);
+                }
+                warnMissingType(context, invoke.getMethod().getHolderType());
+              })
+          .build();
     }
     // Continue with invoke type logic.
     if (invoke.isInvokeStatic()) {
@@ -333,6 +341,43 @@
     return DesugarDescription.nothing();
   }
 
+  private DesugarDescription computeNonInterfaceInvoke(CfInvoke invoke, ProgramMethod context) {
+    assert !invoke.isInterface();
+    // Emulated interface desugaring will rewrite non-interface invokes.
+    if (invoke.isInvokeSpecial()) {
+      DexClass clazz = appView.definitionForHolder(invoke.getMethod(), context);
+      if (clazz == null) {
+        return DesugarDescription.nothing();
+      }
+      return computeEmulatedInterfaceInvokeSpecial(clazz, invoke.getMethod(), context);
+    }
+    if (!invoke.isInvokeVirtual() && !invoke.isInvokeInterface()) {
+      return DesugarDescription.nothing();
+    }
+    DesugarDescription description = computeEmulatedInterfaceVirtualDispatchOrNull(invoke);
+    if (description != null) {
+      return description;
+    }
+    // It may be the case that a virtual invoke resolves to a static method. In such a case, if
+    // a default method could give rise to a forwarding method in the resolution path, the program
+    // would change behavior from throwing ICCE to dispatching to the companion class method.
+    AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+    MethodResolutionResult resolution =
+        appInfo.resolveMethod(invoke.getMethod(), invoke.isInterface());
+    if (!resolution.isSingleResolution()
+        || !resolution.asSingleResolution().getResolvedMethod().isStatic()) {
+      return DesugarDescription.nothing();
+    }
+    DexClass holder = appInfo.definitionFor(invoke.getMethod().getHolderType(), context);
+    DexClassAndMethod target = appInfo.lookupMaximallySpecificMethod(holder, invoke.getMethod());
+    if (target != null && target.isDefaultMethod()) {
+      // Rewrite the invoke to a throw ICCE as the default method forward would otherwise hide the
+      // static / virtual mismatch.
+      return computeInvokeAsThrowRewrite(invoke, resolution.asSingleResolution());
+    }
+    return DesugarDescription.nothing();
+  }
+
   private DesugarDescription computeInvokeSpecial(
       DexClass holder, CfInvoke invoke, ProgramMethod context) {
     if (invoke.isInvokeSuper(context.getHolderType())) {
@@ -344,12 +389,9 @@
   private DesugarDescription computeInvokeStatic(
       DexClass holder, CfInvoke invoke, ProgramMethod context) {
     if (!holder.isInterface()) {
-      if (invoke.isInterface()) {
-        return DesugarDescription.builder()
-            .addScanEffect(() -> leavingStaticInvokeToInterface(context))
-            .build();
-      }
-      return DesugarDescription.nothing();
+      return DesugarDescription.builder()
+          .addScanEffect(() -> leavingStaticInvokeToInterface(context))
+          .build();
     }
     // TODO(b/183998768): This should not be needed. Targeted synthetics should be in place.
     if (appView.getSyntheticItems().isPendingSynthetic(invoke.getMethod().getHolderType())) {
@@ -462,6 +504,11 @@
     if (resolution != null && resolution.getResolvedMethod().isStatic()) {
       return computeInvokeAsThrowRewrite(invoke, resolution);
     }
+    DesugarDescription description = computeEmulatedInterfaceVirtualDispatchOrNull(invoke);
+    return description != null ? description : DesugarDescription.nothing();
+  }
+
+  private DesugarDescription computeEmulatedInterfaceVirtualDispatchOrNull(CfInvoke invoke) {
     DexClassAndMethod defaultMethod =
         defaultMethodForEmulatedDispatchOrNull(invoke.getMethod(), invoke.isInterface());
     if (defaultMethod != null) {
@@ -479,7 +526,7 @@
                           .getReference()))
           .build();
     }
-    return DesugarDescription.nothing();
+    return null;
   }
 
   private DesugarDescription computeInvokeDirect(
@@ -783,6 +830,11 @@
       }
     }
 
+    return computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
+  }
+
+  private DesugarDescription computeEmulatedInterfaceInvokeSpecial(
+      DexClass clazz, DexMethod invokedMethod, ProgramMethod context) {
     DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
     if (emulatedItf == null) {
       if (clazz.isInterface() && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java
index be8211b..01c8b8c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/MergeSynthesizingContextIntoSyntheticLambdaTest.java
@@ -26,6 +26,14 @@
   }
 
   @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("I", "J");
+  }
+
+  @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeClassTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeClassTest.java
index 1269b34..a0838e8 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeClassTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.desugar;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestBuilder;
@@ -78,8 +79,10 @@
         InterfaceDesugarMissingTypeDiagnostic desugarWarning = (InterfaceDesugarMissingTypeDiagnostic) diagnostic;
         assertEquals(
             Reference.classFromClass(MissingInterface.class), desugarWarning.getMissingType());
-        assertEquals(Reference.classFromClass(MyClass.class), desugarWarning.getContextType());
-        assertEquals(Position.UNKNOWN, desugarWarning.getPosition());
+        // The type is both missing from the interface referenced invoke and from the implements.
+        // The diagnostics should likely include all contexts akin to the R8 missing types.
+        assertEquals(Reference.classFromClass(TestClass.class), desugarWarning.getContextType());
+        assertNotEquals(Position.UNKNOWN, desugarWarning.getPosition());
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
index 875ead2..bd50527 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
@@ -3,7 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar;
 
+import static com.android.tools.r8.references.Reference.classFromClass;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestBuilder;
@@ -16,7 +19,6 @@
 import com.android.tools.r8.errors.DesugarDiagnostic;
 import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
 import com.android.tools.r8.position.Position;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
@@ -75,12 +77,12 @@
         assertTrue(diagnostic instanceof DesugarDiagnostic);
         assertTrue(diagnostic instanceof InterfaceDesugarMissingTypeDiagnostic);
         InterfaceDesugarMissingTypeDiagnostic desugarWarning = (InterfaceDesugarMissingTypeDiagnostic) diagnostic;
-        assertEquals(
-            Reference.classFromClass(MissingInterface.class), desugarWarning.getMissingType());
-        // TODO(b/132671303): The context class should not be the synthesized lambda class.
-        assertTrue(SyntheticItemsTestUtils.isInternalLambda(desugarWarning.getContextType()));
-        // TODO(b/132671303): The position info should be the method context.
-        assertEquals(Position.UNKNOWN, desugarWarning.getPosition());
+        assertEquals(classFromClass(MissingInterface.class), desugarWarning.getMissingType());
+        // TODO(b/132671303): The diagnostics should also contain the lambda context, but it should
+        //  not be the synthesized lambda class.
+        assertFalse(SyntheticItemsTestUtils.isInternalLambda(desugarWarning.getContextType()));
+        assertEquals(classFromClass(TestClass.class), desugarWarning.getContextType());
+        assertNotEquals(Position.UNKNOWN, desugarWarning.getPosition());
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 9fe36a6..aeb159d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -155,7 +155,6 @@
             .streamInstructions()
             .filter(instr -> instr.isInvokeInterface() || instr.isInvokeStatic())
             .collect(Collectors.toList());
-    assertEquals(22, invokes.size());
     assertInvokeStaticMatching(invokes, 0, "Set$-EL;->spliterator");
     assertInvokeStaticMatching(invokes, 1, "Collection$-EL;->stream");
     assertInvokeInterfaceMatching(invokes, 2, "Set;->iterator");
@@ -173,6 +172,7 @@
     assertInvokeStaticMatching(invokes, 19, "Comparator$-CC;->comparingInt");
     assertInvokeStaticMatching(invokes, 20, "List$-EL;->sort");
     assertInvokeStaticMatching(invokes, 21, "Collection$-EL;->stream");
+    assertEquals(22, invokes.size());
   }
 
   private void assertInvokeInterfaceMatching(List<InstructionSubject> invokes, int i, String s) {
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
index 667116e..825e3ea 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticInterfaceNestedTest.java
@@ -5,18 +5,20 @@
 package com.android.tools.r8.desugar.staticinterfacemethod;
 
 import static com.android.tools.r8.desugar.staticinterfacemethod.InvokeStaticInterfaceNestedTest.Library.foo;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
 import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.BooleanUtils;
-import java.util.List;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -26,38 +28,63 @@
 public class InvokeStaticInterfaceNestedTest extends TestBase {
 
   private final TestParameters parameters;
-  private final boolean cfToCfDesugar;
-  private final String EXPECTED = "Hello World!";
+  private final String UNEXPECTED_SUCCESS = "Hello World!";
 
-  @Parameters(name = "{0}, desugar: {1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
-  public InvokeStaticInterfaceNestedTest(TestParameters parameters, boolean cfToCfDesugar) {
+  public InvokeStaticInterfaceNestedTest(TestParameters parameters) {
     this.parameters = parameters;
-    this.cfToCfDesugar = cfToCfDesugar;
   }
 
-  @Test
-  public void testRuntime() throws Exception {
-    final SingleTestRunResult<?> runResult =
-        testForRuntime(parameters)
-            .addProgramClassFileData(
-                rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
-                rewriteToUseNonInterfaceMethodReference(Library.class, "foo"))
-            .run(parameters.getRuntime(), Main.class);
-    if (parameters.isCfRuntime() && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) {
-      runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
+  private void checkDexResult(TestRunResult<?> runResult, boolean isDesugared) {
+    boolean didDesugarInterfaceMethods =
+        isDesugared && !parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring();
+    if (parameters.isCfRuntime()) {
+      if (parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) {
+        // The correct expected behavior is ICCE.
+        runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+      } else if (didDesugarInterfaceMethods) {
+        runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+      } else {
+        // Dex VMs and JDK 8 will just dispatch (this is not the intended behavior).
+        runResult.assertSuccessWithOutputLines(UNEXPECTED_SUCCESS);
+      }
+      return;
+    }
+    if (parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()) {
+      // Dex VMs and JDK 8 will just dispatch (this is not the intended behavior).
+      runResult.assertSuccessWithOutputLines(UNEXPECTED_SUCCESS);
+      return;
+    }
+    Version version = parameters.getRuntime().asDex().getVm().getVersion();
+    if (version.isOlderThanOrEqual(Version.V4_4_4)) {
+      runResult.assertFailureWithErrorThatThrows(VerifyError.class);
     } else {
-      // TODO(b/166247515): This should be ICCE.
-      runResult.assertSuccessWithOutputLines(EXPECTED);
+      runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
     }
   }
 
   @Test
+  public void testDesugar() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(
+            rewriteToUseNonInterfaceMethodReference(Main.class, "main"),
+            rewriteToUseNonInterfaceMethodReference(Library.class, "foo"))
+        .run(parameters.getRuntime(), Main.class)
+        .apply(
+            result ->
+                result.applyIf(
+                    DesugarTestConfiguration::isDesugared,
+                    r -> checkDexResult(r, true),
+                    r -> checkDexResult(r, false)));
+  }
+
+  @Test
   public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel() == AndroidApiLevel.B);
     final R8FullTestBuilder testBuilder =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(
@@ -65,16 +92,12 @@
                 rewriteToUseNonInterfaceMethodReference(Library.class, "foo"))
             .addKeepAllClassesRule()
             .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(Main.class)
-            .addOptionsModification(
-                options -> {
-                  options.cfToCfDesugar = cfToCfDesugar;
-                });
-    if (parameters.isCfRuntime()) {
+            .addKeepMainRule(Main.class);
+    if (parameters.isDexRuntime()) {
+      checkDexResult(testBuilder.run(parameters.getRuntime(), Main.class), true);
+    } else {
       // TODO(b/166213037): Should not throw an error.
       assertThrows(CompilationFailedException.class, testBuilder::compile);
-    } else {
-      testBuilder.run(parameters.getRuntime(), Main.class).assertSuccessWithOutputLines(EXPECTED);
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
index e5b0bb3..03aaab4 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialInterfaceWithBridge3Test.java
@@ -11,11 +11,11 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -27,17 +27,14 @@
   private static final String EXPECTED = StringUtils.lines("Hello World!");
 
   private final TestParameters parameters;
-  private final boolean isInterface;
 
-  @Parameters(name = "{0}, itf:{1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public InvokeSpecialInterfaceWithBridge3Test(TestParameters parameters, boolean isInterface) {
+  public InvokeSpecialInterfaceWithBridge3Test(TestParameters parameters) {
     this.parameters = parameters;
-    this.isInterface = isInterface;
   }
 
   @Test
@@ -47,14 +44,28 @@
             .addProgramClasses(I.class, A.class, Main.class)
             .addProgramClassFileData(getClassWithTransformedInvoked())
             .run(parameters.getRuntime(), Main.class);
-    if (parameters.isDexRuntime()) {
-      // TODO(b/166210854): Runs but should have failed.
+    if (parameters.isDexRuntime() && parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      // TODO(b/166210854): Runs really should fail, but since DEX does not have interface
+      //  method references the VM will just dispatch.
       result.assertSuccessWithOutput(EXPECTED);
     } else {
-      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+      result.assertFailureWithErrorThatThrows(getExpectedException());
     }
   }
 
+  private Class<? extends Throwable> getExpectedException() {
+    if (parameters.isDexRuntime()) {
+      Version version = parameters.getRuntime().asDex().getVm().getVersion();
+      if (version.isOlderThanOrEqual(Version.V4_4_4)) {
+        return VerifyError.class;
+      }
+      if (version.isNewerThanOrEqual(Version.V7_0_0)) {
+        return AbstractMethodError.class;
+      }
+    }
+    return IncompatibleClassChangeError.class;
+  }
+
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 801d678..f529432 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -105,9 +105,9 @@
           "In C.m3()",
           "In A.m4()",
           "In A.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
-          "Caught IncompatibleClassChangeError when calling B.m3()",
+          "In A.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
           "In C.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
-          "Caught IncompatibleClassChangeError when calling B.m3()",
+          "In C.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
           "In C.m1()",
           "In C.m3()",
           "");
@@ -179,9 +179,7 @@
       assertThat(classSubject, isPresentAndRenamed());
       assertThat(classSubject.method("void", "m1", ImmutableList.of()), isPresent());
       assertThat(classSubject.method("void", "m2", ImmutableList.of()), isAbsent());
-      assertThat(
-          classSubject.method("void", "m3", ImmutableList.of()),
-          parameters.isCfRuntime() ? isPresent() : isAbsent());
+      assertThat(classSubject.method("void", "m3", ImmutableList.of()), isPresent());
       assertThat(classSubject.method("void", "m4", ImmutableList.of()), isAbsent());
     }
   }
@@ -265,8 +263,7 @@
       System.out.println("In B.m2()");
     }
 
-    // Made static in the dump below. Ends up dead as the targeting call is replaced by throw ICCE.
-    // Except in non-desugaring CF the method will remain instead of inserting a stub.
+    // Made static in the dump below. This method is targeted and can therefore not be removed.
     @Override
     public void m3() {
       System.out.println("In B.m3()");