Merge "Add --adb-options to tools/apk-masseur.py"
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index a3d92ac..b4a83a4 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -26,7 +26,9 @@
*/
public class D8Command extends BaseCommand {
- /** Builder for constructing a D8Command. */
+ /**
+ * Builder for constructing a D8Command.
+ */
public static class Builder extends BaseCommand.Builder<D8Command, Builder> {
private Builder() {
@@ -42,7 +44,9 @@
return this;
}
- /** Build the final D8Command. */
+ /**
+ * Build the final D8Command.
+ */
@Override
public D8Command build() throws CompilationException {
if (isPrintHelp() || isPrintVersion()) {
@@ -148,6 +152,8 @@
internal.allowAccessModification = false;
assert internal.inlineAccessors;
internal.inlineAccessors = false;
+ assert internal.removeSwitchMaps;
+ internal.removeSwitchMaps = false;
assert internal.outline.enabled;
internal.outline.enabled = false;
internal.lazyClasspathLoading = true;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index e98503f..f70a295 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -43,49 +43,65 @@
return this;
}
- /** Enable/disable tree shaking. This overrides any settings in proguard configuration files. */
+ /**
+ * Enable/disable tree shaking. This overrides any settings in proguard configuration files.
+ */
public Builder setTreeShaking(boolean useTreeShaking) {
treeShaking = Optional.of(useTreeShaking);
return this;
}
- /** Enable/disable minification. This overrides any settings in proguard configuration files. */
+ /**
+ * Enable/disable minification. This overrides any settings in proguard configuration files.
+ */
public Builder setMinification(boolean useMinification) {
minification = Optional.of(useMinification);
return this;
}
- /** Add proguard configuration file resources for automatic main dex list calculation. */
+ /**
+ * Add proguard configuration file resources for automatic main dex list calculation.
+ */
public Builder addMainDexRules(Path... paths) {
Collections.addAll(mainDexRules, paths);
return this;
}
- /** Add proguard configuration file resources for automatic main dex list calculation. */
+ /**
+ * Add proguard configuration file resources for automatic main dex list calculation.
+ */
public Builder addMainDexRules(List<Path> paths) {
mainDexRules.addAll(paths);
return this;
}
- /** Add proguard configuration file resources. */
+ /**
+ * Add proguard configuration file resources.
+ */
public Builder addProguardConfigurationFiles(Path... paths) {
Collections.addAll(proguardConfigFiles, paths);
return this;
}
- /** Add proguard configuration file resources. */
+ /**
+ * Add proguard configuration file resources.
+ */
public Builder addProguardConfigurationFiles(List<Path> paths) {
proguardConfigFiles.addAll(paths);
return this;
}
- /** Set a proguard mapping file resource. */
+ /**
+ * Set a proguard mapping file resource.
+ */
public Builder setProguardMapFile(Path path) {
getAppBuilder().setProguardMapFile(path);
return this;
}
- /** Set a package distribution file resource. */
+ /**
+ * Set a package distribution file resource.
+ */
public Builder setPackageDistributionFile(Path path) {
getAppBuilder().setPackageDistributionFile(path);
return this;
@@ -154,6 +170,7 @@
// Internal state to verify parsing properties not enforced by the builder.
private static class ParseState {
+
CompilationMode mode = null;
}
@@ -313,6 +330,7 @@
return useMinification;
}
+ @Override
InternalOptions getInternalOptions() {
InternalOptions internal = new InternalOptions(proguardConfiguration.getDexItemFactory());
assert !internal.debug;
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 40b0ead..cb9fb20 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -82,6 +82,7 @@
public DexString valueOfMethodName = createString("valueOf");
public DexString getClassMethodName = createString("getClass");
+ public DexString ordinalMethodName = createString("ordinal");
public DexString stringDescriptor = createString("Ljava/lang/String;");
public DexString objectDescriptor = createString("Ljava/lang/Object;");
@@ -139,6 +140,7 @@
}
public class LongMethods {
+
public DexMethod compare;
private LongMethods() {
@@ -148,18 +150,20 @@
}
public class ThrowableMethods {
+
public final DexMethod addSuppressed;
public final DexMethod getSuppressed;
private ThrowableMethods() {
addSuppressed = createMethod(throwableDescriptor,
- createString("addSuppressed"), voidDescriptor, new DexString[] { throwableDescriptor });
+ createString("addSuppressed"), voidDescriptor, new DexString[]{throwableDescriptor});
getSuppressed = createMethod(throwableDescriptor,
createString("getSuppressed"), throwableArrayDescriptor, DexString.EMPTY_ARRAY);
}
}
public class ObjectMethods {
+
public DexMethod getClass;
private ObjectMethods() {
@@ -169,6 +173,7 @@
}
public class ObjectsMethods {
+
public DexMethod requireNonNull;
private ObjectsMethods() {
@@ -178,6 +183,7 @@
}
public class StringBuildingMethods {
+
public DexMethod appendBoolean;
public DexMethod appendChar;
public DexMethod appendCharArray;
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 db44f9a..35cf808 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
@@ -13,6 +13,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -840,6 +841,15 @@
}
/**
+ * Remove an instruction.
+ */
+ public void removeInstruction(Instruction toRemove) {
+ int index = instructions.indexOf(toRemove);
+ assert index >= 0;
+ removeInstructions(Collections.singletonList(index));
+ }
+
+ /**
* Create a new basic block with a single goto instruction.
*
* <p>The constructed basic block has no predecessors and has one
@@ -1101,7 +1111,8 @@
}
}
- /** Append catch handlers from another block <code>fromBlock</code> (which must have catch
+ /**
+ * Append catch handlers from another block <code>fromBlock</code> (which must have catch
* handlers) to the catch handlers of this block.
*
* Note that after appending catch handlers their targets are referenced by both
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 96e00f2..a30444e 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
@@ -73,10 +73,18 @@
}
}
- private int numberOfKeys() {
+ public int numberOfKeys() {
return targetBlockIndices.length;
}
+ public int getKey(int index) {
+ if (type == Type.PACKED) {
+ return keys[0] + index;
+ } else {
+ return keys[index];
+ }
+ }
+
public int[] targetBlockIndices() {
return targetBlockIndices;
}
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 12f0aa2..b92ff71 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
@@ -412,7 +412,8 @@
if (!options.debug) {
return;
}
- Value value = writeRegister(register, MoveType.fromConstType(type), ThrowingInfo.NO_THROW, null);
+ Value value = writeRegister(register, MoveType.fromConstType(type), ThrowingInfo.NO_THROW,
+ null);
assert value.getLocalInfo() == null;
addInstruction(new DebugLocalUninitialized(type, value));
}
@@ -637,7 +638,8 @@
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(left, type);
Value in2 = readNumericRegister(right, type);
- Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+ Value out = writeNumericRegister(dest, type,
+ canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
Div instruction = new Div(type, out, in1, in2);
assert instruction.instructionTypeCanThrow() == canThrow;
add(instruction);
@@ -648,7 +650,8 @@
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(value, type);
Value in2 = readLiteral(type, constant);
- Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+ Value out = writeNumericRegister(dest, type,
+ canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
Div instruction = new Div(type, out, in1, in2);
assert instruction.instructionTypeCanThrow() == canThrow;
add(instruction);
@@ -697,7 +700,8 @@
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(left, type);
Value in2 = readNumericRegister(right, type);
- Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+ Value out = writeNumericRegister(dest, type,
+ canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
Rem instruction = new Rem(type, out, in1, in2);
assert instruction.instructionTypeCanThrow() == canThrow;
addInstruction(instruction);
@@ -708,7 +712,8 @@
boolean canThrow = type != NumericType.DOUBLE && type != NumericType.FLOAT;
Value in1 = readNumericRegister(value, type);
Value in2 = readLiteral(type, constant);
- Value out = writeNumericRegister(dest, type, canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
+ Value out = writeNumericRegister(dest, type,
+ canThrow ? ThrowingInfo.CAN_THROW : ThrowingInfo.NO_THROW);
Rem instruction = new Rem(type, out, in1, in2);
assert instruction.instructionTypeCanThrow() == canThrow;
addInstruction(instruction);
@@ -1119,6 +1124,7 @@
Value switchValue = readRegister(value, MoveType.SINGLE);
// Change a switch with just one case to an if.
+ // TODO(62247472): Move these into their own pass.
if (numberOfTargets == 1) {
addSwitchIf(keys[0], value, labelOffsets[0], fallthroughOffset);
return;
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 7aef78c..d12109e 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
@@ -426,6 +426,9 @@
if (memberValuePropagation != null) {
memberValuePropagation.rewriteWithConstantValues(code);
}
+ if (options.removeSwitchMaps) {
+ codeRewriter.removeSwitchMaps(code);
+ }
if (options.inlineAccessors && inliner != null) {
inliner.performInlining(method, code, callGraph);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 81a9e55..9034c37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -9,9 +9,11 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Binop;
@@ -28,6 +30,7 @@
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.JumpInstruction;
@@ -37,15 +40,26 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.ir.code.Switch.Type;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LongInterval;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
+import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -303,6 +317,244 @@
}
/**
+ * Inline the indirection of switch maps into the switch statement.
+ * <p>
+ * To ensure binary compatibility, javac generated code does not use ordinal values of enums
+ * directly in switch statements but instead generates a companion class that computes a mapping
+ * from switch branches to ordinals at runtime. As we have whole-program knowledge, we can
+ * analyze these maps and inline the indirection into the switch map again.
+ * <p>
+ * In particular, we look for code of the form
+ *
+ * <blockquote><pre>
+ * switch(CompanionClass.$switchmap$field[enumValue.ordinal()]) {
+ * ...
+ * }
+ * </pre></blockquote>
+ * See {@link #extractIndexMapFrom} and {@link #extractOrdinalsMapFor} for
+ * details of the companion class and ordinals computation.
+ */
+ public void removeSwitchMaps(IRCode code) {
+ for (BasicBlock block : code.blocks) {
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction insn = it.next();
+ // Pattern match a switch on a switch map as input.
+ if (insn.isSwitch()) {
+ Switch switchInsn = insn.asSwitch();
+ Instruction input = switchInsn.inValues().get(0).definition;
+ if (input == null || !input.isArrayGet()) {
+ continue;
+ }
+ ArrayGet arrayGet = input.asArrayGet();
+ Instruction index = arrayGet.index().definition;
+ if (index == null || !index.isInvokeVirtual()) {
+ continue;
+ }
+ InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
+ DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
+ DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
+ if (enumClass == null
+ || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
+ || ordinalMethod.name != dexItemFactory.ordinalMethodName
+ || ordinalMethod.proto.returnType != dexItemFactory.intType
+ || !ordinalMethod.proto.parameters.isEmpty()) {
+ continue;
+ }
+ Instruction array = arrayGet.array().definition;
+ if (array == null || !array.isStaticGet()) {
+ continue;
+ }
+ StaticGet staticGet = array.asStaticGet();
+ if (staticGet.getField().name.toSourceString().startsWith("$SwitchMap$")) {
+ Int2ReferenceMap<DexField> indexMap = extractIndexMapFrom(staticGet.getField());
+ if (indexMap == null || indexMap.isEmpty()) {
+ continue;
+ }
+ // Due to member rebinding, only the fields are certain to provide the actual enums
+ // class.
+ DexType switchMapHolder = indexMap.values().iterator().next().getHolder();
+ Reference2IntMap ordinalsMap = extractOrdinalsMapFor(switchMapHolder);
+ if (ordinalsMap != null) {
+ Int2IntMap targetMap = new Int2IntArrayMap();
+ int keys[] = new int[switchInsn.numberOfKeys()];
+ for (int i = 0; i < keys.length; i++) {
+ keys[i] = ordinalsMap.getInt(indexMap.get(switchInsn.getKey(i)));
+ targetMap.put(keys[i], switchInsn.targetBlockIndices()[i]);
+ }
+ Arrays.sort(keys);
+ int[] targets = new int[keys.length];
+ for (int i = 0; i < keys.length; i++) {
+ targets[i] = targetMap.get(keys[i]);
+ }
+
+ Switch newSwitch = new Switch(Type.SPARSE, ordinalInvoke.outValue(), keys,
+ targets, switchInsn.getFallthroughBlockIndex());
+ // Replace the switch itself.
+ it.replaceCurrentInstruction(newSwitch);
+ // If the original input to the switch is now unused, remove it too. It is not dead
+ // as it might have side-effects but we ignore these here.
+ if (arrayGet.outValue().numberOfUsers() == 0) {
+ arrayGet.inValues().forEach(v -> v.removeUser(arrayGet));
+ arrayGet.getBlock().removeInstruction(arrayGet);
+ }
+ if (staticGet.outValue().numberOfUsers() == 0) {
+ assert staticGet.inValues().isEmpty();
+ staticGet.getBlock().removeInstruction(staticGet);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Extracts the mapping from ordinal values to switch case constants.
+ * <p>
+ * This is done by pattern-matching on the class initializer of the synthetic switch map class.
+ * For a switch
+ *
+ * <blockquote><pre>
+ * switch (day) {
+ * case WEDNESDAY:
+ * case FRIDAY:
+ * System.out.println("3 or 5");
+ * break;
+ * case SUNDAY:
+ * System.out.println("7");
+ * break;
+ * default:
+ * System.out.println("other");
+ * }
+ * </pre></blockquote>
+ *
+ * the generated companing class initializer will have the form
+ *
+ * <blockquote><pre>
+ * class Switches$1 {
+ * static {
+ * $SwitchMap$switchmaps$Days[Days.WEDNESDAY.ordinal()] = 1;
+ * $SwitchMap$switchmaps$Days[Days.FRIDAY.ordinal()] = 2;
+ * $SwitchMap$switchmaps$Days[Days.SUNDAY.ordinal()] = 3;
+ * }
+ * </pre></blockquote>
+ *
+ * Note that one map per class is generated, so the map might contain additional entries as used
+ * by other switches in the class.
+ */
+ private Int2ReferenceMap<DexField> extractIndexMapFrom(DexField field) {
+ DexClass clazz = appInfo.definitionFor(field.getHolder());
+ if (!clazz.accessFlags.isSynthetic()) {
+ return null;
+ }
+ DexEncodedMethod initializer = clazz.getClassInitializer();
+ if (initializer == null || initializer.getCode() == null) {
+ return null;
+ }
+ IRCode code = initializer.getCode().buildIR(initializer, new InternalOptions());
+ Int2ReferenceMap<DexField> switchMap = new Int2ReferenceArrayMap<>();
+ for (BasicBlock block : code.blocks) {
+ InstructionListIterator it = block.listIterator();
+ Instruction insn = it.nextUntil(i -> i.isStaticGet() && i.asStaticGet().getField() == field);
+ if (insn == null) {
+ continue;
+ }
+ for (Instruction use : insn.outValue().uniqueUsers()) {
+ if (use.isArrayPut()) {
+ Instruction index = use.asArrayPut().source().definition;
+ if (index == null || !index.isConstNumber()) {
+ return null;
+ }
+ int integerIndex = index.asConstNumber().getIntValue();
+ Instruction value = use.asArrayPut().index().definition;
+ if (value == null || !value.isInvokeVirtual()) {
+ return null;
+ }
+ InvokeVirtual invoke = value.asInvokeVirtual();
+ DexClass holder = appInfo.definitionFor(invoke.getInvokedMethod().holder);
+ if (holder == null ||
+ (!holder.accessFlags.isEnum() && holder.type != dexItemFactory.enumType)) {
+ return null;
+ }
+ Instruction enumGet = invoke.arguments().get(0).definition;
+ if (enumGet == null || !enumGet.isStaticGet()) {
+ return null;
+ }
+ DexField enumField = enumGet.asStaticGet().getField();
+ if (!appInfo.definitionFor(enumField.getHolder()).accessFlags.isEnum()) {
+ return null;
+ }
+ if (switchMap.put(integerIndex, enumField) != null) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ }
+ return switchMap;
+ }
+
+ /**
+ * Extracts the ordinal values for an Enum class from the classes static initializer.
+ * <p>
+ * An Enum class has a field for each value. In the class initializer, each field is initialized
+ * to a singleton object that represents the value. This code matches on the corresponding call
+ * to the constructor (instance initializer) and extracts the value of the second argument, which
+ * is the ordinal.
+ */
+ private Reference2IntMap<DexField> extractOrdinalsMapFor(DexType enumClass) {
+ DexClass clazz = appInfo.definitionFor(enumClass);
+ if (clazz == null || clazz.isLibraryClass()) {
+ // We have to keep binary compatibility in tact for libraries.
+ return null;
+ }
+ DexEncodedMethod initializer = clazz.getClassInitializer();
+ if (!clazz.accessFlags.isEnum() || initializer == null || initializer.getCode() == null) {
+ return null;
+ }
+ IRCode code = initializer.getCode().buildIR(initializer, new InternalOptions());
+ Reference2IntMap<DexField> ordinalsMap = new Reference2IntArrayMap<>();
+ ordinalsMap.defaultReturnValue(-1);
+ InstructionIterator it = code.instructionIterator();
+ while (it.hasNext()) {
+ Instruction insn = it.next();
+ if (!insn.isStaticPut()) {
+ continue;
+ }
+ StaticPut staticPut = insn.asStaticPut();
+ if (staticPut.getField().type != enumClass) {
+ continue;
+ }
+ Instruction newInstance = staticPut.inValue().definition;
+ if (newInstance == null || !newInstance.isNewInstance()) {
+ continue;
+ }
+ Instruction ordinal = null;
+ for (Instruction ctorCall : newInstance.outValue().uniqueUsers()) {
+ if (!ctorCall.isInvokeDirect()) {
+ continue;
+ }
+ InvokeDirect invoke = ctorCall.asInvokeDirect();
+ if (!dexItemFactory.isConstructor(invoke.getInvokedMethod())
+ || invoke.arguments().size() < 3) {
+ continue;
+ }
+ ordinal = invoke.arguments().get(2).definition;
+ break;
+ }
+ if (ordinal == null || !ordinal.isConstNumber()) {
+ return null;
+ }
+ if (ordinalsMap.put(staticPut.getField(), ordinal.asConstNumber().getIntValue()) != -1) {
+ return null;
+ }
+ }
+ return ordinalsMap;
+ }
+
+ /**
* Rewrite all branch targets to the destination of trivial goto chains when possible.
* Does not rewrite fallthrough targets as that would require block reordering and the
* transformation only makes sense after SSA destruction where there are no phis.
@@ -920,11 +1172,11 @@
if (current.isInvokeMethod()) {
DexMethod invokedMethod = current.asInvokeMethod().getInvokedMethod();
if (invokedMethod == dexItemFactory.longMethods.compare) {
- List<Value> inValues = current.inValues();
- assert inValues.size() == 2;
- iterator.replaceCurrentInstruction(
- new Cmp(NumericType.LONG, Bias.NONE, current.outValue(), inValues.get(0),
- inValues.get(1)));
+ List<Value> inValues = current.inValues();
+ assert inValues.size() == 2;
+ iterator.replaceCurrentInstruction(
+ new Cmp(NumericType.LONG, Bias.NONE, current.outValue(), inValues.get(0),
+ inValues.get(1)));
} else if (!canUseObjectsNonNull
&& invokedMethod == dexItemFactory.objectsMethods.requireNonNull) {
// Rewrite calls to Objects.requireNonNull(Object) because Javac 9 start to use it for
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 28b68b1..3ccf2b3 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -804,6 +804,9 @@
}
private int getSpillRegister(LiveIntervals intervals) {
+ if (intervals.isArgumentInterval()) {
+ return intervals.getSplitParent().getRegister();
+ }
int registerNumber = nextUnusedRegisterNumber++;
maxRegisterNumber = registerNumber;
if (intervals.getType() == MoveType.WIDE) {
@@ -1331,6 +1334,8 @@
inactive.add(splitOfSplit);
} else if (intervals.getValue().isConstant()) {
splitRangesForSpilledConstant(splitChild, registerNumber);
+ } else if (intervals.isArgumentInterval()) {
+ splitRangesForSpilledArgument(splitChild);
} else {
splitRangesForSpilledInterval(splitChild, registerNumber);
}
@@ -1340,15 +1345,30 @@
active.addAll(newActive);
}
- private void splitRangesForSpilledInterval(LiveIntervals splitChild, int registerNumber) {
+ private void splitRangesForSpilledArgument(LiveIntervals spilled) {
+ assert spilled.isSpilled();
+ assert spilled.isArgumentInterval();
+ // Argument intervals are spilled to the original argument register. We don't know what
+ // that is yet, and therefore we split before the next use to make sure we get a usable
+ // register at the next use.
+ if (!spilled.getUses().isEmpty()) {
+ LiveIntervals split = spilled.splitBefore(spilled.getUses().first().getPosition());
+ unhandled.add(split);
+ }
+ }
+
+ private void splitRangesForSpilledInterval(LiveIntervals spilled, int registerNumber) {
// Spilling a non-pinned, non-rematerializable value. We use the value in the spill
// register for as long as possible to avoid further moves.
- assert splitChild.isSpilled();
- assert !splitChild.getValue().isConstant();
- assert !splitChild.isLinked() || splitChild.isArgumentInterval();
+ assert spilled.isSpilled();
+ assert !spilled.getValue().isConstant();
+ assert !spilled.isLinked() || spilled.isArgumentInterval();
+ if (spilled.isArgumentInterval()) {
+ registerNumber = Constants.U16BIT_MAX;
+ }
LiveIntervalsUse firstUseWithLowerLimit = null;
boolean hasUsesBeforeFirstUseWithLowerLimit = false;
- for (LiveIntervalsUse use : splitChild.getUses()) {
+ for (LiveIntervalsUse use : spilled.getUses()) {
if (registerNumber > use.getLimit()) {
firstUseWithLowerLimit = use;
break;
@@ -1357,10 +1377,10 @@
}
}
if (hasUsesBeforeFirstUseWithLowerLimit) {
- splitChild.setSpilled(false);
+ spilled.setSpilled(false);
}
if (firstUseWithLowerLimit != null) {
- LiveIntervals splitOfSplit = splitChild.splitBefore(firstUseWithLowerLimit.getPosition());
+ LiveIntervals splitOfSplit = spilled.splitBefore(firstUseWithLowerLimit.getPosition());
unhandled.add(splitOfSplit);
}
}
@@ -1722,7 +1742,20 @@
// If the invoke instruction require more than 5 registers we link the inputs because they
// need to be in consecutive registers.
if (invoke.requiredArgumentRegisters() > 5) {
- replaceFullArgumentList(invoke, insertAt);
+ List<Value> arguments = invoke.arguments();
+ Value previous = null;
+ for (int i = 0; i < arguments.size(); i++) {
+ Value argument = arguments.get(i);
+ Value newArgument = createValue(argument.outType(), argument.getDebugInfo());
+ Move move = new Move(newArgument, argument);
+ move.setBlock(invoke.getBlock());
+ replaceArgument(invoke, i, newArgument);
+ insertAt.add(move);
+ if (previous != null) {
+ previous.linkTo(newArgument);
+ }
+ previous = newArgument;
+ }
}
}
@@ -1789,33 +1822,6 @@
}
}
- private void replaceFullArgumentList(Invoke invoke, InstructionListIterator insertAt) {
- List<Value> arguments = invoke.arguments();
- Value previous = null;
- for (int i = 0; i < arguments.size(); i++) {
- generateArgumentMove(invoke, i, insertAt);
- if (previous != null) {
- previous.linkTo(arguments.get(i));
- }
- previous = arguments.get(i);
- }
- }
-
- private Move generateArgumentMove(Invoke invoke, int i) {
- List<Value> arguments = invoke.arguments();
- Value argument = arguments.get(i);
- Value newArgument = createValue(argument.outType(), argument.getDebugInfo());
- Move move = new Move(newArgument, argument);
- move.setBlock(invoke.getBlock());
- replaceArgument(invoke, i, newArgument);
- return move;
- }
-
- private void generateArgumentMove(Invoke invoke, int i, InstructionListIterator insertAt) {
- Move move = generateArgumentMove(invoke, i);
- insertAt.add(move);
- }
-
private void computeNeedsRegister() {
for (BasicBlock block : code.topologicallySortedBlocks()) {
for (Instruction instruction : block.getInstructions()) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index f584e85..7f13315 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -70,6 +70,7 @@
public String packagePrefix = "";
public boolean allowAccessModification = true;
public boolean inlineAccessors = true;
+ public boolean removeSwitchMaps = true;
public final OutlineOptions outline = new OutlineOptions();
public boolean debugKeepRules = false;
public final AttributeRemovalOptions attributeRemoval = new AttributeRemovalOptions();
diff --git a/src/test/debugTestResources/Bridges.java b/src/test/debugTestResources/Bridges.java
new file mode 100644
index 0000000..e1a4380
--- /dev/null
+++ b/src/test/debugTestResources/Bridges.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+public class Bridges {
+
+ interface GenericInterface<T> {
+
+ void get(T t);
+ }
+
+ static class StringImpl implements GenericInterface<String> {
+
+ @Override
+ public void get(String s) {
+ System.out.println(s);
+ }
+ }
+
+ public static void testGenericBridge(GenericInterface<String> obj) {
+ obj.get("Foo");
+ }
+
+ public static void main(String[] args) {
+ testGenericBridge(new StringImpl());
+ }
+}
\ No newline at end of file
diff --git a/src/test/examples/switchmaps/Colors.java b/src/test/examples/switchmaps/Colors.java
new file mode 100644
index 0000000..b2c8dc1
--- /dev/null
+++ b/src/test/examples/switchmaps/Colors.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package switchmaps;
+
+public enum Colors {
+ RED("rar"), BLUE("blew"), GREEN("soylent"), GRAY("fifty");
+
+ private String aField;
+
+ Colors(String string) {
+ aField = string;
+ }
+
+ @Override
+ public String toString() {
+ return aField;
+ }
+}
diff --git a/src/test/examples/switchmaps/Days.java b/src/test/examples/switchmaps/Days.java
new file mode 100644
index 0000000..b484bb5
--- /dev/null
+++ b/src/test/examples/switchmaps/Days.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package switchmaps;
+
+public enum Days {
+ MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
+}
diff --git a/src/test/examples/switchmaps/Switches.java b/src/test/examples/switchmaps/Switches.java
new file mode 100644
index 0000000..bf16856
--- /dev/null
+++ b/src/test/examples/switchmaps/Switches.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package switchmaps;
+
+public class Switches {
+
+ public static void main(String... args) {
+ for (Days value : Days.values()) {
+ switchWithDefault(value);
+ switchFull(value);
+ }
+ for (Colors color : Colors.values()) {
+ switchOnColors(color);
+ }
+ }
+
+ private static void switchOnColors(Colors color) {
+ System.out.println(color.toString());
+ switch (color) {
+ case GRAY:
+ System.out.println("not really");
+ break;
+ case GREEN:
+ System.out.println("sooo green");
+ break;
+ default:
+ System.out.println("colorful");
+ }
+ }
+
+ private static void switchWithDefault(Days day) {
+ switch (day) {
+ case WEDNESDAY:
+ case FRIDAY:
+ System.out.println("3 or 5");
+ break;
+ case SUNDAY:
+ System.out.println("7");
+ break;
+ default:
+ System.out.println("other");
+ }
+ }
+
+ private static void switchFull(Days day) {
+ switch (day) {
+ case MONDAY:
+ case WEDNESDAY:
+ case THURSDAY:
+ System.out.println("1, 3 or 4");
+ case TUESDAY:
+ case FRIDAY:
+ System.out.println("2 or 5");
+ break;
+ case SUNDAY:
+ System.out.println("7");
+ break;
+ case SATURDAY:
+ System.out.println("6");
+ break;
+ default:
+ System.out.println("other");
+ }
+ }
+}
diff --git a/src/test/examplesAndroidO/multidex004/ref-list-1.txt b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
index a29dc78..ac6e888 100644
--- a/src/test/examplesAndroidO/multidex004/ref-list-1.txt
+++ b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
@@ -1,4 +1,4 @@
-Lmultidex004/-$$Lambda$MainActivity$dRBNeLGjbsJicx27QgIJsf0cF7o;
+Lmultidex004/-$$Lambda$MainActivity$g120D_43GXrTOaB3kfYt6wSIJh4;
Lmultidex004/MainActivity;
Lmultidex004/VersionInterface;
Lmultidex004/VersionStatic;
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 7f9ecb0..49c0837 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.TestCondition.D8_COMPILER;
import static com.android.tools.r8.TestCondition.R8_COMPILER;
+import static com.android.tools.r8.TestCondition.R8DEBUG_AFTER_D8_COMPILER;
import static com.android.tools.r8.TestCondition.any;
import static com.android.tools.r8.TestCondition.match;
import static com.android.tools.r8.TestCondition.runtimes;
@@ -4653,6 +4654,14 @@
// 1) t04
// java.lang.AssertionError
+ .put("lang.reflect.Field.getLjava_lang_Object.Field_get_A04", match(R8DEBUG_AFTER_D8_COMPILER))
+ // 1) t02
+ // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
+
+ .put("lang.reflect.Field.getLongLjava_lang_Object.Field_getLong_A04", match(R8DEBUG_AFTER_D8_COMPILER))
+ // 1)
+ // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
+
.build(); // end of failuresToTriage
public static final Multimap<String, TestCondition> flakyWithArt =
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index b026dcd..5c5975c 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.utils.ArtErrorParser;
import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.JarBuilder;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OffOrAuto;
@@ -25,10 +26,10 @@
import com.google.common.collect.Multimap;
import com.google.common.collect.ObjectArrays;
import com.google.common.collect.Sets;
-import com.google.common.io.Files;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -54,7 +55,6 @@
public abstract class R8RunArtTestsTest {
private static final boolean DEX_COMPARE_WITH_DEX_REFERENCE_ON_FAILURE = true;
- private static String[] D8_EXTRA_ARGS = {"--debug"};
private final String name;
private final DexTool toolchain;
@@ -70,7 +70,8 @@
public enum CompilerUnderTest {
D8,
- R8
+ R8,
+ R8DEBUG_AFTER_D8 // refers to the R8/debug step but implies a previous D8 step as well
}
private static final String ART_TESTS_DIR = "tests/art";
@@ -937,13 +938,23 @@
private void executeCompilerUnderTest(
CompilerUnderTest compilerUnderTest, Collection<String> fileNames, String resultPath)
throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
- executeCompilerUnderTest(compilerUnderTest, fileNames, resultPath, null);
+ executeCompilerUnderTest(compilerUnderTest, fileNames, resultPath, null, null);
}
private void executeCompilerUnderTest(
CompilerUnderTest compilerUnderTest,
Collection<String> fileNames,
String resultPath,
+ CompilationMode compilationMode)
+ throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+ executeCompilerUnderTest(compilerUnderTest, fileNames, resultPath, compilationMode, null);
+ }
+
+ private void executeCompilerUnderTest(
+ CompilerUnderTest compilerUnderTest,
+ Collection<String> fileNames,
+ String resultPath,
+ CompilationMode mode,
String keepRulesFile)
throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
switch (compilerUnderTest) {
@@ -951,7 +962,7 @@
assert keepRulesFile == null : "Keep-rules file specified for D8.";
D8Command.Builder builder =
D8Command.builder()
- .setMode(CompilationMode.DEBUG)
+ .setMode(mode == null ? CompilationMode.DEBUG : mode)
.addProgramFiles(ListUtils.map(fileNames, Paths::get));
Integer minSdkVersion = needMinSdkVersion.get(name);
if (minSdkVersion != null) {
@@ -964,7 +975,7 @@
case R8: {
R8Command.Builder builder =
R8Command.builder()
- .setMode(CompilationMode.RELEASE)
+ .setMode(mode == null ? CompilationMode.RELEASE : mode)
.setOutputPath(Paths.get(resultPath))
.addProgramFiles(ListUtils.map(fileNames, Paths::get))
.setIgnoreMissingClasses(true);
@@ -1045,10 +1056,15 @@
DexVm dexVm = ToolHelper.getDexVm();
- File resultDir = temp.getRoot();
+ CompilerUnderTest firstCompilerUnderTest =
+ compilerUnderTest == CompilerUnderTest.R8DEBUG_AFTER_D8
+ ? CompilerUnderTest.D8
+ : compilerUnderTest;
+
+ File resultDir = temp.newFolder(firstCompilerUnderTest.toString().toLowerCase() + "-output");
JctfTestSpecifications.Outcome expectedOutcome = JctfTestSpecifications
- .getExpectedOutcome(name, compilerUnderTest, dexVm);
+ .getExpectedOutcome(name, firstCompilerUnderTest, dexVm);
TestSpecification specification = new TestSpecification(name, DexTool.NONE, resultDir,
expectedOutcome == JctfTestSpecifications.Outcome.TIMEOUTS_WITH_ART
|| expectedOutcome == JctfTestSpecifications.Outcome.FLAKY_WITH_ART,
@@ -1116,7 +1132,53 @@
for (File f : allClassFiles) {
fileNames.add(f.getCanonicalPath());
}
- executeCompilerUnderTest(compilerUnderTest, fileNames, resultDir.getCanonicalPath());
+
+ runJctfTestDoRunOnArt(fileNames,
+ specification,
+ firstCompilerUnderTest,
+ fullClassName,
+ null,
+ dexVm,
+ resultDir);
+
+ // second pass if D8_R8Debug
+ if (compilerUnderTest == CompilerUnderTest.R8DEBUG_AFTER_D8) {
+ List<String> d8OutputFileNames =
+ Files.list(resultDir.toPath())
+ .filter(FileUtils::isDexFile)
+ .map(Path::toString)
+ .collect(Collectors.toList());
+ File r8ResultDir = temp.newFolder("r8-output");
+ expectedOutcome = JctfTestSpecifications
+ .getExpectedOutcome(name, CompilerUnderTest.R8DEBUG_AFTER_D8, dexVm);
+ specification = new TestSpecification(name, DexTool.DX, r8ResultDir,
+ expectedOutcome == JctfTestSpecifications.Outcome.TIMEOUTS_WITH_ART
+ || expectedOutcome == JctfTestSpecifications.Outcome.FLAKY_WITH_ART,
+ expectedOutcome == JctfTestSpecifications.Outcome.FAILS_WITH_ART);
+ if (specification.skipTest) {
+ return;
+ }
+ runJctfTestDoRunOnArt(
+ d8OutputFileNames,
+ specification,
+ CompilerUnderTest.R8,
+ fullClassName,
+ CompilationMode.DEBUG,
+ dexVm,
+ r8ResultDir);
+ }
+ }
+
+ private void runJctfTestDoRunOnArt(
+ Collection<String> fileNames,
+ TestSpecification specification,
+ CompilerUnderTest compilerUnderTest,
+ String fullClassName,
+ CompilationMode mode,
+ DexVm dexVm,
+ File resultDir)
+ throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+ executeCompilerUnderTest(compilerUnderTest, fileNames, resultDir.getAbsolutePath(), mode);
if (!ToolHelper.artSupported()) {
return;
@@ -1196,7 +1258,7 @@
if (toolchain == DexTool.NONE) {
File classes = new File(specification.directory, "classes");
inputFiles =
- Files.fileTreeTraverser().breadthFirstTraversal(classes).filter(
+ com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(classes).filter(
(File f) -> !f.isDirectory()).toArray(File.class);
File smali = new File(specification.directory, "smali");
if (smali.exists()) {
@@ -1207,7 +1269,7 @@
File classes2 = new File(specification.directory, "classes2");
if (classes2.exists()) {
inputFiles = ObjectArrays.concat(inputFiles,
- Files.fileTreeTraverser().breadthFirstTraversal(classes2).filter(
+ com.google.common.io.Files.fileTreeTraverser().breadthFirstTraversal(classes2).filter(
(File f) -> !f.isDirectory()).toArray(File.class), File.class);
}
} else {
@@ -1258,7 +1320,7 @@
}
File expectedFile = specification.resolveFile("expected.txt");
- String expected = Files.toString(expectedFile, Charsets.UTF_8);
+ String expected = com.google.common.io.Files.toString(expectedFile, Charsets.UTF_8);
if (specification.failsWithArt) {
thrown.expect(AssertionError.class);
}
@@ -1281,7 +1343,7 @@
if (checkCommand.exists()) {
// Run the Art test custom check command.
File actualFile = temp.newFile();
- Files.asByteSink(actualFile).write(output.getBytes(Charsets.UTF_8));
+ com.google.common.io.Files.asByteSink(actualFile).write(output.getBytes(Charsets.UTF_8));
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(
specification.resolveFile("check").toString(), expectedFile.toString(),
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 9e7588d..4a06085 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -100,6 +100,7 @@
{"minification.Minification", null},
{"enclosingmethod.Main", null},
{"interfaceinlining.Main", null},
+ {"switchmaps.Switches", null},
};
List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a93fb12..bcb9f7d 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -34,7 +34,7 @@
/**
* Write lines of text to a temporary file.
*/
- protected Path writeTextToTempFile(String... lines) throws IOException{
+ protected Path writeTextToTempFile(String... lines) throws IOException {
Path file = temp.newFile().toPath();
FileUtils.writeTextFile(file, lines);
return file;
@@ -86,7 +86,7 @@
/**
* Compile an application with R8 using the supplied proguard configuration.
*/
- protected AndroidApp compileWithR8(List<Class> classes, Path proguardConfig)
+ protected AndroidApp compileWithR8(List<Class> classes, Path proguardConfig)
throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
return compileWithR8(readClasses(classes), proguardConfig);
}
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index ef2eac3..5ffd779 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -40,6 +40,8 @@
public static final CompilerSet D8_COMPILER = compilers(CompilerUnderTest.D8);
public static final CompilerSet R8_COMPILER = compilers(CompilerUnderTest.R8);
+ public static final CompilerSet R8DEBUG_AFTER_D8_COMPILER =
+ compilers(CompilerUnderTest.R8DEBUG_AFTER_D8);
private static final ToolSet ANY_TOOL = new ToolSet(EnumSet.allOf(DexTool.class));
private static final CompilerSet ANY_COMPILER =
@@ -107,7 +109,13 @@
}
public boolean test(DexTool dexTool, CompilerUnderTest compilerUnderTest, DexVm dexVm) {
- return dexTools.contains(dexTool) && compilers.contains(compilerUnderTest)
+ // R8DEBUG_AFTER_D8 will be set in the R8 phase of the D8-then-R8 tests. So R8DEBUG_AFTER_D8
+ // must match both with plain R8 and itself.
+ boolean compilerMatches = compilers.contains(compilerUnderTest)
+ || (compilerUnderTest == CompilerUnderTest.R8DEBUG_AFTER_D8
+ && compilers.contains(CompilerUnderTest.R8));
+ return dexTools.contains(dexTool)
+ && compilerMatches
&& dexVms.contains(dexVm);
}
}
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 8f2e9ce..fa7443c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -15,6 +15,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -29,12 +30,15 @@
import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ReferenceTypeCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.StackFrameCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.Error;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.EventKind;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.StepDepth;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.StepSize;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.SuspendPolicy;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.TypeTag;
import org.apache.harmony.jpda.tests.framework.jdwp.Location;
import org.apache.harmony.jpda.tests.framework.jdwp.ParsedEvent;
import org.apache.harmony.jpda.tests.framework.jdwp.ParsedEvent.EventThread;
@@ -73,14 +77,15 @@
// Set to true to enable verbose logs
private static final boolean DEBUG_TESTS = false;
- private static final List<DexVm> UNSUPPORTED_ART_VERSIONS = ImmutableList.of(
+ private static final List<DexVm> UNSUPPORTED_ART_VERSIONS = ImmutableList.<DexVm>builder()
// Dalvik does not support command ReferenceType.Methods which is used to set breakpoint.
// TODO(shertz) use command ReferenceType.MethodsWithGeneric instead
- DexVm.ART_4_4_4,
+ .add(DexVm.ART_4_4_4)
// Older runtimes fail on buildbot
// TODO(shertz) re-enable once issue is solved
- DexVm.ART_5_1_1,
- DexVm.ART_6_0_1);
+ .add(DexVm.ART_5_1_1)
+ .add(DexVm.ART_6_0_1)
+ .build();
private static final Path JDWP_JAR = ToolHelper
.getJdwpTestsJarPath(ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()));
@@ -200,7 +205,12 @@
}
protected final JUnit3Wrapper.Command breakpoint(String className, String methodName) {
- return new JUnit3Wrapper.Command.BreakpointCommand(className, methodName);
+ return breakpoint(className, methodName, null);
+ }
+
+ protected final JUnit3Wrapper.Command breakpoint(String className, String methodName,
+ String methodSignature) {
+ return new JUnit3Wrapper.Command.BreakpointCommand(className, methodName, methodSignature);
}
protected final JUnit3Wrapper.Command stepOver() {
@@ -244,12 +254,27 @@
return inspect(t -> Assert.assertTrue(t.getLocalNames().isEmpty()));
}
- protected final JUnit3Wrapper.Command checkLine(int line) {
- return inspect(t -> t.checkLine(line));
+ protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
+ return inspect(t -> {
+ Assert.assertEquals(sourceFile, t.getCurrentSourceFile());
+ Assert.assertEquals(line, t.getCurrentLineNumber());
+ });
}
protected final JUnit3Wrapper.Command checkMethod(String className, String methodName) {
- return inspect(t -> t.checkMethod(className, methodName));
+ return checkMethod(className, methodName, null);
+ }
+
+ protected final JUnit3Wrapper.Command checkMethod(String className, String methodName,
+ String methodSignature) {
+ return inspect(t -> {
+ Assert.assertEquals("Incorrect class name", className, t.getCurrentClassName());
+ Assert.assertEquals("Incorrect method name", methodName, t.getCurrentMethodName());
+ if (methodSignature != null) {
+ Assert.assertEquals("Incorrect method signature", methodSignature,
+ t.getCurrentMethodSignature());
+ }
+ });
}
protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
@@ -477,26 +502,91 @@
Assert.assertEquals(expectedValue, localValue);
}
- public void checkLine(int line) {
- Location location = getLocation();
- int currentLine = getMirror()
- .getLineNumber(location.classID, location.methodID, location.index);
- Assert.assertEquals(line, currentLine);
+ public int getCurrentLineNumber() {
+ ReplyPacket reply = getMirror().getLineTable(location.classID, location.methodID);
+ if (reply.getErrorCode() != 0) {
+ return -1;
+ }
+
+ long startCodeIndex = reply.getNextValueAsLong();
+ long endCodeIndex = reply.getNextValueAsLong();
+ int lines = reply.getNextValueAsInt();
+ int line = -1;
+ long previousLineCodeIndex = -1;
+ for (int i = 0; i < lines; ++i) {
+ long currentLineCodeIndex = reply.getNextValueAsLong();
+ int currentLineNumber = reply.getNextValueAsInt();
+
+ // Code indices are in ascending order.
+ assert currentLineCodeIndex >= startCodeIndex;
+ assert currentLineCodeIndex <= endCodeIndex;
+ assert currentLineCodeIndex > previousLineCodeIndex;
+ previousLineCodeIndex = currentLineCodeIndex;
+
+ if (location.index >= currentLineCodeIndex) {
+ line = currentLineNumber;
+ } else {
+ break;
+ }
+ }
+
+ return line;
+ }
+
+ public String getCurrentSourceFile() {
+ CommandPacket sourceFileCommand = new CommandPacket(
+ JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
+ JDWPCommands.ReferenceTypeCommandSet.SourceFileCommand);
+ sourceFileCommand.setNextValueAsReferenceTypeID(location.classID);
+ ReplyPacket replyPacket = getMirror().performCommand(sourceFileCommand);
+ if (replyPacket.getErrorCode() != 0) {
+ return null;
+ } else {
+ return replyPacket.getNextValueAsString();
+ }
}
public List<String> getLocalNames() {
return getVariablesAt(location).stream().map(v -> v.getName()).collect(Collectors.toList());
}
- public void checkMethod(String className, String methodName) {
- String currentClassSig = getMirror().getClassSignature(location.classID);
- assert currentClassSig.charAt(0) == 'L';
- String currentClassName = currentClassSig.substring(1, currentClassSig.length() - 1)
- .replace('/', '.');
- Assert.assertEquals("Incorrect class name", className, currentClassName);
+ public String getCurrentClassName() {
+ String classSignature = getCurrentClassSignature();
+ assert classSignature.charAt(0) == 'L';
+ // Remove leading 'L' and trailing ';'
+ classSignature = classSignature.substring(1, classSignature.length() - 1);
+ // Return fully qualified name
+ return classSignature.replace('/', '.');
+ }
- String currentMethodName = getMirror().getMethodName(location.classID, location.methodID);
- Assert.assertEquals("Incorrect method name", methodName, currentMethodName);
+ public String getCurrentClassSignature() {
+ return getMirror().getClassSignature(location.classID);
+ }
+
+ public String getCurrentMethodName() {
+ return getMirror().getMethodName(location.classID, location.methodID);
+ }
+
+ public String getCurrentMethodSignature() {
+ CommandPacket command = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+ ReferenceTypeCommandSet.MethodsWithGenericCommand);
+ command.setNextValueAsReferenceTypeID(location.classID);
+
+ ReplyPacket reply = getMirror().performCommand(command);
+ assert reply.getErrorCode() == Error.NONE;
+ int methods = reply.getNextValueAsInt();
+
+ for (int i = 0; i < methods; ++i) {
+ long methodId = reply.getNextValueAsMethodID();
+ reply.getNextValueAsString(); // skip name
+ String methodSignature = reply.getNextValueAsString();
+ reply.getNextValueAsString(); // skip generic signature
+ reply.getNextValueAsInt(); // skip modifiers
+ if (methodId == location.methodID) {
+ return methodSignature;
+ }
+ }
+ throw new AssertionError("No method info for the current location");
}
}
@@ -564,7 +654,14 @@
}
private boolean installBreakpoint(BreakpointInfo breakpointInfo) {
- final long classId = getMirror().getClassID(getClassSignature(breakpointInfo.className));
+ String classSignature = getClassSignature(breakpointInfo.className);
+ byte typeTag = TypeTag.CLASS;
+ long classId = getMirror().getClassID(classSignature);
+ if (classId == -1) {
+ // Is it an interface ?
+ classId = getMirror().getInterfaceID(classSignature);
+ typeTag = TypeTag.INTERFACE;
+ }
if (classId == -1) {
// The class is not ready yet. Request a CLASS_PREPARE to delay the installation of the
// breakpoint.
@@ -575,14 +672,95 @@
new ClassPrepareHandler(breakpointInfo, classPrepareRequestId));
return false;
} else {
- int breakpointId = getMirror()
- .setBreakpointAtMethodBegin(classId, breakpointInfo.methodName);
+ // Find the method.
+ long breakpointMethodId = findMethod(classId, breakpointInfo.methodName,
+ breakpointInfo.methodSignature);
+ long index = getMethodFirstCodeIndex(classId, breakpointMethodId);
+ Assert.assertTrue("No code in method", index >= 0);
+ // Install the breakpoint.
+ ReplyPacket replyPacket = getMirror()
+ .setBreakpoint(new Location(typeTag, classId, breakpointMethodId, index),
+ SuspendPolicy.ALL);
+ checkReplyPacket(replyPacket, "Breakpoint");
+ int breakpointId = replyPacket.getNextValueAsInt();
// Nothing to do on breakpoint
events.put(Integer.valueOf(breakpointId), new DefaultEventHandler());
return true;
}
}
+ private long findMethod(long classId, String methodName, String methodSignature) {
+ class MethodInfo {
+
+ final long methodId;
+ final String methodName;
+ final String methodSignature;
+
+ MethodInfo(long methodId, String methodName, String methodSignature) {
+ this.methodId = methodId;
+ this.methodName = methodName;
+ this.methodSignature = methodSignature;
+ }
+ }
+
+ boolean withGenericSignature = true;
+ CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
+ ReferenceTypeCommandSet.MethodsWithGenericCommand);
+ commandPacket.setNextValueAsReferenceTypeID(classId);
+ ReplyPacket replyPacket = getMirror().performCommand(commandPacket);
+ if (replyPacket.getErrorCode() != Error.NONE) {
+ // Retry with older command ReferenceType.Methods
+ withGenericSignature = false;
+ commandPacket.setCommand(ReferenceTypeCommandSet.MethodsCommand);
+ replyPacket = getMirror().performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE;
+ }
+
+ int methodsCount = replyPacket.getNextValueAsInt();
+ List<MethodInfo> methodInfos = new ArrayList<>(methodsCount);
+ for (int i = 0; i < methodsCount; ++i) {
+ long currentMethodId = replyPacket.getNextValueAsMethodID();
+ String currentMethodName = replyPacket.getNextValueAsString();
+ String currentMethodSignature = replyPacket.getNextValueAsString();
+ if (withGenericSignature) {
+ replyPacket.getNextValueAsString(); // skip generic signature
+ }
+ replyPacket.getNextValueAsInt(); // skip modifiers
+ methodInfos
+ .add(new MethodInfo(currentMethodId, currentMethodName, currentMethodSignature));
+ }
+ Assert.assertTrue(replyPacket.isAllDataRead());
+
+ // Only keep methods with the expected name.
+ methodInfos = methodInfos.stream()
+ .filter(m -> m.methodName.equals(methodName)).collect(
+ Collectors.toList());
+ if (methodSignature != null) {
+ methodInfos = methodInfos.stream()
+ .filter(m -> methodSignature.equals(m.methodSignature)).collect(
+ Collectors.toList());
+ }
+ Assert.assertFalse("No method found", methodInfos.isEmpty());
+ // There must be only one matching method
+ Assert.assertEquals("More than 1 method found: please specify a signature", 1,
+ methodInfos.size());
+ return methodInfos.get(0).methodId;
+ }
+
+ private long getMethodFirstCodeIndex(long classId, long breakpointMethodId) {
+ ReplyPacket replyPacket = getMirror().getLineTable(classId, breakpointMethodId);
+ checkReplyPacket(replyPacket, "Failed to get method line table");
+ replyPacket.getNextValueAsLong(); // start
+ replyPacket.getNextValueAsLong(); // end
+ int linesCount = replyPacket.getNextValueAsInt();
+ if (linesCount == 0) {
+ return -1;
+ } else {
+ // Read only the 1st line because code indices are in ascending order
+ return replyPacket.getNextValueAsLong();
+ }
+ }
+
//
// Command processing
//
@@ -603,22 +781,24 @@
}
}
- // TODO(shertz) add method signature support (when multiple methods have the same name)
class BreakpointCommand implements Command {
private final String className;
private final String methodName;
+ private final String methodSignature;
- public BreakpointCommand(String className, String methodName) {
+ public BreakpointCommand(String className, String methodName,
+ String methodSignature) {
assert className != null;
assert methodName != null;
this.className = className;
this.methodName = methodName;
+ this.methodSignature = methodSignature;
}
@Override
public void perform(JUnit3Wrapper testBase) {
- testBase.installBreakpoint(new BreakpointInfo(className, methodName));
+ testBase.installBreakpoint(new BreakpointInfo(className, methodName, methodSignature));
}
@Override
@@ -740,10 +920,12 @@
private final String className;
private final String methodName;
+ private final String methodSignature;
- private BreakpointInfo(String className, String methodName) {
+ private BreakpointInfo(String className, String methodName, String methodSignature) {
this.className = className;
this.methodName = methodName;
+ this.methodSignature = methodSignature;
}
}
diff --git a/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
index ea116a7..e0eb5ea 100644
--- a/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
@@ -11,6 +11,8 @@
public class DefaultMethodTest extends DebugTestBase {
+ private static final String SOURCE_FILE = "DebugDefaultMethod.java";
+
@Test
public void testDefaultMethod() throws Throwable {
String debuggeeClass = "DebugDefaultMethod";
@@ -21,7 +23,7 @@
commands.add(breakpoint(debuggeeClass, "testDefaultMethod"));
commands.add(run());
commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
- commands.add(checkLine(27));
+ commands.add(checkLine(SOURCE_FILE, 27));
if (!supportsDefaultMethod()) {
// We desugared default method. This means we're going to step through an extra (forward)
// method first.
@@ -50,7 +52,7 @@
commands.add(run());
commands.add(run() /* resume after 1st breakpoint */);
commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
- commands.add(checkLine(27));
+ commands.add(checkLine(SOURCE_FILE, 27));
commands.add(stepInto());
commands.add(checkMethod("DebugDefaultMethod$OverrideImpl", "doSomething"));
commands.add(checkLocal(parameterName));
@@ -61,5 +63,4 @@
runDebugTestJava8(debuggeeClass, commands);
}
-
}
diff --git a/src/test/java/com/android/tools/r8/debug/ExceptionTest.java b/src/test/java/com/android/tools/r8/debug/ExceptionTest.java
index 5f15f8f..3d4202b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExceptionTest.java
@@ -10,6 +10,8 @@
*/
public class ExceptionTest extends DebugTestBase {
+ public static final String SOURCE_FILE = "Exceptions.java";
+
@Test
public void testStepOnCatch() throws Throwable {
int catchLine;
@@ -24,9 +26,9 @@
runDebugTest("Exceptions",
breakpoint("Exceptions", "catchException"),
run(),
- checkLine(9), // line of the method call throwing the exception
+ checkLine(SOURCE_FILE, 9), // line of the method call throwing the exception
stepOver(),
- checkLine(catchLine), // line of the catch declaration
+ checkLine(SOURCE_FILE, catchLine), // line of the catch declaration
run());
}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index 467ae09..52d9c70 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -8,6 +8,8 @@
public class LambdaTest extends DebugTestBase {
+ public static final String SOURCE_FILE = "DebugLambda.java";
+
@Test
public void testLambdaDebugging() throws Throwable {
String debuggeeClass = "DebugLambda";
@@ -17,9 +19,9 @@
breakpoint(debuggeeClass, initialMethodName),
run(),
checkMethod(debuggeeClass, initialMethodName),
- checkLine(12),
+ checkLine(SOURCE_FILE, 12),
stepInto(INTELLIJ_FILTER),
- checkLine(16),
+ checkLine(SOURCE_FILE, 16),
run());
}
}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 0d72ab9..6132ace 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debug;
-import com.android.tools.r8.debug.DebugTestBase;
import org.apache.harmony.jpda.tests.framework.jdwp.Value;
import org.junit.Test;
@@ -12,6 +11,8 @@
*/
public class LocalsTest extends DebugTestBase {
+ public static final String SOURCE_FILE = "Locals.java";
+
@Test
public void testNoLocal() throws Throwable {
final String className = "Locals";
@@ -20,11 +21,11 @@
breakpoint(className, methodName),
run(),
checkMethod(className, methodName),
- checkLine(8),
+ checkLine(SOURCE_FILE, 8),
checkNoLocal(),
stepOver(),
checkMethod(className, methodName),
- checkLine(9),
+ checkLine(SOURCE_FILE, 9),
checkNoLocal(),
run());
}
@@ -37,10 +38,10 @@
breakpoint(className, methodName),
run(),
checkMethod(className, methodName),
- checkLine(12),
+ checkLine(SOURCE_FILE, 12),
checkNoLocal(),
stepOver(),
- checkLine(13),
+ checkLine(SOURCE_FILE, 13),
checkLocal("i", Value.createInt(Integer.MAX_VALUE)),
run());
}
@@ -57,14 +58,14 @@
breakpoint(className, methodName),
run(),
checkMethod(className, methodName),
- checkLine(17),
+ checkLine(SOURCE_FILE, 17),
checkLocal("p", pValue),
stepOver(),
- checkLine(18),
+ checkLine(SOURCE_FILE, 18),
checkLocal("p", pValue),
checkLocal("c", cValue),
stepOver(),
- checkLine(19),
+ checkLine(SOURCE_FILE, 19),
checkLocal("p", pValue),
checkLocal("c", cValue),
checkLocal("v", vValue),
@@ -84,16 +85,16 @@
breakpoint(className, methodName),
run(),
checkMethod(className, methodName),
- checkLine(17),
+ checkLine(SOURCE_FILE, 17),
checkLocal("p", pValue),
stepOver(),
- checkLine(18),
+ checkLine(SOURCE_FILE, 18),
checkLocal("p", pValue),
checkLocal("c", cValue),
setLocal("c", newValue),
checkLocal("c", newValue), // we should see the updated value
stepOver(),
- checkLine(19),
+ checkLine(SOURCE_FILE, 19),
checkLocal("p", pValue),
checkLocal("c", newValue),
checkLocal("v", vValue),
@@ -109,15 +110,15 @@
breakpoint(className, methodName),
run(),
checkMethod(className, methodName),
- checkLine(23),
+ checkLine(SOURCE_FILE, 23),
checkNoLocal(),
stepOver(),
checkMethod(className, methodName),
- checkLine(24),
+ checkLine(SOURCE_FILE, 24),
checkLocal("i", Value.createInt(0)),
setLocal("i", newValueForI),
stepOver(),
- checkLine(25),
+ checkLine(SOURCE_FILE, 25),
checkLocal("i", newValueForI),
checkLocal("f", Value.createFloat(0)),
run());
@@ -133,15 +134,15 @@
breakpoint(className, methodName),
run(),
checkMethod(className, methodName),
- checkLine(29),
+ checkLine(SOURCE_FILE, 29),
checkNoLocal(),
stepOver(),
checkMethod(className, methodName),
- checkLine(30),
+ checkLine(SOURCE_FILE, 30),
checkLocal("i", oldValueForI),
setLocal("i", newValueForI),
stepOver(),
- checkLine(33),
+ checkLine(SOURCE_FILE, 33),
checkLocal("i", newValueForI),
run());
}
diff --git a/src/test/java/com/android/tools/r8/debug/MultipleReturnsTest.java b/src/test/java/com/android/tools/r8/debug/MultipleReturnsTest.java
index 0b7973e..1d708cf 100644
--- a/src/test/java/com/android/tools/r8/debug/MultipleReturnsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MultipleReturnsTest.java
@@ -10,16 +10,18 @@
*/
public class MultipleReturnsTest extends DebugTestBase {
+ public static final String SOURCE_FILE = "MultipleReturns.java";
+
@Test
public void testMultipleReturns() throws Throwable {
runDebugTest("MultipleReturns",
breakpoint("MultipleReturns", "multipleReturns"),
run(),
stepOver(),
- checkLine(16), // this should be the 1st return statement
+ checkLine(SOURCE_FILE, 16), // this should be the 1st return statement
run(),
stepOver(),
- checkLine(18), // this should be the 2nd return statement
+ checkLine(SOURCE_FILE, 18), // this should be the 2nd return statement
run());
}
}
diff --git a/src/test/java/com/android/tools/r8/debug/SyntheticMethodTest.java b/src/test/java/com/android/tools/r8/debug/SyntheticMethodTest.java
index a5b7d4e..fa5917e 100644
--- a/src/test/java/com/android/tools/r8/debug/SyntheticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SyntheticMethodTest.java
@@ -11,22 +11,6 @@
public class SyntheticMethodTest extends DebugTestBase {
- private void debugInnerAccessors(StepFilter stepFilter) throws Throwable {
- String debuggeeClass = "InnerAccessors";
- List<Command> commands = new ArrayList<>();
- commands.add(breakpoint("InnerAccessors$Inner", "callPrivateMethodInOuterClass"));
- commands.add(run());
- commands.add(checkLine(13));
- commands.add(stepInto(stepFilter)); // skip synthetic accessor
- if (stepFilter == NO_FILTER) {
- commands.add(stepInto(stepFilter));
- }
- commands.add(checkMethod(debuggeeClass, "privateMethod"));
- commands.add(checkLine(8));
- commands.add(run());
- runDebugTest(debuggeeClass, commands);
- }
-
@Test
public void testInnerAccessors_NoFilter() throws Throwable {
debugInnerAccessors(NO_FILTER);
@@ -37,4 +21,51 @@
debugInnerAccessors(INTELLIJ_FILTER);
}
+ @Test
+ public void testGenericBridges_NoFilter() throws Throwable {
+ debugGenericBridges(NO_FILTER);
+ }
+
+ @Test
+ public void testGenericBridges_IntelliJ() throws Throwable {
+ debugGenericBridges(INTELLIJ_FILTER);
+ }
+
+ private void debugInnerAccessors(StepFilter stepFilter) throws Throwable {
+ final String sourceFile = "InnerAccessors.java";
+ String debuggeeClass = "InnerAccessors";
+ List<Command> commands = new ArrayList<>();
+ commands.add(breakpoint("InnerAccessors$Inner", "callPrivateMethodInOuterClass"));
+ commands.add(run());
+ commands.add(checkLine(sourceFile, 13));
+ commands.add(stepInto(stepFilter)); // skip synthetic accessor
+ if (stepFilter == NO_FILTER) {
+ commands.add(stepInto(stepFilter));
+ }
+ commands.add(checkMethod(debuggeeClass, "privateMethod"));
+ commands.add(checkLine(sourceFile, 8));
+ commands.add(run());
+ runDebugTest(debuggeeClass, commands);
+ }
+
+ private void debugGenericBridges(StepFilter stepFilter) throws Throwable {
+ final String sourceFile = "Bridges.java";
+ String debuggeeClass = "Bridges";
+ List<Command> commands = new ArrayList<>();
+ commands.add(breakpoint(debuggeeClass, "testGenericBridge"));
+ commands.add(run());
+ commands.add(checkLine(sourceFile, 21));
+ commands.add(stepInto(stepFilter)); // skip synthetic accessor
+ String implementationClassName = "Bridges$StringImpl";
+ String methodName = "get";
+ if (stepFilter == NO_FILTER) {
+ commands.add(checkMethod(implementationClassName, methodName, "(Ljava/lang/Object;)V"));
+ commands.add(stepInto(stepFilter));
+ }
+ commands.add(checkMethod(implementationClassName, methodName, "(Ljava/lang/String;)V"));
+ commands.add(checkLine(sourceFile, 16));
+ commands.add(run());
+ runDebugTest(debuggeeClass, commands);
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index cab3291..ae0f650 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -4,41 +4,27 @@
package com.android.tools.r8.maindexlist;
-import static com.android.tools.r8.ToolHelper.EXAMPLES_DIR;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationResult;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.R8Command.Builder;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OffOrAuto;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
public class MainDexTracingTest {
@@ -155,11 +141,21 @@
.map(dexType -> dexType.descriptor.toString())
.collect(Collectors.toList());
Collections.sort(resultMainDexList);
- StringBuilder resultString = new StringBuilder();
- resultMainDexList.forEach(descriptor -> resultString.append(descriptor).append('\n'));
- String refList = new String(Files.readAllBytes(
- expectedMainDexList), StandardCharsets.UTF_8);
- Assert.assertEquals(refList, resultString.toString());
+ String[] refList = new String(Files.readAllBytes(
+ expectedMainDexList), StandardCharsets.UTF_8).split("\n");
+ for (int i = 0; i < refList.length; i++) {
+ String reference = refList[i];
+ String computed = resultMainDexList.get(i);
+ if (reference.contains("-$$Lambda$")) {
+ // For lambda classes we check that there is a lambda class for the right containing
+ // class. However, we do not check the hash for the generated lambda class. The hash
+ // changes for different compiler versions because different compiler versions generate
+ // different lambda implementation method names.
+ reference = reference.substring(0, reference.lastIndexOf('$'));
+ computed = computed.substring(0, computed.lastIndexOf('$'));
+ }
+ Assert.assertEquals(reference, computed);
+ }
} catch (ExecutionException e) {
throw e.getCause();
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
new file mode 100644
index 0000000..4e8881a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.switchmaps;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class RewriteSwitchMapsTest extends TestBase {
+
+ private static final String JAR_FILE = "switchmaps.jar";
+ private static final String SWITCHMAP_CLASS_NAME = "switchmaps.Switches$1";
+ private static final String PG_CONFIG =
+ "-keep class switchmaps.Switches { public static void main(...); } " +
+ "-dontobfuscate";
+
+ @Test
+ public void checkSwitchMapsRemoved()
+ throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+ AndroidApp.Builder builder = AndroidApp.builder();
+ builder.addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()));
+ builder.addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR).resolve(JAR_FILE));
+ AndroidApp result = compileWithR8(builder.build(), writeTextToTempFile(PG_CONFIG));
+ DexInspector inspector = new DexInspector(result);
+ Assert.assertFalse(inspector.clazz(SWITCHMAP_CLASS_NAME).isPresent());
+ }
+}
diff --git a/tools/create_jctf_tests.py b/tools/create_jctf_tests.py
index 0f1ca98..52907fc 100755
--- a/tools/create_jctf_tests.py
+++ b/tools/create_jctf_tests.py
@@ -71,7 +71,8 @@
raise IOError("Can't find package statement in java file: " + filepath)
-def generate_test(class_name, compiler_under_test, relative_package):
+def generate_test(class_name, compiler_under_test, compiler_under_test_enum,
+ relative_package):
filename = join(DESTINATION_DIR, compiler_under_test,
relative_package.replace('.', '/'), class_name + '.java')
utils.makedirs_if_needed(dirname(filename))
@@ -83,7 +84,7 @@
relativePackage = relative_package,
name = full_class_name,
testClassName = class_name,
- compilerUnderTestEnum = compiler_under_test.upper(),
+ compilerUnderTestEnum = compiler_under_test_enum,
classFile = full_class_name.replace('.', '/') + '.class',
nameWithoutPackagePrefix = '{}.{}'.format(relative_package, class_name))
@@ -120,8 +121,8 @@
assert idx >= 0
relative_package = package[idx + len(dot_java_dot):]
- for d in ['r8', 'd8']:
- generate_test(class_name, d, relative_package)
+ generate_test(class_name, 'd8', 'R8DEBUG_AFTER_D8', relative_package)
+ generate_test(class_name, 'r8', 'R8', relative_package)
if __name__ == '__main__':