Merge "Implement package name obfuscation (w/ access modification)."
diff --git a/build.gradle b/build.gradle
index e2c21b6..a00c516 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,11 +6,15 @@
apply plugin: 'java'
apply plugin: 'idea'
-apply plugin: 'jacoco'
apply plugin: 'com.google.protobuf'
apply from: 'copyAdditionalJctfCommonFiles.gradle'
+
+if (project.hasProperty('with_code_coverage')) {
+ apply plugin: 'jacoco'
+}
+
repositories {
maven { url 'https://maven.google.com' }
mavenCentral()
@@ -918,6 +922,9 @@
println "JCTF: compiling only"
systemProperty 'jctf_compile_only', '1'
}
+ if (project.hasProperty('test_dir')) {
+ systemProperty 'test_dir', project.property('test_dir')
+ }
if (OperatingSystem.current().isLinux()
|| OperatingSystem.current().isMacOsX()
diff --git a/src/main/java/com/android/tools/r8/BSPatch.java b/src/main/java/com/android/tools/r8/BSPatch.java
index eda5f23..8c1d208 100644
--- a/src/main/java/com/android/tools/r8/BSPatch.java
+++ b/src/main/java/com/android/tools/r8/BSPatch.java
@@ -202,7 +202,6 @@
@Override
public void copyOld(int blockSize) throws IOException {
- assert mergeBuffer != null;
assert mergeBuffer.length == blockSize;
byte[] data = new byte[blockSize];
oldInput.get(data);
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 430624b..debe8a1 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -95,6 +95,9 @@
private CompilationMode mode;
private int minApiLevel = Constants.DEFAULT_ANDROID_API;
+ // Internal flag used by CompatDx to ignore dex files in archives.
+ protected boolean ignoreDexInArchive = false;
+
protected Builder(CompilationMode mode) {
this(AndroidApp.builder(), mode);
}
@@ -108,6 +111,7 @@
assert mode != null;
this.app = builder;
this.mode = mode;
+ app.setIgnoreDexInArchive(ignoreDexInArchive);
}
abstract B self();
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 4c8cdd5..ed992a3 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -55,7 +55,7 @@
*/
public final class D8 {
- private static final String kVersion = "v0.0.1";
+ private static final String VERSION = "v0.1.0";
private static final int STATUS_ERROR = 1;
private D8() {}
@@ -110,7 +110,7 @@
return;
}
if (command.isPrintVersion()) {
- System.out.println("D8 " + kVersion);
+ System.out.println("D8 " + VERSION);
return;
}
run(command);
@@ -161,7 +161,7 @@
return options.getMarker();
}
return new Marker(Tool.D8)
- .put("version", kVersion)
+ .put("version", VERSION)
.put("min-api", options.minApiLevel);
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 64a1a0e..cb8c170 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -36,7 +36,7 @@
private boolean intermediate = false;
- private Builder() {
+ protected Builder() {
super(CompilationMode.DEBUG);
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index acbbe0a..da90e82 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,7 +71,7 @@
public class R8 {
- private static final String kVersion = "v0.0.1";
+ private static final String VERSION = "v0.1.0";
private final Timing timing = new Timing("R8");
private final InternalOptions options;
@@ -86,7 +86,7 @@
return options.getMarker();
}
return new Marker(Tool.R8)
- .put("version", kVersion)
+ .put("version", VERSION)
.put("min-api", options.minApiLevel);
}
@@ -434,7 +434,7 @@
options.proguardConfiguration.getPrintMappingFile(),
System.out,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
- outputApp.writeProguardMap(closer, mapOut);
+ outputApp.writeProguardMap(mapOut);
}
}
if (options.proguardConfiguration.isPrintSeeds()) {
@@ -502,7 +502,7 @@
return;
}
if (command.isPrintVersion()) {
- System.out.println("R8 " + kVersion);
+ System.out.println("R8 " + VERSION);
return;
}
run(command);
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 928b28f..ec040a5 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -34,17 +34,14 @@
private Optional<Boolean> treeShaking = Optional.empty();
private Optional<Boolean> minification = Optional.empty();
private boolean ignoreMissingClasses = false;
+ private Path packageDistributionFile = null;
private Builder() {
super(CompilationMode.RELEASE);
- // TODO(b/62048823): Minifier should not depend on -allowaccessmodification.
- proguardConfigurationConsumer = builder -> builder.setAllowAccessModification(true);
}
private Builder(AndroidApp app) {
super(app, CompilationMode.RELEASE);
- // TODO(b/62048823): Minifier should not depend on -allowaccessmodification.
- proguardConfigurationConsumer = builder -> builder.setAllowAccessModification(true);
}
@Override
@@ -131,7 +128,7 @@
* Set a package distribution file resource.
*/
public Builder setPackageDistributionFile(Path path) {
- getAppBuilder().setPackageDistributionFile(path);
+ packageDistributionFile = path;
return self();
}
@@ -151,6 +148,10 @@
throw new CompilationException(
"Option --main-dex-list-output require --main-dex-rules and/or --main-dex-list");
}
+ if (getMode() == CompilationMode.DEBUG && packageDistributionFile != null) {
+ throw new CompilationException(
+ "Package distribution file is not supported in debug mode");
+ }
}
@Override
@@ -193,6 +194,10 @@
addLibraryFiles(configuration.getLibraryjars());
}
+ if (packageDistributionFile != null) {
+ getAppBuilder().setPackageDistributionFile(packageDistributionFile);
+ }
+
boolean useTreeShaking = treeShaking.orElse(configuration.isShrinking());
boolean useMinification = minification.orElse(configuration.isObfuscating());
diff --git a/src/main/java/com/android/tools/r8/code/Const.java b/src/main/java/com/android/tools/r8/code/Const.java
index 301f982..2f63777 100644
--- a/src/main/java/com/android/tools/r8/code/Const.java
+++ b/src/main/java/com/android/tools/r8/code/Const.java
@@ -41,12 +41,12 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 8) +
+ return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) +
" (" + decodedValue() + ")");
}
public String toSmaliString(ClassNameMapper naming) {
- return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 8) +
+ return formatSmaliString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) +
" # " + decodedValue());
}
diff --git a/src/main/java/com/android/tools/r8/code/Const16.java b/src/main/java/com/android/tools/r8/code/Const16.java
index aa00341..baf2ca7 100644
--- a/src/main/java/com/android/tools/r8/code/Const16.java
+++ b/src/main/java/com/android/tools/r8/code/Const16.java
@@ -42,7 +42,7 @@
@Override
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 4) +
+ return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 4) +
" (" + decodedValue() + ")");
}
diff --git a/src/main/java/com/android/tools/r8/code/Const4.java b/src/main/java/com/android/tools/r8/code/Const4.java
index 26da02a..b907e78 100644
--- a/src/main/java/com/android/tools/r8/code/Const4.java
+++ b/src/main/java/com/android/tools/r8/code/Const4.java
@@ -41,12 +41,12 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + A + ", 0x" + StringUtils.hexString(decodedValue(), 1) +
+ return formatString("v" + A + ", " + StringUtils.hexString(decodedValue(), 1) +
" (" + decodedValue() + ")");
}
public String toSmaliString(ClassNameMapper naming) {
- return formatSmaliString("v" + A + ", 0x" + StringUtils.hexString(decodedValue(), 2) +
+ return formatSmaliString("v" + A + ", " + StringUtils.hexString(decodedValue(), 2) +
" # " + decodedValue());
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstHigh16.java b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
index 3245fa9..d3a4264 100644
--- a/src/main/java/com/android/tools/r8/code/ConstHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
@@ -41,12 +41,12 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 8) +
+ return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) +
" (" + decodedValue() + ")");
}
public String toSmaliString(ClassNameMapper naming) {
- return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 8) +
+ return formatSmaliString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 8) +
" # " + decodedValue());
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide.java b/src/main/java/com/android/tools/r8/code/ConstWide.java
index 7323f9b..002a377 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide.java
@@ -41,12 +41,12 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+ return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
" (" + decodedValue() + ")");
}
public String toSmaliString(ClassNameMapper naming) {
- return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+ return formatSmaliString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
"L # " + decodedValue());
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide16.java b/src/main/java/com/android/tools/r8/code/ConstWide16.java
index edd93fc..b593491 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide16.java
@@ -41,13 +41,13 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+ return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
" (" + decodedValue() + ")");
}
public String toSmaliString(ClassNameMapper naming) {
return formatSmaliString(
- "v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) + "L # " + decodedValue());
+ "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + "L # " + decodedValue());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide32.java b/src/main/java/com/android/tools/r8/code/ConstWide32.java
index 540e840..5c4fc7b 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide32.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide32.java
@@ -41,13 +41,13 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+ return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
" (" + decodedValue() + ")");
}
public String toSmaliString(ClassNameMapper naming) {
return formatSmaliString(
- "v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) + " # " + decodedValue());
+ "v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) + " # " + decodedValue());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
index 864a079..182b39a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
@@ -40,12 +40,12 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+ return formatString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
" (" + decodedValue() + ")");
}
public String toSmaliString(ClassNameMapper naming) {
- return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(decodedValue(), 16) +
+ return formatSmaliString("v" + AA + ", " + StringUtils.hexString(decodedValue(), 16) +
"L # " + decodedValue());
}
diff --git a/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java b/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
index 1f69d9c..885a8af 100644
--- a/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
+++ b/src/main/java/com/android/tools/r8/code/FillArrayDataPayload.java
@@ -80,8 +80,7 @@
public String toSmaliString(ClassNameMapper naming) {
StringBuilder builder = new StringBuilder();
builder.append(" ");
- builder.append(".array-data");
- builder.append(" 0x");
+ builder.append(".array-data ");
builder.append(StringUtils.hexString(element_width, 1));
builder.append(" # ");
builder.append(element_width);
@@ -93,7 +92,6 @@
int value = (data[i] >> j * 8) & 0xff;
if (i * 2 + j < size) {
builder.append(" ");
- builder.append("0x");
builder.append(StringUtils.hexString(value, 2));
builder.append(" # ");
builder.append(value);
@@ -109,7 +107,6 @@
value = (Short.toUnsignedLong(data[i]) << (16 * (i % (element_width / 2)))) | value;
if ((((i + 1) * 2) % element_width) == 0) {
builder.append(" ");
- builder.append("0x");
builder.append(StringUtils.hexString(value, element_width * 2));
builder.append(" # ");
builder.append(value);
diff --git a/src/main/java/com/android/tools/r8/code/Format10t.java b/src/main/java/com/android/tools/r8/code/Format10t.java
index e5d2952..8d65d2b 100644
--- a/src/main/java/com/android/tools/r8/code/Format10t.java
+++ b/src/main/java/com/android/tools/r8/code/Format10t.java
@@ -40,8 +40,7 @@
}
public String toString(ClassNameMapper naming) {
- String relative = AA >= 0 ? ("+" + AA) : Integer.toString(AA);
- return formatString(relative + " (" + (getOffset() + AA) + ")");
+ return formatString(formatRelativeOffset(AA));
}
public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/Format20t.java b/src/main/java/com/android/tools/r8/code/Format20t.java
index b3e3627..75721a3 100644
--- a/src/main/java/com/android/tools/r8/code/Format20t.java
+++ b/src/main/java/com/android/tools/r8/code/Format20t.java
@@ -40,7 +40,7 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("" + AAAA + " (" + (getOffset() + AAAA) + ")");
+ return formatString("" + AAAA + " " + formatRelativeOffset(AAAA));
}
public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/Format21s.java b/src/main/java/com/android/tools/r8/code/Format21s.java
index db148bd..cc5d6c0 100644
--- a/src/main/java/com/android/tools/r8/code/Format21s.java
+++ b/src/main/java/com/android/tools/r8/code/Format21s.java
@@ -51,7 +51,7 @@
}
public String toSmaliString(ClassNameMapper naming) {
- return formatSmaliString("v" + AA + ", 0x" + StringUtils.hexString(BBBB, 4) + " # " + BBBB);
+ return formatSmaliString("v" + AA + ", " + StringUtils.hexString(BBBB, 4) + " # " + BBBB);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/code/Format21t.java b/src/main/java/com/android/tools/r8/code/Format21t.java
index 26f7650..6e91554 100644
--- a/src/main/java/com/android/tools/r8/code/Format21t.java
+++ b/src/main/java/com/android/tools/r8/code/Format21t.java
@@ -62,7 +62,7 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", +" + BBBB + " (" + (getOffset() + BBBB) + ")");
+ return formatString("v" + AA + ", " + formatRelativeOffset(BBBB));
}
public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/Format22b.java b/src/main/java/com/android/tools/r8/code/Format22b.java
index 8aeae42..1164b03 100644
--- a/src/main/java/com/android/tools/r8/code/Format22b.java
+++ b/src/main/java/com/android/tools/r8/code/Format22b.java
@@ -56,7 +56,7 @@
public String toSmaliString(ClassNameMapper naming) {
return formatSmaliString(
- "v" + AA + ", v" + BB + ", 0x" + StringUtils.hexString(CC, 2) + " # " + CC);
+ "v" + AA + ", v" + BB + ", " + StringUtils.hexString(CC, 2) + " # " + CC);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/code/Format22s.java b/src/main/java/com/android/tools/r8/code/Format22s.java
index eef5fa5..85fbeab 100644
--- a/src/main/java/com/android/tools/r8/code/Format22s.java
+++ b/src/main/java/com/android/tools/r8/code/Format22s.java
@@ -56,7 +56,7 @@
public String toSmaliString(ClassNameMapper naming) {
return formatSmaliString(
- "v" + A + ", v" + B + ", 0x" + StringUtils.hexString(CCCC, 4) + " # " + CCCC);
+ "v" + A + ", v" + B + ", " + StringUtils.hexString(CCCC, 4) + " # " + CCCC);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/code/Format22t.java b/src/main/java/com/android/tools/r8/code/Format22t.java
index 3ff5190..0a4d664 100644
--- a/src/main/java/com/android/tools/r8/code/Format22t.java
+++ b/src/main/java/com/android/tools/r8/code/Format22t.java
@@ -66,7 +66,7 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + A + ", v" + B + ", +" + CCCC + " (" + (getOffset() + CCCC) + ")");
+ return formatString("v" + A + ", v" + B + ", " + formatRelativeOffset(CCCC));
}
public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/Format30t.java b/src/main/java/com/android/tools/r8/code/Format30t.java
index 77cb49d..098863c 100644
--- a/src/main/java/com/android/tools/r8/code/Format30t.java
+++ b/src/main/java/com/android/tools/r8/code/Format30t.java
@@ -39,7 +39,7 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("" + AAAAAAAA);
+ return formatString(formatOffset(AAAAAAAA));
}
public String toSmaliString(ClassNameMapper naming) {
diff --git a/src/main/java/com/android/tools/r8/code/Format31t.java b/src/main/java/com/android/tools/r8/code/Format31t.java
index ec41ed4..87106f0 100644
--- a/src/main/java/com/android/tools/r8/code/Format31t.java
+++ b/src/main/java/com/android/tools/r8/code/Format31t.java
@@ -58,7 +58,7 @@
}
public String toString(ClassNameMapper naming) {
- return formatString("v" + AA + ", +" + BBBBBBBB + " (" + (getOffset() + BBBBBBBB) + ")");
+ return formatString("v" + AA + ", " + formatRelativeOffset(BBBBBBBB));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 6b61845..617642c 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -142,9 +142,18 @@
return 0;
}
+ static String formatOffset(int offset) {
+ return StringUtils.hexString(offset, 2);
+ }
+
+ String formatRelativeOffset(int offset) {
+ String relativeString = offset >= 0 ? ("+" + offset) : Integer.toString(offset);
+ return formatOffset(getOffset() + offset) + " (" + relativeString + ")";
+ }
+
String formatString(String left) {
StringBuilder builder = new StringBuilder();
- StringUtils.appendLeftPadded(builder, Integer.toString(getOffset()), 6);
+ StringUtils.appendLeftPadded(builder, formatOffset(getOffset()), 6);
builder.append(": ");
StringUtils.appendRightPadded(builder, getName(), 20);
builder.append(left == null ? "" : left);
diff --git a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
index a3e2d15..5032ab0 100644
--- a/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/PackedSwitchPayload.java
@@ -94,7 +94,6 @@
StringBuilder builder = new StringBuilder();
builder.append(" ");
builder.append(".packed-switch ");
- builder.append("0x");
builder.append(StringUtils.hexString(first_key, 8));
builder.append(" # ");
builder.append(first_key);
diff --git a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
index 328e6d2..e0e1280 100644
--- a/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
+++ b/src/main/java/com/android/tools/r8/code/SparseSwitchPayload.java
@@ -103,7 +103,6 @@
builder.append("\n");
for (int i = 0; i < keys.length; i++) {
builder.append(" ");
- builder.append("0x");
builder.append(StringUtils.hexString(keys[i], 8));
builder.append(" -> :label_");
builder.append(payloadUser.getOffset() + targets[i]);
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 6750f4c..9bcf510 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -450,7 +450,7 @@
ExecutorService executor = ThreadUtils.getExecutorService(numberOfThreads);
D8Output result;
try {
- D8Command.Builder builder = D8Command.builder();
+ D8Command.Builder builder = new CompatDxCommandBuilder();
builder
.addProgramFiles(inputs)
.setMode(mode)
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
new file mode 100644
index 0000000..ba9daec
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDxCommandBuilder.java
@@ -0,0 +1,13 @@
+// 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.compatdx;
+
+import com.android.tools.r8.D8Command;
+
+public class CompatDxCommandBuilder extends D8Command.Builder {
+ CompatDxCommandBuilder() {
+ ignoreDexInArchive = true;
+ }
+}
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 2553650..cca3520 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -75,7 +75,7 @@
throws IOException, ExecutionException {
timing.begin("DexApplication.read");
final DexApplication.Builder builder = new DexApplication.Builder(itemFactory, timing);
- try (Closer closer = Closer.create()) {
+ try {
List<Future<?>> futures = new ArrayList<>();
// Still preload some of the classes, primarily for two reasons:
// (a) class lazy loading is not supported for DEX files
@@ -84,9 +84,9 @@
// (b) some of the class file resources don't provide information
// about class descriptor.
// TODO: try and preload less classes.
- readProguardMap(builder, executorService, futures, closer);
- readMainDexList(builder, executorService, futures, closer);
- ClassReader classReader = new ClassReader(executorService, futures, closer);
+ readProguardMap(builder, executorService, futures);
+ readMainDexList(builder, executorService, futures);
+ ClassReader classReader = new ClassReader(executorService, futures);
classReader.readSources();
ThreadUtils.awaitFutures(futures);
classReader.initializeLazyClassCollection(builder);
@@ -130,13 +130,12 @@
}
private void readProguardMap(DexApplication.Builder builder, ExecutorService executorService,
- List<Future<?>> futures, Closer closer)
+ List<Future<?>> futures)
throws IOException {
// Read the Proguard mapping file in parallel with DexCode and DexProgramClass items.
if (inputApp.hasProguardMap()) {
futures.add(executorService.submit(() -> {
- try {
- InputStream map = inputApp.getProguardMap(closer);
+ try (InputStream map = inputApp.getProguardMap()) {
builder.setProguardMap(ProguardMapReader.mapperFromInputStream(map));
} catch (IOException e) {
throw new RuntimeException(e);
@@ -146,18 +145,18 @@
}
private void readMainDexList(DexApplication.Builder builder, ExecutorService executorService,
- List<Future<?>> futures, Closer closer)
+ List<Future<?>> futures)
throws IOException {
if (inputApp.hasMainDexList()) {
futures.add(executorService.submit(() -> {
- try {
- for (Resource resource : inputApp.getMainDexListResources()) {
- InputStream input = closer.register(resource.getStream());
+ for (Resource resource : inputApp.getMainDexListResources()) {
+ try (InputStream input = resource.getStream()) {
builder.addToMainDexList(MainDexList.parse(input, itemFactory));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
- } catch (IOException e) {
- throw new RuntimeException(e);
}
+
builder.addToMainDexList(
inputApp.getMainDexClasses()
.stream()
@@ -170,7 +169,6 @@
private final class ClassReader {
private final ExecutorService executorService;
private final List<Future<?>> futures;
- private final Closer closer;
// We use concurrent queues to collect classes
// since the classes can be collected concurrently.
@@ -180,10 +178,9 @@
// Jar application reader to share across all class readers.
private final JarApplicationReader application = new JarApplicationReader(options);
- ClassReader(ExecutorService executorService, List<Future<?>> futures, Closer closer) {
+ ClassReader(ExecutorService executorService, List<Future<?>> futures) {
this.executorService = executorService;
this.futures = futures;
- this.closer = closer;
}
private <T extends DexClass> void readDexSources(List<Resource> dexSources,
@@ -192,9 +189,11 @@
List<DexFileReader> fileReaders = new ArrayList<>(dexSources.size());
int computedMinApiLevel = options.minApiLevel;
for (Resource input : dexSources) {
- DexFile file = new DexFile(closer.register(input.getStream()));
- computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
- fileReaders.add(new DexFileReader(file, classKind, itemFactory));
+ try (InputStream is = input.getStream()) {
+ DexFile file = new DexFile(is);
+ computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
+ fileReaders.add(new DexFileReader(file, classKind, itemFactory));
+ }
}
options.minApiLevel = computedMinApiLevel;
for (DexFileReader reader : fileReaders) {
@@ -215,8 +214,16 @@
ClassKind classKind, Queue<T> classes) throws IOException, ExecutionException {
JarClassFileReader reader = new JarClassFileReader(
application, classKind.bridgeConsumer(classes::add));
+ // Read classes in parallel.
for (Resource input : classSources) {
- reader.read(DEFAULT_DEX_FILENAME, classKind, closer.register(input.getStream()));
+ futures.add(executorService.submit(() -> {
+ try (InputStream is = input.getStream()) {
+ reader.read(DEFAULT_DEX_FILENAME, classKind, is);
+ }
+ // No other way to have a void callable, but we want the IOException from the previous
+ // line to be wrapped into an ExecutionException.
+ return null;
+ }));
}
}
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 a8f56a7..f1162a9 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -253,10 +253,10 @@
private void sortClassData(Collection<DexProgramClass> classesWithData) {
for (DexProgramClass clazz : classesWithData) {
- sortEncodedFields(clazz.instanceFields);
- sortEncodedFields(clazz.staticFields);
- sortEncodedMethods(clazz.directMethods);
- sortEncodedMethods(clazz.virtualMethods);
+ sortEncodedFields(clazz.instanceFields());
+ sortEncodedFields(clazz.staticFields());
+ sortEncodedMethods(clazz.directMethods());
+ sortEncodedMethods(clazz.virtualMethods());
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/CanonicalizedDexItem.java b/src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/graph/CanonicalizedDexItem.java
rename to src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java
index 7872801..8f110d5 100644
--- a/src/main/java/com/android/tools/r8/graph/CanonicalizedDexItem.java
+++ b/src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.graph;
/**
- * DexItems of this kind have to be canonicalized for the whole application.
+ * DexItems of this kind have cached hash values and quick equals check.
*/
-public abstract class CanonicalizedDexItem extends DexItem {
+public abstract class CachedHashValueDexItem extends DexItem {
private static final int NOT_COMPUTED_HASH_VALUE = -1;
private static final int SENTINEL_HASH_VALUE = 0;
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 54f9470..3845507 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.InternalOptions;
-public abstract class Code extends CanonicalizedDexItem {
+public abstract class Code extends CachedHashValueDexItem {
public abstract IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options);
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index 4277324..db55cd6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -17,9 +17,11 @@
private final List<DexEncodedMethod> methodAnnotations;
private final List<DexEncodedMethod> parameterAnnotations;
private final List<DexEncodedField> fieldAnnotations;
+ private final boolean classHasOnlyInternalizableAnnotations;
public DexAnnotationDirectory(DexProgramClass clazz) {
this.clazz = clazz;
+ this.classHasOnlyInternalizableAnnotations = clazz.hasOnlyInternalizableAnnotations();
assert isSorted(clazz.directMethods());
assert isSorted(clazz.virtualMethods());
OrderedMergingIterator<DexEncodedMethod, DexMethod> methods =
@@ -77,7 +79,7 @@
if (!(obj instanceof DexAnnotationDirectory)) {
return false;
}
- if (clazz.hasOnlyInternalizableAnnotations()) {
+ if (classHasOnlyInternalizableAnnotations) {
DexAnnotationDirectory other = (DexAnnotationDirectory) obj;
if (!other.clazz.hasOnlyInternalizableAnnotations()) {
return false;
@@ -89,7 +91,7 @@
@Override
public final int hashCode() {
- if (clazz.hasOnlyInternalizableAnnotations()) {
+ if (classHasOnlyInternalizableAnnotations) {
return clazz.annotations.hashCode();
}
return super.hashCode();
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index e91e1f0..00b99fa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import java.util.Arrays;
-public class DexAnnotationSet extends DexItem {
+public class DexAnnotationSet extends CachedHashValueDexItem {
private static final int UNSORTED = 0;
private static final DexAnnotationSet THE_EMPTY_ANNOTATIONS_SET =
@@ -25,15 +25,12 @@
}
@Override
- public int hashCode() {
+ public int computeHashCode() {
return Arrays.hashCode(annotations);
}
@Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
+ public boolean computeEquals(Object other) {
if (other instanceof DexAnnotationSet) {
DexAnnotationSet o = (DexAnnotationSet) other;
return Arrays.equals(annotations, o.annotations);
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 d09d395..3f492b5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -22,10 +22,10 @@
public DexType superType;
public DexTypeList interfaces;
public final DexString sourceFile;
- public DexEncodedField[] staticFields;
- public DexEncodedField[] instanceFields;
- public DexEncodedMethod[] directMethods;
- public DexEncodedMethod[] virtualMethods;
+ protected DexEncodedField[] staticFields;
+ protected DexEncodedField[] instanceFields;
+ protected DexEncodedMethod[] directMethods;
+ protected DexEncodedMethod[] virtualMethods;
public DexAnnotationSet annotations;
public DexClass(
@@ -39,10 +39,10 @@
this.accessFlags = accessFlags;
this.superType = superType;
this.type = type;
- this.staticFields = staticFields;
- this.instanceFields = instanceFields;
- this.directMethods = directMethods;
- this.virtualMethods = virtualMethods;
+ setStaticFields(staticFields);
+ setInstanceFields(instanceFields);
+ setDirectMethods(directMethods);
+ setVirtualMethods(virtualMethods);
this.annotations = annotations;
if (type == superType) {
throw new CompilationError("Class " + type.toString() + " cannot extend itself");
@@ -66,13 +66,22 @@
}
public DexEncodedMethod[] directMethods() {
- return MoreObjects.firstNonNull(directMethods, NO_METHODS);
+ return directMethods;
+ }
+
+ public void setDirectMethods(DexEncodedMethod[] values) {
+ directMethods = MoreObjects.firstNonNull(values, NO_METHODS);
}
public DexEncodedMethod[] virtualMethods() {
- return MoreObjects.firstNonNull(virtualMethods, NO_METHODS);
+ return virtualMethods;
}
+ public void setVirtualMethods(DexEncodedMethod[] values) {
+ virtualMethods = MoreObjects.firstNonNull(values, NO_METHODS);
+ }
+
+
public void forEachMethod(Consumer<DexEncodedMethod> consumer) {
for (DexEncodedMethod method : directMethods()) {
consumer.accept(method);
@@ -103,11 +112,19 @@
}
public DexEncodedField[] staticFields() {
- return MoreObjects.firstNonNull(staticFields, NO_FIELDS);
+ return staticFields;
+ }
+
+ public void setStaticFields(DexEncodedField[] values) {
+ staticFields = MoreObjects.firstNonNull(values, NO_FIELDS);
}
public DexEncodedField[] instanceFields() {
- return MoreObjects.firstNonNull(instanceFields, NO_FIELDS);
+ return instanceFields;
+ }
+
+ public void setInstanceFields(DexEncodedField[] values) {
+ instanceFields = MoreObjects.firstNonNull(values, NO_FIELDS);
}
/**
@@ -187,6 +204,14 @@
return getClassInitializer() != null;
}
+ public boolean hasTrivialClassInitializer() {
+ DexEncodedMethod clinit = getClassInitializer();
+ return clinit != null
+ && clinit.getCode() != null
+ && clinit.getCode().asDexCode().isEmptyVoidMethod();
+ }
+
+
public boolean hasNonTrivialClassInitializer() {
DexEncodedMethod clinit = getClassInitializer();
if (clinit == null || clinit.getCode() == null) {
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 7915df3..8bd13f0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -193,7 +193,7 @@
int instructionNumber = 0;
for (Instruction insn : instructions) {
while (debugInfo != null && debugInfo.address == insn.getOffset()) {
- builder.append(" ").append(debugInfo).append("\n");
+ builder.append(" ").append(debugInfo.toString(false)).append("\n");
debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null;
}
StringUtils.appendLeftPadded(builder, Integer.toString(instructionNumber++), 5);
@@ -353,9 +353,9 @@
public String toString() {
return "["
- + startAddress
+ + StringUtils.hexString(startAddress, 2)
+ " .. "
- + (startAddress + instructionCount - 1)
+ + StringUtils.hexString(startAddress + instructionCount - 1, 2)
+ "] -> "
+ handlerIndex;
}
@@ -420,12 +420,12 @@
builder.append(" ");
builder.append(pair.type);
builder.append(" -> ");
- builder.append(pair.addr);
+ builder.append(StringUtils.hexString(pair.addr, 2));
builder.append("\n");
}
if (catchAllAddr != NO_HANDLER) {
builder.append(" default -> ");
- builder.append(catchAllAddr);
+ builder.append(StringUtils.hexString(catchAllAddr, 2));
builder.append("\n");
}
builder.append(" ]");
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index de7b544..f083794 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -33,8 +33,15 @@
@Override
public String toString() {
+ return toString(true);
+ }
+
+ public String toString(boolean withPcPrefix) {
StringBuilder builder = new StringBuilder();
- builder.append("pc 0x").append(StringUtils.hexString(address, 2));
+ if (withPcPrefix) {
+ builder.append("pc ");
+ }
+ builder.append(StringUtils.hexString(address, 2));
builder.append(", line ").append(line);
if (sourceFile != null) {
builder.append(", file ").append(sourceFile);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index f82fe2a..d2c9b3b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -8,7 +8,7 @@
import java.util.Arrays;
import java.util.List;
-public class DexDebugInfo extends CanonicalizedDexItem {
+public class DexDebugInfo extends CachedHashValueDexItem {
public final int startLine;
public final DexString[] parameters;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index c94fc3c..1ba7755 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -15,7 +15,7 @@
public final DexField field;
public final DexAccessFlags accessFlags;
public DexAnnotationSet annotations;
- public final DexValue staticValue;
+ public DexValue staticValue;
public DexEncodedField(DexField field, DexAccessFlags accessFlags, DexAnnotationSet annotations,
DexValue staticValue) {
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 339f4c0..80dea00 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -24,17 +24,19 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
public class DexItemFactory {
- private final Map<DexString, DexString> strings = new HashMap<>();
- private final Map<DexType, DexType> types = new HashMap<>();
- private final Map<DexField, DexField> fields = new HashMap<>();
- private final Map<DexProto, DexProto> protos = new HashMap<>();
- private final Map<DexMethod, DexMethod> methods = new HashMap<>();
- private final Map<DexCallSite, DexCallSite> callSites = new HashMap<>();
- private final Map<DexMethodHandle, DexMethodHandle> methodHandles = new HashMap<>();
+ private final ConcurrentHashMap<DexString, DexString> strings = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexString, DexType> types = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexField, DexField> fields = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexProto, DexProto> protos = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexMethod, DexMethod> methods = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexCallSite, DexCallSite> callSites = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexMethodHandle, DexMethodHandle> methodHandles =
+ new ConcurrentHashMap<>();
// DexDebugEvent Canonicalization.
private final Int2ObjectMap<AdvanceLine> advanceLines = new Int2ObjectOpenHashMap<>();
@@ -85,6 +87,8 @@
public final DexString getClassMethodName = createString("getClass");
public final DexString ordinalMethodName = createString("ordinal");
public final DexString desiredAssertionStatusMethodName = createString("desiredAssertionStatus");
+ public final DexString getNameName = createString("getName");
+ public final DexString getSimpleNameName = createString("getSimpleName");
public final DexString assertionsDisabled = createString("$assertionsDisabled");
public final DexString stringDescriptor = createString("Ljava/lang/String;");
@@ -208,10 +212,15 @@
public class ClassMethods {
public DexMethod desiredAssertionStatus;
+ public DexMethod getName;
+ public DexMethod getSimpleName;
private ClassMethods() {
desiredAssertionStatus = createMethod(classDescriptor,
desiredAssertionStatusMethodName, booleanDescriptor, DexString.EMPTY_ARRAY);
+ getName = createMethod(classDescriptor, getNameName, stringDescriptor, DexString.EMPTY_ARRAY);
+ getSimpleName = createMethod(classDescriptor,
+ getSimpleNameName, stringDescriptor, DexString.EMPTY_ARRAY);
}
}
@@ -273,7 +282,7 @@
}
}
- synchronized private static <T extends DexItem> T canonicalize(Map<T, T> map, T item) {
+ private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
assert item != null;
assert !internalSentinels.contains(item);
T previous = map.putIfAbsent(item, item);
@@ -302,10 +311,16 @@
return null;
}
- public DexType createType(DexString descriptor) {
+ synchronized public DexType createType(DexString descriptor) {
assert !sorted;
- DexType type = new DexType(descriptor);
- return canonicalize(types, type);
+ assert descriptor != null;
+ DexType result = types.get(descriptor);
+ if (result == null) {
+ result = new DexType(descriptor);
+ assert !internalSentinels.contains(result);
+ types.put(descriptor, result);
+ }
+ return result;
}
public DexType createType(String descriptor) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 60a7319..1bdc56c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -152,6 +152,18 @@
directMethods[directMethods.length - 1] = staticMethod;
}
+ public void removeStaticMethod(DexEncodedMethod staticMethod) {
+ assert staticMethod.accessFlags.isStatic();
+ DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[directMethods.length - 1];
+ int toIndex = 0;
+ for (int fromIndex = 0; fromIndex < directMethods.length; fromIndex++) {
+ if (directMethods[fromIndex] != staticMethod) {
+ newDirectMethods[toIndex++] = directMethods[fromIndex];
+ }
+ }
+ directMethods = newDirectMethods;
+ }
+
@Override
public DexProgramClass get() {
return this;
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 d3d36e5..5c8f439 100644
--- a/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
+++ b/src/main/java/com/android/tools/r8/graph/IndexedDexItem.java
@@ -10,7 +10,7 @@
/**
* Subset of dex items that are referenced by some table index.
*/
-public abstract class IndexedDexItem extends CanonicalizedDexItem implements Presorted {
+public abstract class IndexedDexItem extends CachedHashValueDexItem implements Presorted {
private static final int SORTED_INDEX_UNKNOWN = -1;
private int sortedIndex = SORTED_INDEX_UNKNOWN; // assigned globally after reading.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index d53c577..1880668 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -48,6 +48,10 @@
return outValue;
}
+ public boolean getBooleanValue() {
+ return !isZero();
+ }
+
public int getIntValue() {
assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT;
return (int) value;
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 9f28eea..c8a17dc2 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
@@ -110,6 +110,39 @@
};
}
+ /**
+ * Returns an iterator over all dominator blocks of <code>dominated</code>.
+ *
+ * Iteration order is always the immediate dominator of the previously returned block. The
+ * iteration starts by returning <code>dominated</code>.
+ */
+ public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
+ return () -> new Iterator<BasicBlock>() {
+ private BasicBlock current = dominated;
+
+ @Override
+ public boolean hasNext() {
+ return current != null;
+ }
+
+ @Override
+ public BasicBlock next() {
+ if (!hasNext()) {
+ return null;
+ } else {
+ BasicBlock result = current;
+ if (current.getNumber() == 0) {
+ current = null;
+ } else {
+ current = immediateDominator(current);
+ assert current != result;
+ }
+ return result;
+ }
+ }
+ };
+ }
+
public BasicBlock[] getSortedBlocks() {
return sorted;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 2ffb5a1..bf93530 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -426,6 +426,10 @@
return isConstant() && getConstInstruction().isConstNumber();
}
+ public boolean isConstString() {
+ return isConstant() && getConstInstruction().isConstString();
+ }
+
public boolean isConstant() {
return definition.isOutConstant() && getLocalInfo() == null;
}
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 48c8eb0..dad58d8 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
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -328,16 +327,57 @@
this.graph = graph;
}
- private void processInvoke(DexEncodedMethod source, Invoke.Type type, DexMethod method) {
+ private void addClassInitializerTarget(DexClass clazz) {
+ assert clazz != null;
+ if (clazz.hasClassInitializer() && !clazz.isLibraryClass()) {
+ DexEncodedMethod possibleTarget = clazz.getClassInitializer();
+ addTarget(possibleTarget);
+ }
+ }
+
+ private void addClassInitializerTarget(DexType type) {
+ if (type.isArrayType()) {
+ type = type.toBaseType(appInfo.dexItemFactory);
+ }
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz != null) {
+ addClassInitializerTarget(clazz);
+ }
+ }
+
+ private void addTarget(DexEncodedMethod target) {
+ Node callee = graph.ensureMethodNode(target);
+ graph.addCall(caller, callee);
+ }
+
+ private void addPossibleTarget(DexEncodedMethod possibleTarget) {
+ DexClass possibleTargetClass =
+ appInfo.definitionFor(possibleTarget.method.getHolder());
+ if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
+ addTarget(possibleTarget);
+ }
+ }
+
+ private void addPossibleTargets(
+ DexEncodedMethod definition, Set<DexEncodedMethod> possibleTargets) {
+ for (DexEncodedMethod possibleTarget : possibleTargets) {
+ if (possibleTarget != definition) {
+ addPossibleTarget(possibleTarget);
+ }
+ }
+ }
+
+ private void processInvoke(Type type, DexMethod method) {
+ DexEncodedMethod source = caller.method;
method = graphLense.lookupMethod(method, source);
DexEncodedMethod definition = appInfo.lookup(type, method);
if (definition != null) {
assert !source.accessFlags.isBridge() || definition != caller.method;
- DexType definitionHolder = definition.method.getHolder();
- assert definitionHolder.isClassType();
- if (!appInfo.definitionFor(definitionHolder).isLibraryClass()) {
- Node callee = graph.ensureMethodNode(definition);
- graph.addCall(caller, callee);
+ DexClass definitionHolder = appInfo.definitionFor(definition.method.getHolder());
+ assert definitionHolder != null;
+ if (!definitionHolder.isLibraryClass()) {
+ addClassInitializerTarget(definitionHolder);
+ addTarget(definition);
// For virtual and interface calls add all potential targets that could be called.
if (type == Type.VIRTUAL || type == Type.INTERFACE) {
Set<DexEncodedMethod> possibleTargets;
@@ -346,73 +386,74 @@
} else {
possibleTargets = appInfo.lookupVirtualTargets(definition.method);
}
- for (DexEncodedMethod possibleTarget : possibleTargets) {
- if (possibleTarget != definition) {
- DexClass possibleTargetClass =
- appInfo.definitionFor(possibleTarget.method.getHolder());
- if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
- callee = graph.ensureMethodNode(possibleTarget);
- graph.addCall(caller, callee);
- }
- }
- }
+ addPossibleTargets(definition, possibleTargets);
}
}
}
}
+ private void processFieldAccess(DexField field) {
+ // Any field access implicitly calls the class initializer.
+ addClassInitializerTarget(field.getHolder());
+ }
+
@Override
public boolean registerInvokeVirtual(DexMethod method) {
- processInvoke(caller.method, Type.VIRTUAL, method);
+ processInvoke(Type.VIRTUAL, method);
return false;
}
@Override
public boolean registerInvokeDirect(DexMethod method) {
- processInvoke(caller.method, Type.DIRECT, method);
+ processInvoke(Type.DIRECT, method);
return false;
}
@Override
public boolean registerInvokeStatic(DexMethod method) {
- processInvoke(caller.method, Type.STATIC, method);
+ processInvoke(Type.STATIC, method);
return false;
}
@Override
public boolean registerInvokeInterface(DexMethod method) {
- processInvoke(caller.method, Type.INTERFACE, method);
+ processInvoke(Type.INTERFACE, method);
return false;
}
@Override
public boolean registerInvokeSuper(DexMethod method) {
- processInvoke(caller.method, Type.SUPER, method);
+ processInvoke(Type.SUPER, method);
return false;
}
@Override
public boolean registerInstanceFieldWrite(DexField field) {
+ processFieldAccess(field);
return false;
}
@Override
public boolean registerInstanceFieldRead(DexField field) {
+ processFieldAccess(field);
return false;
}
@Override
public boolean registerNewInstance(DexType type) {
+ addClassInitializerTarget(type);
return false;
}
@Override
public boolean registerStaticFieldRead(DexField field) {
+ processFieldAccess(field);
return false;
}
@Override
public boolean registerStaticFieldWrite(DexField field) {
+ processFieldAccess(field);
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 23b6b70..fad378e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -38,8 +38,8 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -89,8 +89,7 @@
this.graphLense = graphLense != null ? graphLense : GraphLense.getIdentityLense();
this.options = options;
this.printer = printer;
- Set<DexType> libraryClassesWithOptimizationInfo = markLibraryMethodsReturningReceiver();
- this.codeRewriter = new CodeRewriter(appInfo, libraryClassesWithOptimizationInfo);
+ this.codeRewriter = new CodeRewriter(appInfo, libraryMethodsReturningReceiver());
this.lambdaRewriter = enableDesugaring ? new LambdaRewriter(this) : null;
this.interfaceMethodRewriter =
(enableDesugaring && enableInterfaceMethodDesugaring())
@@ -183,18 +182,12 @@
throw new Unreachable();
}
- private Set<DexType> markLibraryMethodsReturningReceiver() {
+ private Set<DexMethod> libraryMethodsReturningReceiver() {
+ Set<DexMethod> methods = new HashSet<>();
DexItemFactory dexItemFactory = appInfo.dexItemFactory;
- dexItemFactory.stringBuilderMethods.forEachAppendMethod(this::markReturnsReceiver);
- dexItemFactory.stringBufferMethods.forEachAppendMethod(this::markReturnsReceiver);
- return ImmutableSet.of(dexItemFactory.stringBuilderType, dexItemFactory.stringBufferType);
- }
-
- private void markReturnsReceiver(DexMethod method) {
- DexEncodedMethod definition = appInfo.definitionFor(method);
- if (definition != null) {
- definition.markReturnsArgument(0);
- }
+ dexItemFactory.stringBufferMethods.forEachAppendMethod(methods::add);
+ dexItemFactory.stringBuilderMethods.forEachAppendMethod(methods::add);
+ return methods;
}
private void removeLambdaDeserializationMethods() {
@@ -238,7 +231,11 @@
for (DexProgramClass clazz : classes) {
futures.add(executor.submit(() -> clazz.forEachMethod(this::convertMethodToDex)));
}
+
ThreadUtils.awaitFutures(futures);
+
+ // Get rid of <clinit> methods with no code.
+ removeEmptyClassInitializers();
}
private void convertMethodToDex(DexEncodedMethod method) {
@@ -284,6 +281,9 @@
}, executorService);
timing.end();
+ // Get rid of <clinit> methods with no code.
+ removeEmptyClassInitializers();
+
// Build a new application with jumbo string info.
Builder builder = new Builder(application);
builder.setHighestSortingString(highestSortingString);
@@ -325,6 +325,16 @@
return builder.build();
}
+ private void removeEmptyClassInitializers() {
+ application.classes().forEach(this::removeEmptyClassInitializer);
+ }
+
+ private void removeEmptyClassInitializer(DexProgramClass clazz) {
+ if (clazz.hasTrivialClassInitializer()) {
+ clazz.removeStaticMethod(clazz.getClassInitializer());
+ }
+ }
+
private void clearDexMethodCompilationState() {
application.classes().forEach(this::clearDexMethodCompilationState);
}
@@ -463,6 +473,7 @@
codeRewriter.foldConstants(code);
codeRewriter.rewriteSwitch(code);
codeRewriter.simplifyIf(code);
+ codeRewriter.collectClassInitializerDefaults(method, code);
if (Log.ENABLED) {
Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
method.toSourceString(), code);
@@ -511,7 +522,7 @@
}
printMethod(code, "Final IR (non-SSA)");
- // After all the optimizations have take place, we compute whether method should be inlined.
+ // After all the optimizations have take place, we compute whether method should be inlinedex.
Constraint state;
if (!options.inlineAccessors || inliner == null) {
state = Constraint.NEVER;
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 484354a..8d36ef7 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
@@ -258,23 +258,15 @@
@Override
public void buildPrelude(IRBuilder builder) {
Map<Integer, MoveType> initializedLocals = new HashMap<>(node.localVariables.size());
+ // Record types for arguments.
+ recordArgumentTypes(initializedLocals);
+ // Add debug information for all locals at the initial label.
if (initialLabel != null) {
state.openLocals(initialLabel);
}
- int argumentRegister = 0;
- if (!isStatic()) {
- Type thisType = Type.getType(clazz.descriptor.toString());
- int register = state.writeLocal(argumentRegister++, thisType);
- builder.addThisArgument(register);
- initializedLocals.put(register, moveType(thisType));
- }
- for (Type type : parameterTypes) {
- MoveType moveType = moveType(type);
- int register = state.writeLocal(argumentRegister, type);
- builder.addNonThisArgument(register, moveType);
- argumentRegister += moveType.requiredRegisters();
- initializedLocals.put(register, moveType);
- }
+ // Build the actual argument instructions now that type and debug information is known
+ // for arguments.
+ buildArgumentInstructions(builder);
if (isSynchronized()) {
generatingMethodSynchronization = true;
Type clazzType = Type.getType(clazz.toDescriptorString());
@@ -297,6 +289,23 @@
for (Object o : node.localVariables) {
LocalVariableNode local = (LocalVariableNode) o;
Type localType = Type.getType(local.desc);
+ int sort = localType.getSort();
+ switch (sort) {
+ case Type.OBJECT:
+ case Type.ARRAY:
+ localType = JarState.NULL_TYPE;
+ break;
+ case Type.DOUBLE:
+ case Type.LONG:
+ case Type.FLOAT:
+ break;
+ case Type.VOID:
+ case Type.METHOD:
+ throw new Unreachable("Invalid local variable type: " + localType);
+ default:
+ localType = Type.INT_TYPE;
+ break;
+ }
int localRegister = state.getLocalRegister(local.index, localType);
MoveType exitingLocalType = initializedLocals.get(localRegister);
assert exitingLocalType == null || exitingLocalType == moveType(localType);
@@ -311,6 +320,36 @@
state.setBuilding();
}
+ private void buildArgumentInstructions(IRBuilder builder) {
+ int argumentRegister = 0;
+ if (!isStatic()) {
+ Type thisType = Type.getType(clazz.descriptor.toString());
+ Slot slot = state.readLocal(argumentRegister++, thisType);
+ builder.addThisArgument(slot.register);
+ }
+ for (Type type : parameterTypes) {
+ MoveType moveType = moveType(type);
+ Slot slot = state.readLocal(argumentRegister, type);
+ builder.addNonThisArgument(slot.register, moveType);
+ argumentRegister += moveType.requiredRegisters();
+ }
+ }
+
+ private void recordArgumentTypes(Map<Integer, MoveType> initializedLocals) {
+ int argumentRegister = 0;
+ if (!isStatic()) {
+ Type thisType = Type.getType(clazz.descriptor.toString());
+ int register = state.writeLocal(argumentRegister++, thisType);
+ initializedLocals.put(register, moveType(thisType));
+ }
+ for (Type type : parameterTypes) {
+ MoveType moveType = moveType(type);
+ int register = state.writeLocal(argumentRegister, type);
+ argumentRegister += moveType.requiredRegisters();
+ initializedLocals.put(register, moveType);
+ }
+ }
+
private void computeBlockEntryJarStates(IRBuilder builder) {
Int2ReferenceSortedMap<BlockInfo> CFG = builder.getCFG();
Queue<JarStateWorklistItem> worklist = new LinkedList<>();
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 5009d5e..2a3e431 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
@@ -87,6 +87,9 @@
if (type == BYTE_OR_BOOL_TYPE) {
type = Type.BYTE_TYPE;
}
+ if (other == BYTE_OR_BOOL_TYPE) {
+ other = Type.BYTE_TYPE;
+ }
int sort = type.getSort();
int otherSort = other.getSort();
if (isReferenceCompatible(type, other)) {
@@ -256,7 +259,7 @@
Collection<LocalVariableNode> nodes = localVariableStartPoints.get(label);
ArrayList<Local> locals = new ArrayList<>(nodes.size());
for (LocalVariableNode node : nodes) {
- locals.add(setLocal(node.index, Type.getType(node.desc), localVariables.get(node)));
+ locals.add(setLocalInfo(node.index, Type.getType(node.desc), localVariables.get(node)));
}
// Sort to ensure deterministic instruction ordering (correctness is unaffected).
locals.sort(Comparator.comparingInt(local -> local.slot.register));
@@ -335,6 +338,25 @@
return local;
}
+ private Local setLocalInfo(int index, Type type, DebugLocalInfo info) {
+ return setLocalInfoForRegister(getLocalRegister(index, type), info);
+ }
+
+ private Local setLocalInfoForRegister(int register, DebugLocalInfo info) {
+ Local existingLocal = getLocalForRegister(register);
+ // TODO(ager, zerny): Kotlin debug information contains locals that are not referenced.
+ // That seems broken and we currently do not retain that debug information because
+ // we do not let locals debug information influence code generation. Debug information can
+ // be completely malformed, so we shouldn't let it influence code generation. However, we
+ // need to deal with these unused locals in the debug information. For now we
+ // use a null type for the slot, but we should reconsider that.
+ Slot slot = existingLocal != null ? existingLocal.slot : new Slot(register, null);
+ Local local = new Local(slot, info);
+ locals[register] = local;
+ return local;
+ }
+
+
public int writeLocal(int index, Type type) {
assert nonNullType(type);
Local local = getLocal(index, type);
@@ -344,8 +366,9 @@
}
// We cannot assume consistency for writes because we do not have complete information about the
// scopes of locals. We assume the program to be verified and overwrite if the types mismatch.
- if (local == null || (local.info == null && !typeEquals(local.slot.type, type))) {
- local = setLocal(index, type, null);
+ if (local == null || !typeEquals(local.slot.type, type)) {
+ DebugLocalInfo info = local == null ? null : local.info;
+ local = setLocal(index, type, info);
}
return local.slot.register;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 236c9e5..b7036f5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -80,15 +80,15 @@
}
// Add the methods.
- DexEncodedMethod[] existing = clazz.virtualMethods;
- clazz.virtualMethods = new DexEncodedMethod[existing.length + methodsToImplement.size()];
- System.arraycopy(existing, 0, clazz.virtualMethods, 0, existing.length);
+ DexEncodedMethod[] existing = clazz.virtualMethods();
+ clazz.setVirtualMethods(new DexEncodedMethod[existing.length + methodsToImplement.size()]);
+ System.arraycopy(existing, 0, clazz.virtualMethods(), 0, existing.length);
for (int i = 0; i < methodsToImplement.size(); i++) {
DexEncodedMethod method = methodsToImplement.get(i);
assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
- clazz.virtualMethods[existing.length + i] = newMethod;
+ clazz.virtualMethods()[existing.length + i] = newMethod;
createdMethods.put(newMethod, method);
}
}
@@ -142,7 +142,7 @@
current = clazz;
while (true) {
// Hide candidates by virtual method of the class.
- hideCandidates(current.virtualMethods, candidates, toBeImplemented);
+ hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
if (candidates.isEmpty()) {
return toBeImplemented;
}
@@ -218,12 +218,12 @@
}
// Hide by virtual methods of this interface.
- for (DexEncodedMethod virtual : clazz.virtualMethods) {
+ for (DexEncodedMethod virtual : clazz.virtualMethods()) {
helper.hideMatches(virtual.method);
}
// Add all default methods of this interface.
- for (DexEncodedMethod encoded : clazz.virtualMethods) {
+ for (DexEncodedMethod encoded : clazz.virtualMethods()) {
if (rewriter.isDefaultMethod(encoded)) {
helper.addDefaultMethod(encoded);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index a84e082..2d06abe 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -44,7 +44,7 @@
// Process virtual interface methods first.
List<DexEncodedMethod> remainingMethods = new ArrayList<>();
- for (DexEncodedMethod virtual : iface.virtualMethods) {
+ for (DexEncodedMethod virtual : iface.virtualMethods()) {
if (rewriter.isDefaultMethod(virtual)) {
// Create a new method in a companion class to represent default method implementation.
DexMethod companionMethod = rewriter.defaultAsMethodOfCompanionClass(virtual.method);
@@ -79,14 +79,14 @@
}
// If at least one bridge methods was removed update the table.
- if (remainingMethods.size() < iface.virtualMethods.length) {
- iface.virtualMethods = remainingMethods.toArray(
- new DexEncodedMethod[remainingMethods.size()]);
+ if (remainingMethods.size() < iface.virtualMethods().length) {
+ iface.setVirtualMethods(remainingMethods.toArray(
+ new DexEncodedMethod[remainingMethods.size()]));
}
remainingMethods.clear();
// Process static methods, move them into companion class as well.
- for (DexEncodedMethod direct : iface.directMethods) {
+ for (DexEncodedMethod direct : iface.directMethods()) {
if (direct.accessFlags.isPrivate()) {
// We only expect to see private methods which are lambda$ methods,
// and they are supposed to be relaxed to package private static methods
@@ -105,9 +105,9 @@
remainingMethods.add(direct);
}
}
- if (remainingMethods.size() < iface.directMethods.length) {
- iface.directMethods = remainingMethods.toArray(
- new DexEncodedMethod[remainingMethods.size()]);
+ if (remainingMethods.size() < iface.directMethods().length) {
+ iface.setDirectMethods(remainingMethods.toArray(
+ new DexEncodedMethod[remainingMethods.size()]));
}
if (companionMethods.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 0a6ea7e..be6252a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -444,7 +444,7 @@
DexMethod implMethod = descriptor.implHandle.asMethod();
DexClass implMethodHolder = definitionFor(implMethod.holder);
- DexEncodedMethod[] directMethods = implMethodHolder.directMethods;
+ DexEncodedMethod[] directMethods = implMethodHolder.directMethods();
for (int i = 0; i < directMethods.length; i++) {
DexEncodedMethod encodedMethod = directMethods[i];
if (implMethod.match(encodedMethod)) {
@@ -491,8 +491,8 @@
DexEncodedMethod accessorEncodedMethod = new DexEncodedMethod(
callTarget, accessorFlags, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(),
new SynthesizedCode(new AccessorMethodSourceCode(LambdaClass.this)));
- accessorClass.directMethods = appendMethod(
- accessorClass.directMethods, accessorEncodedMethod);
+ accessorClass.setDirectMethods(appendMethod(
+ accessorClass.directMethods(), accessorEncodedMethod));
rewriter.converter.optimizeSynthesizedMethod(accessorEncodedMethod);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index af32cb7..f6f8f2c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -162,7 +162,7 @@
public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
for (DexProgramClass clazz : classes) {
// Search for a lambda deserialization method and remove it if found.
- DexEncodedMethod[] directMethods = clazz.directMethods;
+ DexEncodedMethod[] directMethods = clazz.directMethods();
if (directMethods != null) {
int methodCount = directMethods.length;
for (int i = 0; i < methodCount; i++) {
@@ -177,7 +177,7 @@
DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
System.arraycopy(directMethods, 0, newMethods, 0, i);
System.arraycopy(directMethods, i + 1, newMethods, i, methodCount - i - 1);
- clazz.directMethods = newMethods;
+ clazz.setDirectMethods(newMethods);
// We assume there is only one such method in the class.
break;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index 27cf89a..9f833f0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -5,19 +5,25 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.google.common.base.Equivalence;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
class BasicBlockInstructionsEquivalence extends Equivalence<BasicBlock> {
-
+ private static final int UNKNOW_HASH = -1;
private static final int MAX_HASH_INSTRUCTIONS = 5;
private final RegisterAllocator allocator;
+ private final int[] hashes;
- BasicBlockInstructionsEquivalence(RegisterAllocator allocator) {
+ BasicBlockInstructionsEquivalence(IRCode code, RegisterAllocator allocator) {
this.allocator = allocator;
+ hashes = new int[code.getHighestBlockNumber() + 1];
+ Arrays.fill(hashes, UNKNOW_HASH);
}
private boolean hasIdenticalInstructions(BasicBlock first, BasicBlock second) {
@@ -60,8 +66,23 @@
return hasIdenticalInstructions(a, b);
}
+ void clearComputedHash(BasicBlock basicBlock) {
+ hashes[basicBlock.getNumber()] = UNKNOW_HASH;
+ }
+
@Override
protected int doHash(BasicBlock basicBlock) {
+ int hash = hashes[basicBlock.getNumber()];
+ if (hash != UNKNOW_HASH) {
+ assert hash == computeHash(basicBlock);
+ return hash;
+ }
+ hash = computeHash(basicBlock);
+ hashes[basicBlock.getNumber()] = hash;
+ return hash;
+ }
+
+ private int computeHash(BasicBlock basicBlock) {
List<Instruction> instructions = basicBlock.getInstructions();
int hash = instructions.size();
for (int i = 0; i < instructions.size() && i < MAX_HASH_INSTRUCTIONS; i++) {
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 48a64b8..78d8369 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
@@ -6,13 +6,27 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
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.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueBoolean;
+import com.android.tools.r8.graph.DexValue.DexValueByte;
+import com.android.tools.r8.graph.DexValue.DexValueChar;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Binop;
@@ -55,6 +69,7 @@
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -82,12 +97,12 @@
private final AppInfo appInfo;
private final DexItemFactory dexItemFactory;
- private final Set<DexType> libraryClassesWithOptimizationInfo;
+ private final Set<DexMethod> libraryMethodsReturningReceiver;
- public CodeRewriter(AppInfo appInfo, Set<DexType> libraryClassesWithOptimizationInfo) {
+ public CodeRewriter(AppInfo appInfo, Set<DexMethod> libraryMethodsReturningReceiver) {
this.appInfo = appInfo;
this.dexItemFactory = appInfo.dexItemFactory;
- this.libraryClassesWithOptimizationInfo = libraryClassesWithOptimizationInfo;
+ this.libraryMethodsReturningReceiver = libraryMethodsReturningReceiver;
}
/**
@@ -503,37 +518,39 @@
// Replace result uses for methods where something is known about what is returned.
public void rewriteMoveResult(IRCode code) {
- if (!appInfo.hasSubtyping()) {
- return;
- }
+ AppInfoWithSubtyping appInfoWithSubtyping = appInfo.withSubtyping();
InstructionIterator iterator = code.instructionIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
if (invoke.outValue() != null && invoke.outValue().getLocalInfo() == null) {
- DexEncodedMethod target = invoke.computeSingleTarget(appInfo.withSubtyping());
- // We have a set of library classes with optimization information - consider those
- // as well.
- if ((target == null) &&
- libraryClassesWithOptimizationInfo.contains(invoke.getInvokedMethod().getHolder())) {
- target = appInfo.definitionFor(invoke.getInvokedMethod());
- }
- if (target != null) {
- DexMethod invokedMethod = target.method;
- // Check if the invoked method is known to return one of its arguments.
- DexEncodedMethod definition = appInfo.definitionFor(invokedMethod);
- if (definition != null && definition.getOptimizationInfo().returnsArgument()) {
- int argumentIndex = definition.getOptimizationInfo().getReturnedArgument();
- // Replace the out value of the invoke with the argument and ignore the out value.
- if (argumentIndex != -1 && checkArgumentType(invoke, target.method, argumentIndex)) {
- Value argument = invoke.arguments().get(argumentIndex);
- assert (invoke.outType() == argument.outType()) ||
- (invoke.outType() == MoveType.OBJECT
- && argument.outType() == MoveType.SINGLE
- && argument.getConstInstruction().asConstNumber().isZero());
- invoke.outValue().replaceUsers(argument);
- invoke.setOutValue(null);
+ boolean isLibraryMethodReturningReceiver =
+ libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod());
+ if (isLibraryMethodReturningReceiver) {
+ if (checkArgumentType(invoke, invoke.getInvokedMethod(), 0)) {
+ invoke.outValue().replaceUsers(invoke.arguments().get(0));
+ invoke.setOutValue(null);
+ }
+ } else if (appInfoWithSubtyping != null) {
+ DexEncodedMethod target = invoke.computeSingleTarget(appInfoWithSubtyping);
+ if (target != null) {
+ DexMethod invokedMethod = target.method;
+ // Check if the invoked method is known to return one of its arguments.
+ DexEncodedMethod definition = appInfo.definitionFor(invokedMethod);
+ if (definition != null && definition.getOptimizationInfo().returnsArgument()) {
+ int argumentIndex = definition.getOptimizationInfo().getReturnedArgument();
+ // Replace the out value of the invoke with the argument and ignore the out value.
+ if (argumentIndex != -1 && checkArgumentType(invoke, target.method,
+ argumentIndex)) {
+ Value argument = invoke.arguments().get(argumentIndex);
+ assert (invoke.outType() == argument.outType()) ||
+ (invoke.outType() == MoveType.OBJECT
+ && argument.outType() == MoveType.SINGLE
+ && argument.getConstInstruction().asConstNumber().isZero());
+ invoke.outValue().replaceUsers(argument);
+ invoke.setOutValue(null);
+ }
}
}
}
@@ -605,6 +622,171 @@
}
}
+ // Check if the static put is a constant derived from the class holding the method.
+ // This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
+ private boolean isClassNameConstant(DexEncodedMethod method, StaticPut put) {
+ if (put.getField().type != dexItemFactory.stringType) {
+ return false;
+ }
+ if (put.inValue().definition != null && put.inValue().definition.isInvokeVirtual()) {
+ InvokeVirtual invoke = put.inValue().definition.asInvokeVirtual();
+ if ((invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName
+ || invoke.getInvokedMethod() == dexItemFactory.classMethods.getName)
+ && invoke.inValues().get(0).definition.isConstClass()
+ && invoke.inValues().get(0).definition.asConstClass().getValue()
+ == method.method.getHolder()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void collectClassInitializerDefaults(DexEncodedMethod method, IRCode code) {
+ if (!method.isClassInitializer()) {
+ return;
+ }
+
+ // Collect relevant static put which are dominated by the exit block, and not dominated by a
+ // static get.
+ // This does not check for instructions that can throw. However, as classes which throw in the
+ // class initializer are never visible to the program (see Java Virtual Machine Specification
+ // section 5.5, https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5), this
+ // does not matter (except maybe for removal of const-string instructions, but that is
+ // acceptable).
+ DominatorTree dominatorTree = new DominatorTree(code);
+ BasicBlock exit = code.getNormalExitBlock();
+ if (exit == null) {
+ return;
+ }
+ Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+ for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+ InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
+ while (iterator.hasPrevious()) {
+ Instruction current = iterator.previous();
+ if (current.isStaticPut()) {
+ StaticPut put = current.asStaticPut();
+ DexField field = put.getField();
+ if (field.getHolder() == method.method.getHolder()) {
+ if (put.inValue().isConstant()) {
+ if ((field.type.isClassType() || field.type.isArrayType())
+ && put.inValue().getConstInstruction().isConstNumber() &&
+ put.inValue().getConstInstruction().asConstNumber().isZero()) {
+ // Collect put of zero as a potential default value.
+ puts.put(put.getField(), put);
+ } else if (field.type.isPrimitiveType() || field.type == dexItemFactory.stringType) {
+ // Collect put as a potential default value.
+ puts.put(put.getField(), put);
+ }
+ } else if (isClassNameConstant(method, put)) {
+ // Collect put of class name constant as a potential default value.
+ puts.put(put.getField(), put);
+ }
+ }
+ }
+ if (current.isStaticGet()) {
+ // If a static field is read, any collected potential default value cannot be a
+ // default value.
+ if (puts.containsKey(current.asStaticGet().getField())) {
+ puts.remove(current.asStaticGet().getField());
+ }
+ }
+ }
+ }
+
+ if (!puts.isEmpty()) {
+ // Set initial values for static fields from the definitive static put instructions collected.
+ for (StaticPut put : puts.values()) {
+ DexField field = put.getField();
+ DexEncodedField encodedField = appInfo.definitionFor(field);
+ if (field.type == dexItemFactory.stringType) {
+ if (put.inValue().isConstant()) {
+ if (put.inValue().getConstInstruction().isConstNumber()) {
+ assert put.inValue().getConstInstruction().asConstNumber().isZero();
+ encodedField.staticValue = DexValueNull.NULL;
+ } else {
+ ConstString cnst = put.inValue().getConstInstruction().asConstString();
+ encodedField.staticValue = new DexValueString(cnst.getValue());
+ }
+ } else {
+ InvokeVirtual invoke = put.inValue().definition.asInvokeVirtual();
+ String name = method.method.getHolder().toSourceString();
+ if (invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName) {
+ String simpleName = name.substring(name.lastIndexOf('.') + 1);
+ encodedField.staticValue =
+ new DexValueString(dexItemFactory.createString(simpleName));
+ } else {
+ assert invoke.getInvokedMethod() == dexItemFactory.classMethods.getName;
+ encodedField.staticValue = new DexValueString(dexItemFactory.createString(name));
+ }
+ }
+ } else if (field.type.isClassType() || field.type.isArrayType()) {
+ if (put.inValue().getConstInstruction().isConstNumber()
+ && put.inValue().getConstInstruction().asConstNumber().isZero()) {
+ encodedField.staticValue = DexValueNull.NULL;
+ } else {
+ throw new Unreachable("Unexpected default value for field type " + field.type + ".");
+ }
+ } else {
+ ConstNumber cnst = put.inValue().getConstInstruction().asConstNumber();
+ if (field.type == dexItemFactory.booleanType) {
+ encodedField.staticValue = DexValueBoolean.create(cnst.getBooleanValue());
+ } else if (field.type == dexItemFactory.byteType) {
+ encodedField.staticValue = DexValueByte.create((byte) cnst.getIntValue());
+ } else if (field.type == dexItemFactory.shortType) {
+ encodedField.staticValue = DexValueShort.create((short) cnst.getIntValue());
+ } else if (field.type == dexItemFactory.intType) {
+ encodedField.staticValue = DexValueInt.create(cnst.getIntValue());
+ } else if (field.type == dexItemFactory.longType) {
+ encodedField.staticValue = DexValueLong.create(cnst.getLongValue());
+ } else if (field.type == dexItemFactory.floatType) {
+ encodedField.staticValue = DexValueFloat.create(cnst.getFloatValue());
+ } else if (field.type == dexItemFactory.doubleType) {
+ encodedField.staticValue = DexValueDouble.create(cnst.getDoubleValue());
+ } else if (field.type == dexItemFactory.charType) {
+ encodedField.staticValue = DexValueChar.create((char) cnst.getIntValue());
+ } else {
+ throw new Unreachable("Unexpected field type " + field.type + ".");
+ }
+ }
+ }
+
+ // Remove the static put instructions now replaced by static filed initial values.
+ List<Instruction> toRemove = new ArrayList<>();
+ for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
+ iterator.remove();
+ // Collect, for removal, the instruction that created the value for the static put,
+ // if all users are gone. This is done even if these instructions can throw as for
+ // the current patterns matched these exceptions are not detectable.
+ StaticPut put = current.asStaticPut();
+ if (put.inValue().uniqueUsers().size() == 0) {
+ if (put.inValue().isConstString()) {
+ toRemove.add(put.inValue().definition);
+ } else if (put.inValue().definition.isInvokeVirtual()) {
+ toRemove.add(put.inValue().definition);
+ }
+ }
+ }
+ }
+ }
+
+ // Remove the instructions collected for removal.
+ if (toRemove.size() > 0) {
+ for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ if (toRemove.contains(iterator.next())) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+ }
+ }
+
/**
* Due to inlining, we might see chains of casts on subtypes. It suffices to cast to the lowest
* subtype, as that will fail if a cast on a supertype would have failed.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MoveEliminator.java b/src/main/java/com/android/tools/r8/ir/optimize/MoveEliminator.java
index 0d93561..a228b20 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MoveEliminator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MoveEliminator.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Move;
+import com.android.tools.r8.ir.code.MoveType;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import java.util.HashSet;
@@ -30,21 +31,32 @@
allocator.getRegisterForValue(activeMove.src(), activeMove.getNumber());
int activeMoveDstRegister =
allocator.getRegisterForValue(activeMove.dest(), activeMove.getNumber());
- if (activeMoveSrcRegister == moveSrcRegister
- && activeMoveDstRegister == moveDstRegister) {
+ if (activeMoveSrcRegister == moveSrcRegister && activeMoveDstRegister == moveDstRegister) {
return true;
}
+ if (activeMoveDstRegister == moveSrcRegister && activeMoveSrcRegister == moveDstRegister) {
+ if (move.outType() != MoveType.WIDE) {
+ return true;
+ }
+ // If the move is wide make sure the register pair is non-overlapping.
+ if (moveSrcRegister != moveDstRegister + 1 && moveSrcRegister + 1 != moveDstRegister) {
+ return true;
+ }
+ }
}
}
if (instruction.outValue() != null && instruction.outValue().needsRegister()) {
Value defined = instruction.outValue();
int definedRegister = allocator.getRegisterForValue(defined, instruction.getNumber());
activeMoves.removeIf((m) -> {
- int moveSrcRegister = allocator.getRegisterForValue(m.inValues().get(0), m.getNumber());
- int moveDstRegister = allocator.getRegisterForValue(m.outValue(), m.getNumber());
+ int moveSrcRegister = allocator.getRegisterForValue(m.src(), m.getNumber());
+ int moveDstRegister = allocator.getRegisterForValue(m.dest(), m.getNumber());
for (int i = 0; i < defined.requiredRegisters(); i++) {
- if (definedRegister + i == moveDstRegister || definedRegister + i == moveSrcRegister) {
- return true;
+ for (int j = 0; j < m.outValue().requiredRegisters(); j++) {
+ if (definedRegister + i == moveDstRegister + j
+ || definedRegister + i == moveSrcRegister + j) {
+ return true;
+ }
}
}
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index 4ad05cc..a7d6e8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -173,7 +173,7 @@
*/
private static void removeIdenticalPredecessorBlocks(IRCode code, RegisterAllocator allocator) {
BasicBlockInstructionsEquivalence equivalence =
- new BasicBlockInstructionsEquivalence(allocator);
+ new BasicBlockInstructionsEquivalence(code, allocator);
// Locate one block at a time that has identical predecessors. Rewrite those predecessors and
// then start over. Restarting when one blocks predecessors have been rewritten simplifies
// the rewriting and reduces the size of the data structures.
@@ -194,6 +194,7 @@
BasicBlock otherPred = block.getPredecessors().get(otherPredIndex);
pred.clearCatchHandlers();
pred.getInstructions().clear();
+ equivalence.clearComputedHash(pred);
for (BasicBlock succ : pred.getSuccessors()) {
succ.removePredecessor(pred);
}
diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/NamingState.java
index f42af78..2011530 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingState.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
-import com.android.tools.r8.graph.CanonicalizedDexItem;
+import com.android.tools.r8.graph.CachedHashValueDexItem;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.utils.StringUtils;
@@ -16,14 +16,14 @@
import java.util.Map;
import java.util.Set;
-class NamingState<T extends CanonicalizedDexItem> {
+class NamingState<T extends CachedHashValueDexItem> {
private final NamingState<T> parent;
private final Map<T, InternalState> usedNames = new IdentityHashMap<>();
private final DexItemFactory itemFactory;
private final ImmutableList<String> dictionary;
- static <T extends CanonicalizedDexItem> NamingState<T> createRoot(
+ static <T extends CachedHashValueDexItem> NamingState<T> createRoot(
DexItemFactory itemFactory, ImmutableList<String> dictionary) {
return new NamingState<>(null, itemFactory, dictionary);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 31c7f1f..300f18e 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -60,7 +60,7 @@
.collect(Collectors.toSet());
for (DexType type : classes) {
DexClass clazz = appInfo.definitionFor(type);
- clazz.virtualMethods = removeMethods(clazz.virtualMethods, unneededVisibilityBridges);
+ clazz.setVirtualMethods(removeMethods(clazz.virtualMethods(), unneededVisibilityBridges));
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index 2850790..3fd5470 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -35,7 +35,7 @@
DexClass holder = appInfo.definitionFor(type);
scope = new ScopedDexItemSet(scope);
if (holder != null) {
- holder.virtualMethods = processMethods(holder.virtualMethods);
+ holder.setVirtualMethods(processMethods(holder.virtualMethods()));
}
type.forAllExtendsSubtypes(this::processClass);
scope = scope.getParent();
@@ -74,10 +74,6 @@
private final ScopedDexItemSet parent;
private final Set<Wrapper<DexMethod>> items = new HashSet<>();
- private ScopedDexItemSet() {
- this(null);
- }
-
private ScopedDexItemSet(ScopedDexItemSet parent) {
this.parent = parent;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index d3306d9..c04b29a 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -257,20 +257,20 @@
? DexTypeList.empty()
: new DexTypeList(interfaces.toArray(new DexType[interfaces.size()]));
// Step 2: replace fields and methods.
- target.directMethods = mergedDirectMethods
- .toArray(new DexEncodedMethod[mergedDirectMethods.size()]);
- target.virtualMethods = mergedVirtualMethods
- .toArray(new DexEncodedMethod[mergedVirtualMethods.size()]);
- target.staticFields = mergedStaticFields
- .toArray(new DexEncodedField[mergedStaticFields.size()]);
- target.instanceFields = mergedInstanceFields
- .toArray(new DexEncodedField[mergedInstanceFields.size()]);
+ target.setDirectMethods(mergedDirectMethods
+ .toArray(new DexEncodedMethod[mergedDirectMethods.size()]));
+ target.setVirtualMethods(mergedVirtualMethods
+ .toArray(new DexEncodedMethod[mergedVirtualMethods.size()]));
+ target.setStaticFields(mergedStaticFields
+ .toArray(new DexEncodedField[mergedStaticFields.size()]));
+ target.setInstanceFields(mergedInstanceFields
+ .toArray(new DexEncodedField[mergedInstanceFields.size()]));
// Step 3: Unlink old class to ease tree shaking.
source.superType = application.dexItemFactory.objectType;
- source.directMethods = null;
- source.virtualMethods = null;
- source.instanceFields = null;
- source.staticFields = null;
+ source.setDirectMethods(null);
+ source.setVirtualMethods(null);
+ source.setInstanceFields(null);
+ source.setStaticFields(null);
source.interfaces = DexTypeList.empty();
// Step 4: Record merging.
mergedClasses.put(source.type, target.type);
@@ -427,11 +427,11 @@
private GraphLense fixupTypeReferences(GraphLense graphLense) {
// Globally substitute merged class types in protos and holders.
for (DexProgramClass clazz : appInfo.classes()) {
- clazz.directMethods = substituteTypesIn(clazz.directMethods);
- clazz.virtualMethods = substituteTypesIn(clazz.virtualMethods);
- clazz.virtualMethods = removeDupes(clazz.virtualMethods);
- clazz.staticFields = substituteTypesIn(clazz.staticFields);
- clazz.instanceFields = substituteTypesIn(clazz.instanceFields);
+ clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
+ clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+ clazz.setVirtualMethods(removeDupes(clazz.virtualMethods()));
+ clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
+ clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
}
// Record type renamings so instanceof and checkcast checks are also fixed.
for (DexType type : mergedClasses.keySet()) {
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 58646a8..64294bd 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -84,10 +84,10 @@
// 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());
+ clazz.setDirectMethods(reachableMethods(clazz.directMethods(), clazz));
+ clazz.setVirtualMethods(reachableMethods(clazz.virtualMethods(), clazz));
+ clazz.setInstanceFields(reachableFields(clazz.instanceFields()));
+ clazz.setStaticFields(reachableFields(clazz.staticFields()));
usagePrinter.visited();
}
}
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 2480417..97a3679 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -212,8 +212,8 @@
/**
* Get the input stream of the proguard-map resource if it exists.
*/
- public InputStream getProguardMap(Closer closer) throws IOException {
- return proguardMap == null ? null : closer.register(proguardMap.getStream());
+ public InputStream getProguardMap() throws IOException {
+ return proguardMap == null ? null : proguardMap.getStream();
}
/**
@@ -382,10 +382,11 @@
}
}
- public void writeProguardMap(Closer closer, OutputStream out) throws IOException {
- InputStream input = getProguardMap(closer);
- assert input != null;
- out.write(ByteStreams.toByteArray(input));
+ public void writeProguardMap(OutputStream out) throws IOException {
+ try (InputStream input = getProguardMap()) {
+ assert input != null;
+ out.write(ByteStreams.toByteArray(input));
+ }
}
public void writeProguardSeeds(Closer closer, OutputStream out) throws IOException {
@@ -422,6 +423,7 @@
private List<Resource> mainDexListResources = new ArrayList<>();
private List<String> mainDexListClasses = new ArrayList<>();
private Resource mainDexListOutput;
+ private boolean ignoreDexInArchive = false;
// See AndroidApp::builder().
private Builder() {
@@ -670,6 +672,17 @@
}
/**
+ * Ignore dex resources in input archives.
+ *
+ * In some situations (e.g. AOSP framework build) the input archives include both class and
+ * dex resources. Setting this flag ignores the dex resources and reads the class resources
+ * only.
+ */
+ public void setIgnoreDexInArchive(boolean value) {
+ ignoreDexInArchive = value;
+ }
+
+ /**
* Build final AndroidApp.
*/
public AndroidApp build() {
@@ -696,7 +709,7 @@
} else if (isClassFile(file)) {
programResources.add(Resource.fromFile(Resource.Kind.CLASSFILE, file));
} else if (isArchive(file)) {
- programFileArchiveReaders.add(new ProgramFileArchiveReader(file));
+ programFileArchiveReaders.add(new ProgramFileArchiveReader(file, ignoreDexInArchive));
} else {
throw new CompilationError("Unsupported source file type for file: " + file);
}
diff --git a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
index 8903525..d6848a4 100644
--- a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
@@ -13,10 +13,11 @@
long result = 0;
int shift = 0;
for (int i = 1; i < numberOfBytes; i++) {
- result |= ((long) ((file.get() & 0xFF))) << shift;
+ result |= ((long) (file.get() & 0xFF)) << shift;
shift += 8;
}
- return result | (file.get() << shift);
+ // Let the last byte sign-extend into any remaining bytes.
+ return result | (((long) file.get()) << shift);
}
// Inspired by com.android.dex.EncodedValueCodec
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
index 814ffd1..8341921 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
@@ -25,11 +25,13 @@
class ProgramFileArchiveReader {
private final Path archive;
+ private boolean ignoreDexInArchive;
private List<Resource> dexResources = null;
private List<Resource> classResources = null;
- ProgramFileArchiveReader(Path archive) {
+ ProgramFileArchiveReader(Path archive, boolean ignoreDexInArchive) {
this.archive = archive;
+ this.ignoreDexInArchive = ignoreDexInArchive;
}
private void readArchive() throws IOException {
@@ -41,9 +43,11 @@
while ((entry = stream.getNextEntry()) != null) {
Path name = Paths.get(entry.getName());
if (isDexFile(name)) {
- Resource resource =
- new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
- dexResources.add(resource);
+ if (!ignoreDexInArchive) {
+ Resource resource =
+ new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
+ dexResources.add(resource);
+ }
} else if (isClassFile(name)) {
String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 8404703..8712b7a 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -109,7 +109,7 @@
return join(collection, separator, BraceType.NONE);
}
- public static <T> String join(String separator, T... strings) {
+ public static String join(String separator, String... strings) {
return join(Arrays.asList(strings), separator, BraceType.NONE);
}
@@ -124,15 +124,15 @@
return builder.toString();
}
- public static <T> String lines(T... lines) {
+ public static String lines(String... lines) {
StringBuilder builder = new StringBuilder();
- for (T line : lines) {
+ for (String line : lines) {
builder.append(line).append(LINE_SEPARATOR);
}
return builder.toString();
}
- public static <T> String joinLines(T... lines) {
+ public static String joinLines(String... lines) {
return join(LINE_SEPARATOR, lines);
}
@@ -158,11 +158,11 @@
assert(0 <= width && width <= 8);
String hex = Integer.toHexString(value);
if (value >= 0) {
- return zeroPrefixString(hex, width);
+ return "0x" + zeroPrefixString(hex, width);
} else {
// Negative ints are always formatted as 8 characters.
assert(hex.length() == 8);
- return hex;
+ return "0x" + hex;
}
}
@@ -170,11 +170,11 @@
assert(0 <= width && width <= 16);
String hex = Long.toHexString(value);
if (value >= 0) {
- return zeroPrefixString(hex, width);
+ return "0x" + zeroPrefixString(hex, width);
} else {
// Negative longs are always formatted as 16 characters.
assert(hex.length() == 16);
- return hex;
+ return "0x" + hex;
}
}
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 063d5fc..beba10e 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4656,14 +4656,6 @@
// 1) t04
// java.lang.AssertionError
- .put("lang.reflect.Field.getLjava_lang_Object.Field_get_A04", match(R8_AFTER_D8_COMPILER))
- // 1) t02
- // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
-
- .put("lang.reflect.Field.getLongLjava_lang_Object.Field_getLong_A04", match(R8_AFTER_D8_COMPILER))
- // 1)
- // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
-
.build(); // end of failuresToTriage
public static final Multimap<String, TestCondition> flakyWithArt =
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index fd4cdccb..eee820f 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -314,10 +314,22 @@
}
}
+ static class RetainedTemporaryFolder extends TemporaryFolder {
+ RetainedTemporaryFolder(java.io.File parentFolder) {
+ super(parentFolder);
+ }
+ protected void after() {} // instead of remove, do nothing
+ }
+
// For non-Linux platforms create the temporary directory in the repository root to simplify
// running Art in a docker container
public static TemporaryFolder getTemporaryFolderForTest() {
- return new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
+ String tmpDir = System.getProperty("test_dir");
+ if (tmpDir == null) {
+ return new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
+ } else {
+ return new RetainedTemporaryFolder(new java.io.File(tmpDir));
+ }
}
public static String getArtBinary() {
diff --git a/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java b/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java
index d1e46f3..6114f61 100644
--- a/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java
+++ b/src/test/java/com/android/tools/r8/dex/ExtraFileTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.dex;
import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -18,6 +19,7 @@
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
public class ExtraFileTest {
@@ -32,8 +34,11 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
@Test
- public void splitMemberRebindingTwoFiles()
+ public void splitMemberRebindingTwoFilesRelease()
throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
if (!ToolHelper.artSupported()) {
return;
@@ -46,6 +51,7 @@
R8Command command =
R8Command.builder()
.addProgramFiles(original)
+ .setMode(CompilationMode.RELEASE)
.setOutputPath(out)
.setMinApiLevel(Constants.ANDROID_L_API) // Allow native multidex.
.setProguardMapFile(proguardMap)
@@ -66,4 +72,24 @@
null,
null);
}
+
+ @Test
+ public void splitMemberRebindingTwoFilesDebug()
+ throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+ thrown.expect(CompilationException.class);
+ Path out = temp.getRoot().toPath();
+ Path original = Paths.get(EXAMPLE_DIR, EXAMPLE_DEX);
+ Path packageMap = Paths.get(ToolHelper.EXAMPLES_DIR, EXAMPLE_PACKAGE_MAP);
+ Path proguardMap = Paths.get(ToolHelper.EXAMPLES_DIR, EXAMPLE_PROGUARD_MAP);
+ R8Command command =
+ R8Command.builder()
+ .addProgramFiles(original)
+ .setMode(CompilationMode.DEBUG)
+ .setOutputPath(out)
+ .setMinApiLevel(Constants.ANDROID_L_API) // Allow native multidex.
+ .setProguardMapFile(proguardMap)
+ .setPackageDistributionFile(packageMap)
+ .build();
+ ToolHelper.runR8(command);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 72bbaed..2782c91 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -183,9 +183,9 @@
"MethodStart:",
".line 1",
- "LabelXStart:",
" ldc 0",
" istore 1",
+ "LabelXStart:",
".line 2",
" invokestatic Test/ensureLine()V",
"LabelXEnd:",
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
index 1812fe1..671698b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jasmin;
+import static junit.framework.Assert.assertNull;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.fail;
@@ -10,6 +11,7 @@
import com.android.tools.r8.errors.CompilationError;
import java.util.Arrays;
import java.util.Collection;
+import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -34,13 +36,17 @@
String artResult = null;
try {
artResult = runOnArt(builder, main);
- } catch (CompilationError t) {
- // Ignore.
+ fail();
+ } catch (ExecutionException t) {
+ if (!(t.getCause() instanceof CompilationError)) {
+ t.printStackTrace(System.out);
+ fail("Invalid dex class names should be compilation errors.");
+ }
} catch (Throwable t) {
t.printStackTrace(System.out);
fail("Invalid dex class names should be compilation errors.");
}
- assert artResult == null : "Invalid dex class names should be rejected.";
+ assertNull("Invalid dex class names should be rejected.", artResult);
}
@Parameters
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index 791ecca..87d350c 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -3,11 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jasmin;
+import static junit.framework.Assert.assertNull;
import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.fail;
import com.android.tools.r8.errors.CompilationError;
import java.util.Arrays;
import java.util.Collection;
+import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -32,10 +35,14 @@
String artResult = null;
try {
artResult = runOnArt(builder, main);
- } catch (CompilationError t) {
- // Ignore.
+ fail();
+ } catch (ExecutionException t) {
+ if (!(t.getCause() instanceof CompilationError)) {
+ t.printStackTrace(System.out);
+ fail("Invalid dex field names should be compilation errors.");
+ }
}
- assert artResult == null : "Invalid dex class names should be rejected.";
+ assertNull("Invalid dex field names should be rejected.", artResult);
}
@Parameters
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
index f8b7615..e1ace71 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
@@ -3,13 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.jasmin;
+import static junit.framework.Assert.assertNull;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.fail;
+import com.android.tools.r8.CompilationException;
import com.android.tools.r8.errors.CompilationError;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -34,13 +37,17 @@
String artResult = null;
try {
artResult = runOnArt(builder, main);
- } catch (CompilationError t) {
- // Ignore.
+ fail();
+ } catch (ExecutionException t) {
+ if (!(t.getCause() instanceof CompilationError)) {
+ t.printStackTrace(System.out);
+ fail("Invalid dex method names should be compilation errors.");
+ }
} catch (Throwable t) {
t.printStackTrace(System.out);
- fail("Invalid dex class names should be compilation errors.");
+ fail("Invalid dex method names should be compilation errors.");
}
- assert artResult == null : "Invalid dex class names should be rejected.";
+ assertNull("Invalid dex method names should be rejected.", artResult);
}
@Parameters
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
new file mode 100644
index 0000000..8c1df56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
@@ -0,0 +1,50 @@
+// 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.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class Regress64658224 extends JasminTestBase {
+
+ @Test
+ public void testInvalidTypeInfoFromLocals() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ clazz.addStaticMethod("foo", ImmutableList.of("I"), "V",
+ ".limit stack 2",
+ ".limit locals 2",
+ ".var 1 is x Ljava/lang/Object; from L1 to L2",
+ " aconst_null",
+ " astore 1",
+ "L1:",
+ " iload 0",
+ " ifeq L3",
+ "L2:",
+ " goto L5",
+ "L3:",
+ " aload 1",
+ " iconst_0",
+ " aaload",
+ " pop",
+ "L5:",
+ " return");
+
+ clazz.addMainMethod(
+ ".limit stack 1",
+ ".limit locals 1",
+ " ldc 2",
+ " invokestatic Test/foo(I)V",
+ " return");
+
+ String expected = "";
+ String javaResult = runOnJava(builder, clazz.name);
+ assertEquals(expected, javaResult);
+ String artResult = runOnArtD8(builder, clazz.name);
+ assertEquals(expected, artResult);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index 6ee0b66..a71291d 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -205,7 +205,7 @@
String pkg = "org.apache.harmony.jpda.tests.jdwp";
command = Arrays.asList(
ToolHelper.getJavaExecutable(),
- "-cp", System.getProperty("java.class.path") + ":" + lib,
+ "-cp", System.getProperty("java.class.path") + File.pathSeparator + lib,
run, pkg + "." + test);
} else {
command = Arrays.asList(
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index 66137df..d5f9c6a 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -70,10 +70,8 @@
.addProguardConfigurationFiles(keepRulesPath)
.build());
if (androidApp.hasProguardMap()) {
- try (Closer closer = Closer.create()) {
- androidApp.writeProguardMap(closer, new FileOutputStream(
+ androidApp.writeProguardMap(new FileOutputStream(
Paths.get(tmpOutputDir.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME).toFile()));
- }
}
}
diff --git a/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java b/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java
index 72b452f..1426a0f 100644
--- a/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/R8DebugStrippingTest.java
@@ -30,6 +30,7 @@
import com.google.common.collect.Maps;
import com.google.common.io.Closer;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
@@ -115,8 +116,8 @@
ToolHelper.runR8(command, (options) -> options.skipDebugLineNumberOpt = !compressRanges);
ClassNameMapper classNameMapper;
- try (Closer closer = Closer.create()) {
- classNameMapper = ProguardMapReader.mapperFromInputStream(result.getProguardMap(closer));
+ try (InputStream is = result.getProguardMap()) {
+ classNameMapper = ProguardMapReader.mapperFromInputStream(is);
}
if (compressRanges) {
classNameMapper.forAllClassNamings(this::ensureRangesAreUniquePerClass);
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
new file mode 100644
index 0000000..195c3aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -0,0 +1,359 @@
+// 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.rewrite.staticvalues;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import org.junit.Test;
+
+public class StaticValuesTest extends SmaliTestBase {
+
+ @Test
+ public void testAllTypes() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("booleanField", "Z");
+ builder.addStaticField("byteField", "B");
+ builder.addStaticField("shortField", "S");
+ builder.addStaticField("intField", "I");
+ builder.addStaticField("longField", "J");
+ builder.addStaticField("floatField", "F");
+ builder.addStaticField("doubleField", "D");
+ builder.addStaticField("charField", "C");
+ builder.addStaticField("stringField", "Ljava/lang/String;");
+
+ builder.addStaticInitializer(
+ 2,
+ "const v0, 1",
+ "sput-byte v0, LTest;->booleanField:Z",
+ "sput-byte v0, LTest;->byteField:B",
+ "const v0, 2",
+ "sput-short v0, LTest;->shortField:S",
+ "const v0, 3",
+ "sput v0, LTest;->intField:I",
+ "const-wide v0, 4",
+ "sput-wide v0, LTest;->longField:J",
+ "const v0, 0x40a00000", // 5.0.
+ "sput v0, LTest;->floatField:F",
+ "const-wide v0, 0x4018000000000000L", // 6.0.
+ "sput-wide v0, LTest;->doubleField:D",
+ "const v0, 0x37", // ASCII 7.
+ "sput-char v0, LTest;->charField:C",
+ "const-string v0, \"8\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-boolean v1, LTest;->booleanField:Z",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Z)V",
+ "sget-byte v1, LTest;->byteField:B",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-short v1, LTest;->shortField:S",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget v1, LTest;->intField:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-wide v1, LTest;->longField:J",
+ "invoke-virtual { v0, v1, v2 }, Ljava/io/PrintStream;->println(J)V",
+ "sget v1, LTest;->floatField:F",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(F)V",
+ "sget-wide v1, LTest;->doubleField:D",
+ "invoke-virtual { v0, v1, v2 }, Ljava/io/PrintStream;->println(D)V",
+ "sget-char v1, LTest;->charField:C",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(C)V",
+ "sget-object v1, LTest;->stringField:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertFalse(inspector.clazz("Test").clinit().isPresent());
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("true\n1\n2\n3\n4\n5.0\n6.0\n7\n8\n", result);
+ }
+
+ @Test
+ public void getBeforePut() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("field1", "I", "1");
+ builder.addStaticField("field2", "I", "2");
+
+ builder.addStaticInitializer(
+ 1,
+ "sget v0, LTest;->field1:I",
+ "sput v0, LTest;->field2:I",
+ "const v0, 0",
+ "sput v0, LTest;->field1:I",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LTest;->field1:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget v1, LTest;->field2:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ MethodSubject clinit = inspector.clazz("Test").clinit();
+ // Nothing changed in the class initializer.
+ assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("0\n1\n", result);
+ }
+
+ @Test
+ public void testNull() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("stringField", "Ljava/lang/String;", "Hello");
+ builder.addStaticField("arrayField", "[I");
+ builder.addStaticField("arrayField2", "[[[[I");
+
+ builder.addStaticInitializer(
+ 2,
+ "const v0, 0",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "sput-object v0, LTest;->arrayField:[I",
+ "sput-object v0, LTest;->arrayField2:[[[[I",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-object v1, LTest;->stringField:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, LTest;->arrayField:[I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V",
+ "sget-object v1, LTest;->arrayField2:[[[[I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertFalse(inspector.clazz("Test").clinit().isPresent());
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("null\nnull\nnull\n", result);
+ }
+
+ @Test
+ public void testString() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("stringField1", "Ljava/lang/String;", "Hello");
+ builder.addStaticField("stringField2", "Ljava/lang/String;", "Hello");
+ builder.addStaticField("stringField3", "Ljava/lang/String;", "Hello");
+
+ builder.addStaticInitializer(
+ 2,
+ "const-string v0, \"Value1\"",
+ "sput-object v0, LTest;->stringField1:Ljava/lang/String;",
+ "const-string v0, \"Value2\"",
+ "sput-object v0, LTest;->stringField2:Ljava/lang/String;",
+ "sput-object v0, LTest;->stringField3:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-object v1, LTest;->stringField1:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, LTest;->stringField2:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, LTest;->stringField3:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertFalse(inspector.clazz("Test").clinit().isPresent());
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("Value1\nValue2\nValue2\n", result);
+ }
+
+ @Test
+ public void testInitializationToOwnClassName() {
+ String className = "org.example.Test";
+ SmaliBuilder builder = new SmaliBuilder(className);
+
+ builder.addStaticField("name1", "Ljava/lang/String;");
+ builder.addStaticField("name2", "Ljava/lang/String;");
+ builder.addStaticField("name3", "Ljava/lang/String;");
+ builder.addStaticField("simpleName1", "Ljava/lang/String;");
+ builder.addStaticField("simpleName2", "Ljava/lang/String;");
+ builder.addStaticField("simpleName3", "Ljava/lang/String;");
+
+ String descriptor = builder.getCurrentClassDescriptor();
+
+ builder.addStaticInitializer(
+ 3,
+ "const-class v0, " + descriptor,
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;",
+ "move-result-object v0",
+ "sput-object v0, " + descriptor + "->simpleName1:Ljava/lang/String;",
+ "const-class v0, " + descriptor,
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getName()Ljava/lang/String;",
+ "move-result-object v0",
+ "sput-object v0, " + descriptor + "->name1:Ljava/lang/String;",
+ "const-class v0, " + descriptor,
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;",
+ "move-result-object v1",
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getName()Ljava/lang/String;",
+ "move-result-object v2",
+ "sput-object v1, " + descriptor + "->simpleName2:Ljava/lang/String;",
+ "sput-object v1, " + descriptor + "->simpleName3:Ljava/lang/String;",
+ "sput-object v2, " + descriptor + "->name2:Ljava/lang/String;",
+ "sput-object v2, " + descriptor + "->name3:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-object v1, " + descriptor + "->simpleName1:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->name1:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->simpleName2:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->name2:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->simpleName3:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->name3:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertTrue(inspector.clazz(className).isPresent());
+ assertFalse(inspector.clazz(className).clinit().isPresent());
+
+ String result = runArt(processedApplication, options, className);
+
+ assertEquals(
+ "Test\n" + className + "\nTest\n" + className + "\nTest\n" + className + "\n", result);
+ }
+
+ @Test
+ public void testInitializationToOtherClassName() {
+ String className = "org.example.Test";
+ SmaliBuilder builder = new SmaliBuilder(className);
+
+ builder.addStaticField("simpleName", "Ljava/lang/String;");
+ builder.addStaticField("name", "Ljava/lang/String;");
+
+ String descriptor = builder.getCurrentClassDescriptor();
+
+ builder.addStaticInitializer(
+ 3,
+ "const-class v0, Lorg/example/Test2;",
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;",
+ "move-result-object v0",
+ "sput-object v0, " + descriptor + "->simpleName:Ljava/lang/String;",
+ "const-class v0, Lorg/example/Test2;",
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getName()Ljava/lang/String;",
+ "move-result-object v0",
+ "sput-object v0, " + descriptor + "->name:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-object v1, " + descriptor + "->simpleName:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->name:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ builder.addClass("org.example.Test2");
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertTrue(inspector.clazz(className).isPresent());
+ assertTrue(inspector.clazz(className).clinit().isPresent());
+
+ String result = runArt(processedApplication, options, className);
+
+ assertEquals("Test2\norg.example.Test2\n", result);
+ }
+
+ @Test
+ public void fieldOnOtherClass() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticInitializer(
+ 1,
+ "const v0, 2",
+ "sput v0, LOther;->field:I",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LOther;->field:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "return-void"
+ );
+
+ builder.addClass("Other");
+ builder.addStaticField("field", "I", "1");
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ MethodSubject clinit = inspector.clazz("Test").clinit();
+ // Nothing changed in the class initializer.
+ assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("2\n", result);
+ }
+
+}
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 4c1fc69..51b6ab5 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -584,9 +584,12 @@
private static void checkSameStructure(FoundMethodSubject refMethod, ClassSubject clazz) {
MethodSignature signature = refMethod.getOriginalSignature();
- Assert.assertTrue("Missing Method: " + clazz.getDexClass().toSourceString() + "."
- + signature.toString(),
- clazz.method(signature).isPresent());
+ // Don't check for existence of class initializers, as the code optimization can remove them.
+ if (!refMethod.isClassInitializer()) {
+ Assert.assertTrue("Missing Method: " + clazz.getDexClass().toSourceString() + "."
+ + signature.toString(),
+ clazz.method(signature).isPresent());
+ }
}
private static void checkSameStructure(FoundFieldSubject refField, ClassSubject clazz) {
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 79ef0b9..1d256e0 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -53,6 +53,9 @@
);
InternalOptions options = new InternalOptions();
+ // (b/64777953) Disable outliner optimization for this test because it increases time
+ // from 1 minute to 7 minutes.
+ options.outline.enabled = false;
DexApplication originalApplication = buildApplication(smaliBuilder, options);
DexApplication processedApplication = processApplication(originalApplication, options);
String result = runArt(processedApplication, options);
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 4656cd6..c726aad 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -828,13 +828,13 @@
DexInspector inspector = new DexInspector(processedApplication);
ClassSubject clazz = inspector.clazz(options.outline.className);
assertTrue(clazz.isPresent());
- assertEquals(3, clazz.getDexClass().directMethods.length);
+ assertEquals(3, clazz.getDexClass().directMethods().length);
// Collect the return types of the putlines for the body of method1 and method2.
List<DexType> r = new ArrayList<>();
- for (int i = 0; i < clazz.getDexClass().directMethods.length; i++) {
- if (clazz.getDexClass().directMethods[i].getCode().asDexCode().instructions[0]
+ for (int i = 0; i < clazz.getDexClass().directMethods().length; i++) {
+ if (clazz.getDexClass().directMethods()[i].getCode().asDexCode().instructions[0]
instanceof InvokeVirtual) {
- r.add(clazz.getDexClass().directMethods[i].method.proto.returnType);
+ r.add(clazz.getDexClass().directMethods()[i].method.proto.returnType);
}
}
assert r.size() == 2;
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 f070130..4253395 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -71,6 +71,10 @@
this.returnType = returnType;
this.parameterTypes = parameterTypes;
}
+
+ public static MethodSignature staticInitializer(String clazz) {
+ return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
+ }
}
public static class SmaliBuilder {
@@ -210,6 +214,29 @@
);
}
+ public void addStaticField(String name, String type, String defaultValue) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".field static ");
+ builder.append(name);
+ builder.append(":");
+ builder.append(type);
+ if (defaultValue != null) {
+ builder.append(" = ");
+ if (type.equals("Ljava/lang/String;")) {
+ builder.append('"');
+ builder.append(defaultValue);
+ builder.append('"');
+ } else {
+ builder.append(defaultValue);
+ }
+ }
+ getSource(currentClassName).add(builder.toString());
+ }
+
+ public void addStaticField(String name, String type) {
+ addStaticField(name, type, null);
+ }
+
public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
int locals, String... instructions) {
StringBuilder builder = new StringBuilder();
@@ -222,8 +249,30 @@
public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
int locals, String code) {
+ return addStaticMethod("", returnType, name, parameters, locals, code);
+ }
+
+ public MethodSignature addStaticInitializer(int locals, String... instructions) {
+ StringBuilder builder = new StringBuilder();
+ for (String instruction : instructions) {
+ builder.append(instruction);
+ builder.append("\n");
+ }
+ return addStaticInitializer(locals, builder.toString());
+ }
+
+ public MethodSignature addStaticInitializer(int locals, String code) {
+ return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
+ }
+
+ private MethodSignature addStaticMethod(String flags, String returnType, String name,
+ List<String> parameters, int locals, String code) {
StringBuilder builder = new StringBuilder();
builder.append(".method public static ");
+ if (flags != null && flags.length() > 0) {
+ builder.append(flags);
+ builder.append(" ");
+ }
builder.append(name);
builder.append("(");
for (String parameter : parameters) {
@@ -469,12 +518,16 @@
}
public String runArt(DexApplication application, InternalOptions options) {
+ return runArt(application, options, DEFAULT_MAIN_CLASS_NAME);
+ }
+
+ public String runArt(DexApplication application, InternalOptions options, String mainClass) {
try {
AndroidApp app = writeDex(application, options);
Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
// TODO(sgjesse): Pass in a unique temp directory for each run.
app.writeToZip(out, OutputMode.Indexed);
- return ToolHelper.runArtNoVerificationErrors(out.toString(), DEFAULT_MAIN_CLASS_NAME);
+ return ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass);
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 61c3c14..0ad240c 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -267,6 +267,10 @@
public abstract MethodSubject method(String returnType, String name, List<String> parameters);
+ public MethodSubject clinit() {
+ return method("void", "<clinit>", ImmutableList.of());
+ }
+
public MethodSubject method(MethodSignature signature) {
return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
}
@@ -517,6 +521,8 @@
public abstract boolean isBridge();
+ public abstract boolean isClassInitializer();
+
public abstract DexEncodedMethod getMethod();
public Iterator<InstructionSubject> iterateInstructions() {
@@ -574,6 +580,11 @@
}
@Override
+ public boolean isClassInitializer() {
+ return false;
+ }
+
+ @Override
public DexEncodedMethod getMethod() {
return null;
}
@@ -640,6 +651,11 @@
}
@Override
+ public boolean isClassInitializer() {
+ return dexMethod.isClassInitializer();
+ }
+
+ @Override
public DexEncodedMethod getMethod() {
return dexMethod;
}
diff --git a/tools/test.py b/tools/test.py
index 8e016c7..1da077b 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -44,7 +44,8 @@
help='Print a line before a tests starts and after it ends to stdout.',
default=False, action='store_true')
result.add_option('--tool',
- help='Tool to run ART tests with: "r8" (default) or "d8". Ignored if "--all_tests" enabled.',
+ help='Tool to run ART tests with: "r8" (default) or "d8". Ignored if'
+ ' "--all_tests" enabled.',
default=None, choices=["r8", "d8"])
result.add_option('--jctf',
help='Run JCTF tests with: "r8" (default) or "d8".',
@@ -56,8 +57,15 @@
help="Don't run, only compile JCTF tests.",
default=False, action='store_true')
result.add_option('--disable_assertions',
- help="Disable assertions when running tests.",
+ help='Disable assertions when running tests.',
default=False, action='store_true')
+ result.add_option('--with_code_coverage',
+ help='Enable code coverage with Jacoco.',
+ default=False, action='store_true')
+ result.add_option('--test_dir',
+ help='Use a custom directory for the test artifacts instead of a'
+ ' temporary (which is automatically removed after the test).'
+ ' Note that the directory will not be cleared before the test.')
return result.parse_args()
@@ -72,10 +80,12 @@
def Main():
(options, args) = ParseOptions()
- gradle_args = ['cleanTest', 'test']
if len(args) > 1:
print("test.py takes at most one argument, the pattern for tests to run")
return -1
+
+ gradle_args = []
+ # Set all necessary Gradle properties and options first.
if options.verbose:
gradle_args.append('-Pprint_test_stdout')
if options.no_internal:
@@ -96,9 +106,8 @@
gradle_args.append('-Pjctf_compile_only')
if options.disable_assertions:
gradle_args.append('-Pdisable_assertions')
- if len(args) > 0:
- gradle_args.append('--tests')
- gradle_args.append(args[0])
+ if options.with_code_coverage:
+ gradle_args.append('-Pwith_code_coverage')
if os.name == 'nt':
# temporary hack
gradle_args.append('-Pno_internal')
@@ -108,6 +117,23 @@
gradle_args.append('jctfCommonJar')
gradle_args.append('-x')
gradle_args.append('jctfTestsClasses')
+ if options.test_dir:
+ gradle_args.append('-Ptest_dir=' + options.test_dir)
+ if not os.path.exists(options.test_dir):
+ os.makedirs(options.test_dir)
+
+ # Add Gradle tasks
+ gradle_args.append('cleanTest')
+ gradle_args.append('test')
+ if len(args) > 0:
+ # Test filtering. Must always follow the 'test' task.
+ gradle_args.append('--tests')
+ gradle_args.append(args[0])
+ if options.with_code_coverage:
+ # Create Jacoco report after tests.
+ gradle_args.append('jacocoTestReport')
+
+ # Now run tests on selected runtime(s).
vms_to_test = [options.dex_vm] if options.dex_vm != "all" else ALL_ART_VMS
for art_vm in vms_to_test:
return_code = gradle.RunGradle(gradle_args + ['-Pdex_vm=%s' % art_vm],
diff --git a/tools/test_framework.py b/tools/test_framework.py
index b6f5a19..9e5f63f 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -43,7 +43,7 @@
' third_party/framework/framework*.jar.'
' Report Golem-compatible CodeSize and RunTimeRaw values.')
parser.add_argument('--tool',
- choices = ['dx', 'd8', 'd8-release', 'goyt'],
+ choices = ['dx', 'd8', 'd8-release', 'goyt', 'goyt-release'],
required = True,
help = 'Compiler tool to use.')
parser.add_argument('--name',
@@ -65,21 +65,25 @@
with utils.TempDir() as temp_dir:
- if args.tool in ['dx', 'goyt']:
+ if args.tool in ['dx', 'goyt', 'goyt-release']:
tool_args = ['--dex', '--output=' + temp_dir, '--multi-dex',
'--min-sdk-version=' + MIN_SDK_VERSION]
- if args.tool == 'goyt':
+ xmx = None
+ if args.tool.startswith('goyt'):
tool_file = GOYT_EXE
tool_args = ['--num-threads=8'] + tool_args
+ if args.tool == 'goyt-release':
+ tool_args.append('--no-locals')
elif args.tool == 'dx':
tool_file = DX_JAR
+ xmx = '-Xmx1600m'
else:
tool_file = D8_JAR
tool_args = ['--output', temp_dir, '--min-api', MIN_SDK_VERSION]
if args.tool == 'd8-release':
tool_args.append('--release')
-
+ xmx = '-Xmx600m'
cmd = []
@@ -89,7 +93,8 @@
cmd.extend(['tools/track_memory.sh', track_memory_file])
if tool_file.endswith('.jar'):
- cmd.extend(['java', '-jar'])
+ assert xmx is not None
+ cmd.extend(['java', xmx, '-jar'])
cmd.extend([tool_file] + tool_args + [FRAMEWORK_JAR])