blob: 3062ae871bb1650f35059d5d7df74c4bdb092631 [file] [log] [blame]
// 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 static org.junit.Assert.assertTrue;
import com.android.tools.r8.ByteDataView;
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.R8Command;
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase;
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.cf.MethodHandleTest.C;
import com.android.tools.r8.cf.MethodHandleTest.I;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
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 MethodHandleTestRunner extends TestBase {
static final Class<?> CLASS = MethodHandleTest.class;
enum LookupType {
DYNAMIC,
CONSTANT,
}
enum MinifyMode {
NONE,
MINIFY,
}
private CompilationMode compilationMode;
private LookupType lookupType;
private ProcessResult runInput;
private MinifyMode minifyMode;
@Parameters(name = "{0}_{1}_{2}")
public static List<String[]> data() {
List<String[]> res = new ArrayList<>();
for (LookupType lookupType : LookupType.values()) {
for (MinifyMode minifyMode : MinifyMode.values()) {
if (lookupType == LookupType.DYNAMIC && minifyMode == MinifyMode.MINIFY) {
// Skip because we don't keep the members looked up dynamically.
continue;
}
for (CompilationMode compilationMode : CompilationMode.values()) {
res.add(new String[] {lookupType.name(), minifyMode.name(), compilationMode.name()});
}
}
}
return res;
}
public MethodHandleTestRunner(String lookupType, String minifyMode, String compilationMode) {
this.lookupType = LookupType.valueOf(lookupType);
this.minifyMode = MinifyMode.valueOf(minifyMode);
this.compilationMode = CompilationMode.valueOf(compilationMode);
}
@Test
public void test() throws Exception {
runInput();
runCf();
// TODO(mathiasr): Once we include a P runtime, change this to "P and above".
if (ToolHelper.getDexVm() == DexVm.ART_DEFAULT && ToolHelper.artSupported()) {
runDex();
}
}
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 runInput() throws Exception {
Path out = temp.getRoot().toPath().resolve("input.jar");
ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
for (Class<?> c : inputClasses) {
archiveConsumer.accept(
ByteDataView.of(getClassAsBytes(c)),
DescriptorUtils.javaTypeToDescriptor(c.getName()),
null);
}
archiveConsumer.finished(null);
String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
runInput = ToolHelper.runJava(out, CLASS.getName(), expected);
if (runInput.exitCode != 0) {
System.out.println(runInput);
}
assertEquals(0, runInput.exitCode);
}
private void runCf() throws Exception {
Path outCf = temp.getRoot().toPath().resolve("cf.jar");
build(new ClassFileConsumer.ArchiveConsumer(outCf));
String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName(), expected);
assertEquals(runCf.stderr, 0, runCf.exitCode);
assertEquals(runInput.toString(), runCf.toString());
// Ensure that we did not inline the const method handle
ensureConstHandleNotInlined(outCf);
}
private void ensureConstHandleNotInlined(Path file) throws IOException, ExecutionException {
CodeInspector inspector = new CodeInspector(file);
MethodSubject subject = inspector.clazz(MethodHandleTest.D.class).method(
"java.lang.MethodHandle", "vcviSpecialMethod");
assertTrue(inspector.clazz(MethodHandleTest.D.class)
.method("java.lang.invoke.MethodHandle", "vcviSpecialMethod").isPresent());
}
private void runDex() throws Exception {
Path outDex = temp.getRoot().toPath().resolve("dex.zip");
build(new DexIndexedConsumer.ArchiveConsumer(outDex));
String expected = lookupType == LookupType.CONSTANT ? "pass" : "exception";
ProcessResult runDex =
ToolHelper.runArtRaw(
outDex.toString(),
CLASS.getCanonicalName(),
cmd -> cmd.appendProgramArgument(expected));
// Only compare stdout and exitCode since dex2oat prints to stderr.
if (runInput.exitCode != runDex.exitCode) {
System.out.println(runDex.stderr);
}
assertEquals(runInput.exitCode, runDex.exitCode);
assertEquals(runInput.stdout, runDex.stdout);
}
private void build(ProgramConsumer programConsumer) throws Exception {
// MethodHandle.invoke() only supported from Android O
// ConstMethodHandle only supported from Android P
Builder builder =
R8Command.builder()
.setMode(compilationMode)
.setProgramConsumer(programConsumer)
.setDisableTreeShaking(true)
.setDisableMinification(true);
if (programConsumer instanceof ClassFileConsumer) {
builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
} else {
AndroidApiLevel apiLevel = AndroidApiLevel.P;
builder
.setMinApiLevel(apiLevel.getLevel())
.addLibraryFiles(ToolHelper.getAndroidJar(apiLevel));
}
for (Class<?> c : inputClasses) {
byte[] classAsBytes = getClassAsBytes(c);
builder.addClassProgramData(classAsBytes, Origin.unknown());
}
if (minifyMode == MinifyMode.MINIFY) {
ToolHelper.allowTestProguardOptions(builder);
builder.addProguardConfiguration(
Arrays.asList(
keepMainProguardConfiguration(MethodHandleTest.class),
neverMergeRule(),
// Prevent the second argument of C.svic(), C.sjic(), I.sjic() and I.svic() from
// being removed although they are never used unused. This is needed since these
// methods are accessed reflectively.
"-keep,allowobfuscation public class " + C.class.getTypeName() + " {",
" static void svic(int, char);",
" static long sjic(int, char);",
"}",
"-keep,allowobfuscation public interface " + I.class.getTypeName() + " {",
" static long sjic(int, char);",
" static void svic(int, char);",
"}"),
Origin.unknown());
}
ToolHelper.runR8(builder.build());
}
private byte[] getClassAsBytes(Class<?> clazz) throws Exception {
if (lookupType == LookupType.CONSTANT) {
if (clazz == MethodHandleTest.D.class) {
return MethodHandleDump.dumpD();
} else if (clazz == MethodHandleTest.class) {
return MethodHandleDump.transform(ToolHelper.getClassAsBytes(clazz));
}
}
return ToolHelper.getClassAsBytes(clazz);
}
}