|  | // 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.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) | 
|  | .transformMethodInsnInMethod( | 
|  | "main", | 
|  | (opcode, owner, name, descriptor, isInterface, visitor) -> { | 
|  | if (opcode == Opcodes.INVOKESTATIC && name.equals("foo")) { | 
|  | visitor.visitInvokeDynamicInsn("foo", "()V", getHandle()); | 
|  | } else { | 
|  | visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); | 
|  | } | 
|  | }) | 
|  | .transform(); | 
|  | } | 
|  | } |