Additional negative tests for invoke interface targets

Bug: 148271337
Change-Id: If4f225e337ff1a90527ef32a5fd2d3e99047b2d0
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index c565c4e..cccaa07 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -896,9 +896,12 @@
       return this;
     }
 
-    /**
-     * Add Java-bytecode program data.
-     */
+    /** Add Java-bytecode program data. */
+    public Builder addClassProgramData(byte[]... data) {
+      return addClassProgramData(Arrays.asList(data));
+    }
+
+    /** Add Java-bytecode program data. */
     public Builder addClassProgramData(Collection<byte[]> data) {
       for (byte[] datum : data) {
         addClassProgramData(datum, Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
new file mode 100644
index 0000000..a81b871
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
@@ -0,0 +1,155 @@
+// 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.resolution.interfacetargets;
+
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assume.assumeTrue;
+
+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.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfaceClInitTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  static {
+  }
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeInterfaceClInitTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.useRuntimeAsNoneRuntime());
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(A.class, B.class)
+                    .addClassProgramData(transformI(), transformMain())
+                    .build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "<clinit>", appInfo.dexItemFactory());
+    Assert.assertThrows(
+        AssertionError.class,
+        () -> {
+          appInfo.resolveMethod(method.holder, method).lookupInterfaceTargets(appInfo);
+        });
+  }
+
+  private Matcher<String> getExpected() {
+    if (parameters.getRuntime().isCf()) {
+      Matcher<String> expected = containsString("java.lang.VerifyError");
+      // JDK 9 and 11 output VerifyError or ClassFormatError non-deterministically.
+      if (parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) {
+        expected = CoreMatchers.anyOf(expected, containsString("java.lang.ClassFormatError"));
+      }
+      return expected;
+    }
+    assert parameters.getRuntime().isDex();
+    DexRuntime dexRuntime = parameters.getRuntime().asDex();
+    if (dexRuntime.getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+      return containsString("NoSuchMethodError");
+    }
+    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
+      return containsString("java.lang.VerifyError");
+    }
+    return containsString("java.lang.VerifyError");
+  }
+
+  @Test
+  public void testRuntimeClInit()
+      throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, B.class)
+        .addProgramClassFileData(transformMain(), transformI())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(getExpected());
+  }
+
+  @Test
+  public void testR8ClInit() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class)
+        .addProgramClassFileData(transformMain(), transformI())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(getExpected());
+  }
+
+  private byte[] transformI() throws IOException {
+    return transformer(I.class)
+        .addClassTransformer(
+            new ClassTransformer() {
+              @Override
+              public MethodVisitor visitMethod(
+                  int access,
+                  String name,
+                  String descriptor,
+                  String signature,
+                  String[] exceptions) {
+                return super.visitMethod(access, "<clinit>", descriptor, signature, exceptions);
+              }
+            })
+        .transform();
+  }
+
+  private byte[] transformMain() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "callClInit",
+            (opcode, owner, name, descriptor, isInterface, continuation) ->
+                continuation.apply(opcode, owner, "<clinit>", descriptor, isInterface))
+        .transform();
+  }
+
+  public interface I {
+
+    default void foo() { // <-- will be rewritten to <clinit>
+      System.out.println("I.foo");
+    }
+  }
+
+  public static class A implements I {}
+
+  public static class B implements I {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      callClInit(args.length == 0 ? new A() : new B());
+    }
+
+    private static void callClInit(I i) {
+      i.foo(); // <-- will be i.<clinit>()
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
new file mode 100644
index 0000000..ede5785
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -0,0 +1,131 @@
+// 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.resolution.interfacetargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
+
+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.TestRuntime;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+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 InvokeInterfaceWithStaticTargetTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvokeInterfaceWithStaticTargetTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testResolution() throws Exception {
+    assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+    AppInfoWithLiveness appInfo =
+        computeAppViewWithLiveness(
+                buildClasses(A.class, I.class).addClassProgramData(transformMain()).build(),
+                Main.class)
+            .appInfo();
+    DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
+    Assert.assertThrows(
+        AssertionError.class,
+        () -> appInfo.resolveMethod(method.holder, method).lookupInterfaceTargets(appInfo));
+  }
+
+  @Test
+  public void testRuntimeClInit()
+      throws IOException, CompilationFailedException, ExecutionException {
+    testForRuntime(parameters)
+        .addProgramClasses(A.class, I.class)
+        .addProgramClassFileData(transformMain())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString(getExpected()));
+  }
+
+  @Test
+  public void testR8ClInit() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, I.class)
+        .addProgramClassFileData(transformMain())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString(getExpected()));
+  }
+
+  private String getExpected() {
+    return parameters.isCfRuntime()
+            || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
+        ? "IncompatibleClassChangeError"
+        : "NoSuchMethodError";
+  }
+
+  private byte[] transformMain() throws IOException {
+    return transformer(Main.class)
+        .transformMethodInsnInMethod(
+            "callFooBar",
+            (opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (name.equals("notify")) {
+                continuation.apply(
+                    INVOKEINTERFACE,
+                    DescriptorUtils.getBinaryNameFromJavaType(I.class.getTypeName()),
+                    "bar",
+                    descriptor,
+                    true);
+              } else {
+                continuation.apply(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  public interface I {
+
+    void foo();
+
+    static void bar() {
+      System.out.println("I.bar");
+    }
+  }
+
+  public static class A implements I {
+
+    @Override
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      callFooBar(args.length == 0 ? () -> System.out.println("Lambda.foo") : new A());
+    }
+
+    public static void callFooBar(I i) {
+      i.foo();
+      i.notify(); // <-- will be i.bar()
+    }
+  }
+}