Merge "Allow compare_apk_sizes to compare based on code sizes instead of full dex size"
diff --git a/build.gradle b/build.gradle
index 00b8a46..c205b2d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -676,9 +676,6 @@
task testJar(type: ShadowJar, dependsOn: testClasses) {
baseName = "r8tests"
from sourceSets.test.output
- if (!project.hasProperty('exclude_deps')) {
- relocate('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm')
- }
}
task testJarNoDeps(type: ShadowJar, dependsOn: testClasses) {
diff --git a/src/main/java/com/android/tools/r8/code/FillArrayData.java b/src/main/java/com/android/tools/r8/code/FillArrayData.java
index 4569897..a49a31d 100644
--- a/src/main/java/com/android/tools/r8/code/FillArrayData.java
+++ b/src/main/java/com/android/tools/r8/code/FillArrayData.java
@@ -44,4 +44,9 @@
public String toSmaliString(ClassNameMapper naming) {
return formatSmaliString("v" + AA + ", :label_" + (getOffset() + BBBBBBBB));
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
}
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 73f7921..5e71072 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -441,7 +441,7 @@
dest.putShort((short) code.incomingRegisterSize);
dest.putShort((short) code.outgoingRegisterSize);
dest.putShort((short) code.tries.length);
- dest.putInt(mixedSectionOffsets.getOffsetFor(code.getDebugInfo()));
+ dest.putInt(mixedSectionOffsets.getOffsetFor(code.getDebugInfoForWriting()));
// Jump over the size.
int insnSizeOffset = dest.position();
dest.forward(4);
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 b754c9a..e02869d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -40,6 +40,7 @@
public DexString highestSortingString;
private DexDebugInfo debugInfo;
+ private DexDebugInfoForWriting debugInfoForWriting;
public DexCode(
int registerSize,
@@ -97,6 +98,9 @@
public void setDebugInfo(DexDebugInfo debugInfo) {
this.debugInfo = debugInfo;
+ if (debugInfoForWriting != null) {
+ debugInfoForWriting = null;
+ }
}
public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
@@ -382,7 +386,7 @@
}
}
if (debugInfo != null) {
- debugInfo.collectIndexedItems(indexedItems);
+ getDebugInfoForWriting().collectIndexedItems(indexedItems);
}
if (handlers != null) {
for (TryHandler handler : handlers) {
@@ -391,6 +395,17 @@
}
}
+ public DexDebugInfoForWriting getDebugInfoForWriting() {
+ if (debugInfo == null) {
+ return null;
+ }
+ if (debugInfoForWriting == null) {
+ debugInfoForWriting = new DexDebugInfoForWriting(debugInfo);
+ }
+
+ return debugInfoForWriting;
+ }
+
private void updateHighestSortingString(DexString candidate) {
assert candidate != null;
if (highestSortingString == null || highestSortingString.slowCompareTo(candidate) < 0) {
@@ -406,7 +421,7 @@
void collectMixedSectionItems(MixedSectionCollection mixedItems) {
if (mixedItems.add(this)) {
if (debugInfo != null) {
- debugInfo.collectMixedSectionItems(mixedItems);
+ getDebugInfoForWriting().collectMixedSectionItems(mixedItems);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java
new file mode 100644
index 0000000..0777b85
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForWriting.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2019, 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.graph;
+
+import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
+import java.util.Arrays;
+
+/**
+ * Wraps DexDebugInfo to make comparison and hashcode not consider
+ * the SetInlineFrames
+ */
+public class DexDebugInfoForWriting extends DexDebugInfo {
+
+ public DexDebugInfoForWriting(DexDebugInfo dexDebugInfo) {
+ super(dexDebugInfo.startLine, dexDebugInfo.parameters,
+ Arrays.stream(dexDebugInfo.events)
+ .filter(d -> !(d instanceof SetInlineFrame))
+ .toArray(DexDebugEvent[]::new));
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 50c91eb..898afed 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -300,23 +300,38 @@
private final BiMap<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
public void map(DexType from, DexType to) {
+ if (from == to) {
+ return;
+ }
typeMap.put(from, to);
}
public void map(DexMethod from, DexMethod to) {
+ if (from == to) {
+ return;
+ }
methodMap.put(from, to);
}
public void map(DexField from, DexField to) {
+ if (from == to) {
+ return;
+ }
fieldMap.put(from, to);
}
public void move(DexMethod from, DexMethod to) {
+ if (from == to) {
+ return;
+ }
map(from, to);
originalMethodSignatures.put(to, from);
}
public void move(DexField from, DexField to) {
+ if (from == to) {
+ return;
+ }
fieldMap.put(from, to);
originalFieldSignatures.put(to, from);
}
@@ -840,9 +855,11 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- for (Map.Entry<DexType, DexType> entry : typeMap.entrySet()) {
- builder.append(entry.getKey().toSourceString()).append(" -> ");
- builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
+ if (typeMap != null) {
+ for (Map.Entry<DexType, DexType> entry : typeMap.entrySet()) {
+ builder.append(entry.getKey().toSourceString()).append(" -> ");
+ builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
+ }
}
for (Map.Entry<DexMethod, DexMethod> entry : methodMap.entrySet()) {
builder.append(entry.getKey().toSourceString()).append(" -> ");
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 85cde7c..3ce1467 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -118,6 +118,10 @@
return true;
}
+ public boolean hasLocalVariableTable() {
+ return getNode().localVariables != null && !getNode().localVariables.isEmpty();
+ }
+
@Override
public IRCode buildIR(
DexEncodedMethod encodedMethod,
@@ -214,9 +218,13 @@
triggerDelayedParsingIfNeccessary();
node.instructions.accept(
new JarRegisterEffectsVisitor(method.getHolder(), registry, application));
- node.tryCatchBlocks.forEach(tryCatchBlockNode ->
+ for (TryCatchBlockNode tryCatchBlockNode : node.tryCatchBlocks) {
+ // Exception type can be null for "catch all" used for try/finally.
+ if (tryCatchBlockNode.type != null) {
registry.registerTypeReference(application.getTypeFromDescriptor(
- DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
+ DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type)));
+ }
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index de0f48c..ed51913 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.code.FillArrayDataPayload;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -74,9 +73,8 @@
}
@Override
- public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
- // Side-effects its input values.
- return false;
+ public boolean instructionTypeCanThrow() {
+ return true;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 6e5e5d7..4dcc839 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -149,7 +149,9 @@
computeInitializers();
typeVerificationHelper = new TypeVerificationHelper(code, factory, appInfo);
typeVerificationHelper.computeVerificationTypes();
- splitExceptionalBlocks();
+ if (!options.testing.noSplittingExceptionalEdges) {
+ splitExceptionalBlocks();
+ }
rewriter.converter.deadCodeRemover.run(code);
rewriteNots();
LoadStoreHelper loadStoreHelper = new LoadStoreHelper(code, typeVerificationHelper, appInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 78e5e7e..881b255 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -356,6 +356,10 @@
return index;
}
if (dex.canThrow()) {
+ // TODO(zerny): Remove this from block computation.
+ if (dex.hasPayload()) {
+ arrayFilledDataPayloadResolver.addPayloadUser((FillArrayData) dex);
+ }
// If the instruction can throw and is in a try block, add edges to its catch successors.
Try tryRange = getTryForOffset(offset);
if (tryRange != null) {
@@ -398,10 +402,6 @@
builder.ensureNormalSuccessorBlock(offset, offset + dex.getSize());
return index;
}
- // TODO(zerny): Remove this from block computation.
- if (dex.hasPayload()) {
- arrayFilledDataPayloadResolver.addPayloadUser((FillArrayData) dex);
- }
// This instruction does not close the block.
return -1;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 4035611..045ba3e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -1661,9 +1661,11 @@
}
public void addNewArrayFilledData(int arrayRef, int elementWidth, long size, short[] data) {
- add(
+ NewArrayFilledData instruction =
new NewArrayFilledData(
- readRegister(arrayRef, ValueTypeConstraint.OBJECT), elementWidth, size, data));
+ readRegister(arrayRef, ValueTypeConstraint.OBJECT), elementWidth, size, data);
+ assert instruction.instructionTypeCanThrow();
+ addInstruction(instruction);
}
public void addNewInstance(int dest, DexType type) {
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 e96acfb..b1ea3f9 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
@@ -879,6 +879,10 @@
printC1VisualizerHeader(method);
String previous = printMethod(code, "Initial IR (SSA)", null);
+ if (options.testing.irModifier != null) {
+ options.testing.irModifier.accept(code);
+ }
+
if (options.canHaveArtStringNewInitBug()) {
CodeRewriter.ensureDirectStringNewToInit(code);
}
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 5938781..7447f76 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
@@ -2701,6 +2701,9 @@
if (contents == null) {
continue;
}
+ if (block.hasCatchHandlers()) {
+ continue;
+ }
int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
NewArrayFilledData fillArray =
new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 59d8717..f8ddcb6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -291,7 +291,7 @@
// invoke-virtual { s1, ... } mtd1
// goto Exit
// b2:
- // s2 <- static-get singleoton
+ // s2 <- static-get singleton
// ...
// invoke-virtual { s2, ... } mtd1
// goto Exit
@@ -503,7 +503,7 @@
}
}
- if (!methodMapping.isEmpty() || fieldMapping.isEmpty()) {
+ if (!methodMapping.isEmpty() || !fieldMapping.isEmpty()) {
classStaticizer.converter.appView.setGraphLense(
new ClassStaticizerGraphLense(
classStaticizer.converter.graphLense(),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 47d3743..1b74e3b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -401,13 +401,22 @@
&& (unknownOption.equals("forceinline") || unknownOption.equals("neverinline"))) {
devMessage = ", this option needs to be turned on explicitly if used for tests.";
}
- reporter.error(new StringDiagnostic(
- "Unknown option \"-" + unknownOption + "\"" + devMessage,
- origin, getPosition(optionStart)));
+ unknownOption(unknownOption, optionStart, devMessage);
}
return true;
}
+ private void unknownOption(String unknownOption, TextPosition optionStart) {
+ unknownOption(unknownOption, optionStart, "");
+ }
+
+ private void unknownOption(
+ String unknownOption, TextPosition optionStart, String additionalMessage) {
+ throw reporter.fatalError((new StringDiagnostic(
+ "Unknown option \"-" + unknownOption + "\"" + additionalMessage,
+ origin, getPosition(optionStart))));
+ }
+
private boolean parseUnsupportedOptionAndErr(TextPosition optionStart) {
String option = Iterables.find(UNSUPPORTED_FLAG_OPTIONS, this::skipFlag, null);
if (option != null) {
@@ -768,14 +777,19 @@
TextPosition start = getPosition();
acceptString("-");
String unknownOption = acceptString();
- throw reporter.fatalError(new StringDiagnostic(
- "Unknown option \"-" + unknownOption + "\"",
- origin,
- start));
+ unknownOption(unknownOption, start);
}
} else {
builder.setType(ProguardKeepRuleType.KEEP);
}
+ if (!eof() && !Character.isWhitespace(peekChar()) && peekChar() != ',') {
+ // The only path to here is through "-keep" with an unsupported suffix.
+ unacceptString("-keep");
+ TextPosition start = getPosition();
+ acceptString("-");
+ String unknownOption = acceptString();
+ unknownOption(unknownOption, start);
+ }
parseRuleModifiers(builder);
}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 56dc071..2499ccb 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -288,6 +288,7 @@
* @return a class descriptor i.e. "Ljava/lang/Object;"
*/
public static String getDescriptorFromClassBinaryName(String typeBinaryName) {
+ assert typeBinaryName != null;
return ('L' + typeBinaryName + ';');
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 4921c79..f34e059 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -532,6 +533,8 @@
public boolean noLocalsTableOnInput = false;
public boolean forceNameReflectionOptimization = false;
public boolean disallowLoadStoreOptimization = false;
+ public Consumer<IRCode> irModifier = null;
+ public boolean noSplittingExceptionalEdges = false;
}
private boolean hasMinApi(AndroidApiLevel level) {
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 1daaad3..9dfb30d 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
+import static com.android.tools.r8.ToolHelper.CLASSPATH_SEPARATOR;
import static com.android.tools.r8.ToolHelper.getJavaExecutable;
import static org.junit.Assert.assertEquals;
@@ -76,7 +77,9 @@
String classPath =
addR8ExternalDeps
- ? r8jar.toAbsolutePath().toString() + ":" + ToolHelper.DEPS_NOT_RELOCATED
+ ? r8jar.toAbsolutePath().toString()
+ + CLASSPATH_SEPARATOR
+ + ToolHelper.DEPS_NOT_RELOCATED
: r8jar.toAbsolutePath().toString();
List<String> command = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index e0baab1..c24ed3b 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -163,6 +163,8 @@
/**
* Write lines of text to a temporary file.
+ *
+ * The file will include a line separator after the last line.
*/
protected Path writeTextToTempFile(String... lines) throws IOException {
return writeTextToTempFile(System.lineSeparator(), Arrays.asList(lines));
@@ -170,11 +172,28 @@
/**
* Write lines of text to a temporary file, along with the specified line separator.
+ *
+ * The file will include a line separator after the last line.
*/
protected Path writeTextToTempFile(String lineSeparator, List<String> lines)
throws IOException {
+ return writeTextToTempFile(lineSeparator, lines, true);
+ }
+
+ /**
+ * Write lines of text to a temporary file, along with the specified line separator.
+ *
+ * The argument <code>includeTerminatingLineSeparator</code> control if the file will include
+ * a line separator after the last line.
+ */
+ protected Path writeTextToTempFile(
+ String lineSeparator, List<String> lines, boolean includeTerminatingLineSeparator)
+ throws IOException {
Path file = temp.newFile().toPath();
- String contents = String.join(lineSeparator, lines) + lineSeparator;
+ String contents = String.join(lineSeparator, lines);
+ if (includeTerminatingLineSeparator) {
+ contents += lineSeparator;
+ }
Files.write(file, contents.getBytes(StandardCharsets.UTF_8));
return file;
}
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 61bf1b3..c67c05c 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -3,16 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.utils.ListUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
@@ -92,14 +88,6 @@
}
static Collection<Path> getFilesForInnerClasses(Collection<Class<?>> classes) throws IOException {
- Set<Path> paths = new HashSet<>();
- for (Class clazz : classes) {
- Path path = ToolHelper.getClassFileForTestClass(clazz);
- String prefix = path.toString().replace(CLASS_EXTENSION, "$");
- paths.addAll(
- ToolHelper.getClassFilesForTestDirectory(
- path.getParent(), p -> p.toString().startsWith(prefix)));
- }
- return paths;
+ return ToolHelper.getClassFilesForInnerClasses(classes);
}
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 0c84e7c..9700d56 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -4,9 +4,6 @@
package com.android.tools.r8;
import static com.android.tools.r8.TestBase.Backend.DEX;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.ToolHelper.DexVm;
@@ -83,57 +80,27 @@
}
public CR assertNoMessages() {
- assertEquals(0, getDiagnosticMessages().getInfos().size());
- assertEquals(0, getDiagnosticMessages().getWarnings().size());
- assertEquals(0, getDiagnosticMessages().getErrors().size());
+ getDiagnosticMessages().assertNoMessages();
return self();
}
public CR assertOnlyInfos() {
- assertNotEquals(0, getDiagnosticMessages().getInfos().size());
- assertEquals(0, getDiagnosticMessages().getWarnings().size());
- assertEquals(0, getDiagnosticMessages().getErrors().size());
+ getDiagnosticMessages().assertOnlyInfos();
return self();
}
public CR assertOnlyWarnings() {
- assertEquals(0, getDiagnosticMessages().getInfos().size());
- assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
- assertEquals(0, getDiagnosticMessages().getErrors().size());
+ getDiagnosticMessages().assertOnlyWarnings();
return self();
}
public CR assertWarningMessageThatMatches(Matcher<String> matcher) {
- assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
- for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
- if (matcher.matches(getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage())) {
- return self();
- }
- }
- StringBuilder builder = new StringBuilder("No warning matches " + matcher.toString());
- builder.append(System.lineSeparator());
- if (getDiagnosticMessages().getWarnings().size() == 0) {
- builder.append("There where no warnings.");
- } else {
- builder.append("There where " + getDiagnosticMessages().getWarnings().size() + " warnings:");
- builder.append(System.lineSeparator());
- for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
- builder.append(getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage());
- builder.append(System.lineSeparator());
- }
- }
- fail(builder.toString());
+ getDiagnosticMessages().assertWarningMessageThatMatches(matcher);
return self();
}
public CR assertNoWarningMessageThatMatches(Matcher<String> matcher) {
- assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
- for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
- String message = getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage();
- if (matcher.matches(message)) {
- fail("The warning: \"" + message + "\" + matches " + matcher + ".");
- }
- }
+ getDiagnosticMessages().assertNoWarningMessageThatMatches(matcher);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 3e734cc..d1e55f4 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -5,6 +5,8 @@
package com.android.tools.r8;
import java.util.List;
+import org.hamcrest.Matcher;
+
public interface TestDiagnosticMessages {
@@ -13,4 +15,20 @@
public List<Diagnostic> getWarnings();
public List<Diagnostic> getErrors();
+
+ public TestDiagnosticMessages assertNoMessages();
+
+ public TestDiagnosticMessages assertOnlyInfos();
+
+ public TestDiagnosticMessages assertOnlyWarnings();
+
+ public TestDiagnosticMessages assertInfosCount(int count);
+
+ public TestDiagnosticMessages assertWarningsCount(int count);
+
+ public TestDiagnosticMessages assertErrorsCount(int count);
+
+ public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher);
+
+ public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher);
}
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index d0476b9..43d71b1 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -4,8 +4,13 @@
package com.android.tools.r8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
import java.util.ArrayList;
import java.util.List;
+import org.hamcrest.Matcher;
public class TestDiagnosticMessagesImpl implements DiagnosticsHandler, TestDiagnosticMessages {
private final List<Diagnostic> infos = new ArrayList<>();
@@ -38,4 +43,75 @@
public List<Diagnostic> getErrors() {
return errors;
}
+
+
+ public TestDiagnosticMessages assertNoMessages() {
+ assertEquals(0, getInfos().size());
+ assertEquals(0, getWarnings().size());
+ assertEquals(0, getErrors().size());
+ return this;
+ }
+
+ public TestDiagnosticMessages assertOnlyInfos() {
+ assertNotEquals(0, getInfos().size());
+ assertEquals(0, getWarnings().size());
+ assertEquals(0, getErrors().size());
+ return this;
+ }
+
+ public TestDiagnosticMessages assertOnlyWarnings() {
+ assertEquals(0, getInfos().size());
+ assertNotEquals(0, getWarnings().size());
+ assertEquals(0, getErrors().size());
+ return this;
+ }
+
+ public TestDiagnosticMessages assertInfosCount(int count) {
+ assertEquals(count, getInfos().size());
+ return this;
+ }
+
+ public TestDiagnosticMessages assertWarningsCount(int count) {
+ assertEquals(count, getWarnings().size());
+ return this;
+ }
+
+ public TestDiagnosticMessages assertErrorsCount(int count) {
+ assertEquals(count, getErrors().size());
+ return this;
+ }
+
+ public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher) {
+ assertNotEquals(0, getWarnings().size());
+ for (int i = 0; i < getWarnings().size(); i++) {
+ if (matcher.matches(getWarnings().get(i).getDiagnosticMessage())) {
+ return this;
+ }
+ }
+ StringBuilder builder = new StringBuilder("No warning matches " + matcher.toString());
+ builder.append(System.lineSeparator());
+ if (getWarnings().size() == 0) {
+ builder.append("There where no warnings.");
+ } else {
+ builder.append("There where " + getWarnings().size() + " warnings:");
+ builder.append(System.lineSeparator());
+ for (int i = 0; i < getWarnings().size(); i++) {
+ builder.append(getWarnings().get(i).getDiagnosticMessage());
+ builder.append(System.lineSeparator());
+ }
+ }
+ fail(builder.toString());
+ return this;
+ }
+
+ public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher) {
+ assertNotEquals(0, getWarnings().size());
+ for (int i = 0; i < getWarnings().size(); i++) {
+ String message = getWarnings().get(i).getDiagnosticMessage();
+ if (matcher.matches(message)) {
+ fail("The warning: \"" + message + "\" + matches " + matcher + ".");
+ }
+ }
+ return this;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index c9fe628..bab857c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.isDexFile;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -57,6 +58,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -95,7 +97,8 @@
public static final String SMALI_BUILD_DIR = TESTS_BUILD_DIR + "smali/";
public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
- public static final String PATH_SEPARATOR = File.pathSeparator;
+ public static final String CLASSPATH_SEPARATOR = File.pathSeparator;
+
public static final String DEFAULT_DEX_FILENAME = "classes.dex";
public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
@@ -856,6 +859,28 @@
Paths.get("", parts.toArray(new String[parts.size() - 1])));
}
+ public static Collection<Path> getClassFilesForInnerClasses(Path path) throws IOException {
+ Set<Path> paths = new HashSet<>();
+ String prefix = path.toString().replace(CLASS_EXTENSION, "$");
+ paths.addAll(
+ ToolHelper.getClassFilesForTestDirectory(
+ path.getParent(), p -> p.toString().startsWith(prefix)));
+ return paths;
+ }
+
+ public static Collection<Path> getClassFilesForInnerClasses(Collection<Class<?>> classes)
+ throws IOException {
+ Set<Path> paths = new HashSet<>();
+ for (Class clazz : classes) {
+ Path path = ToolHelper.getClassFileForTestClass(clazz);
+ String prefix = path.toString().replace(CLASS_EXTENSION, "$");
+ paths.addAll(
+ ToolHelper.getClassFilesForTestDirectory(
+ path.getParent(), p -> p.toString().startsWith(prefix)));
+ }
+ return paths;
+ }
+
public static Path getFileNameForTestClass(Class clazz) {
List<String> parts = getNamePartsForTestClass(clazz);
return Paths.get("", parts.toArray(new String[parts.size() - 1]));
@@ -1062,7 +1087,8 @@
}
public static ProcessResult runJava(List<Path> classpath, String... args) throws IOException {
- String cp = classpath.stream().map(Path::toString).collect(Collectors.joining(PATH_SEPARATOR));
+ String cp =
+ classpath.stream().map(Path::toString).collect(Collectors.joining(CLASSPATH_SEPARATOR));
List<String> cmdline = new ArrayList<String>(Arrays.asList(getJavaExecutable(), "-cp", cp));
cmdline.addAll(Arrays.asList(args));
ProcessBuilder builder = new ProcessBuilder(cmdline);
@@ -1082,7 +1108,8 @@
public static ProcessResult runJavaNoVerify(
List<Path> classpath, String mainClass, List<String> args) throws IOException {
- String cp = classpath.stream().map(Path::toString).collect(Collectors.joining(PATH_SEPARATOR));
+ String cp =
+ classpath.stream().map(Path::toString).collect(Collectors.joining(CLASSPATH_SEPARATOR));
ArrayList<String> cmdline = Lists.newArrayList(
getJavaExecutable(), "-cp", cp, "-noverify", mainClass);
cmdline.addAll(args);
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestLimitRange.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestLimitRange.java
new file mode 100644
index 0000000..5e97ab0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestLimitRange.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2019, 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.cf;
+
+import com.android.tools.r8.NeverInline;
+
+public class TryRangeTestLimitRange {
+
+ @NeverInline
+ public static float doSomething(int x) throws Exception {
+ if (x == 42) {
+ throw new Exception("is 42");
+ } else {
+ return 1;
+ }
+ }
+
+ public static void main(String[] args) {
+ int x = args.length;
+ int y = x + 1;
+ try {
+ doSomething(y);
+ } catch (Exception ex) {
+ System.out.println(x + ": " + y);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
index 98542ff..e4c7098 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -4,8 +4,14 @@
package com.android.tools.r8.cf;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import java.util.ListIterator;
import org.junit.Test;
/**
@@ -18,7 +24,7 @@
public class TryRangeTestRunner extends TestBase {
@Test
- public void test() throws Exception {
+ public void testRegisterAllocationLimitTrailingRange() throws Exception {
testForR8(Backend.CF)
.addProgramClasses(TryRangeTest.class)
.addKeepMainRule(TryRangeTest.class)
@@ -26,8 +32,55 @@
.minification(false)
.noTreeShaking()
.enableInliningAnnotations()
- .addOptionsModification(o -> o.testing.disallowLoadStoreOptimization = true)
+ .addOptionsModification(
+ o -> {
+ o.testing.disallowLoadStoreOptimization = true;
+ })
.run(TryRangeTest.class)
.assertSuccess();
}
+
+ @Test
+ public void testRegisterAllocationLimitLeadingRange() throws Exception {
+ testForR8(Backend.CF)
+ .addProgramClasses(TryRangeTestLimitRange.class)
+ .addKeepMainRule(TryRangeTestLimitRange.class)
+ .setMode(CompilationMode.RELEASE)
+ .minification(false)
+ .noTreeShaking()
+ .enableInliningAnnotations()
+ .addOptionsModification(
+ o -> {
+ o.testing.disallowLoadStoreOptimization = true;
+ o.testing.irModifier = this::processIR;
+ // TODO(mkroghj) Remove this option entirely when splittingExceptionalEdges is moved.
+ o.testing.noSplittingExceptionalEdges = true;
+ })
+ .run(TryRangeTestLimitRange.class)
+ .assertFailure();
+ }
+
+ private void processIR(IRCode code) {
+ if (!code.method.qualifiedName().equals(TryRangeTestLimitRange.class.getName() + ".main")) {
+ return;
+ }
+ BasicBlock entryBlock = code.blocks.get(0);
+ BasicBlock tryBlock = code.blocks.get(1);
+ assertTrue(tryBlock.hasCatchHandlers());
+ ListIterator<Instruction> it = entryBlock.getInstructions().listIterator();
+ Instruction constNumber = it.next();
+ while (!constNumber.isConstNumber()) {
+ constNumber = it.next();
+ }
+ it.remove();
+ Instruction add = it.next();
+ while (!add.isAdd()) {
+ add = it.next();
+ }
+ it.remove();
+ constNumber.setBlock(tryBlock);
+ add.setBlock(tryBlock);
+ tryBlock.getInstructions().add(0, add);
+ tryBlock.getInstructions().add(0, constNumber);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CannonicalizeWithInline.java b/src/test/java/com/android/tools/r8/debuginfo/CannonicalizeWithInline.java
new file mode 100644
index 0000000..5d6e151
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/CannonicalizeWithInline.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2019, 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.debuginfo;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dex.DexParser;
+import com.android.tools.r8.dex.DexSection;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CannonicalizeWithInline extends TestBase {
+
+ private int getNumberOfDebugInfos(Path file) throws IOException {
+ DexSection[] dexSections = DexParser.parseMapFrom(file);
+ for (DexSection dexSection : dexSections) {
+ if (dexSection.type == Constants.TYPE_DEBUG_INFO_ITEM) {
+ return dexSection.length;
+ }
+ }
+ return 0;
+ }
+
+ @Test
+ public void testCannonicalize() throws Exception {
+ Class clazzA = ClassA.class;
+ Class clazzB = ClassB.class;
+
+ R8TestCompileResult result = testForR8(Backend.DEX)
+ .addProgramClasses(clazzA, clazzB)
+ .addKeepRules(
+ "-keepattributes SourceFile,LineNumberTable",
+ "-keep class ** {\n" +
+ "public void call(int);\n" +
+ "}"
+ )
+ .compile();
+ Path classesPath = temp.getRoot().toPath();
+ result.app.write(classesPath, OutputMode.DexIndexed);
+ int numberOfDebugInfos = getNumberOfDebugInfos(
+ Paths.get(temp.getRoot().getCanonicalPath(), "classes.dex"));
+ Assert.assertEquals(1, numberOfDebugInfos);
+ }
+
+ // Two classes which has debug info that looks exactly the same, except for SetInlineFrame.
+ // R8 will inline the call to foobar in both classes, causing us to store a SetInlineFrame in the
+ // debug info.
+ // Ensure that we still canonicalize when writing.
+ public static class ClassA {
+
+ public void call(int a) {
+ foobar(a);
+ }
+
+ private String foobar(int a) {
+ String s = "aFoobar" + a;
+ return s;
+ }
+ }
+
+ public static class ClassB {
+
+ public void call(int a) {
+ foobar(a);
+ }
+
+ private String foobar(int a) {
+ String s = "bFoobar" + a;
+ return s;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
index 97047e8..8af2f8e 100644
--- a/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar;
+import static com.android.tools.r8.ToolHelper.CLASSPATH_SEPARATOR;
+
import com.android.tools.r8.D8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.errors.CompilationError;
@@ -30,8 +32,6 @@
@RunWith(Parameterized.class)
public class BasicTestDependenciesDesugaringTest {
- private static final String CLASSPATH_SEPARATOR = File.pathSeparator;
-
private static final String[] allLibs;
static {
try {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index df797ad..7b3ed4a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -29,11 +29,14 @@
import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictField;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictMethod;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOk;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOkFieldOnly;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOkSideEffects;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictField;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictMethod;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOk;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOkFieldOnly;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOkSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.MoveToHostFieldOnlyTestClass;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.MoveToHostTestClass;
import com.android.tools.r8.ir.optimize.staticizer.trivial.Simple;
import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithGetter;
@@ -150,6 +153,36 @@
}
@Test
+ public void testMoveToHost_fieldOnly() throws Exception {
+ assumeTrue("b/112831361", backend == Backend.DEX);
+ Class<?> main = MoveToHostFieldOnlyTestClass.class;
+ Class<?>[] classes = {
+ NeverInline.class,
+ MoveToHostFieldOnlyTestClass.class,
+ HostOkFieldOnly.class,
+ CandidateOkFieldOnly.class
+ };
+ TestRunResult result = testForR8(backend)
+ .addProgramClasses(classes)
+ .enableProguardTestOptions()
+ .enableInliningAnnotations()
+ .addKeepMainRule(main)
+ .noMinification()
+ .addKeepRules("-allowaccessmodification")
+ .addOptionsModification(this::configure)
+ .run(main);
+
+ CodeInspector inspector = result.inspector();
+ ClassSubject clazz = inspector.clazz(main);
+
+ assertEquals(
+ Lists.newArrayList(),
+ references(clazz, "testOk_fieldOnly", "void"));
+
+ assertFalse(inspector.clazz(CandidateOkFieldOnly.class).isPresent());
+ }
+
+ @Test
public void testMoveToHost() throws Exception {
assumeTrue("b/112831361", backend == Backend.DEX);
Class<?> main = MoveToHostTestClass.class;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkFieldOnly.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkFieldOnly.java
new file mode 100644
index 0000000..48963f4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkFieldOnly.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class CandidateOkFieldOnly {
+ // No instance methods.
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkFieldOnly.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkFieldOnly.java
new file mode 100644
index 0000000..e132627
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkFieldOnly.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class HostOkFieldOnly {
+ static CandidateOkFieldOnly INSTANCE = new CandidateOkFieldOnly();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostFieldOnlyTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostFieldOnlyTestClass.java
new file mode 100644
index 0000000..f725d21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostFieldOnlyTestClass.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+import com.android.tools.r8.NeverInline;
+
+public class MoveToHostFieldOnlyTestClass {
+
+ public static void main(String[] args) {
+ MoveToHostFieldOnlyTestClass test = new MoveToHostFieldOnlyTestClass();
+ test.testOk_fieldOnly();
+ }
+
+ @NeverInline
+ private void testOk_fieldOnly() {
+ // Any instance method call whose target holder is not the candidate will invalidate candidacy,
+ // for example, toString() without overriding, getClass(), etc.
+ // Note that having instance methods in the candidate class guarantees that method mappings will
+ // exist when field mappings do so.
+ // Any other uses other than invoke-virtual or invoke-direct (to either <init> or private) are
+ // not allowed, e.g., System.out.println(INSTANCE), null check, or static-put to somewhere else.
+ // Therefore, it's merely dead code, and thus it has not been harmful to forget to create a
+ // staticizer lense when there is no method mapping (for instance methods to staticized ones)
+ // while there are field mappings as shown in this example.
+ Object x = HostOkFieldOnly.INSTANCE;
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
new file mode 100644
index 0000000..767e818
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2019, 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.kotlin;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ProcessKotlinReflectionLibTest extends KotlinTestBase {
+ private final Backend backend;
+
+ public ProcessKotlinReflectionLibTest(Backend backend, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.backend = backend;
+ }
+
+ @Parameterized.Parameters(name = "Backend: {0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(Backend.values(), KotlinTargetVersion.values());
+ }
+
+ @Test
+ public void testDontShrinkAndDontObfuscate() throws Exception {
+ testForR8(backend)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .addKeepRules("-dontshrink")
+ .addKeepRules("-dontobfuscate")
+ .compile();
+ }
+
+ @Test
+ public void testDontShrink() throws Exception {
+ testForR8(backend)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .addKeepRules("-dontshrink")
+ .compile();
+ }
+
+ @Test
+ public void testDontObfuscate() throws Exception {
+ testForR8(backend)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(ToolHelper.getKotlinReflectJar())
+ .addKeepRules("-dontobfuscate")
+ .compile();
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
new file mode 100644
index 0000000..44047d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2019, 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.kotlin;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ProcessKotlinStdlibTest extends KotlinTestBase {
+ private final Backend backend;
+
+ public ProcessKotlinStdlibTest(Backend backend, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.backend = backend;
+ }
+
+ @Parameterized.Parameters(name = "Backend: {0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(Backend.values(), KotlinTargetVersion.values());
+ }
+
+ @Test
+ public void testDontShrinkAndDontObfuscate() throws Exception {
+ testForR8(backend)
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepRules("-dontshrink")
+ .addKeepRules("-dontobfuscate")
+ .compile();
+ }
+
+ @Test
+ public void testDontShrink() throws Exception {
+ // TODO(b/122819537)
+ if (backend == Backend.DEX) {
+ return;
+ }
+ testForR8(backend)
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepRules("-dontshrink")
+ .compile();
+ }
+
+ @Test
+ public void testDontObfuscate() throws Exception {
+ testForR8(backend)
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepRules("-dontobfuscate")
+ .compile();
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
index 1038f5c..b175785 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
@@ -35,6 +35,7 @@
import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -75,6 +76,7 @@
out = temp.newFolder("out").toPath();
}
+ @Ignore("b/121305642")
@Test
public void test044_obfuscate_and_apply() throws Exception {
// keep rules that allow obfuscations while keeping everything.
@@ -110,7 +112,7 @@
AndroidApp instrApp =
runR8(
ToolHelper.addProguardConfigurationConsumer(
- getCommandForInstrumentation(instrOut, flag, NAMING044_JAR, APPLYMAPPING044_JAR)
+ getCommandForInstrumentation(instrOut, flag, out, APPLYMAPPING044_JAR)
.setDisableMinification(true),
pgConfig -> pgConfig.setApplyMappingFile(proguardMap))
.build());
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
index 2258d67..f7b95f9 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
@@ -56,6 +56,10 @@
.assertFailure();
// Extract actual stack trace and retraced stack trace from failed run result.
+ // TODO(122940268): Remove test code when fixed.
+ System.out.println("<--- TEST RESULT START --->");
+ System.out.println(result);
+ System.out.println("<--- TEST RESULT END --->");
StackTrace actualStackTrace = StackTrace.extractFromArt(result.getStdErr());
StackTrace retracedStackTrace =
actualStackTrace.retrace(result.proguardMap(), temp.newFolder().toPath());
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 0ecd3e4..dd8bc9c 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -165,9 +165,27 @@
}
// Take all lines from the bottom starting with "\tat ".
int first = last;
+ // TODO(122940268): Remove test code when fixed.
+ System.out.println("TOTAL STDERR LINES: " + stderrLines.size());
+ for (int i = 0; i < last; i++) {
+ System.out.print("LINE " + i + ": " + stderrLines.get(i));
+ if (stderrLines.get(i).length() > 3) {
+ System.out.print(" (" + ((int) stderrLines.get(i).charAt(0)));
+ System.out.print(", " + ((int) stderrLines.get(i).charAt(1)));
+ System.out.print(", " + ((int) stderrLines.get(i).charAt(2) + ")"));
+ } else {
+ System.out.print(" (less than three chars)");
+ }
+ if (stderrLines.get(i).startsWith(TAB_AT_PREFIX)) {
+ System.out.println(" IS STACKTRACE LINE");
+ } else {
+ System.out.println(" IS NOT STACKTRACE LINE");
+ }
+ }
while (first - 1 >= 0 && stderrLines.get(first - 1).startsWith(TAB_AT_PREFIX)) {
first--;
}
+ System.out.println("STACKTRACE LINES ARE " + first + " to " + (last - 1));
for (int i = first; i < last; i++) {
stackTraceLines.add(StackTraceLine.parse(stderrLines.get(i)));
}
diff --git a/src/test/java/com/android/tools/r8/regress/b122887884/Regress122887884.java b/src/test/java/com/android/tools/r8/regress/b122887884/Regress122887884.java
new file mode 100644
index 0000000..23650c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b122887884/Regress122887884.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2019, 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.regress.b122887884;
+
+public class Regress122887884 {
+
+ public static int[] foo(String[] args) {
+ if (args.length == 0) {
+ return new int[] {0, 0};
+ }
+ String first = args[0];
+ try {
+ String[] split = first.split("");
+ if (split.length != 2) {
+ // This results in a new-array v2 v2 int[] at which point the exception handler must split.
+ return new int[] {0, 0};
+ }
+ return new int[] {1, 1};
+ } catch (Throwable t) {
+ return new int[] {0, 0};
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo(args)[0]);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b122887884/Regress122887884Runner.java b/src/test/java/com/android/tools/r8/regress/b122887884/Regress122887884Runner.java
new file mode 100644
index 0000000..54f32fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b122887884/Regress122887884Runner.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2019, 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.regress.b122887884;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+public class Regress122887884Runner extends TestBase {
+
+ private final Class<?> CLASS = Regress122887884.class;
+ private final String EXPECTED = StringUtils.lines("0");
+
+ @Test
+ public void test() throws Exception {
+ testForD8().addProgramClasses(CLASS).run(CLASS).assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index ff84486..db5f631 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1113,6 +1113,39 @@
}
@Test
+ public void parseInvalidKeepOption() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-keepx public class * { ",
+ " native <methods>; ",
+ "} "
+ );
+ try {
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ fail();
+ } catch (AbortException e) {
+ checkDiagnostics(handler.errors, proguardConfig, 1, 1,
+ "Unknown option", "-keepx");
+ }
+ }
+
+ @Test
+ public void parseKeepOptionEOF() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ System.lineSeparator(), ImmutableList.of("-keep"), false);
+ try {
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ fail();
+ } catch (AbortException e) {
+ checkDiagnostics(handler.errors, proguardConfig, 1, 6,
+ "Expected [!]interface|@interface|class|enum");
+ }
+ }
+
+ @Test
public void parseInvalidKeepClassOption() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-keepclassx public class * { ",
@@ -1493,7 +1526,7 @@
parser.parse(createConfigurationForTesting(ImmutableList.of(option + " class A { *; }")));
fail("Expect to fail due to testing option being turned off.");
} catch (AbortException e) {
- assertEquals(2, handler.errors.size());
+ assertEquals(1, handler.errors.size());
checkDiagnostics(handler.errors, 0, null, 1, 1, "Unknown option \"" + option + "\"");
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index d878a86..6c23162 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -202,8 +202,7 @@
return !code.asCfCode().getLocalVariables().isEmpty();
}
if (code.isJarCode()) {
- return code.asJarCode().getNode().localVariables != null
- && !code.asJarCode().getNode().localVariables.isEmpty();
+ return code.asJarCode().hasLocalVariableTable();
}
throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
}
diff --git a/tools/apk-masseur.py b/tools/apk_masseur.py
similarity index 85%
rename from tools/apk-masseur.py
rename to tools/apk_masseur.py
index 4f24f0f..6013e6f 100755
--- a/tools/apk-masseur.py
+++ b/tools/apk_masseur.py
@@ -36,10 +36,6 @@
if len(args) != 1:
parser.error('Expected <apk> argument, got: ' + ' '.join(args))
apk = args[0]
- if not options.out:
- options.out = os.path.basename(apk)
- if not options.keystore:
- options.keystore = findKeystore()
return (options, apk)
def findKeystore():
@@ -81,28 +77,36 @@
subprocess.check_call(cmd)
return signed_apk
-def main():
- (options, apk) = parse_options()
+def masseur(
+ apk, dex=None, out=None, adb_options=None, keystore=None, install=False):
+ if not out:
+ out = os.path.basename(apk)
+ if not keystore:
+ keystore = findKeystore()
with utils.TempDir() as temp:
processed_apk = None
- if options.dex:
- processed_apk = repack(options.dex, apk, temp)
+ if dex:
+ processed_apk = repack(dex, apk, temp)
else:
print 'Signing original APK without modifying dex files'
processed_apk = os.path.join(temp, 'processed.apk')
shutil.copyfile(apk, processed_apk)
- signed_apk = sign(processed_apk, options.keystore, temp)
+ signed_apk = sign(processed_apk, keystore, temp)
aligned_apk = align(signed_apk, temp)
- print 'Writing result to', options.out
- shutil.copyfile(aligned_apk, options.out)
+ print 'Writing result to', out
+ shutil.copyfile(aligned_apk, out)
adb_cmd = ['adb']
- if options.adb_options:
+ if adb_options:
adb_cmd.extend(
- [option for option in options.adb_options.split(' ') if option])
- if options.install:
- adb_cmd.extend(['install', '-t', '-r', '-d', options.out]);
+ [option for option in adb_options.split(' ') if option])
+ if install:
+ adb_cmd.extend(['install', '-t', '-r', '-d', out]);
utils.PrintCmd(adb_cmd)
subprocess.check_call(adb_cmd)
+
+def main():
+ (options, apk) = parse_options()
+ masseur(apk, **vars(options))
return 0
if __name__ == '__main__':
diff --git a/tools/as_utils.py b/tools/as_utils.py
index d5db8a3..65ab470 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -4,6 +4,7 @@
# BSD-style license that can be found in the LICENSE file.
from distutils.version import LooseVersion
+from HTMLParser import HTMLParser
import os
import shutil
@@ -56,7 +57,8 @@
def remove_r8_dependency(checkout_dir):
build_file = os.path.join(checkout_dir, 'build.gradle')
- assert os.path.isfile(build_file), 'Expected a file to be present at {}'.format(build_file)
+ assert os.path.isfile(build_file), (
+ 'Expected a file to be present at {}'.format(build_file))
with open(build_file) as f:
lines = f.readlines()
with open(build_file, 'w') as f:
@@ -64,6 +66,54 @@
if (utils.R8_JAR not in line) and (utils.R8LIB_JAR not in line):
f.write(line)
+def IsGradleTaskName(x):
+ # Check that it is non-empty.
+ if not x:
+ return False
+ # Check that there is no whitespace.
+ for c in x:
+ if c.isspace():
+ return False
+ # Check that the first character following an optional ':' is a lower-case
+ # alphabetic character.
+ c = x[0]
+ if c == ':' and len(x) >= 2:
+ c = x[1]
+ return c.isalpha() and c.islower()
+
+def IsGradleCompilerTask(x, shrinker):
+ if 'r8' in shrinker:
+ assert 'transformClassesWithDexBuilderFor' not in x
+ assert 'transformDexArchiveWithDexMergerFor' not in x
+ return 'transformClassesAndResourcesWithR8For' in x
+
+ assert shrinker == 'proguard'
+ return ('transformClassesAndResourcesWithProguard' in x
+ or 'transformClassesWithDexBuilderFor' in x
+ or 'transformDexArchiveWithDexMergerFor' in x)
+
+def SetPrintConfigurationDirective(app, config, checkout_dir, destination):
+ proguard_config_file = FindProguardConfigurationFile(
+ app, config, checkout_dir)
+ with open(proguard_config_file) as f:
+ lines = f.readlines()
+ with open(proguard_config_file, 'w') as f:
+ for line in lines:
+ if '-printconfiguration' not in line:
+ f.write(line)
+ f.write('-printconfiguration {}\n'.format(destination))
+
+def FindProguardConfigurationFile(app, config, checkout_dir):
+ app_module = config.get('app_module', 'app')
+ candidates = ['proguard-rules.pro', 'proguard-rules.txt', 'proguard.cfg']
+ for candidate in candidates:
+ proguard_config_file = os.path.join(checkout_dir, app_module, candidate)
+ if os.path.isfile(proguard_config_file):
+ return proguard_config_file
+ # Currently assuming that the Proguard configuration file can be found at
+ # one of the predefined locations.
+ assert False
+
def Move(src, dst):
print('Moving `{}` to `{}`'.format(src, dst))
dst_parent = os.path.dirname(dst)
@@ -103,3 +153,60 @@
html_dir = os.path.dirname(html_file)
for dir_name in ['css', 'js']:
MoveDir(os.path.join(html_dir, dir_name), os.path.join(dest_dir, dir_name))
+
+def ParseProfileReport(profile_dir):
+ html_file = os.path.join(profile_dir, 'index.html')
+ assert os.path.isfile(html_file)
+
+ parser = ProfileReportParser()
+ with open(html_file) as f:
+ for line in f.readlines():
+ parser.feed(line)
+ return parser.result
+
+# A simple HTML parser that recognizes the following pattern:
+#
+# <tr>
+# <td class="indentPath">:app:transformClassesAndResourcesWithR8ForRelease</td>
+# <td class="numeric">3.490s</td>
+# <td></td>
+# </tr>
+class ProfileReportParser(HTMLParser):
+ entered_table_row = False
+ entered_task_name_cell = False
+ entered_duration_cell = False
+
+ current_task_name = None
+ current_duration = None
+
+ result = {}
+
+ def handle_starttag(self, tag, attrs):
+ entered_table_row_before = self.entered_table_row
+ entered_task_name_cell_before = self.entered_task_name_cell
+
+ self.entered_table_row = (tag == 'tr')
+ self.entered_task_name_cell = (tag == 'td' and entered_table_row_before)
+ self.entered_duration_cell = (
+ self.current_task_name
+ and tag == 'td'
+ and entered_task_name_cell_before)
+
+ def handle_endtag(self, tag):
+ if tag == 'tr':
+ if self.current_task_name and self.current_duration:
+ self.result[self.current_task_name] = self.current_duration
+ self.current_task_name = None
+ self.current_duration = None
+ self.entered_table_row = False
+
+ def handle_data(self, data):
+ stripped = data.strip()
+ if not stripped:
+ return
+ if self.entered_task_name_cell:
+ if IsGradleTaskName(stripped):
+ self.current_task_name = stripped
+ elif self.entered_duration_cell and stripped.endswith('s'):
+ self.current_duration = float(stripped[:-1])
+ self.entered_table_row = False
diff --git a/tools/dex2oat.py b/tools/dex2oat.py
index 1e2cffd..e21dc7d 100755
--- a/tools/dex2oat.py
+++ b/tools/dex2oat.py
@@ -14,6 +14,8 @@
VERSIONS = [
'default',
+ '9.0.0',
+ '8.1.0',
'7.0.0',
'6.0.1',
'5.1.1',
@@ -21,6 +23,8 @@
DIRS = {
'default': 'art',
+ '9.0.0': 'art-9.0.0',
+ '8.1.0': 'art-8.1.0',
'7.0.0': 'art-7.0.0',
'6.0.1': 'art-6.0.1',
'5.1.1': 'art-5.1.1',
@@ -28,6 +32,8 @@
PRODUCTS = {
'default': 'angler',
+ '9.0.0': 'marlin',
+ '8.1.0': 'marlin',
'7.0.0': 'angler',
'6.0.1': 'angler',
'5.1.1': 'mako',
@@ -35,11 +41,23 @@
ARCHS = {
'default': 'arm64',
+ '9.0.0': 'arm64',
+ '8.1.0': 'arm64',
'7.0.0': 'arm64',
'6.0.1': 'arm64',
'5.1.1': 'arm',
}
+VERBOSE_OPTIONS = [
+ 'verifier',
+ 'compiler',
+ 'gc',
+ 'jit',
+ 'jni',
+ 'class',
+ 'all',
+]
+
def ParseOptions():
parser = optparse.OptionParser()
parser.add_option('--version',
@@ -53,6 +71,10 @@
parser.add_option('--output',
help='Where to place the output oat (defaults to no output / temp file).',
default=None)
+ parser.add_option('--verbose',
+ help='Enable verbose dex2oat logging.',
+ choices=VERBOSE_OPTIONS,
+ default=None)
return parser.parse_args()
def Main():
@@ -66,12 +88,15 @@
dexfile = args[0]
oatfile = options.output
versions = VERSIONS if options.all else [options.version]
+ verbose = [options.verbose] if options.verbose else []
+ if 'all' in verbose:
+ verbose = [x for x in VERBOSE_OPTIONS if x is not 'all']
for version in versions:
- run(dexfile, oatfile, version)
+ run(dexfile, oatfile, version, verbose)
print
return 0
-def run(dexfile, oatfile=None, version='default'):
+def run(dexfile, oatfile=None, version='default', verbose=[]):
# dex2oat accepts non-existent dex files, check here instead
if not os.path.exists(dexfile):
raise Exception('DEX file not found: "{}"'.format(dexfile))
@@ -90,6 +115,8 @@
'--oat-file=' + oatfile,
'--instruction-set=' + arch,
]
+ for flag in verbose:
+ cmd += ['--runtime-arg', '-verbose:' + flag]
env = {"LD_LIBRARY_PATH": os.path.join(base, 'lib')}
utils.PrintCmd(cmd)
subprocess.check_call(cmd, env = env)
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 26907a0..770a651 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -3,19 +3,21 @@
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
+import apk_masseur
import apk_utils
import gradle
import os
import optparse
import subprocess
import sys
+import tempfile
import time
import utils
import zipfile
import as_utils
-SHRINKERS = ['r8', 'r8full', 'r8-minified', 'r8full-minified', 'proguard']
+SHRINKERS = ['r8', 'r8-minified', 'r8full', 'r8full-minified', 'proguard']
WORKING_DIR = utils.BUILD
if 'R8_BENCHMARK_DIR' in os.environ and os.path.isdir(os.environ['R8_BENCHMARK_DIR']):
@@ -128,6 +130,31 @@
subprocess.check_call(
['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest])
+def PercentageDiffAsString(before, after):
+ if after < before:
+ return '-' + str(round((1.0 - after / before) * 100)) + '%'
+ else:
+ return '+' + str(round((after - before) / before * 100)) + '%'
+
+def UninstallApkOnEmulator(app, config):
+ app_id = config.get('app_id')
+ process = subprocess.Popen(
+ ['adb', 'uninstall', app_id],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = process.communicate()
+
+ if stdout.strip() == 'Success':
+ # Successfully uninstalled
+ return
+
+ if 'Unknown package: {}'.format(app_id) in stderr:
+ # Application not installed
+ return
+
+ raise Exception(
+ 'Unexpected result from `adb uninstall {}\nStdout: {}\nStderr: {}'.format(
+ app_id, stdout, stderr))
+
def WaitForEmulator():
stdout = subprocess.check_output(['adb', 'devices'])
if '{}\tdevice'.format(emulator_id) in stdout:
@@ -190,24 +217,54 @@
apk_dest = None
result = {}
try:
- (apk_dest, profile_dest_dir) = BuildAppWithShrinker(
- app, config, shrinker, checkout_dir, options)
+ (apk_dest, profile_dest_dir, proguard_config_file) = \
+ BuildAppWithShrinker(app, config, shrinker, checkout_dir, options)
dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
result['apk_dest'] = apk_dest,
result['build_status'] = 'success'
result['dex_size'] = dex_size
result['profile_dest_dir'] = profile_dest_dir
+
+ profile = as_utils.ParseProfileReport(profile_dest_dir)
+ result['profile'] = {
+ task_name:duration for task_name, duration in profile.iteritems()
+ if as_utils.IsGradleCompilerTask(task_name, shrinker)}
except Exception as e:
warn('Failed to build {} with {}'.format(app, shrinker))
if e:
print('Error: ' + str(e))
result['build_status'] = 'failed'
- if options.monkey:
- if result.get('build_status') == 'success':
+ if result.get('build_status') == 'success':
+ if options.monkey:
result['monkey_status'] = 'success' if RunMonkey(
app, config, options, apk_dest) else 'failed'
+ if 'r8' in shrinker and options.r8_compilation_steps > 1:
+ recompilation_results = []
+ previous_apk = apk_dest
+ for i in range(1, options.r8_compilation_steps):
+ try:
+ recompiled_apk_dest = os.path.join(
+ checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
+ RebuildAppWithShrinker(
+ previous_apk, recompiled_apk_dest, proguard_config_file, shrinker)
+ recompilation_result = {
+ 'apk_dest': recompiled_apk_dest,
+ 'build_status': 'success',
+ 'dex_size': ComputeSizeOfDexFilesInApk(recompiled_apk_dest)
+ }
+ if options.monkey:
+ recompilation_result['monkey_status'] = 'success' if RunMonkey(
+ app, config, options, recompiled_apk_dest) else 'failed'
+ recompilation_results.append(recompilation_result)
+ previous_apk = recompiled_apk_dest
+ except Exception as e:
+ warn('Failed to recompile {} with {}'.format(app, shrinker))
+ recompilation_results.append({ 'build_status': 'failed' })
+ break
+ result['recompilation_results'] = recompilation_results
+
result_per_shrinker[shrinker] = result
if IsTrackedByGit('gradle.properties'):
@@ -216,8 +273,10 @@
return result_per_shrinker
def BuildAppWithShrinker(app, config, shrinker, checkout_dir, options):
+ print()
print('Building {} with {}'.format(app, shrinker))
+ # Add/remove 'r8.jar' from top-level build.gradle.
if options.disable_tot:
as_utils.remove_r8_dependency(checkout_dir)
else:
@@ -227,7 +286,7 @@
archives_base_name = config.get(' archives_base_name', app_module)
flavor = config.get('flavor')
- # Ensure that gradle.properties are not modified before modifying it to
+ # Ensure that gradle.properties is not modified before modifying it to
# select shrinker.
if IsTrackedByGit('gradle.properties'):
GitCheckout('gradle.properties')
@@ -244,6 +303,12 @@
if not os.path.exists(out):
os.makedirs(out)
+ # Set -printconfiguration in Proguard rules.
+ proguard_config_dest = os.path.abspath(
+ os.path.join(out, 'proguard-rules.pro'))
+ as_utils.SetPrintConfigurationDirective(
+ app, config, checkout_dir, proguard_config_dest)
+
env = os.environ.copy()
env['ANDROID_HOME'] = android_home
env['JAVA_OPTS'] = '-ea'
@@ -307,12 +372,33 @@
profile_dest_dir = os.path.join(out, 'profile')
as_utils.MoveProfileReportTo(profile_dest_dir, stdout)
- return (apk_dest, profile_dest_dir)
+ return (apk_dest, profile_dest_dir, proguard_config_dest)
+
+def RebuildAppWithShrinker(apk, apk_dest, proguard_config_file, shrinker):
+ assert 'r8' in shrinker
+ assert apk_dest.endswith('.apk')
+
+ # Compile given APK with shrinker to temporary zip file.
+ api = 28 # TODO(christofferqa): Should be the one from build.gradle
+ android_jar = os.path.join(utils.REPO_ROOT, utils.ANDROID_JAR.format(api=api))
+ r8_jar = utils.R8LIB_JAR if IsMinifiedR8(shrinker) else utils.R8_JAR
+ zip_dest = apk_dest[:-3] + '.zip'
+
+ cmd = ['java', '-ea', '-jar', r8_jar, '--release', '--pg-conf',
+ proguard_config_file, '--lib', android_jar, '--output', zip_dest, apk]
+ utils.PrintCmd(cmd)
+
+ subprocess.check_output(cmd)
+
+ # Make a copy of the given APK, move the newly generated dex files into the
+ # copied APK, and then sign the APK.
+ apk_masseur.masseur(apk, dex=zip_dest, out=apk_dest)
def RunMonkey(app, config, options, apk_dest):
if not WaitForEmulator():
return False
+ UninstallApkOnEmulator(app, config)
InstallApkOnEmulator(apk_dest)
app_id = config.get('app_id')
@@ -342,8 +428,10 @@
print(' skipped ({})'.format(error_message))
continue
- baseline = float(
- result_per_shrinker.get('proguard', {}).get('dex_size', -1))
+ proguard_result = result_per_shrinker.get('proguard', {})
+ proguard_dex_size = float(proguard_result.get('dex_size', -1))
+ proguard_duration = sum(proguard_result.get('profile', {}).values())
+
for shrinker in SHRINKERS:
if shrinker not in result_per_shrinker:
continue
@@ -354,23 +442,50 @@
else:
print(' {}:'.format(shrinker))
dex_size = result.get('dex_size')
- if dex_size != baseline and baseline >= 0:
- if dex_size < baseline:
- success(' dex size: {} ({}, -{}%)'.format(
- dex_size, dex_size - baseline,
- round((1.0 - dex_size / baseline) * 100), 1))
- elif dex_size >= baseline:
- warn(' dex size: {} ({}, +{}%)'.format(
- dex_size, dex_size - baseline,
- round((baseline - dex_size) / dex_size * 100, 1)))
+ msg = ' dex size: {}'.format(dex_size)
+ if dex_size != proguard_dex_size and proguard_dex_size >= 0:
+ msg = '{} ({}, {})'.format(
+ msg, dex_size - proguard_dex_size,
+ PercentageDiffAsString(proguard_dex_size, dex_size))
+ success(msg) if dex_size < proguard_dex_size else warn(msg)
else:
- print(' dex size: {}'.format(dex_size))
+ print(msg)
+
+ profile = result.get('profile')
+ duration = sum(profile.values())
+ msg = ' performance: {}s'.format(duration)
+ if duration != proguard_duration and proguard_duration > 0:
+ msg = '{} ({}s, {})'.format(
+ msg, duration - proguard_duration,
+ PercentageDiffAsString(proguard_duration, duration))
+ success(msg) if duration < proguard_duration else warn(msg)
+ else:
+ print(msg)
+ if len(profile) >= 2:
+ for task_name, task_duration in profile.iteritems():
+ print(' {}: {}s'.format(task_name, task_duration))
+
if options.monkey:
monkey_status = result.get('monkey_status')
if monkey_status != 'success':
warn(' monkey: {}'.format(monkey_status))
else:
success(' monkey: {}'.format(monkey_status))
+ recompilation_results = result.get('recompilation_results', [])
+ i = 1
+ for recompilation_result in recompilation_results:
+ build_status = recompilation_result.get('build_status')
+ if build_status != 'success':
+ print(' recompilation #{}: {}'.format(i, build_status))
+ else:
+ dex_size = recompilation_result.get('dex_size')
+ print(' recompilation #{}'.format(i))
+ print(' dex size: {}'.format(dex_size))
+ if options.monkey:
+ monkey_status = recompilation_result.get('monkey_status')
+ msg = ' monkey: {}'.format(monkey_status)
+ success(msg) if monkey_status == 'success' else warn(msg)
+ i += 1
def ParseOptions(argv):
result = optparse.OptionParser()
@@ -396,6 +511,10 @@
result.add_option('--shrinker',
help='The shrinkers to use (by default, all are run)',
action='append')
+ result.add_option('--r8-compilation-steps', '--r8_compilation_steps',
+ help='Number of times R8 should be run on each app',
+ default=2,
+ type=int)
result.add_option('--disable-tot', '--disable_tot',
help='Whether to disable the use of the ToT version of R8',
default=False,
@@ -405,6 +524,9 @@
default=False,
action='store_true')
(options, args) = result.parse_args(argv)
+ if options.disable_tot:
+ # r8.jar is required for recompiling the generated APK
+ options.r8_compilation_steps = 1
if options.shrinker:
for shrinker in options.shrinker:
assert shrinker in SHRINKERS