Merge "Extend tests for b/116092333"
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index 1e16b73..5651dde 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -27,11 +27,11 @@
}
public CompatProguardCommandBuilder(
- boolean forceProguardCompatibility, boolean enableVerticalClassMerging) {
+ boolean forceProguardCompatibility, boolean disableVerticalClassMerging) {
if (forceProguardCompatibility) {
internalForceProguardCompatibility();
}
- setEnableVerticalClassMerging(enableVerticalClassMerging);
+ setDisableVerticalClassMerging(disableVerticalClassMerging);
setIgnoreDexInArchive(true);
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 4c5e0b9..964f699 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -252,8 +252,7 @@
internal.enableInlining = false;
assert internal.enableClassInlining;
internal.enableClassInlining = false;
- // TODO(christofferqa): Remove negation when enabling vertical class merging by default.
- assert !internal.enableVerticalClassMerging;
+ assert internal.enableVerticalClassMerging;
internal.enableVerticalClassMerging = false;
assert internal.enableClassStaticizer;
internal.enableClassStaticizer = false;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 0970a0c..dc94041 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -81,7 +81,7 @@
private final List<ProguardConfigurationSource> proguardConfigs = new ArrayList<>();
private boolean disableTreeShaking = false;
private boolean disableMinification = false;
- private boolean enableVerticalClassMerging = false;
+ private boolean disableVerticalClassMerging = false;
private boolean forceProguardCompatibility = false;
private StringConsumer proguardMapConsumer = null;
@@ -116,8 +116,8 @@
this.forceProguardCompatibility = true;
}
- void setEnableVerticalClassMerging(boolean enableVerticalClassMerging) {
- this.enableVerticalClassMerging = enableVerticalClassMerging;
+ void setDisableVerticalClassMerging(boolean disableVerticalClassMerging) {
+ this.disableVerticalClassMerging = disableVerticalClassMerging;
}
@Override
@@ -443,7 +443,7 @@
desugaring,
configuration.isShrinking(),
configuration.isObfuscating(),
- enableVerticalClassMerging,
+ disableVerticalClassMerging,
forceProguardCompatibility,
proguardMapConsumer,
proguardCompatibilityRulesOutput,
@@ -509,7 +509,7 @@
private final ProguardConfiguration proguardConfiguration;
private final boolean enableTreeShaking;
private final boolean enableMinification;
- private final boolean enableVerticalClassMerging;
+ private final boolean disableVerticalClassMerging;
private final boolean forceProguardCompatibility;
private final StringConsumer proguardMapConsumer;
private final Path proguardCompatibilityRulesOutput;
@@ -573,7 +573,7 @@
boolean enableDesugaring,
boolean enableTreeShaking,
boolean enableMinification,
- boolean enableVerticalClassMerging,
+ boolean disableVerticalClassMerging,
boolean forceProguardCompatibility,
StringConsumer proguardMapConsumer,
Path proguardCompatibilityRulesOutput,
@@ -587,7 +587,7 @@
this.proguardConfiguration = proguardConfiguration;
this.enableTreeShaking = enableTreeShaking;
this.enableMinification = enableMinification;
- this.enableVerticalClassMerging = enableVerticalClassMerging;
+ this.disableVerticalClassMerging = disableVerticalClassMerging;
this.forceProguardCompatibility = forceProguardCompatibility;
this.proguardMapConsumer = proguardMapConsumer;
this.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
@@ -600,7 +600,7 @@
proguardConfiguration = null;
enableTreeShaking = false;
enableMinification = false;
- enableVerticalClassMerging = false;
+ disableVerticalClassMerging = false;
forceProguardCompatibility = false;
proguardMapConsumer = null;
proguardCompatibilityRulesOutput = null;
@@ -650,6 +650,7 @@
? LineNumberOptimization.ON
: LineNumberOptimization.OFF;
+ assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
if (internal.debug) {
// TODO(zerny): Should we support inlining in debug mode? b/62937285
internal.enableInlining = false;
@@ -713,8 +714,9 @@
// EXPERIMENTAL flags.
assert !internal.forceProguardCompatibility;
internal.forceProguardCompatibility = forceProguardCompatibility;
- assert !internal.enableVerticalClassMerging;
- internal.enableVerticalClassMerging = enableVerticalClassMerging;
+ if (disableVerticalClassMerging) {
+ internal.enableVerticalClassMerging = false;
+ }
internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index ec15120..5416350 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -40,8 +40,8 @@
public final List<String> proguardConfig;
public boolean printHelpAndExit;
- // Flags to enable experimental features.
- public boolean enableVerticalClassMerging;
+ // Flags to disable experimental features.
+ public boolean disableVerticalClassMerging;
CompatProguardOptions(
List<String> proguardConfig,
@@ -52,7 +52,7 @@
boolean includeDataResources,
String mainDexList,
boolean printHelpAndExit,
- boolean verticalClassMerging) {
+ boolean disableVerticalClassMerging) {
this.output = output;
this.minApi = minApi;
this.forceProguardCompatibility = forceProguardCompatibility;
@@ -61,7 +61,7 @@
this.mainDexList = mainDexList;
this.proguardConfig = proguardConfig;
this.printHelpAndExit = printHelpAndExit;
- this.enableVerticalClassMerging = verticalClassMerging;
+ this.disableVerticalClassMerging = disableVerticalClassMerging;
}
public static CompatProguardOptions parse(String[] args) {
@@ -72,8 +72,8 @@
boolean multiDex = false;
String mainDexList = null;
boolean printHelpAndExit = false;
- // Flags to enable experimental features.
- boolean verticalClassMerging = false;
+ // Flags to disable experimental features.
+ boolean disableVerticalClassMerging = false;
// These flags are currently ignored.
boolean minimalMainDex = false;
boolean coreLibrary = false;
@@ -101,8 +101,8 @@
mainDexList = args[++i];
} else if (arg.startsWith("--main-dex-list=")) {
mainDexList = arg.substring("--main-dex-list=".length());
- } else if (arg.equals("--vertical-class-merging")) {
- verticalClassMerging = true;
+ } else if (arg.equals("--no-vertical-class-merging")) {
+ disableVerticalClassMerging = true;
} else if (arg.equals("--minimal-main-dex")) {
minimalMainDex = true;
} else if (arg.equals("--core-library")) {
@@ -138,7 +138,7 @@
includeDataResources,
mainDexList,
printHelpAndExit,
- verticalClassMerging);
+ disableVerticalClassMerging);
}
public static void print() {
@@ -177,7 +177,7 @@
}
CompatProguardCommandBuilder builder =
new CompatProguardCommandBuilder(
- options.forceProguardCompatibility, options.enableVerticalClassMerging);
+ options.forceProguardCompatibility, options.disableVerticalClassMerging);
builder
.setOutput(Paths.get(options.output), OutputMode.DexIndexed, options.includeDataResources)
.addProguardConfiguration(options.proguardConfig, CommandLineOrigin.INSTANCE)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
index 2bfe88d..2eb732d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.NumericType;
/**
* A {@link TypeLatticeElement} that abstracts primitive types.
@@ -57,6 +58,24 @@
}
}
+ public static PrimitiveTypeLatticeElement fromNumericType(NumericType numericType) {
+ switch(numericType) {
+ case BYTE:
+ case CHAR:
+ case SHORT:
+ case INT:
+ return IntTypeLatticeElement.getInstance();
+ case FLOAT:
+ return FloatTypeLatticeElement.getInstance();
+ case LONG:
+ return LongTypeLatticeElement.getInstance();
+ case DOUBLE:
+ return DoubleTypeLatticeElement.getInstance();
+ default:
+ throw new Unreachable("Invalid numeric type '" + numericType + "'");
+ }
+ }
+
public static TypeLatticeElement join(
PrimitiveTypeLatticeElement t1, PrimitiveTypeLatticeElement t2) {
if (t1 == t2) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
index f1c0049..d676216 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
@@ -20,6 +20,9 @@
import com.android.tools.r8.code.LongToFloat;
import com.android.tools.r8.code.LongToInt;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -146,6 +149,11 @@
}
@Override
+ public TypeLatticeElement evaluate(AppInfo appInfo) {
+ return PrimitiveTypeLatticeElement.fromNumericType(to);
+ }
+
+ @Override
public void buildCf(CfBuilder builder) {
builder.add(new CfNumberConversion(from, to));
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 382dd64..6a302c2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.IROrdering;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.ThrowingBiConsumer;
@@ -39,7 +40,6 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -117,7 +117,7 @@
if (isSelfRecursive()) {
builder.append(", recursive");
}
- builder.append(", invoke count " + invokeCount);
+ builder.append(", invoke count ").append(invokeCount);
builder.append(").\n");
if (callees.size() > 0) {
builder.append("Callees:\n");
@@ -140,7 +140,7 @@
}
private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>();
- private final Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> shuffle;
+ private final IROrdering shuffle;
private final Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
private final Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
@@ -220,7 +220,7 @@
*
* <p>
*/
- private Set<DexEncodedMethod> extractLeaves() {
+ private Collection<DexEncodedMethod> extractLeaves() {
if (isEmpty()) {
return Collections.emptySet();
}
@@ -230,8 +230,10 @@
leaf.callers.forEach(caller -> caller.callees.remove(leaf));
nodes.remove(leaf.method);
});
- return shuffle.apply(leaves.stream().map(leaf -> leaf.method)
- .collect(Collectors.toCollection(LinkedHashSet::new)));
+ Set<DexEncodedMethod> methods =
+ leaves.stream().map(x -> x.method).collect(Collectors.toCollection(LinkedHashSet::new));
+ // TODO(b/116282409): Resolve why shuffling makes art.none.r8.Art800_smaliTest flaky.
+ return methods;
}
public static class CycleEliminator {
@@ -457,7 +459,7 @@
ExecutorService executorService)
throws ExecutionException {
while (!isEmpty()) {
- Set<DexEncodedMethod> methods = extractLeaves();
+ Collection<DexEncodedMethod> methods = extractLeaves();
assert methods.size() > 0;
List<Future<?>> futures = new ArrayList<>();
for (DexEncodedMethod method : methods) {
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 406412b..f4a1e4e 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
@@ -777,7 +777,9 @@
inliner.performInlining(
method, code, isProcessedConcurrently, callSiteInformation);
}
- stringOptimizer.computeConstStringLength(code, appInfo.dexItemFactory);
+ if (!options.debug) {
+ stringOptimizer.computeConstStringLength(code, appInfo.dexItemFactory);
+ }
if (devirtualizer != null) {
devirtualizer.devirtualizeInvokeInterface(code, method.method.getHolder());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index e356bb1..0abbfde 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -51,15 +51,14 @@
List<Value> ins = invoke.arguments();
assert ins.size() == 1;
Value in = ins.get(0);
- if (in.definition == null || !in.definition.isConstString()) {
+ if (in.definition == null
+ || !in.definition.isConstString()
+ || !in.definition.outValue().isConstant()) {
continue;
}
ConstString constString = in.definition.asConstString();
int length = constString.getValue().toString().length();
ConstNumber constNumber = code.createIntConstant(length);
- if (invoke.outValue().hasLocalInfo()) {
- constNumber.outValue().setLocalInfo(invoke.outValue().getLocalInfo());
- }
it.replaceCurrentInstruction(constNumber);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 4c64cf6..5128b96 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -18,14 +18,14 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
public final class IdentifierNameStringUtils {
@@ -295,6 +295,66 @@
return itemBasedString;
}
+ // Perform a conservative evaluation of the constant content of an array from its construction
+ // until its use at a given instruction.
+ private static ConstInstruction[] evaluateConstArrayContentFromConstructionToUse(
+ NewArrayEmpty newArray, int size, Instruction user) {
+ ConstInstruction[] values = new ConstInstruction[size];
+ int remaining = size;
+ Set<Instruction> users = newArray.outValue().uniqueUsers();
+ // Follow the path from the array construction to the requested use collecting the constants
+ // put into the array. Conservatively bail out if the content of the array cannot be statically
+ // computed.
+ BasicBlock block = newArray.getBlock();
+ InstructionListIterator iterator = block.listIterator();
+ iterator.nextUntil(i -> i == newArray);
+ do {
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ // Ignore instructions which do not use the array.
+ if (!users.contains(instruction)) {
+ continue;
+ }
+ if (instruction == user) {
+ // Return the array content if all elements are known when hitting the user for which
+ // the content was requested.
+ return remaining == 0 ? values : null;
+ }
+ // Any other kinds of use besides array-put mean that the array escapes and could be
+ // altered.
+ if (!instruction.isArrayPut()) {
+ return null;
+ }
+ ArrayPut arrayPut = instruction.asArrayPut();
+ if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) {
+ return null;
+ }
+ int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
+ if (index < 0 || index >= values.length) {
+ return null;
+ }
+ // Allow several writes to the same array element.
+ if (values[index] == null) {
+ remaining--;
+ }
+ ConstInstruction value = arrayPut.value().getConstInstruction();
+ values[index] = value;
+ }
+ if (!block.exit().isGoto()) {
+ return null;
+ }
+ block = block.exit().asGoto().getTarget();
+ // Don't allow any other control flow into the sequence of blocks filling the array from
+ // construction to requested use. This will also includes loopback and guarantee that
+ // this will terminate without marking visited blocks.
+ if (block.getPredecessors().size() != 1) {
+ return null;
+ }
+ iterator = block.listIterator();
+ } while (iterator != null);
+ return null;
+ }
+
/**
* Visits all {@link ArrayPut}'s with the given {@param classListValue} as array and {@link Class}
* as value. Then collects all corresponding {@link DexType}s so as to determine reflective cases.
@@ -305,64 +365,56 @@
*/
private static DexTypeList retrieveDexTypeListFromClassList(
InvokeMethod invoke, Value classListValue) {
- // Make sure this Value refers to an array.
- if (!classListValue.definition.isInvokeNewArray()
- && !classListValue.definition.isNewArrayEmpty()) {
+
+ // The code
+ // A.class.getMethod("m", String.class, String.class)
+ // results in the following Java byte code from javac:
+ //
+ // LDC LA;.class
+ // LDC "m"
+ // ICONST_2
+ // ANEWARRAY java/lang/Class
+ // DUP
+ // ICONST_0
+ // LDC Ljava/lang/String;.class
+ // AASTORE
+ // DUP
+ // ICONST_1
+ // LDC Ljava/lang/String;.class
+ // AASTORE
+ // INVOKEVIRTUAL java/lang/Class.getMethod \
+ // (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
+
+ // Make sure this Value refers to a new array.
+ if (!classListValue.definition.isNewArrayEmpty()) {
return null;
}
- // The only pattern we consider is: new Class[] { A.class, B.class, ... }, which looks like
- // new-array va ...
- // const-class vc ...
- // const/4 vi ...
- // aput-object vc va vi
- // ... repeat putting const class into one location at a time ...
- // invoke-static ... va // Use that array at {@param invoke}.
- BasicBlock block = classListValue.definition.getBlock();
- InstructionIterator iterator = block.iterator();
- iterator.nextUntil(instr -> instr == classListValue.definition);
- Set<Instruction> users = classListValue.definition.outValue().uniqueUsers();
- int maxIndex = -1;
- Map<Integer, DexType> typeMap = new Int2ObjectArrayMap<>();
- while (iterator.hasNext()) {
- Instruction instr = iterator.next();
- // Iterate the instructions up to the current {@param invoke}.
- if (instr == invoke) {
- break;
- }
- if (!users.contains(instr)) {
- continue;
- }
- // Any other kinds of users mean that elements could be escaped and altered.
- if (!instr.isArrayPut()) {
- return null;
- }
- ArrayPut arrayPut = instr.asArrayPut();
- assert arrayPut.array() == classListValue;
- // Ignore statically unknown index.
- if (!(arrayPut.value().isConstClass() && arrayPut.index().isConstNumber())) {
- return null;
- }
- int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
- // Filter out out-of-bound index or non-deterministic index.
- if (index < 0 || typeMap.containsKey(index)) {
- return null;
- }
- maxIndex = maxIndex < index ? index : maxIndex;
- DexType type = arrayPut.value().getConstInstruction().asConstClass().getValue();
- typeMap.put(index, type);
- }
- if (maxIndex < 0) {
+
+ int size =
+ classListValue
+ .definition
+ .asNewArrayEmpty()
+ .size()
+ .getConstInstruction()
+ .asConstNumber()
+ .getIntValue();
+ if (size == 0) {
return DexTypeList.empty();
}
- // Make sure we were able to collect *all* {@link ConstClass}'s.
- for (int i = 0; i <= maxIndex; i++) {
- if (!typeMap.containsKey(i)) {
+
+ ConstInstruction[] arrayContent =
+ evaluateConstArrayContentFromConstructionToUse(
+ classListValue.definition.asNewArrayEmpty(), size, invoke);
+
+ if (arrayContent == null) {
+ return null;
+ }
+ DexType[] types = new DexType[size];
+ for (int i = 0; i < size; i++) {
+ if (!arrayContent[i].isConstClass()) {
return null;
}
- }
- DexType[] types = new DexType [maxIndex + 1];
- for (int i = 0; i <= maxIndex; i++) {
- types[i] = typeMap.get(i);
+ types[i] = arrayContent[i].asConstClass().getValue();
}
return new DexTypeList(types);
}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index e03890c..43eab1d 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -18,6 +18,7 @@
protected final AppInfoWithLiveness appInfo;
protected final RootSet rootSet;
+ protected final InternalOptions options;
protected final ImmutableList<String> dictionary;
protected final Map<MemberType, DexString> renaming = new IdentityHashMap<>();
@@ -29,6 +30,7 @@
MemberNameMinifier(AppInfoWithLiveness appInfo, RootSet rootSet, InternalOptions options) {
this.appInfo = appInfo;
this.rootSet = rootSet;
+ this.options = options;
this.dictionary = options.proguardConfiguration.getObfuscationDictionary();
this.useUniqueMemberNames = options.proguardConfiguration.isUseUniqueClassMemberNames();
this.overloadAggressively =
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 1f206a3..6b109c5 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -168,8 +168,9 @@
Map<Wrapper<DexMethod>, DexString> renamingAtThisLevel = new HashMap<>();
NamingState<DexProto, ?> state =
computeStateIfAbsent(type, k -> getState(holder.superType).createChild());
- holder.forEachMethod(method ->
- assignNameToMethod(method, state, renamingAtThisLevel, doPrivates));
+ for (DexEncodedMethod method : holder.allMethodsSorted()) {
+ assignNameToMethod(method, state, renamingAtThisLevel, doPrivates);
+ }
if (!doPrivates && !useUniqueMemberNames) {
renamingAtThisLevel.forEach(
(key, candidate) -> {
@@ -236,8 +237,10 @@
DexClass clazz = appInfo.definitionFor(iface);
if (clazz != null) {
Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface, frontierMap);
- clazz.forEachMethod(method -> addStatesToGlobalMapForMethod(
- method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface));
+ for (DexEncodedMethod method : shuffleMethods(clazz.methods())) {
+ addStatesToGlobalMapForMethod(
+ method, collectedStates, globalStateMap, sourceMethodsMap, originStates, iface);
+ }
}
});
// Collect the live call sites for multi-interface lambda expression renaming. For code with
@@ -469,7 +472,9 @@
: parent.createChild());
if (holder != null) {
boolean keepAll = holder.isLibraryClass() || holder.accessFlags.isAnnotation();
- holder.forEachMethod(method -> reserveNamesForMethod(method, keepAll, state));
+ for (DexEncodedMethod method : shuffleMethods(holder.methods())) {
+ reserveNamesForMethod(method, keepAll, state);
+ }
}
return state;
}
@@ -528,4 +533,10 @@
return proto;
}
}
+
+ // Shuffles the given methods if assertions are enabled and deterministic debugging is disabled.
+ // Used to ensure that the generated output is deterministic.
+ private Iterable<DexEncodedMethod> shuffleMethods(Iterable<DexEncodedMethod> methods) {
+ return options.testing.irOrdering.order(methods);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/IROrdering.java b/src/main/java/com/android/tools/r8/utils/IROrdering.java
new file mode 100644
index 0000000..4b4ded3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IROrdering.java
@@ -0,0 +1,62 @@
+// 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.utils;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public interface IROrdering {
+
+ Iterable<DexEncodedMethod> order(Iterable<DexEncodedMethod> methods);
+
+ Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods);
+
+ class IdentityIROrdering implements IROrdering {
+
+ private static final IdentityIROrdering INSTANCE = new IdentityIROrdering();
+
+ private IdentityIROrdering() {}
+
+ public static IdentityIROrdering getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Iterable<DexEncodedMethod> order(Iterable<DexEncodedMethod> methods) {
+ return methods;
+ }
+
+ @Override
+ public Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
+ return methods;
+ }
+ }
+
+ class NondeterministicIROrdering implements IROrdering {
+
+ private static final NondeterministicIROrdering INSTANCE = new NondeterministicIROrdering();
+
+ private NondeterministicIROrdering() {}
+
+ public static NondeterministicIROrdering getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public List<DexEncodedMethod> order(Iterable<DexEncodedMethod> methods) {
+ List<DexEncodedMethod> toShuffle = Lists.newArrayList(methods);
+ Collections.shuffle(toShuffle);
+ return toShuffle;
+ }
+
+ @Override
+ public List<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
+ return order((Iterable<DexEncodedMethod>) methods);
+ }
+ }
+}
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 421d55c..16f181d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -22,6 +22,8 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
+import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -30,7 +32,6 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
-import java.util.function.Function;
public class InternalOptions {
@@ -97,7 +98,7 @@
public boolean passthroughDexCode = false;
// Optimization-related flags. These should conform to -dontoptimize.
- public boolean enableVerticalClassMerging = false;
+ public boolean enableVerticalClassMerging = true;
public boolean enableDevirtualization = true;
public boolean enableNonNullTracking = true;
public boolean enableInlining =
@@ -293,6 +294,12 @@
public Path proguardCompatibilityRulesOutput = null;
+ public static boolean assertionsEnabled() {
+ boolean assertionsEnabled = false;
+ assert assertionsEnabled = true; // Intentional side-effect.
+ return assertionsEnabled;
+ }
+
public void warningMissingEnclosingMember(DexType clazz, Origin origin, int version) {
TypeVersionPair pair = new TypeVersionPair(version, clazz);
synchronized (missingEnclosingMembers) {
@@ -442,8 +449,10 @@
public static class TestingOptions {
- public Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> irOrdering =
- Function.identity();
+ public IROrdering irOrdering =
+ InternalOptions.assertionsEnabled() && !InternalOptions.DETERMINISTIC_DEBUGGING
+ ? NondeterministicIROrdering.getInstance()
+ : IdentityIROrdering.getInstance();
public boolean alwaysUsePessimisticRegisterAllocation = false;
public boolean invertConditionals = false;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 01fa54d..2816a9c 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -411,7 +411,13 @@
/** Compile an application with R8 using the supplied proguard configuration. */
protected AndroidApp compileWithR8(AndroidApp app, String proguardConfig)
throws IOException, CompilationFailedException {
- return compileWithR8(app, proguardConfig, null);
+ return compileWithR8(app, proguardConfig, null, Backend.DEX);
+ }
+
+ /** Compile an application with R8 using the supplied proguard configuration. */
+ protected AndroidApp compileWithR8(AndroidApp app, String proguardConfig, Backend backend)
+ throws IOException, CompilationFailedException {
+ return compileWithR8(app, proguardConfig, null, backend);
}
/** Compile an application with R8 using the supplied proguard configuration. */
@@ -676,6 +682,11 @@
}
/** Run application on Art or Java with the specified main class. */
+ protected String runOnVM(AndroidApp app, Class mainClass, Backend backend) throws IOException {
+ return runOnVM(app, mainClass.getName(), backend);
+ }
+
+ /** Run application on Art or Java with the specified main class. */
protected String runOnVM(AndroidApp app, String mainClass, Backend backend) throws IOException {
switch (backend) {
case CF:
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index c63f425..06e6a4f 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -95,7 +95,16 @@
}
@Test
- public void testInstanceMethodRelaxation() throws Exception {
+ public void testInstanceMethodRelaxationWithVerticalClassMerging() throws Exception {
+ testInstanceMethodRelaxation(true);
+ }
+
+ @Test
+ public void testInstanceMethodRelaxationWithoutVerticalClassMerging() throws Exception {
+ testInstanceMethodRelaxation(false);
+ }
+
+ private void testInstanceMethodRelaxation(boolean enableVerticalClassMerging) throws Exception {
Class mainClass = TestMain.class;
R8Command.Builder builder = loadProgramFiles(backend, mainClass.getPackage());
@@ -122,27 +131,34 @@
),
Origin.unknown());
- AndroidApp app = ToolHelper.runR8(builder.build());
+ AndroidApp app =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> options.enableVerticalClassMerging = enableVerticalClassMerging);
compareReferenceJVMAndProcessed(app, mainClass);
+ // When vertical class merging is enabled, Itf1 is merged into Sub1 and Itf2 is merged into
+ // Sub2, and as a result of these merges, neither Sub1 nor Sub2 end up in the output because of
+ // inlining.
CodeInspector codeInspector = new CodeInspector(app);
- assertPublic(codeInspector, Base.class,
- new MethodSignature("foo", STRING, ImmutableList.of()));
+ assertPublic(codeInspector, Base.class, new MethodSignature("foo", STRING, ImmutableList.of()));
// Base#foo?() can't be publicized due to Itf<1>#foo<1>().
- assertNotPublic(codeInspector, Base.class,
- new MethodSignature("foo1", STRING, ImmutableList.of()));
- assertNotPublic(codeInspector, Base.class,
- new MethodSignature("foo2", STRING, ImmutableList.of()));
+ assertNotPublic(
+ codeInspector, Base.class, new MethodSignature("foo1", STRING, ImmutableList.of()));
+ assertNotPublic(
+ codeInspector, Base.class, new MethodSignature("foo2", STRING, ImmutableList.of()));
- // Sub?#bar1(int) can be publicized as they don't bother each other.
- assertPublic(codeInspector, Sub1.class,
- new MethodSignature("bar1", STRING, ImmutableList.of("int")));
- assertPublic(codeInspector, Sub2.class,
- new MethodSignature("bar1", STRING, ImmutableList.of("int")));
+ if (!enableVerticalClassMerging) {
+ // Sub?#bar1(int) can be publicized as they don't bother each other.
+ assertPublic(
+ codeInspector, Sub1.class, new MethodSignature("bar1", STRING, ImmutableList.of("int")));
+ assertPublic(
+ codeInspector, Sub2.class, new MethodSignature("bar1", STRING, ImmutableList.of("int")));
- // Sub2#bar2(int) is unique throughout the hierarchy, hence publicized.
- assertPublic(codeInspector, Sub2.class,
- new MethodSignature("bar2", STRING, ImmutableList.of("int")));
+ // Sub2#bar2(int) is unique throughout the hierarchy, hence publicized.
+ assertPublic(
+ codeInspector, Sub2.class, new MethodSignature("bar2", STRING, ImmutableList.of("int")));
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
index cf95846..ede9fee 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardTest.java
@@ -95,4 +95,13 @@
CompatProguardOptions options = parseArgs("--no-data-resources");
assertFalse(options.includeDataResources);
}
+
+ @Test
+ public void testDisableVerticalClassMerging() throws Exception {
+ CompatProguardOptions enabledOptions = parseArgs();
+ assertFalse(enabledOptions.disableVerticalClassMerging);
+
+ CompatProguardOptions disabledOptions = parseArgs("--no-vertical-class-merging");
+ assertTrue(disabledOptions.disableVerticalClassMerging);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
new file mode 100644
index 0000000..c90e0fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compatproguard/reflection/ReflectionTest.java
@@ -0,0 +1,151 @@
+// 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.compatproguard.reflection;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class A {
+
+ public void method0() {
+ System.out.print(0);
+ }
+
+ public void method1(String s) {
+ System.out.print(s);
+ }
+
+ public void method2(String s1, String s2) {
+ System.out.print(s1 + s2);
+ }
+
+ public void method3(int i1, int i2) {
+ System.out.print(i1 + i2);
+ }
+}
+
+class Main {
+ public static void main(String[] args) throws Exception {
+ A a = new A();
+
+ Method m;
+ m = A.class.getMethod("method0");
+ m.invoke(a);
+ m = A.class.getMethod("method1", String.class);
+ m.invoke(a, "1");
+ m = A.class.getMethod("method2", String.class, String.class);
+ m.invoke(a, "2", "3");
+ m = A.class.getDeclaredMethod("method0");
+ m.invoke(a);
+ m = A.class.getDeclaredMethod("method1", String.class);
+ m.invoke(a, "1");
+ m = A.class.getDeclaredMethod("method2", String.class, String.class);
+ m.invoke(a, "2", "3");
+ m = A.class.getDeclaredMethod("method3", int.class, int.class);
+ m.invoke(a, 2, 2);
+
+ try {
+ m = A.class.getMethod("method0");
+ m.invoke(a);
+ m = A.class.getMethod("method1", String.class);
+ m.invoke(a, "1");
+ m = A.class.getMethod("method2", String.class, String.class);
+ m.invoke(a, "2", "3");
+ m = A.class.getDeclaredMethod("method0");
+ m.invoke(a);
+ m = A.class.getDeclaredMethod("method1", String.class);
+ m.invoke(a, "1");
+ m = A.class.getDeclaredMethod("method2", String.class, String.class);
+ m.invoke(a, "2", "3");
+ m = A.class.getDeclaredMethod("method3", int.class, int.class);
+ m.invoke(a, 2, 2);
+ } catch (Exception e) {
+ }
+
+ Class[] argumentTypes;
+ argumentTypes = new Class[2];
+ argumentTypes[1] = int.class;
+ argumentTypes[0] = int.class;
+ argumentTypes[0] = String.class;
+ argumentTypes[1] = String.class;
+ m = A.class.getDeclaredMethod("method2", argumentTypes);
+ m.invoke(a, "2", "3");
+ m = A.class.getDeclaredMethod("method2", argumentTypes);
+ m.invoke(a, "4", "5");
+
+ argumentTypes[1] = int.class;
+ argumentTypes[0] = int.class;
+ m = A.class.getDeclaredMethod("method3", argumentTypes);
+ m.invoke(a, 3, 3);
+ m = A.class.getDeclaredMethod("method3", argumentTypes);
+ m.invoke(a, 3, 4);
+
+ try {
+ argumentTypes = new Class[2];
+ argumentTypes[1] = int.class;
+ argumentTypes[0] = int.class;
+ argumentTypes[0] = String.class;
+ argumentTypes[1] = String.class;
+ m = A.class.getDeclaredMethod("method2", argumentTypes);
+ m.invoke(a, "2", "3");
+ m = A.class.getDeclaredMethod("method2", argumentTypes);
+ m.invoke(a, "4", "7");
+
+ argumentTypes[1] = int.class;
+ argumentTypes[0] = int.class;
+ m = A.class.getDeclaredMethod("method3", argumentTypes);
+ m.invoke(a, 3, 3);
+ m = A.class.getDeclaredMethod("method3", argumentTypes);
+ m.invoke(a, 3, 4);
+ } catch (Exception e) {
+ }
+ }
+}
+
+@RunWith(Parameterized.class)
+public class ReflectionTest extends TestBase {
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public ReflectionTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ AndroidApp output =
+ compileWithR8(
+ readClasses(A.class, Main.class), keepMainProguardConfiguration(Main.class), backend);
+ CodeInspector inspector = new CodeInspector(output);
+ assertThat(inspector.clazz(A.class).method("void", "method0", ImmutableList.of()), isRenamed());
+ assertThat(
+ inspector.clazz(A.class).method("void", "method1", ImmutableList.of("java.lang.String")),
+ isRenamed());
+ assertThat(
+ inspector
+ .clazz(A.class)
+ .method("void", "method2", ImmutableList.of("java.lang.String", "java.lang.String")),
+ isRenamed());
+
+ assertEquals(runOnJava(Main.class), runOnVM(output, Main.class, backend));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index 6ae151e..b63d4ba 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -10,17 +10,11 @@
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.beust.jcommander.internal.Lists;
-import java.io.IOException;
+import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
import org.junit.Test;
public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
@@ -30,13 +24,7 @@
String proguardMap;
}
- private static Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
- List<DexEncodedMethod> toShuffle = Lists.newArrayList(methods);
- Collections.shuffle(toShuffle);
- return new LinkedHashSet<>(toShuffle);
- }
-
- private CompilationResult doRun() throws IOException, CompilationFailedException {
+ private CompilationResult doRun() throws CompilationFailedException {
R8Command command =
R8Command.builder()
.addProgramFiles(Paths.get(GMSCORE_V7_DIR, GMSCORE_APK))
@@ -49,7 +37,7 @@
command,
options -> {
// For this test just do random shuffle.
- options.testing.irOrdering = R8GMSCoreDeterministicTest::shuffle;
+ options.testing.irOrdering = NondeterministicIROrdering.getInstance();
// Only use one thread to process to process in the order decided by the callback.
options.numberOfThreads = 1;
// Ignore the missing classes.
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 fdb86a1..a2f8d3e 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
@@ -25,6 +25,7 @@
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.NumberConversion;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -60,6 +61,7 @@
private static final TypeLatticeElement NULL = NullLatticeElement.getInstance();
private static final TypeLatticeElement SINGLE = SingleTypeLatticeElement.getInstance();
private static final TypeLatticeElement INT = IntTypeLatticeElement.getInstance();
+ private static final TypeLatticeElement LONG = LongTypeLatticeElement.getInstance();
private final String dirName;
private final String smaliFileName;
@@ -287,7 +289,18 @@
method.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
TypeAnalysis analysis = new TypeAnalysis(appInfo, method);
analysis.widening(method, irCode);
- forEachOutValue(irCode, (v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
+ forEachOutValue(irCode, (v, l) -> {
+ verifyTypeEnvironment(expectedLattices, v, l);
+ // There are double-to-int, long-to-int, and int-to-long conversions in this example.
+ if (v.definition != null && v.definition.isNumberConversion()) {
+ NumberConversion instr = v.definition.asNumberConversion();
+ if (instr.to.isWide()) {
+ assertEquals(LONG, l);
+ } else {
+ assertEquals(INT, l);
+ }
+ }
+ });
}
// One more complicated example.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthDebugTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthDebugTest.java
new file mode 100644
index 0000000..35b2788
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthDebugTest.java
@@ -0,0 +1,14 @@
+// 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.optimize.string;
+
+class StringLengthDebugTest {
+ public static void main(String[] args) {
+ String x = "ABC";
+ int l1 = x.length();
+ System.out.println(l1);
+ int l2 = "XYZ".length();
+ System.out.println(l2);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthDebugTestRunner.java
new file mode 100644
index 0000000..21033e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthDebugTestRunner.java
@@ -0,0 +1,85 @@
+// 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.optimize.string;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
+import com.android.tools.r8.debug.D8DebugTestConfig;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.FrameInspector;
+import com.android.tools.r8.debug.DebugTestConfig;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class StringLengthDebugTestRunner extends DebugTestBase {
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V5_1_1)
+ public void test() throws Throwable {
+ Class<?> main = StringLengthDebugTest.class;
+ DebugTestConfig config = new D8DebugTestConfig()
+ .compileAndAdd(temp, ToolHelper.getClassFileForTestClass(main));
+ runDebugTest(config, main.getCanonicalName(),
+ breakpoint(main.getCanonicalName(), "main"),
+ run(),
+ stepOver(),
+ // String x = "ABC";
+ inspect(
+ i -> {
+ FrameInspector frame = i.getFrame(0);
+ frame.checkLocal("x");
+ frame.checkNoLocal("l1");
+ frame.checkNoLocal("l2");
+ }
+ ),
+ // int l1 = x.length();
+ stepInto(),
+ inspect(
+ i -> {
+ FrameInspector frame = i.getFrame(0);
+ assertEquals(String.class.getCanonicalName(), frame.getClassName());
+ assertEquals("length", frame.getMethodName());
+ }
+ ),
+ stepOut(),
+ stepOver(),
+ inspect(
+ i -> {
+ FrameInspector frame = i.getFrame(0);
+ frame.checkLocal("x");
+ frame.checkLocal("l1");
+ frame.checkNoLocal("l2");
+ }
+ ),
+ // System.out.println(l1);
+ stepOver(),
+ // int l2 = "XYZ".length();
+ stepInto(),
+ inspect(
+ i -> {
+ FrameInspector frame = i.getFrame(0);
+ assertEquals(String.class.getCanonicalName(), frame.getClassName());
+ assertEquals("length", frame.getMethodName());
+ }
+ ),
+ stepOut(),
+ stepOver(),
+ inspect(
+ i -> {
+ FrameInspector frame = i.getFrame(0);
+ frame.checkLocal("x");
+ frame.checkLocal("l1");
+ frame.checkLocal("l2");
+ }
+ ),
+ run()
+ );
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
index 307dd26..6e18f74 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
@@ -9,6 +9,8 @@
import static org.junit.Assert.assertThat;
import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.ForceInline;
import com.android.tools.r8.NeverInline;
@@ -63,8 +65,7 @@
System.out.println(s1.length());
String s2 = simpleInlinable();
- // Depends on inlining, and thus R8 can whereas D8 can't.
- // For R8: constCount++, for D8: stringLengthCount++
+ // Depends on inlining: constCount++
System.out.println(s2.length());
String s3 = simpleInlinable();
System.out.println(s3);
@@ -81,6 +82,10 @@
System.out.println(s5.codePointCount(0, s5.length()));
System.out.println(s5);
+ // Make sure this is not optimized in DEBUG mode.
+ int l = "ABC".length();
+ System.out.println(l);
+
try {
npe();
} catch (NullPointerException npe) {
@@ -199,9 +204,15 @@
}
AndroidApp app = buildAndroidApp(classes);
- AndroidApp processedApp = compileWithD8(app);
- // No inlining, thus the 2nd length() can't be computed.
- test(processedApp, 1, 3);
+ D8Command.Builder builder = ToolHelper.prepareD8CommandBuilder(app);
+ builder.setMode(CompilationMode.RELEASE);
+ AndroidApp processedApp = ToolHelper.runD8(builder);
+ test(processedApp, 1, 4);
+
+ builder = ToolHelper.prepareD8CommandBuilder(app);
+ builder.setMode(CompilationMode.DEBUG);
+ processedApp = ToolHelper.runD8(builder);
+ test(processedApp, 6, 0);
}
@Test
@@ -224,6 +235,6 @@
builder.addProguardConfiguration(ImmutableList.of(pgConf), Origin.unknown());
AndroidApp processedApp = ToolHelper.runR8(builder.build());
- test(processedApp, 0, 4);
+ test(processedApp, 0, 5);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
index d938819..6d24386 100644
--- a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -79,7 +79,16 @@
public class ParameterTypeTest extends TestBase {
@Test
- public void test_fromJavac() throws Exception {
+ public void test_fromJavacWithVerticalClassMerging() throws Exception {
+ test_fromJavac(true);
+ }
+
+ @Test
+ public void test_fromJavacWithoutVerticalClassMerging() throws Exception {
+ test_fromJavac(false);
+ }
+
+ private void test_fromJavac(boolean enableVerticalClassMerging) throws Exception {
String mainName = B112452064TestMain.class.getCanonicalName();
ProcessResult javaResult = ToolHelper.runJava(ToolHelper.getClassPathForTests(), mainName);
assertEquals(0, javaResult.exitCode);
@@ -102,6 +111,7 @@
builder.addProguardConfiguration(config, Origin.unknown());
AndroidApp processedApp = ToolHelper.runR8(builder.build(), options -> {
options.enableInlining = false;
+ options.enableVerticalClassMerging = enableVerticalClassMerging;
});
Path outDex = temp.getRoot().toPath().resolve("dex.zip");
@@ -117,7 +127,11 @@
MethodSubject foo = superInterface1.method("void", "foo", ImmutableList.of());
assertThat(foo, isRenamed());
ClassSubject superInterface2 = inspector.clazz(B112452064SuperInterface2.class);
- assertThat(superInterface2, isRenamed());
+ if (enableVerticalClassMerging) {
+ assertThat(superInterface2, not(isPresent()));
+ } else {
+ assertThat(superInterface2, isRenamed());
+ }
MethodSubject bar = superInterface1.method("void", "bar", ImmutableList.of());
assertThat(bar, not(isPresent()));
ClassSubject subInterface = inspector.clazz(B112452064SubInterface.class);