Merge "Fix potential issues related to CFG printing"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 6d405b5..6988b06 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -176,10 +176,11 @@
CompilationResult output =
new CompilationResult(
- new ApplicationWriter(app, appInfo, options, NamingLens.getIdentityLens(), null)
- .write(null, executor),
- app,
- appInfo);
+ new ApplicationWriter(
+ app, appInfo, options, null, NamingLens.getIdentityLens(), null)
+ .write(null, executor),
+ app,
+ appInfo);
options.printWarnings();
return output;
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index dc13143..3f6fcd4 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -37,6 +37,7 @@
import com.android.tools.r8.shaking.TreePruner;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.PackageDistribution;
import com.android.tools.r8.utils.ThreadUtils;
@@ -77,13 +78,15 @@
ExecutorService executorService,
DexApplication application,
AppInfo appInfo,
+ byte[] deadCode,
NamingLens namingLens,
byte[] proguardSeedsData,
PackageDistribution packageDistribution,
InternalOptions options)
throws ExecutionException {
try {
- return new ApplicationWriter(application, appInfo, options, namingLens, proguardSeedsData)
+ return new ApplicationWriter(
+ application, appInfo, options, deadCode, namingLens, proguardSeedsData)
.write(packageDistribution, executorService);
} catch (IOException e) {
throw new RuntimeException("Cannot write dex application", e);
@@ -185,7 +188,7 @@
}
}
- static CompilationResult runForTesting(
+ private static CompilationResult runForTesting(
AndroidApp app,
InternalOptions options,
ExecutorService executor)
@@ -342,6 +345,7 @@
executorService,
application,
appInfo,
+ application.deadCode,
namingLens,
proguardSeedsData,
packageDistribution,
@@ -391,42 +395,46 @@
if (options.printMapping && !options.skipMinification) {
assert outputApp.hasProguardMap();
try (Closer closer = Closer.create()) {
- OutputStream mapOut = openPathWithDefault(
+ OutputStream mapOut = FileUtils.openPathWithDefault(
closer,
options.printMappingFile,
- System.out);
+ System.out,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
outputApp.writeProguardMap(closer, mapOut);
}
}
if (options.printSeeds) {
assert outputApp.hasProguardSeeds();
try (Closer closer = Closer.create()) {
- OutputStream seedsOut = openPathWithDefault(closer, options.seedsFile, System.out);
+ OutputStream seedsOut = FileUtils.openPathWithDefault(
+ closer,
+ options.seedsFile,
+ System.out,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
outputApp.writeProguardSeeds(closer, seedsOut);
}
}
if (options.printMainDexList && outputApp.hasMainDexList()) {
try (Closer closer = Closer.create()) {
OutputStream mainDexOut =
- openPathWithDefault(closer, options.printMainDexListFile, System.out);
+ FileUtils.openPathWithDefault(
+ closer,
+ options.printMainDexListFile,
+ System.out,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
outputApp.writeMainDexList(closer, mainDexOut);
}
}
- }
-
- private static OutputStream openPathWithDefault(Closer closer,
- Path file,
- PrintStream defaultOutput) throws IOException {
- OutputStream mapOut;
- if (file == null) {
- mapOut = defaultOutput;
- } else {
- mapOut =
- Files.newOutputStream(
- file, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
- closer.register(mapOut);
+ if (options.printUsage && outputApp.hasDeadCode()) {
+ try (Closer closer = Closer.create()) {
+ OutputStream deadCodeOut = FileUtils.openPathWithDefault(
+ closer,
+ options.printUsageFile,
+ System.out,
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ outputApp.writeDeadCode(closer, deadCodeOut);
+ }
}
- return mapOut;
}
/**
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 d7b23c5..4fe3773 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -177,7 +177,7 @@
throws IOException, ExecutionException {
InternalOptions options = new InternalOptions();
AppInfo appInfo = new AppInfo(app);
- ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null);
+ ApplicationWriter writer = new ApplicationWriter(app, appInfo, options, null, null, null);
AndroidApp outApp = writer.write(null, executor);
outApp.writeToDirectory(output, OutputMode.Indexed);
}
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectState.java b/src/main/java/com/android/tools/r8/bisect/BisectState.java
index 654f1ec..489c78d 100644
--- a/src/main/java/com/android/tools/r8/bisect/BisectState.java
+++ b/src/main/java/com/android/tools/r8/bisect/BisectState.java
@@ -318,10 +318,7 @@
}
private static List<DexProgramClass> getSortedClasses(DexApplication app) {
- List<DexProgramClass> classes = new ArrayList<>();
- for (DexProgramClass clazz : app.classes()) {
- classes.add(clazz);
- }
+ List<DexProgramClass> classes = new ArrayList<>(app.classes());
app.dexItemFactory.sort(NamingLens.getIdentityLens());
classes.sort((a, b) -> a.type.compareTo(b.type));
app.dexItemFactory.resetSortedIndices();
diff --git a/src/main/java/com/android/tools/r8/code/Format4rcc.java b/src/main/java/com/android/tools/r8/code/Format4rcc.java
index 7072805..ceac4c8 100644
--- a/src/main/java/com/android/tools/r8/code/Format4rcc.java
+++ b/src/main/java/com/android/tools/r8/code/Format4rcc.java
@@ -56,7 +56,7 @@
return false;
}
Format4rcc o = (Format4rcc) other;
- return o.AA == AA && o.CCCC == CCCC && o.BBBB.equals(BBBB) & o.HHHH.equals(HHHH);
+ return o.AA == AA && o.CCCC == CCCC && o.BBBB.equals(BBBB) && o.HHHH.equals(HHHH);
}
public String toString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
index 9b102b2..e6a16f5 100644
--- a/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
+++ b/src/main/java/com/android/tools/r8/code/InvokePolymorphicRange.java
@@ -39,7 +39,7 @@
}
public DexMethod getMethod() {
- return (DexMethod) BBBB;
+ return BBBB;
}
@Override
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 39f741f..c9da10b 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -42,6 +42,7 @@
public final DexApplication application;
public final AppInfo appInfo;
+ public final byte[] deadCode;
public final NamingLens namingLens;
public final byte[] proguardSeedsData;
public final InternalOptions options;
@@ -107,6 +108,7 @@
DexApplication application,
AppInfo appInfo,
InternalOptions options,
+ byte[] deadCode,
NamingLens namingLens,
byte[] proguardSeedsData) {
assert application != null;
@@ -114,6 +116,7 @@
this.appInfo = appInfo;
assert options != null;
this.options = options;
+ this.deadCode = deadCode;
this.namingLens = namingLens;
this.proguardSeedsData = proguardSeedsData;
}
@@ -171,6 +174,9 @@
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for future.", e);
}
+ if (deadCode != null) {
+ builder.setDeadCode(deadCode);
+ }
// Write the proguard map file after writing the dex files, as the map writer traverses
// the DexProgramClass structures, which are destructively updated during dex file writing.
byte[] proguardMapResult = writeProguardMapFile();
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 2c9366c..0d2edcb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import com.google.common.primitives.Bytes;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
@@ -38,6 +39,7 @@
private LibraryClassCollection libraryClasses;
public final ImmutableSet<DexType> mainDexList;
+ public final byte[] deadCode;
private final ClassNameMapper proguardMap;
@@ -55,6 +57,7 @@
ClasspathClassCollection classpathClasses,
LibraryClassCollection libraryClasses,
ImmutableSet<DexType> mainDexList,
+ byte[] deadCode,
DexItemFactory dexItemFactory,
DexString highestSortingString,
Timing timing) {
@@ -64,6 +67,7 @@
this.classpathClasses = classpathClasses;
this.libraryClasses = libraryClasses;
this.mainDexList = mainDexList;
+ this.deadCode = deadCode;
this.dexItemFactory = dexItemFactory;
this.highestSortingString = highestSortingString;
this.timing = timing;
@@ -305,16 +309,18 @@
private LibraryClassCollection libraryClasses;
public final DexItemFactory dexItemFactory;
- public ClassNameMapper proguardMap;
+ ClassNameMapper proguardMap;
private final Timing timing;
- public DexString highestSortingString;
+ DexString highestSortingString;
+ private byte[] deadCode;
private final Set<DexType> mainDexList = Sets.newIdentityHashSet();
public Builder(DexItemFactory dexItemFactory, Timing timing) {
this.programClasses = new ArrayList<>();
this.dexItemFactory = dexItemFactory;
this.timing = timing;
+ this.deadCode = null;
this.classpathClasses = null;
this.libraryClasses = null;
}
@@ -328,6 +334,7 @@
highestSortingString = application.highestSortingString;
dexItemFactory = application.dexItemFactory;
mainDexList.addAll(application.mainDexList);
+ deadCode = application.deadCode;
}
public synchronized Builder setProguardMap(ClassNameMapper proguardMap) {
@@ -343,6 +350,19 @@
return this;
}
+ public Builder appendDeadCode(byte[] deadCodeAtAnotherRound) {
+ if (deadCodeAtAnotherRound == null) {
+ return this;
+ }
+ if (this.deadCode == null) {
+ this.deadCode = deadCodeAtAnotherRound;
+ return this;
+ }
+ // Concatenate existing byte[] and the given byte[].
+ this.deadCode = Bytes.concat(this.deadCode, deadCodeAtAnotherRound);
+ return this;
+ }
+
public synchronized Builder setHighestSortingString(DexString value) {
highestSortingString = value;
return this;
@@ -389,6 +409,7 @@
classpathClasses,
libraryClasses,
ImmutableSet.copyOf(mainDexList),
+ deadCode,
dexItemFactory,
highestSortingString,
timing);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index 4410f2a..39ec7d0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -9,6 +9,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
/**
* Builder to construct a "per position" representation of the debug information.
@@ -160,8 +161,9 @@
private ImmutableMap<Integer, DebugLocalInfo> getLocals() {
ImmutableMap.Builder<Integer, DebugLocalInfo> builder = ImmutableMap.builder();
- for (Integer register : locals.keySet()) {
- LocalEntry entry = locals.get(register);
+ for (Entry<Integer, LocalEntry> mapEntry : locals.entrySet()) {
+ Integer register = mapEntry.getKey();
+ LocalEntry entry = mapEntry.getValue();
if (entry.current != null) {
builder.put(register, entry.current);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index db4a2ab..572bea9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -376,7 +376,7 @@
public void registerReachableDefinitions(UseRegistry registry) {
if (code != null) {
if (Log.ENABLED) {
- Log.verbose((Class) getClass(), "Registering definitions reachable from `%s`.", method);
+ Log.verbose(getClass(), "Registering definitions reachable from `%s`.", method);
}
code.registerReachableDefinitions(registry);
}
diff --git a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
index 6c7f3b3..d3d36e5 100644
--- a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
+++ b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
@@ -30,7 +30,7 @@
* as many entries in the index as there are files (we only expand when we need to). If we lookup
* the value of an entry that is out of bounds it is equivalent to {@link #UNASSOCIATED_VALUE}
*
- * <p>This field is initialized on first write in {@link #updateVirtualFileData(int, int)}}. It
+ * <p>This field is initialized on first write in {@link #updateVirtualFileData(int)}}. It
* is assumed that multiple files are processed concurrently and thus the allocation of the
* array is synchronized. However, for any a given file id, sequential access is assumed.
*/
@@ -106,7 +106,7 @@
* Assigns an actual index for this item in the given file.
*
* <p>May only be used after this item has been assigned to the file using {@link
- * #assignToVirtualFile(int, int)}.
+ * #assignToVirtualFile(int)}.
*/
public void assignVirtualFileIndex(int virtualFileId, int index) {
assert virtualFileIndexes != null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 26a85b9..290a1a2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -15,7 +15,6 @@
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -143,7 +142,7 @@
public void removeSuccessor(BasicBlock block) {
int index = successors.indexOf(block);
assert index >= 0 : "removeSuccessor did not find the successor to remove";
- removeSuccessorsByIndex(Arrays.asList(index));
+ removeSuccessorsByIndex(Collections.singletonList(index));
}
public void removePredecessor(BasicBlock block) {
@@ -469,7 +468,7 @@
successor.predecessors.add(this);
}
- private boolean allPredecessorsDominated(BasicBlock block, DominatorTree dominator) {
+ private static boolean allPredecessorsDominated(BasicBlock block, DominatorTree dominator) {
for (BasicBlock pred : block.predecessors) {
if (!dominator.dominatedBy(pred, block)) {
return false;
@@ -478,7 +477,7 @@
return true;
}
- private boolean blocksClean(List<BasicBlock> blocks) {
+ private static boolean blocksClean(List<BasicBlock> blocks) {
blocks.forEach((b) -> {
assert b.predecessors.size() == 0;
assert b.successors.size() == 0;
@@ -591,7 +590,7 @@
// The proper incoming register for a catch successor (that is otherwise shadowed by the out-value
// of a throwing instruction) is stored at the negative register-index in the definitions map.
// (See readCurrentDefinition/writeCurrentDefinition/updateCurrentDefinition).
- private int onThrowValueRegister(int register) {
+ private static int onThrowValueRegister(int register) {
return -(register + 1);
}
@@ -713,16 +712,16 @@
return incompletePhis.keySet();
}
- private void appendBasicBlockList(
+ private static void appendBasicBlockList(
StringBuilder builder, List<BasicBlock> list, Function<BasicBlock, String> postfix) {
if (list.size() > 0) {
for (BasicBlock block : list) {
builder.append(block.number >= 0 ? block.number : "<unknown>");
builder.append(postfix.apply(block));
- builder.append(" ");
+ builder.append(' ');
}
} else {
- builder.append("-");
+ builder.append('-');
}
}
@@ -748,7 +747,7 @@
builder.append(number);
builder.append(" (");
builder.append(System.identityHashCode(this));
- builder.append(")");
+ builder.append(')');
builder.append(", pred-counts: " + predecessors.size());
if (unfilledPredecessorsCount > 0) {
builder.append(" (" + unfilledPredecessorsCount + " unfilled)");
@@ -756,10 +755,10 @@
builder.append(", succ-count: " + successors.size());
builder.append(", filled: " + isFilled());
builder.append(", sealed: " + isSealed());
- builder.append("\n");
+ builder.append('\n');
builder.append("predecessors: ");
appendBasicBlockList(builder, predecessors, b -> "");
- builder.append("\n");
+ builder.append('\n');
builder.append("successors: ");
appendBasicBlockList(builder, successors, this::predecessorPostfix);
if (successors.size() > 0) {
@@ -771,14 +770,14 @@
}
builder.append(" try/catch successors)");
}
- builder.append("\n");
+ builder.append('\n');
if (phis != null && phis.size() > 0) {
for (Phi phi : phis) {
builder.append(phi.printPhi());
if (incompletePhis.values().contains(phi)) {
builder.append(" (incomplete)");
}
- builder.append("\n");
+ builder.append('\n');
}
} else {
builder.append("no phis\n");
@@ -786,13 +785,13 @@
if (localsAtEntry != null) {
builder.append("locals: ");
StringUtils.append(builder, localsAtEntry.int2ReferenceEntrySet(), ", ", BraceType.NONE);
- builder.append("\n");
+ builder.append('\n');
}
for (Instruction instruction : instructions) {
StringUtils.appendLeftPadded(builder, Integer.toString(instruction.getNumber()), 6);
builder.append(": ");
StringUtils.appendRightPadded(builder, instruction.toString(), 20);
- builder.append("\n");
+ builder.append('\n');
}
return builder.toString();
}
@@ -1036,7 +1035,7 @@
newBlock.setNumber(blockNumber);
// Copy all successors including catch handlers to the new block, and update predecessors.
- successors.forEach(newBlock.successors::add);
+ newBlock.successors.addAll(successors);
for (BasicBlock successor : newBlock.getSuccessors()) {
successor.replacePredecessor(this, newBlock);
}
@@ -1172,7 +1171,7 @@
* this method should only be called from either {@link #moveCatchHandlers} or
* {@link #copyCatchHandlers} which know how to handle phis.
*
- * @returns the catch successors that are reused in both blocks after appending.
+ * @return the catch successors that are reused in both blocks after appending.
*/
private List<BasicBlock> appendCatchHandlers(BasicBlock fromBlock) {
assert fromBlock.hasCatchHandlers();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index ac4781d..bb88b64 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -45,7 +45,7 @@
/**
* Continue to call {@link #next} while {@code predicate} tests {@code false}.
*
- * @returns the instruction that matched the predicate or {@code null} if all instructions fails
+ * @return the instruction that matched the predicate or {@code null} if all instructions fails
* the predicate test
*/
default Instruction nextUntil(Predicate<Instruction> predicate) {
@@ -168,7 +168,7 @@
List<BasicBlock> blocksToRemove, DexType downcast);
/**
- * See {@link #inlineInvoke(IRCode, IRCode, ListIterator<BasicBlock>, List<BasicBlock>, DexType)}.
+ * See {@link #inlineInvoke(IRCode, IRCode, ListIterator, List, DexType)}.
*/
default BasicBlock inlineInvoke(IRCode code, IRCode inlinee) {
List<BasicBlock> blocksToRemove = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 4e3f704..70e156b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -45,7 +45,7 @@
*/
public class CallGraph {
- private class Node {
+ private static class Node {
public final DexEncodedMethod method;
private int invokeCount = 0;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 7aca069..9d11fae 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -16,6 +16,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LocalVariableNode;
@@ -389,13 +390,14 @@
}
}
// TODO(zerny): Precompute and sort the local ranges.
- for (LocalVariableNode node : localVariables.keySet()) {
+ for (Entry<LocalVariableNode, DebugLocalInfo> entry : localVariables.entrySet()) {
+ LocalVariableNode node = entry.getKey();
int startOffset = source.getOffset(node.start);
int endOffset = source.getOffset(node.end);
if (startOffset <= target && target < endOffset) {
int register = getLocalRegister(node.index, Type.getType(node.desc));
Local local = locals[register];
- locals[register] = new Local(local.slot, localVariables.get(node));
+ locals[register] = new Local(local.slot, entry.getValue());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 4a51135..850f37d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -162,7 +162,7 @@
boolean staticTarget = implHandle.type.isInvokeStatic();
boolean instanceTarget = implHandle.type.isInvokeInstance();
boolean initTarget = implHandle.type.isInvokeConstructor();
- assert instanceTarget || staticTarget | initTarget;
+ assert instanceTarget || staticTarget || initTarget;
if (targetMethod == null) {
// The target cannot be a private method, since otherwise it
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 9ec776f..bfaee32 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -464,7 +464,7 @@
private int addPrimitiveBoxing(int register, DexType primitiveType, DexType boxType) {
// Generate factory method fo boxing.
DexItemFactory factory = factory();
- DexProto proto = factory.createProto(boxType, new DexType[]{primitiveType});
+ DexProto proto = factory.createProto(boxType, primitiveType);
DexMethod method = factory.createMethod(boxType, proto, factory.valueOfMethodName);
MoveType moveType = MoveType.fromDexType(primitiveType);
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 203ca80..014a064 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
@@ -1126,7 +1126,7 @@
}
}
- private class ExpressionEquivalence extends Equivalence<Instruction> {
+ private static class ExpressionEquivalence extends Equivalence<Instruction> {
@Override
protected boolean doEquivalent(Instruction a, Instruction b) {
@@ -1457,8 +1457,7 @@
DexType javaLangSystemType = dexItemFactory.createType("Ljava/lang/System;");
DexType javaIoPrintStreamType = dexItemFactory.createType("Ljava/io/PrintStream;");
- DexProto proto = dexItemFactory.createProto(
- dexItemFactory.voidType, new DexType[]{dexItemFactory.objectType});
+ DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print");
DexMethod printLn = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "println");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index df5cb27..8a02435 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -37,7 +37,7 @@
ASSUME_VALUES
}
- private class ProguardMemberRuleLookup {
+ private static class ProguardMemberRuleLookup {
final RuleType type;
final ProguardMemberRule rule;
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 10c89df..d8d3b8d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -30,7 +30,7 @@
import java.util.Set;
import java.util.function.Consumer;
-public class ClassNameMinifier {
+class ClassNameMinifier {
private final AppInfoWithLiveness appInfo;
private final RootSet rootSet;
@@ -47,7 +47,7 @@
private GenericSignatureParser<DexType> genericSignatureParser =
new GenericSignatureParser<>(genericSignatureRewriter);
- public ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix,
+ ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix,
List<String> dictionary, boolean keepInnerClassStructure) {
this.appInfo = appInfo;
this.rootSet = rootSet;
@@ -56,7 +56,7 @@
this.keepInnerClassStructure = keepInnerClassStructure;
}
- public Map<DexType, DexString> computeRenaming() {
+ Map<DexType, DexString> computeRenaming() {
Iterable<DexProgramClass> classes = appInfo.classes();
// Collect names we have to keep.
for (DexClass clazz : classes) {
@@ -83,22 +83,10 @@
for (DexClass clazz : appInfo.classes()) {
rewriteGenericSignatures(clazz.annotations.annotations,
genericSignatureParser::parseClassSignature);
- for (DexEncodedField field : clazz.staticFields) {
- rewriteGenericSignatures(field.annotations.annotations,
- genericSignatureParser::parseFieldSignature);
- }
- for (DexEncodedField field : clazz.instanceFields) {
- rewriteGenericSignatures(field.annotations.annotations,
- genericSignatureParser::parseFieldSignature);
- }
- for (DexEncodedMethod method : clazz.directMethods) {
- rewriteGenericSignatures(method.annotations.annotations,
- genericSignatureParser::parseMethodSignature);
- }
- for (DexEncodedMethod method : clazz.virtualMethods) {
- rewriteGenericSignatures(method.annotations.annotations,
- genericSignatureParser::parseMethodSignature);
- }
+ clazz.forEachField(field -> rewriteGenericSignatures(
+ field.annotations.annotations, genericSignatureParser::parseFieldSignature));
+ clazz.forEachMethod(method -> rewriteGenericSignatures(
+ method.annotations.annotations, genericSignatureParser::parseMethodSignature));
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 3a72512..8177e89 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -14,7 +14,7 @@
import java.util.List;
import java.util.Map;
-public class FieldNameMinifier {
+class FieldNameMinifier {
private final AppInfoWithSubtyping appInfo;
private final RootSet rootSet;
@@ -22,7 +22,7 @@
private final List<String> dictionary;
private final Map<DexType, NamingState<DexType>> states = new IdentityHashMap<>();
- public FieldNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, List<String> dictionary) {
+ FieldNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, List<String> dictionary) {
this.appInfo = appInfo;
this.rootSet = rootSet;
this.dictionary = dictionary;
@@ -50,18 +50,17 @@
return;
}
NamingState<DexType> newState = states.computeIfAbsent(type, t -> state.createChild());
- reserveFieldNames(newState, holder.instanceFields(), holder.isLibraryClass());
- reserveFieldNames(newState, holder.staticFields(), holder.isLibraryClass());
+ holder.forEachField(field -> reserveFieldName(field, newState, holder.isLibraryClass()));
type.forAllExtendsSubtypes(subtype -> reserveNamesInSubtypes(subtype, newState));
}
- private void reserveFieldNames(NamingState<DexType> state, DexEncodedField[] fields,
+ private void reserveFieldName(
+ DexEncodedField encodedField,
+ NamingState<DexType> state,
boolean isLibrary) {
- for (DexEncodedField encodedField : fields) {
- if (isLibrary || rootSet.noObfuscation.contains(encodedField)) {
- DexField field = encodedField.field;
- state.reserveName(field.name, field.type);
- }
+ if (isLibrary || rootSet.noObfuscation.contains(encodedField)) {
+ DexField field = encodedField.field;
+ state.reserveName(field.name, field.type);
}
}
@@ -72,17 +71,14 @@
}
NamingState<DexType> state = states.get(clazz.type);
assert state != null;
- renameFields(clazz.instanceFields(), state);
- renameFields(clazz.staticFields(), state);
+ clazz.forEachField(field -> renameField(field, state));
type.forAllExtendsSubtypes(this::renameFieldsInSubtypes);
}
- private void renameFields(DexEncodedField[] fields, NamingState<DexType> state) {
- for (DexEncodedField encodedField : fields) {
- DexField field = encodedField.field;
- if (!state.isReserved(field.name, field.type)) {
- renaming.put(field, state.assignNewNameFor(field.name, field.type, false));
- }
+ private void renameField(DexEncodedField encodedField, NamingState<DexType> state) {
+ DexField field = encodedField.field;
+ if (!state.isReserved(field.name, field.type)) {
+ renaming.put(field, state.assignNewNameFor(field.name, field.type, false));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index ec7fa46..4aca460 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -84,7 +84,7 @@
* TODO(herhut): Currently, we do not minify members of annotation interfaces, as this would require
* parsing and minification of the string arguments to annotations.
*/
-public class MethodNameMinifier {
+class MethodNameMinifier {
private final AppInfoWithSubtyping appInfo;
private final RootSet rootSet;
@@ -93,7 +93,7 @@
private MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
private final List<String> dictionary;
- public MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet,
+ MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet,
List<String> dictionary) {
this.appInfo = appInfo;
this.rootSet = rootSet;
@@ -101,7 +101,7 @@
this.globalState = NamingState.createRoot(appInfo.dexItemFactory, dictionary);
}
- public Map<DexMethod, DexString> computeRenaming(Timing timing) {
+ Map<DexMethod, DexString> computeRenaming(Timing timing) {
// Phase 1: Reserve all the names that need to be kept and allocate linked state in the
// library part.
timing.begin("Phase 1");
@@ -198,10 +198,8 @@
DexClass clazz = appInfo.definitionFor(iface);
if (clazz != null) {
Set<NamingState<DexProto>> collectedStates = getReachableStates(iface, frontierMap);
- addStatesToGlobalMapForMethods(clazz.directMethods(), collectedStates, globalStateMap,
- sourceMethodsMap, originStates, iface);
- addStatesToGlobalMapForMethods(clazz.virtualMethods(), collectedStates, globalStateMap,
- sourceMethodsMap, originStates, iface);
+ clazz.forEachMethod(method -> addStatesToGlobalMapForMethod(
+ method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface));
}
});
timing.end();
@@ -237,7 +235,6 @@
private void collectSubInterfaces(DexType iface, Set<DexType> interfaces) {
- DexClass clazz = appInfo.definitionFor(iface);
iface.forAllExtendsSubtypes(subtype -> {
assert subtype.isInterface();
if (interfaces.add(subtype)) {
@@ -246,19 +243,17 @@
});
}
- private void addStatesToGlobalMapForMethods(
- DexEncodedMethod[] methods, Set<NamingState<DexProto>> collectedStates,
+ private void addStatesToGlobalMapForMethod(
+ DexEncodedMethod method, Set<NamingState<DexProto>> collectedStates,
Map<Wrapper<DexMethod>, Set<NamingState<DexProto>>> globalStateMap,
Map<Wrapper<DexMethod>, Set<DexMethod>> sourceMethodsMap,
Map<Wrapper<DexMethod>, NamingState<DexProto>> originStates, DexType originInterface) {
- for (DexEncodedMethod method : methods) {
- Wrapper<DexMethod> key = equivalence.wrap(method.method);
- Set<NamingState<DexProto>> stateSet = globalStateMap
- .computeIfAbsent(key, k -> new HashSet<>());
- stateSet.addAll(collectedStates);
- sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method.method);
- originStates.putIfAbsent(key, states.get(originInterface));
- }
+ Wrapper<DexMethod> key = equivalence.wrap(method.method);
+ Set<NamingState<DexProto>> stateSet =
+ globalStateMap.computeIfAbsent(key, k -> new HashSet<>());
+ stateSet.addAll(collectedStates);
+ sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method.method);
+ originStates.putIfAbsent(key, states.get(originInterface));
}
private void assignNameForInterfaceMethodInAllStates(DexMethod method,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 3b78e1b..cc29125 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -159,8 +159,6 @@
if (isOptionalArgumentGiven()) {
configurationBuilder.setPrintUsageFile(parseFileName());
}
- // TODO(b/36799826): once fully implemented, no longer necessary to warn.
- System.out.println("WARNING: Ignoring option: -printusage");
} else if (acceptString("verbose")) {
configurationBuilder.setVerbose(true);
} else if (acceptString("ignorewarnings")) {
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 3968c9a..2a5c9e0 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -22,25 +23,27 @@
private DexApplication application;
private final AppInfoWithLiveness appInfo;
private final InternalOptions options;
+ private UsagePrinter usagePrinter;
public TreePruner(
DexApplication application, AppInfoWithLiveness appInfo, InternalOptions options) {
this.application = application;
this.appInfo = appInfo;
this.options = options;
+ this.usagePrinter = options.printUsage ? new UsagePrinter() : UsagePrinter.DONT_PRINT;
}
- public DexApplication run() {
+ public DexApplication run() throws IOException {
application.timing.begin("Pruning application...");
if (options.debugKeepRules && !options.skipMinification) {
System.out.println(
- "NOTE: Debugging keep rules on a minified build might yield broken builds, as\n" +
- " minifcation also depends on the used keep rules. We recommend using\n" +
- " --skip-minification.");
+ "NOTE: Debugging keep rules on a minified build might yield broken builds, as\n"
+ + " minification also depends on the used keep rules. We recommend using\n"
+ + " --skip-minification.");
}
DexApplication result;
try {
- result = removeUnused(application).build();
+ result = removeUnused(application).appendDeadCode(usagePrinter.toByteArray()).build();
} finally {
application.timing.end();
}
@@ -60,6 +63,7 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Removing class: " + clazz);
}
+ usagePrinter.printUnusedClass(clazz);
} else {
newClasses.add(clazz);
if (!appInfo.instantiatedTypes.contains(clazz.type) &&
@@ -78,10 +82,12 @@
}
// The class is used and must be kept. Remove the unused fields and methods from
// the class.
+ usagePrinter.visiting(clazz);
clazz.directMethods = reachableMethods(clazz.directMethods(), clazz);
clazz.virtualMethods = reachableMethods(clazz.virtualMethods(), clazz);
clazz.instanceFields = reachableFields(clazz.instanceFields());
clazz.staticFields = reachableFields(clazz.staticFields());
+ usagePrinter.visited();
}
}
return newClasses;
@@ -122,18 +128,18 @@
reachableMethods.add(methods[i]);
}
for (int i = firstUnreachable; i < methods.length; i++) {
+ DexEncodedMethod method = methods[i];
if (appInfo.liveMethods.contains(methods[i].getKey())) {
- reachableMethods.add(methods[i]);
- } else if (options.debugKeepRules && isDefaultConstructor(methods[i])) {
+ reachableMethods.add(method);
+ } else if (options.debugKeepRules && isDefaultConstructor(method)) {
// Keep the method but rewrite its body, if it has one.
reachableMethods.add(methods[i].accessFlags.isAbstract()
- ? methods[i]
- : methods[i].toMethodThatLogsError(application.dexItemFactory));
- } else if (appInfo.targetedMethods.contains(methods[i].getKey())) {
+ ? method
+ : method.toMethodThatLogsError(application.dexItemFactory));
+ } else if (appInfo.targetedMethods.contains(method.getKey())) {
if (Log.ENABLED) {
- Log.debug(getClass(), "Making method %s abstract.", methods[i].method);
+ Log.debug(getClass(), "Making method %s abstract.", method.method);
}
- DexEncodedMethod method = methods[i];
// Final classes cannot be abstract, so we have to keep the method in that case.
// Also some other kinds of methods cannot be abstract, so keep them around.
boolean allowAbstract = clazz.accessFlags.isAbstract()
@@ -144,10 +150,13 @@
// By construction, private and static methods cannot be reachable but non-live.
assert !method.accessFlags.isPrivate() && !method.accessFlags.isStatic();
reachableMethods.add(allowAbstract
- ? methods[i].toAbstractMethod()
- : methods[i].toEmptyThrowingMethod());
- } else if (Log.ENABLED) {
- Log.debug(getClass(), "Removing method %s.", methods[i].method);
+ ? method.toAbstractMethod()
+ : method.toEmptyThrowingMethod());
+ } else {
+ if (Log.ENABLED) {
+ Log.debug(getClass(), "Removing method %s.", method.method);
+ }
+ usagePrinter.printUnusedMethod(method);
}
}
return reachableMethods.toArray(new DexEncodedMethod[reachableMethods.size()]);
@@ -155,21 +164,27 @@
private DexEncodedField[] reachableFields(DexEncodedField[] fields) {
int firstUnreachable = firstUnreachableIndex(fields, appInfo.liveFields);
+ // Return the original array if all fields are used.
if (firstUnreachable == -1) {
return fields;
}
if (Log.ENABLED) {
Log.debug(getClass(), "Removing field: " + fields[firstUnreachable]);
}
+ usagePrinter.printUnusedField(fields[firstUnreachable]);
ArrayList<DexEncodedField> reachableFields = new ArrayList<>(fields.length);
for (int i = 0; i < firstUnreachable; i++) {
reachableFields.add(fields[i]);
}
for (int i = firstUnreachable + 1; i < fields.length; i++) {
- if (appInfo.liveFields.contains(fields[i].getKey())) {
- reachableFields.add(fields[i]);
- } else if (Log.ENABLED) {
- Log.debug(getClass(), "Removing field: " + fields[i]);
+ DexEncodedField field = fields[i];
+ if (appInfo.liveFields.contains(field.getKey())) {
+ reachableFields.add(field);
+ } else {
+ if (Log.ENABLED) {
+ Log.debug(getClass(), "Removing field: " + field);
+ }
+ usagePrinter.printUnusedField(field);
}
}
return reachableFields.toArray(new DexEncodedField[reachableFields.size()]);
diff --git a/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java b/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
new file mode 100644
index 0000000..7e41671
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/UsagePrinter.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import java.nio.charset.StandardCharsets;
+
+class UsagePrinter {
+ private static final String INDENT = " ";
+
+ static final UsagePrinter DONT_PRINT = new NoOpUsagePrinter();
+
+ private final StringBuilder writer;
+ private DexProgramClass enclosingClazz = null;
+ private boolean clazzPrefixPrinted = false;
+
+ UsagePrinter() {
+ writer = new StringBuilder();
+ }
+
+ byte[] toByteArray() {
+ return writer.toString().getBytes(StandardCharsets.UTF_8);
+ }
+
+ void printUnusedClass(DexProgramClass clazz) {
+ writer.append(clazz.toSourceString());
+ writer.append('\n');
+ }
+
+ // Visiting methods and fields of the given clazz.
+ void visiting(DexProgramClass clazz) {
+ assert enclosingClazz == null;
+ enclosingClazz = clazz;
+ }
+
+ // Visited methods and fields of the top at the clazz stack.
+ void visited() {
+ enclosingClazz = null;
+ clazzPrefixPrinted = false;
+ }
+
+ private void printClazzPrefixIfNecessary() {
+ assert enclosingClazz != null;
+ if (!clazzPrefixPrinted) {
+ writer.append(enclosingClazz.toSourceString());
+ writer.append('\n');
+ clazzPrefixPrinted = true;
+ }
+ }
+
+ void printUnusedMethod(DexEncodedMethod method) {
+ printClazzPrefixIfNecessary();
+ writer.append(INDENT);
+ String accessFlags = method.accessFlags.toString();
+ if (!accessFlags.isEmpty()) {
+ writer.append(accessFlags).append(' ');
+ }
+ writer.append(method.method.proto.returnType.toSourceString()).append(' ');
+ writer.append(method.method.name.toSourceString());
+ writer.append('(');
+ for (int i = 0; i < method.method.proto.parameters.values.length; i++) {
+ if (i != 0) {
+ writer.append(',');
+ }
+ writer.append(method.method.proto.parameters.values[i].toSourceString());
+ }
+ writer.append(')');
+ writer.append('\n');
+ }
+
+ void printUnusedField(DexEncodedField field) {
+ printClazzPrefixIfNecessary();
+ writer.append(INDENT);
+ String accessFlags = field.accessFlags.toString();
+ if (!accessFlags.isEmpty()) {
+ writer.append(accessFlags).append(' ');
+ }
+ writer.append(field.field.type.toSourceString()).append(" ");
+ writer.append(field.field.name.toSourceString());
+ writer.append('\n');
+ }
+
+ // Empty implementation to silently ignore printing dead code.
+ private static class NoOpUsagePrinter extends UsagePrinter {
+
+ @Override
+ byte[] toByteArray() {
+ return null;
+ }
+
+ @Override
+ void printUnusedClass(DexProgramClass clazz) {
+ // Intentionally left empty.
+ }
+
+ @Override
+ void visiting(DexProgramClass clazz) {
+ // Intentionally left empty.
+ }
+
+ @Override
+ void visited() {
+ // Intentionally left empty.
+ }
+
+ @Override
+ void printUnusedMethod(DexEncodedMethod method) {
+ // Intentionally left empty.
+ }
+
+ @Override
+ void printUnusedField(DexEncodedField field) {
+ // Intentionally left empty.
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 3877f1a..15ca87b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -52,6 +52,7 @@
private final ImmutableList<Resource> programResources;
private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
private final ImmutableList<ClassFileResourceProvider> libraryResourceProviders;
+ private final Resource deadCode;
private final Resource proguardMap;
private final Resource proguardSeeds;
private final Resource packageDistribution;
@@ -62,6 +63,7 @@
ImmutableList<Resource> programResources,
ImmutableList<ClassFileResourceProvider> classpathResourceProviders,
ImmutableList<ClassFileResourceProvider> libraryResourceProviders,
+ Resource deadCode,
Resource proguardMap,
Resource proguardSeeds,
Resource packageDistribution,
@@ -69,6 +71,7 @@
this.programResources = programResources;
this.classpathResourceProviders = classpathResourceProviders;
this.libraryResourceProviders = libraryResourceProviders;
+ this.deadCode = deadCode;
this.proguardMap = proguardMap;
this.proguardSeeds = proguardSeeds;
this.packageDistribution = packageDistribution;
@@ -169,6 +172,20 @@
}
/**
+ * True if the dead-code resource exists.
+ */
+ public boolean hasDeadCode() {
+ return deadCode != null;
+ }
+
+ /**
+ * Get the input stream of the dead-code resource if exists.
+ */
+ public InputStream getDeadCode(Closer closer) throws IOException {
+ return deadCode == null ? null : deadCode.getStream(closer);
+ }
+
+ /**
* True if the proguard-map resource exists.
*/
public boolean hasProguardMap() {
@@ -337,6 +354,12 @@
out.write(ByteStreams.toByteArray(input));
}
+ public void writeDeadCode(Closer closer, OutputStream out) throws IOException {
+ InputStream input = getDeadCode(closer);
+ assert input != null;
+ out.write(ByteStreams.toByteArray(input));
+ }
+
/**
* Builder interface for constructing an AndroidApp.
*/
@@ -345,6 +368,7 @@
private final List<Resource> programResources = new ArrayList<>();
private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
+ private Resource deadCode;
private Resource proguardMap;
private Resource proguardSeeds;
private Resource packageDistribution;
@@ -359,6 +383,7 @@
programResources.addAll(app.programResources);
classpathResourceProviders.addAll(app.classpathResourceProviders);
libraryResourceProviders.addAll(app.libraryResourceProviders);
+ deadCode = app.deadCode;
proguardMap = app.proguardMap;
proguardSeeds = app.proguardSeeds;
packageDistribution = app.packageDistribution;
@@ -499,6 +524,14 @@
}
/**
+ * Set dead-code data.
+ */
+ public Builder setDeadCode(byte[] content) {
+ deadCode = content == null ? null : Resource.fromBytes(null, content);
+ return this;
+ }
+
+ /**
* Set proguard-map file.
*/
public Builder setProguardMapFile(Path file) {
@@ -561,6 +594,7 @@
ImmutableList.copyOf(programResources),
ImmutableList.copyOf(classpathResourceProviders),
ImmutableList.copyOf(libraryResourceProviders),
+ deadCode,
proguardMap,
proguardSeeds,
packageDistribution,
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 16f41cc..5c5a776 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -4,11 +4,16 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.CompilationException;
+import com.google.common.io.Closer;
import java.io.BufferedReader;
import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.OpenOption;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -79,10 +84,27 @@
if (!isJarOrZip && !(Files.exists(path) && Files.isDirectory(path))) {
throw new CompilationException(
"Invalid output: "
- + path +
- "\nOutput must be a .zip or .jar archive or an existing directory");
+ + path
+ + "\nOutput must be a .zip or .jar archive or an existing directory");
}
}
return path;
}
+
+ public static OutputStream openPathWithDefault(
+ Closer closer,
+ Path file,
+ PrintStream defaultOutput,
+ OpenOption... openOptions)
+ throws IOException {
+ OutputStream mapOut;
+ if (file == null) {
+ mapOut = defaultOutput;
+ } else {
+ mapOut = Files.newOutputStream(file, openOptions);
+ closer.register(mapOut);
+ }
+ return mapOut;
+ }
+
}
diff --git a/src/main/java/com/android/tools/r8/utils/PackageDistribution.java b/src/main/java/com/android/tools/r8/utils/PackageDistribution.java
index 6d36bc3..b242b9d 100644
--- a/src/main/java/com/android/tools/r8/utils/PackageDistribution.java
+++ b/src/main/java/com/android/tools/r8/utils/PackageDistribution.java
@@ -154,7 +154,7 @@
}
public int maxReferencedIndex() {
- return map.values().stream().max(Integer::compare).orElseGet(() -> 0);
+ return map.values().stream().max(Integer::compare).orElse(0);
}
public Set<String> getFiles() {
diff --git a/src/test/examples/shaking1/keep-rules-printusage.txt b/src/test/examples/shaking1/keep-rules-printusage.txt
new file mode 100644
index 0000000..5db2d89
--- /dev/null
+++ b/src/test/examples/shaking1/keep-rules-printusage.txt
@@ -0,0 +1,8 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+@keep-rules.txt
+
+-dontobfuscate
+-printusage
diff --git a/src/test/examples/shaking12/keep-rules-printusage.txt b/src/test/examples/shaking12/keep-rules-printusage.txt
new file mode 100644
index 0000000..5db2d89
--- /dev/null
+++ b/src/test/examples/shaking12/keep-rules-printusage.txt
@@ -0,0 +1,8 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+@keep-rules.txt
+
+-dontobfuscate
+-printusage
diff --git a/src/test/examples/shaking2/keep-rules-printusage.txt b/src/test/examples/shaking2/keep-rules-printusage.txt
new file mode 100644
index 0000000..5db2d89
--- /dev/null
+++ b/src/test/examples/shaking2/keep-rules-printusage.txt
@@ -0,0 +1,8 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+@keep-rules.txt
+
+-dontobfuscate
+-printusage
diff --git a/src/test/examples/shaking4/keep-rules-printusage.txt b/src/test/examples/shaking4/keep-rules-printusage.txt
new file mode 100644
index 0000000..5db2d89
--- /dev/null
+++ b/src/test/examples/shaking4/keep-rules-printusage.txt
@@ -0,0 +1,8 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+@keep-rules.txt
+
+-dontobfuscate
+-printusage
diff --git a/src/test/examples/shaking8/keep-rules-printusage.txt b/src/test/examples/shaking8/keep-rules-printusage.txt
new file mode 100644
index 0000000..5db2d89
--- /dev/null
+++ b/src/test/examples/shaking8/keep-rules-printusage.txt
@@ -0,0 +1,8 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+@keep-rules.txt
+
+-dontobfuscate
+-printusage
diff --git a/src/test/examples/shaking9/keep-rules-printusage.txt b/src/test/examples/shaking9/keep-rules-printusage.txt
new file mode 100644
index 0000000..5db2d89
--- /dev/null
+++ b/src/test/examples/shaking9/keep-rules-printusage.txt
@@ -0,0 +1,8 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+@keep-rules.txt
+
+-dontobfuscate
+-printusage
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 6ceb912..e5bbad6 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -85,6 +85,7 @@
Executors.newSingleThreadExecutor(),
app,
info,
+ null,
NamingLens.getIdentityLens(),
null,
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 416a266..001883f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -440,7 +440,7 @@
DexApplication application = builder.build();
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
ApplicationWriter writer =
- new ApplicationWriter(application, appInfo, options, NamingLens.getIdentityLens(), null);
+ new ApplicationWriter(application, appInfo, options, null, NamingLens.getIdentityLens(), null);
ExecutorService executor = ThreadUtils.getExecutorService(options);
try {
return writer.write(null, executor);
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
new file mode 100644
index 0000000..47c6330
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -0,0 +1,255 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.shaking.TreeShakingTest.getTestOptionalParameter;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.PrintUsageTest.PrintUsageInspector.ClassSubject;
+import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PrintUsageTest {
+ private static final String ANDROID_JAR = ToolHelper.getDefaultAndroidJar();
+ private static final String PRINT_USAGE_FILE_SUFFIX = "-print-usage.txt";
+
+ private final String test;
+ private final String programFile;
+ private final List<String> keepRulesFiles;
+ private final Consumer<PrintUsageInspector> inspection;
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ public PrintUsageTest(
+ String test,
+ List<String> keepRulesFiles,
+ Consumer<PrintUsageInspector> inspection) {
+ this.test = test;
+ this.programFile = ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar";
+ this.keepRulesFiles = keepRulesFiles;
+ this.inspection = inspection;
+ }
+
+ @Before
+ public void runR8andGetPrintUsage()
+ throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+ Path out = temp.getRoot().toPath();
+ R8Command command =
+ R8Command.builder()
+ .setOutputPath(out)
+ .addProgramFiles(Paths.get(programFile))
+ .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
+ .addLibraryFiles(Paths.get(ANDROID_JAR))
+ .build();
+ ToolHelper.runR8(command, options -> {
+ options.printUsage = true;
+ options.printUsageFile = out.resolve(test + PRINT_USAGE_FILE_SUFFIX);
+ });
+ }
+
+ @Test
+ public void printUsageTest() throws IOException, ExecutionException {
+ Path out = temp.getRoot().toPath();
+ Path printUsageFile = out.resolve(test + PRINT_USAGE_FILE_SUFFIX);
+ if (inspection != null) {
+ PrintUsageInspector inspector = new PrintUsageInspector(printUsageFile);
+ inspection.accept(inspector);
+ }
+ }
+
+ @Parameters(name = "test: {0} keep: {1}")
+ public static Collection<Object[]> data() {
+ List<String> tests = Arrays.asList(
+ "shaking1", "shaking2", "shaking4", "shaking8", "shaking9", "shaking12");
+
+ Map<String, Consumer<PrintUsageInspector>> inspections = new HashMap<>();
+ inspections.put("shaking1:keep-rules-printusage.txt", PrintUsageTest::inspectShaking1);
+ inspections.put("shaking2:keep-rules-printusage.txt", PrintUsageTest::inspectShaking2);
+ inspections.put("shaking4:keep-rules-printusage.txt", PrintUsageTest::inspectShaking4);
+ inspections.put("shaking8:keep-rules-printusage.txt", PrintUsageTest::inspectShaking8);
+ inspections.put("shaking9:keep-rules-printusage.txt", PrintUsageTest::inspectShaking9);
+ inspections.put("shaking12:keep-rules-printusage.txt", PrintUsageTest::inspectShaking12);
+
+ List<Object[]> testCases = new ArrayList<>();
+ Set<String> usedInspections = new HashSet<>();
+ for (String test : tests) {
+ File[] keepFiles = new File(ToolHelper.EXAMPLES_DIR + test)
+ .listFiles(file -> file.isFile() && file.getName().endsWith(".txt"));
+ for (File keepFile : keepFiles) {
+ String keepName = keepFile.getName();
+ Consumer<PrintUsageInspector> inspection =
+ getTestOptionalParameter(inspections, usedInspections, test, keepName);
+ if (inspection != null) {
+ testCases.add(new Object[]{test, ImmutableList.of(keepFile.getPath()), inspection});
+ }
+ }
+ }
+ assert usedInspections.size() == inspections.size();
+ return testCases;
+ }
+
+ private static void inspectShaking1(PrintUsageInspector inspector) {
+ assertTrue(inspector.clazz("shaking1.Unused").isPresent());
+ assertFalse(inspector.clazz("shaking1.Used").isPresent());
+ }
+
+ private static void inspectShaking2(PrintUsageInspector inspector) {
+ Optional<ClassSubject> staticFields = inspector.clazz("shaking2.StaticFields");
+ assertTrue(staticFields.isPresent());
+ assertTrue(staticFields.get().field("int", "completelyUnused"));
+ assertTrue(staticFields.get().field("int", "unused"));
+ Optional<ClassSubject> subClass1 = inspector.clazz("shaking2.SubClass1");
+ assertTrue(subClass1.isPresent());
+ assertTrue(subClass1.get().method("void", "unusedVirtualMethod", Collections.emptyList()));
+ Optional<ClassSubject> superClass = inspector.clazz("shaking2.SuperClass");
+ assertTrue(superClass.isPresent());
+ assertTrue(superClass.get().method("void", "unusedStaticMethod", Collections.emptyList()));
+ }
+
+ private static void inspectShaking4(PrintUsageInspector inspector) {
+ assertTrue(inspector.clazz("shaking4.Interface").isPresent());
+ }
+
+ private static void inspectShaking8(PrintUsageInspector inspector) {
+ Optional<ClassSubject> thing = inspector.clazz("shaking8.Thing");
+ assertTrue(thing.isPresent());
+ assertTrue(thing.get().field("int", "aField"));
+ assertFalse(inspector.clazz("shaking8.OtherThing").isPresent());
+ assertTrue(inspector.clazz("shaking8.YetAnotherThing").isPresent());
+ }
+
+ private static void inspectShaking9(PrintUsageInspector inspector) {
+ assertFalse(inspector.clazz("shaking9.Superclass").isPresent());
+ Optional<ClassSubject> subClass = inspector.clazz("shaking9.Subclass");
+ assertTrue(subClass.isPresent());
+ assertTrue(subClass.get().method("void", "aMethod", Collections.emptyList()));
+ }
+
+ private static void inspectShaking12(PrintUsageInspector inspector) {
+ assertFalse(inspector.clazz("shaking12.PeopleClass").isPresent());
+ Optional<ClassSubject> animal = inspector.clazz("shaking12.AnimalClass");
+ assertTrue(animal.isPresent());
+ assertTrue(animal.get().method("java.lang.String", "getName", Collections.emptyList()));
+ }
+
+ static class PrintUsageInspector {
+ private Map<String, ClassSubject> printedUsage;
+
+ PrintUsageInspector(Path printUsageFile) throws IOException {
+ printedUsage = new HashMap<>();
+ try (Stream<String> lines = Files.lines(printUsageFile)) {
+ lines.forEach(line -> {
+ if (line.startsWith(" ")) {
+ if (line.contains("(") && line.contains(")")) {
+ readMethod(line);
+ } else {
+ readField(line);
+ }
+ } else {
+ readClazz(line);
+ }
+ });
+ }
+ }
+
+ private ClassSubject lastClazz = null;
+
+ private void readClazz(String line) {
+ if (printedUsage.containsKey(line)) {
+ lastClazz = printedUsage.get(line);
+ } else {
+ lastClazz = new ClassSubject();
+ printedUsage.put(line, lastClazz);
+ }
+ }
+
+ private void readMethod(String line) {
+ assert lastClazz != null;
+ lastClazz.putMethod(line);
+ }
+
+ private void readField(String line) {
+ assert lastClazz != null;
+ lastClazz.putField(line);
+ }
+
+ public Optional<ClassSubject> clazz(String name) {
+ if (printedUsage.containsKey(name)) {
+ return Optional.of(printedUsage.get(name));
+ }
+ return Optional.empty();
+ }
+
+ static class ClassSubject {
+ private Set<String> methods;
+ private Set<String> fields;
+
+ public ClassSubject() {
+ methods = new HashSet<>();
+ fields = new HashSet<>();
+ }
+
+ void putMethod(String line) {
+ String[] tokens = line.split(" ");
+ assert tokens.length >= 2;
+ methods.add(tokens[tokens.length - 2] + " " + tokens[tokens.length - 1]);
+ }
+
+ void putField(String line) {
+ String[] tokens = line.split(" ");
+ assert tokens.length >= 2;
+ fields.add(tokens[tokens.length - 2] + " " + tokens[tokens.length - 1]);
+ }
+
+ public boolean method(String returnType, String name, List<String> parameters) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(returnType).append(" ").append(name);
+ builder.append("(");
+ for (int i = 0; i < parameters.size(); i++) {
+ if (i != 0) {
+ builder.append(",");
+ }
+ builder.append(parameters.get(i));
+ }
+ builder.append(")");
+ return methods.contains(builder.toString());
+ }
+
+ public boolean field(String type, String name) {
+ return fields.contains(type + " " + name);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 55a9fc0..5b94e6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -727,7 +727,7 @@
}
}
- private static <T> T getTestOptionalParameter(
+ static <T> T getTestOptionalParameter(
Map<String, T> specifications, Set<String> usedSpecifications, String test,
String keepName) {
T parameter = specifications.get(test);
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 71ee09f..9a12d3e 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -500,6 +500,7 @@
Executors.newSingleThreadExecutor(),
application,
appInfo,
+ null,
NamingLens.getIdentityLens(),
null,
null,