Merge "Add test for signature attribute parsing"
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
new file mode 100644
index 0000000..b1bc7ca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -0,0 +1,83 @@
+// 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;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.RootSetBuilder;
+import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class PrintSeeds {
+
+ private static final String USAGE =
+ "Arguments: <rt.jar> <r8.jar> <pg-conf.txt>\n"
+ + "\n"
+ + "PrintSeeds prints the classes, interfaces, methods and fields selected by\n"
+ + "<pg-conf.txt> when compiling <r8.jar> alongside <rt.jar>.";
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 3) {
+ System.out.println(USAGE);
+ System.exit(1);
+ }
+ Path rtJar = Paths.get(args[0]);
+ Path r8Jar = Paths.get(args[1]);
+ Path pgConf = Paths.get(args[2]);
+ R8Command command =
+ R8Command.builder()
+ .addLibraryFiles(rtJar)
+ .addProgramFiles(r8Jar)
+ .addProguardConfigurationFiles(pgConf)
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .build();
+ Set<String> descriptors = new ArchiveClassFileProvider(r8Jar).getClassDescriptors();
+ InternalOptions options = command.getInternalOptions();
+ ExecutorService executorService = ThreadUtils.getExecutorService(options);
+ ExceptionUtils.withR8CompilationHandler(
+ command.getReporter(),
+ () -> {
+ try {
+ run(command, descriptors, options, executorService);
+ } finally {
+ executorService.shutdown();
+ }
+ });
+ }
+
+ private static void run(
+ R8Command command, Set<String> descriptors, InternalOptions options, ExecutorService executor)
+ throws IOException {
+ Timing timing = new Timing("PrintSeeds");
+ try {
+ DexApplication application =
+ new ApplicationReader(command.getInputApp(), options, timing).read(executor).toDirect();
+ AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+ RootSet rootSet =
+ new RootSetBuilder(
+ appInfo, application, options.proguardConfiguration.getRules(), options)
+ .run(executor);
+ Enqueuer enqueuer = new Enqueuer(appInfo, options, false);
+ appInfo = enqueuer.traceApplication(rootSet, executor, timing);
+ RootSetBuilder.writeSeeds(
+ appInfo.withLiveness(),
+ System.out,
+ type -> descriptors.contains(type.toDescriptorString()));
+ } catch (ExecutionException e) {
+ R8.unwrapExecutionException(e);
+ throw new AssertionError(e); // unwrapping method should have thrown
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
new file mode 100644
index 0000000..0e69de6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -0,0 +1,307 @@
+// 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;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class PrintUses {
+
+ private static final String USAGE =
+ "Arguments: <rt.jar> <r8.jar> <sample.jar>\n"
+ + "\n"
+ + "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,\n"
+ + "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.\n"
+ + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.";
+
+ private final Set<String> descriptors;
+ private final PrintStream out;
+ private Set<DexType> types = Sets.newIdentityHashSet();
+ private Map<DexType, Set<DexMethod>> methods = Maps.newIdentityHashMap();
+ private Map<DexType, Set<DexField>> fields = Maps.newIdentityHashMap();
+ private final DexApplication application;
+ private final AppInfoWithSubtyping appInfo;
+ private int errors;
+
+ class UseCollector extends UseRegistry {
+
+ @Override
+ public boolean registerInvokeVirtual(DexMethod method) {
+ DexEncodedMethod target = appInfo.lookupVirtualTarget(method.holder, method);
+ if (target != null && target.method != method) {
+ addType(method.holder);
+ addMethod(target.method);
+ } else {
+ addMethod(method);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean registerInvokeDirect(DexMethod method) {
+ addMethod(method);
+ return false;
+ }
+
+ @Override
+ public boolean registerInvokeStatic(DexMethod method) {
+ DexEncodedMethod target = appInfo.lookupStaticTarget(method);
+ if (target != null && target.method != method) {
+ addType(method.holder);
+ addMethod(target.method);
+ } else {
+ addMethod(method);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean registerInvokeInterface(DexMethod method) {
+ return registerInvokeVirtual(method);
+ }
+
+ @Override
+ public boolean registerInvokeSuper(DexMethod method) {
+ addMethod(method);
+ return false;
+ }
+
+ @Override
+ public boolean registerInstanceFieldWrite(DexField field) {
+ addField(field);
+ return false;
+ }
+
+ @Override
+ public boolean registerInstanceFieldRead(DexField field) {
+ addField(field);
+ return false;
+ }
+
+ @Override
+ public boolean registerNewInstance(DexType type) {
+ addType(type);
+ return false;
+ }
+
+ @Override
+ public boolean registerStaticFieldRead(DexField field) {
+ addField(field);
+ return false;
+ }
+
+ @Override
+ public boolean registerStaticFieldWrite(DexField field) {
+ addField(field);
+ return false;
+ }
+
+ @Override
+ public boolean registerTypeReference(DexType type) {
+ addType(type);
+ return false;
+ }
+
+ private void addType(DexType type) {
+ if (isTargetType(type) && types.add(type)) {
+ methods.put(type, Sets.newIdentityHashSet());
+ fields.put(type, Sets.newIdentityHashSet());
+ }
+ }
+
+ private boolean isTargetType(DexType type) {
+ return descriptors.contains(type.toDescriptorString());
+ }
+
+ private void addField(DexField field) {
+ addType(field.type);
+ addType(field.clazz);
+ Set<DexField> typeFields = fields.get(field.clazz);
+ if (typeFields != null) {
+ typeFields.add(field);
+ }
+ }
+
+ private void addMethod(DexMethod method) {
+ addType(method.holder);
+ for (DexType parameterType : method.proto.parameters.values) {
+ addType(parameterType);
+ }
+ addType(method.proto.returnType);
+ Set<DexMethod> typeMethods = methods.get(method.holder);
+ if (typeMethods != null) {
+ typeMethods.add(method);
+ }
+ }
+
+ private void registerField(DexEncodedField field) {
+ registerTypeReference(field.field.type);
+ }
+
+ private void registerMethod(DexEncodedMethod method) {
+ DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method.method, method.method.holder);
+ if (superTarget != null) {
+ registerInvokeSuper(superTarget.method);
+ }
+ for (DexType type : method.method.proto.parameters.values) {
+ registerTypeReference(type);
+ }
+ registerTypeReference(method.method.proto.returnType);
+ method.registerCodeReferences(this);
+ }
+
+ private void registerSuperType(DexProgramClass clazz, DexType superType) {
+ registerTypeReference(superType);
+ // If clazz overrides any methods in superType, we should keep those as well.
+ clazz.forEachMethod(
+ method -> {
+ ResolutionResult resolutionResult = appInfo.resolveMethod(superType, method.method);
+ for (DexEncodedMethod dexEncodedMethod : resolutionResult.asListOfTargets()) {
+ addMethod(dexEncodedMethod.method);
+ }
+ });
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 3) {
+ System.out.println(USAGE);
+ return;
+ }
+ AndroidApp.Builder builder = AndroidApp.builder();
+ Path rtJar = Paths.get(args[0]);
+ builder.addLibraryFile(rtJar);
+ Path r8Jar = Paths.get(args[1]);
+ builder.addLibraryFile(r8Jar);
+ Path sampleJar = Paths.get(args[2]);
+ builder.addProgramFile(sampleJar);
+ Set<String> descriptors = new HashSet<>(getDescriptors(r8Jar));
+ descriptors.removeAll(getDescriptors(sampleJar));
+ PrintUses printUses = new PrintUses(descriptors, builder.build(), System.out);
+ printUses.analyze();
+ printUses.print();
+ if (printUses.errors > 0) {
+ System.err.println(printUses.errors + " errors");
+ System.exit(1);
+ }
+ }
+
+ private static Set<String> getDescriptors(Path path) throws IOException {
+ return new ArchiveClassFileProvider(path).getClassDescriptors();
+ }
+
+ private PrintUses(Set<String> descriptors, AndroidApp inputApp, PrintStream out)
+ throws Exception {
+ this.descriptors = descriptors;
+ this.out = out;
+ InternalOptions options = new InternalOptions();
+ application =
+ new ApplicationReader(inputApp, options, new Timing("PrintUses")).read().toDirect();
+ appInfo = new AppInfoWithSubtyping(application);
+ }
+
+ private void analyze() {
+ UseCollector useCollector = new UseCollector();
+ for (DexProgramClass dexProgramClass : application.classes()) {
+ useCollector.registerSuperType(dexProgramClass, dexProgramClass.superType);
+ for (DexType implementsType : dexProgramClass.interfaces.values) {
+ useCollector.registerSuperType(dexProgramClass, implementsType);
+ }
+ dexProgramClass.forEachMethod(useCollector::registerMethod);
+ dexProgramClass.forEachField(useCollector::registerField);
+ }
+ }
+
+ private void print() {
+ List<DexType> types = new ArrayList<>(this.types);
+ types.sort(Comparator.comparing(DexType::toSourceString));
+ for (DexType type : types) {
+ String typeName = type.toSourceString();
+ DexClass dexClass = application.definitionFor(type);
+ if (dexClass == null) {
+ error("Could not find definition for type " + type.toSourceString());
+ continue;
+ }
+ out.println(typeName);
+ List<DexMethod> methods = new ArrayList<>(this.methods.get(type));
+ List<String> methodDefinitions = new ArrayList<>(methods.size());
+ for (DexMethod method : methods) {
+ DexEncodedMethod encodedMethod = dexClass.lookupMethod(method);
+ if (encodedMethod == null) {
+ error("Could not find definition for method " + method.toSourceString());
+ continue;
+ }
+ methodDefinitions.add(getMethodSourceString(encodedMethod));
+ }
+ methodDefinitions.sort(Comparator.naturalOrder());
+ for (String encodedMethod : methodDefinitions) {
+ out.println(typeName + ": " + encodedMethod);
+ }
+ List<DexField> fields = new ArrayList<>(this.fields.get(type));
+ fields.sort(Comparator.comparing(DexField::toSourceString));
+ for (DexField field : fields) {
+ out.println(
+ typeName + ": " + field.type.toSourceString() + " " + field.name.toSourceString());
+ }
+ }
+ }
+
+ private void error(String message) {
+ out.println("# Error: " + message);
+ errors += 1;
+ }
+
+ private static String getMethodSourceString(DexEncodedMethod encodedMethod) {
+ DexMethod method = encodedMethod.method;
+ StringBuilder builder = new StringBuilder();
+ if (encodedMethod.accessFlags.isConstructor()) {
+ if (encodedMethod.accessFlags.isStatic()) {
+ builder.append("<clinit>");
+ } else {
+ String holderName = method.holder.toSourceString();
+ String constructorName = holderName.substring(holderName.lastIndexOf('.') + 1);
+ builder.append(constructorName);
+ }
+ } else {
+ builder
+ .append(method.proto.returnType.toSourceString())
+ .append(" ")
+ .append(method.name.toSourceString());
+ }
+ builder.append("(");
+ for (int i = 0; i < method.getArity(); i++) {
+ if (i != 0) {
+ builder.append(",");
+ }
+ builder.append(method.proto.parameters.values[i].toSourceString());
+ }
+ builder.append(")");
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 077e220..e42d8b0 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -287,7 +287,7 @@
if (options.proguardConfiguration.isPrintSeeds()) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes);
- RootSetBuilder.writeSeeds(appInfo.withLiveness(), out);
+ RootSetBuilder.writeSeeds(appInfo.withLiveness(), out, type -> true);
out.flush();
proguardSeedsData = bytes.toString();
}
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index 0e35847..28e1f93 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -28,38 +28,32 @@
return;
}
switch (args[0]) {
- case "r8":
- R8.main(shift(args));
- break;
- case "d8":
- D8.main(shift(args));
+ case "bisect":
+ Bisect.main(shift(args));
break;
case "compatdx":
CompatDx.main(shift(args));
break;
- case "dexfilemerger":
- DexFileMerger.main(shift(args));
- break;
- case "dexsplitter":
- DexSplitter.main(shift(args));
- break;
case "compatproguard":
CompatProguard.main(shift(args));
break;
+ case "d8":
+ D8.main(shift(args));
+ break;
case "d8logger":
D8Logger.main(shift(args));
break;
- case "disasm":
- Disassemble.main(shift(args));
- break;
- case "bisect":
- Bisect.main(shift(args));
+ case "dexfilemerger":
+ DexFileMerger.main(shift(args));
break;
case "dexsegments":
DexSegments.main(shift(args));
break;
- case "maindex":
- GenerateMainDexList.main(shift(args));
+ case "dexsplitter":
+ DexSplitter.main(shift(args));
+ break;
+ case "disasm":
+ Disassemble.main(shift(args));
break;
case "extractmarker":
ExtractMarker.main(shift(args));
@@ -67,6 +61,18 @@
case "jardiff":
JarDiff.main(shift(args));
break;
+ case "maindex":
+ GenerateMainDexList.main(shift(args));
+ break;
+ case "printseeds":
+ PrintSeeds.main(shift(args));
+ break;
+ case "printuses":
+ PrintUses.main(shift(args));
+ break;
+ case "r8":
+ R8.main(shift(args));
+ break;
default:
runDefault(args);
break;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 0ba40b3..fb3b067 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.2.20-dev";
+ public static final String LABEL = "1.2.21-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 015b8a3..0eed4a6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.conversion.CfSourceCode;
@@ -52,6 +53,10 @@
@Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ if (!builder.isGeneratingClassFiles()) {
+ // TODO(b/109789539): Implement this case (see JarSourceCode.buildPrelude()/buildPostlude()).
+ throw new Unimplemented("CfMultiANewArray to DEX backend");
+ }
int[] dimensions = state.popReverse(this.dimensions);
builder.addMultiNewArray(type, state.push(type).register, dimensions);
}
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 ffa34d1..b8e0bd8 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -225,8 +225,11 @@
ValueNumberGenerator generator,
Position callerPosition,
Origin origin) {
- assert !options.isGeneratingDex() || !encodedMethod.accessFlags.isSynchronized()
- : "Converting CfCode to IR not supported for DEX output of synchronized methods.";
+ // TODO(b/109789541): Implement CF->IR->DEX for synchronized methods.
+ if (options.isGeneratingDex() && encodedMethod.accessFlags.isSynchronized()) {
+ throw new Unimplemented(
+ "Converting CfCode to IR not supported for DEX output of synchronized methods.");
+ }
CfSourceCode source = new CfSourceCode(this, encodedMethod, callerPosition, origin);
IRBuilder builder =
(generator == null)
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 454ed29..afc2969 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -533,7 +533,8 @@
return MemberType.OBJECT;
case Opcodes.BALOAD:
case Opcodes.BASTORE:
- return MemberType.BOOLEAN; // TODO: Distinguish byte and boolean.
+ // TODO(b/109788783): Distinguish byte and boolean.
+ return MemberType.BOOLEAN;
case Opcodes.CALOAD:
case Opcodes.CASTORE:
return MemberType.CHAR;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index e68e3d5..ca79b0f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -324,7 +324,7 @@
buildArgumentInstructions(builder);
recordStateForTarget(0, state.getSnapshot());
// TODO: addDebugLocalUninitialized + addDebugLocalStart for non-argument locals live at 0
- // TODO: Generate method synchronization
+ // TODO(b/109789541): Generate method synchronization for DEX backend.
inPrelude = false;
}
@@ -353,7 +353,7 @@
@Override
public void buildPostlude(IRBuilder builder) {
- // Since we're generating classfiles, we never need to synthesize monitor enter/exit.
+ // TODO(b/109789541): Generate method synchronization for DEX backend.
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index bd3442c..ae57f48 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1707,10 +1707,8 @@
return values;
}
}
- block =
- block.exit().isGoto() && !visitedBlocks.contains(block.exit().asGoto().getTarget())
- ? block.exit().asGoto().getTarget()
- : null;
+ BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
+ block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
it = block != null ? block.listIterator() : null;
} while (it != null);
return null;
@@ -1801,7 +1799,9 @@
// Second pass: remove all the array put instructions for the array for which we have
// inserted a fill array data instruction instead.
if (!storesToRemoveForArray.isEmpty()) {
+ Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
do {
+ visitedBlocks.add(block);
it = block.listIterator();
while (it.hasNext()) {
Instruction instruction = it.next();
@@ -1823,7 +1823,8 @@
}
}
}
- block = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
+ BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
+ block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
} while (block != null);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 085d9b3..51feb9d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -734,6 +734,7 @@
private void transitionDefaultMethodsForInstantiatedClass(DexType iface, DexType instantiatedType,
ScopedDexMethodSet seen) {
DexClass clazz = appInfo.definitionFor(iface);
+ assert clazz != null : "Missing class " + iface.toSourceString();
assert clazz.accessFlags.isInterface();
SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(iface);
if (reachableMethods != null) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 700e146..c860d83 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -43,6 +43,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
public class RootSetBuilder {
@@ -441,17 +442,28 @@
}
// TODO(67934426): Test this code.
- public static void writeSeeds(AppInfoWithLiveness appInfo, PrintStream out) {
+ public static void writeSeeds(
+ AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
for (DexItem seed : appInfo.getPinnedItems()) {
if (seed instanceof DexType) {
- out.println(seed.toSourceString());
+ if (include.test((DexType) seed)) {
+ out.println(seed.toSourceString());
+ }
} else if (seed instanceof DexField) {
DexField field = ((DexField) seed);
- out.println(
- field.clazz.toSourceString() + ": " + field.type.toSourceString() + " " + field.name
- .toSourceString());
+ if (include.test(field.clazz)) {
+ out.println(
+ field.clazz.toSourceString()
+ + ": "
+ + field.type.toSourceString()
+ + " "
+ + field.name.toSourceString());
+ }
} else if (seed instanceof DexMethod) {
DexMethod method = (DexMethod) seed;
+ if (!include.test(method.holder)) {
+ continue;
+ }
out.print(method.holder.toSourceString() + ": ");
DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
if (encodedMethod.accessFlags.isConstructor()) {
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index 37aa208..499ba47 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -54,17 +54,18 @@
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
try (InputStream stream = zipFile.getInputStream(entry)) {
- Path name = Paths.get(entry.getName());
- Origin entryOrigin = new ArchiveEntryOrigin(entry.getName(), origin);
- if (archive.matchesFile(name)) {
- if (isDexFile(name)) {
+ String name = entry.getName();
+ Path path = Paths.get(name);
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+ if (archive.matchesFile(path)) {
+ if (isDexFile(path)) {
if (!ignoreDexInArchive) {
ProgramResource resource =
OneShotByteResource.create(
Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null);
dexResources.add(resource);
}
- } else if (isClassFile(name)) {
+ } else if (isClassFile(path)) {
String descriptor = DescriptorUtils.guessTypeDescriptor(name);
ProgramResource resource =
OneShotByteResource.create(
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index fa045e6..a846ea7 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -341,22 +341,35 @@
}
}
- // Guess class descriptor from location of the class file.
+ /**
+ * Guess class descriptor from location of the class file on the file system
+ *
+ * @param name Path of the file to convert to the corresponding descriptor
+ * @return java class descriptor
+ */
public static String guessTypeDescriptor(Path name) {
- return guessTypeDescriptor(name.toString());
+ String fileName = name.toString();
+ if (File.separatorChar != '/') {
+ fileName = fileName.replace(File.separatorChar, '/');
+ }
+ return guessTypeDescriptor(fileName);
}
- // Guess class descriptor from location of the class file.
+ /**
+ * Guess class descriptor from location of the class file. This method assumes that the
+ * name uses '/' as the separator. Therefore, this should not be the name of a file
+ * on a file system.
+ *
+ * @param name the location of the class file to convert to descriptor
+ * @return java class descriptor
+ */
public static String guessTypeDescriptor(String name) {
assert name != null;
assert name.endsWith(CLASS_EXTENSION) :
"Name " + name + " must have " + CLASS_EXTENSION + " suffix";
- String fileName =
- File.separatorChar == '/' ? name.toString() :
- name.toString().replace(File.separatorChar, '/');
- String descriptor = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
+ String descriptor = name.substring(0, name.length() - CLASS_EXTENSION.length());
if (descriptor.contains(".")) {
- throw new CompilationError("Unexpected class file name: " + fileName);
+ throw new CompilationError("Unexpected class file name: " + name);
}
return 'L' + descriptor + ';';
}
diff --git a/src/test/examples/barray/BArray.java b/src/test/examples/barray/BArray.java
index 0ca6505..26ac5f8 100644
--- a/src/test/examples/barray/BArray.java
+++ b/src/test/examples/barray/BArray.java
@@ -6,20 +6,59 @@
public class BArray {
public static void main(String[] args) {
+ System.out.println("null boolean: " + readNullBooleanArray());
+ System.out.println("null byte: " + readNullByteArray());
+ System.out.println("boolean: " + readBooleanArray(writeBooleanArray(args)));
+ System.out.println("byte: " + readByteArray(writeByteArray(args)));
+ }
+
+ public static boolean readNullBooleanArray() {
boolean[] boolArray = null;
+ try {
+ return boolArray[0] || boolArray[1];
+ } catch (Throwable e) {
+ return true;
+ }
+ }
+
+ public static byte readNullByteArray() {
byte[] byteArray = null;
- boolean bool;
- byte bits;
try {
- bool = boolArray[0] || boolArray[1];
+ return byteArray[0];
} catch (Throwable e) {
- bool = true;
+ return 42;
}
+ }
+
+ public static boolean[] writeBooleanArray(String[] args) {
+ boolean[] array = new boolean[args.length];
+ for (int i = 0; i < args.length; i++) {
+ array[i] = args[i].length() == 42;
+ }
+ return array;
+ }
+
+ public static byte[] writeByteArray(String[] args) {
+ byte[] array = new byte[args.length];
+ for (int i = 0; i < args.length; i++) {
+ array[i] = (byte) args[i].length();
+ }
+ return array;
+ }
+
+ public static boolean readBooleanArray(boolean[] boolArray) {
try {
- bits = byteArray[0];
+ return boolArray[0] || boolArray[1];
} catch (Throwable e) {
- bits = 42;
+ return true;
}
- System.out.println("bits " + bits + " and bool " + bool);
+ }
+
+ public static byte readByteArray(byte[] byteArray) {
+ try {
+ return byteArray[0];
+ } catch (Throwable e) {
+ return 42;
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 9cc2b48..8038c98 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -64,6 +64,10 @@
Assert.assertEquals(javaResult.stdout, d8Result.stdout);
Assert.assertEquals(javaResult.stdout, r8Result.stdout);
Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
+ Assert.assertEquals(0, javaResult.exitCode);
+ Assert.assertEquals(0, d8Result.exitCode);
+ Assert.assertEquals(0, r8Result.exitCode);
+ Assert.assertEquals(0, r8ShakenResult.exitCode);
}
protected void ensureR8FailsWithCompilationError(String main, byte[]... classes)
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index fe5ae72..4afd0c2 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -148,6 +148,11 @@
if (output == Output.CF && getFailingCompileCf().contains(mainClass)) {
thrown.expect(Throwable.class);
}
+ if (frontend == Frontend.CF
+ && output == Output.DEX
+ && getFailingCompileCfToDex().contains(mainClass)) {
+ thrown.expect(Throwable.class);
+ }
OutputMode outputMode = output == Output.CF ? OutputMode.ClassFile : OutputMode.DexIndexed;
switch (compiler) {
case D8: {
@@ -215,6 +220,12 @@
thrown = ExpectedException.none();
}
+ if (frontend == Frontend.CF
+ && output == Output.DEX
+ && getFailingRunCfToDex().contains(mainClass)) {
+ thrown.expect(Throwable.class);
+ }
+
if (output == Output.CF) {
ToolHelper.ProcessResult result = ToolHelper.runJava(generated, mainClass);
if (result.exitCode != 0) {
@@ -257,6 +268,11 @@
protected abstract Map<String, TestCondition> getFailingRunCf();
+ protected abstract Set<String> getFailingCompileCfToDex();
+
+ // TODO(mathiasr): Add CompilerSet for CfToDex so we can fold this into getFailingRun().
+ protected abstract Set<String> getFailingRunCfToDex();
+
protected abstract Set<String> getFailingCompileCf();
protected abstract Set<String> getFailingOutputCf();
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
index 97c8624..dc94a71 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
@@ -49,6 +49,16 @@
}
@Override
+ protected Set<String> getFailingCompileCfToDex() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ protected Set<String> getFailingRunCfToDex() {
+ return Collections.emptySet();
+ }
+
+ @Override
protected Set<String> getFailingCompileCf() {
return Collections.emptySet();
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index d8b886c..4ee928b 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -109,6 +109,14 @@
test,
Frontend.CF,
Output.CF));
+ fullTestList.add(
+ makeTest(
+ Input.JAVAC_ALL,
+ CompilerUnderTest.R8,
+ CompilationMode.RELEASE,
+ test,
+ Frontend.CF,
+ Output.DEX));
}
return fullTestList;
}
@@ -143,6 +151,29 @@
}
@Override
+ protected Set<String> getFailingCompileCfToDex() {
+ return new ImmutableSet.Builder<String>()
+ // TODO(b/109788783): Implement byte/boolean distinction for array load/store.
+ .add("arrayaccess.ArrayAccess")
+ .add("barray.BArray")
+ .add("filledarray.FilledArray")
+ .build();
+ }
+
+ @Override
+ protected Set<String> getFailingRunCfToDex() {
+ return new ImmutableSet.Builder<String>()
+ // TODO(b/109789541): Implement method synchronization for DEX backend.
+ .add("sync.Sync")
+ // TODO(b/109789539): Implement CfMultiANewArray.buildIR() for DEX backend.
+ .add("newarray.NewArray")
+ .add("trycatch.TryCatch")
+ .add("regress_70737019.Test")
+ .add("regress_72361252.Test")
+ .build();
+ }
+
+ @Override
protected Set<String> getFailingCompileCf() {
return new ImmutableSet.Builder<String>()
.build();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java b/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java
new file mode 100644
index 0000000..3f7bcfb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java
@@ -0,0 +1,39 @@
+// 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.ir.optimize;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import org.junit.Test;
+
+public class B87341268 extends TestBase {
+ @Test
+ public void test() throws Exception {
+ AndroidApp app = compileWithD8(readClasses(TestClass.class));
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+ assertThat(clazz, isPresent());
+ }
+}
+
+class TestClass {
+ int loop(String arg) {
+ long[] array = { 0L, 1L, 2L };
+ int length = -1;
+ while (true) {
+ try {
+ length = arg.length();
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ break;
+ }
+ }
+ return length;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump_WithPhi.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump_WithPhi.java
index 8a6bdcf..bfb0674 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump_WithPhi.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump_WithPhi.java
@@ -231,21 +231,25 @@
mv.visitJumpInsn(IFEQ, l7_b1);
mv.visitLabel(l7_b0);
mv.visitInsn(ACONST_NULL);
+ // At this point, NULL flows to l7_join.
mv.visitJumpInsn(GOTO, l7_join);
mv.visitLabel(l7_b1);
mv.visitVarInsn(ALOAD, 10);
- // Swap the new-instance or null, with the byte-array.
+ // At this point, an uninitialized String flows to l7_join.
mv.visitLabel(l7_join);
+ // At this point, stack contains [staticByteArray, NULL/uninitialized]
mv.visitInsn(SWAP);
- // Load the new-instacne and swap again with the byte-array.
+ // At this point, stack contains [NULL/uninitialized, staticByteArray]
mv.visitVarInsn(ALOAD, 10);
+ // At this point, stack contains [NULL/uninitialized, staticByteArray, uninitialized]
mv.visitInsn(SWAP);
+ // At this point, stack contains [NULL/uninitialized, uninitialized, staticByteArray]
mv.visitInsn(ICONST_0);
- // Invoke special will now always be on the new-instance as receiver.
+ // At this point, stack contains [NULL/uninitialized, uninitialized, staticByteArray, 0]
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/String", "<init>", "([BI)V", false);
- // Return will be either new-instance or null.
+ // Just before ARETURN, stack contains [NULL/String].
// This will force a non-trivial phi of the new-instance on this block, ie, prior to <init>.
- // This phi must remain.
+ // This phi must remain since it is not trivial.
mv.visitInsn(ARETURN);
mv.visitLabel(l8);
mv.visitVarInsn(ILOAD, 0);
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java
index ff7da8f..ccf6467 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java
@@ -31,13 +31,14 @@
public static void compare(String output, int iterations) {
String expected = "java.security.SecureRandom";
- if (output.equals(expected)) {
+ if (expected.equals(output)) {
return;
}
System.out.println(
"After " + iterations + " iterations, expected \"" +
expected + "\", but got \"" + output + "\"");
- System.exit(1);
+ // Exit with code 0 to allow test to use ensureSameOutput().
+ System.exit(0);
}
public static void compareHash(int a, int b, byte[] c, int iterations) {
@@ -53,6 +54,7 @@
System.out.println("staticIntB: " + b);
System.out.print("staticIntByteArray: ");
printByteArray(c);
- System.exit(1);
+ // Exit with code 0 to allow test to use ensureSameOutput().
+ System.exit(0);
}
}
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
index 3c7c465..3a56004 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
@@ -3,8 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.regress.b78493232;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.AndroidApp;
import org.junit.Test;
// Variant of Regress78493232, but where the new-instance is forced to flow to a non-trivial phi
@@ -15,6 +22,57 @@
public void test() throws Exception {
// Run test on JVM and ART(x86) to ensure expected behavior.
// Running the same test on an ARM JIT causes errors.
+
+ // TODO(b/80118070): Remove this if-statement when fixed.
+ if (ToolHelper.getDexVm().getVersion() != Version.V5_1_1) {
+ AndroidApp app =
+ buildAndroidApp(
+ Regress78493232Dump_WithPhi.dump(),
+ ToolHelper.getClassAsBytes(Regress78493232Utils.class));
+ ProcessResult javaResult =
+ runOnJava(
+ Regress78493232Dump_WithPhi.CLASS_NAME,
+ Regress78493232Dump_WithPhi.dump(),
+ ToolHelper.getClassAsBytes(Regress78493232Utils.class));
+ ProcessResult d8Result =
+ runOnArtRaw(compileWithD8(app), Regress78493232Dump_WithPhi.CLASS_NAME);
+ ProcessResult r8Result =
+ runOnArtRaw(compileWithR8(app), Regress78493232Dump_WithPhi.CLASS_NAME);
+ String proguardConfig =
+ keepMainProguardConfiguration(Regress78493232Dump_WithPhi.CLASS_NAME)
+ + "-dontobfuscate\n";
+ ProcessResult r8ShakenResult =
+ runOnArtRaw(compileWithR8(app, proguardConfig), Regress78493232Dump_WithPhi.CLASS_NAME);
+ assertEquals(
+ "After 0 iterations, expected \"java.security.SecureRandom\", but got \"null\"\n",
+ javaResult.stdout);
+ assertEquals(0, javaResult.exitCode);
+ switch (ToolHelper.getDexVm().getVersion()) {
+ case V4_0_4:
+ case V4_4_4:
+ case V7_0_0:
+ case DEFAULT:
+ assertNotEquals(-1, d8Result.stderr.indexOf("java.lang.VerifyError"));
+ assertNotEquals(-1, r8Result.stderr.indexOf("java.lang.VerifyError"));
+ assertNotEquals(-1, r8ShakenResult.stderr.indexOf("java.lang.VerifyError"));
+ assertEquals(1, d8Result.exitCode);
+ assertEquals(1, r8Result.exitCode);
+ assertEquals(1, r8ShakenResult.exitCode);
+ break;
+ case V6_0_1:
+ assertEquals("Completed successfully after 1000 iterations\n", d8Result.stdout);
+ assertEquals("Completed successfully after 1000 iterations\n", r8Result.stdout);
+ assertEquals("Completed successfully after 1000 iterations\n", r8ShakenResult.stdout);
+ assertEquals(0, d8Result.exitCode);
+ assertEquals(0, r8Result.exitCode);
+ assertEquals(0, r8ShakenResult.exitCode);
+ break;
+ case V5_1_1:
+ default:
+ throw new Unreachable();
+ }
+ return;
+ }
ensureSameOutput(
Regress78493232Dump_WithPhi.CLASS_NAME,
Regress78493232Dump_WithPhi.dump(),
diff --git a/src/test/java/com/android/tools/r8/utils/DescriptorUtilsTest.java b/src/test/java/com/android/tools/r8/utils/DescriptorUtilsTest.java
index 58180e3..7ad9163 100644
--- a/src/test/java/com/android/tools/r8/utils/DescriptorUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/DescriptorUtilsTest.java
@@ -6,7 +6,10 @@
import static org.junit.Assert.assertEquals;
+import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import org.junit.Test;
public class DescriptorUtilsTest {
@@ -67,4 +70,16 @@
assertEquals("java.lang.Object", DescriptorUtils.descriptorToJavaType("Ljava/lang/Object;"));
assertEquals("a.b.C", DescriptorUtils.descriptorToJavaType("La/b/C;"));
}
+
+ @Test
+ public void guessClassDescriptor() {
+ String obj = "java/lang/Object.class";
+ assertEquals("Ljava/lang/Object;", DescriptorUtils.guessTypeDescriptor(obj));
+ String objBackslash = "java\\lang\\Object.class";
+ assertEquals("Ljava\\lang\\Object;", DescriptorUtils.guessTypeDescriptor(objBackslash));
+ String objFileSeparatorChar =
+ "java" + File.separatorChar + "lang" + File.separatorChar + "Object.class";
+ assertEquals("Ljava/lang/Object;",
+ DescriptorUtils.guessTypeDescriptor(Paths.get(objFileSeparatorChar)));
+ }
}
diff --git a/tools/api_sample_coverage.py b/tools/api_sample_coverage.py
new file mode 100755
index 0000000..b14ad1a
--- /dev/null
+++ b/tools/api_sample_coverage.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# 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.
+
+'''
+Compare the R8 API used by the API usage sample to the API kept by @Keep.
+'''
+
+import argparse
+import os
+import subprocess
+import utils
+
+parser = argparse.ArgumentParser(description=__doc__.strip(),
+ formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('-o', '--output-dir')
+
+API_SAMPLE_JAR = 'tests/d8_api_usage_sample.jar'
+
+
+def main(output_dir=None):
+ if output_dir is None:
+ output_dir = ''
+
+ printseeds_path = os.path.join(output_dir, 'keep-seeds.txt')
+ printseeds_args = [
+ 'java', '-jar', utils.R8_JAR, 'printseeds',
+ utils.RT_JAR, utils.R8_JAR, utils.R8LIB_KEEP_RULES,
+ ]
+ write_sorted_lines(printseeds_args, printseeds_path)
+
+ printuses_path = os.path.join(output_dir, 'sample-uses.txt')
+ printuses_args = [
+ 'java', '-jar', utils.R8_JAR, 'printuses',
+ utils.RT_JAR, utils.R8_JAR, API_SAMPLE_JAR,
+ ]
+ write_sorted_lines(printuses_args, printuses_path)
+
+ print_diff(printseeds_path, printuses_path)
+
+
+def write_sorted_lines(cmd_args, output_path):
+ utils.PrintCmd(cmd_args)
+ output_lines = subprocess.check_output(cmd_args).splitlines(True)
+ print("Write output to %s" % output_path)
+ output_lines.sort()
+ with open(output_path, 'w') as fp:
+ for line in output_lines:
+ fp.write(line)
+
+
+def print_diff(printseeds_path, printuses_path):
+ with open(printseeds_path) as fp:
+ seeds = set(fp.read().splitlines())
+ with open(printuses_path) as fp:
+ uses = set(fp.read().splitlines())
+ only_in_seeds = seeds - uses
+ only_in_uses = uses - seeds
+ if only_in_seeds:
+ print("%s lines with '-' are marked @Keep " % len(only_in_seeds) +
+ "but not used by sample.")
+ if only_in_uses:
+ print("%s lines with '+' are used by sample " % len(only_in_uses) +
+ "but are missing @Keep annotations.")
+ for line in sorted(only_in_seeds):
+ print('-' + line)
+ for line in sorted(only_in_uses):
+ print('+' + line)
+ if not only_in_seeds and not only_in_uses:
+ print('Sample uses the entire set of members marked @Keep. Well done!')
+
+
+if __name__ == '__main__':
+ main(**vars(parser.parse_args()))
diff --git a/tools/build_r8lib.py b/tools/build_r8lib.py
index abaa619..c855bc5 100755
--- a/tools/build_r8lib.py
+++ b/tools/build_r8lib.py
@@ -18,7 +18,6 @@
formatter_class=argparse.RawTextHelpFormatter)
SAMPLE_JAR = os.path.join(utils.REPO_ROOT, 'tests/d8_api_usage_sample.jar')
-KEEP_RULES = os.path.join(utils.REPO_ROOT, 'src/main/keep.txt')
R8LIB_JAR = os.path.join(utils.LIBS, 'r8lib.jar')
R8LIB_MAP_FILE = os.path.join(utils.LIBS, 'r8lib-map.txt')
@@ -34,7 +33,7 @@
'--lib', utils.RT_JAR,
utils.R8_JAR,
'--output', R8LIB_JAR,
- '--pg-conf', KEEP_RULES,
+ '--pg-conf', utils.R8LIB_KEEP_RULES,
'--pg-map-output', R8LIB_MAP_FILE))
diff --git a/tools/printseeds.py b/tools/printseeds.py
new file mode 100755
index 0000000..4f389d5
--- /dev/null
+++ b/tools/printseeds.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('printseeds', sys.argv[1:]))
diff --git a/tools/printuses.py b/tools/printuses.py
new file mode 100755
index 0000000..17d3df1
--- /dev/null
+++ b/tools/printuses.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('printuses', sys.argv[1:]))
diff --git a/tools/utils.py b/tools/utils.py
index 859da97..851f3bd 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -37,6 +37,7 @@
MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
GENERATED_LICENSE = os.path.join(GENERATED_LICENSE_DIR, 'LICENSE')
RT_JAR = os.path.join(REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
+R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
def PrintCmd(s):
if type(s) is list: