Merge "Introduce --intermediate option to D8 for native multidex mode."
diff --git a/src/main/java/com/android/tools/r8/BaseOutput.java b/src/main/java/com/android/tools/r8/BaseOutput.java
index 16e8264..b1b9166 100644
--- a/src/main/java/com/android/tools/r8/BaseOutput.java
+++ b/src/main/java/com/android/tools/r8/BaseOutput.java
@@ -8,6 +8,7 @@
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.List;
abstract class BaseOutput {
@@ -39,9 +40,9 @@
* resources.get(N - 1) ~=~ classesN.dex (where N > 0).
* </pre>
*
- * @return list of compiled DEX resources.
+ * @return an immutable list of compiled DEX resources.
*/
- public ImmutableList<Resource> getDexResources() {
+ public List<Resource> getDexResources() {
return ImmutableList.copyOf(app.getDexProgramResources());
}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 3c408cb..6988b06 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -17,7 +17,10 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
@@ -173,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;
@@ -202,9 +206,11 @@
if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
System.out.print(printer.toString());
} else {
- java.io.FileWriter writer = new java.io.FileWriter(options.printCfgFile);
- writer.write(printer.toString());
- writer.close();
+ try (OutputStreamWriter writer = new OutputStreamWriter(
+ new FileOutputStream(options.printCfgFile),
+ StandardCharsets.UTF_8)) {
+ writer.write(printer.toString());
+ }
}
}
return application;
diff --git a/src/main/java/com/android/tools/r8/D8Logger.java b/src/main/java/com/android/tools/r8/D8Logger.java
index 25f238b..f0791b0 100644
--- a/src/main/java/com/android/tools/r8/D8Logger.java
+++ b/src/main/java/com/android/tools/r8/D8Logger.java
@@ -10,8 +10,6 @@
import java.nio.file.Paths;
import java.util.Arrays;
-import static java.util.Arrays.stream;
-
public final class D8Logger {
private static final int STATUS_ERROR = 1;
diff --git a/src/main/java/com/android/tools/r8/DexSegments.java b/src/main/java/com/android/tools/r8/DexSegments.java
index 9902d3b..3827cae 100644
--- a/src/main/java/com/android/tools/r8/DexSegments.java
+++ b/src/main/java/com/android/tools/r8/DexSegments.java
@@ -110,7 +110,7 @@
Map<String, Integer> result = new HashMap<>();
try (Closer closer = Closer.create()) {
for (Resource resource : app.getDexProgramResources()) {
- for (Segment segment: DexFileReader.parseMapFrom(resource.getStream(closer))) {
+ for (Segment segment: DexFileReader.parseMapFrom(closer.register(resource.getStream()))) {
int value = result.computeIfAbsent(segment.typeName(), (key) -> 0);
result.put(segment.typeName(), value + segment.size());
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8fa9543..19fc358 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -37,17 +37,20 @@
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.InternalOptions.AttributeRemovalOptions;
import com.android.tools.r8.utils.PackageDistribution;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
@@ -65,8 +68,7 @@
private final Timing timing = new Timing("R8");
private final InternalOptions options;
- // TODO(zerny): Refactor tests to go through testing methods and make this private.
- public R8(InternalOptions options) {
+ private R8(InternalOptions options) {
this.options = options;
options.itemFactory.resetSortedIndices();
}
@@ -75,27 +77,40 @@
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);
}
}
- public DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo)
+ static DexApplication optimize(
+ DexApplication application,
+ AppInfoWithSubtyping appInfo,
+ InternalOptions options)
+ throws ProguardRuleParserException, ExecutionException, IOException {
+ return new R8(options).optimize(application, appInfo);
+ }
+
+ private DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo)
throws IOException, ProguardRuleParserException, ExecutionException {
return optimize(application, appInfo, GraphLense.getIdentityLense(),
Executors.newSingleThreadExecutor());
}
- public DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo,
- GraphLense graphLense, ExecutorService executorService)
+ private DexApplication optimize(
+ DexApplication application,
+ AppInfoWithSubtyping appInfo,
+ GraphLense graphLense,
+ ExecutorService executorService)
throws IOException, ProguardRuleParserException, ExecutionException {
final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
@@ -123,9 +138,11 @@
if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
System.out.print(printer.toString());
} else {
- java.io.FileWriter writer = new java.io.FileWriter(options.printCfgFile);
- writer.write(printer.toString());
- writer.close();
+ try (OutputStreamWriter writer = new OutputStreamWriter(
+ new FileOutputStream(options.printCfgFile),
+ StandardCharsets.UTF_8)) {
+ writer.write(printer.toString());
+ }
}
}
return application;
@@ -181,7 +198,7 @@
}
}
- static CompilationResult runForTesting(
+ private static CompilationResult runForTesting(
AndroidApp app,
InternalOptions options,
ExecutorService executor)
@@ -237,10 +254,6 @@
appInfo = appInfo.withLiveness().prunedCopyFrom(application);
new AbstractMethodRemover(appInfo).run();
new AnnotationRemover(appInfo.withLiveness(), options).run();
- } else if (!options.skipMinification) {
- // TODO(38188583): Ensure signatures are removed when minifying.
- new AnnotationRemover(appInfo.withLiveness(), true,
- AttributeRemovalOptions.filterOnlySignatures());
}
} finally {
timing.end();
@@ -342,6 +355,7 @@
executorService,
application,
appInfo,
+ application.deadCode,
namingLens,
proguardSeedsData,
packageDistribution,
@@ -391,42 +405,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/Resource.java b/src/main/java/com/android/tools/r8/Resource.java
index eaabc99..3208e52 100644
--- a/src/main/java/com/android/tools/r8/Resource.java
+++ b/src/main/java/com/android/tools/r8/Resource.java
@@ -4,7 +4,6 @@
package com.android.tools.r8;
-import com.google.common.io.Closer;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
@@ -48,7 +47,7 @@
public abstract Set<String> getClassDescriptors();
/** Get the resource as a stream. */
- public abstract InputStream getStream(Closer closer) throws IOException;
+ public abstract InputStream getStream() throws IOException;
/** File based application resource. */
private static class FileResource extends Resource {
@@ -66,8 +65,8 @@
}
@Override
- public InputStream getStream(Closer closer) throws IOException {
- return closer.register(new FileInputStream(file.toFile()));
+ public InputStream getStream() throws IOException {
+ return new FileInputStream(file.toFile());
}
}
@@ -89,8 +88,7 @@
}
@Override
- public InputStream getStream(Closer closer) throws IOException {
- // Note: closing a byte-array input stream is a no-op.
+ public InputStream getStream() throws IOException {
return new ByteArrayInputStream(bytes);
}
}
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 1c76f46..4fe3773 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
@@ -31,7 +30,6 @@
public class Bisect {
private final BisectOptions options;
- private final DexItemFactory factory = new DexItemFactory();
private final Timing timing = new Timing("bisect");
public interface Command {
@@ -179,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 148c167..489c78d 100644
--- a/src/main/java/com/android/tools/r8/bisect/BisectState.java
+++ b/src/main/java/com/android/tools/r8/bisect/BisectState.java
@@ -142,7 +142,6 @@
}
private final String signature;
- private final DexApplication goodApp;
private final DexApplication badApp;
private final List<DexProgramClass> sortedGoodClasses;
private final Map<DexType, Integer> indexMap;
@@ -154,7 +153,6 @@
private Range nextRange = null;
public BisectState(DexApplication goodApp, DexApplication badApp, File stateFile) {
- this.goodApp = goodApp;
this.badApp = badApp;
this.stateFile = stateFile;
signature = makeSignature(goodApp);
@@ -320,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/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index c871e75..6bbb406 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -472,8 +472,7 @@
+ "Reduce the input-program size or run with --multi-dex enabled");
}
if (isDexFile(output)) {
- try (Closer closer = Closer.create()) {
- InputStream stream = result.getDexResources().get(0).getStream(closer);
+ try (InputStream stream = result.getDexResources().get(0).getStream()) {
Files.copy(stream, output, StandardCopyOption.REPLACE_EXISTING);
}
return;
@@ -544,7 +543,7 @@
// Add dex files.
List<Resource> dexProgramSources = output.getDexResources();
for (int i = 0; i < dexProgramSources.size(); i++) {
- addEntry(getDexFileName(i), dexProgramSources.get(i).getStream(closer), out);
+ addEntry(getDexFileName(i), closer.register(dexProgramSources.get(i).getStream()), out);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 36b65d7..355080f 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -183,7 +183,7 @@
List<DexFileReader> fileReaders = new ArrayList<>(dexSources.size());
int computedMinApiLevel = options.minApiLevel;
for (Resource input : dexSources) {
- DexFile file = new DexFile(input.getStream(closer));
+ DexFile file = new DexFile(closer.register(input.getStream()));
computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
fileReaders.add(new DexFileReader(file, classKind, itemFactory));
}
@@ -207,7 +207,7 @@
JarClassFileReader reader = new JarClassFileReader(
application, classKind.bridgeConsumer(classes::add));
for (Resource input : classSources) {
- reader.read(DEFAULT_DEX_FILENAME, classKind, input.getStream(closer));
+ reader.read(DEFAULT_DEX_FILENAME, classKind, closer.register(input.getStream()));
}
}
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 b3f51ca..6586a57 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/dex/Constants.java b/src/main/java/com/android/tools/r8/dex/Constants.java
index c216cec..977d703 100644
--- a/src/main/java/com/android/tools/r8/dex/Constants.java
+++ b/src/main/java/com/android/tools/r8/dex/Constants.java
@@ -12,6 +12,7 @@
public static final int ANDROID_N_API = 24;
public static final int ANDROID_L_API = 21;
public static final int ANDROID_K_API = 19;
+ public static final int ANDROID_I_API = 14;
public static final int DEFAULT_ANDROID_API = 1;
/** dex file version number for Android O (API level 26) */
@@ -26,7 +27,6 @@
public static final int DEX_MAGIC_SIZE = 8;
- public static final int HEADER_SIZE = 0x70;
public static final int MAGIC_OFFSET = 0;
public static final int CHECKSUM_OFFSET = MAGIC_OFFSET + DEX_MAGIC_SIZE;
public static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + 4;
diff --git a/src/main/java/com/android/tools/r8/dex/DexFileReader.java b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
index 1c37073..768f207 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFileReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
@@ -771,8 +771,7 @@
for (int j = 0; j < realHsize; j++) {
int typeIdx = file.getUleb128();
int addr = file.getUleb128();
- pairs[j] = new TypeAddrPair(indexedItems.getType(typeIdx), addr,
- encodedCatchHandlerOffset);
+ pairs[j] = new TypeAddrPair(indexedItems.getType(typeIdx), addr);
}
int catchAllAddr = -1;
if (hsize <= 0) {
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index b56f712..332f292 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -234,7 +234,7 @@
layout.setEndOfFile(dest.position());
// Now that we have all mixedSectionOffsets, lets write the indexed items.
- dest.moveTo(Constants.HEADER_SIZE);
+ dest.moveTo(Constants.TYPE_HEADER_ITEM_SIZE);
writeFixedSectionItems(mapping.getStrings(), layout.stringIdsOffset, this::writeStringItem);
writeFixedSectionItems(mapping.getTypes(), layout.typeIdsOffset, this::writeTypeItem);
writeFixedSectionItems(mapping.getProtos(), layout.protoIdsOffset, this::writeProtoItem);
@@ -790,7 +790,7 @@
// Leave out checksum and signature for now.
dest.moveTo(Constants.FILE_SIZE_OFFSET);
dest.putInt(layout.getEndOfFile());
- dest.putInt(Constants.HEADER_SIZE);
+ dest.putInt(Constants.TYPE_HEADER_ITEM_SIZE);
dest.putInt(Constants.ENDIAN_CONSTANT);
dest.putInt(0);
dest.putInt(0);
@@ -897,7 +897,7 @@
static Layout from(ObjectToOffsetMapping mapping) {
int offset = 0;
return new Layout(
- offset = Constants.HEADER_SIZE,
+ offset = Constants.TYPE_HEADER_ITEM_SIZE,
offset += mapping.getStrings().length * Constants.TYPE_STRING_ID_ITEM_SIZE,
offset += mapping.getTypes().length * Constants.TYPE_TYPE_ID_ITEM_SIZE,
offset += mapping.getProtos().length * Constants.TYPE_PROTO_ID_ITEM_SIZE,
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/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index a029a61..aedb6df 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -11,7 +11,6 @@
import com.google.common.base.MoreObjects;
import java.util.Arrays;
-import java.util.Comparator;
import java.util.function.Consumer;
public abstract class DexClass extends DexItem {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 3535bfa..d939868 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -428,12 +428,10 @@
public final DexType type;
public final /* offset */ int addr;
- public final /* offset to the start of an encoded_catch_handler. */ int offset;
- public TypeAddrPair(DexType type, int addr, int offset) {
+ public TypeAddrPair(DexType type, int addr) {
this.type = type;
this.addr = addr;
- this.offset = offset;
}
public void collectIndexedItems(IndexedItemCollection indexedItems) {
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 c8fbbea..572bea9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -14,10 +14,8 @@
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeDirect;
import com.android.tools.r8.code.InvokeStatic;
-import com.android.tools.r8.code.InvokeSuper;
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.code.Throw;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.ir.code.IRCode;
@@ -33,7 +31,6 @@
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
public class DexEncodedMethod extends KeyedDexItem<DexMethod> {
@@ -379,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/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 04e34a5..6500e8f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -228,7 +228,6 @@
public final DexMethod appendObject;
public final DexMethod appendString;
public final DexMethod appendStringBuffer;
- public final DexMethod toString;
private StringBuildingMethods(DexType receiver) {
DexType sbufType = createType(createString("Ljava/lang/StringBuffer;"));
@@ -251,7 +250,6 @@
appendObject = createMethod(receiver, createProto(receiver, objectType), append);
appendString = createMethod(receiver, createProto(receiver, stringType), append);
appendStringBuffer = createMethod(receiver, createProto(receiver, sbufType), append);
- toString = createMethod(receiver, createProto(stringType), toStringMethodName);
}
public void forEachAppendMethod(Consumer<DexMethod> consumer) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 234773f..ac7d9ee 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -364,9 +364,8 @@
int leadingSquareBrackets = getNumberOfLeadingSquareBrackets();
byte[] content = new byte[newBase.descriptor.content.length + leadingSquareBrackets];
Arrays.fill(content, 0, leadingSquareBrackets, (byte) '[');
- for (int i = 0; i < newBase.descriptor.content.length; i++) {
- content[leadingSquareBrackets + i] = newBase.descriptor.content[i];
- }
+ System.arraycopy(newBase.descriptor.content, 0, content, leadingSquareBrackets,
+ newBase.descriptor.content.length);
DexString newDesc = dexItemFactory
.createString(newBase.descriptor.size + leadingSquareBrackets, content);
return dexItemFactory.createType(newDesc);
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index a0cfa7c..29f59e5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -593,6 +593,10 @@
this.values = values;
}
+ public DexValue[] getValues() {
+ return values;
+ }
+
@Override
public void collectIndexedItems(IndexedItemCollection indexedItems) {
collectAll(indexedItems, values);
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/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index e814dbe..b5a7b75 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -11,7 +11,6 @@
public class DominatorTree {
- IRCode code;
private BasicBlock[] sorted;
private BasicBlock[] doms;
@@ -21,7 +20,6 @@
// TODO(sgjesse) Get rid of this constructor and blocksToIgnore.
DominatorTree(IRCode code, List<BasicBlock> blocksToIgnore) {
- this.code = code;
this.sorted = code.topologicallySortedBlocks(blocksToIgnore);
numberBlocks();
build();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 9e5c972..f38614f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import java.util.Arrays;
public class InstanceGet extends FieldInstruction {
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 8b348a0..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
@@ -21,7 +21,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -46,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/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 737c7e5..737dc46 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -588,7 +588,7 @@
assert i == handlerGroup.getGuards().size() - 1;
catchAllOffset = targetOffset;
} else {
- pairs.add(new TypeAddrPair(type, targetOffset, -1));
+ pairs.add(new TypeAddrPair(type, targetOffset));
}
}
TypeAddrPair[] pairsArray = pairs.toArray(new TypeAddrPair[pairs.size()]);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index ba63d80..ec09396 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -86,6 +86,8 @@
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -241,11 +243,11 @@
private BasicBlock currentBlock = null;
// Mappings for canonicalizing constants of a given type at IR construction time.
- private Map<Long, ConstNumber> intConstants = new HashMap<>();
- private Map<Long, ConstNumber> longConstants = new HashMap<>();
- private Map<Long, ConstNumber> floatConstants = new HashMap<>();
- private Map<Long, ConstNumber> doubleConstants = new HashMap<>();
- private Map<Long, ConstNumber> nullConstants = new HashMap<>();
+ private Long2ObjectMap<ConstNumber> intConstants = new Long2ObjectArrayMap<>();
+ private Long2ObjectMap<ConstNumber> longConstants = new Long2ObjectArrayMap<>();
+ private Long2ObjectMap<ConstNumber> floatConstants = new Long2ObjectArrayMap<>();
+ private Long2ObjectMap<ConstNumber> doubleConstants = new Long2ObjectArrayMap<>();
+ private Long2ObjectMap<ConstNumber> nullConstants = new Long2ObjectArrayMap<>();
private List<BasicBlock> exitBlocks = new ArrayList<>();
private BasicBlock normalExitBlock;
@@ -669,7 +671,7 @@
// to disable constant canonicalization in debug builds to make sure we have separate values
// for separate locals.
private void canonicalizeAndAddConst(
- ConstType type, int dest, long value, Map<Long, ConstNumber> table) {
+ ConstType type, int dest, long value, Long2ObjectMap<ConstNumber> table) {
ConstNumber existing = table.get(value);
if (existing != null) {
currentBlock.writeCurrentDefinition(dest, existing.outValue(), ThrowingInfo.NO_THROW);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index a2c1cbd..12d8b6a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -195,7 +195,7 @@
JarApplicationReader application) {
DebugLocalInfo info = new DebugLocalInfo(
application.getString(node.name),
- application.getType(Type.getType(node.desc)),
+ application.getTypeFromDescriptor(node.desc),
node.signature == null ? null : application.getString(node.signature));
DebugLocalInfo canonical = canonicalLocalVariables.putIfAbsent(info, info);
return canonical != null ? canonical : info;
@@ -892,15 +892,6 @@
}
}
- static Type getArrayElementType(Type array) {
- if (array == JarState.NULL_TYPE) {
- return null;
- }
- String desc = array.getDescriptor();
- assert desc.charAt(0) == '[';
- return Type.getType(desc.substring(1));
- }
-
private static Type makeArrayType(Type elementType) {
return Type.getObjectType("[" + elementType.getDescriptor());
}
@@ -1153,8 +1144,7 @@
case Opcodes.CALOAD:
case Opcodes.SALOAD: {
state.pop();
- Slot array = state.pop(JarState.ARRAY_TYPE);
- Type elementType = getArrayElementType(array.type);
+ Type elementType = state.pop(JarState.ARRAY_TYPE).getArrayElementType();
if (elementType == null) {
// We propagate the null type, which will then get resolved to an
// actual type if we have a non-null type on another flow edge.
@@ -1860,7 +1850,7 @@
case Opcodes.SALOAD: {
Slot index = state.pop(Type.INT_TYPE);
Slot array = state.pop(JarState.ARRAY_TYPE);
- Type elementType = getArrayElementType(array.type);
+ Type elementType = array.getArrayElementType();
if (elementType == null) {
elementType = getArrayElementTypeForOpcode(opcode);
}
@@ -1880,7 +1870,7 @@
Slot value = state.pop();
Slot index = state.pop(Type.INT_TYPE);
Slot array = state.pop(JarState.ARRAY_TYPE);
- Type elementType = getArrayElementType(array.type);
+ Type elementType = array.getArrayElementType();
if (elementType == null) {
elementType = getArrayElementTypeForOpcode(opcode);
}
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 51a8117..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
@@ -3,8 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
-import static com.android.tools.r8.ir.conversion.JarSourceCode.getArrayElementType;
-
import com.android.tools.r8.graph.DebugLocalInfo;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
@@ -18,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;
@@ -37,9 +36,6 @@
// Type representative for the null value (non-existent but works for tracking the types here).
public static final Type NULL_TYPE = Type.getObjectType("<null>");
- // Type representative for an address type (used by JSR/RET).
- public static final Type ADDR_TYPE = Type.getObjectType("<address>");
-
// Typed mapping from a local slot or stack slot to a virtual register.
public static class Slot {
public final int register;
@@ -66,6 +62,14 @@
return isCategory1(type);
}
+ public Type getArrayElementType() {
+ assert type == NULL_TYPE || type == ARRAY_TYPE || type.getSort() == Type.ARRAY;
+ if (type == JarState.NULL_TYPE) {
+ return null;
+ }
+ return getArrayElementType(type);
+ }
+
public static boolean isCategory1(Type type) {
return type != Type.LONG_TYPE && type != Type.DOUBLE_TYPE;
}
@@ -90,6 +94,12 @@
return type.equals(other);
}
+ private static Type getArrayElementType(Type type) {
+ String desc = type.getDescriptor();
+ assert desc.charAt(0) == '[';
+ return Type.getType(desc.substring(1));
+ }
+
private static boolean isIntCompatible(int sort) {
return Type.BOOLEAN <= sort && sort <= Type.INT;
}
@@ -357,9 +367,7 @@
Snapshot snapshot = targetStates.get(offset);
assert snapshot != null;
assert locals.length == snapshot.locals.length;
- for (int i = 0; i < locals.length; i++) {
- locals[i] = snapshot.locals[i];
- }
+ System.arraycopy(snapshot.locals, 0, locals, 0, locals.length);
stack.clear();
stack.addAll(snapshot.stack);
topOfStack = startOfStack + 2 * stack.size();
@@ -382,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/conversion/SwitchPayloadResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/SwitchPayloadResolver.java
index 5367a3a..1c20efb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SwitchPayloadResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SwitchPayloadResolver.java
@@ -19,14 +19,12 @@
public final static int NO_SIZE = -1;
public int userOffset;
- public int fallthroughOffset;
public int[] absoluteTargets = null;
public int[] keys = null;
public int size = NO_SIZE;
- public PayloadData(int userOffset, int fallthroughOffset) {
+ public PayloadData(int userOffset) {
this.userOffset = userOffset;
- this.fallthroughOffset = fallthroughOffset;
}
}
@@ -36,7 +34,7 @@
public void addPayloadUser(Instruction dex) {
int offset = dex.getOffset();
int payloadOffset = offset + dex.getPayloadOffset();
- payloadToData.put(payloadOffset, new PayloadData(offset, offset + dex.getSize()));
+ payloadToData.put(payloadOffset, new PayloadData(offset));
if (unresolvedPayload.containsKey(payloadOffset)) {
SwitchPayload payload = unresolvedPayload.remove(payloadOffset);
resolve(payload);
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/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index fe59b79..0bd7188 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -39,7 +39,6 @@
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayList;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
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 9f26fb5..d8d3b8d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -5,11 +5,17 @@
import com.android.tools.r8.graph.DexAnnotation;
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.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.naming.signature.GenericSignatureAction;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -22,8 +28,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
-public class ClassNameMinifier {
+class ClassNameMinifier {
private final AppInfoWithLiveness appInfo;
private final RootSet rootSet;
@@ -31,11 +38,16 @@
private final Set<DexString> usedTypeNames = Sets.newIdentityHashSet();
private final Map<DexType, DexString> renaming = Maps.newIdentityHashMap();
- private final Map<String, NamingState> states = new HashMap<>();
+ private final Map<String, ClassNamingState> states = new HashMap<>();
private final List<String> dictionary;
private final boolean keepInnerClassStructure;
- public ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix,
+ private GenericSignatureRewriter genericSignatureRewriter = new GenericSignatureRewriter();
+
+ private GenericSignatureParser<DexType> genericSignatureParser =
+ new GenericSignatureParser<>(genericSignatureRewriter);
+
+ ClassNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, String packagePrefix,
List<String> dictionary, boolean keepInnerClassStructure) {
this.appInfo = appInfo;
this.rootSet = rootSet;
@@ -44,26 +56,61 @@
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 : appInfo.classes()) {
+ for (DexClass clazz : classes) {
if (rootSet.noObfuscation.contains(clazz)) {
assert !renaming.containsKey(clazz.type);
registerClassAsUsed(clazz.type);
}
}
- for (DexClass clazz : appInfo.classes()) {
+ for (DexClass clazz : classes) {
if (!renaming.containsKey(clazz.type)) {
DexString renamed = computeName(clazz);
renaming.put(clazz.type, renamed);
}
}
+
+ renameTypesInGenericSignatures();
+
appInfo.dexItemFactory.forAllTypes(this::renameArrayTypeIfNeeded);
return Collections.unmodifiableMap(renaming);
}
+ private void renameTypesInGenericSignatures() {
+ for (DexClass clazz : appInfo.classes()) {
+ rewriteGenericSignatures(clazz.annotations.annotations,
+ genericSignatureParser::parseClassSignature);
+ clazz.forEachField(field -> rewriteGenericSignatures(
+ field.annotations.annotations, genericSignatureParser::parseFieldSignature));
+ clazz.forEachMethod(method -> rewriteGenericSignatures(
+ method.annotations.annotations, genericSignatureParser::parseMethodSignature));
+ }
+ }
+
+ private void rewriteGenericSignatures(DexAnnotation[] annotations, Consumer<String> parser) {
+ for (int i = 0; i < annotations.length; i++) {
+ DexAnnotation annotation = annotations[i];
+ if (DexAnnotation.isSignatureAnnotation(annotation, appInfo.dexItemFactory)) {
+ parser.accept(getSignatureFromAnnotation(annotation));
+ annotations[i] = DexAnnotation.createSignatureAnnotation(
+ genericSignatureRewriter.getRenamedSignature(),
+ appInfo.dexItemFactory);
+ }
+ }
+ }
+
+ private static String getSignatureFromAnnotation(DexAnnotation signatureAnnotation) {
+ DexValueArray elements = (DexValueArray) signatureAnnotation.annotation.elements[0].value;
+ StringBuilder signature = new StringBuilder();
+ for (DexValue element : elements.getValues()) {
+ signature.append(((DexValueString) element).value.toString());
+ }
+ return signature.toString();
+ }
+
/**
* Registers the given type as used.
* <p>
@@ -105,7 +152,7 @@
}
private DexString computeName(DexClass clazz) {
- NamingState state = null;
+ ClassNamingState state = null;
if (keepInnerClassStructure) {
// When keeping the nesting structure of inner classes, we have to insert the name
// of the outer class for the $ prefix.
@@ -129,11 +176,11 @@
}
}
- private NamingState getStateFor(String packageName) {
- return states.computeIfAbsent(packageName, NamingState::new);
+ private ClassNamingState getStateFor(String packageName) {
+ return states.computeIfAbsent(packageName, ClassNamingState::new);
}
- private NamingState getStateForOuterClass(DexType outer) {
+ private ClassNamingState getStateForOuterClass(DexType outer) {
String prefix = DescriptorUtils
.getClassBinaryNameFromDescriptor(outer.toDescriptorString());
return states.computeIfAbsent(prefix, k -> {
@@ -150,7 +197,7 @@
}
}
String binaryName = DescriptorUtils.getClassBinaryNameFromDescriptor(renamed.toString());
- return new NamingState(binaryName, "$");
+ return new ClassNamingState(binaryName, "$");
});
}
@@ -171,21 +218,19 @@
}
}
- private class NamingState {
+ private class ClassNamingState {
private final char[] packagePrefix;
- private final String separator;
private int typeCounter = 1;
private Iterator<String> dictionaryIterator;
- NamingState(String packageName) {
+ ClassNamingState(String packageName) {
this(packageName, "/");
}
- NamingState(String packageName, String separator) {
+ ClassNamingState(String packageName, String separator) {
this.packagePrefix = ("L" + packageName + (packageName.isEmpty() ? "" : separator))
.toCharArray();
- this.separator = separator;
this.dictionaryIterator = dictionary.iterator();
}
@@ -211,4 +256,63 @@
return candidate;
}
}
+
+ private class GenericSignatureRewriter implements GenericSignatureAction<DexType> {
+
+ private StringBuilder renamedSignature;
+
+ public String getRenamedSignature() {
+ return renamedSignature.toString();
+ }
+
+ @Override
+ public void parsedSymbol(char symbol) {
+ renamedSignature.append(symbol);
+ }
+
+ @Override
+ public void parsedIdentifier(String identifier) {
+ renamedSignature.append(identifier);
+ }
+
+ @Override
+ public DexType parsedTypeName(String name) {
+ DexType type = appInfo.dexItemFactory.createType(
+ DescriptorUtils.getDescriptorFromClassBinaryName(name));
+ DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
+ renamedSignature.append(DescriptorUtils.getClassBinaryNameFromDescriptor(
+ renamedDescriptor.toString()));
+ return type;
+ }
+
+ @Override
+ public DexType parsedInnerTypeName(DexType enclosingType, String name) {
+ assert enclosingType.isClassType();
+ String enclosingDescriptor = enclosingType.toDescriptorString();
+ DexType type =
+ appInfo.dexItemFactory.createType(
+ DescriptorUtils.getDescriptorFromClassBinaryName(
+ DescriptorUtils.getClassBinaryNameFromDescriptor(enclosingDescriptor)
+ + '$' + name));
+ String enclosingRenamedBinaryName =
+ DescriptorUtils.getClassBinaryNameFromDescriptor(renaming.getOrDefault(enclosingType,
+ enclosingType.descriptor).toString());
+ String renamed = DescriptorUtils.getClassBinaryNameFromDescriptor(
+ renaming.getOrDefault(type, type.descriptor).toString());
+ assert renamed.startsWith(enclosingRenamedBinaryName + '$');
+ String outName = renamed.substring(enclosingRenamedBinaryName.length() + 1);
+ renamedSignature.append(outName);
+ return type;
+ }
+
+ @Override
+ public void start() {
+ renamedSignature = new StringBuilder();
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+ }
}
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/naming/signature/GenericSignatureAction.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
new file mode 100644
index 0000000..6753e06
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
@@ -0,0 +1,23 @@
+// 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.naming.signature;
+
+/**
+ * Actions triggered by the generic signature parser.
+ */
+public interface GenericSignatureAction<T> {
+
+ public void parsedSymbol(char symbol);
+
+ public void parsedIdentifier(String identifier);
+
+ public T parsedTypeName(String name);
+
+ public T parsedInnerTypeName(T enclosingType, String name);
+
+ public void start();
+
+ public void stop();
+}
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
new file mode 100644
index 0000000..c5d78f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
@@ -0,0 +1,403 @@
+// Copyright (c) 2016, 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.naming.signature;
+
+import java.lang.reflect.GenericSignatureFormatError;
+
+/**
+ * Implements a parser for the generics signature attribute as defined by JVMS 7 $ 4.3.4.
+ * Uses a top-down, recursive descent parsing approach for the following grammar:
+ * <pre>
+ * ClassSignature ::=
+ * OptFormalTypeParams SuperclassSignature {SuperinterfaceSignature}.
+ * SuperclassSignature ::= ClassTypeSignature.
+ * SuperinterfaceSignature ::= ClassTypeSignature.
+ *
+ * OptFormalTypeParams ::=
+ * ["<" FormalTypeParameter {FormalTypeParameter} ">"].
+ *
+ * FormalTypeParameter ::= Ident ClassBound {InterfaceBound}.
+ * ClassBound ::= ":" [FieldTypeSignature].
+ * InterfaceBound ::= ":" FieldTypeSignature.
+ *
+ * FieldTypeSignature ::=
+ * ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
+ * ArrayTypeSignature ::= "[" TypSignature.
+ *
+ * ClassTypeSignature ::=
+ * "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments} ";".
+ *
+ * OptTypeArguments ::= "<" TypeArgument {TypeArgument} ">".
+ *
+ * TypeArgument ::= ([WildcardIndicator] FieldTypeSignature) | "*".
+ * WildcardIndicator ::= "+" | "-".
+ *
+ * TypeVariableSignature ::= "T" Ident ";".
+ *
+ * TypSignature ::= FieldTypeSignature | BaseType.
+ * BaseType ::= "B" | "C" | "D" | "F" | "I" | "J" | "S" | "Z".
+ *
+ * MethodTypeSignature ::=
+ * OptFormalTypeParams "(" {TypeSignature} ")" ReturnType {ThrowsSignature}.
+ * ThrowsSignature ::= ("^" ClassTypeSignature) | ("^" TypeVariableSignature).
+ *
+ * ReturnType ::= TypSignature | VoidDescriptor.
+ * VoidDescriptor ::= "V".
+ * </pre>
+ */
+public class GenericSignatureParser<T> {
+
+ private final GenericSignatureAction<T> actions;
+
+ /*
+ * Parser:
+ */
+ private char symbol; // 0: eof; else valid term symbol or first char of identifier.
+
+ private String identifier;
+
+ /*
+ * Scanner:
+ * eof is private to the scan methods
+ * and it's set only when a scan is issued at the end of the buffer.
+ */
+ private boolean eof;
+
+ private char[] buffer;
+
+ private int pos;
+
+ public GenericSignatureParser(GenericSignatureAction<T> actions) {
+ this.actions = actions;
+ }
+
+ public void parseClassSignature(String signature) {
+ actions.start();
+ setInput(signature);
+ parseClassSignature();
+ actions.stop();
+ }
+
+ public void parseMethodSignature(String signature) {
+ actions.start();
+ setInput(signature);
+ parseMethodTypeSignature();
+ actions.stop();
+ }
+
+ public void parseFieldSignature(String signature) {
+ actions.start();
+ setInput(signature);
+ parseFieldTypeSignature();
+ actions.stop();
+ }
+
+ private void setInput(String input) {
+ this.buffer = input.toCharArray();
+ this.eof = false;
+ pos = 0;
+ symbol = 0;
+ identifier = null;
+ scanSymbol();
+ }
+
+ //
+ // Parser:
+ //
+
+ void parseClassSignature() {
+ // ClassSignature ::= OptFormalTypeParameters SuperclassSignature {SuperinterfaceSignature}.
+
+ parseOptFormalTypeParameters();
+
+ // SuperclassSignature ::= ClassTypeSignature.
+ parseClassTypeSignature();
+
+ while (symbol > 0) {
+ // SuperinterfaceSignature ::= ClassTypeSignature.
+ parseClassTypeSignature();
+ }
+ }
+
+ void parseOptFormalTypeParameters() {
+ // OptFormalTypeParameters ::= ["<" FormalTypeParameter {FormalTypeParameter} ">"].
+
+ if (symbol == '<') {
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+
+ updateFormalTypeParameter();
+
+ while ((symbol != '>') && (symbol > 0)) {
+ updateFormalTypeParameter();
+ }
+
+ actions.parsedSymbol(symbol);
+ expect('>');
+ }
+ }
+
+ void updateFormalTypeParameter() {
+ // FormalTypeParameter ::= Ident ClassBound {InterfaceBound}.
+ scanIdentifier();
+ assert identifier != null;
+ actions.parsedIdentifier(identifier);
+
+ // ClassBound ::= ":" [FieldTypeSignature].
+ actions.parsedSymbol(symbol);
+ expect(':');
+
+ if (symbol == 'L' || symbol == '[' || symbol == 'T') {
+ parseFieldTypeSignature();
+ }
+
+ while (symbol == ':') {
+ // InterfaceBound ::= ":" FieldTypeSignature.
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+ parseFieldTypeSignature();
+ }
+ }
+
+ private void parseFieldTypeSignature() {
+ // FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
+ switch (symbol) {
+ case 'L':
+ parseClassTypeSignature();
+ break;
+ case '[':
+ // ArrayTypeSignature ::= "[" TypSignature.
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+ updateTypeSignature();
+ break;
+ case 'T':
+ updateTypeVariableSignature();
+ break;
+ default:
+ throw new GenericSignatureFormatError();
+ }
+ }
+
+ private void parseClassTypeSignature() {
+ // ClassTypeSignature ::= "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments}
+ // ";".
+ actions.parsedSymbol(symbol);
+ expect('L');
+
+ StringBuilder qualIdent = new StringBuilder();
+ scanIdentifier();
+ assert identifier != null;
+ while (symbol == '/') {
+ qualIdent.append(identifier).append(symbol);
+ scanSymbol();
+ scanIdentifier();
+ assert identifier != null;
+ }
+
+ qualIdent.append(this.identifier);
+ T parsedEnclosingType = actions.parsedTypeName(qualIdent.toString());
+
+ updateOptTypeArguments();
+
+ while (symbol == '.') {
+ // Deal with Member Classes:
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+ scanIdentifier();
+ assert identifier != null;
+ parsedEnclosingType = actions.parsedInnerTypeName(parsedEnclosingType, identifier);
+ updateOptTypeArguments();
+ }
+
+ actions.parsedSymbol(symbol);
+ expect(';');
+ }
+
+ private void updateOptTypeArguments() {
+ // OptTypeArguments ::= "<" TypeArgument {TypeArgument} ">".
+ if (symbol == '<') {
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+
+ updateTypeArgument();
+ while ((symbol != '>') && (symbol > 0)) {
+ updateTypeArgument();
+ }
+
+ actions.parsedSymbol(symbol);
+ expect('>');
+ }
+ }
+
+ private void updateTypeArgument() {
+ // TypeArgument ::= (["+" | "-"] FieldTypeSignature) | "*".
+ if (symbol == '*') {
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+ } else if (symbol == '+') {
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+ parseFieldTypeSignature();
+ } else if (symbol == '-') {
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+ parseFieldTypeSignature();
+ } else {
+ parseFieldTypeSignature();
+ }
+ }
+
+ private void updateTypeVariableSignature() {
+ // TypeVariableSignature ::= "T" Ident ";".
+ actions.parsedSymbol(symbol);
+ expect('T');
+
+ scanIdentifier();
+ assert identifier != null;
+ actions.parsedIdentifier(identifier);
+
+ actions.parsedSymbol(symbol);
+ expect(';');
+ }
+
+ private void updateTypeSignature() {
+ switch (symbol) {
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'F':
+ case 'I':
+ case 'J':
+ case 'S':
+ case 'Z':
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+ break;
+ default:
+ // Not an elementary type, but a FieldTypeSignature.
+ parseFieldTypeSignature();
+ }
+ }
+
+ private void parseMethodTypeSignature() {
+ // MethodTypeSignature ::= [FormalTypeParameters] "(" {TypeSignature} ")" ReturnType
+ // {ThrowsSignature}.
+ parseOptFormalTypeParameters();
+
+ actions.parsedSymbol(symbol);
+ expect('(');
+
+ while (symbol != ')' && (symbol > 0)) {
+ updateTypeSignature();
+ }
+
+ actions.parsedSymbol(symbol);
+ expect(')');
+
+ updateReturnType();
+
+ if (symbol == '^') {
+ do {
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+
+ // ThrowsSignature ::= ("^" ClassTypeSignature) | ("^" TypeVariableSignature).
+ if (symbol == 'T') {
+ updateTypeVariableSignature();
+ } else {
+ parseClassTypeSignature();
+ }
+ } while (symbol == '^');
+ }
+ }
+
+ private void updateReturnType() {
+ // ReturnType ::= TypeSignature | "V".
+ if (symbol != 'V') {
+ updateTypeSignature();
+ } else {
+ actions.parsedSymbol(symbol);
+ scanSymbol();
+ }
+ }
+
+
+ //
+ // Scanner:
+ //
+
+ private void scanSymbol() {
+ if (!eof) {
+ assert buffer != null;
+ if (pos < buffer.length) {
+ symbol = buffer[pos];
+ pos++;
+ } else {
+ symbol = 0;
+ eof = true;
+ }
+ } else {
+ throw new GenericSignatureFormatError();
+ }
+ }
+
+ private void expect(char c) {
+ if (symbol == c) {
+ scanSymbol();
+ } else {
+ throw new GenericSignatureFormatError();
+ }
+ }
+
+ private boolean isStopSymbol(char ch) {
+ switch (ch) {
+ case ':':
+ case '/':
+ case ';':
+ case '<':
+ case '.':
+ return true;
+ }
+ return false;
+ }
+
+ // PRE: symbol is the first char of the identifier.
+ // POST: symbol = the next symbol AFTER the identifier.
+ private void scanIdentifier() {
+ if (!eof) {
+ StringBuilder identBuf = new StringBuilder(32);
+ if (!isStopSymbol(symbol)) {
+ identBuf.append(symbol);
+
+ // FINDBUGS
+ char[] bufferLocal = buffer;
+ assert bufferLocal != null;
+ do {
+ char ch = bufferLocal[pos];
+ if ((ch >= 'a') && (ch <= 'z') || (ch >= 'A') && (ch <= 'Z')
+ || !isStopSymbol(ch)) {
+ identBuf.append(bufferLocal[pos]);
+ pos++;
+ } else {
+ identifier = identBuf.toString();
+ scanSymbol();
+ return;
+ }
+ } while (pos != bufferLocal.length);
+ identifier = identBuf.toString();
+ symbol = 0;
+ eof = true;
+ } else {
+ // Ident starts with incorrect char.
+ symbol = 0;
+ eof = true;
+ throw new GenericSignatureFormatError();
+ }
+ } else {
+ throw new GenericSignatureFormatError();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 4cd44ef..3106c0c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -21,17 +21,15 @@
public class AnnotationRemover {
private final AppInfoWithLiveness appInfo;
- private final boolean minificationEnabled;
private final AttributeRemovalOptions keep;
public AnnotationRemover(AppInfoWithLiveness appInfo, InternalOptions options) {
- this(appInfo, !options.skipMinification, options.attributeRemoval);
+ this(appInfo, options.attributeRemoval);
}
- public AnnotationRemover(AppInfoWithLiveness appInfo, boolean minificationEnabled,
+ public AnnotationRemover(AppInfoWithLiveness appInfo,
AttributeRemovalOptions keep) {
this.appInfo = appInfo;
- this.minificationEnabled = minificationEnabled;
this.keep = keep;
}
@@ -104,7 +102,7 @@
}
public void run() {
- keep.ensureValid(minificationEnabled);
+ keep.ensureValid();
for (DexProgramClass clazz : appInfo.classes()) {
clazz.annotations = stripAnnotations(clazz.annotations, this::filterAnnotations);
clazz.forEachMethod(this::processMethod);
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index a7a27fe..e675525 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import java.util.Arrays;
import java.util.Set;
public class DiscardedChecker {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index f6ac140..b589535 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -11,10 +11,8 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
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.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProgramClass;
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..e767030 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 : closer.register(deadCode.getStream());
+ }
+
+ /**
* True if the proguard-map resource exists.
*/
public boolean hasProguardMap() {
@@ -179,7 +196,7 @@
* Get the input stream of the proguard-map resource if it exists.
*/
public InputStream getProguardMap(Closer closer) throws IOException {
- return proguardMap == null ? null : proguardMap.getStream(closer);
+ return proguardMap == null ? null : closer.register(proguardMap.getStream());
}
/**
@@ -193,7 +210,7 @@
* Get the input stream of the proguard-seeds resource if it exists.
*/
public InputStream getProguardSeeds(Closer closer) throws IOException {
- return proguardSeeds == null ? null : proguardSeeds.getStream(closer);
+ return proguardSeeds == null ? null : closer.register(proguardSeeds.getStream());
}
/**
@@ -207,7 +224,7 @@
* Get the input stream of the package distribution resource if it exists.
*/
public InputStream getPackageDistribution(Closer closer) throws IOException {
- return packageDistribution == null ? null : packageDistribution.getStream(closer);
+ return packageDistribution == null ? null : closer.register(packageDistribution.getStream());
}
/**
@@ -221,7 +238,7 @@
* Get the input stream of the main dex list resource if it exists.
*/
public InputStream getMainDexList(Closer closer) throws IOException {
- return mainDexList == null ? null : mainDexList.getStream(closer);
+ return mainDexList == null ? null : closer.register(mainDexList.getStream());
}
/**
@@ -254,7 +271,7 @@
if (!Files.exists(filePath.getParent())) {
Files.createDirectories(filePath.getParent());
}
- Files.copy(dexProgramSources.get(i).getStream(closer), filePath, options);
+ Files.copy(closer.register(dexProgramSources.get(i).getStream()), filePath, options);
}
}
}
@@ -290,7 +307,7 @@
List<Resource> dexProgramSources = getDexProgramResources();
for (int i = 0; i < dexProgramSources.size(); i++) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
- ByteStreams.copy(dexProgramSources.get(i).getStream(closer), out);
+ ByteStreams.copy(closer.register(dexProgramSources.get(i).getStream()), out);
dex.add(out.toByteArray());
}
// TODO(sgjesse): Add Proguard map and seeds.
@@ -309,7 +326,8 @@
List<Resource> dexProgramSources = getDexProgramResources();
for (int i = 0; i < dexProgramSources.size(); i++) {
ZipEntry zipEntry = new ZipEntry(outputMode.getOutputPath(dexProgramSources.get(i), i));
- byte[] bytes = ByteStreams.toByteArray(dexProgramSources.get(i).getStream(closer));
+ byte[] bytes =
+ ByteStreams.toByteArray(closer.register(dexProgramSources.get(i).getStream()));
zipEntry.setSize(bytes.length);
out.putNextEntry(zipEntry);
out.write(bytes);
@@ -337,6 +355,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 +369,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 +384,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 +525,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 +595,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/ClassProvider.java b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
index 25abdce..63a847a 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
@@ -100,7 +100,7 @@
try (Closer closer = Closer.create()) {
JarClassFileReader classReader =
new JarClassFileReader(reader, classKind.bridgeConsumer(classConsumer));
- classReader.read(DEFAULT_DEX_FILENAME, classKind, resource.getStream(closer));
+ classReader.read(DEFAULT_DEX_FILENAME, classKind, closer.register(resource.getStream()));
} catch (IOException e) {
throw new CompilationError("Failed to load class: " + descriptor, e);
}
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 eae33d1..277ee0f 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -148,6 +148,18 @@
return classDescriptor.substring(1, classDescriptor.length() - 1);
}
+
+ /**
+ * Convert a class binary name to a descriptor.
+ *
+ * @param typeBinaryName class binary name i.e. "java/lang/Object"
+ * @return a class descriptor i.e. "Ljava/lang/Object;"
+ */
+ public static String getDescriptorFromClassBinaryName(String typeBinaryName) {
+ return ('L' + typeBinaryName + ';');
+ }
+
+
/**
* Get class name from its binary name.
*
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/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 6149846..eb746d7 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -7,14 +7,12 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.ir.conversion.CallGraph;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.shaking.ProguardTypeMatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.List;
-import java.util.function.BiFunction;
import java.util.function.Function;
public class InternalOptions {
@@ -251,7 +249,7 @@
annotationDefault = update(annotationDefault, ANNOTATION_DEFAULT, patterns);
}
- public void ensureValid(boolean isMinifying) {
+ public void ensureValid() {
if (innerClasses && !enclosingMethod) {
throw new CompilationError("Attribute InnerClasses requires EnclosingMethod attribute. "
+ "Check -keepattributes directive.");
@@ -261,10 +259,6 @@
} else if (signature && !innerClasses) {
throw new CompilationError("Attribute Signature requires InnerClasses attribute. Check "
+ "-keepattributes directive.");
- } else if (signature && isMinifying) {
- // TODO(38188583): Allow this once we can minify signatures.
- throw new CompilationError("Attribute Signature cannot be kept when minifying. "
- + "Check -keepattributes directive.");
}
}
}
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/minifygeneric/AA.java b/src/test/examples/minifygeneric/AA.java
new file mode 100644
index 0000000..66b7aaf8
--- /dev/null
+++ b/src/test/examples/minifygeneric/AA.java
@@ -0,0 +1,7 @@
+// 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 minifygeneric;
+
+public class AA {
+}
diff --git a/src/test/examples/minifygeneric/BB.java b/src/test/examples/minifygeneric/BB.java
new file mode 100644
index 0000000..c29ca97
--- /dev/null
+++ b/src/test/examples/minifygeneric/BB.java
@@ -0,0 +1,7 @@
+// 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 minifygeneric;
+
+public class BB {
+}
diff --git a/src/test/examples/minifygeneric/Generic.java b/src/test/examples/minifygeneric/Generic.java
new file mode 100644
index 0000000..53f9e3d
--- /dev/null
+++ b/src/test/examples/minifygeneric/Generic.java
@@ -0,0 +1,32 @@
+// 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 minifygeneric;
+
+import java.util.List;
+import java.util.Map;
+
+public class Generic<T extends AA> {
+
+ public <U extends List> T m (Object o, T[] t, Map<T,U> m) {
+ return null;
+ }
+
+ public <U extends Map> T m2 (Object o, T[] t, Map<T,U> m) {
+ return null;
+ }
+
+ public <U extends List> T m3 (Object o, T t, Map<T,U> m) {
+ return null;
+ }
+
+ public <U extends List> T m4 (Object o, T[] t, List<U> m) {
+ return null;
+ }
+
+ public Generic<T> f;
+ public Generic<T> get() {
+ return this;
+ }
+
+}
diff --git a/src/test/examples/minifygeneric/Minifygeneric.java b/src/test/examples/minifygeneric/Minifygeneric.java
new file mode 100644
index 0000000..36406bb
--- /dev/null
+++ b/src/test/examples/minifygeneric/Minifygeneric.java
@@ -0,0 +1,42 @@
+// 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 minifygeneric;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+public class Minifygeneric {
+
+ public static void main(String[] args) throws NoSuchMethodException, SecurityException, NoSuchFieldException {
+ for (TypeVariable<Class<Generic>> var : Generic.class.getTypeParameters()) {
+ System.out.println(var.getName());
+ Type bound = var.getBounds()[0];
+ System.out.println(((Class<?>) bound).getName().equals(AA.class.getName()));
+ }
+
+ Field f = Generic.class.getField("f");
+ ParameterizedType fieldType = (java.lang.reflect.ParameterizedType)f.getGenericType();
+ checkOneParameterType(fieldType, Generic.class, AA.class);
+
+ ParameterizedType methodReturnType =
+ (ParameterizedType) Generic.class.getMethod("get").getGenericReturnType();
+ checkOneParameterType(methodReturnType, Generic.class, AA.class);
+ }
+
+ private static void checkOneParameterType(ParameterizedType toCheck, Class<?> rawType,
+ Class<?>... bounds) {
+ System.out.println(((Class<?>) toCheck.getRawType()).getName()
+ .equals(rawType.getName()));
+ Type[] parameters = toCheck.getActualTypeArguments();
+ System.out.println(parameters.length);
+ TypeVariable<?> parameter = (TypeVariable<?>) parameters[0];
+ System.out.println(parameter.getName());
+ Type[] actualBounds = parameter.getBounds();
+ for (int i = 0; i < bounds.length; i++) {
+ System.out.println(((Class<?>) actualBounds[i]).getName().equals(bounds[i].getName()));
+ }
+ }
+}
diff --git a/src/test/examples/minifygeneric/keep-rules.txt b/src/test/examples/minifygeneric/keep-rules.txt
new file mode 100644
index 0000000..741d5c3
--- /dev/null
+++ b/src/test/examples/minifygeneric/keep-rules.txt
@@ -0,0 +1,15 @@
+# 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,allowobfuscation class ** {
+*;
+}
+-keep class *.Minifygeneric {
+*;
+}
+-keepclassmembernames class *.Generic {
+*;
+}
+-keepattributes *
+-allowaccessmodification
diff --git a/src/test/examples/minifygenericwithinner/AA.java b/src/test/examples/minifygenericwithinner/AA.java
new file mode 100644
index 0000000..ff283da
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/AA.java
@@ -0,0 +1,7 @@
+// 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 minifygenericwithinner;
+
+public class AA {
+}
diff --git a/src/test/examples/minifygenericwithinner/BB.java b/src/test/examples/minifygenericwithinner/BB.java
new file mode 100644
index 0000000..31ceb33
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/BB.java
@@ -0,0 +1,7 @@
+// 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 minifygenericwithinner;
+
+public class BB {
+}
diff --git a/src/test/examples/minifygenericwithinner/Generic.java b/src/test/examples/minifygenericwithinner/Generic.java
new file mode 100644
index 0000000..203561e
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/Generic.java
@@ -0,0 +1,42 @@
+// 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 minifygenericwithinner;
+
+import java.util.List;
+import java.util.Map;
+
+public class Generic<T extends AA> {
+
+ public <U extends List> T m (Object o, T[] t, Map<T,U> m) {
+ return null;
+ }
+
+ public <U extends Map> T m2 (Object o, T[] t, Map<T,U> m) {
+ return null;
+ }
+
+ public <U extends List> T m3 (Object o, T t, Map<T,U> m) {
+ return null;
+ }
+
+ public <U extends List> T m4 (Object o, T[] t, List<U> m) {
+ return null;
+ }
+
+ public <V extends BB> Inner<V> getInner(V obj) {
+ return new Inner<>();
+ }
+
+ public class Inner<V extends BB> {
+
+ public Generic<T>.Inner<V> f;
+ public <U extends List> T m5 (V o, T[] t, Map<T,U> m) {
+ return m(o, t, m);
+ }
+
+ public Generic<T>.Inner<V> get() {
+ return this;
+ }
+ }
+}
diff --git a/src/test/examples/minifygenericwithinner/Minifygenericwithinner.java b/src/test/examples/minifygenericwithinner/Minifygenericwithinner.java
new file mode 100644
index 0000000..882fa83
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/Minifygenericwithinner.java
@@ -0,0 +1,52 @@
+// 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 minifygenericwithinner;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+public class Minifygenericwithinner {
+
+ public static void main(String[] args)
+ throws NoSuchMethodException, SecurityException, NoSuchFieldException {
+ for (TypeVariable<Class<Generic>> var : Generic.class.getTypeParameters()) {
+ System.out.println(var.getName());
+ Type bound = var.getBounds()[0];
+ System.out.println(((Class<?>) bound).getName().equals(AA.class.getName()));
+ }
+ for (TypeVariable<Class<Generic.Inner>> var : Generic.Inner.class.getTypeParameters()) {
+ System.out.println(var.getName());
+ Type bound = var.getBounds()[0];
+ System.out.println(((Class<?>) bound).getName().equals(BB.class.getName()));
+ }
+
+ Field f = Generic.Inner.class.getField("f");
+ ParameterizedType fieldType = (java.lang.reflect.ParameterizedType)f.getGenericType();
+ checkOneParameterType(fieldType, Generic.Inner.class, BB.class);
+ ParameterizedType ownerType = (ParameterizedType) fieldType.getOwnerType();
+ checkOneParameterType(ownerType, Generic.class, AA.class);
+
+ ParameterizedType methodReturnType =
+ (ParameterizedType) Generic.Inner.class.getMethod("get").getGenericReturnType();
+ checkOneParameterType(methodReturnType, Generic.Inner.class, BB.class);
+ ownerType = (ParameterizedType) methodReturnType.getOwnerType();
+ checkOneParameterType(ownerType, Generic.class, AA.class);
+ }
+
+ private static void checkOneParameterType(ParameterizedType toCheck, Class<?> rawType,
+ Class<?>... bounds) {
+ System.out.println(((Class<?>) toCheck.getRawType()).getName()
+ .equals(rawType.getName()));
+ Type[] parameters = toCheck.getActualTypeArguments();
+ System.out.println(parameters.length);
+ TypeVariable<?> parameter = (TypeVariable<?>) parameters[0];
+ System.out.println(parameter.getName());
+ Type[] actualBounds = parameter.getBounds();
+ for (int i = 0; i < bounds.length; i++) {
+ System.out.println(((Class<?>) actualBounds[i]).getName().equals(bounds[i].getName()));
+ }
+ }
+}
diff --git a/src/test/examples/minifygenericwithinner/keep-rules.txt b/src/test/examples/minifygenericwithinner/keep-rules.txt
new file mode 100644
index 0000000..357b321
--- /dev/null
+++ b/src/test/examples/minifygenericwithinner/keep-rules.txt
@@ -0,0 +1,16 @@
+# 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,allowobfuscation class ** {
+*;
+}
+-keep class *.Minifygenericwithinner {
+*;
+}
+-keepclassmembernames class *.Generic$* {
+*;
+}
+
+-keepattributes *
+-allowaccessmodification
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/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index d8b9bbc..803d760 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -294,7 +294,7 @@
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[16384];
try (Closer closer = Closer.create()) {
- InputStream stream = resource.getStream(closer);
+ InputStream stream = closer.register(resource.getStream());
int read;
while ((read = stream.read(buffer, 0, buffer.length)) != -1) {
output.write(buffer, 0, read);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 9aec981..38868b6 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -61,18 +61,16 @@
}
TestRunner withClassCheck(Consumer<FoundClassSubject> check) {
- withDexCheck(inspector -> inspector.forAllClasses(check));
- return this;
+ return withDexCheck(inspector -> inspector.forAllClasses(check));
}
TestRunner withMethodCheck(Consumer<FoundMethodSubject> check) {
- withClassCheck(clazz -> clazz.forAllMethods(check));
- return this;
+ return withClassCheck(clazz -> clazz.forAllMethods(check));
}
- <T extends InstructionSubject> TestRunner
- withInstructionCheck(Predicate<InstructionSubject> filter, Consumer<T> check) {
- withMethodCheck(method -> {
+ <T extends InstructionSubject> TestRunner withInstructionCheck(
+ Predicate<InstructionSubject> filter, Consumer<T> check) {
+ return withMethodCheck(method -> {
if (method.isAbstract()) {
return;
}
@@ -81,7 +79,6 @@
check.accept(iterator.next());
}
});
- return this;
}
TestRunner withOptionConsumer(Consumer<InternalOptions> consumer) {
@@ -267,7 +264,7 @@
@Test
public void paramNames() throws Throwable {
test("paramnames", "paramnames", "ParameterNames")
- .withMinApiLevel(26)
+ .withMinApiLevel(ANDROID_O_API)
.withOptionConsumer((internalOptions) -> internalOptions.allowParameterName = true)
.run();
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e01ecc0..d0032cd 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
@@ -56,7 +57,7 @@
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
- private static final int DEFAULT_MIN_SDK = 14;
+ private static final int DEFAULT_MIN_SDK = Constants.ANDROID_I_API;
public enum DexVm {
ART_4_4_4("4.4.4"),
@@ -487,6 +488,14 @@
.build());
}
+ public static DexApplication optimizeWithR8(
+ DexApplication application,
+ AppInfoWithSubtyping appInfo,
+ InternalOptions options)
+ throws ProguardRuleParserException, ExecutionException, IOException {
+ return R8.optimize(application, appInfo, options);
+ }
+
public static AndroidApp runD8(AndroidApp app) throws CompilationException, IOException {
return runD8(app, null);
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index 8fdcfa5..fc9ee3f 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -62,8 +62,8 @@
List<Resource> files2 = app2.getDexProgramResources();
assertEquals(files1.size(), files2.size());
for (int index = 0; index < files1.size(); index++) {
- InputStream file1 = files1.get(index).getStream(closer);
- InputStream file2 = files2.get(index).getStream(closer);
+ InputStream file1 = closer.register(files1.get(index).getStream());
+ InputStream file2 = closer.register(files2.get(index).getStream());
byte[] bytes1 = ByteStreams.toByteArray(file1);
byte[] bytes2 = ByteStreams.toByteArray(file2);
assertArrayEquals("File index " + index, bytes1, bytes2);
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
index d28c37d..fa193d4 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
@@ -34,7 +34,7 @@
int bytes = 0;
try (Closer closer = Closer.create()) {
for (Resource dex : app.getDexProgramResources()) {
- bytes += ByteStreams.toByteArray(dex.getStream(closer)).length;
+ bytes += ByteStreams.toByteArray(closer.register(dex.getStream())).length;
}
}
assertTrue("Expected max size of " + maxSize + ", got " + bytes, bytes < maxSize);
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
index b26d1aa..8ee3674 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
@@ -36,7 +36,7 @@
int bytes = 0;
try (Closer closer = Closer.create()) {
for (Resource dex : app.getDexProgramResources()) {
- bytes += ByteStreams.toByteArray(dex.getStream(closer)).length;
+ bytes += ByteStreams.toByteArray(closer.register(dex.getStream())).length;
}
}
assertTrue("Expected max size of " + maxSize + ", got " + bytes, bytes < maxSize);
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..04d84e4 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,
@@ -131,6 +132,6 @@
protected static DexApplication process(DexApplication app, InternalOptions options)
throws IOException, ProguardRuleParserException, ExecutionException {
- return new R8(options).optimize(app, new AppInfoWithSubtyping(app));
+ return ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), options);
}
}
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 f4baa2d..69fdd65 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -477,7 +477,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/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 2549ff1..57c8cf6 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.maindexlist;
+import static com.android.tools.r8.dex.Constants.ANDROID_I_API;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
@@ -44,7 +45,7 @@
EXAMPLE_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
- 14);
+ ANDROID_I_API);
}
@Test
@@ -55,7 +56,7 @@
EXAMPLE_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "main-dex-rules-2.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-2.txt"),
- 14);
+ ANDROID_I_API);
}
@Test
@@ -66,7 +67,7 @@
EXAMPLE_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex002", "ref-list-1.txt"),
- 14);
+ ANDROID_I_API);
}
@Test
@@ -77,7 +78,7 @@
EXAMPLE_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex003", "ref-list-1.txt"),
- 14);
+ ANDROID_I_API);
}
@Test
@@ -88,7 +89,7 @@
EXAMPLE_O_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
- 14);
+ ANDROID_I_API);
}
private void doTest(
@@ -137,9 +138,9 @@
CompilationResult result = ToolHelper.runR8WithFullResult(command, optionsConsumer);
List<String> resultMainDexList =
result.dexApplication.mainDexList.stream()
- .filter(dexType -> isApplicationClass(dexType, result) != null)
- .map(dexType -> dexType.descriptor.toString())
- .collect(Collectors.toList());
+ .filter(dexType -> isApplicationClass(dexType, result))
+ .map(dexType -> dexType.descriptor.toString())
+ .collect(Collectors.toList());
Collections.sort(resultMainDexList);
String[] refList = new String(Files.readAllBytes(
expectedMainDexList), StandardCharsets.UTF_8).split("\n");
@@ -161,7 +162,7 @@
}
}
- private Object isApplicationClass(DexType dexType, CompilationResult result) {
+ private boolean isApplicationClass(DexType dexType, CompilationResult result) {
DexClass clazz = result.appInfo.definitionFor(dexType);
return clazz != null && clazz.isProgramClass();
}
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 3487694..5b94e6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -69,7 +69,12 @@
"shaking16:keep-rules-2.txt:DEX:false",
"shaking16:keep-rules-2.txt:JAR:false",
"shaking15:keep-rules.txt:DEX:false",
- "shaking15:keep-rules.txt:JAR:false"
+ "shaking15:keep-rules.txt:JAR:false",
+ "minifygeneric:keep-rules.txt:DEX:false",
+ "minifygeneric:keep-rules.txt:JAR:false",
+ "minifygenericwithinner:keep-rules.txt:DEX:false",
+ "minifygenericwithinner:keep-rules.txt:JAR:false"
+
);
private final boolean minify;
@@ -534,6 +539,8 @@
"shaking16",
"inlining",
"minification",
+ "minifygeneric",
+ "minifygenericwithinner",
"assumenosideeffects1",
"assumenosideeffects2",
"assumenosideeffects3",
@@ -720,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..f070130 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -385,8 +385,7 @@
protected DexApplication processApplication(DexApplication application, InternalOptions options) {
try {
- R8 r8 = new R8(options);
- return r8.optimize(application, new AppInfoWithSubtyping(application));
+ return ToolHelper.optimizeWithR8(application, new AppInfoWithSubtyping(application), options);
} catch (IOException | ProguardRuleParserException | ExecutionException e) {
throw new RuntimeException(e);
}
@@ -500,6 +499,7 @@
Executors.newSingleThreadExecutor(),
application,
appInfo,
+ null,
NamingLens.getIdentityLens(),
null,
null,
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index e49ea84..f10add0 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.R8;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.code.Const;
import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.ConstHigh16;
@@ -284,7 +285,7 @@
" return");
DexApplication app = builder.read();
- app = new R8(new InternalOptions()).optimize(app, new AppInfoWithSubtyping(app));
+ app = ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), new InternalOptions());
MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
DexEncodedMethod method = getMethod(app, signature);
@@ -344,7 +345,7 @@
" return");
DexApplication app = builder.read();
- app = new R8(new InternalOptions()).optimize(app, new AppInfoWithSubtyping(app));
+ app = ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), new InternalOptions());
MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
DexEncodedMethod method = getMethod(app, signature);
@@ -417,7 +418,7 @@
" return");
DexApplication app = builder.read();
- app = new R8(new InternalOptions()).optimize(app, new AppInfoWithSubtyping(app));
+ app = ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), new InternalOptions());
MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
DexEncodedMethod method = getMethod(app, signature);
diff --git a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
index 5bae2ed..6f45137 100644
--- a/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/D8CommandTest.java
@@ -172,11 +172,11 @@
tmpClassesDir.toString());
AndroidApp inputApp = ToolHelper.getApp(command);
assertEquals(1, inputApp.getClasspathResourceProviders().size());
- assertEquals(tmpClassesDir,
- ((DirectoryClassFileProvider) inputApp.getClasspathResourceProviders().get(0)).getRoot());
+ assertTrue(Files.isSameFile(tmpClassesDir,
+ ((DirectoryClassFileProvider) inputApp.getClasspathResourceProviders().get(0)).getRoot()));
assertEquals(1, inputApp.getLibraryResourceProviders().size());
- assertEquals(tmpClassesDir,
- ((DirectoryClassFileProvider) inputApp.getLibraryResourceProviders().get(0)).getRoot());
+ assertTrue(Files.isSameFile(tmpClassesDir,
+ ((DirectoryClassFileProvider) inputApp.getLibraryResourceProviders().get(0)).getRoot()));
}
@Test
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index a207ed8..1dabbd3 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -100,6 +100,7 @@
return os.path.abspath(f.name)
def main(argv):
+ utils.check_java_version()
app_provided_pg_conf = False;
(options, args) = ParseOptions(argv)
outdir = options.out
diff --git a/tools/run_proguard_dx_on_app.py b/tools/run_proguard_dx_on_app.py
index 8578d1e..92a75ba 100755
--- a/tools/run_proguard_dx_on_app.py
+++ b/tools/run_proguard_dx_on_app.py
@@ -56,6 +56,7 @@
return parser.parse_args(argv)
def Main(argv):
+ utils.check_java_version()
options = parse_arguments(argv)
outdir = options.out
diff --git a/tools/test_framework.py b/tools/test_framework.py
index 04a12d3..b328375 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -59,6 +59,7 @@
return parser.parse_args()
def Main():
+ utils.check_java_version()
args = parse_arguments()
with utils.TempDir() as temp_dir:
diff --git a/tools/utils.py b/tools/utils.py
index dfc35d7..dab1dd1 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -170,3 +170,18 @@
for segment_name, size in getDexSegmentSizes(dex_files).items():
print('{}-{}(CodeSize): {}'
.format(prefix, segment_name, size))
+
+# ensure that java version is 1.8.*-internal,
+# as opposed to e.g. 1.7* or 1.8.*-google-v7
+def check_java_version():
+ cmd= ['java', '-version']
+ output = subprocess.check_output(cmd, stderr = subprocess.STDOUT)
+ m = re.search('openjdk version "([^"]*)"', output)
+ if m is None:
+ raise Exception("Can't check java version: no version string in output"
+ " of 'java -version': '{}'".format(output))
+ version = m.groups(0)[0]
+ m = re.search('1[.]8[.].*-internal', version)
+ if m is None:
+ raise Exception("Incorrect java version, expected: '1.8.*-internal',"
+ " actual: {}".format(version))