Test for behavior of invalid bootstrap method handles.

Change-Id: I932b4ba73c890f9ac12706b809e725e58f4eb20b
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index 3249abf..8c76ca1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
@@ -68,7 +69,7 @@
     if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC
         && bsmHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
       // JVM9 §4.7.23 note: Tag must be InvokeStatic or NewInvokeSpecial.
-      throw new Unreachable("Bootstrap handle invalid: tag == " + bsmHandle.getTag());
+      throw new CompilationError("Bootstrap handle invalid: tag == " + bsmHandle.getTag());
     }
     // Resolve the bootstrap method.
     DexMethodHandle bootstrapMethod = DexMethodHandle.fromAsmHandle(bsmHandle, application, clazz);
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTest.java b/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTest.java
new file mode 100644
index 0000000..900fb85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTest.java
@@ -0,0 +1,192 @@
+// 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.cf.methodhandles;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InvalidBootstrapMethodHandleTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Called foo!");
+
+  static final Map<String, Integer> HANDLE_TYPES =
+      ImmutableMap.<String, Integer>builder()
+          .put("GETFIELD", Opcodes.H_GETFIELD)
+          .put("GETSTATIC", Opcodes.H_GETSTATIC)
+          .put("PUTFIELD", Opcodes.H_PUTFIELD)
+          .put("PUTSTATIC", Opcodes.H_PUTSTATIC)
+          .put("INVOKEVIRTUAL", Opcodes.H_INVOKEVIRTUAL)
+          .put("INVOKESTATIC", Opcodes.H_INVOKESTATIC)
+          .put("INVOKESPECIAL", Opcodes.H_INVOKESPECIAL)
+          .put("NEWINVOKESPECIAL", Opcodes.H_NEWINVOKESPECIAL)
+          .put("INVOKEINTERFACE", Opcodes.H_INVOKEINTERFACE)
+          .build();
+
+  private final TestParameters parameters;
+  private final String handleTypeString;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withAllRuntimes()
+            .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
+            .build(),
+        new TreeSet<>(HANDLE_TYPES.keySet()));
+  }
+
+  public InvalidBootstrapMethodHandleTest(TestParameters parameters, String handleTypeString) {
+    this.parameters = parameters;
+    this.handleTypeString = handleTypeString;
+  }
+
+  private int handleTypeOpcode() {
+    return HANDLE_TYPES.get(handleTypeString);
+  }
+
+  @Test
+  public void test() throws Exception {
+    // The compiler will fail on opcodes different from 6 and 8.
+    // Investigate why 8 is supposedly valid.
+    if (parameters.isDexRuntime()
+        && handleTypeOpcode() != Opcodes.H_INVOKESTATIC
+        && handleTypeOpcode() != Opcodes.H_NEWINVOKESPECIAL) {
+      try {
+        testForD8()
+            .addProgramClasses(InvalidBootstrapMethodHandleTestInterface.class)
+            .addProgramClassFileData(getProgramClassFileData())
+            .compileWithExpectedDiagnostics(
+                diagnotics ->
+                    diagnotics.assertErrorMessageThatMatches(
+                        containsString("Bootstrap handle invalid")));
+        fail("Expected compilation to fail");
+      } catch (CompilationFailedException e) {
+        // Expected failure.
+        return;
+      }
+    }
+    TestRunResult<?> result =
+        testForRuntime(parameters)
+            .addProgramClasses(InvalidBootstrapMethodHandleTestInterface.class)
+            .addProgramClassFileData(getProgramClassFileData())
+            .run(parameters.getRuntime(), InvalidBootstrapMethodHandleTestClass.class);
+    // The static target is valid and should run as expected.
+    if (handleTypeOpcode() == Opcodes.H_INVOKESTATIC) {
+      result.assertSuccessWithOutput(EXPECTED);
+      return;
+    }
+    // The invalid targets will trigger an error due to the bootstrap method not being able to be
+    // cast to the expected signature for a bootstrap method. This happens prior to invoking the
+    // target of that handle.
+    if (parameters.isCfRuntime()) {
+      result.assertFailureWithErrorThatThrows(BootstrapMethodError.class);
+      result.assertFailureWithErrorThatMatches(containsString("cannot convert"));
+      result.assertFailureWithErrorThatMatches(
+          containsString("to (Lookup,String,MethodType)Object"));
+      return;
+    }
+    // D8 allows new-invoke-special type during compilation, but it is not accepted by ART.
+    if (handleTypeOpcode() == Opcodes.H_NEWINVOKESPECIAL) {
+      result.assertFailureWithErrorThatMatches(
+          containsString("handle type is not InvokeStatic: 6"));
+    }
+  }
+
+  // Each handle is returned such that the method handle itself is valid and will not cause an
+  // error due to failed handle construction.
+  private Handle getHandle() {
+    String bootstrapSignature =
+        "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
+
+    switch (handleTypeOpcode()) {
+      case Opcodes.H_GETFIELD:
+      case Opcodes.H_PUTFIELD:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "nonStaticField",
+            "I",
+            false);
+
+      case Opcodes.H_GETSTATIC:
+      case Opcodes.H_PUTSTATIC:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "staticField",
+            "I",
+            false);
+
+      case Opcodes.H_INVOKESTATIC:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "staticMethod",
+            bootstrapSignature,
+            handleTypeOpcode() == Opcodes.H_INVOKEINTERFACE);
+
+      case Opcodes.H_INVOKEVIRTUAL:
+      case Opcodes.H_INVOKESPECIAL:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "virtualMethod",
+            bootstrapSignature,
+            false);
+
+      case Opcodes.H_INVOKEINTERFACE:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestInterface.class),
+            "virtualMethod",
+            bootstrapSignature,
+            true);
+
+      case Opcodes.H_NEWINVOKESPECIAL:
+        return new Handle(
+            handleTypeOpcode(),
+            binaryName(InvalidBootstrapMethodHandleTestClass.class),
+            "<init>",
+            "()V",
+            false);
+
+      default:
+        throw new RuntimeException("Unexpected handle type");
+    }
+  }
+
+  private byte[] getProgramClassFileData() throws Exception {
+    return transformer(InvalidBootstrapMethodHandleTestClass.class)
+        .addMethodTransformer(
+            new MethodTransformer() {
+              @Override
+              public void visitMethodInsn(
+                  int opcode, String owner, String name, String descriptor, boolean isInterface) {
+                if (opcode == Opcodes.INVOKESTATIC && name.equals("foo")) {
+                  visitInvokeDynamicInsn("foo", "()V", getHandle());
+                } else {
+                  super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                }
+              }
+            })
+        .transform();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTestClass.java b/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTestClass.java
new file mode 100644
index 0000000..9040a68
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/methodhandles/InvalidBootstrapMethodHandleTestClass.java
@@ -0,0 +1,56 @@
+// 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.cf.methodhandles;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+interface InvalidBootstrapMethodHandleTestInterface {
+  CallSite virtualMethod(MethodHandles.Lookup caller, String name, MethodType type)
+      throws Exception;
+}
+
+// Test data class for InvalidBootstrapMethodHandleTest.
+// Making this an inner class of the test causes changes to the VMs reflection.
+public class InvalidBootstrapMethodHandleTestClass
+    implements InvalidBootstrapMethodHandleTestInterface {
+
+  // Static field to target.
+  public static int staticField = 42;
+
+  // Non-static field to target.
+  public int nonStaticField = 42;
+
+  // Virtual method to target.
+  public CallSite virtualMethod(MethodHandles.Lookup caller, String name, MethodType type)
+      throws Exception {
+    return new ConstantCallSite(caller.findStatic(caller.lookupClass(), name, type));
+  }
+
+  // Actual valid static bootstrap method.
+  public static CallSite staticMethod(MethodHandles.Lookup caller, String name, MethodType type)
+      throws Exception {
+    return new ConstantCallSite(caller.findStatic(caller.lookupClass(), name, type));
+  }
+
+  // Constructor to target.
+  public InvalidBootstrapMethodHandleTestClass() {}
+
+  // Called by the valid virtual invoke.
+  public static void foo() {
+    System.out.println("Called foo!");
+  }
+
+  public static void main(String[] args) {
+    try {
+      // Rewritten to invoke-dynamic for each handle type.
+      foo();
+    } catch (BootstrapMethodError e) {
+      System.out.println(e.getCause().getMessage());
+      throw e;
+    }
+  }
+}