| // Copyright (c) 2018, 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; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| import com.android.tools.r8.ClassFileConsumer; |
| import com.android.tools.r8.CompilationMode; |
| import com.android.tools.r8.DexIndexedConsumer; |
| import com.android.tools.r8.ProgramConsumer; |
| import com.android.tools.r8.R8; |
| import com.android.tools.r8.R8Command; |
| import com.android.tools.r8.R8Command.Builder; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.ToolHelper.DexVm; |
| import com.android.tools.r8.ToolHelper.ProcessResult; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import java.nio.file.Path; |
| import java.util.Arrays; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| |
| public class MethodHandleTestRunner { |
| static final Class<?> CLASS = MethodHandleTest.class; |
| |
| private boolean ldc = false; |
| private boolean minify = false; |
| |
| @Rule |
| public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); |
| |
| @Test |
| public void testMethodHandlesLookup() throws Exception { |
| // Run test with dynamic method lookups, i.e. using MethodHandles.lookup().find*() |
| ldc = false; |
| test(); |
| } |
| |
| @Test |
| public void testLdcMethodHandle() throws Exception { |
| // Run test with LDC methods, i.e. without java.lang.invoke.MethodHandles |
| ldc = true; |
| test(); |
| } |
| |
| @Test |
| public void testMinify() throws Exception { |
| // Run test with LDC methods, i.e. without java.lang.invoke.MethodHandles |
| ldc = true; |
| ProcessResult runInput = runInput(); |
| assertEquals(0, runInput.exitCode); |
| Path outCf = temp.getRoot().toPath().resolve("cf.jar"); |
| build(new ClassFileConsumer.ArchiveConsumer(outCf), true); |
| ProcessResult runCf = |
| ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception"); |
| assertEquals(runInput.toString(), runCf.toString()); |
| } |
| |
| private final Class[] inputClasses = { |
| MethodHandleTest.class, |
| MethodHandleTest.C.class, |
| MethodHandleTest.I.class, |
| MethodHandleTest.Impl.class, |
| MethodHandleTest.D.class, |
| MethodHandleTest.E.class, |
| MethodHandleTest.F.class, |
| }; |
| |
| private void test() throws Exception { |
| ProcessResult runInput = runInput(); |
| Path outCf = temp.getRoot().toPath().resolve("cf.jar"); |
| build(new ClassFileConsumer.ArchiveConsumer(outCf), false); |
| Path outDex = temp.getRoot().toPath().resolve("dex.zip"); |
| build(new DexIndexedConsumer.ArchiveConsumer(outDex), false); |
| |
| ProcessResult runCf = |
| ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception"); |
| assertEquals(runInput.toString(), runCf.toString()); |
| // TODO(mathiasr): Once we include a P runtime, change this to "P and above". |
| if (ToolHelper.getDexVm() != DexVm.ART_DEFAULT) { |
| return; |
| } |
| ProcessResult runDex = |
| ToolHelper.runArtRaw( |
| outDex.toString(), |
| CLASS.getCanonicalName(), |
| cmd -> cmd.appendProgramArgument(ldc ? "pass" : "exception")); |
| // Only compare stdout and exitCode since dex2oat prints to stderr. |
| if (runInput.exitCode != runDex.exitCode) { |
| System.out.println(runDex.stderr); |
| } |
| assertEquals(runInput.stdout, runDex.stdout); |
| assertEquals(runInput.exitCode, runDex.exitCode); |
| } |
| |
| private void build(ProgramConsumer programConsumer, boolean minify) throws Exception { |
| // MethodHandle.invoke() only supported from Android O |
| // ConstMethodHandle only supported from Android P |
| AndroidApiLevel apiLevel = AndroidApiLevel.P; |
| Builder cfBuilder = |
| R8Command.builder() |
| .setMode(CompilationMode.DEBUG) |
| .addLibraryFiles(ToolHelper.getAndroidJar(apiLevel)) |
| .setProgramConsumer(programConsumer); |
| if (!(programConsumer instanceof ClassFileConsumer)) { |
| cfBuilder.setMinApiLevel(apiLevel.getLevel()); |
| } |
| for (Class<?> c : inputClasses) { |
| byte[] classAsBytes = getClassAsBytes(c); |
| cfBuilder.addClassProgramData(classAsBytes, Origin.unknown()); |
| } |
| if (minify) { |
| cfBuilder.addProguardConfiguration( |
| Arrays.asList( |
| "-keep public class com.android.tools.r8.cf.MethodHandleTest {", |
| " public static void main(...);", |
| "}"), |
| Origin.unknown()); |
| } |
| R8.run(cfBuilder.build()); |
| } |
| |
| private ProcessResult runInput() throws Exception { |
| Path out = temp.getRoot().toPath().resolve("input.jar"); |
| ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out); |
| for (Class<?> c : inputClasses) { |
| archiveConsumer.accept( |
| getClassAsBytes(c), DescriptorUtils.javaTypeToDescriptor(c.getName()), null); |
| } |
| archiveConsumer.finished(null); |
| ProcessResult runInput = ToolHelper.runJava(out, CLASS.getName(), ldc ? "error" : "exception"); |
| if (runInput.exitCode != 0) { |
| System.out.println(runInput); |
| } |
| assertEquals(0, runInput.exitCode); |
| return runInput; |
| } |
| |
| private byte[] getClassAsBytes(Class<?> clazz) throws Exception { |
| if (ldc) { |
| if (clazz == MethodHandleTest.D.class) { |
| return MethodHandleDump.dumpD(); |
| } else if (clazz == MethodHandleTest.class) { |
| return MethodHandleDump.transform(ToolHelper.getClassAsBytes(clazz)); |
| } |
| } |
| return ToolHelper.getClassAsBytes(clazz); |
| } |
| } |