blob: 18c34364a659576d812076c216b5ef5b514929cc [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;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static com.google.common.io.ByteStreams.toByteArray;
import static org.junit.Assert.assertEquals;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.ASMifier;
import org.objectweb.asm.util.TraceClassVisitor;
@RunWith(Parameterized.class)
public class CfFrontendExamplesTest extends TestBase {
static final Collection<Object[]> TESTS = Arrays.asList(
makeTest("arithmetic.Arithmetic"),
makeTest("arrayaccess.ArrayAccess"),
makeTest("barray.BArray"),
makeTest("bridge.BridgeMethod"),
makeTest("cse.CommonSubexpressionElimination"),
makeTest("constants.Constants"),
makeTest("controlflow.ControlFlow"),
makeTest("conversions.Conversions"),
makeTest("floating_point_annotations.FloatingPointValuedAnnotationTest"),
makeTest("filledarray.FilledArray"),
makeTest("hello.Hello"),
makeTest("ifstatements.IfStatements"),
makeTest("instancevariable.InstanceVariable"),
makeTest("instanceofstring.InstanceofString"),
makeTest("invoke.Invoke"),
makeTest("jumbostring.JumboString"),
makeTest("loadconst.LoadConst"),
makeTest("loop.UdpServer"),
makeTest("newarray.NewArray"),
makeTest("regalloc.RegAlloc"),
makeTest("returns.Returns"),
makeTest("staticfield.StaticField"),
makeTest("stringbuilding.StringBuilding"),
makeTest("switches.Switches"),
makeTest("sync.Sync"),
makeTest("throwing.Throwing"),
makeTest("trivial.Trivial"),
makeTest("trycatch.TryCatch"),
makeTest("nestedtrycatches.NestedTryCatches"),
makeTest("trycatchmany.TryCatchMany"),
makeTest("invokeempty.InvokeEmpty"),
makeTest("regress.Regress"),
makeTest("regress2.Regress2"),
makeTest("regress_37726195.Regress"),
makeTest("regress_37658666.Regress", CfFrontendExamplesTest::compareRegress37658666),
makeTest("regress_37875803.Regress"),
makeTest("regress_37955340.Regress"),
makeTest("regress_62300145.Regress"),
makeTest("regress_64881691.Regress"),
makeTest("regress_65104300.Regress"),
makeTest("regress_70703087.Test"),
makeTest("regress_70736958.Test"),
makeTest("regress_70737019.Test"),
makeTest("regress_72361252.Test"),
makeTest("memberrebinding2.Memberrebinding"),
makeTest("memberrebinding3.Memberrebinding"),
makeTest("minification.Minification"),
makeTest("enclosingmethod.Main"),
makeTest("enclosingmethod_proguarded.Main"),
makeTest("interfaceinlining.Main"),
makeTest("switchmaps.Switches")
);
private static Object[] makeTest(String className) {
return makeTest(className, null);
}
private static Object[] makeTest(String className, BiConsumer<byte[], byte[]> comparator) {
return new Object[] {className, comparator};
}
@Parameters(name = "{0}")
public static Collection<Object[]> data() {
return TESTS;
}
private static void compareRegress37658666(byte[] expectedBytes, byte[] actualBytes) {
// javac emits LDC(-0.0f) instead of the shorter FCONST_0 FNEG emitted by CfConstNumber.
String ldc = "mv.visitLdcInsn(new Float(\"-0.0\"));";
String constNeg = "mv.visitInsn(FCONST_0);\nmv.visitInsn(FNEG);";
assertEquals(
asmToString(expectedBytes).replace(ldc, constNeg),
asmToString(actualBytes));
}
private final Path inputJar;
private final BiConsumer<byte[], byte[]> comparator;
public CfFrontendExamplesTest(String clazz, BiConsumer<byte[], byte[]> comparator) {
this.comparator = comparator;
String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
String suffix = "_debuginfo_all";
inputJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + suffix + JAR_EXTENSION);
}
@Test
public void test() throws Exception {
Path outputJar = temp.getRoot().toPath().resolve("output.jar");
R8Command command =
R8Command.builder()
.addProgramFiles(inputJar)
.setMode(CompilationMode.DEBUG)
.setOutput(outputJar, OutputMode.ClassFile)
.build();
ToolHelper.runR8(
command,
options -> {
options.skipIR = true;
options.enableCfFrontend = true;
});
ArchiveClassFileProvider expected = new ArchiveClassFileProvider(inputJar);
ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
for (String descriptor : expected.getClassDescriptors()) {
byte[] expectedBytes = getClassAsBytes(expected, descriptor);
byte[] actualBytes = getClassAsBytes(actual, descriptor);
if (comparator != null) {
comparator.accept(expectedBytes, actualBytes);
} else if (!Arrays.equals(expectedBytes, actualBytes)) {
assertEquals(
"Class " + descriptor + " differs",
asmToString(expectedBytes),
asmToString(actualBytes));
}
}
}
private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
Collections.sort(descriptorList);
return descriptorList;
}
private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
throws Exception {
return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
}
private static String asmToString(byte[] clazz) {
StringWriter stringWriter = new StringWriter();
printAsm(new PrintWriter(stringWriter), clazz);
return stringWriter.toString();
}
private static void printAsm(PrintWriter pw, byte[] clazz) {
new ClassReader(clazz).accept(new TraceClassVisitor(null, new ASMifierSorted(), pw), 0);
}
/** Sort methods and fields in the output of ASMifier to make diffing possible. */
private static class ASMifierSorted extends ASMifier {
private static class Part implements Comparable<Part> {
private final String key;
private final int start;
private final int end;
Part(String key, int start, int end) {
this.key = key;
this.start = start;
this.end = end;
}
@Override
public int compareTo(Part part) {
int i = key.compareTo(part.key);
return i != 0 ? i : Integer.compare(start, part.start);
}
}
private final List<Part> parts = new ArrayList<>();
ASMifierSorted() {
super(Opcodes.ASM6, "cw", 0);
}
@Override
public ASMifier visitField(
int access, String name, String desc, String signature, Object value) {
init();
int i = text.size();
ASMifier res = super.visitField(access, name, desc, signature, value);
parts.add(new Part((String) text.get(i), i, text.size()));
return res;
}
@Override
public ASMifier visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
init();
int i = text.size();
ASMifier res = super.visitMethod(access, name, desc, signature, exceptions);
parts.add(new Part((String) text.get(i), i, text.size()));
return res;
}
private void init() {
if (parts.isEmpty()) {
parts.add(new Part("", 0, text.size()));
}
}
@Override
public void print(PrintWriter pw) {
init();
int end = parts.get(parts.size() - 1).end;
Collections.sort(parts);
parts.add(new Part("", end, text.size()));
ArrayList<Object> tmp = new ArrayList<>(text);
text.clear();
for (Part part : parts) {
for (int i = part.start; i < part.end; i++) {
text.add(tmp.get(i));
}
}
super.print(pw);
}
}
}