Merge "Keep annotations for targeted methods"
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 34a3a93..584b4de 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -25,6 +25,7 @@
private final CompilationMode mode;
private final ProgramConsumer programConsumer;
+ private final StringConsumer mainDexListConsumer;
private final int minApiLevel;
private final Reporter reporter;
private final boolean enableDesugaring;
@@ -33,6 +34,7 @@
BaseCompilerCommand(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
programConsumer = null;
+ mainDexListConsumer = null;
mode = null;
minApiLevel = 0;
reporter = new Reporter();
@@ -44,6 +46,7 @@
AndroidApp app,
CompilationMode mode,
ProgramConsumer programConsumer,
+ StringConsumer mainDexListConsumer,
int minApiLevel,
Reporter reporter,
boolean enableDesugaring,
@@ -53,6 +56,7 @@
assert mode != null;
this.mode = mode;
this.programConsumer = programConsumer;
+ this.mainDexListConsumer = mainDexListConsumer;
this.minApiLevel = minApiLevel;
this.reporter = reporter;
this.enableDesugaring = enableDesugaring;
@@ -81,6 +85,13 @@
return programConsumer;
}
+ /**
+ * Get the main dex list consumer that will receive the final complete main dex list.
+ */
+ public StringConsumer getMainDexListConsumer() {
+ return mainDexListConsumer;
+ }
+
/** Get the use-desugaring state. True if enabled, false otherwise. */
public boolean getEnableDesugaring() {
return enableDesugaring;
@@ -109,6 +120,7 @@
extends BaseCommand.Builder<C, B> {
private ProgramConsumer programConsumer = null;
+ private StringConsumer mainDexListConsumer = null;
private Path outputPath = null;
// TODO(b/70656566): Remove default output mode when deprecated API is removed.
private OutputMode outputMode = OutputMode.DexIndexed;
@@ -189,6 +201,13 @@
}
/**
+ * Get the main dex list consumer that will receive the final complete main dex list.
+ */
+ public StringConsumer getMainDexListConsumer() {
+ return mainDexListConsumer;
+ }
+
+ /**
* If set to true, legacy multidex partitioning will be optimized to reduce LinearAlloc usage
* during Dalvik DexOpt. Has no effect when compiling for a target with native multidex support
* or without main dex list specification.
@@ -224,6 +243,33 @@
}
/**
+ * Set an output destination to which main-dex-list content should be written.
+ *
+ * <p>This is a short-hand for setting a {@link StringConsumer.FileConsumer} using {@link
+ * #setMainDexListConsumer}. Note that any subsequent call to this method or {@link
+ * #setMainDexListConsumer} will override the previous setting.
+ *
+ * @param mainDexListOutputPath File-system path to write output at.
+ */
+ public B setMainDexListOutputPath(Path mainDexListOutputPath) {
+ mainDexListConsumer = new StringConsumer.FileConsumer(mainDexListOutputPath);
+ return self();
+ }
+
+ /**
+ * Set a consumer for receiving the main-dex-list content.
+ *
+ * <p>Note that any subsequent call to this method or {@link #setMainDexListOutputPath} will
+ * override the previous setting.
+ *
+ * @param mainDexListConsumer Consumer to receive the content once produced.
+ */
+ public B setMainDexListConsumer(StringConsumer mainDexListConsumer) {
+ this.mainDexListConsumer = mainDexListConsumer;
+ return self();
+ }
+
+ /**
* Set the output path-and-mode.
*
* <p>Setting the output path-and-mode will override any previous set consumer or any previous
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index e0f330d..f0217b9 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -137,6 +137,8 @@
if (getProgramConsumer() instanceof DexFilePerClassFileConsumer) {
reporter.error("Option --main-dex-list cannot be used with --file-per-class");
}
+ } else if (getMainDexListConsumer() != null) {
+ reporter.error("Option --main-dex-list-output require --main-dex-list");
}
super.validate();
}
@@ -153,6 +155,7 @@
getAppBuilder().build(),
getMode(),
getProgramConsumer(),
+ getMainDexListConsumer(),
getMinApiLevel(),
getReporter(),
!getDisableDesugaring(),
@@ -209,6 +212,7 @@
AndroidApp inputApp,
CompilationMode mode,
ProgramConsumer programConsumer,
+ StringConsumer mainDexListConsumer,
int minApiLevel,
Reporter diagnosticsHandler,
boolean enableDesugaring,
@@ -218,6 +222,7 @@
inputApp,
mode,
programConsumer,
+ mainDexListConsumer,
minApiLevel,
diagnosticsHandler,
enableDesugaring,
@@ -235,6 +240,7 @@
assert !internal.debug;
internal.debug = getMode() == CompilationMode.DEBUG;
internal.programConsumer = getProgramConsumer();
+ internal.mainDexListConsumer = getMainDexListConsumer();
internal.minimalMainDex = internal.debug;
internal.minApiLevel = getMinApiLevel();
internal.intermediate = intermediate;
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index a933b8f..b84b398 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -104,6 +104,7 @@
" --file-per-class # Produce a separate dex file per input class",
" --no-desugaring # Force disable desugaring.",
" --main-dex-list <file> # List of classes to place in the primary dex file.",
+ " --main-dex-list-output <file> # Output resulting main dex list in <file>.",
" --version # Print the version of d8.",
" --help # Print this message."));
@@ -198,6 +199,8 @@
}
} else if (arg.equals("--main-dex-list")) {
builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
+ } else if (arg.equals("--main-dex-list-output")) {
+ builder.setMainDexListOutputPath(Paths.get(expandedArgs[++i]));
} else if (arg.equals("--optimize-multidex-for-linearalloc")) {
builder.setOptimizeMultidexForLinearAlloc(true);
} else if (arg.equals("--min-api")) {
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 118df2e..e235e67 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -160,7 +160,7 @@
public void accept(
int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
super.accept(fileIndex, data, descriptors, handler);
- outputBuilder.addFile(getDexFileName(fileIndex), data, handler);
+ outputBuilder.addIndexedClassFile(fileIndex, getDexFileName(fileIndex), data, handler);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index a1858a9..338fb4f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -90,8 +90,6 @@
private boolean allowPartiallyImplementedProguardOptions = false;
private boolean allowTestProguardOptions = false;
- private StringConsumer mainDexListConsumer = null;
-
// TODO(zerny): Consider refactoring CompatProguardCommandBuilder to avoid subclassing.
Builder() {
this(new DefaultR8DiagnosticsHandler());
@@ -178,33 +176,6 @@
return self();
}
- /**
- * Set an output destination to which main-dex-list content should be written.
- *
- * <p>This is a short-hand for setting a {@link StringConsumer.FileConsumer} using {@link
- * #setMainDexListConsumer}. Note that any subsequent call to this method or {@link
- * #setMainDexListConsumer} will override the previous setting.
- *
- * @param mainDexListOutputPath File-system path to write output at.
- */
- public Builder setMainDexListOutputPath(Path mainDexListOutputPath) {
- mainDexListConsumer = new StringConsumer.FileConsumer(mainDexListOutputPath);
- return self();
- }
-
- /**
- * Set a consumer for receiving the main-dex-list content.
- *
- * <p>Note that any subsequent call to this method or {@link #setMainDexListOutputPath} will
- * override the previous setting.
- *
- * @param mainDexListConsumer Consumer to receive the content once produced.
- */
- public Builder setMainDexListConsumer(StringConsumer mainDexListConsumer) {
- this.mainDexListConsumer = mainDexListConsumer;
- return self();
- }
-
/** Add proguard configuration-file resources. */
public Builder addProguardConfigurationFiles(Path... paths) {
guard(() -> {
@@ -321,7 +292,7 @@
if (getProgramConsumer() instanceof DexFilePerClassFileConsumer) {
reporter.error("R8 does not support compiling to a single DEX file per Java class file");
}
- if (mainDexListConsumer != null
+ if (getMainDexListConsumer() != null
&& mainDexRules.isEmpty()
&& !getAppBuilder().hasMainDexList()) {
reporter.error(
@@ -433,7 +404,7 @@
getAppBuilder().build(),
getProgramConsumer(),
mainDexKeepRules,
- mainDexListConsumer,
+ getMainDexListConsumer(),
configuration,
getMode(),
getMinApiLevel(),
@@ -503,7 +474,6 @@
static final String USAGE_MESSAGE = R8CommandParser.USAGE_MESSAGE;
private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
- private final StringConsumer mainDexListConsumer;
private final ProguardConfiguration proguardConfiguration;
private final boolean enableTreeShaking;
private final boolean enableMinification;
@@ -576,12 +546,11 @@
StringConsumer proguardMapConsumer,
Path proguardCompatibilityRulesOutput,
boolean optimizeMultidexForLinearAlloc) {
- super(inputApp, mode, programConsumer, minApiLevel, reporter, enableDesugaring,
- optimizeMultidexForLinearAlloc);
+ super(inputApp, mode, programConsumer, mainDexListConsumer, minApiLevel, reporter,
+ enableDesugaring, optimizeMultidexForLinearAlloc);
assert proguardConfiguration != null;
assert mainDexKeepRules != null;
this.mainDexKeepRules = mainDexKeepRules;
- this.mainDexListConsumer = mainDexListConsumer;
this.proguardConfiguration = proguardConfiguration;
this.enableTreeShaking = enableTreeShaking;
this.enableMinification = enableMinification;
@@ -594,7 +563,6 @@
private R8Command(boolean printHelp, boolean printVersion) {
super(printHelp, printVersion);
mainDexKeepRules = ImmutableList.of();
- mainDexListConsumer = null;
proguardConfiguration = null;
enableTreeShaking = false;
enableMinification = false;
@@ -641,7 +609,7 @@
assert !internal.verbose;
internal.mainDexKeepRules = mainDexKeepRules;
internal.minimalMainDex = getMode() == CompilationMode.DEBUG;
- internal.mainDexListConsumer = mainDexListConsumer;
+ internal.mainDexListConsumer = getMainDexListConsumer();
internal.lineNumberOptimization =
!internal.debug && (proguardConfiguration.isOptimizing() || internal.enableMinification)
? LineNumberOptimization.ON
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 e7fd7c1..38842da 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
@@ -36,6 +36,8 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
@@ -94,6 +96,7 @@
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
public class IRConverter {
@@ -1255,20 +1258,116 @@
}
}
+ /**
+ * For each block, we look to see if the header matches:
+ *
+ * <pre>
+ * pseudo-instructions*
+ * v2 <- long-{mul,div} v0 v1
+ * pseudo-instructions*
+ * v5 <- long-{add,sub} v3 v4
+ * </pre>
+ *
+ * where v2 ~=~ v3 or v2 ~=~ v4 (with ~=~ being equal or an alias of) and the block is not a
+ * fallthrough target.
+ */
private void materializeInstructionBeforeLongOperationsWorkaround(IRCode code) {
if (!options.canHaveDex2OatLinkedListBug()) {
return;
}
+ DexItemFactory factory = options.itemFactory;
+ final Supplier<DexMethod> javaLangLangSignum =
+ Suppliers.memoize(
+ () ->
+ factory.createMethod(
+ factory.createString("Ljava/lang/Long;"),
+ factory.createString("signum"),
+ factory.intDescriptor,
+ new DexString[] {factory.longDescriptor}));
for (BasicBlock block : code.blocks) {
InstructionListIterator it = block.listIterator();
- Instruction firstMaterializing =
- it.nextUntil(IRConverter::isMaterializingInstructionOnArtArmVersionM);
- if (needsInstructionBeforeLongOperation(firstMaterializing)) {
- ensureInstructionBefore(code, firstMaterializing, it);
+ Instruction firstMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
+ if (!isLongMul(firstMaterializing)) {
+ continue;
+ }
+ Instruction secondMaterializing = it.nextUntil(IRConverter::isNotPseudoInstruction);
+ if (!isLongAddOrSub(secondMaterializing)) {
+ continue;
+ }
+ if (isFallthoughTarget(block)) {
+ continue;
+ }
+ Value outOfMul = firstMaterializing.outValue();
+ for (Value inOfAddOrSub : secondMaterializing.inValues()) {
+ if (isAliasOf(inOfAddOrSub, outOfMul)) {
+ it = block.listIterator();
+ it.nextUntil(i -> i == firstMaterializing);
+ Value longValue = firstMaterializing.inValues().get(0);
+ InvokeStatic invokeLongSignum =
+ new InvokeStatic(
+ javaLangLangSignum.get(), null, Collections.singletonList(longValue));
+ ensureThrowingInstructionBefore(code, firstMaterializing, it, invokeLongSignum);
+ return;
+ }
}
}
}
+ private static boolean isAliasOf(Value usedValue, Value definingValue) {
+ while (true) {
+ if (usedValue == definingValue) {
+ return true;
+ }
+ Instruction definition = usedValue.definition;
+ if (definition == null || !definition.isMove()) {
+ return false;
+ }
+ usedValue = definition.asMove().src();
+ }
+ }
+
+ private static boolean isNotPseudoInstruction(Instruction instruction) {
+ return !(instruction.isDebugInstruction() || instruction.isMove());
+ }
+
+ private static boolean isLongMul(Instruction instruction) {
+ return instruction != null
+ && instruction.isMul()
+ && instruction.asBinop().getNumericType() == NumericType.LONG
+ && instruction.outValue() != null;
+ }
+
+ private static boolean isLongAddOrSub(Instruction instruction) {
+ return instruction != null
+ && (instruction.isAdd() || instruction.isSub())
+ && instruction.asBinop().getNumericType() == NumericType.LONG;
+ }
+
+ private static boolean isFallthoughTarget(BasicBlock block) {
+ for (BasicBlock pred : block.getPredecessors()) {
+ if (pred.exit().fallthroughBlock() == block) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void ensureThrowingInstructionBefore(
+ IRCode code, Instruction addBefore, InstructionListIterator it, Instruction instruction) {
+ Instruction check = it.previous();
+ assert addBefore == check;
+ BasicBlock block = check.getBlock();
+ if (block.hasCatchHandlers()) {
+ // Split so the existing instructions retain their handlers and the new instruction has none.
+ BasicBlock split = it.split(code);
+ assert split.hasCatchHandlers();
+ assert !block.hasCatchHandlers();
+ it = block.listIterator(block.getInstructions().size() - 1);
+ }
+ instruction.setPosition(addBefore.getPosition());
+ it.add(instruction);
+ }
+
private static void ensureInstructionBefore(
IRCode code, Instruction addBefore, InstructionListIterator it) {
// Force materialize a constant-zero before the long operation.
@@ -1287,33 +1386,6 @@
it.add(fixitUser);
}
- private static boolean needsInstructionBeforeLongOperation(Instruction instruction) {
- // The cortex fixup will only trigger on long sub and long add instructions.
- if (!((instruction.isAdd() || instruction.isSub()) && instruction.outType().isWide())) {
- return false;
- }
- // If the block with the instruction is a fallthrough block, then it can't end up being
- // preceded by the incorrectly linked prologue/epilogue..
- BasicBlock block = instruction.getBlock();
- for (BasicBlock pred : block.getPredecessors()) {
- if (pred.exit().fallthroughBlock() == block) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isMaterializingInstructionOnArtArmVersionM(Instruction instruction) {
- return !instruction.isDebugInstruction()
- && !instruction.isMove()
- && !isPossiblyNonMaterializingLongOperationOnArtArmVersionM(instruction);
- }
-
- private static boolean isPossiblyNonMaterializingLongOperationOnArtArmVersionM(
- Instruction instruction) {
- return (instruction.isMul() || instruction.isDiv()) && instruction.outType().isWide();
- }
-
private void printC1VisualizerHeader(DexEncodedMethod method) {
if (printer != null) {
printer.begin("compilation");
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 4111bed..69d65ea 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -17,6 +17,10 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;
@@ -27,6 +31,9 @@
private ZipOutputStream stream = null;
private boolean closed = false;
private int openCount = 0;
+ private int classesFileIndex = 0;
+ private Map<Integer, DelayedData> delayedClassesDexFiles = new HashMap<>();
+ private SortedSet<DelayedData> delayedWrites = new TreeSet<>();
public ArchiveBuilder(Path archive) {
this.archive = archive;
@@ -44,6 +51,7 @@
assert !closed;
openCount--;
if (openCount == 0) {
+ writeDelayed(handler);
closed = true;
try {
getStreamRaw().close();
@@ -54,6 +62,20 @@
}
}
+ private void writeDelayed(DiagnosticsHandler handler) {
+ // We should never have any indexed files at this point
+ assert delayedClassesDexFiles.isEmpty();
+ for (DelayedData data : delayedWrites) {
+ if (data.isDirectory) {
+ assert data.content == null;
+ writeDirectoryNow(data.name, handler);
+ } else {
+ assert data.content != null;
+ writeFileNow(data.name, data.content, handler);
+ }
+ }
+ }
+
private ZipOutputStream getStreamRaw() throws IOException {
if (stream != null) {
return stream;
@@ -85,7 +107,11 @@
}
@Override
- public void addDirectory(String name, DiagnosticsHandler handler) {
+ public synchronized void addDirectory(String name, DiagnosticsHandler handler) {
+ delayedWrites.add(DelayedData.createDirectory(name));
+ }
+
+ private void writeDirectoryNow(String name, DiagnosticsHandler handler) {
if (name.charAt(name.length() - 1) != DataResource.SEPARATOR) {
name += DataResource.SEPARATOR;
}
@@ -105,7 +131,10 @@
@Override
public void addFile(String name, DataEntryResource content, DiagnosticsHandler handler) {
try (InputStream in = content.getByteStream()) {
- addFile(name, ByteDataView.of(ByteStreams.toByteArray(in)), handler);
+ ByteDataView view = ByteDataView.of(ByteStreams.toByteArray(in));
+ synchronized (this) {
+ delayedWrites.add(DelayedData.createFile(name, view));
+ }
} catch (IOException e) {
handleIOException(e, handler);
} catch (ResourceException e) {
@@ -116,6 +145,10 @@
@Override
public synchronized void addFile(String name, ByteDataView content, DiagnosticsHandler handler) {
+ delayedWrites.add(DelayedData.createFile(name, ByteDataView.of(content.copyByteData())));
+ }
+
+ private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
try {
ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.STORED);
} catch (IOException e) {
@@ -123,6 +156,30 @@
}
}
+ private void writeNextIfAvailable(DiagnosticsHandler handler) {
+ DelayedData data = delayedClassesDexFiles.remove(classesFileIndex);
+ while (data != null) {
+ writeFileNow(data.name, data.content, handler);
+ classesFileIndex++;
+ data = delayedClassesDexFiles.remove(classesFileIndex);
+ }
+ }
+
+ @Override
+ public synchronized void addIndexedClassFile(
+ int index, String name, ByteDataView content, DiagnosticsHandler handler) {
+ if (index == classesFileIndex) {
+ // Fast case, we got the file in order (or we only had one).
+ writeFileNow(name, content, handler);
+ classesFileIndex++;
+ writeNextIfAvailable(handler);
+ } else {
+ // Data is released in the application writer, take a copy.
+ delayedClassesDexFiles.put(index,
+ new DelayedData(name, ByteDataView.of(content.copyByteData()), false));
+ }
+ }
+
@Override
public Origin getOrigin() {
return origin;
@@ -132,4 +189,32 @@
public Path getPath() {
return archive;
}
+
+ private static class DelayedData implements Comparable<DelayedData> {
+ public final String name;
+ public final ByteDataView content;
+ public final boolean isDirectory;
+
+ public static DelayedData createFile(String name, ByteDataView content) {
+ return new DelayedData(name, content, false);
+ }
+
+ public static DelayedData createDirectory(String name) {
+ return new DelayedData(name, null, true);
+ }
+
+ private DelayedData(String name, ByteDataView content, boolean isDirectory) {
+ this.name = name;
+ this.content = content;
+ this.isDirectory = isDirectory;
+ }
+
+ @Override
+ public int compareTo(DelayedData other) {
+ if (other == null) {
+ return name.compareTo(null);
+ }
+ return name.compareTo(other.name);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
index 8e0b054..f78983f 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
@@ -68,6 +68,12 @@
}
@Override
+ public void addIndexedClassFile(
+ int index, String name, ByteDataView content, DiagnosticsHandler handler) {
+ addFile(name, content, handler);
+ }
+
+ @Override
public Origin getOrigin() {
return origin;
}
diff --git a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
index 9077f7b..be739e3 100644
--- a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
@@ -23,6 +23,9 @@
void addFile(String name, ByteDataView content, DiagnosticsHandler handler);
+ void addIndexedClassFile(
+ int index, String name, ByteDataView content, DiagnosticsHandler handler);
+
Path getPath();
Origin getOrigin();
diff --git a/src/test/java/com/android/tools/r8/DXTestBuilder.java b/src/test/java/com/android/tools/r8/DXTestBuilder.java
index 8312729..190db23 100644
--- a/src/test/java/com/android/tools/r8/DXTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/DXTestBuilder.java
@@ -67,6 +67,11 @@
}
@Override
+ public DXTestBuilder addProgramClassFileData(Collection<byte[]> classes) {
+ throw new Unimplemented("No support for adding classfile data directly");
+ }
+
+ @Override
public DXTestBuilder addProgramFiles(Collection<Path> files) {
injars.addAll(files);
return self();
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
new file mode 100644
index 0000000..c4425b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+
+public class Dex2OatTestRunResult extends TestRunResult<Dex2OatTestRunResult> {
+
+ public Dex2OatTestRunResult(AndroidApp app, ProcessResult result) {
+ super(app, result);
+ }
+
+ @Override
+ protected Dex2OatTestRunResult self() {
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 44a3bed..f95e666 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -147,6 +147,12 @@
"No support for adding paths directly (we need to compute the descriptor)");
}
+ @Override
+ public JvmTestBuilder addProgramClassFileData(Collection<byte[]> files) {
+ throw new Unimplemented(
+ "No support for adding classfile data directly (we need to compute the descriptor)");
+ }
+
public JvmTestBuilder addClasspath(Path... paths) {
return addClasspath(Arrays.asList(paths));
}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 28ce1e6..fa4c41c 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -184,6 +184,12 @@
}
@Override
+ public ProguardTestBuilder addProgramClassFileData(Collection<byte[]> classes) {
+ throw new Unimplemented(
+ "No support for adding classfile data directly (we need to compute the descriptor)");
+ }
+
+ @Override
public ProguardTestBuilder addKeepRules(Collection<String> rules) {
config.addAll(rules);
return self();
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 0675118..61bf1b3 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -39,6 +39,12 @@
public abstract T addProgramFiles(Collection<Path> files);
+ public abstract T addProgramClassFileData(Collection<byte[]> classes);
+
+ public T addProgramClassFileData(byte[]... classes) {
+ return addProgramClassFileData(Arrays.asList(classes));
+ }
+
public T addProgramClasses(Class<?>... classes) {
return addProgramClasses(Arrays.asList(classes));
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 12d1990..61f9def 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.TestBase.Backend.DEX;
import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.debug.CfDebugTestConfig;
import com.android.tools.r8.debug.DebugTestConfig;
@@ -98,4 +99,17 @@
ProcessResult result = ToolHelper.runArtRaw(out.toString(), mainClass);
return createRunResult(app, result);
}
+
+ public Dex2OatTestRunResult runDex2Oat() throws IOException {
+ return runDex2Oat(ToolHelper.getDexVm());
+ }
+
+ public Dex2OatTestRunResult runDex2Oat(DexVm vm) throws IOException {
+ assert getBackend() == DEX;
+ Path tmp = state.getNewTempFolder();
+ Path jarFile = tmp.resolve("out.jar");
+ Path oatFile = tmp.resolve("out.oat");
+ app.writeToZip(jarFile, OutputMode.DexIndexed);
+ return new Dex2OatTestRunResult(app, ToolHelper.runDex2OatRaw(jarFile, oatFile, vm));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index f60db7f..9425df9 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -36,6 +37,7 @@
// Default initialized setup. Can be overwritten if needed.
private Path defaultLibrary;
private ProgramConsumer programConsumer;
+ private StringConsumer mainDexListConsumer;
private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
@@ -61,6 +63,7 @@
public CR compile() throws CompilationFailedException {
AndroidAppConsumers sink = new AndroidAppConsumers();
builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
+ builder.setMainDexListConsumer(mainDexListConsumer);
if (defaultLibrary != null) {
builder.addLibraryFiles(defaultLibrary);
}
@@ -112,6 +115,25 @@
return self();
}
+ public T setMainDexListConsumer(StringConsumer consumer) {
+ assert consumer != null;
+ this.mainDexListConsumer = consumer;
+ return self();
+ }
+
+ public T addMainDexListFiles(Collection<Path> files) {
+ builder.addMainDexListFiles(files);
+ return self();
+ }
+
+ @Override
+ public T addProgramClassFileData(Collection<byte[]> classes) {
+ for (byte[] clazz : classes) {
+ builder.addClassProgramData(clazz, Origin.unknown());
+ }
+ return self();
+ }
+
@Override
public T addProgramFiles(Collection<Path> files) {
builder.addProgramFiles(files);
@@ -124,4 +146,9 @@
builder.addLibraryFiles(files);
return self();
}
+
+ public T noDesugaring() {
+ builder.setDisableDesugaring(true);
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index f3ec339..a193bdb 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -88,6 +88,13 @@
return self();
}
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ appendInfo(builder);
+ return builder.toString();
+ }
+
private String errorMessage(String message) {
return errorMessage(message, null);
}
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 0607b0e..af238f2 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -41,13 +42,18 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.junit.Assert;
import org.junit.ComparisonFailure;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
@@ -77,6 +83,22 @@
return result;
}
+ public void assertIdenticalZipFiles(File file1, File file2) throws IOException {
+ try (ZipFile zipFile1 = new ZipFile(file1); ZipFile zipFile2 = new ZipFile(file2)) {
+ final Enumeration<? extends ZipEntry> entries1 = zipFile1.entries();
+ final Enumeration<? extends ZipEntry> entries2 = zipFile2.entries();
+
+ while (entries1.hasMoreElements()) {
+ Assert.assertTrue(entries2.hasMoreElements());
+ ZipEntry entry1 = entries1.nextElement();
+ ZipEntry entry2 = entries2.nextElement();
+ Assert.assertEquals(entry1.getName(), entry2.getName());
+ Assert.assertEquals(entry1.getCrc(), entry2.getCrc());
+ Assert.assertEquals(entry1.getSize(), entry2.getSize());
+ }
+ }
+ }
+
public AndroidApp runAndCheckVerification(
CompilerUnderTest compiler,
CompilationMode mode,
@@ -85,6 +107,19 @@
Consumer<InternalOptions> optionsConsumer,
List<String> inputs)
throws ExecutionException, IOException, CompilationFailedException {
+ return runAndCheckVerification(compiler, mode, referenceApk, pgConfs, optionsConsumer,
+ DexIndexedConsumer::emptyConsumer, inputs);
+ }
+
+ public AndroidApp runAndCheckVerification(
+ CompilerUnderTest compiler,
+ CompilationMode mode,
+ String referenceApk,
+ List<String> pgConfs,
+ Consumer<InternalOptions> optionsConsumer,
+ Supplier<DexIndexedConsumer> dexIndexedConsumerSupplier,
+ List<String> inputs)
+ throws ExecutionException, IOException, CompilationFailedException {
assertTrue(referenceApk == null || new File(referenceApk).exists());
AndroidAppConsumers outputApp;
if (compiler == CompilerUnderTest.R8) {
@@ -95,7 +130,7 @@
pgConfs.stream().map(Paths::get).collect(Collectors.toList()));
}
builder.setMode(mode);
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ builder.setProgramConsumer(dexIndexedConsumerSupplier.get());
builder.setMinApiLevel(AndroidApiLevel.L.getLevel());
ToolHelper.allowPartiallyImplementedProguardOptions(builder);
ToolHelper.addProguardConfigurationConsumer(
@@ -119,6 +154,7 @@
return checkVerification(outputApp.build(), referenceApk);
}
+
public AndroidApp checkVerification(AndroidApp outputApp, String referenceApk)
throws IOException, ExecutionException {
Path out = temp.getRoot().toPath().resolve("all.zip");
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
index 09ce657..634ff6f 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
@@ -13,6 +14,7 @@
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
+import java.util.function.Supplier;
public class GMSCoreDeployJarVerificationTest extends GMSCoreCompilationTestBase {
@@ -38,4 +40,20 @@
optionsConsumer,
Collections.singletonList(base + DEPLOY_JAR));
}
+
+ public AndroidApp buildFromDeployJar(
+ CompilerUnderTest compiler, CompilationMode mode, String base, boolean hasReference,
+ Consumer<InternalOptions> optionsConsumer, Supplier<DexIndexedConsumer> consumerSupplier)
+ throws ExecutionException, IOException, ProguardRuleParserException,
+ CompilationFailedException {
+ return runAndCheckVerification(
+ compiler,
+ mode,
+ hasReference ? base + REFERENCE_APK : null,
+ null,
+ optionsConsumer,
+ consumerSupplier,
+ Collections.singletonList(base + DEPLOY_JAR));
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
index d881185..5803355 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
@@ -7,8 +7,12 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
import com.android.tools.r8.utils.AndroidApp;
+import java.io.File;
+import java.util.function.Supplier;
import org.junit.Test;
public class R8GMSCoreV10DeployJarVerificationTest extends GMSCoreDeployJarVerificationTest {
@@ -19,6 +23,9 @@
@Test
public void buildFromDeployJar() throws Exception {
// TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
+ File tempFolder = temp.newFolder();
+ File app1Zip = new File(tempFolder, "app1.zip");
+ File app2Zip = new File(tempFolder, "app2.zip");
AndroidApp app1 =
buildFromDeployJar(
CompilerUnderTest.R8,
@@ -27,7 +34,8 @@
false,
options ->
options.proguardMapConsumer =
- (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+ (proguardMap, handler) -> this.proguardMap1 = proguardMap,
+ ()-> new ArchiveConsumer(app1Zip.toPath(), true));
AndroidApp app2 =
buildFromDeployJar(
CompilerUnderTest.R8,
@@ -36,10 +44,14 @@
false,
options ->
options.proguardMapConsumer =
- (proguardMap, handler) -> this.proguardMap2 = proguardMap);
+ (proguardMap, handler) -> this.proguardMap2 = proguardMap,
+ ()-> new ArchiveConsumer(app2Zip.toPath(), true));
+
+
// Verify that the result of the two compilations was the same.
assertIdenticalApplications(app1, app2);
+ assertIdenticalZipFiles(app1Zip, app2Zip);
assertEquals(proguardMap1, proguardMap2);
}
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index c053e62..9d9f21d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -9,16 +9,19 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
+import java.io.IOException;
import java.nio.file.Path;
import java.util.stream.Collectors;
import org.junit.Rule;
@@ -27,6 +30,34 @@
public class MainDexListOutputTest extends TestBase {
+ interface MyConsumer<T> {
+ void accept(T element);
+ }
+
+ class TestClass {
+ public void f(MyConsumer<String> s) {
+ s.accept("asdf");
+ }
+
+ public void g() {
+ f(System.out::println);
+ }
+ }
+
+ private static String testClassMainDexName =
+ "com/android/tools/r8/maindexlist/MainDexListOutputTest$TestClass.class";
+
+ private static class TestMainDexListConsumer implements StringConsumer {
+ public boolean called = false;
+
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ called = true;
+ assertTrue(string.contains(testClassMainDexName));
+ assertTrue(string.contains("Lambda"));
+ }
+ }
+
class Reporter implements DiagnosticsHandler {
int errorCount = 0;
@@ -77,4 +108,37 @@
.filter(s -> !s.isEmpty())
.collect(Collectors.toList()));
}
+
+ @Test
+ public void testD8DesugaredLambdasInMainDexList() throws IOException, CompilationFailedException {
+ Path mainDexList = writeTextToTempFile(testClassMainDexName);
+ TestMainDexListConsumer consumer = new TestMainDexListConsumer();
+ testForD8()
+ .addProgramClasses(ImmutableList.of(TestClass.class, MyConsumer.class))
+ .addMainDexListFiles(ImmutableList.of(mainDexList))
+ .setMainDexListConsumer(consumer)
+ .compile();
+ assertTrue(consumer.called);
+ }
+
+ @Test
+ public void testD8DesugaredLambdasInMainDexListMerging()
+ throws IOException, CompilationFailedException {
+ Path mainDexList = writeTextToTempFile(testClassMainDexName);
+ Path dexOutput = temp.getRoot().toPath().resolve("classes.zip");
+ // Build intermediate dex code first.
+ testForD8()
+ .addProgramClasses(ImmutableList.of(TestClass.class, MyConsumer.class))
+ .setIntermediate(true)
+ .setProgramConsumer(new ArchiveConsumer(dexOutput))
+ .compile();
+ // Now test that when merging with a main dex list it is correctly updated.
+ TestMainDexListConsumer consumer = new TestMainDexListConsumer();
+ testForD8()
+ .addProgramFiles(dexOutput)
+ .addMainDexListFiles(ImmutableList.of(mainDexList))
+ .setMainDexListConsumer(consumer)
+ .compile();
+ assertTrue(consumer.called);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index ddb0e7d..e013b02 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -20,15 +20,21 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
import org.junit.Assert;
import org.junit.Test;
@@ -301,6 +307,34 @@
nonLambdaOffset++;
}
}
+ testZipfileOrder(out);
+ }
+
+ private void testZipfileOrder(Path out) throws IOException {
+ // Sanity check that the classes.dex files are in the correct order
+ ZipFile outZip = new ZipFile(out.toFile());
+ Enumeration<? extends ZipEntry> entries = outZip.entries();
+ int index = 0;
+ LinkedList<String> entryNames = new LinkedList<>();
+ // We expect classes*.dex files first, in order, then the rest of the files, in order.
+ while(entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (!entry.getName().startsWith("classes") || !entry.getName().endsWith(".dex")) {
+ entryNames.add(entry.getName());
+ continue;
+ }
+ if (index == 0) {
+ Assert.assertEquals("classes.dex", entry.getName());
+ } else {
+ Assert.assertEquals("classes" + (index + 1) + ".dex", entry.getName());
+ }
+ index++;
+ }
+ // Everything else should be sorted according to name.
+ String[] entriesUnsorted = entryNames.toArray(new String[0]);
+ String[] entriesSorted = entryNames.toArray(new String[0]);
+ Arrays.sort(entriesSorted);
+ Assert.assertArrayEquals(entriesUnsorted, entriesSorted);
}
private boolean isLambda(String mainDexEntry) {
diff --git a/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java
new file mode 100644
index 0000000..12bd452
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Runner.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.regress.b118075510;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Regress118075510Runner extends AsmTestBase {
+
+ public static final Class<?> CLASS = Regress118075510Test.class;
+ public static final String EXPECTED = StringUtils.lines("0", "0");
+
+ @Test
+ public void test()
+ throws CompilationFailedException, IOException, ExecutionException, NoSuchMethodException {
+ testForJvm().addTestClasspath().run(CLASS).assertSuccessWithOutput(EXPECTED);
+
+ D8TestCompileResult d8Result =
+ testForD8().addProgramClasses(CLASS).setMinApi(AndroidApiLevel.M).release().compile();
+
+ CodeInspector inspector = d8Result.inspector();
+ checkMethodContainsLongSignum(inspector, "fooNoTryCatch");
+ checkMethodContainsLongSignum(inspector, "fooWithTryCatch");
+ // Check the program runs on ART/Dalvik
+ d8Result.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ // Check the program can be dex2oat compiled to arm64. This will diverge without the fixup.
+ d8Result.runDex2Oat().assertSuccess();
+ }
+
+ private void checkMethodContainsLongSignum(CodeInspector inspector, String methodName)
+ throws NoSuchMethodException {
+ MethodSubject method = inspector.method(CLASS.getMethod(methodName, long.class, long.class));
+ Assert.assertTrue(
+ "Did not contain Long.signum workaround in "
+ + methodName
+ + ":\n"
+ + method.getMethod().codeToString(),
+ method
+ .streamInstructions()
+ .anyMatch(
+ i ->
+ i.isInvoke() && i.getMethod().qualifiedName().equals("java.lang.Long.signum")));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Test.java b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Test.java
new file mode 100644
index 0000000..cc4dece
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b118075510/Regress118075510Test.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.regress.b118075510;
+
+public class Regress118075510Test {
+
+ public static void fooNoTryCatch(long a, long b) {
+ // Call a method on the runner class that will not be on the classpath at runtime.
+ // This causes the optimizing 6.0.1 compiler to delegate to the old quick compiler.
+ if (a == b) Regress118075510Runner.class.getMethods();
+ // The else branch of the conditional here ends up with an invalid previous pointer at its
+ // block header (ie, previous of mul-long (2addr) points to epilogue-end which is self-linked.
+ System.out.println(a < b ? 0 : a * b + b);
+ }
+
+ public static void fooWithTryCatch(long a, long b) {
+ // Call a method on the runner class that will not be on the classpath at runtime.
+ // This causes the optimizing 6.0.1 compiler to delegate to the old quick compiler.
+ if (a == b) Regress118075510Runner.class.getMethods();
+ // The else branch of the conditional here ends up with an invalid previous pointer at its
+ // block header (ie, previous of mul-long (2addr) points to epilogue-end which is self-linked.
+ try {
+ if (a < b) {
+ System.out.println((long) 0);
+ } else {
+ System.out.println(a * b + b);
+ }
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void main(String[] args) {
+ fooNoTryCatch(args.length, 456);
+ fooWithTryCatch(456, args.length);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
index cbc6e1b..3ab3467 100644
--- a/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
+++ b/src/test/java/com/android/tools/r8/regress/b77842465/Regress77842465.java
@@ -5,29 +5,20 @@
import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.D8;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.IOException;
-import java.nio.file.Path;
import org.junit.Test;
public class Regress77842465 extends AsmTestBase {
@Test
public void test() throws CompilationFailedException, IOException {
-
- Path dexOut = temp.getRoot().toPath().resolve("out.jar");
- Path oatOut = temp.getRoot().toPath().resolve("out.odex");
-
- D8.run(D8Command.builder()
- .addClassProgramData(Regress77842465Dump.dump(), Origin.unknown())
- .setOutput(dexOut, OutputMode.DexIndexed)
- .setDisableDesugaring(true)
- .build());
-
- ToolHelper.runDex2Oat(dexOut, oatOut);
+ testForD8()
+ .addProgramClassFileData(Regress77842465Dump.dump())
+ .noDesugaring()
+ .setMinApi(AndroidApiLevel.M)
+ .compile()
+ .runDex2Oat()
+ .assertSuccess();
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 1e21552..98bb8d0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -5,8 +5,10 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.google.common.collect.Streams;
import java.util.Iterator;
import java.util.function.Predicate;
+import java.util.stream.Stream;
public abstract class MethodSubject extends MemberSubject {
@@ -40,4 +42,8 @@
public abstract LineNumberTable getLineNumberTable();
public abstract boolean hasLocalVariableTable();
+
+ public Stream<InstructionSubject> streamInstructions() {
+ return Streams.stream(iterateInstructions());
+ }
}