|  | // 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; | 
|  | } | 
|  |  | 
|  | public static byte[] getClassAsBytes(ClassFileResourceProvider inputJar, String descriptor) | 
|  | throws Exception { | 
|  | return toByteArray(inputJar.getProgramResource(descriptor).getByteStream()); | 
|  | } | 
|  |  | 
|  | public static String asmToString(byte[] clazz) { | 
|  | StringWriter stringWriter = new StringWriter(); | 
|  | printAsm(new PrintWriter(stringWriter), clazz); | 
|  | return stringWriter.toString(); | 
|  | } | 
|  |  | 
|  | public 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); | 
|  | } | 
|  | } | 
|  | } |