blob: 48711860475abdd2fdb303e0fefb5b946ceaa513 [file] [log] [blame]
// Copyright (c) 2022, 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.utils;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramClass;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class DeterminismChecker {
/**
* Create a new checker with a directory of log files as backing
*
* <p>The checker must be created as a new instance for each compilation. Withing the directory
* the respective check points will be emitted/checked via successive log files.
*/
public static DeterminismChecker createWithFileBacking(Path directory) {
return new DeterminismChecker(
new LineCallbackSupplier() {
// Index of the "checkpoint", there can be several if checked at multiple points during
// compiler execution.
private int index = 0;
@Override
public LineCallback createCallback() throws IOException {
// This is called on each "checkpoint" and the index is bumped.
Path log = directory.resolve("" + index++ + ".log");
if (Files.exists(log)) {
System.out.println("Checking against determinism log: " + log);
return new LineCallbackChecker(Files.newBufferedReader(log, StandardCharsets.UTF_8));
} else {
System.out.println("Writing determinism log: " + log);
BufferedWriter bufferedWriter =
Files.newBufferedWriter(
log,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
return new LineCallbackWriter(bufferedWriter);
}
}
});
}
/** Supplier/factory to support multiple checkpoints. */
public interface LineCallbackSupplier {
LineCallback createCallback() throws IOException;
}
/** Interface for the write-or-check of each line. */
public interface LineCallback extends Closeable {
boolean onLine(String line) throws IOException;
}
private final LineCallbackSupplier callbackFactory;
private DeterminismChecker(LineCallbackSupplier callbackFactory) {
this.callbackFactory = callbackFactory;
}
private static String fmtClass(DexProgramClass clazz) {
return clazz.getType().toSourceString()
+ " "
+ clazz.getMethodCollection().getBackingDescriptionString();
}
private static String fmtMethod(DexEncodedMethod method) {
return method.getReference().toSourceString();
}
public void check(AppView<?> appView) {
try (LineCallback callback = callbackFactory.createCallback()) {
List<DexProgramClass> classes = new ArrayList<>(appView.appInfo().classes());
classes.sort(Comparator.comparing(ProgramClass::getType));
for (int i = 0; i < classes.size(); i++) {
DexProgramClass clazz = classes.get(i);
checkClass(callback, clazz);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void checkClass(LineCallback callback, DexProgramClass clazz) throws IOException {
String line = fmtClass(clazz);
if (!callback.onLine(line)) {
return;
}
for (DexEncodedMethod method : clazz.methods()) {
checkMethod(callback, method);
}
}
private void checkMethod(LineCallback callback, DexEncodedMethod method) throws IOException {
String header = fmtMethod(method);
if (!callback.onLine(header)) {
return;
}
if (method.hasCode()) {
String[] lines = method.getCode().toString().split("\n");
for (String line : lines) {
if (!callback.onLine(line)) {
return;
}
}
} else {
if (!callback.onLine("<nocode>")) {
return;
}
}
}
/** Shared escape function. */
private static String escape(String line) {
// Replace \r so the line splitting is always consistently on \n.
return line.replace("\r", "<CR>");
}
/** Implementation of the checker (must be consistent with the writer). */
private static class LineCallbackChecker implements LineCallback {
private final BufferedReader reader;
public LineCallbackChecker(BufferedReader reader) {
this.reader = reader;
}
@Override
public boolean onLine(String unescapedLine) throws IOException {
String line = escape(unescapedLine);
String dumpLine = reader.readLine();
boolean equals = dumpLine.equals(line);
if (!equals) {
throw new AssertionError(
"\nMismatch for line: " + line + "\n" + " and dump-line: " + dumpLine);
}
return equals;
}
@Override
public void close() throws IOException {
reader.close();
}
}
/** Implementation of the writer (must be consistent with the checker). */
private static class LineCallbackWriter implements LineCallback {
private final Writer writer;
public LineCallbackWriter(Writer writer) {
this.writer = writer;
}
@Override
public boolean onLine(String line) throws IOException {
writer.write(escape(line));
writer.write('\n');
return true;
}
@Override
public void close() throws IOException {
writer.close();
}
}
}