| // 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 com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.google.common.collect.ImmutableList; |
| 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.Collections; |
| import java.util.List; |
| import java.util.function.BiConsumer; |
| import org.junit.Test; |
| import org.objectweb.asm.ClassReader; |
| import org.objectweb.asm.util.ASMifier; |
| import org.objectweb.asm.util.TraceClassVisitor; |
| |
| public class CfFrontendExamplesTest extends TestBase { |
| |
| @Test |
| public void testArithmetic() throws Exception { |
| runTest("arithmetic.Arithmetic"); |
| } |
| |
| @Test |
| public void testArrayAccess() throws Exception { |
| runTest("arrayaccess.ArrayAccess"); |
| } |
| |
| @Test |
| public void testBArray() throws Exception { |
| runTest("barray.BArray"); |
| } |
| |
| @Test |
| public void testBridgeMethod() throws Exception { |
| runTest("bridge.BridgeMethod"); |
| } |
| |
| @Test |
| public void testCommonSubexpressionElimination() throws Exception { |
| runTest("cse.CommonSubexpressionElimination"); |
| } |
| |
| @Test |
| public void testConstants() throws Exception { |
| runTest("constants.Constants"); |
| } |
| |
| @Test |
| public void testControlFlow() throws Exception { |
| runTest("controlflow.ControlFlow"); |
| } |
| |
| @Test |
| public void testConversions() throws Exception { |
| runTest("conversions.Conversions"); |
| } |
| |
| @Test |
| public void testFloatingPointValuedAnnotation() throws Exception { |
| runTest("floating_point_annotations.FloatingPointValuedAnnotationTest"); |
| } |
| |
| @Test |
| public void testFilledArray() throws Exception { |
| runTest("filledarray.FilledArray"); |
| } |
| |
| @Test |
| public void testHello() throws Exception { |
| runTest("hello.Hello"); |
| } |
| |
| @Test |
| public void testIfStatements() throws Exception { |
| runTest("ifstatements.IfStatements"); |
| } |
| |
| @Test |
| public void testInlining() throws Exception { |
| runTest("inlining.Inlining"); |
| } |
| |
| @Test |
| public void testInstanceVariable() throws Exception { |
| runTest("instancevariable.InstanceVariable"); |
| } |
| |
| @Test |
| public void testInstanceofString() throws Exception { |
| runTest("instanceofstring.InstanceofString"); |
| } |
| |
| @Test |
| public void testInvoke() throws Exception { |
| runTest("invoke.Invoke"); |
| } |
| |
| @Test |
| public void testJumboString() throws Exception { |
| runTest("jumbostring.JumboString"); |
| } |
| |
| @Test |
| public void testLoadConst() throws Exception { |
| runTest("loadconst.LoadConst"); |
| } |
| |
| @Test |
| public void testUdpServer() throws Exception { |
| runTest("loop.UdpServer"); |
| } |
| |
| @Test |
| public void testRegAlloc() throws Exception { |
| runTest("regalloc.RegAlloc"); |
| } |
| |
| @Test |
| public void testReturns() throws Exception { |
| runTest("returns.Returns"); |
| } |
| |
| @Test |
| public void testStaticField() throws Exception { |
| runTest("staticfield.StaticField"); |
| } |
| |
| @Test |
| public void testStringBuilding() throws Exception { |
| runTest("stringbuilding.StringBuilding"); |
| } |
| |
| @Test |
| public void testSwitches() throws Exception { |
| runTest("switches.Switches"); |
| } |
| |
| @Test |
| public void testSync() throws Exception { |
| runTest("sync.Sync"); |
| } |
| |
| @Test |
| public void testThrowing() throws Exception { |
| runTest("throwing.Throwing"); |
| } |
| |
| @Test |
| public void testTrivial() throws Exception { |
| runTest("trivial.Trivial"); |
| } |
| |
| @Test |
| public void testTryCatch() throws Exception { |
| runTest("trycatch.TryCatch"); |
| } |
| |
| @Test |
| public void testNestedTryCatches() throws Exception { |
| runTest("nestedtrycatches.NestedTryCatches"); |
| } |
| |
| @Test |
| public void testTryCatchMany() throws Exception { |
| runTest("trycatchmany.TryCatchMany"); |
| } |
| |
| @Test |
| public void testInvokeEmpty() throws Exception { |
| runTest("invokeempty.InvokeEmpty"); |
| } |
| |
| @Test |
| public void testRegress() throws Exception { |
| runTest("regress.Regress"); |
| } |
| |
| @Test |
| public void testRegress2() throws Exception { |
| runTest("regress2.Regress2"); |
| } |
| |
| @Test |
| public void testRegress37726195() throws Exception { |
| runTest("regress_37726195.Regress"); |
| } |
| |
| @Test |
| public void testRegress37658666() throws Exception { |
| runTest( |
| "regress_37658666.Regress", |
| (expected, actual) -> { |
| // javac emits LDC(-0.0f) instead of the shorter FCONST_0 FNEG emitted by CfConstNumber. |
| String ldc = "methodVisitor.visitLdcInsn(new Float(\"-0.0\"));"; |
| String constNeg = "methodVisitor.visitInsn(FCONST_0);\nmethodVisitor.visitInsn(FNEG);"; |
| assertEquals(expected.replace(ldc, constNeg), actual); |
| }); |
| } |
| |
| @Test |
| public void testRegress37875803() throws Exception { |
| runTest("regress_37875803.Regress"); |
| } |
| |
| @Test |
| public void testRegress37955340() throws Exception { |
| runTest("regress_37955340.Regress"); |
| } |
| |
| @Test |
| public void testRegress62300145() throws Exception { |
| runTest("regress_62300145.Regress"); |
| } |
| |
| @Test |
| public void testRegress64881691() throws Exception { |
| runTest("regress_64881691.Regress"); |
| } |
| |
| @Test |
| public void testRegress65104300() throws Exception { |
| runTest("regress_65104300.Regress"); |
| } |
| |
| @Test |
| public void testRegress70703087() throws Exception { |
| runTest("regress_70703087.Test"); |
| } |
| |
| @Test |
| public void testRegress70736958() throws Exception { |
| runTest("regress_70736958.Test"); |
| } |
| |
| @Test |
| public void testRegress70737019() throws Exception { |
| runTest("regress_70737019.Test"); |
| } |
| |
| @Test |
| public void testRegress72361252() throws Exception { |
| runTest("regress_72361252.Test"); |
| } |
| |
| @Test |
| public void testMemberrebinding2() throws Exception { |
| runTest("memberrebinding2.Memberrebinding"); |
| } |
| |
| @Test |
| public void testMemberrebinding3() throws Exception { |
| runTest("memberrebinding3.Memberrebinding"); |
| } |
| |
| @Test |
| public void testMinification() throws Exception { |
| runTest("minification.Minification"); |
| } |
| |
| @Test |
| public void testEnclosingmethod() throws Exception { |
| runTest("enclosingmethod.Main"); |
| } |
| |
| @Test |
| public void testEnclosingmethodProguarded() throws Exception { |
| runTest("enclosingmethod_proguarded.Main"); |
| } |
| |
| @Test |
| public void testInterfaceInlining() throws Exception { |
| runTest("interfaceinlining.Main"); |
| } |
| |
| @Test |
| public void testSwitchmaps() throws Exception { |
| runTest("switchmaps.Switches"); |
| } |
| |
| private void runTest(String clazz) throws Exception { |
| runTest( |
| clazz, (expected, actual) -> assertEquals("Class " + clazz + " differs", expected, actual)); |
| } |
| |
| private void runTest(String clazz, BiConsumer<String, String> comparator) throws Exception { |
| assert comparator != null; |
| String pkg = clazz.substring(0, clazz.lastIndexOf('.')); |
| String suffix = "_debuginfo_all"; |
| Path inputJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + suffix + JAR_EXTENSION); |
| Path outputJar = temp.getRoot().toPath().resolve("output.jar"); |
| R8Command command = |
| R8Command.builder() |
| .addProgramFiles(inputJar) |
| .addLibraryFiles(ToolHelper.getJava8RuntimeJar()) |
| .setMode(CompilationMode.DEBUG) |
| .setDisableTreeShaking(true) |
| .setDisableMinification(true) |
| .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()) |
| .setOutput(outputJar, OutputMode.ClassFile) |
| .build(); |
| ToolHelper.runR8( |
| command, |
| options -> { |
| options.skipIR = true; |
| options.testing.readInputStackMaps = 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 (!Arrays.equals(expectedBytes, actualBytes)) { |
| String expectedString = replaceCatchThrowableByCatchAll(asmToString(expectedBytes)); |
| String actualString = asmToString(actualBytes); |
| comparator.accept(expectedString, actualString); |
| } |
| } |
| } |
| |
| private static String replaceCatchThrowableByCatchAll(String content) { |
| String catchThrowablePrefix = "methodVisitor.visitTryCatchBlock("; |
| String catchThrowableSuffix = ", \"java/lang/Throwable\");"; |
| StringBuilder expected = new StringBuilder(); |
| List<String> expectedLines = StringUtils.splitLines(content); |
| for (String line : expectedLines) { |
| if (line.startsWith(catchThrowablePrefix) && line.endsWith(catchThrowableSuffix)) { |
| expected.append(line.replace("\"java/lang/Throwable\"", "null")); |
| } else { |
| expected.append(line); |
| } |
| expected.append('\n'); |
| } |
| return expected.toString(); |
| } |
| |
| 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(InternalOptions.ASM_VERSION, "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); |
| } |
| } |
| } |