Make the interpretation of invoke-special more precise

Before putting in bridges to handle the case where we have an
invoke-special to a virtual method in the same class (b/110175213)
this CL will make the initial analysis a bit more intelligent.

This seems to be the closest we can come to supporting invoke-special
without actually implementing it :)

Bug: 144450911
Change-Id: Ifd6aa42a7527468341d1cbb874a953e925576d21
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index fd0c968..afdc15b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -13,6 +14,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -158,26 +160,10 @@
           if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
             type = Type.DIRECT;
           } else if (code.getOriginalHolder() == method.holder) {
-            if (!this.itf || builder.appView.options().isInterfaceMethodDesugaringEnabled()) {
-              // When desugaring default interface methods, it is expected they are targeted with
-              // invoke-direct.
-              type = Type.DIRECT;
-            } else {
-              DexProgramClass clazz = builder.appView.definitionForProgramType(method.holder);
-              assert clazz != null;
-              DexEncodedMethod encodedMethod = clazz.lookupDirectMethod(method);
-              if (encodedMethod != null) {
-                assert encodedMethod.isStatic() || encodedMethod.isPrivateMethod();
-                type = Type.DIRECT;
-              } else {
-                // This is a default interface method.
-                type = Type.SUPER;
-              }
-            }
+            type = invokeTypeForInvokeSpecialToNonInitMethodOnHolder(builder.appView, code);
           } else {
             type = Type.SUPER;
           }
-          assert type == Type.SUPER || type == Type.DIRECT;
           break;
         }
       case Opcodes.INVOKESTATIC:
@@ -216,4 +202,39 @@
     return InliningConstraintVisitor.getConstraintForInvoke(
         opcode, method, graphLense, appView, inliningConstraints, invocationContext);
   }
+
+  private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
+      AppView<?> appView, CfSourceCode code) {
+    boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
+    DexEncodedMethod encodedMethod = lookupMethod(appView, method);
+    if (encodedMethod == null) {
+      // The method is not defined on the class, we can use super to target. When desugaring
+      // default interface methods, it is expected they are targeted with invoke-direct.
+      return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
+    }
+    if (!encodedMethod.isVirtualMethod()) {
+      return Type.DIRECT;
+    }
+    if (encodedMethod.accessFlags.isFinal()) {
+      // This method is final which indicates no subtype will overwrite it, we can use
+      // invoke-virtual.
+      return Type.VIRTUAL;
+    }
+    if (this.itf && encodedMethod.isDefaultMethod()) {
+      return desugaringEnabled ? Type.DIRECT : Type.SUPER;
+    }
+    // We cannot emulate the semantics of invoke-special in this case and should throw a compilation
+    // error.
+    throw new CompilationError(
+        "Failed to compile unsupported use of invokespecial", code.getOrigin());
+  }
+
+  private DexEncodedMethod lookupMethod(AppView<?> appView, DexMethod method) {
+    GraphLenseLookupResult lookupResult =
+        appView.graphLense().lookupMethod(method, method, Type.DIRECT);
+    DexMethod rewrittenMethod = lookupResult.getMethod();
+    DexProgramClass clazz = appView.definitionForProgramType(rewrittenMethod.holder);
+    assert clazz != null;
+    return clazz.lookupMethod(rewrittenMethod);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 28e95f8..a7c90fe 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -39,8 +39,6 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -251,6 +249,10 @@
             && method.accessFlags.isSynchronized();
   }
 
+  public Origin getOrigin() {
+    return origin;
+  }
+
   public DexType getOriginalHolder() {
     return code.getOriginalHolder();
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index f8e0eb0..13059f6 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -237,6 +237,11 @@
     return self();
   }
 
+  public CR assertOnlyErrors() {
+    getDiagnosticMessages().assertOnlyErrors();
+    return self();
+  }
+
   public CR assertInfoMessageThatMatches(Matcher<String> matcher) {
     getDiagnosticMessages().assertInfoMessageThatMatches(matcher);
     return self();
@@ -257,6 +262,16 @@
     return self();
   }
 
+  public CR assertErrorMessageThatMatches(Matcher<String> matcher) {
+    getDiagnosticMessages().assertErrorMessageThatMatches(matcher);
+    return self();
+  }
+
+  public CR assertNoErrorMessageThatMatches(Matcher<String> matcher) {
+    getDiagnosticMessages().assertNoErrorMessageThatMatches(matcher);
+    return self();
+  }
+
   public CR disassemble(PrintStream ps) throws IOException, ExecutionException {
     ToolHelper.disassemble(app, ps);
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 5759c3a..702815a 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -36,4 +36,8 @@
   public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher);
 
   public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher);
+
+  public TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher);
+
+  public TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher);
 }
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 8282c77..478a60d 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -171,4 +171,14 @@
   public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher) {
     return assertNoMessageThatMatches(getWarnings(), "warning", matcher);
   }
+
+  @Override
+  public TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher) {
+    return assertMessageThatMatches(getErrors(), "error", matcher);
+  }
+
+  @Override
+  public TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher) {
+    return assertNoMessageThatMatches(getErrors(), "error", matcher);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
index 1765fc2..f72a98f 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeFinalTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.graph;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertTrue;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 
@@ -13,7 +12,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.TestRuntime.DexRuntime;
 import com.android.tools.r8.ToolHelper.DexVm;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -39,29 +37,23 @@
   @Test
   public void testCallingFinal()
       throws IOException, CompilationFailedException, ExecutionException {
+    boolean hasIncorrectSuperLookup =
+        parameters.isDexRuntime()
+            && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+            && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_6_0_1_HOST);
     TestRunResult<?> runResult =
         testForRuntime(parameters)
             .addProgramClasses(Main.class, A.class)
             .addProgramClassFileData(
                 getClassWithTransformedInvoked(B.class), getClassWithTransformedInvoked(C.class))
-            .run(parameters.getRuntime(), Main.class);
-    // TODO(b/144450911): Remove when fixed.
-    if (parameters.isCfRuntime()) {
-      runResult.assertSuccessWithOutputLines(
-          "Hello from B",
-          "Hello from B",
-          "Hello from B",
-          "Hello from B",
-          "Hello from A",
-          "Hello from B");
-    } else {
-      DexRuntime dexRuntime = parameters.getRuntime().asDex();
-      if (dexRuntime.getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
-        runResult.assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
-      } else {
-        runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
-      }
-    }
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines(
+                "Hello from B",
+                "Hello from B",
+                hasIncorrectSuperLookup ? "Hello from A" : "Hello from B",
+                "Hello from B",
+                "Hello from A",
+                "Hello from B");
   }
 
   private byte[] getClassWithTransformedInvoked(Class<?> clazz) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
index 1c0c5b8..816288d 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForInvokeVirtualTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.graph;
 
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
@@ -43,17 +41,8 @@
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
             .addProgramClasses(A.class, Main.class)
             .addProgramClassFileData(getClassBWithTransformedInvoked())
-            .run(parameters.getRuntime(), Main.class);
-    // TODO(b/144450911): Remove when fixed.
-    if (parameters.isCfRuntime()) {
-      runResult.assertSuccessWithOutputLines("Hello World!");
-    } else {
-      runResult.assertFailureWithErrorThatMatches(
-          anyOf(
-              containsString("IncompatibleClassChangeError"),
-              containsString(
-                  "com.android.tools.r8.graph.InvokeSpecialForInvokeVirtualTest$B.foo")));
-    }
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("Hello World!");
   }
 
   private byte[] getClassBWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
index 5852bc1..a54fa1d 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialForNonDeclaredInvokeVirtualTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.graph;
 
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
@@ -43,17 +41,8 @@
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
             .addProgramClasses(A.class, B.class, Main.class)
             .addProgramClassFileData(getClassCWithTransformedInvoked())
-            .run(parameters.getRuntime(), Main.class);
-    // TODO(b/144450911): Remove when fixed.
-    if (parameters.isCfRuntime()) {
-      runResult.assertSuccessWithOutputLines("Hello World!");
-    } else {
-      runResult.assertFailureWithErrorThatMatches(
-          anyOf(
-              containsString("IncompatibleClassChangeError"),
-              containsString(
-                  "com.android.tools.r8.graph.InvokeSpecialForNonDeclaredInvokeVirtualTest$C.foo")));
-    }
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("Hello World!");
   }
 
   private byte[] getClassCWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
index bd97457..f49c764 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.graph;
 
-import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
@@ -40,23 +39,21 @@
 
   @Test
   public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
+    boolean hasSegmentationFaultOnInvokeSuper =
+        parameters.isDexRuntime()
+            && parameters.getRuntime().asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+            && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_6_0_1_HOST);
     TestRunResult<?> runResult =
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
             .addProgramClasses(I.class, Main.class)
             .addProgramClassFileData(getClassWithTransformedInvoked())
             .run(parameters.getRuntime(), Main.class);
-    // TODO(b/144450911): Remove when fixed.
+    // TODO(b/110175213): Remove when fixed.
     if (parameters.isCfRuntime()) {
       runResult.assertSuccessWithOutputLines("Hello World!");
-    } else if (parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
-      runResult.assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
     } else {
       runResult.assertFailureWithErrorThatMatches(
-          anyOf(
-              containsString("IncompatibleClassChangeError"),
-              containsString(
-                  "com.android.tools.r8.graph.InvokeSpecialForInvokeVirtualTest$B.foo")));
+          containsString(hasSegmentationFaultOnInvokeSuper ? "SIGSEGV" : "NoSuchMethodError"));
     }
   }
 
@@ -80,7 +77,7 @@
   public static class B implements I {
 
     public void bar() {
-      foo(); // Will be rewritten to invoke-special I.foo()
+      foo(); // Will be rewritten to invoke-special B.foo()
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
index 62b5aba..1253df8 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialInterfaceWithBridgeTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.graph;
 
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
@@ -15,7 +13,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -45,20 +42,8 @@
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
             .addProgramClasses(I.class, A.class, Main.class)
             .addProgramClassFileData(getClassWithTransformedInvoked())
-            .run(parameters.getRuntime(), Main.class);
-    // TODO(b/144450911): Remove when fixed.
-    if (parameters.isCfRuntime()) {
-      runResult.assertSuccessWithOutputLines("Hello World!");
-    } else if (parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
-      runResult.assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
-    } else {
-      runResult.assertFailureWithErrorThatMatches(
-          anyOf(
-              containsString("IncompatibleClassChangeError"),
-              containsString(
-                  "com.android.tools.r8.graph.InvokeSpecialForInvokeVirtualTest$B.foo")));
-    }
+            .run(parameters.getRuntime(), Main.class)
+            .assertSuccessWithOutputLines("Hello World!");
   }
 
   private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
index 59f8e99..e046730 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialMissingInvokeVirtualTest.java
@@ -43,8 +43,8 @@
         testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
             .addProgramClasses(A.class, Main.class)
             .addProgramClassFileData(getClassWithTransformedInvoked())
-            .run(parameters.getRuntime(), Main.class);
-    runResult.assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
+            .run(parameters.getRuntime(), Main.class)
+            .assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
   }
 
   private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
index 60a788f..00f4bb3 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialOnSameClassTest.java
@@ -5,15 +5,14 @@
 package com.android.tools.r8.graph;
 
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 
 import com.android.tools.r8.CompilationFailedException;
 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.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
@@ -37,21 +36,18 @@
 
   @Test
   public void testRuntime() throws IOException, CompilationFailedException, ExecutionException {
-    TestRunResult<?> runResult =
-        testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
-            .addProgramClasses(Main.class)
-            .addProgramClassFileData(getClassWithTransformedInvoked())
-            .run(parameters.getRuntime(), Main.class);
-    // TODO(b/144450911): Remove when fixed.
-    if (parameters.isCfRuntime()) {
-      runResult.assertSuccessWithOutputLines("Hello World!");
-    } else {
-      DexRuntime dexRuntime = parameters.getRuntime().asDex();
-      if (dexRuntime.getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
-        runResult.assertFailureWithErrorThatMatches(containsString("NoSuchMethodError"));
-      } else {
-        runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
-      }
+    try {
+      testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
+          .addProgramClasses(Main.class)
+          .addProgramClassFileData(getClassWithTransformedInvoked())
+          .run(parameters.getRuntime(), Main.class)
+          .assertSuccessWithOutputLines("Hello World!");
+      // TODO(b/110175213): Remove when fixed.
+      assertTrue(parameters.isCfRuntime());
+    } catch (CompilationFailedException compilation) {
+      assertThat(
+          compilation.getCause().getMessage(),
+          containsString("Failed to compile unsupported use of invokespecial"));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
index 5e2d674..83d0b83 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.invokespecial.Main;
@@ -52,13 +53,15 @@
         .assertSuccessWithOutput(StringUtils.lines("true", "false"));
   }
 
-  @Test
+  @Test(expected = CompilationFailedException.class)
   public void testD8Behavior() throws Exception {
     // TODO(b/110175213): Should succeed with output "true\nfalse\n".
     testForD8()
         .addProgramFiles(inputJar)
-        .run(Main.class)
-        .assertFailureWithErrorThatMatches(containsString(getExpectedOutput()));
+        .compileWithExpectedDiagnostics(
+            testDiagnosticMessages ->
+                testDiagnosticMessages.assertErrorMessageThatMatches(
+                    containsString("Failed to compile unsupported use of invokespecial")));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
index 1907f33..b0e72f0 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.ToolHelper;
 import com.google.common.collect.ImmutableList;
 import org.junit.Rule;
@@ -71,11 +72,12 @@
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
 
-    // TODO(zerny): Should we fail early on the above code? Art fails with a verification error
-    // because Test.foo is expected to be in the direct method table.
     if (ToolHelper.artSupported()) {
-      thrown.expect(AssertionError.class);
+      thrown.expect(CompilationFailedException.class);
     }
+
+    // TODO(b/110175213): This will fail with a compilation exception since we cannot translate
+    //  an invoke-special to a member on the same class.
     String artResult = runOnArtD8(builder, clazz.name);
     assertEquals(expected, artResult);
   }