Run TreeShakingTest on the CF backend

* Implement hasNonTrivialClassInitializer() for JarCode+CfCode

* Make SourceFileRewriter a no-op for CfCode

* Implement DexEncodedMethod.toEmptyThrowingMethodCf() for CF backend
  - Requires an implementation of CfCode.buildIR() to handle this
    specific pattern

Change-Id: I78d4965c53c3317d0e6235b672b2dee00d075f04
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index a14811a..61cd5a7 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,17 +5,28 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Throw;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -155,8 +166,35 @@
   }
 
   @Override
+  public boolean isEmptyVoidMethod() {
+    for (CfInstruction insn : instructions) {
+      if (!(insn instanceof CfReturnVoid)
+          && !(insn instanceof CfLabel)
+          && !(insn instanceof CfPosition)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
+    if (instructions.size() == 2
+        && instructions.get(0) instanceof CfConstNull
+        && instructions.get(1) instanceof CfThrow) {
+      BasicBlock block = new BasicBlock();
+      block.setNumber(1);
+      Value nullValue = new Value(0, ValueType.OBJECT, null);
+      block.add(new ConstNumber(nullValue, 0L));
+      block.add(new Throw(nullValue));
+      block.close(null);
+      for (Instruction insn : block.getInstructions()) {
+        insn.setPosition(Position.none());
+      }
+      LinkedList<BasicBlock> blocks = new LinkedList<>(Collections.singleton(block));
+      return new IRCode(options, encodedMethod, blocks, null, false);
+    }
     throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 35a6381..52217a3 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -81,4 +81,6 @@
   void collectMixedSectionItems(MixedSectionCollection collection) {
     throw new Unreachable();
   }
+
+  public abstract boolean isEmptyVoidMethod();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 58053e2..e9b9255 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -339,9 +339,7 @@
       return superType == null;
     }
     DexEncodedMethod clinit = getClassInitializer();
-    return clinit != null
-        && clinit.getCode() != null
-        && clinit.getCode().asDexCode().isEmptyVoidMethod();
+    return clinit != null && clinit.getCode() != null && clinit.getCode().isEmptyVoidMethod();
   }
 
   public boolean hasNonTrivialClassInitializer() {
@@ -353,11 +351,7 @@
     if (clinit == null || clinit.getCode() == null) {
       return false;
     }
-    if (clinit.getCode().isDexCode()) {
-      return !clinit.getCode().asDexCode().isEmptyVoidMethod();
-    }
-    // For non-dex code we don't try to check the code.
-    return true;
+    return !clinit.getCode().isEmptyVoidMethod();
   }
 
   public boolean hasDefaultInitializer() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 7d2ac23..50117c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -157,6 +157,7 @@
     return false;
   }
 
+  @Override
   public boolean isEmptyVoidMethod() {
     return instructions.length == 1 && instructions[0] instanceof ReturnVoid;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 0a357bf..e18bf8a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -10,6 +10,9 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
 
 import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.code.Const;
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.ConstStringJumbo;
@@ -40,6 +43,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
@@ -365,12 +369,28 @@
         outRegisters, instructions, new DexCode.Try[0], new DexCode.TryHandler[0], null, null);
   }
 
-  public DexEncodedMethod toEmptyThrowingMethod() {
-    Instruction insn[] = {new Const(0, 0), new Throw(0)};
-    DexCode code = generateCodeFromTemplate(1, 0, insn);
-    assert !accessFlags.isAbstract();
+  public DexEncodedMethod toEmptyThrowingMethodDex() {
+    assert !accessFlags.isAbstract() && !accessFlags.isNative();
     Builder builder = builder(this);
-    builder.setCode(code);
+    Instruction insn[] = {new Const(0, 0), new Throw(0)};
+    DexCode emptyThrowingCode = generateCodeFromTemplate(1, 0, insn);
+    builder.setCode(emptyThrowingCode);
+    return builder.build();
+  }
+
+  public DexEncodedMethod toEmptyThrowingMethodCf() {
+    assert !accessFlags.isAbstract() && !accessFlags.isNative();
+    Builder builder = builder(this);
+    CfInstruction insn[] = {new CfConstNull(), new CfThrow()};
+    CfCode emptyThrowingCode =
+        new CfCode(
+            method,
+            1,
+            method.proto.parameters.size() + 1,
+            Arrays.asList(insn),
+            Collections.emptyList(),
+            Collections.emptyList());
+    builder.setCode(emptyThrowingCode);
     return builder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index aa1faaf..8c1a6c5 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -18,12 +18,16 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.commons.JSRInlinerAdapter;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.LabelNode;
+import org.objectweb.asm.tree.LineNumberNode;
 import org.objectweb.asm.tree.MethodNode;
 import org.objectweb.asm.util.Textifier;
 import org.objectweb.asm.util.TraceMethodVisitor;
@@ -98,6 +102,19 @@
   }
 
   @Override
+  public boolean isEmptyVoidMethod() {
+    for (Iterator<AbstractInsnNode> it = getNode().instructions.iterator(); it.hasNext(); ) {
+      AbstractInsnNode insn = it.next();
+      if (insn.getType() != Opcodes.RETURN
+          && !(insn instanceof LabelNode)
+          && !(insn instanceof LineNumberNode)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
     triggerDelayedParsingIfNeccessary();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 41e4afb..dba6183 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -996,6 +996,11 @@
     }
 
     @Override
+    public boolean isEmptyVoidMethod() {
+      return false;
+    }
+
+    @Override
     public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
         throws ApiLevelException {
       OutlineSourceCode source = new OutlineSourceCode(outline);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index e90b135..53bc57e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -32,6 +32,11 @@
   }
 
   @Override
+  public boolean isEmptyVoidMethod() {
+    return false;
+  }
+
+  @Override
   public final IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
     return new IRBuilder(encodedMethod, sourceCode, options).build();
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 3736008..1dd6e31 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -50,16 +50,20 @@
         if (code == null) {
           return;
         }
-        assert code.isDexCode();
-        DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
-        if (dexDebugInfo == null) {
-          return;
+        if (code.isDexCode()) {
+          DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
+          if (dexDebugInfo == null) {
+            return;
+          }
+          // Thanks to a single global source file, we can safely remove DBG_SET_FILE entirely.
+          dexDebugInfo.events =
+              Arrays.stream(dexDebugInfo.events)
+                  .filter(dexDebugEvent -> !(dexDebugEvent instanceof SetFile))
+                  .toArray(DexDebugEvent[]::new);
+        } else {
+          assert code.isCfCode();
+          // CF has nothing equivalent to SetFile, so there is nothing to remove.
         }
-        // Thanks to a single global source file, we can safely remove DBG_SET_FILE entirely.
-        dexDebugInfo.events =
-            Arrays.stream(dexDebugInfo.events)
-                .filter(dexDebugEvent -> !(dexDebugEvent instanceof SetFile))
-                .toArray(DexDebugEvent[]::new);
       });
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 27a6405..962247e 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -151,9 +151,12 @@
         // this can only happen as the result of an invalid invoke. They will not actually be
         // called at runtime but we have to keep them as non-abstract (see above) to produce the
         // same failure mode.
-        reachableMethods.add(allowAbstract
-            ? method.toAbstractMethod()
-            : method.toEmptyThrowingMethod());
+        reachableMethods.add(
+            allowAbstract
+                ? method.toAbstractMethod()
+                : (options.isGeneratingClassFiles()
+                    ? method.toEmptyThrowingMethodCf()
+                    : method.toEmptyThrowingMethodDex()));
       } else {
         if (Log.ENABLED) {
           Log.debug(getClass(), "Removing method %s.", method.method);
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index fe1f1eb..ae2a432 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -57,8 +59,7 @@
 public class TreeShakingTest {
 
   private static final Path ANDROID_JAR = ToolHelper.getDefaultAndroidJar();
-  private static final List<Path> JAR_LIBRARIES = ImmutableList
-      .of(ANDROID_JAR, Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
+  private final List<Path> JAR_LIBRARIES;
   private static final String EMPTY_FLAGS = "src/test/proguard/valid/empty.flags";
   private static final Set<String> IGNORED_FLAGS = ImmutableSet.of(
       "examples/minification:conflict-mapping.txt",
@@ -66,33 +67,35 @@
   );
   private static final Set<String> IGNORED = ImmutableSet.of(
       // there's no point in running those without obfuscation
-      "examples/shaking1:keep-rules-repackaging.txt:DEX:NONE",
-      "examples/shaking1:keep-rules-repackaging.txt:JAR:NONE",
-      "examples/shaking16:keep-rules-1.txt:DEX:NONE",
-      "examples/shaking16:keep-rules-1.txt:JAR:NONE",
-      "examples/shaking16:keep-rules-2.txt:DEX:NONE",
-      "examples/shaking16:keep-rules-2.txt:JAR:NONE",
-      "examples/shaking15:keep-rules.txt:DEX:NONE",
-      "examples/shaking15:keep-rules.txt:JAR:NONE",
-      "examples/minifygeneric:keep-rules.txt:DEX:NONE",
-      "examples/minifygeneric:keep-rules.txt:JAR:NONE",
-      "examples/minifygenericwithinner:keep-rules.txt:DEX:NONE",
-      "examples/minifygenericwithinner:keep-rules.txt:JAR:NONE",
+      "examples/shaking1:keep-rules-repackaging.txt:*:NONE",
+      "examples/shaking16:keep-rules-1.txt:*:NONE",
+      "examples/shaking16:keep-rules-2.txt:*:NONE",
+      "examples/shaking15:keep-rules.txt:*:NONE",
+      "examples/minifygeneric:keep-rules.txt:*:NONE",
+      "examples/minifygenericwithinner:keep-rules.txt:*:NONE",
       // No prebuild DEX files for AndroidN
-      "examplesAndroidN/shaking:keep-rules.txt:DEX:NONE",
-      "examplesAndroidN/shaking:keep-rules.txt:DEX:JAVA",
-      "examplesAndroidN/shaking:keep-rules.txt:DEX:AGGRESSIVE"
+      "examplesAndroidN/shaking:keep-rules.txt:DEX:DEX:*",
+      // TODO(b/75997473): No inlining in R8/CF yet
+      "examples/inlining:keep-rules-discard.txt:JAR:CF:*",
+      "examples/inlining:keep-rules.txt:JAR:CF:*"
   );
 
   private static Set<String> SKIPPED = Collections.emptySet();
 
   private final MinifyMode minify;
+  private Path proguardMap;
+  private Path out;
 
   private enum Frontend {
     DEX, JAR
-
   }
-  private final Frontend kind;
+
+  private enum Backend {
+    DEX, CF
+  }
+
+  private final Frontend frontend;
+  private final Backend backend;
   private final String originalDex;
   private final String programFile;
   private final String mainClass;
@@ -105,13 +108,14 @@
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
-  public TreeShakingTest(String test, Frontend kind, String mainClass, List<String> keepRulesFiles,
-      MinifyMode minify, Consumer<DexInspector> inspection,
+  public TreeShakingTest(String test, Frontend frontend, Backend backend, String mainClass,
+      List<String> keepRulesFiles, MinifyMode minify, Consumer<DexInspector> inspection,
       BiConsumer<String, String> outputComparator,
       BiConsumer<DexInspector, DexInspector> dexComparator) {
-    this.kind = kind;
+    this.frontend = frontend;
+    this.backend = backend;
     originalDex = ToolHelper.TESTS_BUILD_DIR + test + "/classes.dex";
-    if (kind == Frontend.DEX) {
+    if (frontend == Frontend.DEX) {
       this.programFile = originalDex;
     } else {
       this.programFile = ToolHelper.TESTS_BUILD_DIR + test + ".jar";
@@ -122,12 +126,23 @@
     this.minify = minify;
     this.outputComparator = outputComparator;
     this.dexComparator = dexComparator;
+    if (backend == Backend.CF) {
+      JAR_LIBRARIES =
+          ImmutableList.of(
+              Paths.get(ToolHelper.JAVA_8_RUNTIME),
+              Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
+    } else {
+      JAR_LIBRARIES =
+          ImmutableList.of(
+              ANDROID_JAR, Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
+    }
   }
 
   @Before
   public void generateTreeShakedVersion() throws Exception {
+    out = temp.getRoot().toPath().resolve("out.zip");
+    proguardMap = temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE);
     // Generate R8 processed version without library option.
-    Path out = temp.getRoot().toPath();
     boolean inline = programFile.contains("inlining");
 
     R8Command.Builder builder =
@@ -135,15 +150,24 @@
                 R8Command.builder(),
                 pgConfig -> {
                   pgConfig.setPrintMapping(true);
-                  pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+                  pgConfig.setPrintMappingFile(proguardMap);
                   pgConfig.setOverloadAggressively(minify == MinifyMode.AGGRESSIVE);
                   if (!minify.isMinify()) {
                     pgConfig.disableObfuscation();
                   }
                 })
-            .setOutput(out, OutputMode.DexIndexed)
             .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
             .addLibraryFiles(JAR_LIBRARIES);
+    switch (backend) {
+      case CF:
+        builder.setOutput(out, OutputMode.ClassFile);
+        break;
+      case DEX:
+        builder.setOutput(out, OutputMode.DexIndexed);
+        break;
+      default:
+        throw new Unreachable();
+    }
     ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(programFile));
     ToolHelper.runR8(builder.build(), options -> options.enableInlining = inline);
   }
@@ -364,6 +388,10 @@
     ClassSubject mainClass = inspector.clazz("shaking13.Shaking");
     MethodSubject testMethod = mainClass.method("void", "fieldTest", Collections.emptyList());
     Assert.assertTrue(testMethod.isPresent());
+    if (testMethod.getMethod().getCode().isJarCode()) {
+      // TODO(mathiasr): Implement iterateInstructions() for JarCode/CfCode
+      return;
+    }
     Iterator<FieldAccessInstructionSubject> iterator =
         testMethod.iterateInstructions(InstructionSubject::isFieldAccess);
     Assert.assertTrue(iterator.hasNext() && iterator.next().holder().is("shakinglib.LibraryClass"));
@@ -617,7 +645,7 @@
         clazz.field(signature.type, signature.name).isPresent());
   }
 
-  @Parameters(name = "dex: {0} frontend: {1} keep: {3} minify: {4}")
+  @Parameters(name = "dex:{0} mode:{1}-{2} keep:{4} minify:{5}")
   public static Collection<Object[]> data() {
     List<String> tests = Arrays
         .asList(
@@ -837,21 +865,38 @@
       return;
     }
     for (MinifyMode mode : MinifyMode.values()) {
-      addTestCase(testCases, test, Frontend.JAR, mainClass, keepName, keepList, mode, inspection,
-          outputComparator, dexComparator);
-      addTestCase(testCases, test, Frontend.DEX, mainClass, keepName, keepList, mode, inspection,
-          outputComparator, dexComparator);
+      addTestCase(testCases, test, Frontend.JAR, Backend.CF, mainClass, keepName, keepList, mode,
+          inspection, outputComparator, dexComparator);
+      addTestCase(testCases, test, Frontend.JAR, Backend.DEX, mainClass, keepName, keepList, mode,
+          inspection, outputComparator, dexComparator);
+      addTestCase(testCases, test, Frontend.DEX, Backend.DEX, mainClass, keepName, keepList, mode,
+          inspection, outputComparator, dexComparator);
     }
   }
 
-  private static void addTestCase(List<Object[]> testCases, String test, Frontend kind,
-      String mainClass, String keepName, List<String> keepList, MinifyMode minify,
+  private static void addTestCase(List<Object[]> testCases, String test, Frontend frontend,
+      Backend backend, String mainClass, String keepName, List<String> keepList, MinifyMode minify,
       Consumer<DexInspector> inspection, BiConsumer<String, String> outputComparator,
       BiConsumer<DexInspector, DexInspector> dexComparator) {
+    String full = test + ":" + keepName + ":" + frontend + ":" + backend + ":" + minify;
+    String anyKind = test + ":" + keepName + ":*:" + minify;
+    String anyMinify = test + ":" + keepName + ":" + frontend + ":" + backend + ":*";
     if (!IGNORED_FLAGS.contains(test + ":" + keepName)
-        && !IGNORED.contains(test + ":" + keepName + ":" + kind + ":" + minify)) {
-      testCases.add(new Object[]{
-          test, kind, mainClass, keepList, minify, inspection, outputComparator, dexComparator});
+        && !IGNORED.contains(full)
+        && !IGNORED.contains(anyKind)
+        && !IGNORED.contains(anyMinify)) {
+      testCases.add(
+          new Object[] {
+            test,
+            frontend,
+            backend,
+            mainClass,
+            keepList,
+            minify,
+            inspection,
+            outputComparator,
+            dexComparator
+          });
     }
   }
 
@@ -888,11 +933,32 @@
 
   @Test
   public void treeShakingTest() throws IOException, InterruptedException, ExecutionException {
+    if (backend == Backend.CF) {
+      Path shakinglib = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "shakinglib.jar");
+      ProcessResult resultInput =
+          ToolHelper.runJava(Arrays.asList(Paths.get(programFile), shakinglib), mainClass);
+      Assert.assertEquals(0, resultInput.exitCode);
+      ProcessResult resultOutput =
+          ToolHelper.runJava(Arrays.asList(out, shakinglib), mainClass);
+      if (outputComparator != null) {
+        outputComparator.accept(resultInput.stdout, resultOutput.stdout);
+      } else {
+        Assert.assertEquals(resultInput.toString(), resultOutput.toString());
+      }
+      if (inspection != null) {
+        DexInspector inspector =
+            new DexInspector(
+                out,
+                minify.isMinify()
+                    ? proguardMap.toString()
+                    : null);
+        inspection.accept(inspector);
+      }
+      return;
+    }
     if (!ToolHelper.artSupported()) {
       return;
     }
-    String out = temp.getRoot().getCanonicalPath();
-    Path generated = Paths.get(out, "classes.dex");
     Consumer<ArtCommandBuilder> extraArtArgs = builder -> {
       builder.appendClasspath(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib/classes.dex");
     };
@@ -902,19 +968,18 @@
         String output1 = ToolHelper.runArtNoVerificationErrors(
             Collections.singletonList(originalDex), mainClass, extraArtArgs, null);
         String output2 = ToolHelper.runArtNoVerificationErrors(
-            Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
+            Collections.singletonList(out.toString()), mainClass, extraArtArgs, null);
         outputComparator.accept(output1, output2);
       } else {
         ToolHelper.checkArtOutputIdentical(Collections.singletonList(originalDex),
-            Collections.singletonList(generated.toString()), mainClass,
+            Collections.singletonList(out.toString()), mainClass,
             extraArtArgs, null);
       }
 
       if (dexComparator != null) {
         DexInspector ref = new DexInspector(Paths.get(originalDex));
-        DexInspector inspector = new DexInspector(generated,
-            minify.isMinify() ? temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE)
-                .toString()
+        DexInspector inspector = new DexInspector(out,
+            minify.isMinify() ? proguardMap.toString()
                 : null);
         dexComparator.accept(ref, inspector);
       }
@@ -922,12 +987,12 @@
       Assert.assertNull(outputComparator);
       Assert.assertNull(dexComparator);
       ToolHelper.runArtNoVerificationErrors(
-          Collections.singletonList(generated.toString()), mainClass, extraArtArgs, null);
+          Collections.singletonList(out.toString()), mainClass, extraArtArgs, null);
     }
 
     if (inspection != null) {
-      DexInspector inspector = new DexInspector(generated,
-          minify.isMinify() ? temp.getRoot().toPath().resolve(DEFAULT_PROGUARD_MAP_FILE).toString()
+      DexInspector inspector = new DexInspector(out,
+          minify.isMinify() ? proguardMap.toString()
               : null);
       inspection.accept(inspector);
     }