Checksum Marker in Dex File.
The motivation / details are at go/dex-comp-extract.
On a high level, this CL introduce an API that
1) Allows annotating the .class file checksum in the dex file.
2) Gives the user the ability to selectively abort dex parsing of specific class
with a given checksum (which effectively removes that class from compilation)
Change-Id: Iea760720ecd08b4f792c5aaddd6ff44b5a4cd422
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 562a4e4..79f0009 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -11,6 +11,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.BiPredicate;
/**
* Base class for commands and command builders for compiler applications/tools which besides an
@@ -29,7 +30,9 @@
private final int minApiLevel;
private final Reporter reporter;
private final boolean enableDesugaring;
+ private final boolean includeClassesChecksum;
private final boolean optimizeMultidexForLinearAlloc;
+ private final BiPredicate<String, Long> dexClassChecksumFilter;
BaseCompilerCommand(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
@@ -39,7 +42,9 @@
minApiLevel = 0;
reporter = new Reporter();
enableDesugaring = true;
+ includeClassesChecksum = false;
optimizeMultidexForLinearAlloc = false;
+ dexClassChecksumFilter = (name, checksum) -> true;
}
BaseCompilerCommand(
@@ -50,7 +55,9 @@
int minApiLevel,
Reporter reporter,
boolean enableDesugaring,
- boolean optimizeMultidexForLinearAlloc) {
+ boolean includeClassesChecksum,
+ boolean optimizeMultidexForLinearAlloc,
+ BiPredicate<String, Long> dexClassChecksumFilter) {
super(app);
assert minApiLevel > 0;
assert mode != null;
@@ -60,7 +67,9 @@
this.minApiLevel = minApiLevel;
this.reporter = reporter;
this.enableDesugaring = enableDesugaring;
+ this.includeClassesChecksum = includeClassesChecksum;
this.optimizeMultidexForLinearAlloc = optimizeMultidexForLinearAlloc;
+ this.dexClassChecksumFilter = dexClassChecksumFilter;
}
/**
@@ -97,6 +106,16 @@
return enableDesugaring;
}
+ /** True if the output dex files has checksum information encoded in it. False otherwise. */
+ public boolean getIncludeClassesChecksum() {
+ return includeClassesChecksum;
+ }
+
+ /** Filter used to skip parsing of certain class in a dex file. */
+ public BiPredicate<String, Long> getDexClassChecksumFilter() {
+ return dexClassChecksumFilter;
+ }
+
/**
* If true, legacy multidex partitioning will be optimized to reduce LinearAlloc usage during
* Dalvik DexOpt.
@@ -128,8 +147,10 @@
private CompilationMode mode;
private int minApiLevel = 0;
private boolean disableDesugaring = false;
+ private boolean includeClassesChecksum = false;
private boolean lookupLibraryBeforeProgram = true;
private boolean optimizeMultidexForLinearAlloc = false;
+ private BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
abstract CompilationMode defaultCompilationMode();
@@ -209,6 +230,13 @@
}
/**
+ * Filter used to skip parsing of certain class in a dex file.
+ */
+ public BiPredicate<String, Long> getDexClassChecksumFilter() {
+ return dexClassChecksumFilter;
+ }
+
+ /**
* If set to true, legacy multidex partitioning will be optimized to reduce LinearAlloc usage
* during Dalvik DexOpt. Has no effect when compiling for a target with native multidex support
* or without main dex list specification.
@@ -294,6 +322,18 @@
return self();
}
+ /**
+ * Setting a dex class filter.
+ *
+ * A filter is a function that given a name of a class and a checksum can return false the user
+ * decides to skip parsing and ignore that class in the dex file.
+ */
+ public B setDexClassChecksumFilter(BiPredicate<String, Long> filter) {
+ assert filter != null;
+ this.dexClassChecksumFilter = filter;
+ return self();
+ }
+
protected InternalProgramOutputPathConsumer createProgramOutputConsumer(
Path path,
OutputMode mode,
@@ -379,6 +419,17 @@
return disableDesugaring;
}
+ /** Encodes checksum for each class when generating dex files. */
+ public B setIncludeClassesChecksum(boolean enabled) {
+ this.includeClassesChecksum = enabled;
+ return self();
+ }
+
+ /** Encodes the checksums into the dex output. */
+ public boolean getIncludeClassesChecksum() {
+ return includeClassesChecksum;
+ }
+
@Override
void validate() {
Reporter reporter = getReporter();
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 03b7ada..7900976 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -215,6 +215,7 @@
null,
options,
marker == null ? null : ImmutableList.copyOf(markers),
+ app.getChecksums(),
null,
GraphLense.getIdentityLense(),
PrefixRewritingNamingLens.createPrefixRewritingNamingLens(
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index f427e0f..e14b10a 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.utils.StringDiagnostic;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.function.BiPredicate;
/**
* Immutable command structure for an invocation of the {@link D8} compiler.
@@ -164,8 +165,10 @@
getMinApiLevel(),
getReporter(),
!getDisableDesugaring(),
+ getIncludeClassesChecksum(),
intermediate,
- isOptimizeMultidexForLinearAlloc());
+ isOptimizeMultidexForLinearAlloc(),
+ getDexClassChecksumFilter());
}
}
@@ -221,8 +224,10 @@
int minApiLevel,
Reporter diagnosticsHandler,
boolean enableDesugaring,
+ boolean encodeChecksum,
boolean intermediate,
- boolean optimizeMultidexForLinearAlloc) {
+ boolean optimizeMultidexForLinearAlloc,
+ BiPredicate<String, Long> dexClassChecksumFilter) {
super(
inputApp,
mode,
@@ -231,7 +236,9 @@
minApiLevel,
diagnosticsHandler,
enableDesugaring,
- optimizeMultidexForLinearAlloc);
+ encodeChecksum,
+ optimizeMultidexForLinearAlloc,
+ dexClassChecksumFilter);
this.intermediate = intermediate;
}
@@ -270,6 +277,8 @@
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
internal.enableDesugaring = getEnableDesugaring();
+ internal.encodeChecksums = getIncludeClassesChecksum();
+ internal.dexClassChecksumFilter = getDexClassChecksumFilter();
internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
// This is currently only used for testing.
assert internal.rewritePrefix.isEmpty();
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index e728f6d..849eadc 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -101,6 +101,7 @@
options,
markers,
null,
+ null,
GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 595e1e3..01db907 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -102,6 +102,7 @@
options,
markers,
null,
+ null,
GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null,
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 0c3a045..f18c30f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -227,6 +227,7 @@
appView,
options,
Collections.singletonList(marker),
+ null,
deadCode,
graphLense,
namingLens,
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 1e1c922..f86762a 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -33,6 +33,8 @@
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
import java.util.function.Consumer;
/**
@@ -91,6 +93,7 @@
private StringConsumer proguardConfigurationConsumer = null;
private GraphConsumer keptGraphConsumer = null;
private GraphConsumer mainDexKeptGraphConsumer = null;
+ private BiFunction<String, Long, Boolean> dexClassChecksumFilter = (name, checksum) -> true;
// Internal compatibility mode for use from CompatProguard tool.
Path proguardCompatibilityRulesOutput = null;
@@ -490,6 +493,7 @@
getMinApiLevel(),
reporter,
desugaring,
+ getIncludeClassesChecksum(),
configuration.isShrinking(),
configuration.isObfuscating(),
disableVerticalClassMerging,
@@ -502,7 +506,8 @@
keptGraphConsumer,
mainDexKeptGraphConsumer,
syntheticProguardRulesConsumer,
- isOptimizeMultidexForLinearAlloc());
+ isOptimizeMultidexForLinearAlloc(),
+ getDexClassChecksumFilter());
return command;
}
@@ -640,6 +645,7 @@
int minApiLevel,
Reporter reporter,
boolean enableDesugaring,
+ boolean encodeChecksum,
boolean enableTreeShaking,
boolean enableMinification,
boolean disableVerticalClassMerging,
@@ -652,7 +658,8 @@
GraphConsumer keptGraphConsumer,
GraphConsumer mainDexKeptGraphConsumer,
Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer,
- boolean optimizeMultidexForLinearAlloc) {
+ boolean optimizeMultidexForLinearAlloc,
+ BiPredicate<String, Long> dexClassChecksumFilter) {
super(
inputApp,
mode,
@@ -661,7 +668,9 @@
minApiLevel,
reporter,
enableDesugaring,
- optimizeMultidexForLinearAlloc);
+ encodeChecksum,
+ optimizeMultidexForLinearAlloc,
+ dexClassChecksumFilter);
assert proguardConfiguration != null;
assert mainDexKeepRules != null;
this.mainDexKeepRules = mainDexKeepRules;
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 6b386c5..d54ea1a 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -187,7 +187,7 @@
AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
ApplicationWriter writer =
new ApplicationWriter(
- app, null, options, null, null, null, NamingLens.getIdentityLens(), null);
+ app, null, options, null, null, null, null, NamingLens.getIdentityLens(), null);
writer.write(executor);
options.signalFinishedToConsumers();
compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index bf258b6..da04cd3 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -59,6 +59,7 @@
private final DexItemFactory itemFactory;
private final Timing timing;
private final AndroidApp inputApp;
+ private final ClassesChecksum checksums = new ClassesChecksum();
public interface ProgramClassConflictResolver {
DexProgramClass resolveClassConflict(DexProgramClass a, DexProgramClass b);
@@ -121,8 +122,12 @@
readProguardMap(proguardMap, builder, executorService, futures);
readMainDexList(builder, executorService, futures);
ClassReader classReader = new ClassReader(executorService, futures);
- classReader.readSources();
+ JarClassFileReader jcf = classReader.readSources();
ThreadUtils.awaitFutures(futures);
+ // Merge all the checksum gathered from the class file's CRC as well as the marker
+ // implanted into the dex file.
+ builder.mergeChecksums(jcf.getChecksums());
+ builder.mergeChecksums(classReader.application.options.itemFactory.extractChecksum());
classReader.initializeLazyClassCollection(builder);
for (ProgramResourceProvider provider : inputApp.getProgramResourceProviders()) {
DataResourceProvider dataResourceProvider = provider.getDataResourceProvider();
@@ -236,6 +241,7 @@
}
dexParsers.add(new DexParser(dexReader, classKind, options));
}
+
options.minApiLevel = computedMinApiLevel;
for (DexParser dexParser : dexParsers) {
dexParser.populateIndexTables();
@@ -252,7 +258,7 @@
}
}
- private <T extends DexClass> void readClassSources(
+ private <T extends DexClass> JarClassFileReader readClassSources(
List<ProgramResource> classSources, ClassKind classKind, Queue<T> classes) {
JarClassFileReader reader = new JarClassFileReader(
application, classKind.bridgeConsumer(classes::add));
@@ -270,9 +276,10 @@
return null;
}));
}
+ return reader;
}
- void readSources() throws IOException, ResourceException {
+ JarClassFileReader readSources() throws IOException, ResourceException {
Collection<ProgramResource> resources = inputApp.computeAllProgramResources();
List<ProgramResource> dexResources = new ArrayList<>(resources.size());
List<ProgramResource> cfResources = new ArrayList<>(resources.size());
@@ -285,7 +292,7 @@
}
}
readDexSources(dexResources, PROGRAM, programClasses);
- readClassSources(cfResources, PROGRAM, programClasses);
+ return readClassSources(cfResources, PROGRAM, programClasses);
}
private <T extends DexClass> ClassProvider<T> buildClassProvider(ClassKind classKind,
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index c912c68..f1c879f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -43,8 +43,12 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
import com.google.common.collect.ObjectArrays;
+import com.google.common.primitives.Longs;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -55,6 +59,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import java.util.zip.CRC32;
public class ApplicationWriter {
@@ -66,6 +71,8 @@
public final InternalOptions options;
public List<Marker> markers;
public List<DexString> markerStrings;
+ private final ClassesChecksum checksums;
+
public DexIndexedConsumer programConsumer;
public final ProguardMapSupplier proguardMapSupplier;
@@ -131,6 +138,7 @@
AppView<?> appView,
InternalOptions options,
List<Marker> markers,
+ ClassesChecksum checksums,
String deadCode,
GraphLense graphLense,
NamingLens namingLens,
@@ -140,6 +148,7 @@
appView,
options,
markers,
+ checksums,
deadCode,
graphLense,
namingLens,
@@ -152,6 +161,7 @@
AppView<?> appView,
InternalOptions options,
List<Marker> markers,
+ ClassesChecksum checksums,
String deadCode,
GraphLense graphLense,
NamingLens namingLens,
@@ -163,6 +173,7 @@
assert options != null;
this.options = options;
this.markers = markers;
+ this.checksums = checksums;
this.deadCode = deadCode;
this.graphLense = graphLense;
this.namingLens = namingLens;
@@ -186,7 +197,61 @@
distributor = new VirtualFile.FillFilesDistributor(this, options, executorService);
}
- return distributor.run();
+ Iterable<VirtualFile> result = distributor.run();
+ return result;
+ }
+
+ /**
+ * For each class within a virtual file, this function insert a string that contains the
+ * checksum information about that class.
+ *
+ * This needs to be done after distribute but before dex string sorting.
+ */
+ private void encodeChecksums(Iterable<VirtualFile> files) {
+ ImmutableMap<String, Long> inputChecksums = checksums.getChecksums();
+ Map<String, Long> synthesizedChecksums = Maps.newHashMap();
+ for (DexProgramClass clazz : application.classes()) {
+ Collection<DexProgramClass> synthesizedFrom = clazz.getSynthesizedFrom();
+
+ if (synthesizedFrom.isEmpty()) {
+ if (inputChecksums.containsKey(clazz.getType().descriptor.toASCIIString())) {
+ continue;
+ } else {
+ throw new CompilationError(clazz + " from " + clazz.origin +
+ " has no checksum information while checksum encoding is requested");
+ }
+ }
+
+ // Checksum of synthesized classes are compute based off the depending input. This might
+ // create false positives (ie: unchanged lambda class detected as changed even thought only
+ // an unrelated part from a synthesizedFrom class is changed).
+
+ // Ideally, we should use some hashcode of the dex program class that is deterministic across
+ // compiles.
+ ByteBuffer buffer = ByteBuffer.allocate(synthesizedFrom.size() * Longs.BYTES);
+ for (DexProgramClass from : synthesizedFrom) {
+ buffer.putLong(inputChecksums.get(from.getType().descriptor.toASCIIString()));
+ }
+ CRC32 crc = new CRC32();
+ crc.update(buffer.array());
+ synthesizedChecksums.put(clazz.getType().descriptor.toASCIIString(), crc.getValue());
+ }
+
+ for (VirtualFile f : files) {
+ ClassesChecksum toWrite = new ClassesChecksum();
+ for (String desc : f.getClassDescriptors()) {
+ Long checksum = inputChecksums.get(desc);
+ if (checksum == null) {
+ checksum = synthesizedChecksums.get(desc);
+ }
+
+ // All classes should have a checksum from the inputChecksum (previous marker) or it was
+ // computed eariler in the function. Otherwise, we would have throw an compilation error.
+ assert checksum != null;
+ toWrite.addChecksum(desc, checksum);
+ }
+ f.injectString(application.dexItemFactory.createString(toWrite.toString()));
+ }
}
public void write(ExecutorService executorService) throws IOException, ExecutionException {
@@ -211,6 +276,13 @@
try {
insertAttributeAnnotations();
+ // Generate the dex file contents.
+ List<Future<Boolean>> dexDataFutures = new ArrayList<>();
+ Iterable<VirtualFile> virtualFiles = distribute(executorService);
+ if (options.encodeChecksums) {
+ encodeChecksums(virtualFiles);
+ }
+
application.dexItemFactory.sort(namingLens);
assert markers == null
|| markers.isEmpty()
@@ -219,9 +291,6 @@
SortAnnotations sortAnnotations = new SortAnnotations();
application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
- // Generate the dex file contents.
- List<Future<Boolean>> dexDataFutures = new ArrayList<>();
- Iterable<VirtualFile> virtualFiles = distribute(executorService);
for (VirtualFile virtualFile : virtualFiles) {
if (virtualFile.isEmpty()) {
continue;
diff --git a/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java b/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
new file mode 100644
index 0000000..48ad1a0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
@@ -0,0 +1,80 @@
+package com.android.tools.r8.dex;
+
+import com.android.tools.r8.graph.DexString;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import java.util.Comparator;
+import java.util.Map;
+
+public class ClassesChecksum {
+
+ private static final char PREFIX_CHAR0 = '~';
+ private static final char PREFIX_CHAR1 = '~';
+ private static final char PREFIX_CHAR2 = '~';
+
+
+ // private final JsonObject dictionary;
+ Map<String, Long> dictionary = Maps.newHashMap();
+
+ /**
+ * Checksum to be inserted.
+ */
+ public ClassesChecksum() {
+ }
+
+ private ClassesChecksum(JsonObject json) {
+ json.entrySet().forEach(entry ->
+ dictionary.put(entry.getKey(), Long.parseLong(entry.getValue().getAsString(), 16)));
+ }
+
+ public synchronized ClassesChecksum addChecksum(String classDescriptor, Long crc) {
+ dictionary.put(classDescriptor, crc);
+ return this;
+ }
+
+ public synchronized ImmutableMap<String, Long> getChecksums() {
+ return ImmutableMap.copyOf(dictionary);
+ }
+
+ public synchronized ClassesChecksum merge(ClassesChecksum other) {
+ if (other != null) {
+ other.dictionary.entrySet().stream().forEach(entry -> this.dictionary.put(
+ entry.getKey(), entry.getValue()));
+ }
+ return this;
+ }
+
+ @Override
+ public synchronized String toString() {
+ // In order to make printing of markers deterministic we sort the entries by key.
+ final JsonObject sortedJson = new JsonObject();
+ dictionary.entrySet()
+ .stream()
+ .sorted(Comparator.comparing(Map.Entry::getKey))
+ .forEach(
+ entry -> sortedJson.addProperty(entry.getKey(), Long.toHexString(entry.getValue())));
+ return "" + PREFIX_CHAR0 + PREFIX_CHAR1 + PREFIX_CHAR2 + sortedJson;
+ }
+
+ // Try to parse str as a marker.
+ // Returns null if parsing fails.
+ public static ClassesChecksum parse(DexString dexString) {
+ if (dexString.size > 2
+ && dexString.content[0] == PREFIX_CHAR0
+ && dexString.content[1] == PREFIX_CHAR1
+ && dexString.content[2] == PREFIX_CHAR2) {
+ String str = dexString.toString().substring(3);
+ try {
+ JsonElement result = new JsonParser().parse(str);
+ if (result.isJsonObject()) {
+ return new ClassesChecksum(result.getAsJsonObject());
+ }
+ } catch (JsonSyntaxException ignored) {}
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index df86e96..61ef335 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -60,6 +60,7 @@
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -86,6 +87,7 @@
private int[] stringIDs;
private final ClassKind classKind;
private final InternalOptions options;
+ private ImmutableMap<String, Long> checksums;
public static DexSection[] parseMapFrom(Path file) throws IOException {
return parseMapFrom(Files.newInputStream(file), new PathOrigin(file));
@@ -133,7 +135,11 @@
this.options = options;
}
- private void ensureCodesInited() {
+ private void ensureCodesInited(int offset) {
+ if (offset == 0) {
+ return;
+ }
+
if (codes == null) {
codes = new Int2ObjectOpenHashMap<>();
}
@@ -146,12 +152,14 @@
if (dexSection.length == 0) {
return;
}
- dexReader.position(dexSection.offset);
- for (int i = 0; i < dexSection.length; i++) {
- dexReader.align(4); // code items are 4 byte aligned.
- int offset = dexReader.position();
+
+ if (!codes.containsKey(offset)) {
+ int currentPos = dexReader.position();
+ dexReader.position(offset);
+ dexReader.align(4);
DexCode code = parseCodeItem();
codes.put(offset, code); // Update the file local offset to code mapping.
+ dexReader.position(currentPos);
}
}
@@ -618,6 +626,7 @@
int codeOff = dexReader.getUleb128();
DexCode code = null;
if (!skipCodes) {
+ ensureCodesInited(codeOff);
assert codeOff == 0 || codes.get(codeOff) != null;
code = codes.get(codeOff);
}
@@ -642,7 +651,6 @@
}
void addClassDefsTo(Consumer<DexClass> classCollection) {
- ensureCodesInited();
final DexSection dexSection = lookupSection(Constants.TYPE_CLASS_DEF_ITEM);
final int length = dexSection.length;
indexedItems.initializeClasses(length);
@@ -692,6 +700,14 @@
DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
AnnotationsDirectory annotationsDirectory = annotationsDirectoryAt(annotationsOffsets[i]);
+
+ if (checksums != null && classDataOffsets[i] != 0) {
+ String desc = type.descriptor.toASCIIString();
+ Long checksum = checksums.get(desc);
+ if (!options.dexClassChecksumFilter.test(desc, checksum)) {
+ continue;
+ }
+ }
if (classDataOffsets[i] != 0) {
DexEncodedArray staticValues = encodedArrayAt(staticValuesOffsets[i]);
@@ -870,6 +886,10 @@
void populateIndexTables() {
// Populate structures that are already sorted upon read.
populateStrings(); // Depends on nothing.
+ ClassesChecksum checksumMarker = dexItemFactory.extractChecksum(); // Depends on Strings.
+ if (checksumMarker != null) {
+ checksums = checksumMarker.getChecksums();
+ }
populateTypes(); // Depends on Strings.
populateFields(); // Depends on Types, and Strings.
populateProtos(); // Depends on Types and Strings.
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 0d12915..1e2a264 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -125,6 +125,11 @@
return prefix;
}
+ public void injectString(DexString string) {
+ transaction.addString(string);
+ commitTransaction();
+ }
+
private static Map<DexProgramClass, String> computeOriginalNameMapping(
Collection<DexProgramClass> classes,
ClassNameMapper proguardMap) {
@@ -520,7 +525,7 @@
}
}
- private static class IndexedItemTransaction implements IndexedItemCollection {
+ public static class IndexedItemTransaction implements IndexedItemCollection {
private final VirtualFileIndexedItemCollection base;
private final NamingLens namingLens;
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 3ed2bc7..e87ab25 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
+import com.android.tools.r8.dex.ClassesChecksum;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
@@ -35,6 +36,7 @@
public final InternalOptions options;
public final DexItemFactory dexItemFactory;
+ public final ClassesChecksum checksums;
// Information on the lexicographically largest string referenced from code.
public final DexString highestSortingString;
@@ -45,6 +47,7 @@
ImmutableList<DataResourceProvider> dataResourceProviders,
ImmutableSet<DexType> mainDexList,
String deadCode,
+ ClassesChecksum checksums,
InternalOptions options,
DexString highestSortingString,
Timing timing) {
@@ -52,6 +55,7 @@
this.dataResourceProviders = dataResourceProviders;
this.mainDexList = mainDexList;
this.deadCode = deadCode;
+ this.checksums = checksums;
this.options = options;
this.dexItemFactory = options.itemFactory;
this.highestSortingString = highestSortingString;
@@ -103,6 +107,10 @@
return classes;
}
+ public ClassesChecksum getChecksums() {
+ return checksums;
+ }
+
public abstract DexClass definitionFor(DexType type);
public abstract DexProgramClass programDefinitionFor(DexType type);
@@ -128,6 +136,7 @@
public final InternalOptions options;
public final DexItemFactory dexItemFactory;
+ protected ClassesChecksum checksums;
ClassNameMapper proguardMap;
final Timing timing;
@@ -142,6 +151,7 @@
this.timing = timing;
this.deadCode = null;
this.synthesizedClasses = new ArrayList<>();
+ this.checksums = null;
}
abstract T self();
@@ -157,6 +167,7 @@
mainDexList.addAll(application.mainDexList);
deadCode = application.deadCode;
synthesizedClasses = new ArrayList<>();
+ checksums = application.checksums;
}
public synchronized T setProguardMap(ClassNameMapper proguardMap) {
@@ -228,6 +239,15 @@
return this;
}
+ public Builder<T> mergeChecksums(ClassesChecksum other) {
+ if (checksums == null) {
+ checksums = other;
+ } else {
+ checksums.merge(other);
+ }
+ return this;
+ }
+
public abstract DexApplication build();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index ae6e0a3..2adbb70 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement.computeLeastUpperBoundOfInterfaces;
import static com.google.common.base.Predicates.alwaysTrue;
+import com.android.tools.r8.dex.ClassesChecksum;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
@@ -48,6 +49,7 @@
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -1124,6 +1126,11 @@
return markers;
}
+ public synchronized ClassesChecksum extractChecksum() {
+ return strings.keySet().stream().map(s -> ClassesChecksum.parse(s)).filter(Objects::nonNull)
+ .reduce(null, (s1, s2) -> s1 == null ? s2 : s1.merge(s2));
+ }
+
synchronized public DexType createType(DexString descriptor) {
assert !sorted;
assert descriptor != null;
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 533e983..b6b4d15 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -7,6 +7,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.dex.ClassesChecksum;
import com.android.tools.r8.graph.LazyLoadedDexApplication.AllClasses;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
@@ -41,6 +42,7 @@
ImmutableList<DataResourceProvider> dataResourceProviders,
ImmutableSet<DexType> mainDexList,
String deadCode,
+ ClassesChecksum checksums,
InternalOptions options,
DexString highestSortingString,
Timing timing) {
@@ -49,6 +51,7 @@
dataResourceProviders,
mainDexList,
deadCode,
+ checksums,
options,
highestSortingString,
timing);
@@ -203,6 +206,7 @@
ImmutableList.copyOf(dataResourceProviders),
ImmutableSet.copyOf(mainDexList),
deadCode,
+ checksums,
options,
highestSortingString,
timing);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 6ab4f8a..3ccc362 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -12,6 +12,7 @@
import static org.objectweb.asm.Opcodes.V9;
import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.dex.ClassesChecksum;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
@@ -51,6 +52,7 @@
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.zip.CRC32;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
@@ -73,11 +75,13 @@
private final JarApplicationReader application;
private final Consumer<DexClass> classConsumer;
+ private final ClassesChecksum checksums;
public JarClassFileReader(
JarApplicationReader application, Consumer<DexClass> classConsumer) {
this.application = application;
this.classConsumer = classConsumer;
+ this.checksums = new ClassesChecksum();
}
public void read(Origin origin, ClassKind classKind, InputStream input) throws IOException {
@@ -112,9 +116,8 @@
parsingOptions |= SKIP_DEBUG;
}
}
- reader.accept(
- new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
- parsingOptions);
+ reader.accept(new CreateDexClassVisitor(
+ origin, classKind, reader.b, application, classConsumer, checksums), parsingOptions);
// Read marker.
if (reader.getItemCount() > CfApplicationWriter.MARKER_STRING_CONSTANT_POOL_INDEX
@@ -133,6 +136,10 @@
}
}
+ public ClassesChecksum getChecksums() {
+ return checksums;
+ }
+
private static int cleanAccessFlags(int access) {
// Clear the "synthetic attribute" and "deprecated" attribute-flags if present.
return access & ~ACC_SYNTHETIC_ATTRIBUTE & ~ACC_DEPRECATED;
@@ -211,19 +218,22 @@
private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
private boolean hasReachabilitySensitiveMethod = false;
+ private ClassesChecksum checksums;
public CreateDexClassVisitor(
Origin origin,
ClassKind classKind,
byte[] classCache,
JarApplicationReader application,
- Consumer<DexClass> classConsumer) {
+ Consumer<DexClass> classConsumer,
+ ClassesChecksum checksums) {
super(ASM_VERSION);
this.origin = origin;
this.classKind = classKind;
this.classConsumer = classConsumer;
this.context.classCache = classCache;
this.application = application;
+ this.checksums = checksums;
}
@Override
@@ -414,6 +424,11 @@
}
if (clazz.isProgramClass()) {
clazz.asProgramClass().setInitialClassFileVersion(version);
+ if (application.options.encodeChecksums) {
+ CRC32 crc = new CRC32();
+ crc.update(this.context.classCache);
+ checksums.addChecksum(type.descriptor.toASCIIString(), crc.getValue());
+ }
}
classConsumer.accept(clazz);
}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index a7f163e..db87cbb 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
+import com.android.tools.r8.dex.ClassesChecksum;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.ClasspathClassCollection;
import com.android.tools.r8.utils.InternalOptions;
@@ -36,6 +37,7 @@
LibraryClassCollection libraryClasses,
ImmutableSet<DexType> mainDexList,
String deadCode,
+ ClassesChecksum checksum,
InternalOptions options,
DexString highestSortingString,
Timing timing) {
@@ -44,6 +46,7 @@
dataResourceProviders,
mainDexList,
deadCode,
+ checksum,
options,
highestSortingString,
timing);
@@ -236,6 +239,7 @@
libraryClasses,
ImmutableSet.copyOf(mainDexList),
deadCode,
+ checksums,
options,
highestSortingString,
timing);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 2e1a254..029c308 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -54,6 +54,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.BiPredicate;
import java.util.function.Consumer;
import org.objectweb.asm.Opcodes;
@@ -187,6 +188,8 @@
// TODO(b/114002137): Enable this when it is worthwhile, e.g., support non-String args.
public boolean enableStringConcatenationOptimization = false;
public boolean enableTreeShakingOfLibraryMethodOverrides = false;
+ public boolean encodeChecksums = false;
+ public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
// This defines the max depth threshold for the cycle eliminator. If the length of a call chain
// exceeds the threshold, we treat it as if we have found a cycle. This ensures that we won't run
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index a6b48a0..7696d5f 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -39,6 +39,7 @@
private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
private PrintStream stdout = null;
+ protected OutputMode outputMode = OutputMode.DexIndexed;
TestCompilerBuilder(TestState state, B builder, Backend backend) {
super(state, builder);
@@ -216,6 +217,11 @@
return self();
}
+ public T setIncludeClassesChecksum(boolean include) {
+ builder.setIncludeClassesChecksum(include);
+ return self();
+ }
+
@Override
public T addLibraryFiles(Collection<Path> files) {
defaultLibrary = null;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 52ddab0..03e199e 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -73,6 +73,8 @@
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import java.util.zip.CRC32;
+import org.junit.Assert;
import org.junit.Assume;
import org.junit.rules.TemporaryFolder;
@@ -565,6 +567,18 @@
return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
}
+ public static long getClassByteCrc(Class clazz) {
+ byte[] bytes = null;
+ try {
+ bytes = getClassAsBytes(clazz);
+ } catch (IOException ioe) {
+ Assert.fail(ioe.toString());
+ }
+ CRC32 crc = new CRC32();
+ crc.update(bytes);
+ return crc.getValue();
+ }
+
public static String getArtDir(DexVm version) {
String dir = ART_DIRS.get(version);
if (dir == null) {
diff --git a/src/test/java/com/android/tools/r8/classFiltering/ClassFilteringTest.java b/src/test/java/com/android/tools/r8/classFiltering/ClassFilteringTest.java
new file mode 100644
index 0000000..3c6aab6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classFiltering/ClassFilteringTest.java
@@ -0,0 +1,217 @@
+// 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.classFiltering;
+
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassFilteringTest extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ClassFilteringTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNoFiltering() throws Exception {
+ List<Class<?>> input =
+ ImmutableList.of(TestClass.class, TestClass.Remove.class, TestClass.Keep.class);
+
+ // Run a test with normal providers, verify nothing is removed.
+ testForD8()
+ .addProgramClasses(input)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput("Keep Remove ");
+ }
+
+ @Test
+ public void testFilterByChecksum() throws Exception {
+ // Step #1: Build the inputs with checksum encoded.
+ final Path output = testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(TestClass.class, TestClass.Remove.class, TestClass.Keep.class)
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .writeToZip();
+
+ // Step #2: "Re-compile" the output dex with a filter that removes a class by checksum.
+
+ // Remember the check some of Remove.class to search for in the ProgramResourceProvider
+ final long crc = ToolHelper.getClassByteCrc(TestClass.Remove.class);
+
+ testForD8()
+ .addProgramFiles(output)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().setDexClassChecksumFilter(
+ (classDescriptor, checksum) -> !checksum.equals(crc)))
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput("Keep No Remove ");
+ }
+
+ @Test
+ public void testDexMergingWithChecksum() throws Exception {
+ // Step #1: Build the dex file seperately as an incremental build tools usually do.
+ Set<Long> keepCrcs = Sets.newHashSet();
+ Path[] dexInput = new Path[] {
+ buildDex(TestClass.class,true, keepCrcs),
+ buildDex(TestClass.Keep.class,true, keepCrcs),
+ buildDex(TestClass.Remove.class, true, null)};
+
+ // Step #2: Now use D8 as a merging tool.
+ final Path merged = testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramFiles(dexInput)
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .writeToZip();
+
+ // Try it with and without checksum. Both should yield identical result.
+ testForD8()
+ .addProgramFiles(merged)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().setDexClassChecksumFilter(
+ (classDescriptor, checksum) -> keepCrcs.contains(checksum)))
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput("Keep No Remove ");
+
+ testForD8()
+ .addProgramFiles(merged)
+ .setMinApi(parameters.getApiLevel())
+ .apply(b -> b.getBuilder().setDexClassChecksumFilter(
+ (classDescriptor, checksum) -> keepCrcs.contains(checksum)))
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput("Keep No Remove ");
+ }
+
+ @Test
+ public void testDexMergingWithChecksumMissing() throws Exception {
+ // Step #1: Build the dex file seperately as an incremental build tools usually do but this time
+ // make one of the dex file missing checksum information.
+ Path[] dexInput = new Path[] {
+ buildDex(TestClass.class,true, null),
+ buildDex(TestClass.Keep.class,true, null),
+ buildDex(TestClass.Remove.class, false, null)};
+
+ // Step #2: Now use D8 as a merging tool and verify that the compilation fails as expect.
+ try {
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramFiles(dexInput)
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .writeToZip();
+ Assert.fail("Compilation should fail.");
+ } catch (CompilationFailedException failure) {
+ Assert.assertTrue(failure.getCause().getMessage(), failure.getCause().getMessage().contains(
+ "has no checksum information while checksum encoding is requested"));
+ }
+ }
+
+ /*
+ @Test
+ public void testMultidexOutput()
+ throws CompilationFailedException, IOException, ExecutionException, ResourceException {
+ // Step #1: Build the program pretending to be multidex files with DexPerClass.
+ final Path outZip = testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(TestClass.class, TestClass.Keep.class, TestClass.Remove.class)
+ .setIncludeClassesChecksum(true)
+ .setOutputMode(OutputMode.DexFilePerClass)
+ .compile()
+ .writeToZip();
+
+ final long crc = ToolHelper.getClassByteCrc(TestClass.Remove.class);
+
+ // Step #2: Verify that the checksums are present and filtering is working as expected.
+ ProgramResourceProvider filter = new ArchiveProvider(outZip) {
+ @Override
+ public boolean includeClassWithChecksum(String classDescriptor, Long checksum) {
+ return !checksum.equals(crc);
+ }
+ };
+ testForD8()
+ .addProgramResourceProvider(filter)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput("Keep No Remove ");
+ }
+ */
+
+ @Test
+ public void testLambdaChecksum() throws Exception {
+ final Path output = testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(TestDesugar.class, TestDesugar.Consumer.class)
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .writeToZip();
+
+ List<String> classesWithChecksum = Lists.newArrayList();
+ testForD8()
+ .addProgramFiles(output)
+ .setIncludeClassesChecksum(true)
+ .apply(b -> b.getBuilder().setDexClassChecksumFilter(
+ (classDescriptor, checksum) -> { classesWithChecksum.add(classDescriptor); return true; }))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestDesugar.class)
+ .assertSuccessWithOutput("TestDesugar.consume");
+
+ // Make sure the provider saw all 3 checksums (class, innerclass and the lambda class).
+ Assert.assertEquals(3, classesWithChecksum.size());
+ }
+
+ /**
+ * Builds a given class into a dex file specified by output.
+ *
+ * @param checksum If true, the dex file will be encoded with checkout information.
+ * @param crcCollection If not null, add the CRC of the .class file to a collection after
+ * compilation.
+ * @return The CRC of the .class class file after compilation.
+ */
+ private Path buildDex(Class c, boolean checksum, Collection<Long> crcCollection)
+ throws IOException, CompilationFailedException {
+ if (crcCollection != null) {
+ crcCollection.add(ToolHelper.getClassByteCrc(c));
+ }
+ return testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .addProgramClasses(c)
+ .setIncludeClassesChecksum(checksum)
+ .compile()
+ .writeToZip();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classFiltering/TestClass.java b/src/test/java/com/android/tools/r8/classFiltering/TestClass.java
new file mode 100644
index 0000000..6abebea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classFiltering/TestClass.java
@@ -0,0 +1,24 @@
+package com.android.tools.r8.classFiltering;
+
+public class TestClass {
+
+ public static void main(String[] args) {
+ try {
+ new Keep();
+ System.out.print("Keep ");
+ } catch (Throwable t) {
+ System.out.print("No Keep ");
+ }
+
+ try {
+ new Remove();
+ System.out.print("Remove ");
+ } catch (Throwable t) {
+ System.out.print("No Remove ");
+ }
+ }
+
+ public static class Keep { }
+
+ public static class Remove { }
+}
diff --git a/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java b/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
new file mode 100644
index 0000000..a346177
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
@@ -0,0 +1,16 @@
+package com.android.tools.r8.classFiltering;
+
+public class TestDesugar {
+ public static void main(String[] args) {
+ consume(e -> System.out.print(e));
+ }
+
+ public static void consume(Consumer lambda) {
+ lambda.consume("TestDesugar.consume");
+ }
+
+
+ public interface Consumer {
+ public void consume(String s);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 8b40ea9..e67475a 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -145,6 +145,7 @@
options,
null,
null,
+ null,
GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index fdc4296..cf84955 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -864,6 +864,7 @@
options,
null,
null,
+ null,
GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null);
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 3d1ec2b..bef0f2a 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -118,6 +118,7 @@
options,
null,
null,
+ null,
GraphLense.getIdentityLense(),
NamingLens.getIdentityLens(),
null);