Merge "Archive repackaged r8 for maven compatibility"
diff --git a/build.gradle b/build.gradle
index 94c002f..86ba02f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -703,6 +703,20 @@
}
}
+task jardiff(type: Jar) {
+ from sourceSets.main.output
+ baseName 'jardiff'
+ manifest {
+ attributes 'Main-Class': 'com.android.tools.r8.JarDiff'
+ }
+ // In order to build without dependencies, pass the exclude_deps property using:
+ // gradle -Pexclude_deps jardiff
+ if (!project.hasProperty('exclude_deps')) {
+ // Also include dependencies
+ from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+ }
+}
+
task sourceJar(type: Jar, dependsOn: classes) {
classifier = 'src'
from sourceSets.main.allSource
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 3db9699..26097c1 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -59,7 +59,7 @@
// Print -whyareyoukeeping results if any.
if (mainDexRootSet.reasonAsked.size() > 0) {
// Print reasons on the application after pruning, so that we reflect the actual result.
- TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ TreePruner pruner = new TreePruner(application, mainDexAppInfo.withLiveness(), options);
application = pruner.run();
ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
reasonPrinter.run(application);
diff --git a/src/main/java/com/android/tools/r8/JarDiff.java b/src/main/java/com/android/tools/r8/JarDiff.java
new file mode 100644
index 0000000..dad57d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/JarDiff.java
@@ -0,0 +1,260 @@
+// 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 static com.google.common.io.ByteStreams.toByteArray;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * Command-line program to compare two JARs. Given two JARs as input, the program first outputs a
+ * list of classes only in one of the input JARs. Then, for each common class, the program outputs a
+ * list of methods only in one of the input JARs. For each common method, the output of
+ * CfInstruction.toString() on each instruction is compared to find instruction-level differences. A
+ * simple diffing algorithm is used that simply removes the common prefix and common suffix and
+ * prints everything from the first difference to the last difference in the method code.
+ */
+public class JarDiff {
+
+ private static final String USAGE =
+ "Arguments: <input1.jar> <input2.jar>\n"
+ + "\n"
+ + "JarDiff computes the difference between two JAR files that contain Java classes.\n"
+ + "\n"
+ + "Only method codes are compared. Fields, parameters, annotations, generic\n"
+ + "signatures etc. are ignored.\n"
+ + "\n"
+ + "Note: Jump targets are ignored, so if two methods differ only in what label an\n"
+ + "IF or GOTO instruction jumps to, no difference is output.";
+
+ public static void main(String[] args) throws Exception {
+ JarDiff jarDiff = JarDiff.parse(args);
+ if (jarDiff == null) {
+ System.out.println(USAGE);
+ } else {
+ jarDiff.run();
+ }
+ }
+
+ public static JarDiff parse(String[] args) {
+ int arg = 0;
+ int before = 3;
+ int after = 3;
+ while (arg + 1 < args.length) {
+ if (args[arg].equals("-B")) {
+ before = Integer.parseInt(args[arg + 1]);
+ arg += 2;
+ } else if (args[arg].equals("-A")) {
+ after = Integer.parseInt(args[arg + 1]);
+ arg += 2;
+ } else {
+ break;
+ }
+ }
+ if (args.length != arg + 2) {
+ return null;
+ }
+ return new JarDiff(Paths.get(args[arg]), Paths.get(args[arg + 1]), before, after);
+ }
+
+ private final Path path1;
+ private final Path path2;
+ private final int before;
+ private final int after;
+ private final JarApplicationReader applicationReader;
+ private ArchiveClassFileProvider archive1;
+ private ArchiveClassFileProvider archive2;
+
+ public JarDiff(Path path1, Path path2, int before, int after) {
+ this.path1 = path1;
+ this.path2 = path2;
+ this.before = before;
+ this.after = after;
+ InternalOptions options = new InternalOptions();
+ options.enableCfFrontend = true;
+ applicationReader = new JarApplicationReader(options);
+ }
+
+ public void run() throws Exception {
+ archive1 = new ArchiveClassFileProvider(path1);
+ archive2 = new ArchiveClassFileProvider(path2);
+ for (String descriptor : getCommonDescriptors()) {
+ byte[] bytes1 = getClassAsBytes(archive1, descriptor);
+ byte[] bytes2 = getClassAsBytes(archive2, descriptor);
+ if (Arrays.equals(bytes1, bytes2)) {
+ continue;
+ }
+ DexProgramClass class1 = getDexProgramClass(path1, bytes1);
+ DexProgramClass class2 = getDexProgramClass(path2, bytes2);
+ compareMethods(class1, class2);
+ }
+ }
+
+ private List<String> getCommonDescriptors() {
+ List<String> descriptors1 = getSortedDescriptorList(archive1);
+ List<String> descriptors2 = getSortedDescriptorList(archive2);
+ if (descriptors1.equals(descriptors2)) {
+ return descriptors1;
+ }
+ List<String> only1 = setMinus(descriptors1, descriptors2);
+ List<String> only2 = setMinus(descriptors2, descriptors1);
+ if (!only1.isEmpty()) {
+ System.out.println("Only in " + path1 + ": " + only1);
+ }
+ if (!only2.isEmpty()) {
+ System.out.println("Only in " + path2 + ": " + only2);
+ }
+ return setIntersection(descriptors1, descriptors2);
+ }
+
+ private List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+ ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+ Collections.sort(descriptorList);
+ return descriptorList;
+ }
+
+ private byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+ throws Exception {
+ return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+ }
+
+ private DexProgramClass getDexProgramClass(Path path, byte[] bytes) throws IOException {
+
+ class Collector implements Consumer<DexClass> {
+
+ private DexClass dexClass;
+
+ @Override
+ public void accept(DexClass dexClass) {
+ this.dexClass = dexClass;
+ }
+
+ public DexClass get() {
+ assert dexClass != null;
+ return dexClass;
+ }
+ }
+
+ Collector collector = new Collector();
+ JarClassFileReader reader = new JarClassFileReader(applicationReader, collector);
+ reader.read(new PathOrigin(path), ClassKind.PROGRAM, new ByteArrayInputStream(bytes));
+ return collector.get().asProgramClass();
+ }
+
+ private void compareMethods(DexProgramClass class1, DexProgramClass class2) {
+ class1.forEachMethod(
+ method1 -> {
+ DexEncodedMethod method2 = class2.lookupMethod(method1.method);
+ if (method2 == null) {
+ compareMethods(method1, method2);
+ }
+ });
+ class2.forEachMethod(
+ method2 -> {
+ DexEncodedMethod method1 = class1.lookupMethod(method2.method);
+ compareMethods(method1, method2);
+ });
+ }
+
+ private void compareMethods(DexEncodedMethod m1, DexEncodedMethod m2) {
+ if (m1 == null) {
+ System.out.println("Only in " + path2 + ": " + m2.method.toSourceString());
+ return;
+ }
+ if (m2 == null) {
+ System.out.println("Only in " + path1 + ": " + m1.method.toSourceString());
+ return;
+ }
+ List<String> code1 = getInstructionStrings(m1);
+ List<String> code2 = getInstructionStrings(m2);
+ if (code1.equals(code2)) {
+ return;
+ }
+ int i = getCommonPrefix(code1, code2);
+ int j = getCommonSuffix(code1, code2);
+ int length1 = code1.size() - i - j;
+ int length2 = code2.size() - i - j;
+ int before = Math.min(i, this.before);
+ int after = Math.min(j, this.after);
+ int context = before + after;
+ System.out.println("--- " + path1 + "/" + m1.method.toSmaliString());
+ System.out.println("+++ " + path2 + "/" + m2.method.toSmaliString());
+ System.out.println(
+ "@@ -" + (i - before) + "," + (length1 + context)
+ + " +" + (i - before) + "," + (length2 + context) + " @@ "
+ + m1.method.toSourceString());
+ for (int k = 0; k < before; k++) {
+ System.out.println(" " + code1.get(i - before + k));
+ }
+ for (int k = 0; k < length1; k++) {
+ System.out.println("-" + code1.get(i + k));
+ }
+ for (int k = 0; k < length2; k++) {
+ System.out.println("+" + code2.get(i + k));
+ }
+ for (int k = 0; k < after; k++) {
+ System.out.println(" " + code1.get(i + length1 + k));
+ }
+ }
+
+ private static List<String> getInstructionStrings(DexEncodedMethod method) {
+ List<CfInstruction> instructions = method.getCode().asCfCode().getInstructions();
+ return instructions.stream().map(CfInstruction::toString).collect(Collectors.toList());
+ }
+
+ private static List<String> setIntersection(List<String> set1, List<String> set2) {
+ ArrayList<String> result = new ArrayList<>(set1);
+ result.retainAll(new HashSet<>(set2));
+ return result;
+ }
+
+ private static List<String> setMinus(List<String> set, List<String> toRemove) {
+ ArrayList<String> result = new ArrayList<>(set);
+ result.removeAll(new HashSet<>(toRemove));
+ return result;
+ }
+
+ private static int getCommonPrefix(List<String> code1, List<String> code2) {
+ int i = 0;
+ while (i < code1.size() && i < code2.size()) {
+ if (code1.get(i).equals(code2.get(i))) {
+ i++;
+ } else {
+ break;
+ }
+ }
+ return i;
+ }
+
+ private static int getCommonSuffix(List<String> code1, List<String> code2) {
+ int j = 0;
+ while (j < code1.size() && j < code2.size()) {
+ if (code1.get(code1.size() - j - 1).equals(code2.get(code2.size() - j - 1))) {
+ j++;
+ } else {
+ break;
+ }
+ }
+ return j;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6a1d7c8..53e2f87 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -358,12 +358,9 @@
}
application = application.asDirect().rewrittenWithLense(graphLense);
appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense);
- // TODO(mathiasr): Remove this check when CF->IR construction is complete.
- if (!options.skipIR) {
- // Collect switch maps and ordinals maps.
- appInfo = new SwitchMapCollector(appInfo.withLiveness(), options).run();
- appInfo = new EnumOrdinalMapCollector(appInfo.withLiveness(), options).run();
- }
+ // Collect switch maps and ordinals maps.
+ appInfo = new SwitchMapCollector(appInfo.withLiveness(), options).run();
+ appInfo = new EnumOrdinalMapCollector(appInfo.withLiveness(), options).run();
// TODO(b/79143143): re-enable once fixed.
// graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index a52d694..8a46d57 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -12,9 +12,11 @@
import com.android.tools.r8.shaking.ProguardConfigurationParser;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceBytes;
import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
@@ -22,15 +24,17 @@
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
/**
- * Immutable command structure for an invocation of the {@link D8} compiler.
+ * Immutable command structure for an invocation of the {@link R8} compiler.
*
* <p>To build a R8 command use the {@link R8Command.Builder} class. For example:
*
@@ -282,21 +286,54 @@
mainDexKeepRules = parser.getConfig().getRules();
}
- ProguardConfiguration.Builder configurationBuilder;
- if (proguardConfigs.isEmpty()) {
- configurationBuilder = ProguardConfiguration.builder(factory, reporter);
- } else {
- ProguardConfigurationParser parser =
- new ProguardConfigurationParser(factory, reporter);
+ ProguardConfigurationParser parser = new ProguardConfigurationParser(factory, reporter);
+ if (!proguardConfigs.isEmpty()) {
parser.parse(proguardConfigs);
- configurationBuilder = parser.getConfigurationBuilder();
- configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
}
+ ProguardConfiguration.Builder configurationBuilder = parser.getConfigurationBuilder();
+ configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
if (proguardConfigurationConsumer != null) {
proguardConfigurationConsumer.accept(configurationBuilder);
}
+ // Process Proguard configurations supplied through data resources in the input.
+ DataResourceProvider.Visitor embeddedProguardConfigurationVisitor =
+ new DataResourceProvider.Visitor() {
+ @Override
+ public void visit(DataDirectoryResource directory) {
+ // Don't do anything.
+ }
+
+ @Override
+ public void visit(DataEntryResource resource) {
+ if (resource.getName().startsWith("META-INF/proguard/")) {
+ try (InputStream in = resource.getByteStream()) {
+ ProguardConfigurationSource source =
+ new ProguardConfigurationSourceBytes(in, resource.getOrigin());
+ parser.parse(source);
+ } catch (ResourceException e) {
+ reporter.error(new StringDiagnostic("Failed to open input: " + e.getMessage(),
+ resource.getOrigin()));
+ } catch (Exception e) {
+ reporter.error(new ExceptionDiagnostic(e, resource.getOrigin()));
+ }
+ }
+ }
+ };
+
+ getAppBuilder().getProgramResourceProviders()
+ .stream()
+ .map(ProgramResourceProvider::getDataResourceProvider)
+ .filter(Objects::nonNull)
+ .forEach(dataResourceProvider -> {
+ try {
+ dataResourceProvider.accept(embeddedProguardConfigurationVisitor);
+ } catch (ResourceException e) {
+ reporter.error(new ExceptionDiagnostic(e));
+ }
+ });
+
if (disableTreeShaking) {
configurationBuilder.disableShrinking();
}
@@ -369,6 +406,11 @@
}
return resources;
}
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return provider.getDataResourceProvider();
+ }
}
// Internal state to verify parsing properties not enforced by the builder.
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 6d7ae19..f907ffb 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -50,6 +50,7 @@
import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.If;
@@ -124,9 +125,10 @@
for (int i = 0; i < tryCatch.guards.size(); i++) {
newline();
DexType guard = tryCatch.guards.get(i);
+ assert guard != null;
builder
.append(".catch ")
- .append(guard == null ? "all" : guard.getInternalName())
+ .append(guard == DexItemFactory.catchAllType ? "all" : guard.getInternalName())
.append(" from ")
.append(getLabel(tryCatch.start))
.append(" to ")
@@ -430,7 +432,7 @@
Kind kind = cfSwitch.getKind();
builder.append(kind == Kind.LOOKUP ? "lookup" : "table").append("switch");
IntList keys = cfSwitch.getKeys();
- List<CfLabel> targets = cfSwitch.getTargets();
+ List<CfLabel> targets = cfSwitch.getSwitchTargets();
for (int i = 0; i < targets.size(); i++) {
indent();
int key = kind == Kind.LOOKUP ? keys.getInt(i) : (keys.getInt(0) + i);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index 423bba2..842cbe9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -6,6 +6,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -116,4 +120,36 @@
public void write(MethodVisitor visitor, NamingLens lens) {
visitor.visitInsn(getAsmOpcode());
}
+
+ @Override
+ public boolean canThrow() {
+ return (type != NumericType.FLOAT && type != NumericType.DOUBLE)
+ && (opcode == Opcode.Div || opcode == Opcode.Rem);
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int right = state.pop().register;
+ int left = state.pop().register;
+ int dest = state.push(ValueType.fromNumericType(type)).register;
+ switch (opcode) {
+ case Add:
+ builder.addAdd(type, dest, left, right);
+ break;
+ case Sub:
+ builder.addSub(type, dest, left, right);
+ break;
+ case Mul:
+ builder.addMul(type, dest, left, right);
+ break;
+ case Div:
+ builder.addDiv(type, dest, left, right);
+ break;
+ case Rem:
+ builder.addRem(type, dest, left, right);
+ break;
+ default:
+ throw new Unreachable("CfArithmeticBinop has unknown opcode " + opcode);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index dfe7c1b..8fb77c1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -19,4 +23,16 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ Slot array = state.pop();
+ assert array.type.isObjectOrNull();
+ builder.addArrayLength(state.push(builder.getFactory().intType).register, array.register);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 85405e4..c0b1a07 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -6,6 +6,11 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -55,4 +60,25 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ Slot index = state.pop();
+ Slot array = state.pop();
+ Slot value;
+ assert array.type.isObjectOrNull();
+ ValueType memberType = ValueType.fromMemberType(type);
+ if (array.preciseType != null) {
+ value = state.push(array.preciseType.toArrayElementType(builder.getFactory()));
+ assert state.peek().type == memberType;
+ } else {
+ value = state.push(memberType);
+ }
+ builder.addArrayGet(type, value.register, array.register, index.register);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 4b13af4..ff76777 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -6,6 +6,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -55,4 +59,17 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ Slot value = state.pop();
+ Slot index = state.pop();
+ Slot array = state.pop();
+ builder.addArrayPut(type, value.register, array.register, index.register);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index e46d475..c01d6d6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -6,6 +6,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -36,4 +40,17 @@
public void registerUse(UseRegistry registry, DexType clazz) {
registry.registerCheckCast(type);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ // Pop the top value and push it back on with the checked type.
+ state.pop();
+ Slot object = state.push(type);
+ builder.addCheckCast(object.register, type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 9c6d3d7..b96ca89 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -8,6 +8,10 @@
import com.android.tools.r8.ir.code.Cmp;
import com.android.tools.r8.ir.code.Cmp.Bias;
import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -66,4 +70,11 @@
public void write(MethodVisitor visitor, NamingLens lens) {
visitor.visitInsn(getAsmOpcode());
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int right = state.pop().register;
+ int left = state.pop().register;
+ builder.addCmp(type, bias, state.push(ValueType.fromNumericType(type)).register, left, right);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index e4037e4..4da0b5c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -7,6 +7,9 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
@@ -33,6 +36,11 @@
printer.print(this);
}
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
private String getInternalName(NamingLens lens) {
switch (type.toShorty()) {
case '[':
@@ -63,4 +71,9 @@
public void registerUse(UseRegistry registry, DexType clazz) {
registry.registerConstClass(type);
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ builder.addConstClass(state.push(builder.getFactory().classType).register, type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index ca34be7..0a98cb3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -3,10 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
@@ -36,4 +40,17 @@
public void registerUse(UseRegistry registry, DexType clazz) {
registry.registerMethodHandle(handle);
}
+
+ @Override
+ public boolean canThrow() {
+ // const-class and const-string* may throw in dex.
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+ throws ApiLevelException {
+ builder.addConstMethodHandle(
+ state.push(builder.getFactory().methodHandleType).register, handle);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 7e06a88..e933c11 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -3,10 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
@@ -37,4 +41,16 @@
public void registerUse(UseRegistry registry, DexType clazz) {
registry.registerProto(type);
}
+
+ @Override
+ public boolean canThrow() {
+ // const-class and const-string* may throw in dex.
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+ throws ApiLevelException {
+ builder.addConstMethodType(state.push(builder.getFactory().methodTypeType).register, type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index 385669d..e72c9e1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -19,4 +23,9 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ builder.addNullConst(state.push(ValueType.OBJECT).register);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index f3aa7f3..c24490c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -6,6 +6,9 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -118,4 +121,9 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ builder.addConst(type, state.push(type).register, value);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index a22f279..54ae1c6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -5,6 +5,9 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
@@ -29,4 +32,15 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public boolean canThrow() {
+ // const-class and const-string* may throw in dex.
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ builder.addConstString(state.push(builder.getFactory().stringType).register, string);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 130362f..350c1c9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -8,6 +8,10 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -65,4 +69,42 @@
throw new Unreachable("Unexpected opcode " + opcode);
}
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ DexType type = field.type;
+ switch (opcode) {
+ case Opcodes.GETSTATIC:
+ {
+ builder.addStaticGet(state.push(type).register, field);
+ break;
+ }
+ case Opcodes.PUTSTATIC:
+ {
+ Slot value = state.pop();
+ builder.addStaticPut(value.register, field);
+ break;
+ }
+ case Opcodes.GETFIELD:
+ {
+ Slot object = state.pop();
+ builder.addInstanceGet(state.push(type).register, object.register, field);
+ break;
+ }
+ case Opcodes.PUTFIELD:
+ {
+ Slot value = state.pop();
+ Slot object = state.pop();
+ builder.addInstancePut(value.register, object.register, field);
+ break;
+ }
+ default:
+ throw new Unreachable("Unexpected opcode " + opcode);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index d015de1..6e46028 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -9,6 +9,9 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.util.List;
@@ -50,6 +53,22 @@
return null;
}
+ public boolean isUninitializedThis() {
+ return false;
+ }
+
+ public boolean isInitialized() {
+ return false;
+ }
+
+ public DexType getInitializedType() {
+ return null;
+ }
+
+ public boolean isTop() {
+ return false;
+ }
+
private FrameType() {}
}
@@ -92,6 +111,16 @@
public boolean isWide() {
return type.isPrimitiveType() && (type.toShorty() == 'J' || type.toShorty() == 'D');
}
+
+ @Override
+ public boolean isInitialized() {
+ return true;
+ }
+
+ @Override
+ public DexType getInitializedType() {
+ return type;
+ }
}
private static class Top extends FrameType {
@@ -107,6 +136,11 @@
Object getTypeOpcode(NamingLens lens) {
return Opcodes.TOP;
}
+
+ @Override
+ public boolean isTop() {
+ return true;
+ }
}
private static class UninitializedNew extends FrameType {
@@ -127,6 +161,11 @@
}
@Override
+ public boolean isUninitializedNew() {
+ return true;
+ }
+
+ @Override
public CfLabel getUninitializedLabel() {
return label;
}
@@ -144,6 +183,11 @@
public String toString() {
return "uninitialized this";
}
+
+ @Override
+ public boolean isUninitializedThis() {
+ return true;
+ }
}
private final Int2ReferenceSortedMap<FrameType> locals;
@@ -232,4 +276,15 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ // TODO(mathiasr): Verify stack map frames before building IR.
+ code.setStateFromFrame(this);
+ }
+
+ @Override
+ public boolean emitsIR() {
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
index 47cfed6..c4975d8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfGoto.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -16,6 +19,7 @@
this.target = target;
}
+ @Override
public CfLabel getTarget() {
return target;
}
@@ -29,4 +33,9 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ builder.addGoto(code.getLabelOffset(target));
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIf.java b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
index d39c358..c97f3b6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIf.java
@@ -8,6 +8,9 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -32,6 +35,7 @@
return kind;
}
+ @Override
public CfLabel getTarget() {
return target;
}
@@ -64,4 +68,17 @@
public void write(MethodVisitor visitor, NamingLens lens) {
visitor.visitJumpInsn(getOpcode(), target.getLabel());
}
+
+ @Override
+ public boolean isConditionalJump() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int value = state.pop().register;
+ int trueTargetOffset = code.getLabelOffset(target);
+ int falseTargetOffset = code.getCurrentInstructionIndex() + 1;
+ builder.addIfZero(kind, type, value, trueTargetOffset, falseTargetOffset);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index 2a076e4..26410c8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -8,6 +8,9 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -32,6 +35,7 @@
return type;
}
+ @Override
public CfLabel getTarget() {
return target;
}
@@ -64,4 +68,18 @@
public void write(MethodVisitor visitor, NamingLens lens) {
visitor.visitJumpInsn(getOpcode(), target.getLabel());
}
+
+ @Override
+ public boolean isConditionalJump() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int right = state.pop().register;
+ int left = state.pop().register;
+ int trueTargetOffset = code.getLabelOffset(target);
+ int falseTargetOffset = code.getCurrentInstructionIndex() + 1;
+ builder.addIf(kind, type, left, right, trueTargetOffset, falseTargetOffset);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
index 6db7250..20b3928 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
@@ -34,4 +38,10 @@
public int getIncrement() {
return increment;
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int local = state.read(var).register;
+ builder.addAddLiteral(NumericType.INT, local, local, increment);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index deba4ad..6a093df 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -6,6 +6,9 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -36,4 +39,15 @@
public void registerUse(UseRegistry registry, DexType clazz) {
registry.registerTypeReference(type);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int value = state.pop().register;
+ builder.addInstanceOf(state.push(builder.getFactory().booleanType).register, value, type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 04ab380..eaa7b2a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -3,9 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
@@ -25,4 +29,31 @@
public void registerUse(UseRegistry registry, DexType clazz) {
// Intentionally empty.
}
+
+ public CfLabel getTarget() {
+ return null;
+ }
+
+ /** Return true if this instruction is CfReturn or CfReturnVoid. */
+ public boolean isReturn() {
+ return false;
+ }
+
+ /** Return true if this instruction is CfIf or CfIfCmp. */
+ public boolean isConditionalJump() {
+ return false;
+ }
+
+ /** Return true if this instruction or its DEX equivalent can throw. */
+ public boolean canThrow() {
+ return false;
+ }
+
+ public abstract void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+ throws ApiLevelException;
+
+ /** Return true if this instruction directly emits IR instructions. */
+ public boolean emitsIR() {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 82f2438..32c7022 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -3,13 +3,23 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
+import java.util.Arrays;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -74,4 +84,73 @@
throw new Unreachable("unknown CfInvoke opcode " + opcode);
}
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+ throws ApiLevelException {
+ Invoke.Type type;
+ DexMethod canonicalMethod;
+ DexProto callSiteProto = null;
+ switch (opcode) {
+ case Opcodes.INVOKEINTERFACE:
+ {
+ canonicalMethod = method;
+ type = Type.INTERFACE;
+ break;
+ }
+ case Opcodes.INVOKEVIRTUAL:
+ {
+ canonicalMethod = builder.getFactory().polymorphicMethods.canonicalize(method);
+ if (canonicalMethod == null) {
+ type = Type.VIRTUAL;
+ canonicalMethod = method;
+ } else {
+ type = Type.POLYMORPHIC;
+ callSiteProto = method.proto;
+ }
+ break;
+ }
+ case Opcodes.INVOKESPECIAL:
+ {
+ canonicalMethod = method;
+ if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
+ type = Type.DIRECT;
+ } else if (builder.getMethod().holder == method.holder) {
+ type = Type.DIRECT;
+ } else {
+ type = Type.SUPER;
+ }
+ break;
+ }
+ case Opcodes.INVOKESTATIC:
+ {
+ canonicalMethod = method;
+ type = Type.STATIC;
+ break;
+ }
+ default:
+ throw new Unreachable("unknown CfInvoke opcode " + opcode);
+ }
+ int parameterCount = method.proto.parameters.size();
+ if (type != Type.STATIC) {
+ parameterCount += 1;
+ }
+ ValueType[] types = new ValueType[parameterCount];
+ Integer[] registers = new Integer[parameterCount];
+ for (int i = parameterCount - 1; i >= 0; i--) {
+ Slot slot = state.pop();
+ types[i] = slot.type;
+ registers[i] = slot.register;
+ }
+ builder.addInvoke(
+ type, canonicalMethod, callSiteProto, Arrays.asList(types), Arrays.asList(registers), itf);
+ if (!method.proto.returnType.isVoidType()) {
+ builder.addMoveResult(state.push(method.proto.returnType).register);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index ca6c1de..f93709e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -19,7 +19,12 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
+import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
@@ -83,4 +88,26 @@
public void registerUse(UseRegistry registry, DexType clazz) {
registry.registerCallSite(callSite);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ DexType[] parameterTypes = callSite.methodProto.parameters.values;
+ List<Integer> registers = new ArrayList<>(parameterTypes.length);
+ for (int register : state.popReverse(parameterTypes.length)) {
+ registers.add(register);
+ }
+ List<ValueType> types = new ArrayList<>(parameterTypes.length);
+ for (DexType value : parameterTypes) {
+ types.add(ValueType.fromDexType(value));
+ }
+ builder.addInvokeCustom(callSite, types, registers);
+ if (!callSite.methodProto.returnType.isVoidType()) {
+ builder.addMoveResult(state.push(callSite.methodProto.returnType).register);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
index 401e4e8..da24ade 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLabel.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
@@ -28,4 +31,14 @@
public void write(MethodVisitor visitor, NamingLens lens) {
visitor.visitLabel(getLabel());
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ // Intentionally left empty.
+ }
+
+ @Override
+ public boolean emitsIR() {
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
index 58c59a5..1f4c3e0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLoad.java
@@ -6,6 +6,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -54,4 +58,16 @@
public int getLocalIndex() {
return var;
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ Slot local = state.read(var);
+ Slot stack = state.push(local);
+ builder.addMove(local.type, stack.register, local.register);
+ }
+
+ @Override
+ public boolean emitsIR() {
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index 2aab288..a8527f1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -6,6 +6,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -91,4 +95,33 @@
public void write(MethodVisitor visitor, NamingLens lens) {
visitor.visitInsn(getAsmOpcode());
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int right = state.pop().register;
+ int left = state.pop().register;
+ int dest = state.push(ValueType.fromNumericType(type)).register;
+ switch (opcode) {
+ case Shl:
+ builder.addShl(type, dest, left, right);
+ break;
+ case Shr:
+ builder.addShr(type, dest, left, right);
+ break;
+ case Ushr:
+ builder.addUshr(type, dest, left, right);
+ break;
+ case And:
+ builder.addAnd(type, dest, left, right);
+ break;
+ case Or:
+ builder.addOr(type, dest, left, right);
+ break;
+ case Xor:
+ builder.addXor(type, dest, left, right);
+ break;
+ default:
+ throw new Unreachable("CfLogicalBinop has unknown opcode " + opcode);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index b38ef12..3b19b03 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -5,6 +5,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.ir.code.Monitor.Type;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -30,4 +34,15 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ Slot object = state.pop();
+ builder.addMonitor(type, object.register);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index a2f3da2..2dfc8fa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -3,9 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf.code;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
@@ -41,4 +45,16 @@
public void registerUse(UseRegistry registry, DexType clazz) {
registry.registerTypeReference(type);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code)
+ throws ApiLevelException {
+ int[] dimensions = state.popReverse(this.dimensions);
+ builder.addMultiNewArray(type, state.push(type).register, dimensions);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index db0019a..daac41d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -6,6 +6,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -60,4 +64,10 @@
throw new Unreachable("Invalid opcode for CfNeg " + opcode);
}
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int value = state.pop().register;
+ builder.addNeg(type, state.push(ValueType.fromNumericType(type)).register, value);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index b7261b6..70e5add 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -6,6 +6,9 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -36,4 +39,14 @@
public void registerUse(UseRegistry registry, DexType clazz) {
registry.registerNewInstance(type);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ builder.addNewInstance(state.push(type).register, type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index 708a414..92feb63 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -7,6 +7,10 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.DescriptorUtils;
import org.objectweb.asm.MethodVisitor;
@@ -76,4 +80,16 @@
registry.registerTypeReference(type);
}
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ Slot size = state.pop();
+ Slot push = state.push(type);
+ builder.addNewArrayEmpty(push.register, size.register, type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index 2cdd37c..b6308f4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -19,4 +22,14 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ // Intentionally left empty.
+ }
+
+ @Override
+ public boolean emitsIR() {
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index b585cb6..e9cefb7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -6,6 +6,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -127,4 +131,10 @@
throw new Unreachable("Unexpected CfNumberConversion opcode " + opcode);
}
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int source = state.pop().register;
+ builder.addConversion(to, from, state.push(ValueType.fromNumericType(to)).register, source);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 169d252..7508ea0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -5,6 +5,9 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
@@ -31,4 +34,10 @@
public Position getPosition() {
return position;
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ state.setPosition(position);
+ builder.addDebugPosition(position);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
index 7993919..532800d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturn.java
@@ -6,6 +6,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -48,4 +52,15 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public boolean isReturn() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ Slot pop = state.pop();
+ builder.addReturn(pop.type, pop.register);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
index 7d98073..cac89e0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfReturnVoid.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -19,4 +22,14 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public boolean isReturn() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ builder.addReturn();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index efda3f6..733f2d9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -4,8 +4,13 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -78,4 +83,162 @@
public Opcode getOpcode() {
return opcode;
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ switch (opcode) {
+ case Pop:
+ {
+ Slot pop = state.pop();
+ assert !pop.type.isWide();
+ break;
+ }
+ case Pop2:
+ {
+ Slot value = state.pop();
+ if (!value.type.isWide()) {
+ state.pop();
+ throw new Unimplemented("Building IR for Pop2 of narrow value not supported");
+ }
+ break;
+ }
+ case Dup:
+ {
+ Slot dupValue = state.peek();
+ assert !dupValue.type.isWide();
+ builder.addMove(dupValue.type, state.push(dupValue).register, dupValue.register);
+ break;
+ }
+ case DupX1:
+ {
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ assert !value1.type.isWide();
+ assert !value2.type.isWide();
+ dupX1(builder, state, value1, value2);
+ break;
+ }
+ case DupX2:
+ {
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ assert !value1.type.isWide();
+ if (value2.type.isWide()) {
+ dupX1(builder, state, value1, value2);
+ throw new Unimplemented("Building IR for DupX2 of wide value not supported");
+ } else {
+ Slot value3 = state.pop();
+ assert !value3.type.isWide();
+ // Input stack: ..., A:value3, B:value2, C:value1
+ Slot outValue1 = state.push(value1);
+ Slot outValue3 = state.push(value3);
+ Slot outValue2 = state.push(value2);
+ Slot outValue1Copy = state.push(value1);
+ // Output stack: ..., A:outValue1, B:outValue3, C:outValue2, D:outValue1Copy
+ // Move D(outValue1Copy) <- C(value1)
+ builder.addMove(value1.type, outValue1Copy.register, value1.register);
+ // Move C(outValue2) <- B(value2)
+ builder.addMove(value2.type, outValue2.register, value2.register);
+ // Move B(outValue3) <- A(value3)
+ builder.addMove(value3.type, outValue3.register, value3.register);
+ // Move A(outValue1) <- D(outValue1Copy)
+ builder.addMove(value1.type, outValue1.register, outValue1Copy.register);
+ }
+ break;
+ }
+ case Dup2:
+ {
+ Slot value1 = state.peek();
+ if (value1.type.isWide()) {
+ builder.addMove(value1.type, state.push(value1).register, value1.register);
+ } else {
+ Slot value2 = state.peek(1);
+ builder.addMove(value2.type, state.push(value2).register, value2.register);
+ builder.addMove(value1.type, state.push(value1).register, value1.register);
+ }
+ break;
+ }
+ case Dup2X1:
+ {
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ assert !value2.type.isWide();
+ if (value1.type.isWide()) {
+ dupX1(builder, state, value1, value2);
+ } else {
+ // Input stack: ..., A:value3, B:value2, C:value1
+ Slot value3 = state.pop();
+ assert !value3.type.isWide();
+ Slot outValue2 = state.push(value2);
+ Slot outValue1 = state.push(value1);
+ Slot outValue3 = state.push(value3);
+ Slot outValue2Copy = state.push(value2);
+ Slot outValue1Copy = state.push(value1);
+ // Output: ..., A:outValue2, B:outValue1, C:outValue3, D:outValue2Copy, E:outValue1Copy
+ // Move E(outValue1Copy) <- C(value1)
+ builder.addMove(value1.type, outValue1Copy.register, value1.register);
+ // Move D(outValue2Copy) <- B(value2)
+ builder.addMove(value2.type, outValue2Copy.register, value2.register);
+ // Move C(outValue3) <- A(value3)
+ builder.addMove(value3.type, outValue3.register, value3.register);
+ // Move B(outValue1) <- E(outValue1Copy)
+ builder.addMove(value1.type, outValue1.register, outValue1Copy.register);
+ // Move A(outValue2) <- D(outValue2Copy)
+ builder.addMove(value2.type, outValue2.register, outValue2Copy.register);
+ throw new Unimplemented("Building IR for Dup2X1 narrow not supported");
+ }
+ break;
+ }
+ case Dup2X2:
+ {
+ // Input stack:
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ assert !value2.type.isWide();
+ if (value1.type.isWide()) {
+ // Input stack: ..., value2, value1
+ dupX1(builder, state, value1, value2);
+ // Output stack: ..., value1, value2, value1
+ throw new Unimplemented("Building IR for Dup2X2 wide not supported");
+ } else {
+ throw new Unimplemented("Building IR for Dup2X2 narrow not supported");
+ }
+ // break;
+ }
+ case Swap:
+ {
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ assert !value1.type.isWide();
+ assert !value2.type.isWide();
+ // Input stack: ..., value2, value1
+ dupX1(builder, state, value1, value2);
+ // Current stack: ..., value1, value2, value1copy
+ state.pop();
+ // Output stack: ..., value1, value2
+ throw new Unimplemented(
+ "Building IR for CfStackInstruction " + opcode + " not supported");
+ // break;
+ }
+ }
+ }
+
+ private void dupX1(IRBuilder builder, CfState state, Slot inValue1, Slot inValue2) {
+ // Input stack: ..., A:inValue2, B:inValue1 (values already popped)
+ Slot outValue1 = state.push(inValue1);
+ Slot outValue2 = state.push(inValue2);
+ Slot outValue1Copy = state.push(inValue1);
+ // Output stack: ..., A:outValue1, B:outValue2, C:outValue1Copy
+ // Move C(outValue1Copy) <- B(inValue1)
+ builder.addMove(inValue1.type, outValue1Copy.register, inValue1.register);
+ // Move B(outValue2) <- A(inValue2)
+ builder.addMove(inValue2.type, outValue2.register, inValue2.register);
+ // Move A(outValue1) <- C(outValue1Copy)
+ builder.addMove(outValue1Copy.type, outValue1.register, outValue1Copy.register);
+ }
+
+ @Override
+ public boolean emitsIR() {
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStore.java b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
index 817045c..f364eaa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStore.java
@@ -6,6 +6,10 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -54,4 +58,15 @@
public int getLocalIndex() {
return var;
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ Slot pop = state.pop();
+ builder.addMove(type, state.write(var, pop).register, pop.register);
+ }
+
+ @Override
+ public boolean emitsIR() {
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index 1dc72d4..e0ed773 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
@@ -41,7 +45,7 @@
return new IntArrayList(keys);
}
- public List<CfLabel> getTargets() {
+ public List<CfLabel> getSwitchTargets() {
return targets;
}
@@ -67,4 +71,14 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ int[] labelOffsets = new int[targets.size()];
+ for (int i = 0; i < targets.size(); i++) {
+ labelOffsets[i] = code.getLabelOffset(targets.get(i));
+ }
+ Slot value = state.pop();
+ builder.addSwitch(value.register, keys, code.getLabelOffset(defaultTarget), labelOffsets);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
index ef73d10..483d3a8 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfThrow.java
@@ -4,6 +4,10 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -19,4 +23,15 @@
public void print(CfPrinter printer) {
printer.print(this);
}
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+ Slot exception = state.pop();
+ builder.addThrow(exception.register);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
index 6dceda0..5c1aa30 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -21,6 +21,14 @@
this.end = end;
this.guards = guards;
this.targets = targets;
+ assert verifyAllNonNull(guards);
+ }
+
+ private static boolean verifyAllNonNull(List<DexType> types) {
+ for (DexType type : types) {
+ assert type != null;
+ }
+ return true;
}
public static CfTryCatch fromBuilder(
diff --git a/src/main/java/com/android/tools/r8/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
index aba9e08..6a1bef5 100644
--- a/src/main/java/com/android/tools/r8/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/code/ConstMethodType.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.ObjectToOffsetMapping;
import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.ClassNameMapper;
import java.nio.ShortBuffer;
@@ -56,6 +57,11 @@
}
@Override
+ public void registerUse(UseRegistry registry) {
+ registry.registerProto(getMethodType());
+ }
+
+ @Override
public void write(ShortBuffer dest, ObjectToOffsetMapping mapping) {
int index = BBBB.getOffset(mapping);
if (index != (index & 0xffff)) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 2ba0890..55bc2db 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -62,6 +63,11 @@
return app.definitionFor(type);
}
+ public Origin originFor(DexType type) {
+ DexClass definition = app.definitionFor(type);
+ return definition == null ? Origin.unknown() : definition.origin;
+ }
+
public DexEncodedMethod definitionFor(DexMethod method) {
return (DexEncodedMethod) getDefinitions(method.getHolder()).get(method);
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index ab1eca5..1ac2246 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -22,8 +22,11 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Collections;
import java.util.LinkedList;
@@ -120,6 +123,29 @@
}
@Override
+ public int estimatedSizeForInlining() {
+ return countNonStackOperations(Integer.MAX_VALUE);
+ }
+
+ @Override
+ public boolean estimatedSizeForInliningAtMost(int threshold) {
+ return countNonStackOperations(threshold) <= threshold;
+ }
+
+ private int countNonStackOperations(int threshold) {
+ int result = 0;
+ for (CfInstruction instruction : instructions) {
+ if (instruction.emitsIR()) {
+ result++;
+ if (result > threshold) {
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
public boolean isCfCode() {
return true;
}
@@ -183,7 +209,7 @@
}
@Override
- public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
throws ApiLevelException {
if (instructions.size() == 2
&& instructions.get(0) instanceof CfConstNull
@@ -200,7 +226,7 @@
LinkedList<BasicBlock> blocks = new LinkedList<>(Collections.singleton(block));
return new IRCode(options, encodedMethod, blocks, null, false);
}
- throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
+ return internalBuild(encodedMethod, options, null, null, origin);
}
@Override
@@ -208,9 +234,29 @@
DexEncodedMethod encodedMethod,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
- Position callerPosition)
+ Position callerPosition,
+ Origin origin)
throws ApiLevelException {
- throw new Unimplemented("Converting Java class-file bytecode to IR not yet supported");
+ assert valueNumberGenerator != null;
+ assert callerPosition != null;
+ return internalBuild(encodedMethod, options, valueNumberGenerator, callerPosition, origin);
+ }
+
+ private IRCode internalBuild(
+ DexEncodedMethod encodedMethod,
+ InternalOptions options,
+ ValueNumberGenerator generator,
+ Position callerPosition,
+ Origin origin)
+ throws ApiLevelException {
+ assert !options.isGeneratingDex() || !encodedMethod.accessFlags.isSynchronized()
+ : "Converting CfCode to IR not supported for DEX output of synchronized methods.";
+ CfSourceCode source = new CfSourceCode(this, encodedMethod, callerPosition, origin);
+ IRBuilder builder =
+ (generator == null)
+ ? new IRBuilder(encodedMethod, source, options)
+ : new IRBuilder(encodedMethod, source, options, generator);
+ return builder.build();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index db9306d..5a4faff 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -12,18 +12,21 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
public abstract class Code extends CachedHashValueDexItem {
- public abstract IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ public abstract IRCode buildIR(
+ DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
throws ApiLevelException;
public IRCode buildInliningIR(
DexEncodedMethod encodedMethod,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
- Position callerPosition)
+ Position callerPosition,
+ Origin origin)
throws ApiLevelException {
throw new Unreachable("Unexpected attempt to build IR graph for inlining from: "
+ getClass().getCanonicalName());
@@ -52,10 +55,16 @@
return false;
}
+ /** Estimate the number of IR instructions emitted by buildIR(). */
public int estimatedSizeForInlining() {
return Integer.MAX_VALUE;
}
+ /** Compute estimatedSizeForInlining() <= threshold. */
+ public boolean estimatedSizeForInliningAtMost(int threshold) {
+ return estimatedSizeForInlining() <= threshold;
+ }
+
public CfCode asCfCode() {
throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()");
}
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 a023357..d9560c8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.conversion.DexSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.StringUtils;
@@ -163,7 +164,7 @@
}
@Override
- public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
throws ApiLevelException {
DexSourceCode source =
new DexSourceCode(
@@ -177,7 +178,8 @@
DexEncodedMethod encodedMethod,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
- Position callerPosition)
+ Position callerPosition,
+ Origin origin)
throws ApiLevelException {
DexSourceCode source =
new DexSourceCode(
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index d6bde0b..d514a9a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -42,6 +42,7 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Arrays;
import java.util.Collections;
@@ -227,20 +228,23 @@
compilationState = CompilationState.NOT_PROCESSED;
}
- public IRCode buildIR(InternalOptions options) throws ApiLevelException {
- return code == null ? null : code.buildIR(this, options);
+ public IRCode buildIR(InternalOptions options, Origin origin) throws ApiLevelException {
+ return code == null ? null : code.buildIR(this, options, origin);
}
public IRCode buildInliningIRForTesting(
InternalOptions options, ValueNumberGenerator valueNumberGenerator)
throws ApiLevelException {
- return buildInliningIR(options, valueNumberGenerator, null);
+ return buildInliningIR(options, valueNumberGenerator, null, Origin.unknown());
}
public IRCode buildInliningIR(
- InternalOptions options, ValueNumberGenerator valueNumberGenerator, Position callerPosition)
+ InternalOptions options,
+ ValueNumberGenerator valueNumberGenerator,
+ Position callerPosition,
+ Origin origin)
throws ApiLevelException {
- return code.buildInliningIR(this, options, valueNumberGenerator, callerPosition);
+ return code.buildInliningIR(this, options, valueNumberGenerator, callerPosition, origin);
}
public void setCode(Code code) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 9e77dd5..b971ab4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -125,6 +125,8 @@
public final DexString getMethodName = createString("getMethod");
public final DexString getDeclaredMethodName = createString("getDeclaredMethod");
public final DexString assertionsDisabled = createString("$assertionsDisabled");
+ public final DexString invokeMethodName = createString("invoke");
+ public final DexString invokeExactMethodName = createString("invokeExact");
public final DexString stringDescriptor = createString("Ljava/lang/String;");
public final DexString stringArrayDescriptor = createString("[Ljava/lang/String;");
@@ -209,6 +211,7 @@
public final AtomicFieldUpdaterMethods atomicFieldUpdaterMethods =
new AtomicFieldUpdaterMethods();
public final Kotlin kotlin;
+ public final PolymorphicMethods polymorphicMethods = new PolymorphicMethods();
// Dex system annotations.
// See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation
@@ -471,6 +474,75 @@
}
}
+ public class PolymorphicMethods {
+
+ private final DexProto signature = createProto(objectType, objectArrayType);
+ private final DexProto setSignature = createProto(voidType, objectArrayType);
+ private final DexProto compareAndSetSignature = createProto(booleanType, objectArrayType);
+
+ private final Set<DexString> varHandleMethods =
+ createStrings(
+ "compareAndExchange",
+ "compareAndExchangeAcquire",
+ "compareAndExchangeRelease",
+ "get",
+ "getAcquire",
+ "getAndAdd",
+ "getAndAddAcquire",
+ "getAndAddRelease",
+ "getAndBitwiseAnd",
+ "getAndBitwiseAndAcquire",
+ "getAndBitwiseAndRelease",
+ "getAndBitwiseOr",
+ "getAndBitwiseOrAcquire",
+ "getAndBitwiseOrRelease",
+ "getAndBitwiseXor",
+ "getAndBitwiseXorAcquire",
+ "getAndBitwiseXorRelease",
+ "getAndSet",
+ "getAndSetAcquire",
+ "getAndSetRelease",
+ "getOpaque",
+ "getVolatile");
+
+ private final Set<DexString> varHandleSetMethods =
+ createStrings("set", "setOpaque", "setRelease", "setVolatile");
+
+ private final Set<DexString> varHandleCompareAndSetMethods =
+ createStrings(
+ "compareAndSet",
+ "weakCompareAndSet",
+ "weakCompareAndSetAcquire",
+ "weakCompareAndSetPlain",
+ "weakCompareAndSetRelease");
+
+ public DexMethod canonicalize(DexMethod invokeProto) {
+ if (invokeProto.holder == methodHandleType) {
+ if (invokeProto.name == invokeMethodName || invokeProto.name == invokeExactMethodName) {
+ return createMethod(methodHandleType, signature, invokeProto.name);
+ }
+ } else if (invokeProto.holder == varHandleType) {
+ if (varHandleMethods.contains(invokeProto.name)) {
+ return createMethod(varHandleType, signature, invokeProto.name);
+ } else if (varHandleSetMethods.contains(invokeProto.name)) {
+ return createMethod(varHandleType, setSignature, invokeProto.name);
+ } else if (varHandleCompareAndSetMethods.contains(invokeProto.name)) {
+ return createMethod(varHandleType, compareAndSetSignature, invokeProto.name);
+ }
+ }
+ return null;
+ }
+
+ private Set<DexString> createStrings(String... strings) {
+ IdentityHashMap<DexString, DexString> map = new IdentityHashMap<>();
+ for (String string : strings) {
+ DexString dexString = createString(string);
+ map.put(dexString, dexString);
+ }
+ return map.keySet();
+ }
+ }
+
private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
assert item != null;
assert !DexItemFactory.isInternalSentinel(item);
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 dc75307..6c414ac 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -104,7 +104,7 @@
}
@Override
- public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
throws ApiLevelException {
triggerDelayedParsingIfNeccessary();
return options.debug
@@ -117,7 +117,8 @@
DexEncodedMethod encodedMethod,
InternalOptions options,
ValueNumberGenerator generator,
- Position callerPosition)
+ Position callerPosition,
+ Origin origin)
throws ApiLevelException {
assert generator != null;
triggerDelayedParsingIfNeccessary();
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index a864aba..e816bd5 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -141,9 +141,19 @@
}
@Override
- public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ public int estimatedSizeForInlining() {
+ return asCfCode().estimatedSizeForInlining();
+ }
+
+ @Override
+ public boolean estimatedSizeForInliningAtMost(int threshold) {
+ return asCfCode().estimatedSizeForInliningAtMost(threshold);
+ }
+
+ @Override
+ public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
throws ApiLevelException {
- return asCfCode().buildIR(encodedMethod, options);
+ return asCfCode().buildIR(encodedMethod, options, origin);
}
@Override
@@ -151,9 +161,11 @@
DexEncodedMethod encodedMethod,
InternalOptions options,
ValueNumberGenerator valueNumberGenerator,
- Position callerPosition)
+ Position callerPosition,
+ Origin origin)
throws ApiLevelException {
- return asCfCode().buildInliningIR(encodedMethod, options, valueNumberGenerator, callerPosition);
+ return asCfCode().buildInliningIR(
+ encodedMethod, options, valueNumberGenerator, callerPosition, origin);
}
@Override
@@ -302,7 +314,7 @@
private DexType createTypeFromInternalType(String local) {
assert local.indexOf('.') == -1;
- return factory.createType("L" + local + ";");
+ return factory.createType(Type.getObjectType(local).getDescriptor());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index d59df07..113d522 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -424,6 +424,10 @@
this.number = number;
}
+ public String getNumberAsString() {
+ return number >= 0 ? "" + number : "<unknown>";
+ }
+
public int numberInstructions(int nextInstructionNumber) {
for (Instruction instruction : instructions) {
instruction.setNumber(nextInstructionNumber);
@@ -826,7 +830,7 @@
StringBuilder builder, List<BasicBlock> list, Function<BasicBlock, String> postfix) {
if (list.size() > 0) {
for (BasicBlock block : list) {
- builder.append(block.number >= 0 ? block.number : "<unknown>");
+ builder.append(block.getNumberAsString());
builder.append(postfix.apply(block));
builder.append(' ');
}
@@ -901,16 +905,15 @@
int lineColumn = 0;
int numberColumn = 0;
for (Instruction instruction : instructions) {
- lineColumn = Math.max(lineColumn, instruction.getPosition().toString().length());
+ lineColumn = Math.max(lineColumn, instruction.getPositionAsString().length());
numberColumn = Math.max(numberColumn, digits(instruction.getNumber()));
}
- Position currentPosition = null;
+ String currentPosition = null;
for (Instruction instruction : instructions) {
if (lineColumn > 0) {
String line = "";
- if (!instruction.getPosition().equals(currentPosition)) {
- currentPosition = instruction.getPosition();
- line = currentPosition.toString();
+ if (!instruction.getPositionAsString().equals(currentPosition)) {
+ line = currentPosition = instruction.getPositionAsString();
}
StringUtils.appendLeftPadded(builder, line, lineColumn + 1);
builder.append(": ");
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index b763298..cd5fcc8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -65,7 +65,7 @@
@Override
public String toString() {
if (getBlock() != null && !getBlock().getSuccessors().isEmpty()) {
- return super.toString() + "block " + blockNumberToString(getTarget());
+ return super.toString() + "block " + getTarget().getNumberAsString();
}
return super.toString() + "block <unknown>";
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 0235a14..2e1609d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -130,9 +130,9 @@
+ " "
+ type
+ " block "
- + blockNumberToString(getTrueTarget())
+ + getTrueTarget().getNumberAsString()
+ " (fallthrough "
- + blockNumberToString(fallthroughBlock())
+ + fallthroughBlock().getNumberAsString()
+ ")";
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 1c3423c..999699c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -72,6 +72,10 @@
this.position = position;
}
+ public String getPositionAsString() {
+ return position == null ? "???" : position.toString();
+ }
+
public List<Value> inValues() {
return inValues;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index 349d5df..db151c6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -51,11 +51,4 @@
return Constraint.ALWAYS;
}
- static String blockNumberToString(BasicBlock block) {
- try {
- return "" + block.getNumber();
- } catch (AssertionError e) {
- return "<invalid>";
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 50e59f1..871500c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -266,7 +266,7 @@
builder.append(" ");
builder.append(getKey(i));
builder.append(" -> ");
- builder.append(blockNumberToString(targetBlock(i)));
+ builder.append(targetBlock(i).getNumberAsString());
builder.append("\n");
}
builder.append(" F -> ");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
new file mode 100644
index 0000000..382a0a9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -0,0 +1,627 @@
+// 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.ir.conversion;
+
+import static it.unimi.dsi.fastutil.ints.Int2ObjectSortedMaps.emptyMap;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfSwitch;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
+import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CfState.Snapshot;
+import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
+import com.android.tools.r8.origin.Origin;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ReferenceSet;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CfSourceCode implements SourceCode {
+
+ private BlockInfo currentBlockInfo;
+
+ private static class TryHandlerList {
+
+ public final int startOffset;
+ public final int endOffset;
+ public final List<DexType> guards;
+ public final IntList offsets;
+
+ TryHandlerList(int startOffset, int endOffset, List<DexType> guards, IntList offsets) {
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ this.guards = guards;
+ this.offsets = offsets;
+ }
+
+ boolean validFor(int instructionOffset) {
+ return startOffset <= instructionOffset && instructionOffset < endOffset;
+ }
+
+ boolean isEmpty() {
+ assert guards.isEmpty() == offsets.isEmpty();
+ return guards.isEmpty();
+ }
+
+ static TryHandlerList computeTryHandlers(
+ int instructionOffset,
+ List<CfTryCatch> tryCatchRanges,
+ Reference2IntMap<CfLabel> labelOffsets) {
+ int startOffset = Integer.MIN_VALUE;
+ int endOffset = Integer.MAX_VALUE;
+ List<DexType> guards = new ArrayList<>();
+ IntList offsets = new IntArrayList();
+ ReferenceSet<DexType> seen = new ReferenceOpenHashSet<>();
+ for (CfTryCatch tryCatch : tryCatchRanges) {
+ int start = labelOffsets.getInt(tryCatch.start);
+ int end = labelOffsets.getInt(tryCatch.end);
+ if (start > instructionOffset) {
+ endOffset = Math.min(endOffset, start);
+ continue;
+ } else if (instructionOffset >= end) {
+ startOffset = Math.max(startOffset, end);
+ continue;
+ }
+ startOffset = Math.max(startOffset, start);
+ endOffset = Math.min(endOffset, end);
+ boolean seenCatchAll = false;
+ for (int i = 0; i < tryCatch.guards.size() && !seenCatchAll; i++) {
+ DexType guard = tryCatch.guards.get(i);
+ if (seen.add(guard)) {
+ guards.add(guard);
+ offsets.add(labelOffsets.getInt(tryCatch.targets.get(i)));
+ seenCatchAll = guard == DexItemFactory.catchAllType;
+ }
+ }
+ if (seenCatchAll) {
+ break;
+ }
+ }
+ return new TryHandlerList(startOffset, endOffset, guards, offsets);
+ }
+ }
+
+ private static class LocalVariableList {
+
+ public static final LocalVariableList EMPTY = new LocalVariableList(0, 0, emptyMap());
+ public final int startOffset;
+ public final int endOffset;
+ public final Int2ObjectMap<DebugLocalInfo> locals;
+
+ private LocalVariableList(
+ int startOffset, int endOffset, Int2ObjectMap<DebugLocalInfo> locals) {
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ this.locals = locals;
+ }
+
+ static LocalVariableList compute(
+ int instructionOffset,
+ List<CfCode.LocalVariableInfo> locals,
+ Reference2IntMap<CfLabel> labelOffsets) {
+ int startOffset = Integer.MIN_VALUE;
+ int endOffset = Integer.MAX_VALUE;
+ Int2ObjectMap<DebugLocalInfo> currentLocals = null;
+ for (LocalVariableInfo local : locals) {
+ int start = labelOffsets.getInt(local.getStart());
+ int end = labelOffsets.getInt(local.getEnd());
+ if (start > instructionOffset) {
+ endOffset = Math.min(endOffset, start);
+ continue;
+ } else if (instructionOffset >= end) {
+ startOffset = Math.max(startOffset, end);
+ continue;
+ }
+ if (currentLocals == null) {
+ currentLocals = new Int2ObjectOpenHashMap<>();
+ }
+ startOffset = Math.max(startOffset, start);
+ endOffset = Math.min(endOffset, end);
+ currentLocals.put(local.getIndex(), local.getLocal());
+ }
+ return new LocalVariableList(
+ startOffset, endOffset, currentLocals == null ? emptyMap() : currentLocals);
+ }
+
+ boolean validFor(int instructionOffset) {
+ return startOffset <= instructionOffset && instructionOffset < endOffset;
+ }
+
+ public DebugLocalInfo getLocal(int register) {
+ return locals.get(register);
+ }
+
+ public Int2ObjectOpenHashMap<DebugLocalInfo> merge(LocalVariableList other) {
+ return merge(this, other);
+ }
+
+ private static Int2ObjectOpenHashMap<DebugLocalInfo> merge(
+ LocalVariableList a, LocalVariableList b) {
+ if (a.locals.size() > b.locals.size()) {
+ return merge(b, a);
+ }
+ Int2ObjectOpenHashMap<DebugLocalInfo> result = new Int2ObjectOpenHashMap<>();
+ for (Entry<DebugLocalInfo> local : a.locals.int2ObjectEntrySet()) {
+ if (local.getValue().equals(b.getLocal(local.getIntKey()))) {
+ result.put(local.getIntKey(), local.getValue());
+ }
+ }
+ return result;
+ }
+ }
+
+ private CfState state;
+ private final CfCode code;
+ private final DexEncodedMethod method;
+ private final Position callerPosition;
+ private final Origin origin;
+
+ // Synthetic position with line = 0.
+ private final Position preamblePosition;
+ private final Reference2IntMap<CfLabel> labelOffsets = new Reference2IntOpenHashMap<>();
+ private TryHandlerList cachedTryHandlerList;
+ private LocalVariableList cachedLocalVariableList;
+ private int currentInstructionIndex;
+ private boolean inPrelude;
+ private Int2ObjectMap<DebugLocalInfo> incomingLocals;
+ private Int2ObjectMap<DebugLocalInfo> outgoingLocals;
+ private Int2ReferenceMap<Int2ObjectMap<DebugLocalInfo>> definitelyLiveIncomingLocals =
+ new Int2ReferenceOpenHashMap<>();
+ private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
+
+ public CfSourceCode(
+ CfCode code, DexEncodedMethod method, Position callerPosition, Origin origin) {
+ this.code = code;
+ this.method = method;
+ this.callerPosition = callerPosition;
+ this.origin = origin;
+ preamblePosition = Position.synthetic(0, method.method, null);
+ for (int i = 0; i < code.getInstructions().size(); i++) {
+ CfInstruction instruction = code.getInstructions().get(i);
+ if (instruction instanceof CfLabel) {
+ labelOffsets.put((CfLabel) instruction, instructionOffset(i));
+ }
+ }
+ this.state = new CfState(origin);
+ }
+
+ @Override
+ public int instructionCount() {
+ return code.getInstructions().size();
+ }
+
+ @Override
+ public int instructionIndex(int instructionOffset) {
+ return instructionOffset;
+ }
+
+ @Override
+ public int instructionOffset(int instructionIndex) {
+ return instructionIndex;
+ }
+
+ @Override
+ public boolean verifyRegister(int register) {
+ return true;
+ }
+
+ @Override
+ public void setUp() {}
+
+ @Override
+ public void clear() {}
+
+ @Override
+ public int traceInstruction(int instructionIndex, IRBuilder builder) {
+ CfInstruction instruction = code.getInstructions().get(instructionIndex);
+ if (instruction.canThrow()) {
+ TryHandlerList tryHandlers = getTryHandlers(instructionIndex);
+ if (!tryHandlers.isEmpty()) {
+ // Ensure the block starts at the start of the try-range (don't enqueue, not a target).
+ builder.ensureBlockWithoutEnqueuing(tryHandlers.startOffset);
+ IntSet seen = new IntOpenHashSet();
+ for (int offset : tryHandlers.offsets) {
+ if (seen.add(offset)) {
+ builder.ensureExceptionalSuccessorBlock(instructionIndex, offset);
+ }
+ }
+ if (!(instruction instanceof CfThrow)) {
+ builder.ensureNormalSuccessorBlock(instructionIndex, instructionIndex + 1);
+ }
+ return instructionIndex;
+ }
+ // If the throwable instruction is "throw" it closes the block.
+ return (instruction instanceof CfThrow) ? instructionIndex : -1;
+ }
+ if (isControlFlow(instruction)) {
+ for (int target : getTargets(instructionIndex)) {
+ builder.ensureNormalSuccessorBlock(instructionIndex, target);
+ }
+ return instructionIndex;
+ }
+ return -1;
+ }
+
+ private TryHandlerList getTryHandlers(int instructionOffset) {
+ if (cachedTryHandlerList == null || !cachedTryHandlerList.validFor(instructionOffset)) {
+ cachedTryHandlerList =
+ TryHandlerList.computeTryHandlers(
+ instructionOffset, code.getTryCatchRanges(), labelOffsets);
+ }
+ return cachedTryHandlerList;
+ }
+
+ private LocalVariableList getLocalVariables(int instructionOffset) {
+ if (cachedLocalVariableList == null || !cachedLocalVariableList.validFor(instructionOffset)) {
+ cachedLocalVariableList =
+ LocalVariableList.compute(instructionOffset, code.getLocalVariables(), labelOffsets);
+ }
+ return cachedLocalVariableList;
+ }
+
+ private int[] getTargets(int instructionIndex) {
+ CfInstruction instruction = code.getInstructions().get(instructionIndex);
+ assert isControlFlow(instruction);
+ CfLabel target = instruction.getTarget();
+ if (instruction.isReturn() || instruction instanceof CfThrow) {
+ assert target == null;
+ return new int[] {};
+ }
+ assert instruction instanceof CfSwitch || target != null
+ : "getTargets(): Non-control flow instruction " + instruction.getClass();
+ if (instruction instanceof CfSwitch) {
+ CfSwitch cfSwitch = (CfSwitch) instruction;
+ List<CfLabel> targets = cfSwitch.getSwitchTargets();
+ int[] res = new int[targets.size() + 1];
+ for (int i = 0; i < targets.size(); i++) {
+ res[i] = labelOffsets.getInt(targets.get(i));
+ }
+ res[targets.size()] = labelOffsets.getInt(cfSwitch.getDefaultTarget());
+ return res;
+ }
+ int targetIndex = labelOffsets.getInt(target);
+ if (instruction instanceof CfGoto) {
+ return new int[] {targetIndex};
+ }
+ assert instruction.isConditionalJump();
+ return new int[] {instructionIndex + 1, targetIndex};
+ }
+
+ @Override
+ public void buildPrelude(IRBuilder builder) {
+ assert !inPrelude;
+ inPrelude = true;
+ state.buildPrelude();
+ setLocalVariableLists();
+ buildArgumentInstructions(builder);
+ recordStateForTarget(0, state.getSnapshot());
+ // TODO: addDebugLocalUninitialized + addDebugLocalStart for non-argument locals live at 0
+ // TODO: Generate method synchronization
+ inPrelude = false;
+ }
+
+ private void buildArgumentInstructions(IRBuilder builder) {
+ int argumentRegister = 0;
+ if (!isStatic()) {
+ DexType type = method.method.holder;
+ state.write(argumentRegister, type);
+ builder.addThisArgument(argumentRegister++);
+ }
+ for (DexType type : method.method.proto.parameters.values) {
+ state.write(argumentRegister, type);
+ if (type.isBooleanType()) {
+ builder.addBooleanNonThisArgument(argumentRegister++);
+ } else {
+ ValueType valueType = ValueType.fromDexType(type);
+ builder.addNonThisArgument(argumentRegister, valueType);
+ argumentRegister += valueType.requiredRegisters();
+ }
+ }
+ }
+
+ private boolean isStatic() {
+ return method.accessFlags.isStatic();
+ }
+
+ @Override
+ public void buildPostlude(IRBuilder builder) {
+ // Since we're generating classfiles, we never need to synthesize monitor enter/exit.
+ }
+
+ @Override
+ public void buildInstruction(
+ IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
+ throws ApiLevelException {
+ CfInstruction instruction = code.getInstructions().get(instructionIndex);
+ currentInstructionIndex = instructionIndex;
+ if (firstBlockInstruction) {
+ currentBlockInfo = builder.getCFG().get(instructionIndex);
+ state.reset(incomingState.get(instructionIndex), instructionIndex == 0);
+ }
+ setLocalVariableLists();
+ readEndingLocals(builder);
+ if (isControlFlow(instruction)) {
+ ensureDebugValueLivenessControl(builder);
+ instruction.buildIR(builder, state, this);
+ Snapshot stateSnapshot = state.getSnapshot();
+ for (int target : getTargets(instructionIndex)) {
+ recordStateForTarget(target, stateSnapshot);
+ }
+ state.clear();
+ } else {
+ if (currentBlockInfo != null && instruction.canThrow()) {
+ Snapshot exceptionTransfer =
+ state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
+ for (int target : currentBlockInfo.exceptionalSuccessors) {
+ recordStateForTarget(target, exceptionTransfer);
+ }
+ }
+ instruction.buildIR(builder, state, this);
+ ensureDebugValueLiveness(builder);
+ if (builder.getCFG().containsKey(currentInstructionIndex + 1)) {
+ recordStateForTarget(currentInstructionIndex + 1, state.getSnapshot());
+ }
+ }
+ }
+
+ private void recordStateForTarget(int target, Snapshot snapshot) {
+ Snapshot existing = incomingState.get(target);
+ Snapshot merged = CfState.merge(existing, snapshot, origin);
+ if (merged != existing) {
+ incomingState.put(target, merged);
+ }
+ }
+
+ public int getCurrentInstructionIndex() {
+ return currentInstructionIndex;
+ }
+
+ public int getLabelOffset(CfLabel label) {
+ assert labelOffsets.containsKey(label);
+ return labelOffsets.getInt(label);
+ }
+
+ public void setStateFromFrame(CfFrame frame) {
+ Int2ReferenceSortedMap<FrameType> frameLocals = frame.getLocals();
+ DexType[] locals = new DexType[frameLocals.isEmpty() ? 0 : frameLocals.lastIntKey() + 1];
+ DexType[] stack = new DexType[frame.getStack().size()];
+ for (Int2ReferenceMap.Entry<FrameType> entry : frameLocals.int2ReferenceEntrySet()) {
+ locals[entry.getIntKey()] = convertUninitialized(entry.getValue());
+ }
+ for (int i = 0; i < stack.length; i++) {
+ stack[i] = convertUninitialized(frame.getStack().get(i));
+ }
+ state.setStateFromFrame(locals, stack, getDebugPositionAtOffset(currentInstructionIndex));
+ }
+
+ private DexType convertUninitialized(FrameType type) {
+ if (type.isInitialized()) {
+ return type.getInitializedType();
+ }
+ if (type.isUninitializedNew()) {
+ int labelOffset = getLabelOffset(type.getUninitializedLabel());
+ int insnOffset = labelOffset + 1;
+ while (insnOffset < code.getInstructions().size()) {
+ CfInstruction instruction = code.getInstructions().get(insnOffset);
+ if (!(instruction instanceof CfLabel)
+ && !(instruction instanceof CfFrame)
+ && !(instruction instanceof CfPosition)) {
+ assert instruction instanceof CfNew;
+ break;
+ }
+ insnOffset += 1;
+ }
+ CfInstruction instruction = code.getInstructions().get(insnOffset);
+ assert instruction instanceof CfNew;
+ return ((CfNew) instruction).getType();
+ }
+ if (type.isUninitializedThis()) {
+ return method.method.holder;
+ }
+ assert type.isTop();
+ return null;
+ }
+
+ @Override
+ public void resolveAndBuildSwitch(
+ int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {}
+
+ @Override
+ public void resolveAndBuildNewArrayFilledData(
+ int arrayRef, int payloadOffset, IRBuilder builder) {}
+
+ @Override
+ public DebugLocalInfo getIncomingLocal(int register) {
+ return incomingLocals.get(register);
+ }
+
+ @Override
+ public DebugLocalInfo getOutgoingLocal(int register) {
+ if (inPrelude) {
+ return getIncomingLocal(register);
+ }
+ assert !isControlFlow(code.getInstructions().get(currentInstructionIndex))
+ : "Outgoing local is undefined for control-flow instructions";
+ return outgoingLocals.get(register);
+ }
+
+ private void setLocalVariableLists() {
+ incomingLocals = getLocalVariables(currentInstructionIndex).locals;
+ CfInstruction currentInstruction = code.getInstructions().get(currentInstructionIndex);
+ if (inPrelude) {
+ outgoingLocals = incomingLocals;
+ return;
+ }
+ if (currentInstruction.isReturn() || currentInstruction instanceof CfThrow) {
+ outgoingLocals = emptyMap();
+ return;
+ }
+ if (isControlFlow(currentInstruction)) {
+ // We need to read all locals that are not live on all successors to ensure liveness.
+ // Determine outgoingLocals as the intersection of all successors' locals.
+ outgoingLocals = null;
+ int[] targets = getTargets(currentInstructionIndex);
+ for (int target : targets) {
+ Int2ObjectMap<DebugLocalInfo> locals = getLocalVariables(target).locals;
+ outgoingLocals = intersectMaps(outgoingLocals, locals);
+ }
+ assert outgoingLocals != null;
+ // Pass outgoingLocals to all successors.
+ for (int target : targets) {
+ Int2ObjectMap<DebugLocalInfo> existing = definitelyLiveIncomingLocals.get(target);
+ definitelyLiveIncomingLocals.put(target, intersectMaps(existing, outgoingLocals));
+ }
+ } else {
+ outgoingLocals = getLocalVariables(currentInstructionIndex + 1).locals;
+ }
+ }
+
+ private void readEndingLocals(IRBuilder builder) {
+ if (!outgoingLocals.equals(incomingLocals)) {
+ // Add reads of locals ending after the current instruction.
+ for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+ if (!entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
+ builder.addDebugLocalRead(entry.getIntKey(), entry.getValue());
+ }
+ }
+ }
+ }
+
+ private Int2ObjectMap<DebugLocalInfo> intersectMaps(
+ Int2ObjectMap<DebugLocalInfo> existing, Int2ObjectMap<DebugLocalInfo> update) {
+ assert update != null;
+ if (existing == null) {
+ return update;
+ }
+ if (existing.size() > update.size()) {
+ return intersectMaps(update, existing);
+ }
+ if (existing.equals(update)) {
+ return existing;
+ }
+ Int2ObjectOpenHashMap<DebugLocalInfo> result = new Int2ObjectOpenHashMap<>();
+ for (Entry<DebugLocalInfo> local : existing.int2ObjectEntrySet()) {
+ if (local.getValue().equals(update.get(local.getIntKey()))) {
+ result.put(local.getIntKey(), local.getValue());
+ }
+ }
+ return result;
+ }
+
+ private void ensureDebugValueLiveness(IRBuilder builder) {
+ if (incomingLocals.equals(outgoingLocals)) {
+ return;
+ }
+ for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+ if (entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
+ continue;
+ }
+ builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
+ }
+ for (Entry<DebugLocalInfo> entry : outgoingLocals.int2ObjectEntrySet()) {
+ if (entry.getValue().equals(incomingLocals.get(entry.getIntKey()))) {
+ continue;
+ }
+ builder.addDebugLocalStart(entry.getIntKey(), entry.getValue());
+ }
+ }
+
+ private void ensureDebugValueLivenessControl(IRBuilder builder) {
+ if (incomingLocals.equals(outgoingLocals)) {
+ return;
+ }
+ for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+ if (entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
+ continue;
+ }
+ builder.addDebugLocalRead(entry.getIntKey(), entry.getValue());
+ }
+ assert outgoingLocals
+ .int2ObjectEntrySet()
+ .stream()
+ .allMatch(entry -> entry.getValue().equals(incomingLocals.get(entry.getIntKey())));
+ }
+
+ private boolean isControlFlow(CfInstruction currentInstruction) {
+ return currentInstruction.isReturn()
+ || currentInstruction.getTarget() != null
+ || currentInstruction instanceof CfSwitch
+ || currentInstruction instanceof CfThrow;
+ }
+
+ @Override
+ public CatchHandlers<Integer> getCurrentCatchHandlers() {
+ TryHandlerList tryHandlers = getTryHandlers(instructionOffset(currentInstructionIndex));
+ if (tryHandlers.isEmpty()) {
+ return null;
+ }
+ return new CatchHandlers<>(tryHandlers.guards, tryHandlers.offsets);
+ }
+
+ @Override
+ public int getMoveExceptionRegister() {
+ return CfState.Slot.STACK_OFFSET;
+ }
+
+ @Override
+ public boolean verifyCurrentInstructionCanThrow() {
+ return code.getInstructions().get(currentInstructionIndex).canThrow();
+ }
+
+ @Override
+ public boolean verifyLocalInScope(DebugLocalInfo local) {
+ return false;
+ }
+
+ @Override
+ public Position getDebugPositionAtOffset(int offset) {
+ while (offset + 1 < code.getInstructions().size()) {
+ CfInstruction insn = code.getInstructions().get(offset);
+ if (!(insn instanceof CfLabel) && !(insn instanceof CfFrame)) {
+ break;
+ }
+ offset += 1;
+ }
+ while (offset >= 0 && !(code.getInstructions().get(offset) instanceof CfPosition)) {
+ offset -= 1;
+ }
+ if (offset < 0) {
+ return Position.noneWithMethod(method.method, callerPosition);
+ }
+ return ((CfPosition) code.getInstructions().get(offset)).getPosition();
+ }
+
+ @Override
+ public Position getCurrentPosition() {
+ return state.getPosition();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
new file mode 100644
index 0000000..2ac8e60
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
@@ -0,0 +1,542 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.origin.Origin;
+
+public class CfState {
+
+ private abstract static class SlotType {
+
+ public abstract DexType getPrecise();
+
+ public abstract ValueType getImprecise();
+
+ private static class Precise extends SlotType {
+ private final DexType type;
+
+ Precise(DexType type) {
+ this.type = type;
+ }
+
+ @Override
+ public DexType getPrecise() {
+ return type;
+ }
+
+ @Override
+ public ValueType getImprecise() {
+ return ValueType.fromDexType(type);
+ }
+
+ @Override
+ public String toString() {
+ return "Precise(" + type + ")";
+ }
+ }
+
+ private static class Imprecise extends SlotType {
+
+ private final ValueType type;
+
+ Imprecise(ValueType type) {
+ this.type = type;
+ }
+
+ @Override
+ public DexType getPrecise() {
+ return null;
+ }
+
+ @Override
+ public ValueType getImprecise() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return "Imprecise(" + type + ")";
+ }
+ }
+ }
+
+ private final Origin origin;
+ private Snapshot current;
+
+ public CfState(Origin origin) {
+ this.origin = origin;
+ }
+
+ private static final int MAX_UPDATES = 4;
+
+ public void buildPrelude() {
+ current = new BaseSnapshot();
+ }
+
+ public void clear() {
+ current = null;
+ }
+
+ public void reset(Snapshot snapshot, boolean isMethodEntry) {
+ assert !isMethodEntry || snapshot != null : "Must have snapshot for method entry.";
+ current = snapshot;
+ }
+
+ public void setStateFromFrame(DexType[] locals, DexType[] stack, Position position) {
+ assert current == null || stackHeight() == stack.length;
+ current = new BaseSnapshot(locals, stack, position);
+ }
+
+ public void merge(Snapshot snapshot) {
+ if (current == null) {
+ current = snapshot == null ? new BaseSnapshot() : snapshot;
+ } else {
+ current = merge(current, snapshot, origin);
+ }
+ }
+
+ public Snapshot getSnapshot() {
+ return current;
+ }
+
+ public static Snapshot merge(Snapshot current, Snapshot update, Origin origin) {
+ assert update != null;
+ if (current == null) {
+ return update;
+ }
+ return merge(current.asBase(), update.asBase(), origin);
+ }
+
+ private static Snapshot merge(BaseSnapshot current, BaseSnapshot update, Origin origin) {
+ if (current.stack.length != update.stack.length) {
+ throw new CompilationError(
+ "Different stack heights at jump target: "
+ + current.stack.length
+ + " != "
+ + update.stack.length,
+ origin);
+ }
+ // At this point, JarState checks if `current` has special "NULL" or "BYTE/BOOL" types
+ // that `update` does not have, and if so it computes a refinement.
+ // For now, let's just check that we didn't mix single/wide/reference types.
+ for (int i = 0; i < current.stack.length; i++) {
+ ValueType currentType = current.stack[i].getImprecise();
+ ValueType updateType = update.stack[i].getImprecise();
+ if (!currentType.compatible(updateType)) {
+ throw new CompilationError(
+ "Incompatible types in stack position "
+ + i
+ + ": "
+ + current.stack[i]
+ + " and "
+ + update.stack[i],
+ origin);
+ }
+ }
+ // We could check that locals are compatible, but that doesn't make sense since locals can be
+ // dead at this point.
+ return current;
+ }
+
+ public int stackHeight() {
+ return current.stackHeight();
+ }
+
+ public Slot push(Slot fromSlot) {
+ return push(fromSlot.slotType);
+ }
+
+ public Slot push(DexType type) {
+ return push(new SlotType.Precise(type));
+ }
+
+ public Slot push(ValueType type) {
+ return push(new SlotType.Imprecise(type));
+ }
+
+ private Slot push(SlotType slotType) {
+ Push newSnapshot = new Push(this.current, slotType);
+ updateState(newSnapshot);
+ return current.peek();
+ }
+
+ private void updateState(Snapshot newSnapshot) {
+ current = newSnapshot.updates >= MAX_UPDATES ? new BaseSnapshot(newSnapshot) : newSnapshot;
+ }
+
+ public Slot pop() {
+ Slot top = current.peek();
+ updateState(new Pop(current));
+ return top;
+ }
+
+ public int[] popReverse(int count) {
+ int[] registers = new int[count];
+ for (int i = count - 1; i >= 0; i--) {
+ registers[i] = pop().register;
+ }
+ return registers;
+ }
+
+ public Slot peek() {
+ return current.peek();
+ }
+
+ public Slot peek(int skip) {
+ return current.getStack(current.stackHeight() - 1 - skip);
+ }
+
+ public Slot read(int localIndex) {
+ return current.getLocal(localIndex);
+ }
+
+ public Slot write(int localIndex, DexType type) {
+ return write(localIndex, new SlotType.Precise(type));
+ }
+
+ public Slot write(int localIndex, Slot src) {
+ return write(localIndex, src.slotType);
+ }
+
+ private Slot write(int localIndex, SlotType slotType) {
+ updateState(new Write(current, localIndex, slotType));
+ return current.getLocal(localIndex);
+ }
+
+ public Position getPosition() {
+ return current.getPosition();
+ }
+
+ public void setPosition(Position position) {
+ assert position != null;
+ updateState(new SetPosition(current, position));
+ }
+
+ @Override
+ public String toString() {
+ return new BaseSnapshot(current).toString();
+ }
+
+ public static class Slot {
+
+ public static final int STACK_OFFSET = 100000;
+ public final int register;
+ public final ValueType type;
+ public final DexType preciseType;
+ private final SlotType slotType;
+
+ private Slot(int register, DexType preciseType) {
+ this(register, new SlotType.Precise(preciseType));
+ }
+
+ private Slot(int register, SlotType type) {
+ this.register = register;
+ this.slotType = type;
+ this.type = type.getImprecise();
+ this.preciseType = type.getPrecise();
+ }
+
+ private static Slot stackSlot(int stackPosition, SlotType type) {
+ return new Slot(stackPosition + STACK_OFFSET, type);
+ }
+
+ private int stackPosition() {
+ assert register >= STACK_OFFSET;
+ return register - STACK_OFFSET;
+ }
+
+ @Override
+ public String toString() {
+ return register < STACK_OFFSET
+ ? register + "=" + slotType
+ : "s" + (register - STACK_OFFSET) + "=" + slotType;
+ }
+ }
+
+ public abstract static class Snapshot {
+ final Snapshot parent;
+ final int updates;
+
+ private Snapshot(Snapshot parent, int updates) {
+ this.parent = parent;
+ this.updates = updates;
+ }
+
+ public int stackHeight() {
+ return parent.stackHeight();
+ }
+
+ public int maxLocal() {
+ return parent.maxLocal();
+ }
+
+ public Slot getStack(int i) {
+ return parent.getStack(i);
+ }
+
+ public Slot peek() {
+ return parent.peek();
+ }
+
+ public Slot getLocal(int i) {
+ return parent.getLocal(i);
+ }
+
+ public Position getPosition() {
+ return parent.getPosition();
+ }
+
+ void build(BaseSnapshot base) {
+ parent.build(base);
+ }
+
+ BaseSnapshot asBase() {
+ return new BaseSnapshot(this);
+ }
+
+ public Snapshot exceptionTransfer(DexType throwableType) {
+ BaseSnapshot result = new BaseSnapshot(maxLocal() + 1, 1, getPosition());
+ build(result);
+ result.stack[0] = new SlotType.Precise(throwableType);
+ return result;
+ }
+ }
+
+ private static class BaseSnapshot extends Snapshot {
+ final SlotType[] locals;
+ final SlotType[] stack;
+ final Position position;
+
+ BaseSnapshot() {
+ this(0, 0, Position.none());
+ }
+
+ BaseSnapshot(int locals, int stack, Position position) {
+ super(null, 0);
+ assert position != null;
+ this.locals = new SlotType[locals];
+ this.stack = new SlotType[stack];
+ this.position = position;
+ }
+
+ BaseSnapshot(Snapshot newSnapshot) {
+ this(newSnapshot.maxLocal() + 1, newSnapshot.stackHeight(), newSnapshot.getPosition());
+ newSnapshot.build(this);
+ }
+
+ BaseSnapshot(DexType[] locals, DexType[] stack, Position position) {
+ super(null, 0);
+ assert position != null;
+ this.locals = new SlotType[locals.length];
+ this.stack = new SlotType[stack.length];
+ this.position = position;
+ for (int i = 0; i < locals.length; i++) {
+ this.locals[i] = locals[i] == null ? null : getSlotType(locals[i]);
+ }
+ for (int i = 0; i < stack.length; i++) {
+ assert stack[i] != null;
+ this.stack[i] = getSlotType(stack[i]);
+ }
+ }
+
+ private SlotType getSlotType(DexType local) {
+ return local.toDescriptorString().equals("NULL")
+ ? new SlotType.Imprecise(ValueType.OBJECT)
+ : new SlotType.Precise(local);
+ }
+
+ @Override
+ public int stackHeight() {
+ return stack.length;
+ }
+
+ @Override
+ public int maxLocal() {
+ return locals.length - 1;
+ }
+
+ @Override
+ public Slot getStack(int i) {
+ return Slot.stackSlot(i, stack[i]);
+ }
+
+ @Override
+ public Slot peek() {
+ assert stackHeight() > 0;
+ return getStack(stackHeight() - 1);
+ }
+
+ @Override
+ public Slot getLocal(int i) {
+ return new Slot(i, locals[i]);
+ }
+
+ @Override
+ public Position getPosition() {
+ return position;
+ }
+
+ @Override
+ void build(BaseSnapshot dest) {
+ for (int i = 0; i < locals.length && i < dest.locals.length; i++) {
+ dest.locals[i] = locals[i];
+ }
+ for (int i = 0; i < stack.length && i < dest.stack.length; i++) {
+ dest.stack[i] = stack[i];
+ }
+ }
+
+ @Override
+ BaseSnapshot asBase() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder =
+ new StringBuilder().append("position: ").append(position).append(" stack: [");
+ String sep = "";
+ for (SlotType type : stack) {
+ stringBuilder.append(sep).append(type);
+ sep = ", ";
+ }
+ stringBuilder.append("] locals: [");
+ sep = "";
+ for (int i = 0; i < locals.length; i++) {
+ if (locals[i] != null) {
+ stringBuilder.append(sep).append(i).append(':').append(locals[i]);
+ sep = ", ";
+ }
+ }
+ return stringBuilder.append(']').toString();
+ }
+ }
+
+ private static class Push extends Snapshot {
+
+ private final Slot slot;
+
+ Push(Snapshot parent, SlotType type) {
+ super(parent, parent.updates + 1);
+ slot = Slot.stackSlot(parent.stackHeight(), type);
+ assert 100000 <= slot.register && slot.register < 200000;
+ }
+
+ @Override
+ public int stackHeight() {
+ return slot.stackPosition() + 1;
+ }
+
+ @Override
+ public Slot getStack(int i) {
+ return i == slot.stackPosition() ? peek() : parent.getStack(i);
+ }
+
+ @Override
+ public Slot peek() {
+ return slot;
+ }
+
+ @Override
+ void build(BaseSnapshot base) {
+ parent.build(base);
+ if (slot.stackPosition() < base.stack.length) {
+ base.stack[slot.stackPosition()] = slot.slotType;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return parent.toString() + "; push(" + slot.slotType + ")";
+ }
+ }
+
+ private static class Pop extends Snapshot {
+
+ private final int stackHeight;
+
+ Pop(Snapshot parent) {
+ super(parent, parent.updates + 1);
+ stackHeight = parent.stackHeight() - 1;
+ assert stackHeight >= 0;
+ }
+
+ @Override
+ public int stackHeight() {
+ return stackHeight;
+ }
+
+ @Override
+ public Slot getStack(int i) {
+ assert i < stackHeight;
+ return parent.getStack(i);
+ }
+
+ @Override
+ public Slot peek() {
+ return parent.getStack(stackHeight - 1);
+ }
+
+ @Override
+ public String toString() {
+ return parent.toString() + "; pop";
+ }
+ }
+
+ private static class Write extends Snapshot {
+
+ private final Slot slot;
+
+ Write(Snapshot parent, int slotIndex, SlotType type) {
+ super(parent, parent.updates + 1);
+ slot = new Slot(slotIndex, type);
+ assert 0 <= slotIndex && slotIndex < 100000;
+ }
+
+ @Override
+ public int maxLocal() {
+ return Math.max(slot.register, parent.maxLocal());
+ }
+
+ @Override
+ public Slot getLocal(int i) {
+ return i == slot.register ? slot : parent.getLocal(i);
+ }
+
+ @Override
+ void build(BaseSnapshot base) {
+ parent.build(base);
+ base.locals[slot.register] = slot.slotType;
+ }
+
+ @Override
+ public String toString() {
+ return parent.toString() + "; write " + slot.register + " := " + slot.slotType;
+ }
+ }
+
+ private static class SetPosition extends Snapshot {
+
+ private final Position position;
+
+ SetPosition(Snapshot parent, Position position) {
+ super(parent, parent.updates + 1);
+ this.position = position;
+ }
+
+ @Override
+ public Position getPosition() {
+ return position;
+ }
+
+ @Override
+ public String toString() {
+ return parent.toString() + "; set pos " + position;
+ }
+ }
+}
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 58ed822..1a210b2 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
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItem;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProto;
@@ -113,6 +114,10 @@
public static final int INITIAL_BLOCK_OFFSET = -1;
+ public DexItemFactory getFactory() {
+ return options.itemFactory;
+ }
+
// SSA construction uses a worklist of basic blocks reachable from the entry and their
// instruction offsets.
private static class WorklistItem {
@@ -240,6 +245,35 @@
fallthroughInfo.exceptionalSuccessors = new IntArraySet(this.exceptionalSuccessors);
return fallthroughInfo;
}
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder =
+ new StringBuilder()
+ .append("block ")
+ .append(block.getNumberAsString())
+ .append(" predecessors: ");
+ String sep = "";
+ for (int offset : normalPredecessors) {
+ stringBuilder.append(sep).append(offset);
+ sep = ", ";
+ }
+ for (int offset : exceptionalPredecessors) {
+ stringBuilder.append(sep).append('*').append(offset);
+ sep = ", ";
+ }
+ stringBuilder.append(" successors: ");
+ sep = "";
+ for (int offset : normalSuccessors) {
+ stringBuilder.append(sep).append(offset);
+ sep = ", ";
+ }
+ for (int offset : exceptionalSuccessors) {
+ stringBuilder.append(sep).append('*').append(offset);
+ sep = ", ";
+ }
+ return stringBuilder.toString();
+ }
}
// Mapping from instruction offsets to basic-block targets.
@@ -304,6 +338,10 @@
return targets;
}
+ public DexMethod getMethod() {
+ return method.method;
+ }
+
private void addToWorklist(BasicBlock block, int firstInstructionIndex) {
// TODO(ager): Filter out the ones that are already in the worklist, mark bit in block?
if (!block.isFilled()) {
@@ -615,6 +653,7 @@
assert local != null;
assert local == getOutgoingLocal(register);
ValueType valueType = ValueType.fromDexType(local.type);
+ // TODO(mathiasr): Here we create a Phi with type based on debug info. That's just wrong!
Value incomingValue = readRegisterIgnoreLocal(register, valueType);
// TODO(mathiasr): This can be simplified once trivial phi removal is local-info aware.
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 7e573f5..20cbd10 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
@@ -578,7 +578,7 @@
feedback.markProcessed(method, Constraint.NEVER);
return;
}
- IRCode code = method.buildIR(options);
+ IRCode code = method.buildIR(options, appInfo.originFor(method.method.holder));
if (code == null) {
feedback.markProcessed(method, Constraint.NEVER);
return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
index 31fc38f..c2af6ea 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
@@ -56,7 +56,7 @@
return;
}
DexEncodedMethod initializer = clazz.getClassInitializer();
- IRCode code = initializer.getCode().buildIR(initializer, options);
+ IRCode code = initializer.getCode().buildIR(initializer, options, clazz.origin);
Reference2IntMap<DexField> ordinalsMap = new Reference2IntArrayMap<>();
ordinalsMap.defaultReturnValue(-1);
InstructionIterator it = code.instructionIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 678ddae..d6efdbb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
@@ -268,7 +269,8 @@
Position callerPosition)
throws ApiLevelException {
// Build the IR for a yet not processed method, and perform minimal IR processing.
- IRCode code = target.buildInliningIR(options, generator, callerPosition);
+ Origin origin = appInfo.originFor(target.method.holder);
+ IRCode code = target.buildInliningIR(options, generator, callerPosition, origin);
if (!target.isProcessed()) {
new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 7947c01..beb4caa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -139,8 +139,7 @@
private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
// 10 is found from measuring.
return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
- && candidate.getCode().isDexCode()
- && (candidate.getCode().asDexCode().instructions.length <= 10);
+ && candidate.getCode().estimatedSizeForInliningAtMost(10);
}
private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
@@ -204,7 +203,7 @@
if (reason == Reason.SIMPLE) {
// If we are looking for a simple method, only inline if actually simple.
Code code = candidate.getCode();
- if (code.estimatedSizeForInlining() > inliningInstructionLimit) {
+ if (!code.estimatedSizeForInliningAtMost(inliningInstructionLimit)) {
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index e9781b4..5a257df 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -46,6 +46,7 @@
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -1002,7 +1003,7 @@
}
@Override
- public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
throws ApiLevelException {
OutlineSourceCode source = new OutlineSourceCode(outline);
IRBuilder builder = new IRBuilder(encodedMethod, source, options);
@@ -1085,7 +1086,30 @@
direct,
DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
options.itemFactory.getSkipNameValidationForTesting());
+ if (options.isGeneratingClassFiles()) {
+ // Don't set class file version below 50.0 (JDK release 1.6).
+ clazz.setClassFileVersion(Math.max(50, getClassFileVersion()));
+ }
return clazz;
}
+
+ private int getClassFileVersion() {
+ assert options.isGeneratingClassFiles();
+ int classFileVersion = -1;
+ Set<DexType> seen = Sets.newIdentityHashSet();
+ for (List<DexEncodedMethod> methods : candidates.values()) {
+ for (DexEncodedMethod method : methods) {
+ DexType holder = method.method.holder;
+ if (seen.add(holder)) {
+ DexProgramClass programClass = appInfo.definitionFor(holder).asProgramClass();
+ assert programClass != null : "Attempt to outline from library class";
+ assert programClass.originatesFromClassResource()
+ : "Attempt to outline from non-classfile input to classfile output";
+ classFileVersion = Math.max(classFileVersion, programClass.getClassFileVersion());
+ }
+ }
+ }
+ return classFileVersion;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index 25b1d1b..bd85615 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -93,7 +93,7 @@
List<DexEncodedField> switchMapFields = Arrays.stream(clazz.staticFields())
.filter(this::maybeIsSwitchMap).collect(Collectors.toList());
if (!switchMapFields.isEmpty()) {
- IRCode initializer = clazz.getClassInitializer().buildIR(options);
+ IRCode initializer = clazz.getClassInitializer().buildIR(options, clazz.origin);
switchMapFields.forEach(field -> extractSwitchMap(field, initializer));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index 53bc57e..b2100e2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import java.util.function.Consumer;
@@ -37,7 +38,8 @@
}
@Override
- public final IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
+ public final IRCode buildIR(
+ DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
throws ApiLevelException {
return new IRBuilder(encodedMethod, sourceCode, options).build();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index c0af336..25df235 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1343,7 +1343,7 @@
private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
try {
- IRCode code = method.buildIR(options);
+ IRCode code = method.buildIR(options, appInfo.originFor(method.method.holder));
code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
} catch (ApiLevelException e) {
// Ignore this exception here. It will be hit again further in the pipeline when
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 5f5237b..91cab92 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -137,8 +137,7 @@
parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
}
- // package visible for testing
- void parse(ProguardConfigurationSource source) {
+ public void parse(ProguardConfigurationSource source) {
parse(ImmutableList.of(source));
}
@@ -968,6 +967,11 @@
private Path parseFileName() throws ProguardRuleParserException {
TextPosition start = getPosition();
skipWhitespace();
+
+ if (baseDirectory == null) {
+ throw parseError("Options with file names are not supported", start);
+ }
+
String fileName = acceptString(character ->
character != File.pathSeparatorChar
&& !Character.isWhitespace(character)
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java
new file mode 100644
index 0000000..3f379a4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java
@@ -0,0 +1,47 @@
+// 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.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+
+public class ProguardConfigurationSourceBytes implements ProguardConfigurationSource {
+ private final byte[] bytes;
+ private final Origin origin;
+
+ public ProguardConfigurationSourceBytes(byte[] bytes, Origin origin) {
+ this.bytes = bytes;
+ this.origin = origin;
+ }
+
+ public ProguardConfigurationSourceBytes(InputStream in, Origin origin) throws IOException {
+ this(ByteStreams.toByteArray(in), origin);
+ }
+
+ @Override
+ public String get() throws IOException {
+ return new String(bytes, StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public Path getBaseDirectory() {
+ // Options with relative names (including -include) is not supported.
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return origin.toString();
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index d8dffea..1b026c0 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -589,5 +589,9 @@
throw new CompilationError("Unsupported source file type", new PathOrigin(file));
}
}
+
+ public List<ProgramResourceProvider> getProgramResourceProviders() {
+ return programResourceProviders;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
index 9fd91d9..99149ff 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
+import com.android.tools.r8.ResourceException;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import java.io.FileNotFoundException;
@@ -19,6 +20,10 @@
this.origin = origin;
}
+ public ExceptionDiagnostic(ResourceException e) {
+ this(e, e.getOrigin());
+ }
+
@Override
public Origin getOrigin() {
return origin;
diff --git a/src/test/debugTestResourcesKotlin/KotlinInline.kt b/src/test/debugTestResourcesKotlin/KotlinInline.kt
index 7f914e4..4b55c57 100644
--- a/src/test/debugTestResourcesKotlin/KotlinInline.kt
+++ b/src/test/debugTestResourcesKotlin/KotlinInline.kt
@@ -47,6 +47,30 @@
emptyMethod(-1)
}
+ // Double inlining
+ fun testNestedInlining() {
+ val l1 = Int.MAX_VALUE
+ val l2 = Int.MIN_VALUE
+ inlinee1(l1, l2)
+ }
+ inline fun inlinee1(a: Int, b: Int) {
+ val c = a - 2
+ inlinee2(1) {
+ val left = a + b
+ val right = a - b
+ foo(left, right)
+ }
+ inlinee2(c) {
+ foo(b, a)
+ }
+ }
+
+ inline fun inlinee2(p: Int, block: () -> Unit) {
+ if (p > 0) {
+ block()
+ }
+ }
+
companion object {
@JvmStatic fun main(args: Array<String>) {
println("Hello world!")
@@ -54,6 +78,7 @@
instance.processObject(instance, instance::printObject)
instance.invokeInlinedFunctions()
instance.singleInline()
+ instance.testNestedInlining()
}
}
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 18c3436..4e07849 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -13,110 +13,287 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.ASMifier;
import org.objectweb.asm.util.TraceClassVisitor;
-@RunWith(Parameterized.class)
public class CfFrontendExamplesTest extends TestBase {
- static final Collection<Object[]> TESTS = Arrays.asList(
- makeTest("arithmetic.Arithmetic"),
- makeTest("arrayaccess.ArrayAccess"),
- makeTest("barray.BArray"),
- makeTest("bridge.BridgeMethod"),
- makeTest("cse.CommonSubexpressionElimination"),
- makeTest("constants.Constants"),
- makeTest("controlflow.ControlFlow"),
- makeTest("conversions.Conversions"),
- makeTest("floating_point_annotations.FloatingPointValuedAnnotationTest"),
- makeTest("filledarray.FilledArray"),
- makeTest("hello.Hello"),
- makeTest("ifstatements.IfStatements"),
- makeTest("instancevariable.InstanceVariable"),
- makeTest("instanceofstring.InstanceofString"),
- makeTest("invoke.Invoke"),
- makeTest("jumbostring.JumboString"),
- makeTest("loadconst.LoadConst"),
- makeTest("loop.UdpServer"),
- makeTest("newarray.NewArray"),
- makeTest("regalloc.RegAlloc"),
- makeTest("returns.Returns"),
- makeTest("staticfield.StaticField"),
- makeTest("stringbuilding.StringBuilding"),
- makeTest("switches.Switches"),
- makeTest("sync.Sync"),
- makeTest("throwing.Throwing"),
- makeTest("trivial.Trivial"),
- makeTest("trycatch.TryCatch"),
- makeTest("nestedtrycatches.NestedTryCatches"),
- makeTest("trycatchmany.TryCatchMany"),
- makeTest("invokeempty.InvokeEmpty"),
- makeTest("regress.Regress"),
- makeTest("regress2.Regress2"),
- makeTest("regress_37726195.Regress"),
- makeTest("regress_37658666.Regress", CfFrontendExamplesTest::compareRegress37658666),
- makeTest("regress_37875803.Regress"),
- makeTest("regress_37955340.Regress"),
- makeTest("regress_62300145.Regress"),
- makeTest("regress_64881691.Regress"),
- makeTest("regress_65104300.Regress"),
- makeTest("regress_70703087.Test"),
- makeTest("regress_70736958.Test"),
- makeTest("regress_70737019.Test"),
- makeTest("regress_72361252.Test"),
- makeTest("memberrebinding2.Memberrebinding"),
- makeTest("memberrebinding3.Memberrebinding"),
- makeTest("minification.Minification"),
- makeTest("enclosingmethod.Main"),
- makeTest("enclosingmethod_proguarded.Main"),
- makeTest("interfaceinlining.Main"),
- makeTest("switchmaps.Switches")
- );
-
- private static Object[] makeTest(String className) {
- return makeTest(className, null);
- }
-
- private static Object[] makeTest(String className, BiConsumer<byte[], byte[]> comparator) {
- return new Object[] {className, comparator};
- }
-
- @Parameters(name = "{0}")
- public static Collection<Object[]> data() {
- return TESTS;
- }
-
- private static void compareRegress37658666(byte[] expectedBytes, byte[] actualBytes) {
- // javac emits LDC(-0.0f) instead of the shorter FCONST_0 FNEG emitted by CfConstNumber.
- String ldc = "mv.visitLdcInsn(new Float(\"-0.0\"));";
- String constNeg = "mv.visitInsn(FCONST_0);\nmv.visitInsn(FNEG);";
- assertEquals(
- asmToString(expectedBytes).replace(ldc, constNeg),
- asmToString(actualBytes));
- }
-
- private final Path inputJar;
- private final BiConsumer<byte[], byte[]> comparator;
-
- public CfFrontendExamplesTest(String clazz, BiConsumer<byte[], byte[]> comparator) {
- this.comparator = comparator;
- String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
- String suffix = "_debuginfo_all";
- inputJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + suffix + JAR_EXTENSION);
+ @Test
+ public void testArithmetic() throws Exception {
+ runTest("arithmetic.Arithmetic");
}
@Test
- public void test() throws Exception {
+ public void testArrayAccess() throws Exception {
+ runTest("arrayaccess.ArrayAccess");
+ }
+
+ @Test
+ public void testBArray() throws Exception {
+ runTest("barray.BArray");
+ }
+
+ @Test
+ public void testBridgeMethod() throws Exception {
+ runTest("bridge.BridgeMethod");
+ }
+
+ @Test
+ public void testCommonSubexpressionElimination() throws Exception {
+ runTest("cse.CommonSubexpressionElimination");
+ }
+
+ @Test
+ public void testConstants() throws Exception {
+ runTest("constants.Constants");
+ }
+
+ @Test
+ public void testControlFlow() throws Exception {
+ runTest("controlflow.ControlFlow");
+ }
+
+ @Test
+ public void testConversions() throws Exception {
+ runTest("conversions.Conversions");
+ }
+
+ @Test
+ public void testFloatingPointValuedAnnotation() throws Exception {
+ runTest("floating_point_annotations.FloatingPointValuedAnnotationTest");
+ }
+
+ @Test
+ public void testFilledArray() throws Exception {
+ runTest("filledarray.FilledArray");
+ }
+
+ @Test
+ public void testHello() throws Exception {
+ runTest("hello.Hello");
+ }
+
+ @Test
+ public void testIfStatements() throws Exception {
+ runTest("ifstatements.IfStatements");
+ }
+
+ @Test
+ public void testInstanceVariable() throws Exception {
+ runTest("instancevariable.InstanceVariable");
+ }
+
+ @Test
+ public void testInstanceofString() throws Exception {
+ runTest("instanceofstring.InstanceofString");
+ }
+
+ @Test
+ public void testInvoke() throws Exception {
+ runTest("invoke.Invoke");
+ }
+
+ @Test
+ public void testJumboString() throws Exception {
+ runTest("jumbostring.JumboString");
+ }
+
+ @Test
+ public void testLoadConst() throws Exception {
+ runTest("loadconst.LoadConst");
+ }
+
+ @Test
+ public void testUdpServer() throws Exception {
+ runTest("loop.UdpServer");
+ }
+
+ @Test
+ public void testNewArray() throws Exception {
+ runTest("newarray.NewArray");
+ }
+
+ @Test
+ public void testRegAlloc() throws Exception {
+ runTest("regalloc.RegAlloc");
+ }
+
+ @Test
+ public void testReturns() throws Exception {
+ runTest("returns.Returns");
+ }
+
+ @Test
+ public void testStaticField() throws Exception {
+ runTest("staticfield.StaticField");
+ }
+
+ @Test
+ public void testStringBuilding() throws Exception {
+ runTest("stringbuilding.StringBuilding");
+ }
+
+ @Test
+ public void testSwitches() throws Exception {
+ runTest("switches.Switches");
+ }
+
+ @Test
+ public void testSync() throws Exception {
+ runTest("sync.Sync");
+ }
+
+ @Test
+ public void testThrowing() throws Exception {
+ runTest("throwing.Throwing");
+ }
+
+ @Test
+ public void testTrivial() throws Exception {
+ runTest("trivial.Trivial");
+ }
+
+ @Test
+ public void testTryCatch() throws Exception {
+ runTest("trycatch.TryCatch");
+ }
+
+ @Test
+ public void testNestedTryCatches() throws Exception {
+ runTest("nestedtrycatches.NestedTryCatches");
+ }
+
+ @Test
+ public void testTryCatchMany() throws Exception {
+ runTest("trycatchmany.TryCatchMany");
+ }
+
+ @Test
+ public void testInvokeEmpty() throws Exception {
+ runTest("invokeempty.InvokeEmpty");
+ }
+
+ @Test
+ public void testRegress() throws Exception {
+ runTest("regress.Regress");
+ }
+
+ @Test
+ public void testRegress2() throws Exception {
+ runTest("regress2.Regress2");
+ }
+
+ @Test
+ public void testRegress37726195() throws Exception {
+ runTest("regress_37726195.Regress");
+ }
+
+ @Test
+ public void testRegress37658666() throws Exception {
+ runTest(
+ "regress_37658666.Regress",
+ (expectedBytes, actualBytes) -> {
+ // javac emits LDC(-0.0f) instead of the shorter FCONST_0 FNEG emitted by CfConstNumber.
+ String ldc = "mv.visitLdcInsn(new Float(\"-0.0\"));";
+ String constNeg = "mv.visitInsn(FCONST_0);\nmv.visitInsn(FNEG);";
+ assertEquals(asmToString(expectedBytes).replace(ldc, constNeg), asmToString(actualBytes));
+ });
+ }
+
+ @Test
+ public void testRegress37875803() throws Exception {
+ runTest("regress_37875803.Regress");
+ }
+
+ @Test
+ public void testRegress37955340() throws Exception {
+ runTest("regress_37955340.Regress");
+ }
+
+ @Test
+ public void testRegress62300145() throws Exception {
+ runTest("regress_62300145.Regress");
+ }
+
+ @Test
+ public void testRegress64881691() throws Exception {
+ runTest("regress_64881691.Regress");
+ }
+
+ @Test
+ public void testRegress65104300() throws Exception {
+ runTest("regress_65104300.Regress");
+ }
+
+ @Test
+ public void testRegress70703087() throws Exception {
+ runTest("regress_70703087.Test");
+ }
+
+ @Test
+ public void testRegress70736958() throws Exception {
+ runTest("regress_70736958.Test");
+ }
+
+ @Test
+ public void testRegress70737019() throws Exception {
+ runTest("regress_70737019.Test");
+ }
+
+ @Test
+ public void testRegress72361252() throws Exception {
+ runTest("regress_72361252.Test");
+ }
+
+ @Test
+ public void testMemberrebinding2() throws Exception {
+ runTest("memberrebinding2.Memberrebinding");
+ }
+
+ @Test
+ public void testMemberrebinding3() throws Exception {
+ runTest("memberrebinding3.Memberrebinding");
+ }
+
+ @Test
+ public void testMinification() throws Exception {
+ runTest("minification.Minification");
+ }
+
+ @Test
+ public void testEnclosingmethod() throws Exception {
+ runTest("enclosingmethod.Main");
+ }
+
+ @Test
+ public void testEnclosingmethodProguarded() throws Exception {
+ runTest("enclosingmethod_proguarded.Main");
+ }
+
+ @Test
+ public void testInterfaceInlining() throws Exception {
+ runTest("interfaceinlining.Main");
+ }
+
+ @Test
+ public void testSwitchmaps() throws Exception {
+ runTest("switchmaps.Switches");
+ }
+
+ private void runTest(String clazz) throws Exception {
+ runTest(clazz, null);
+ }
+
+ private void runTest(String clazz, BiConsumer<byte[], byte[]> comparator) throws Exception {
+ String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
+ String suffix = "_debuginfo_all";
+ Path inputJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + suffix + JAR_EXTENSION);
Path outputJar = temp.getRoot().toPath().resolve("output.jar");
R8Command command =
R8Command.builder()
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index fc22895..2265c3e 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -3,14 +3,23 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.graph.invokesuper.Consumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.utils.ListUtils;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
// Helper to check that a particular error occurred.
-class DiagnosticsChecker implements DiagnosticsHandler {
+public class DiagnosticsChecker implements DiagnosticsHandler {
public List<Diagnostic> errors = new ArrayList<>();
public List<Diagnostic> warnings = new ArrayList<>();
public List<Diagnostic> infos = new ArrayList<>();
@@ -51,4 +60,64 @@
throw e;
}
}
+
+ public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Consumer<Origin> originChecker,
+ int lineStart, int columnStart, String... messageParts) {
+ if (originChecker != null) {
+ originChecker.accept(diagnostic.getOrigin());
+ }
+ TextPosition position;
+ if (diagnostic.getPosition() instanceof TextRange) {
+ position = ((TextRange) diagnostic.getPosition()).getStart();
+ } else {
+ position = ((TextPosition) diagnostic.getPosition());
+ }
+ assertEquals(lineStart, position.getLine());
+ assertEquals(columnStart, position.getColumn());
+ for (String part : messageParts) {
+ assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
+ diagnostic.getDiagnosticMessage().contains(part));
+ }
+ return diagnostic;
+ }
+
+ public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Consumer<Origin> originChecker,
+ String... messageParts) {
+ if (originChecker != null) {
+ originChecker.accept(diagnostic.getOrigin());
+ }
+ assertEquals(diagnostic.getPosition(), Position.UNKNOWN);
+ for (String part : messageParts) {
+ assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
+ diagnostic.getDiagnosticMessage().contains(part));
+ }
+ return diagnostic;
+ }
+
+ static class PathOriginChecker implements Consumer<Origin> {
+ private final Path path;
+ PathOriginChecker(Path path) {
+ this.path = path;
+ }
+
+ public void accept(Origin origin) {
+ if (path != null) {
+ assertEquals(path, ((PathOrigin) origin).getPath());
+ } else {
+ assertSame(Origin.unknown(), origin);
+ }
+ }
+ }
+
+ public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Path path,
+ int lineStart, int columnStart, String... messageParts) {
+ return checkDiagnostic(diagnostic, new PathOriginChecker(path), lineStart, columnStart,
+ messageParts);
+ }
+
+ public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, Path path,
+ int lineStart, int columnStart, String... messageParts) {
+ assertEquals(1, diagnostics.size());
+ return checkDiagnostic(diagnostics.get(0), path, lineStart, columnStart, messageParts);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
new file mode 100644
index 0000000..377bfc0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
@@ -0,0 +1,115 @@
+// 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.cf.LambdaTest;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.io.ByteStreams;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class R8CFExamplesTests extends TestBase {
+
+ private static final Path ART_TESTS_DIR = Paths.get(R8RunArtTestsTest.ART_TESTS_DIR, "dx");
+ private final TestMode testMode;
+ private final CompilationMode compilationMode;
+
+ public enum TestMode {
+ CF_SKIP_IR,
+ JAR_TO_IR,
+ CF_TO_IR,
+ }
+
+ @Parameters(name = "{0}:{1}")
+ public static List<Object[]> data() {
+ List<Object[]> data = new ArrayList<>();
+ for (TestMode testMode : TestMode.values()) {
+ for (CompilationMode compilationMode : CompilationMode.values()) {
+ data.add(new Object[] {testMode, compilationMode});
+ }
+ }
+ return data;
+ }
+
+ public R8CFExamplesTests(TestMode testMode, CompilationMode compilationMode) {
+ this.testMode = testMode;
+ this.compilationMode = compilationMode;
+ }
+
+ @Test
+ public void testConstMethodHandle() throws Exception {
+ Path testDirectory = ART_TESTS_DIR.resolve("979-const-method-handle/classes");
+ String classNames[] = {
+ "constmethodhandle.ConstTest", "Main",
+ };
+ runTest(writeInput(testDirectory, classNames), "Main");
+ }
+
+ @Test
+ public void testLambda() throws Exception {
+ runTest(LambdaTest.class);
+ }
+
+ private void runTest(Class<?> clazz) throws Exception {
+ runTest(writeInput(clazz), clazz.getName());
+ }
+
+ private Path writeInput(Class<?> clazz) throws Exception {
+ Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+ ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(clazz.getName());
+ inputConsumer.accept(ToolHelper.getClassAsBytes(clazz), descriptor, null);
+ inputConsumer.finished(null);
+ return inputJar;
+ }
+
+ private Path writeInput(Path testDirectory, String[] classNames) throws Exception {
+ Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+ ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
+ for (String className : classNames) {
+ Path path = testDirectory.resolve(className.replace('.', '/') + ".class");
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(className);
+ try (InputStream inputStream = new FileInputStream(path.toFile())) {
+ inputConsumer.accept(ByteStreams.toByteArray(inputStream), descriptor, null);
+ }
+ }
+ inputConsumer.finished(null);
+ return inputJar;
+ }
+
+ private void runTest(Path inputJar, String mainClass) throws Exception {
+ ProcessResult runInput = ToolHelper.runJava(inputJar, mainClass);
+ Assert.assertEquals(0, runInput.exitCode);
+ Path outputJar = runR8(inputJar, compilationMode, "output.jar");
+ ProcessResult runOutput = ToolHelper.runJava(outputJar, mainClass);
+ Assert.assertEquals(runInput.toString(), runOutput.toString());
+ }
+
+ private Path runR8(Path inputJar, CompilationMode mode, String outputName) throws Exception {
+ Path outputJar = temp.getRoot().toPath().resolve(outputName);
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .setMode(mode)
+ .addProgramFiles(inputJar)
+ .setOutput(outputJar, OutputMode.ClassFile)
+ .build(),
+ o -> {
+ o.skipIR = this.testMode == TestMode.CF_SKIP_IR;
+ o.enableCfFrontend = this.testMode != TestMode.JAR_TO_IR;
+ });
+ return outputJar;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 923398d..ac0955e 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -83,7 +83,7 @@
D8_AFTER_R8CF
}
- private static final String ART_TESTS_DIR = "tests/2017-10-04/art";
+ public static final String ART_TESTS_DIR = "tests/2017-10-04/art";
private static final String ART_LEGACY_TESTS_DIR = "tests/2016-12-19/art/";
private static final String ART_TESTS_NATIVE_LIBRARY_DIR = "tests/2017-10-04/art/lib64";
private static final String ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR = "tests/2016-12-19/art/lib64";
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 9d45f6a..b973818 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -33,6 +33,11 @@
DX, JAVAC, JAVAC_ALL, JAVAC_NONE
}
+ protected enum Frontend {
+ JAR,
+ CF
+ }
+
protected enum Output {
DEX,
CF
@@ -45,8 +50,20 @@
protected static String[] makeTest(
Input input, CompilerUnderTest compiler, CompilationMode mode, String clazz, Output output) {
+ return makeTest(input, compiler, mode, clazz, Frontend.JAR, output);
+ }
+
+ protected static String[] makeTest(
+ Input input,
+ CompilerUnderTest compiler,
+ CompilationMode mode,
+ String clazz,
+ Frontend frontend,
+ Output output) {
String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
- return new String[] {pkg, input.name(), compiler.name(), mode.name(), clazz, output.name()};
+ return new String[] {
+ pkg, input.name(), compiler.name(), mode.name(), clazz, frontend.name(), output.name()
+ };
}
@Rule
@@ -57,6 +74,7 @@
private final CompilationMode mode;
private final String pkg;
private final String mainClass;
+ private final Frontend frontend;
private final Output output;
public R8RunExamplesCommon(
@@ -65,12 +83,14 @@
String compiler,
String mode,
String mainClass,
+ String frontend,
String output) {
this.pkg = pkg;
this.input = Input.valueOf(input);
this.compiler = CompilerUnderTest.valueOf(compiler);
this.mode = CompilationMode.valueOf(mode);
this.mainClass = mainClass;
+ this.frontend = Frontend.valueOf(frontend);
this.output = Output.valueOf(output);
}
@@ -135,17 +155,24 @@
.build());
break;
}
- case R8: {
- R8Command command = addInputFile(R8Command.builder())
- .setOutput(getOutputFile(), outputMode)
- .setMode(mode)
- .build();
- ExceptionUtils.withR8CompilationHandler(command.getReporter(), () ->
- ToolHelper.runR8(
- command,
- options -> options.lineNumberOptimization = LineNumberOptimization.OFF));
- break;
- }
+ case R8:
+ {
+ R8Command command =
+ addInputFile(R8Command.builder())
+ .setOutput(getOutputFile(), outputMode)
+ .setMode(mode)
+ .build();
+ ExceptionUtils.withR8CompilationHandler(
+ command.getReporter(),
+ () ->
+ ToolHelper.runR8(
+ command,
+ options -> {
+ options.lineNumberOptimization = LineNumberOptimization.OFF;
+ options.enableCfFrontend = frontend == Frontend.CF;
+ }));
+ break;
+ }
default:
throw new Unreachable();
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
index 02c2d27..97c8624 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
@@ -17,7 +17,7 @@
@RunWith(Parameterized.class)
public class R8RunExamplesKotlinTest extends R8RunExamplesCommon {
- @Parameters(name = "{0}_{1}_{2}_{3}_{5}")
+ @Parameters(name = "{0}_{1}_{2}_{3}_{5}_{6}")
public static Collection<String[]> data() {
String[] tests = {
"loops.LoopKt"
@@ -74,7 +74,8 @@
String compiler,
String mode,
String mainClass,
+ String frontend,
String output) {
- super(pkg, input, compiler, mode, mainClass, output);
+ super(pkg, input, compiler, mode, mainClass, frontend, output);
}
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index ba036dd..913278a 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -24,7 +24,7 @@
private static final boolean ONLY_RUN_CF_TESTS = false;
- @Parameters(name = "{0}_{1}_{2}_{3}_{5}")
+ @Parameters(name = "{0}_{1}_{2}_{3}_{5}_{6}")
public static Collection<String[]> data() {
String[] tests = {
"arithmetic.Arithmetic",
@@ -99,6 +99,14 @@
fullTestList.add(
makeTest(
Input.JAVAC_ALL, CompilerUnderTest.R8, CompilationMode.RELEASE, test, Output.CF));
+ fullTestList.add(
+ makeTest(
+ Input.JAVAC_ALL,
+ CompilerUnderTest.R8,
+ CompilationMode.RELEASE,
+ test,
+ Frontend.CF,
+ Output.CF));
}
return fullTestList;
}
@@ -109,8 +117,9 @@
String compiler,
String mode,
String mainClass,
+ String frontend,
String output) {
- super(pkg, input, compiler, mode, mainClass, output);
+ super(pkg, input, compiler, mode, mainClass, frontend, output);
}
@Override
@@ -134,7 +143,6 @@
@Override
protected Set<String> getFailingCompileCf() {
return new ImmutableSet.Builder<String>()
- .add("invoke.Invoke") // outline / CF->IR
.build();
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a29d673..96591d8 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -153,19 +153,26 @@
protected Path jarTestClasses(Class... classes) throws IOException {
Path jar = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
- for (Class clazz : classes) {
- try (FileInputStream in =
- new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
- out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
- ByteStreams.copy(in, out);
- out.closeEntry();
- }
- }
+ addTestClassesToJar(out, classes);
}
return jar;
}
/**
+ * Create a temporary JAR file containing the specified test classes.
+ */
+ protected void addTestClassesToJar(JarOutputStream out, Class... classes) throws IOException {
+ for (Class clazz : classes) {
+ try (FileInputStream in =
+ new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
+ out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
+ ByteStreams.copy(in, out);
+ out.closeEntry();
+ }
+ }
+ }
+
+ /**
* Create a temporary JAR file containing all test classes in a package.
*/
protected Path jarTestClassesInPackage(Package pkg) throws IOException {
@@ -430,20 +437,6 @@
}
/**
- * Run application on Art with the specified main class.
- */
- protected String runOnArt(AndroidApp app, String mainClass) throws IOException {
- return runOnArtRaw(app, mainClass).stdout;
- }
-
- /**
- * Run application on Art with the specified main class.
- */
- protected String runOnArt(AndroidApp app, Class mainClass) throws IOException {
- return runOnArtRaw(app, mainClass).stdout;
- }
-
- /**
* Run application on Art with the specified main class and provided arguments.
*/
protected String runOnArt(AndroidApp app, Class mainClass, String... args) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java
index 414752f..be00acf 100644
--- a/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java
@@ -46,8 +46,7 @@
for (Class<?> c : CLASSES) {
builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
}
- // TODO(b/75997473): Enable inlining when supported
- ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+ ToolHelper.runR8(builder.build());
ProcessResult runOutput = ToolHelper.runJava(out, CLASS.getCanonicalName());
assertEquals(runInput.toString(), runOutput.toString());
}
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
index 0f92fb3..7fdad0d 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
@@ -56,10 +56,7 @@
@Override
public String toString() {
- // TODO(mathiasr): Add pgMap to output when resource API (go/r8g/19460) has landed.
- // Without resource API, comparing pgMaps will fail because R8 does not keep the resource
- // indicating which Git revision R8 was compiled from.
- return processResult.toString();
+ return processResult.toString() + "\n\n" + pgMap;
}
}
@@ -71,37 +68,50 @@
assertEquals(0, runHello.exitCode);
// Run r8.jar on hello.jar to ensure that r8.jar is a working compiler.
- R8Result runInputR8 = runExternalR8(R8_STABLE_JAR, hello, "input", KEEP_HELLO);
+ R8Result runInputR8 = runExternalR8(R8_STABLE_JAR, hello, "input", KEEP_HELLO, "--debug");
ProcessResult runHelloR8 = ToolHelper.runJava(runInputR8.outputJar, "hello.Hello");
assertEquals(runHello.toString(), runHelloR8.toString());
+ compareR8(hello, runInputR8, CompilationMode.RELEASE, "r8-r8-rel", "--release", "output-rel");
+ compareR8(hello, runInputR8, CompilationMode.DEBUG, "r8-r8", "--debug", "output");
+ }
+
+ private void compareR8(
+ Path hello,
+ R8Result runInputR8,
+ CompilationMode internalMode,
+ String internalOutput,
+ String externalMode,
+ String externalOutput)
+ throws Exception {
// Run R8 on r8.jar, and run the resulting compiler on hello.jar.
- Path output = runR8(R8_STABLE_JAR, "r8-r8", KEEP_R8);
- R8Result runR8R8 = runExternalR8(output, hello, "output", KEEP_HELLO);
+ Path output = runR8(R8_STABLE_JAR, internalOutput, KEEP_R8, internalMode);
+ R8Result runR8R8 = runExternalR8(output, hello, externalOutput, KEEP_HELLO, externalMode);
// Check that the process outputs (exit code, stdout, stderr) are the same.
assertEquals(runInputR8.toString(), runR8R8.toString());
// Check that the output jars are the same.
assertProgramsEqual(runInputR8.outputJar, runR8R8.outputJar);
}
- private Path runR8(Path inputJar, String outputFolder, String[] keepRules) throws Exception {
+ private Path runR8(Path inputJar, String outputFolder, String[] keepRules, CompilationMode mode)
+ throws Exception {
Path outputPath = temp.newFolder(outputFolder).toPath();
Path outputJar = outputPath.resolve("output.jar");
Path pgConfigFile = outputPath.resolve("keep.rules");
FileUtils.writeTextFile(pgConfigFile, keepRules);
ToolHelper.runR8(
R8Command.builder()
- .setMode(CompilationMode.DEBUG)
+ .setMode(mode)
.addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
- // TODO(mathiasr): Add resources to output when resource API (go/r8g/19460) has landed.
- .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar))
+ .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
.addProgramFiles(inputJar)
.addProguardConfigurationFiles(pgConfigFile)
.build());
return outputJar;
}
- private R8Result runExternalR8(Path r8Jar, Path inputJar, String outputFolder, String[] keepRules)
+ private R8Result runExternalR8(
+ Path r8Jar, Path inputJar, String outputFolder, String[] keepRules, String mode)
throws Exception {
Path outputPath = temp.newFolder(outputFolder).toPath();
Path pgConfigFile = outputPath.resolve("keep.rules");
@@ -120,7 +130,7 @@
outputJar.toString(),
"--pg-conf",
pgConfigFile.toString(),
- "--debug",
+ mode,
"--pg-map-output",
pgMapFile.toString());
if (processResult.exitCode != 0) {
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
index fed7637..ad6a72b 100644
--- a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
@@ -58,11 +58,6 @@
.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
.setProgramConsumer(consumer);
input.accept(builder);
- ToolHelper.runR8(
- builder.build(),
- o -> {
- o.invalidDebugInfoFatal = true;
- o.enableInlining = false;
- });
+ ToolHelper.runR8(builder.build(), o -> o.invalidDebugInfoFatal = true);
}
}
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
index f47bdc6..6c9d7b6 100644
--- a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
@@ -12,8 +11,10 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.JarCode;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -22,18 +23,10 @@
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
-import java.util.ListIterator;
+import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnList;
-import org.objectweb.asm.tree.InvokeDynamicInsnNode;
-import org.objectweb.asm.tree.MethodNode;
public class LambdaTestRunner {
@@ -49,8 +42,10 @@
// is not modified by the R8 compilation.
// First, extract the InvokeDynamic instruction from the input class.
byte[] inputClass = ToolHelper.getClassAsBytes(CLASS);
- int opcode = Opcodes.INVOKEDYNAMIC;
- InvokeDynamicInsnNode insnInput = findFirstInMethod(inputClass, opcode);
+ AndroidApp inputApp =
+ AndroidApp.builder().addClassProgramData(inputClass, Origin.unknown()).build();
+ CfInvokeDynamic insnInput = findFirstInMethod(inputApp);
+ Assert.assertNotNull("No CfInvokeDynamic found in input", insnInput);
// Compile with R8 and extract the InvokeDynamic instruction from the output class.
AndroidAppConsumers appBuilder = new AndroidAppConsumers();
Path outPath = temp.getRoot().toPath().resolve("out.jar");
@@ -61,13 +56,11 @@
.setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
.addClassProgramData(inputClass, Origin.unknown())
.build());
- AndroidApp app = appBuilder.build();
- InvokeDynamicInsnNode insnOutput = findFirstInMethod(app, opcode);
+ AndroidApp outputApp = appBuilder.build();
+ CfInvokeDynamic insnOutput = findFirstInMethod(outputApp);
+ Assert.assertNotNull("No CfInvokeDynamic found in output", insnOutput);
// Check that the InvokeDynamic instruction is not modified.
- assertEquals(insnInput.name, insnOutput.name);
- assertEquals(insnInput.desc, insnOutput.desc);
- assertEquals(insnInput.bsm, insnOutput.bsm);
- assertArrayEquals(insnInput.bsmArgs, insnOutput.bsmArgs);
+ assertEquals(print(insnInput), print(insnOutput));
// Check that execution gives the same output.
ProcessResult inputResult =
ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getName());
@@ -75,45 +68,24 @@
assertEquals(inputResult.toString(), outputResult.toString());
}
- private InvokeDynamicInsnNode findFirstInMethod(AndroidApp app, int opcode) throws Exception {
+ private static CfInvokeDynamic findFirstInMethod(AndroidApp app) throws Exception {
String returnType = "void";
- DexInspector inspector = new DexInspector(app);
+ DexInspector inspector = new DexInspector(app, o -> o.enableCfFrontend = true);
List<String> args = Collections.singletonList(String[].class.getTypeName());
DexEncodedMethod method = inspector.clazz(CLASS).method(returnType, METHOD, args).getMethod();
- JarCode jarCode = method.getCode().asJarCode();
- MethodNode outputMethod = jarCode.getNode();
- return (InvokeDynamicInsnNode) findFirstInstruction(outputMethod, opcode);
- }
-
- private InvokeDynamicInsnNode findFirstInMethod(byte[] clazz, int opcode) {
- MethodNode[] method = {null};
- new ClassReader(clazz)
- .accept(
- new ClassNode(Opcodes.ASM6) {
- @Override
- public MethodVisitor visitMethod(
- int access, String name, String desc, String signature, String[] exceptions) {
- if (name.equals(METHOD)) {
- method[0] = new MethodNode(access, name, desc, signature, exceptions);
- return method[0];
- } else {
- return null;
- }
- }
- },
- 0);
- return (InvokeDynamicInsnNode) findFirstInstruction(method[0], opcode);
- }
-
- private AbstractInsnNode findFirstInstruction(MethodNode node, int opcode) {
- assert node != null;
- InsnList asmInsns = node.instructions;
- for (ListIterator<AbstractInsnNode> it = asmInsns.iterator(); it.hasNext(); ) {
- AbstractInsnNode insn = it.next();
- if (insn.getOpcode() == opcode) {
- return insn;
+ CfCode code = method.getCode().asCfCode();
+ for (CfInstruction instruction : code.getInstructions()) {
+ if (instruction instanceof CfInvokeDynamic) {
+ return (CfInvokeDynamic) instruction;
}
}
- throw new RuntimeException("Instruction not found");
+ return null;
}
+
+ private static String print(CfInstruction instruction) {
+ CfPrinter printer = new CfPrinter();
+ instruction.print(printer);
+ return printer.toString();
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index ae3b95e..e43ffd7 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -9,55 +9,91 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Reporter;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
-import org.junit.Rule;
+import java.util.List;
+import org.junit.Assume;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
-public class MethodHandleTestRunner {
+@RunWith(Parameterized.class)
+public class MethodHandleTestRunner extends TestBase {
static final Class<?> CLASS = MethodHandleTest.class;
- private boolean ldc = false;
- private boolean minify = false;
+ enum LookupType {
+ DYNAMIC,
+ CONSTANT,
+ }
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ enum MinifyMode {
+ NONE,
+ MINIFY,
+ }
- @Test
- public void testMethodHandlesLookup() throws Exception {
- // Run test with dynamic method lookups, i.e. using MethodHandles.lookup().find*()
- ldc = false;
- test();
+ enum Frontend {
+ JAR,
+ CF,
+ }
+
+ private CompilationMode compilationMode;
+ private LookupType lookupType;
+ private Frontend frontend;
+ private ProcessResult runInput;
+ private MinifyMode minifyMode;
+
+ @Parameters(name = "{0}_{1}_{2}_{3}")
+ public static List<String[]> data() {
+ List<String[]> res = new ArrayList<>();
+ for (LookupType lookupType : LookupType.values()) {
+ for (Frontend frontend : Frontend.values()) {
+ for (MinifyMode minifyMode : MinifyMode.values()) {
+ if (lookupType == LookupType.DYNAMIC && minifyMode == MinifyMode.MINIFY) {
+ // Skip because we don't keep the members looked up dynamically.
+ continue;
+ }
+ for (CompilationMode compilationMode : CompilationMode.values()) {
+ res.add(
+ new String[] {
+ lookupType.name(), frontend.name(), minifyMode.name(), compilationMode.name()
+ });
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ public MethodHandleTestRunner(
+ String lookupType, String frontend, String minifyMode, String compilationMode) {
+ this.lookupType = LookupType.valueOf(lookupType);
+ this.frontend = Frontend.valueOf(frontend);
+ this.minifyMode = MinifyMode.valueOf(minifyMode);
+ this.compilationMode = CompilationMode.valueOf(compilationMode);
}
@Test
- public void testLdcMethodHandle() throws Exception {
- // Run test with LDC methods, i.e. without java.lang.invoke.MethodHandles
- ldc = true;
- test();
- }
-
- @Test
- public void testMinify() throws Exception {
- // Run test with LDC methods, i.e. without java.lang.invoke.MethodHandles
- ldc = true;
- ProcessResult runInput = runInput();
- assertEquals(0, runInput.exitCode);
- Path outCf = temp.getRoot().toPath().resolve("cf.jar");
- build(new ClassFileConsumer.ArchiveConsumer(outCf), true);
- ProcessResult runCf =
- ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception");
- assertEquals(runInput.toString(), runCf.toString());
+ public void test() throws Exception {
+ runInput();
+ runCf();
+ // TODO(mathiasr): Once we include a P runtime, change this to "P and above".
+ if (ToolHelper.getDexVm() == DexVm.ART_DEFAULT && ToolHelper.artSupported()) {
+ runDex();
+ }
}
private final Class[] inputClasses = {
@@ -70,25 +106,39 @@
MethodHandleTest.F.class,
};
- private void test() throws Exception {
- ProcessResult runInput = runInput();
- Path outCf = temp.getRoot().toPath().resolve("cf.jar");
- build(new ClassFileConsumer.ArchiveConsumer(outCf), false);
- Path outDex = temp.getRoot().toPath().resolve("dex.zip");
- build(new DexIndexedConsumer.ArchiveConsumer(outDex), false);
-
- ProcessResult runCf =
- ToolHelper.runJava(outCf, CLASS.getCanonicalName(), ldc ? "error" : "exception");
- assertEquals(runInput.toString(), runCf.toString());
- // TODO(mathiasr): Once we include a P runtime, change this to "P and above".
- if (ToolHelper.getDexVm() != DexVm.ART_DEFAULT) {
- return;
+ private void runInput() throws Exception {
+ Path out = temp.getRoot().toPath().resolve("input.jar");
+ ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
+ for (Class<?> c : inputClasses) {
+ archiveConsumer.accept(
+ getClassAsBytes(c), DescriptorUtils.javaTypeToDescriptor(c.getName()), null);
}
+ archiveConsumer.finished(null);
+ String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
+ runInput = ToolHelper.runJava(out, CLASS.getName(), expected);
+ if (runInput.exitCode != 0) {
+ System.out.println(runInput);
+ }
+ assertEquals(0, runInput.exitCode);
+ }
+
+ private void runCf() throws Exception {
+ Path outCf = temp.getRoot().toPath().resolve("cf.jar");
+ build(new ClassFileConsumer.ArchiveConsumer(outCf));
+ String expected = lookupType == LookupType.CONSTANT ? "error" : "exception";
+ ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName(), expected);
+ assertEquals(runInput.toString(), runCf.toString());
+ }
+
+ private void runDex() throws Exception {
+ Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+ build(new DexIndexedConsumer.ArchiveConsumer(outDex));
+ String expected = lookupType == LookupType.CONSTANT ? "pass" : "exception";
ProcessResult runDex =
ToolHelper.runArtRaw(
outDex.toString(),
CLASS.getCanonicalName(),
- cmd -> cmd.appendProgramArgument(ldc ? "pass" : "exception"));
+ cmd -> cmd.appendProgramArgument(expected));
// Only compare stdout and exitCode since dex2oat prints to stderr.
if (runInput.exitCode != runDex.exitCode) {
System.out.println(runDex.stderr);
@@ -97,51 +147,49 @@
assertEquals(runInput.exitCode, runDex.exitCode);
}
- private void build(ProgramConsumer programConsumer, boolean minify) throws Exception {
+ private void build(ProgramConsumer programConsumer) throws Exception {
// MethodHandle.invoke() only supported from Android O
// ConstMethodHandle only supported from Android P
AndroidApiLevel apiLevel = AndroidApiLevel.P;
- Builder cfBuilder =
+ Builder command =
R8Command.builder()
- .setMode(CompilationMode.DEBUG)
+ .setMode(compilationMode)
.addLibraryFiles(ToolHelper.getAndroidJar(apiLevel))
.setProgramConsumer(programConsumer);
if (!(programConsumer instanceof ClassFileConsumer)) {
- cfBuilder.setMinApiLevel(apiLevel.getLevel());
+ command.setMinApiLevel(apiLevel.getLevel());
}
for (Class<?> c : inputClasses) {
byte[] classAsBytes = getClassAsBytes(c);
- cfBuilder.addClassProgramData(classAsBytes, Origin.unknown());
+ command.addClassProgramData(classAsBytes, Origin.unknown());
}
- if (minify) {
- cfBuilder.addProguardConfiguration(
+ if (minifyMode == MinifyMode.MINIFY) {
+ command.addProguardConfiguration(
Arrays.asList(
"-keep public class com.android.tools.r8.cf.MethodHandleTest {",
" public static void main(...);",
"}"),
Origin.unknown());
}
- R8.run(cfBuilder.build());
- }
-
- private ProcessResult runInput() throws Exception {
- Path out = temp.getRoot().toPath().resolve("input.jar");
- ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
- for (Class<?> c : inputClasses) {
- archiveConsumer.accept(
- getClassAsBytes(c), DescriptorUtils.javaTypeToDescriptor(c.getName()), null);
+ try {
+ ToolHelper.runR8(
+ command.build(), options -> options.enableCfFrontend = frontend == Frontend.CF);
+ } catch (CompilationError e) {
+ if (frontend == Frontend.CF && compilationMode == CompilationMode.DEBUG) {
+ // TODO(b/79725635): Investigate why these tests fail on the buildbot.
+ // Use a Reporter to extract origin info to standard error.
+ new Reporter(new DefaultDiagnosticsHandler()).error(e);
+ // Print the stack trace since this is not always printed by JUnit.
+ e.printStackTrace();
+ Assume.assumeNoException(
+ "TODO(b/79725635): Investigate why these tests fail on the buildbot.", e);
+ }
+ throw e;
}
- archiveConsumer.finished(null);
- ProcessResult runInput = ToolHelper.runJava(out, CLASS.getName(), ldc ? "error" : "exception");
- if (runInput.exitCode != 0) {
- System.out.println(runInput);
- }
- assertEquals(0, runInput.exitCode);
- return runInput;
}
private byte[] getClassAsBytes(Class<?> clazz) throws Exception {
- if (ldc) {
+ if (lookupType == LookupType.CONSTANT) {
if (clazz == MethodHandleTest.D.class) {
return MethodHandleDump.dumpD();
} else if (clazz == MethodHandleTest.class) {
diff --git a/src/test/java/com/android/tools/r8/code/ClassWithNativeMethodTest.java b/src/test/java/com/android/tools/r8/code/ClassWithNativeMethodTest.java
deleted file mode 100644
index 293485a..0000000
--- a/src/test/java/com/android/tools/r8/code/ClassWithNativeMethodTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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.code;
-
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.VmTestRunner;
-import com.android.tools.r8.jasmin.JasminBuilder;
-import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(VmTestRunner.class)
-public class ClassWithNativeMethodTest extends TestBase {
-
- @Test
- public void test() throws Exception {
- JasminBuilder jasminBuilder = new JasminBuilder();
-
- ClassBuilder cls = jasminBuilder.addClass("Main");
- cls.addDefaultConstructor();
- cls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V",
- ".limit stack 3",
- ".limit locals 2",
- "getstatic java/lang/System/out Ljava/io/PrintStream;",
- "aload_1",
- "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
- "return");
- cls.addNativeMethod("n1", ImmutableList.of("Ljava/lang/String;"), "V",
- "return");
- cls.addNativeMethod("n2", ImmutableList.of("Ljava/lang/String;"), "V",
- "return");
- cls.addNativeMethod("n3", ImmutableList.of("Ljava/lang/String;"), "V",
- "return");
- cls.addNativeMethod("n4", ImmutableList.of("Ljava/lang/String;"), "V",
- "return");
- cls.addMainMethod(
- ".limit stack 4",
- ".limit locals 2",
- "new " + cls.name,
- "dup",
- "invokespecial " + cls.name + "/<init>()V",
- "astore_0",
- "aload_0",
- "ldc \"foo\"",
- "invokevirtual " + cls.name + "/foo(Ljava/lang/String;)V",
- "return");
-
- String mainClassName = cls.name;
-
- Path outputDirectory = temp.newFolder().toPath();
- jasminBuilder.writeClassFiles(outputDirectory);
- ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
- // Native .o is not given. Expect to see JNI error.
- assertNotEquals(0, javaResult.exitCode);
- assertThat(javaResult.stderr, containsString("JNI"));
-
- AndroidApp processedApp = compileWithD8(jasminBuilder.build());
-
- DexInspector inspector = new DexInspector(processedApp);
- ClassSubject mainSubject = inspector.clazz(cls.name);
- MethodSubject nativeMethod =
- mainSubject.method("void", "n1", ImmutableList.of("java.lang.String"));
- assertThat(nativeMethod, isPresent());
-
- ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
- // ART can process main() even without native definitions.
- assertEquals(0, artResult.exitCode);
- assertThat(artResult.stdout, containsString("foo"));
- }
-
-
-}
diff --git a/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java b/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
new file mode 100644
index 0000000..83e711c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
@@ -0,0 +1,109 @@
+// 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.code;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class NativeMethodWithCodeTest extends TestBase {
+
+ // Test that D8 removes code from native methods (to match the behavior of dx).
+ // Note that the JVM rejects a class if it has a native method with a code attribute,
+ // but D8 has to handle that and cannot simply throw an error even though the JVM does.
+
+ @Test
+ public void test() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+
+ ClassBuilder cls = jasminBuilder.addClass("Main");
+ cls.addDefaultConstructor();
+ cls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V",
+ ".limit stack 3",
+ ".limit locals 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "aload_1",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ "return");
+ cls.addNativeMethodWithCode("n1", ImmutableList.of("Ljava/lang/String;"), "V",
+ "return");
+ cls.addNativeMethodWithCode("n2", ImmutableList.of("Ljava/lang/String;"), "V",
+ "return");
+ cls.addNativeMethodWithCode("n3", ImmutableList.of("Ljava/lang/String;"), "V",
+ "return");
+ cls.addNativeMethodWithCode("n4", ImmutableList.of("Ljava/lang/String;"), "V",
+ "return");
+ cls.addMainMethod(
+ ".limit stack 4",
+ ".limit locals 2",
+ "new " + cls.name,
+ "dup",
+ "invokespecial " + cls.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "ldc \"foo\"",
+ "invokevirtual " + cls.name + "/foo(Ljava/lang/String;)V",
+ "return");
+
+ String mainClassName = cls.name;
+
+ Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+ jasminBuilder.writeJar(inputJar, null);
+ AndroidApp inputApp = AndroidApp.builder().addProgramFiles(inputJar).build();
+ // TODO(mathiasr): Consider making frontend read in code on native methods.
+ // If we do that, this assertNull should change to assertNotNull.
+ assertNull(getNativeMethod(mainClassName, inputApp).getMethod().getCode());
+
+ // JVM throws ClassFormatError because the input contains code on a native method. (JVM8§4.7.3)
+ ProcessResult javaResult = ToolHelper.runJava(inputJar, mainClassName);
+ assertNotEquals(0, javaResult.exitCode);
+ assertThat(javaResult.stderr, containsString("ClassFormatError"));
+
+ // DX strips code from native methods.
+ Path dxOutputDir = temp.newFolder().toPath();
+ Path dxOutputFile = dxOutputDir.resolve("classes.dex");
+ ToolHelper.runDexer(inputJar.toString(), dxOutputDir.toString());
+ AndroidApp dxApp = AndroidApp.builder().addProgramFiles(dxOutputFile).build();
+ assertNull(getNativeMethod(mainClassName, dxApp).getMethod().getCode());
+
+ // D8 should also strip code from native methods.
+ AndroidApp processedApp = compileWithD8(inputApp);
+ assertThat(getNativeMethod(mainClassName, processedApp), isPresent());
+ // TODO(mathiasr): Consider making D8 maintain code on native methods.
+ // If we do that, this assertNull should change to assertNotNull.
+ assertNull(getNativeMethod(mainClassName, processedApp).getMethod().getCode());
+
+ ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
+ assertEquals(0, artResult.exitCode);
+ assertThat(artResult.stdout, containsString("foo"));
+ }
+
+ private MethodSubject getNativeMethod(String mainClassName, AndroidApp processedApp)
+ throws IOException, ExecutionException {
+ DexInspector inspector = new DexInspector(processedApp);
+ ClassSubject mainSubject = inspector.clazz(mainClassName);
+ return mainSubject.method("void", "n1", ImmutableList.of("java.lang.String"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index e8d21f2..25356ae 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -390,6 +390,14 @@
return inspect(t -> t.checkLocal(localName));
}
+ protected final JUnit3Wrapper.Command checkLocals(String... localNames) {
+ return inspect(t -> {
+ for (String str : localNames) {
+ t.checkLocal(str);
+ }
+ });
+ }
+
protected final JUnit3Wrapper.Command checkLocal(String localName, Value expectedValue) {
return inspect(t -> t.checkLocal(localName, expectedValue));
}
@@ -398,6 +406,14 @@
return inspect(t -> t.checkNoLocal(localName));
}
+ protected final JUnit3Wrapper.Command checkNoLocals(String... localNames) {
+ return inspect(t -> {
+ for (String str : localNames) {
+ t.checkNoLocal(str);
+ }
+ });
+ }
+
protected final JUnit3Wrapper.Command checkNoLocal() {
return inspect(t -> {
List<String> localNames = t.getLocalNames();
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 913d39e..02e995f 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -10,57 +10,375 @@
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.function.Consumer;
import java.util.stream.Stream;
import org.junit.Assume;
+import org.junit.Ignore;
import org.junit.Test;
public class ExamplesDebugTest extends DebugTestBase {
- private Path getExampleJar(String pkg) {
- return Paths.get(
- ToolHelper.EXAMPLES_BUILD_DIR, pkg + "_debuginfo_all" + FileUtils.JAR_EXTENSION);
+ private String clazzName;
+ private Path inputJar;
+
+ private Stream<DebuggeeState> input() throws Exception {
+ return streamDebugTest(new CfDebugTestConfig(inputJar), clazzName, ANDROID_FILTER);
}
- public DebugTestConfig cfConfig(String pkg) throws Exception {
- return new CfDebugTestConfig(getExampleJar(pkg));
+ private Stream<DebuggeeState> d8() throws Exception {
+ D8DebugTestConfig config = new D8DebugTestConfig().compileAndAdd(temp, inputJar);
+ return streamDebugTest(config, clazzName, ANDROID_FILTER);
}
- public DebugTestConfig d8Config(String pkg) throws Exception {
- return new D8DebugTestConfig().compileAndAdd(temp, getExampleJar(pkg));
+ private Stream<DebuggeeState> r8jar() throws Exception {
+ return streamDebugTest(getCfConfig("r8jar.jar", o -> {}), clazzName, ANDROID_FILTER);
}
- public DebugTestConfig r8DebugCfConfig(String pkg) throws Exception {
- Path input = getExampleJar(pkg);
- Path output = temp.newFolder().toPath().resolve("r8_debug_cf_output.jar");
+ private Stream<DebuggeeState> r8cf() throws Exception {
+ return streamDebugTest(
+ getCfConfig("r8cf.jar", options -> options.enableCfFrontend = true),
+ clazzName,
+ ANDROID_FILTER);
+ }
+
+ private DebugTestConfig getCfConfig(String outputName, Consumer<InternalOptions> optionsConsumer)
+ throws Exception {
+ Path input = inputJar;
+ Path output = temp.newFolder().toPath().resolve(outputName);
ToolHelper.runR8(
R8Command.builder()
.addProgramFiles(input)
.setMode(CompilationMode.DEBUG)
.setOutput(output, OutputMode.ClassFile)
- .build());
+ .build(),
+ optionsConsumer);
return new CfDebugTestConfig(output);
}
@Test
public void testArithmetic() throws Throwable {
+ testDebugging("arithmetic", "Arithmetic");
+ }
+
+ @Test
+ public void testArrayAccess() throws Exception {
+ testDebugging("arrayaccess", "ArrayAccess");
+ }
+
+ @Test
+ public void testBArray() throws Exception {
+ testDebugging("barray", "BArray");
+ }
+
+ @Test
+ public void testBridgeMethod() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("bridge", "BridgeMethod");
+ }
+
+ @Test
+ public void testCommonSubexpressionElimination() throws Exception {
+ testDebugging("cse", "CommonSubexpressionElimination");
+ }
+
+ @Test
+ public void testConstants() throws Exception {
+ testDebugging("constants", "Constants");
+ }
+
+ @Test
+ public void testControlFlow() throws Exception {
+ testDebugging("controlflow", "ControlFlow");
+ }
+
+ @Test
+ public void testConversions() throws Exception {
+ testDebugging("conversions", "Conversions");
+ }
+
+ @Test
+ public void testFloatingPointValuedAnnotation() throws Exception {
+ // D8 has no source file.
+ testDebuggingJvmOnly("floating_point_annotations", "FloatingPointValuedAnnotationTest");
+ }
+
+ @Test
+ public void testFilledArray() throws Exception {
+ testDebugging("filledarray", "FilledArray");
+ }
+
+ @Test
+ public void testHello() throws Exception {
+ testDebugging("hello", "Hello");
+ }
+
+ @Test
+ public void testIfStatements() throws Exception {
+ testDebugging("ifstatements", "IfStatements");
+ }
+
+ @Test
+ public void testInstanceVariable() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("instancevariable", "InstanceVariable");
+ }
+
+ @Test
+ public void testInstanceofString() throws Exception {
+ testDebugging("instanceofstring", "InstanceofString");
+ }
+
+ @Test
+ public void testInvoke() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("invoke", "Invoke");
+ }
+
+ @Test
+ public void testJumboString() throws Exception {
+ testDebugging("jumbostring", "JumboString");
+ }
+
+ @Test
+ public void testLoadConst() throws Exception {
+ testDebugging("loadconst", "LoadConst");
+ }
+
+ @Test
+ public void testUdpServer() throws Exception {
+ // TODO(b/79671093): We don't match JVM's behavior on this example.
+ testDebuggingJvmOutputOnly("loop", "UdpServer");
+ }
+
+ @Test
+ public void testNewArray() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("newarray", "NewArray");
+ }
+
+ @Test
+ public void testRegAlloc() throws Exception {
+ testDebugging("regalloc", "RegAlloc");
+ }
+
+ @Test
+ public void testReturns() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("returns", "Returns");
+ }
+
+ @Test
+ public void testStaticField() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("staticfield", "StaticField");
+ }
+
+ @Test
+ public void testStringBuilding() throws Exception {
+ testDebugging("stringbuilding", "StringBuilding");
+ }
+
+ @Test
+ public void testSwitches() throws Exception {
+ testDebugging("switches", "Switches");
+ }
+
+ @Test
+ public void testSync() throws Exception {
+ // D8 has two local variables with empty names.
+ testDebuggingJvmOnly("sync", "Sync");
+ }
+
+ @Test
+ public void testThrowing() throws Exception {
+ // TODO(b/79671093): We don't match JVM's behavior on this example.
+ testDebuggingJvmOutputOnly("throwing", "Throwing");
+ }
+
+ @Test
+ public void testTrivial() throws Exception {
+ testDebugging("trivial", "Trivial");
+ }
+
+ @Ignore("TODO(mathiasr): InvalidDebugInfoException in CfSourceCode")
+ @Test
+ public void testTryCatch() throws Exception {
+ // TODO(b/79671093): We don't match JVM's behavior on this example.
+ testDebuggingJvmOutputOnly("trycatch", "TryCatch");
+ }
+
+ @Test
+ public void testNestedTryCatches() throws Exception {
+ testDebugging("nestedtrycatches", "NestedTryCatches");
+ }
+
+ @Test
+ public void testTryCatchMany() throws Exception {
+ testDebugging("trycatchmany", "TryCatchMany");
+ }
+
+ @Test
+ public void testInvokeEmpty() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("invokeempty", "InvokeEmpty");
+ }
+
+ @Test
+ public void testRegress() throws Exception {
+ testDebugging("regress", "Regress");
+ }
+
+ @Test
+ public void testRegress2() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("regress2", "Regress2");
+ }
+
+ @Ignore("TODO(mathiasr): Different behavior CfSourceCode vs JarSourceCode")
+ @Test
+ public void testRegress37726195() throws Exception {
+ // TODO(b/79671093): We don't match JVM's behavior on this example.
+ testDebuggingJvmOutputOnly("regress_37726195", "Regress");
+ }
+
+ @Test
+ public void testRegress37658666() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("regress_37658666", "Regress");
+ }
+
+ @Test
+ public void testRegress37875803() throws Exception {
+ testDebugging("regress_37875803", "Regress");
+ }
+
+ @Test
+ public void testRegress37955340() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("regress_37955340", "Regress");
+ }
+
+ @Test
+ public void testRegress62300145() throws Exception {
+ // D8 has no source file.
+ testDebuggingJvmOnly("regress_62300145", "Regress");
+ }
+
+ @Test
+ public void testRegress64881691() throws Exception {
+ testDebugging("regress_64881691", "Regress");
+ }
+
+ @Test
+ public void testRegress65104300() throws Exception {
+ testDebugging("regress_65104300", "Regress");
+ }
+
+ @Ignore("TODO(b/79671093): This test seems to take forever")
+ @Test
+ public void testRegress70703087() throws Exception {
+ testDebugging("regress_70703087", "Test");
+ }
+
+ @Test
+ public void testRegress70736958() throws Exception {
+ // D8 has a local variable with empty name.
+ testDebuggingJvmOnly("regress_70736958", "Test");
+ }
+
+ @Ignore("TODO(mathiasr): Different behavior CfSourceCode vs JarSourceCode")
+ @Test
+ public void testRegress70737019() throws Exception {
+ // TODO(b/79671093): We don't match JVM's behavior on this example.
+ testDebuggingJvmOutputOnly("regress_70737019", "Test");
+ }
+
+ @Test
+ public void testRegress72361252() throws Exception {
+ // D8 output has variable with empty name.
+ testDebuggingJvmOnly("regress_72361252", "Test");
+ }
+
+ @Test
+ public void testMemberrebinding2() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("memberrebinding2", "Memberrebinding");
+ }
+
+ @Test
+ public void testMemberrebinding3() throws Exception {
+ testDebugging("memberrebinding3", "Memberrebinding");
+ }
+
+ @Test
+ public void testMinification() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("minification", "Minification");
+ }
+
+ @Test
+ public void testEnclosingmethod() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("enclosingmethod", "Main");
+ }
+
+ @Test
+ public void testEnclosingmethod_proguarded() throws Exception {
+ // TODO(b/79671093): We don't match JVM's behavior on this example.
+ testDebuggingJvmOutputOnly("enclosingmethod_proguarded", "Main");
+ }
+
+ @Test
+ public void testInterfaceinlining() throws Exception {
+ testDebugging("interfaceinlining", "Main");
+ }
+
+ @Test
+ public void testSwitchmaps() throws Exception {
+ // TODO(b/79671093): D8 has different line number info during stepping.
+ testDebuggingJvmOnly("switchmaps", "Switches");
+ }
+
+ private void testDebugging(String pkg, String clazz) throws Exception {
+ init(pkg, clazz)
+ .add("Input", input())
+ .add("R8/CfSourceCode", r8cf())
+ .add("R8/JarSourceCode", r8jar())
+ .add("D8", d8())
+ .compare();
+ }
+
+ private void testDebuggingJvmOnly(String pkg, String clazz) throws Exception {
+ init(pkg, clazz)
+ .add("Input", input())
+ .add("R8/CfSourceCode", r8cf())
+ .add("R8/JarSourceCode", r8jar())
+ .compare();
+ }
+
+ private void testDebuggingJvmOutputOnly(String pkg, String clazz) throws Exception {
+ init(pkg, clazz)
+ .add("R8/CfSourceCode", r8cf())
+ .add("R8/JarSourceCode", r8jar())
+ .compare();
+ }
+
+ private DebugStreamComparator init(String pkg, String clazz) throws Exception {
// See verifyStateLocation in DebugTestBase.
- Assume.assumeTrue("Streaming on Dalvik DEX runtimes has some unknown interference issue",
+ Assume.assumeTrue(
+ "Streaming on Dalvik DEX runtimes has some unknown interference issue",
ToolHelper.getDexVm().getVersion().isAtLeast(Version.V6_0_1));
- Assume.assumeTrue("Skipping test " + testName.getMethodName()
+ Assume.assumeTrue(
+ "Skipping test "
+ + testName.getMethodName()
+ " because debug tests are not yet supported on Windows",
!ToolHelper.isWindows());
- String pkg = "arithmetic";
- String clazzName = pkg + ".Arithmetic";
- Stream<DebuggeeState> cf = streamDebugTest(cfConfig("arithmetic"), clazzName, ANDROID_FILTER);
- Stream<DebuggeeState> d8 = streamDebugTest(d8Config("arithmetic"), clazzName, ANDROID_FILTER);
- Stream<DebuggeeState> r8 =
- streamDebugTest(r8DebugCfConfig("arithmetic"), clazzName, ANDROID_FILTER);
- new DebugStreamComparator()
- .add("CF", cf)
- .add("D8", d8)
- .add("R8/CF", r8)
- .compare();
+ clazzName = pkg + "." + clazz;
+ inputJar =
+ Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + "_debuginfo_all" + FileUtils.JAR_EXTENSION);
+ return new DebugStreamComparator();
}
}
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
index 843a0f8..bc390b9 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -119,12 +119,6 @@
} else {
builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
}
- // TODO(b/75997473): Enable inlining when supported by CF backend
- ToolHelper.runR8(
- builder.build(),
- options -> {
- options.enableInlining = false;
- options.invalidDebugInfoFatal = true;
- });
+ ToolHelper.runR8(builder.build(), options -> options.invalidDebugInfoFatal = true);
}
}
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
index 5ddd98c..63bc007 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
@@ -4,9 +4,6 @@
package com.android.tools.r8.debug;
-import com.android.tools.r8.ToolHelper;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
index cdba21b..3837bb1 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -6,40 +6,46 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import org.apache.harmony.jpda.tests.framework.jdwp.Value;
import org.junit.Test;
-// TODO check double-depth inline (an inline in another inline)
public class KotlinInlineTest extends KotlinDebugTestBase {
+ public static final String DEBUGGEE_CLASS = "KotlinInline";
+ public static final String SOURCE_FILE = "KotlinInline.kt";
+
@Test
public void testStepOverInline() throws Throwable {
String methodName = "singleInline";
runDebugTest(
getD8Config(),
- "KotlinInline",
- breakpoint("KotlinInline", methodName),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, methodName),
run(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(41, s.getLineNumber());
s.checkLocal("this");
}),
stepOver(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(42, s.getLineNumber());
s.checkLocal("this");
}),
kotlinStepOver(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(43, s.getLineNumber());
s.checkLocal("this");
}),
@@ -51,29 +57,29 @@
String methodName = "singleInline";
runDebugTest(
getD8Config(),
- "KotlinInline",
- breakpoint("KotlinInline", methodName),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, methodName),
run(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(41, s.getLineNumber());
s.checkLocal("this");
}),
stepOver(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(42, s.getLineNumber());
s.checkLocal("this");
}),
stepInto(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
// The actual line number (the one encoded in debug information) is different than the
// source file one.
// TODO(shertz) extract original line number from JSR-45's SMAP (only supported on
@@ -89,47 +95,65 @@
String methodName = "singleInline";
runDebugTest(
getD8Config(),
- "KotlinInline",
- breakpoint("KotlinInline", methodName),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, methodName),
run(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(41, s.getLineNumber());
s.checkLocal("this");
}),
stepOver(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(42, s.getLineNumber());
s.checkLocal("this");
}),
stepInto(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
}),
kotlinStepOut(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(43, s.getLineNumber());
s.checkLocal("this");
}),
run());
}
+ private static String mangleFunctionNameFromInlineScope(String functionName) {
+ return "$i$f$" + functionName;
+ }
+
+ private static String mangleLambdaNameFromInlineScope(String functionName, int lambdaId) {
+ assert lambdaId > 0;
+ return "$i$a$" + lambdaId + "$" + functionName;
+ }
+
+ private static String mangleLvNameFromInlineScope(String lvName, int inlineDepth) {
+ assert inlineDepth > 0;
+ StringBuilder builder = new StringBuilder(lvName);
+ for (int i = 0; i < inlineDepth; ++i) {
+ builder.append("$iv");
+ }
+ return builder.toString();
+ }
+
@Test
public void testKotlinInline() throws Throwable {
final String inliningMethodName = "invokeInlinedFunctions";
runDebugTest(
getD8Config(),
- "KotlinInline",
- breakpoint("KotlinInline", inliningMethodName),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, inliningMethodName),
run(),
inspect(s -> {
assertEquals(inliningMethodName, s.getMethodName());
@@ -157,8 +181,8 @@
s.checkLocal("this");
s.checkLocal("inA", Value.createInt(1));
// This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
- s.checkLocal("$i$f$inlinedA");
- s.checkLocal("$i$a$1$inlinedA");
+ s.checkLocal(mangleFunctionNameFromInlineScope("inlinedA"));
+ s.checkLocal(mangleLambdaNameFromInlineScope("inlinedA", 1));
}),
stepInto(),
inspect(s -> {
@@ -181,10 +205,144 @@
s.checkLocal("this");
s.checkLocal("inB", Value.createInt(2));
// This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
- s.checkLocal("$i$f$inlinedB");
- s.checkLocal("$i$a$1$inlinedB");
+ s.checkLocal(mangleFunctionNameFromInlineScope("inlinedB"));
+ s.checkLocal(mangleLambdaNameFromInlineScope("inlinedB", 1));
}),
run());
}
+ @Test
+ public void testNestedInlining() throws Throwable {
+ // Count the number of lines in the source file. This is needed to check that inlined code
+ // refers to non-existing line numbers.
+ Path sourceFilePath = Paths.get(ToolHelper.TESTS_DIR, "debugTestResourcesKotlin", SOURCE_FILE);
+ assert sourceFilePath.toFile().exists();
+ final int maxLineNumber = Files.readAllLines(sourceFilePath).size();
+ final String inliningMethodName = "testNestedInlining";
+
+ // Local variables that represent the scope (start,end) of function's code that has been
+ // inlined.
+ final String inlinee1_inlineScope = mangleFunctionNameFromInlineScope("inlinee1");
+ final String inlinee2_inlineScope = mangleFunctionNameFromInlineScope("inlinee2");
+
+ // Local variables that represent the scope (start,end) of lambda's code that has been inlined.
+ final String inlinee2_lambda1_inlineScope = mangleLambdaNameFromInlineScope("inlinee2", 1);
+ final String inlinee2_lambda2_inlineScope = mangleLambdaNameFromInlineScope("inlinee2", 2);
+ final String c_mangledLvName = mangleLvNameFromInlineScope("c", 1);
+ final String left_mangledLvName = mangleLvNameFromInlineScope("left", 1);
+ final String right_mangledLvName = mangleLvNameFromInlineScope("right", 1);
+ final String p_mangledLvName = mangleLvNameFromInlineScope("p", 2);
+
+ runDebugTest(
+ getD8Config(),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, inliningMethodName),
+ run(),
+ inspect(s -> {
+ assertEquals(inliningMethodName, s.getMethodName());
+ assertEquals(52, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ checkLocal("this"),
+ checkNoLocals("l1", "l2"),
+ stepOver(),
+ checkLine(SOURCE_FILE, 53),
+ checkLocals("this", "l1"),
+ checkNoLocal("l2"),
+ stepOver(),
+ checkLine(SOURCE_FILE, 54),
+ checkLocals("this", "l1", "l2"),
+ stepInto(),
+ // We jumped into 1st inlinee but the current method is the same
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ inspect(state -> {
+ assertEquals(SOURCE_FILE, state.getSourceFile());
+ assertTrue(state.getLineNumber() > maxLineNumber);
+ }),
+ checkNoLocal(c_mangledLvName),
+ stepInto(),
+ checkLocal(c_mangledLvName),
+ stepInto(),
+ // We jumped into 2nd inlinee which is nested in the 1st inlinee
+ checkLocal(inlinee2_inlineScope),
+ checkLocal(inlinee1_inlineScope),
+ inspect(state -> {
+ assertEquals(SOURCE_FILE, state.getSourceFile());
+ assertTrue(state.getLineNumber() > maxLineNumber);
+ }),
+ // We must see the local variable "p" with a 2-level inline depth.
+ checkLocal(p_mangledLvName),
+ checkNoLocals(left_mangledLvName, right_mangledLvName),
+ // Enter the if block of inlinee2
+ stepInto(),
+ checkLocal(p_mangledLvName),
+ checkNoLocals(left_mangledLvName, right_mangledLvName),
+ // Enter the inlined lambda
+ stepInto(),
+ checkLocal(p_mangledLvName),
+ checkLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocals(left_mangledLvName, right_mangledLvName),
+ stepInto(),
+ checkLocal(inlinee2_lambda1_inlineScope),
+ checkLocal(left_mangledLvName),
+ checkNoLocal(right_mangledLvName),
+ stepInto(),
+ checkLocals(left_mangledLvName, right_mangledLvName),
+ // Enter "foo"
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, "foo"),
+ checkLine(SOURCE_FILE, 34),
+ stepOut(),
+ // We're back to the inline section, at the end of the lambda
+ inspect(state -> {
+ assertEquals(SOURCE_FILE, state.getSourceFile());
+ assertTrue(state.getLineNumber() > maxLineNumber);
+ }),
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocal(inlinee2_lambda2_inlineScope),
+ stepInto(),
+ // We're in inlinee2, after the call to the inlined lambda.
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocal(inlinee2_lambda2_inlineScope),
+ checkLocal(p_mangledLvName),
+ stepInto(),
+ // We're out of inlinee2
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ checkNoLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ // Enter the new call to "inlinee2"
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocal(inlinee2_lambda2_inlineScope),
+ checkNoLocal(p_mangledLvName),
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocal(inlinee2_lambda2_inlineScope),
+ checkNoLocal(p_mangledLvName),
+ // We enter the 2nd lambda
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ checkLocal(inlinee2_lambda2_inlineScope),
+ // Enter the call to "foo"
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, "foo"),
+ checkLine(SOURCE_FILE, 34),
+ run());
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
new file mode 100644
index 0000000..3ea4017
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
@@ -0,0 +1,77 @@
+// 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.debug;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class NopDebugTestRunner extends DebugTestBase {
+
+ private final Class<?> CLAZZ = NopTest.class;
+
+ @Test
+ public void testNop() throws Exception {
+ // R8 will dead-code eliminate "args = args" in NopTest.
+ // In debug mode the programmer should still be able to break on dead code,
+ // so R8 inserts a NOP in the output. Test that CfNop.buildIR() works.
+ runTwiceTest(writeInput(CLAZZ), CLAZZ.getName());
+ }
+
+ private Path writeInput(Class<?> clazz) throws Exception {
+ Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+ ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(clazz.getName());
+ inputConsumer.accept(ToolHelper.getClassAsBytes(clazz), descriptor, null);
+ inputConsumer.finished(null);
+ return inputJar;
+ }
+
+ private void runTwiceTest(Path inputJar, String mainClass) throws Exception {
+ Path output1 = runR8(inputJar, "output1.jar");
+ Path output2 = runR8(output1, "output2.jar");
+ stepOutput(mainClass, inputJar, output1, output2);
+ }
+
+ private Path runR8(Path inputJar, String outputName) throws Exception {
+ Path outputJar = temp.getRoot().toPath().resolve(outputName);
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .setMode(CompilationMode.DEBUG)
+ .addProgramFiles(inputJar)
+ .setOutput(outputJar, OutputMode.ClassFile)
+ .build(),
+ o -> o.enableCfFrontend = true);
+ return outputJar;
+ }
+
+ private void stepOutput(String mainClass, Path inputJar, Path output1, Path output2)
+ throws Exception {
+ Assume.assumeTrue(
+ "Skipping test "
+ + testName.getMethodName()
+ + " because debug tests are not yet supported on Windows",
+ !ToolHelper.isWindows());
+ new DebugStreamComparator()
+ .add("Input", streamDebugTest(mainClass, new CfDebugTestConfig(inputJar)))
+ .add("R8/CF", streamDebugTest(mainClass, new CfDebugTestConfig(output1)))
+ .add("R8/CF^2", streamDebugTest(mainClass, new CfDebugTestConfig(output2)))
+ .compare();
+ }
+
+ private Stream<DebuggeeState> streamDebugTest(String mainClass, DebugTestConfig config)
+ throws Exception {
+ return streamDebugTest(config, mainClass, ANDROID_FILTER);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/NopTest.java b/src/test/java/com/android/tools/r8/debug/NopTest.java
new file mode 100644
index 0000000..8d51961
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/NopTest.java
@@ -0,0 +1,12 @@
+// 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.debug;
+
+public class NopTest {
+
+ public static void main(String[] args) {
+ args = args;
+ System.out.println(args);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
index 108b474..103c07f 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
@@ -78,12 +78,6 @@
} else {
builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
}
- // TODO(b/75997473): Enable inlining when supported by CF backend
- ToolHelper.runR8(
- builder.build(),
- options -> {
- options.enableInlining = false;
- options.invalidDebugInfoFatal = true;
- });
+ ToolHelper.runR8(builder.build(), options -> options.invalidDebugInfoFatal = true);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
index f9541e5..4f2bdeb 100644
--- a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
@@ -115,12 +115,6 @@
} else {
builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
}
- // TODO(b/75997473): Enable inlining when supported by CF backend
- ToolHelper.runR8(
- builder.build(),
- options -> {
- options.enableInlining = false;
- options.invalidDebugInfoFatal = true;
- });
+ ToolHelper.runR8(builder.build(), options -> options.invalidDebugInfoFatal = true);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 5150223..443a20e 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.DexInspector;
@@ -57,7 +58,7 @@
AppInfo appInfo = new AppInfo(dexApplication);
DexInspector dexInspector = new DexInspector(appInfo.app);
DexEncodedMethod foo = dexInspector.clazz(mainClass.getName()).method(signature).getMethod();
- IRCode irCode = foo.buildIR(TEST_OPTIONS);
+ IRCode irCode = foo.buildIR(TEST_OPTIONS, Origin.unknown());
NonNullTracker nonNullTracker = new NonNullTracker();
nonNullTracker.addNonNull(irCode);
TypeAnalysis analysis = new TypeAnalysis(appInfo, foo, irCode);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index ed15f7c..509b922 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -125,7 +125,7 @@
new MethodSignature("subtractConstants8bitRegisters", "int", ImmutableList.of()))
.getMethod();
try {
- IRCode irCode = subtract.buildIR(TEST_OPTIONS);
+ IRCode irCode = subtract.buildIR(TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, subtract, irCode);
analysis.forEach((v, l) -> {
assertEither(l, PRIMITIVE, NULL, TOP);
@@ -143,7 +143,7 @@
.method(new MethodSignature("fibonacci", "int", ImmutableList.of("int")))
.getMethod();
try {
- IRCode irCode = fib.buildIR(TEST_OPTIONS);
+ IRCode irCode = fib.buildIR(TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
analysis.forEach((v, l) -> {
assertEither(l, PRIMITIVE, NULL);
@@ -161,7 +161,7 @@
.method(new MethodSignature("test1", "int[]", ImmutableList.of()))
.getMethod();
try {
- IRCode irCode = test1.buildIR(TEST_OPTIONS);
+ IRCode irCode = test1.buildIR(TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
Value array = null;
InstructionIterator iterator = irCode.instructionIterator();
@@ -196,7 +196,7 @@
.method(new MethodSignature("test4", "int[]", ImmutableList.of()))
.getMethod();
try {
- IRCode irCode = test4.buildIR(TEST_OPTIONS);
+ IRCode irCode = test4.buildIR(TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
Value array = null;
InstructionIterator iterator = irCode.instructionIterator();
@@ -231,7 +231,7 @@
.method(new MethodSignature("loop2", "void", ImmutableList.of()))
.getMethod();
try {
- IRCode irCode = loop2.buildIR(TEST_OPTIONS);
+ IRCode irCode = loop2.buildIR(TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
analysis.forEach((v, l) -> {
if (l.isClassTypeLatticeElement()) {
@@ -254,7 +254,7 @@
.method(new MethodSignature("test2_throw", "int", ImmutableList.of()))
.getMethod();
try {
- IRCode irCode = test2.buildIR(TEST_OPTIONS);
+ IRCode irCode = test2.buildIR(TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
analysis.forEach((v, l) -> {
if (l.isClassTypeLatticeElement()) {
@@ -284,7 +284,7 @@
CheckCast.class, new ClassTypeLatticeElement(test, true),
NewInstance.class, new ClassTypeLatticeElement(test, false));
try {
- IRCode irCode = method.buildIR(TEST_OPTIONS);
+ IRCode irCode = method.buildIR(TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
} catch (ApiLevelException e) {
@@ -306,7 +306,7 @@
InstanceOf.class, PRIMITIVE,
StaticGet.class, new ClassTypeLatticeElement(test, true));
try {
- IRCode irCode = method.buildIR(TEST_OPTIONS);
+ IRCode irCode = method.buildIR(TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
} catch (ApiLevelException e) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index b6109d4..fac2ca0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterNullCheck;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
@@ -47,7 +48,7 @@
AppInfo appInfo = new AppInfo(dexApplication);
DexInspector dexInspector = new DexInspector(appInfo.app);
DexEncodedMethod foo = dexInspector.clazz(testClass.getName()).method(signature).getMethod();
- IRCode irCode = foo.buildIR(TEST_OPTIONS);
+ IRCode irCode = foo.buildIR(TEST_OPTIONS, Origin.unknown());
checkCountOfNonNull(irCode, 0);
NonNullTracker nonNullTracker = new NonNullTracker();
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
index f03e6f6..08058e5 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
@@ -5,12 +5,9 @@
package com.android.tools.r8.ir.regalloc;
import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import org.junit.Test;
@@ -22,13 +19,13 @@
DexInspector inspector = new DexInspector(app);
DexInspector.ClassSubject clazz = inspector.clazz(TestClass.class);
assertThat(clazz, isPresent());
- // TODO(christofferqa): Ensure runOnArt checks that there are no verification errors, and then
- // use runOnArt instead of runOnArtRaw.
- ToolHelper.ProcessResult d8Result = runOnArtRaw(app, TestClass.class.getCanonicalName());
- assertThat(d8Result.stderr, not(containsString("Verification error")));
+ // Throws if a method in TestClass does not verify.
+ runOnArt(app, TestClass.class.getName());
}
private static class TestClass {
+ public static void main(String[] args) {}
+
public void method() {
Object x = this;
TestClass y = this;
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 674fba8..b50718d 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -5,6 +5,8 @@
import static com.android.tools.r8.utils.DescriptorUtils.getPathFromDescriptor;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -136,7 +138,11 @@
return addMethod("public", name, argumentTypes, returnType, lines);
}
- public MethodSignature addNativeMethod(
+ /**
+ * Note that the JVM rejects native methods with code. This method is used to test that D8
+ * removes code from native methods.
+ */
+ public MethodSignature addNativeMethodWithCode(
String name,
List<String> argumentTypes,
String returnType,
@@ -367,6 +373,19 @@
return outputs;
}
+ public void writeClassFiles(ClassFileConsumer consumer, DiagnosticsHandler handler)
+ throws Exception {
+ for (ClassBuilder clazz : classes) {
+ consumer.accept(compile(clazz), clazz.getDescriptor(), handler);
+ }
+ }
+
+ public void writeJar(Path output, DiagnosticsHandler handler) throws Exception {
+ ClassFileConsumer consumer = new ClassFileConsumer.ArchiveConsumer(output);
+ writeClassFiles(consumer, handler);
+ consumer.finished(handler);
+ }
+
public DexApplication read() throws Exception {
return read(new InternalOptions());
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index d6b26f0..2ff2f71 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -619,7 +619,7 @@
DexAnnotationSet.empty(),
DexAnnotationSetRefList.empty(),
code);
- IRCode ir = code.buildIR(method, options);
+ IRCode ir = code.buildIR(method, options, Origin.unknown());
RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
method.setCode(ir, allocator, options);
directMethods[i] = method;
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index 490162a..3429c2a 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -12,7 +12,6 @@
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.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -30,7 +29,6 @@
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(Utils.class));
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
builder.setMode(CompilationMode.RELEASE);
builder.addProguardConfiguration(
ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java b/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
index bf13fc3..bf74250 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
@@ -5,6 +5,8 @@
package com.android.tools.r8.movestringconstants;
public class TestClass {
+ public static void main(String[] args) {}
+
static void foo(String arg1, String arg2, String arg3, String arg4) {
Utils.check(arg1, "StringConstants::foo#1");
Utils.check("", "StringConstants::foo#2");
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
index 3b2cf8c..e1fa3d0 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -100,11 +100,6 @@
for (Class<?> c : CLASSES) {
builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
}
- if (consumer instanceof ClassFileConsumer) {
- // TODO(b/75997473): Enable inlining when supported by CF backend
- ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
- } else {
- ToolHelper.runR8(builder.build());
- }
+ ToolHelper.runR8(builder.build());
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
index 9e062b3..c7c74e9 100644
--- a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -16,7 +16,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import java.io.IOException;
@@ -119,13 +118,10 @@
.addProgramFiles(inputJar)
.setProgramConsumer(consumer)
.addProguardConfigurationFiles(writeProguardRules(aggressive));
- if (consumer instanceof ClassFileConsumer) {
- // TODO(b/75997473): Enable inlining when supported by CF backend
- ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
- } else {
+ if (!(consumer instanceof ClassFileConsumer)) {
builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
- ToolHelper.runR8(builder.build());
}
+ ToolHelper.runR8(builder.build());
}
private void buildAndRunProguard(String outName, boolean aggressive) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index 4a7ba49..1f9c20f 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -13,7 +13,6 @@
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.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -31,7 +30,6 @@
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
builder.setMode(mode);
builder.addProguardConfiguration(
ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/regress/b72485384/Main.java b/src/test/java/com/android/tools/r8/regress/b72485384/Main.java
index c569c5e..f492fcc 100644
--- a/src/test/java/com/android/tools/r8/regress/b72485384/Main.java
+++ b/src/test/java/com/android/tools/r8/regress/b72485384/Main.java
@@ -3,14 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.regress.b72485384;
-import com.google.common.base.Functions;
+import java.util.function.Function;
public class Main {
public static void main(String[] args) {
GenericOuter<String> outer = new GenericOuter<>();
- GenericOuter<String>.GenericInner<String> inner = outer
- .makeInner(Functions.identity(), "Hello World!");
+ GenericOuter<String>.GenericInner<String> inner =
+ outer.makeInner(Function.identity(), "Hello World!");
System.out.println(inner.innerGetter(inner));
System.out.println(outer.outerGetter(inner));
}
diff --git a/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java b/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
index 3205434..ba65d92 100644
--- a/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
@@ -3,47 +3,70 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.regress.b72485384;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.regress.b72485384.GenericOuter.GenericInner;
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class Regress72485384Test extends TestBase {
+ @Rule public ExpectedException thrown = ExpectedException.none();
@Parameters(name = "{0}")
- public static List<String> getParameters() {
+ public static Collection<Object[]> getParameters() {
String baseConfig =
keepMainProguardConfiguration(Main.class)
+ "-keepattributes Signature,InnerClasses,EnclosingMethod ";
- return ImmutableList.of(
- baseConfig,
- baseConfig + "-dontshrink",
- baseConfig + "-dontshrink -dontobfuscate",
- baseConfig + "-dontobfuscate",
- "",
- "-dontshrink",
- "-keep class DoesNotExist -dontshrink"
- );
+ return Arrays.asList(
+ new Object[][] {
+ {baseConfig, null},
+ {baseConfig + "-dontshrink", null},
+ {baseConfig + "-dontshrink -dontobfuscate", null},
+ {baseConfig + "-dontobfuscate", null},
+ {"", null},
+ {"-dontshrink", null},
+ {"-keep class DoesNotExist -dontshrink", "ClassNotFoundException"}
+ });
}
private final static List<Class> CLASSES = ImmutableList
.of(GenericOuter.class, GenericInner.class, Main.class);
private final String proguardConfig;
+ private final String expectedErrorMessage;
- public Regress72485384Test(String proguardConfig) {
+ public Regress72485384Test(String proguardConfig, String expectedErrorMessage) {
this.proguardConfig = proguardConfig;
+ this.expectedErrorMessage = expectedErrorMessage;
}
@Test
public void testSignatureRewrite() throws Exception {
- AndroidApp result = compileWithR8(CLASSES, proguardConfig);
- runOnArt(result, Main.class.getCanonicalName());
+ AndroidApp app = compileWithR8(CLASSES, proguardConfig);
+
+ if (expectedErrorMessage == null) {
+ if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(ToolHelper.DexVm.Version.V6_0_1)) {
+ // Resolution of java.util.function.Function fails.
+ thrown.expect(AssertionError.class);
+ }
+
+ runOnArt(app, Main.class.getCanonicalName());
+ } else {
+ ToolHelper.ProcessResult result = runOnArtRaw(app, Main.class.getCanonicalName());
+ assertThat(result.stderr, containsString(expectedErrorMessage));
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
index 3e75ac6..d1fc83e 100644
--- a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
+++ b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
@@ -69,11 +69,6 @@
for (Class<?> c : CLASSES) {
builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
}
- if (consumer instanceof ClassFileConsumer) {
- // TODO(b/75997473): Enable inlining when supported by CF.
- ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
- } else {
- ToolHelper.runR8(builder.build());
- }
+ ToolHelper.runR8(builder.build());
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
index 27f5321..474af4d 100644
--- a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import java.io.IOException;
@@ -112,13 +111,10 @@
.addProgramFiles(inputJar)
.setProgramConsumer(consumer)
.addProguardConfigurationFiles(writeProguardRules(aggressive));
- if (consumer instanceof ClassFileConsumer) {
- // TODO(b/75997473): Enable inlining when supported by CF backend
- ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
- } else {
+ if (!(consumer instanceof ClassFileConsumer)) {
builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
- ToolHelper.runR8(builder.build());
}
+ ToolHelper.runR8(builder.build());
}
private void buildAndRunProguard(String outName, boolean aggressive) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
new file mode 100644
index 0000000..6ecdb09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -0,0 +1,201 @@
+// 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.shaking;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharSource;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import org.junit.Test;
+
+class A {
+ private static String buildClassName(String className) {
+ return A.class.getPackage().getName() + "." + className;
+ }
+
+ public static void main(String[] args) {
+ try {
+ Class bClass = Class.forName(buildClassName("B"));
+ System.out.println("YES");
+ } catch (ClassNotFoundException e) {
+ System.out.println("NO");
+ }
+ }
+}
+
+class B {
+
+}
+
+public class LibraryProvidedProguardRulesTest extends TestBase {
+ private void addTextJarEntry(JarOutputStream out, String name, String content) throws Exception {
+ out.putNextEntry(new ZipEntry(name));
+ ByteStreams.copy(
+ CharSource.wrap(content).asByteSource(StandardCharsets.UTF_8).openBufferedStream(), out);
+ out.closeEntry();
+ }
+
+ private AndroidApp runTest(List<String> rules, DiagnosticsHandler handler) throws Exception {
+ Path jar = temp.newFile("test.jar").toPath();
+ try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
+ addTestClassesToJar(out, A.class, B.class);
+ for (int i = 0; i < rules.size(); i++) {
+ String name = "META-INF/proguard/jar" + (i == 0 ? "" : i) + ".rules";
+ addTextJarEntry(out, name, rules.get(i));
+ }
+ }
+
+ try {
+ R8Command command = (handler != null ? R8Command.builder(handler) : R8Command.builder())
+ .addProgramFiles(jar)
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .build();
+ return ToolHelper.runR8(command);
+ } catch (CompilationFailedException e) {
+ assertNotNull(handler);
+ return null;
+ }
+ }
+
+ private AndroidApp runTest(String rules, DiagnosticsHandler handler) throws Exception {
+ return runTest(ImmutableList.of(rules), handler);
+ }
+
+
+ @Test
+ public void keepOnlyA() throws Exception {
+ AndroidApp app = runTest("-keep class " + A.class.getTypeName() +" {}", null);
+ DexInspector inspector = new DexInspector(app);
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), not(isPresent()));
+ }
+
+ @Test
+ public void keepOnlyB() throws Exception {
+ AndroidApp app = runTest("-keep class **B {}", null);
+ DexInspector inspector = new DexInspector(app);
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ assertThat(inspector.clazz(B.class), isPresent());
+ }
+
+ @Test
+ public void keepBoth() throws Exception {
+ AndroidApp app = runTest("-keep class ** {}", null);
+ DexInspector inspector = new DexInspector(app);
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ }
+
+ @Test
+ public void multipleFiles() throws Exception {
+ AndroidApp app = runTest(ImmutableList.of("-keep class **A {}", "-keep class **B {}"), null);
+ DexInspector inspector = new DexInspector(app);
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ }
+
+ private void checkOrigin(Origin origin) {
+ assertTrue(origin instanceof ArchiveEntryOrigin);
+ assertEquals(origin.part(), "META-INF/proguard/jar.rules");
+ assertTrue(origin.parent() instanceof PathOrigin);
+ }
+
+ @Test
+ public void syntaxError() throws Exception {
+ DiagnosticsChecker checker = new DiagnosticsChecker();
+ AndroidApp app = runTest("error", checker);
+ assertNull(app);
+ DiagnosticsChecker.checkDiagnostic(
+ checker.errors.get(0), this::checkOrigin, 1, 1, "Expected char '-'");
+ }
+
+ @Test
+ public void includeError() throws Exception {
+ DiagnosticsChecker checker = new DiagnosticsChecker();
+ AndroidApp app = runTest("-include other.rules", checker);
+ assertNull(app);
+ DiagnosticsChecker.checkDiagnostic(checker.errors.get(0), this::checkOrigin, 1, 10,
+ "Options with file names are not supported");
+ }
+
+ class TestProvider implements ProgramResourceProvider, DataResourceProvider {
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ byte[] bytes;
+ try {
+ bytes = ByteStreams.toByteArray(A.class.getResourceAsStream("A.class"));
+ } catch (IOException e) {
+ throw new ResourceException(Origin.unknown(), "Unexpected");
+ }
+ return ImmutableList.of(
+ ProgramResource.fromBytes(Origin.unknown(), Kind.CF, bytes,
+ Collections.singleton(DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()))));
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return this;
+ }
+
+ @Override
+ public void accept(Visitor visitor) throws ResourceException {
+ throw new ResourceException(Origin.unknown(), "Cannot provide data resources after all");
+ }
+ }
+
+ @Test
+ public void throwingDataResourceProvider() throws Exception {
+ DiagnosticsChecker checker = new DiagnosticsChecker();
+ try {
+ R8Command command = R8Command.builder(checker)
+ .addProgramResourceProvider(new TestProvider())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .build();
+ fail("Should not succeed");
+ } catch (CompilationFailedException e) {
+ DiagnosticsChecker.checkDiagnostic(
+ checker.errors.get(0),
+ origin -> assertSame(origin, Origin.unknown()),
+ "Cannot provide data resources after all");
+ }
+ }
+}
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 b7277cb..1012421 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1718,6 +1718,7 @@
assertEquals(0, handler.errors.size());
}
+ // TODO(sgjesse): Change to use DiagnosticsChecker.
private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
int columnStart, String... messageParts) {
assertEquals(1, diagnostics.size());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
index 8c67135..b77b29e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingInliningTest.java
@@ -21,7 +21,7 @@
public static Collection<Object[]> data() {
List<Object[]> parameters = new ArrayList<>();
for (MinifyMode minify : MinifyMode.values()) {
- // TODO(b/75997473): Add Frontend.JAR, Backend.CF when inlining is supported in CF backend.
+ parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
}
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index d2391ff..31abcf1 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
@@ -67,7 +68,7 @@
DexEncodedMethod method = getMethod(originalApplication, methodSig);
// Get the IR pre-optimization.
- IRCode code = method.buildIR(new InternalOptions());
+ IRCode code = method.buildIR(new InternalOptions(), Origin.unknown());
// Find the exit block and assert that the value is a phi merging the exceptional edge
// with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 29676da..ace5c9b 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -136,6 +136,19 @@
.read(app.getProguardMapOutputData()));
}
+ public DexInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
+ throws IOException, ExecutionException {
+ this(
+ new ApplicationReader(app, runOptionsConsumer(optionsConsumer), new Timing("DexInspector"))
+ .read(app.getProguardMapOutputData()));
+ }
+
+ private static InternalOptions runOptionsConsumer(Consumer<InternalOptions> optionsConsumer) {
+ InternalOptions internalOptions = new InternalOptions();
+ optionsConsumer.accept(internalOptions);
+ return internalOptions;
+ }
+
public DexInspector(AndroidApp app, Path proguardMap) throws IOException, ExecutionException {
this(
new ApplicationReader(app, new InternalOptions(), new Timing("DexInspector"))