blob: ee4477d20e4a8bb41019945256f14fc03f896a58 [file] [log] [blame]
// 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 com.android.tools.r8.bisect.BisectOptions.Result;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Timing;
import com.google.common.io.CharStreams;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Bisect {
private final BisectOptions options;
private final Timing timing = new Timing("bisect");
public interface Command {
Result apply(DexApplication application) throws Exception;
}
private static class StreamReader implements Runnable {
private InputStream stream;
private String result;
public StreamReader(InputStream stream) {
this.stream = stream;
}
public String getResult() {
return result;
}
@Override
public void run() {
try {
result = CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8));
stream.close();
} catch (IOException e) {
result = "Failed reading result for stream " + stream;
}
}
}
public Bisect(BisectOptions options) {
this.options = options;
}
public static DexProgramClass run(BisectState state, Command command, Path output,
ExecutorService executor)
throws Exception {
while (true) {
DexApplication app = state.bisect();
state.write();
if (app == null) {
return state.getFinalClass();
}
if (command == null) {
writeApp(app, output, executor);
System.out.println("Bisecting completed with build in " + output + "/");
System.out.println("Continue bisection by passing either --"
+ BisectOptions.RESULT_GOOD_FLAG + " or --"
+ BisectOptions.RESULT_BAD_FLAG);
return null;
}
state.setPreviousResult(command.apply(app));
}
}
public DexProgramClass run() throws Exception {
// Setup output directory (or write to a temp dir).
Path output;
if (options.output != null) {
output = options.output.toPath();
} else {
File temp = File.createTempFile("bisect", "", new File("/tmp"));
temp.delete();
temp.mkdir();
output = temp.toPath();
}
ExecutorService executor = Executors.newWorkStealingPool();
try {
DexApplication goodApp = readApp(options.goodBuild, executor);
DexApplication badApp = readApp(options.badBuild, executor);
File stateFile = options.stateFile != null
? options.stateFile
: output.resolve("bisect.state").toFile();
// Setup initial (or saved) bisection state.
BisectState state = new BisectState(goodApp, badApp, stateFile);
if (options.stateFile != null) {
state.read();
}
// If a "previous" result is supplied on the command line, record it.
if (options.result != Result.UNKNOWN) {
state.setPreviousResult(options.result);
}
// Setup post-build command.
Command command = null;
if (options.command != null) {
command = (application) -> {
writeApp(application, output, executor);
return runCommand(output);
};
}
// Run bisection.
return run(state, command, output, executor);
} finally {
executor.shutdown();
}
}
private Result runCommand(Path output) throws IOException {
List<String> args = new ArrayList<>();
args.add("/bin/bash");
args.add(options.command.toString());
args.add(output.toString());
ProcessBuilder builder = new ProcessBuilder(args);
Process process = builder.start();
StreamReader stdoutReader = new StreamReader(process.getInputStream());
StreamReader stderrReader = new StreamReader(process.getErrorStream());
Thread stdoutThread = new Thread(stdoutReader);
Thread stderrThread = new Thread(stderrReader);
stdoutThread.start();
stderrThread.start();
try {
process.waitFor();
stdoutThread.join();
stderrThread.join();
} catch (InterruptedException e) {
throw new RuntimeException("Execution interrupted", e);
}
int result = process.exitValue();
if (result == 0) {
return Result.GOOD;
} else if (result == 1) {
return Result.BAD;
}
System.out.println("Failed to run command " + args);
System.out.println("Exit code: " + result + " (expected 0 for good, 1 for bad)");
System.out.println("Std out:\n" + stdoutReader.getResult());
System.out.println("Std err:\n" + stderrReader.getResult());
throw new CompilationError("Failed to run command " + args);
}
private DexApplication readApp(File apk, ExecutorService executor)
throws IOException, ExecutionException {
AndroidApp app = AndroidApp.fromProgramFiles(apk.toPath());
return new ApplicationReader(app, new InternalOptions(), timing).read(executor);
}
private static void writeApp(DexApplication app, Path output, ExecutorService executor)
throws IOException, ExecutionException {
InternalOptions options = new InternalOptions();
AppInfo appInfo = new AppInfo(app);
ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null, null, null);
AndroidApp outApp = writer.write(null, executor);
outApp.writeToDirectory(output, OutputMode.Indexed);
}
public static void main(String[] args) throws Exception {
BisectOptions options = null;
try {
options = BisectOptions.parse(args);
} catch (CompilationError e) {
System.err.println(e.getMessage());
BisectOptions.printHelp(System.err);
return;
}
if (options == null) {
return;
}
DexProgramClass clazz = new Bisect(options).run();
if (clazz != null) {
System.out.println("Bisection found final bad class " + clazz);
}
}
}