| // Copyright (c) 2017, 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.bisect; | |
| import static org.junit.Assert.assertEquals; | |
| import com.android.tools.r8.TestBase; | |
| import com.android.tools.r8.TestParameters; | |
| import com.android.tools.r8.TestParametersCollection; | |
| import com.android.tools.r8.bisect.BisectOptions.Result; | |
| import com.android.tools.r8.dex.ApplicationReader; | |
| import com.android.tools.r8.graph.DexApplication; | |
| import com.android.tools.r8.graph.DexProgramClass; | |
| import com.android.tools.r8.origin.Origin; | |
| import com.android.tools.r8.smali.SmaliBuilder; | |
| import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; | |
| import com.android.tools.r8.utils.AndroidApp; | |
| import com.android.tools.r8.utils.InternalOptions; | |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; | |
| import com.android.tools.r8.utils.internal.StringUtils; | |
| import com.android.tools.r8.utils.timing.Timing; | |
| import com.google.common.collect.ImmutableList; | |
| import java.nio.file.Path; | |
| import java.util.concurrent.ExecutorService; | |
| import java.util.concurrent.Executors; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.Parameterized; | |
| import org.junit.runners.Parameterized.Parameters; | |
| @RunWith(Parameterized.class) | |
| public class BisectTest extends TestBase { | |
| @Parameters(name = "{0}") | |
| public static TestParametersCollection parameters() { | |
| return getTestParameters().withNoneRuntime().build(); | |
| } | |
| @Test | |
| public void testHelpMessage() throws Exception { | |
| assertEquals( | |
| StringUtils.lines( | |
| "Usage: bisect [options]", | |
| "where options are:", | |
| " --bad <apk> # Known bad APK.", | |
| " --command <file> # Command to run after each bisection.", | |
| " --good <apk> # Known good APK.", | |
| " --output <dir> # Output directory.", | |
| " --result-bad # Bisect again assuming previous run was bad.", | |
| " --result-good # Bisect again assuming previous run was good.", | |
| " --state <file> # Bisection state.", | |
| " --help", | |
| " -h # Print usage information."), | |
| BisectOptions.usageMessage()); | |
| } | |
| public BisectTest(TestParameters parameters) { | |
| parameters.assertNoneRuntime(); | |
| } | |
| private final String[] CLASSES = {"A", "B", "C", "D", "E", "F", "G", "H"}; | |
| private final String ERRONEOUS_CLASS = "F"; | |
| private final String ERRONEOUS_METHOD = "foo"; | |
| private final String VALID_METHOD = "bar"; | |
| // Set during build to more easily inspect later. | |
| private MethodSignature erroneousMethodSignature = null; | |
| // Build "good" application with no method in "F". | |
| AndroidApp buildGood() throws Exception { | |
| SmaliBuilder builderGood = new SmaliBuilder(); | |
| for (String clazz : CLASSES) { | |
| builderGood.addClass(clazz); | |
| builderGood.addStaticMethod( | |
| "void", VALID_METHOD, ImmutableList.of(), 0, "return-void"); | |
| } | |
| return AndroidApp.builder().addDexProgramData(builderGood.compile(), Origin.unknown()).build(); | |
| } | |
| AndroidApp buildBad() throws Exception { | |
| // Build "bad" application with a method "foo" in "F". | |
| SmaliBuilder builderBad = new SmaliBuilder(); | |
| for (String clazz : CLASSES) { | |
| builderBad.addClass(clazz); | |
| if (clazz.equals(ERRONEOUS_CLASS)) { | |
| erroneousMethodSignature = builderBad.addStaticMethod( | |
| "void", ERRONEOUS_METHOD, ImmutableList.of(), 0, "return-void"); | |
| } else { | |
| builderBad.addStaticMethod( | |
| "void", VALID_METHOD, ImmutableList.of(), 0, "return-void"); | |
| } | |
| } | |
| return AndroidApp.builder().addDexProgramData(builderBad.compile(), Origin.unknown()).build(); | |
| } | |
| @Test | |
| public void bisectWithExternalCommand() throws Exception { | |
| AndroidApp goodInput = buildGood(); | |
| AndroidApp badInput = buildBad(); | |
| ExecutorService executor = Executors.newWorkStealingPool(); | |
| DexProgramClass clazz = null; | |
| Path stateFile = temp.newFolder().toPath().resolve("state.txt"); | |
| try { | |
| Result lastResult = Result.UNKNOWN; | |
| while (clazz == null) { | |
| InternalOptions options = new InternalOptions(); | |
| Timing timing = Timing.empty(); | |
| DexApplication appGood = new ApplicationReader(goodInput, options, timing).read(); | |
| DexApplication appBad = new ApplicationReader(badInput, options, timing).read(); | |
| BisectState state = new BisectState(appGood, appBad, stateFile); | |
| state.read(); | |
| if (lastResult != Result.UNKNOWN) { | |
| state.setPreviousResult(lastResult); | |
| } | |
| Path output = temp.newFolder().toPath(); | |
| clazz = Bisect.run(timing, state, null, output, executor); | |
| state.write(); | |
| if (clazz == null) { | |
| lastResult = command(new CodeInspector(output.resolve("classes.dex"))); | |
| } | |
| } | |
| } finally { | |
| executor.shutdown(); | |
| } | |
| assertEquals(clazz.type.toString(), ERRONEOUS_CLASS); | |
| } | |
| @Test | |
| public void bisectWithInternalCommand() throws Exception { | |
| InternalOptions options = new InternalOptions(); | |
| Timing timing = Timing.empty(); | |
| DexApplication appGood = new ApplicationReader(buildGood(), options, timing).read(); | |
| DexApplication appBad = new ApplicationReader(buildBad(), options, timing).read(); | |
| ExecutorService executor = Executors.newWorkStealingPool(); | |
| try { | |
| BisectState state = new BisectState(appGood, appBad, null); | |
| DexProgramClass clazz = | |
| Bisect.run( | |
| timing, | |
| state, | |
| app -> command(new CodeInspector(app)), | |
| temp.newFolder().toPath(), | |
| executor); | |
| assertEquals(clazz.type.toString(), ERRONEOUS_CLASS); | |
| } finally { | |
| executor.shutdown(); | |
| } | |
| } | |
| private Result command(CodeInspector inspector) { | |
| if (inspector | |
| .clazz(ERRONEOUS_CLASS) | |
| .method(erroneousMethodSignature.returnType, | |
| erroneousMethodSignature.name, | |
| erroneousMethodSignature.parameterTypes) | |
| .isPresent()) { | |
| return Result.BAD; | |
| } | |
| return Result.GOOD; | |
| } | |
| } |