| // 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); | |
| } | |
| } | |
| } |