Merge "Reland "Revised workaround for Android 6.0 dex2oat divergence issue.""
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 627bbe6..f0c9636 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -6,24 +6,34 @@
import com.android.tools.r8.cf.CfPrinter;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.conversion.CfSourceCode;
import com.android.tools.r8.ir.conversion.CfState;
import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
import com.android.tools.r8.naming.NamingLens;
import org.objectweb.asm.MethodVisitor;
public class CfDexItemBasedConstString extends CfInstruction {
- private DexReference item;
+ private final DexReference item;
+ private final ClassNameComputationInfo classNameComputationInfo;
- public CfDexItemBasedConstString(DexReference item) {
+ public CfDexItemBasedConstString(
+ DexReference item, ClassNameComputationInfo classNameComputationInfo) {
this.item = item;
+ this.classNameComputationInfo = classNameComputationInfo;
}
public DexReference getItem() {
return item;
}
+ public ClassNameComputationInfo getClassNameComputationInfo() {
+ return classNameComputationInfo;
+ }
+
@Override
public CfDexItemBasedConstString asDexItemBasedConstString() {
return this;
@@ -52,6 +62,13 @@
}
@Override
+ public void registerUse(UseRegistry registry, DexType clazz) {
+ if (item.isDexType() && classNameComputationInfo.needsToRegisterTypeReference()) {
+ registry.registerTypeReference(item.asDexType());
+ }
+ }
+
+ @Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
builder.addDexItemBasedConstString(state.push(builder.getFactory().stringType).register, item);
}
diff --git a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
index 1871c41..a55ddfa 100644
--- a/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/code/DexItemBasedConstString.java
@@ -6,7 +6,9 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
import com.android.tools.r8.naming.ClassNameMapper;
import java.nio.ShortBuffer;
@@ -15,14 +17,22 @@
public static final String NAME = "DexItemBasedConstString";
public static final String SMALI_NAME = "const-string*";
- public DexItemBasedConstString(int register, DexReference string) {
+ private final ClassNameComputationInfo classNameComputationInfo;
+
+ public DexItemBasedConstString(
+ int register, DexReference string, ClassNameComputationInfo classNameComputationInfo) {
super(register, string);
+ this.classNameComputationInfo = classNameComputationInfo;
}
public DexReference getItem() {
return (DexReference) BBBB;
}
+ public ClassNameComputationInfo getClassNameComputationInfo() {
+ return classNameComputationInfo;
+ }
+
@Override
public String getName() {
return NAME;
@@ -68,6 +78,13 @@
}
@Override
+ public void registerUse(UseRegistry registry) {
+ if (getItem().isDexType() && classNameComputationInfo.needsToRegisterTypeReference()) {
+ registry.registerTypeReference(getItem().asDexType());
+ }
+ }
+
+ @Override
public void buildIR(IRBuilder builder) {
builder.addDexItemBasedConstString(AA, (DexReference) BBBB);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 0474696..46738e2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1203,14 +1203,14 @@
optimizationInfo = info;
}
- public void copyMetadataFromInlinee(DexEncodedMethod inlinee) {
+ public void copyMetadata(DexEncodedMethod from) {
checkIfObsolete();
// Record that the current method uses identifier name string if the inlinee did so.
- if (inlinee.getOptimizationInfo().useIdentifierNameString()) {
+ if (from.getOptimizationInfo().useIdentifierNameString()) {
getMutableOptimizationInfo().markUseIdentifierNameString();
}
- if (inlinee.classFileVersion > classFileVersion) {
- upgradeClassFileVersion(inlinee.getClassFileVersion());
+ if (from.classFileVersion > classFileVersion) {
+ upgradeClassFileVersion(from.getClassFileVersion());
}
}
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 0450937..0b07644 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -128,6 +128,9 @@
public final DexString containsMethodName = createString("contains");
public final DexString startsWithMethodName = createString("startsWith");
public final DexString endsWithMethodName = createString("endsWith");
+ public final DexString equalsMethodName = createString("equals");
+ public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase");
+ public final DexString contentEqualsMethodName = createString("contentEquals");
public final DexString valueOfMethodName = createString("valueOf");
public final DexString toStringMethodName = createString("toString");
@@ -580,6 +583,9 @@
public final DexMethod contains;
public final DexMethod startsWith;
public final DexMethod endsWith;
+ public final DexMethod equals;
+ public final DexMethod equalsIgnoreCase;
+ public final DexMethod contentEqualsCharSequence;
public final DexMethod valueOf;
public final DexMethod toString;
@@ -590,17 +596,25 @@
length = createMethod(
stringDescriptor, lengthMethodName, intDescriptor, DexString.EMPTY_ARRAY);
- DexString[] needsOneCharSequence = new DexString[]{charSequenceDescriptor};
+ DexString[] needsOneCharSequence = { charSequenceDescriptor };
+ DexString[] needsOneString = { stringDescriptor };
+ DexString[] needsOneObject = { objectDescriptor };
+
contains = createMethod(
stringDescriptor, containsMethodName, booleanDescriptor, needsOneCharSequence);
- DexString[] needsOneString = new DexString[]{stringDescriptor};
startsWith = createMethod(
stringDescriptor, startsWithMethodName, booleanDescriptor, needsOneString);
endsWith = createMethod(
stringDescriptor, endsWithMethodName, booleanDescriptor, needsOneString);
+ equals = createMethod(
+ stringDescriptor, equalsMethodName, booleanDescriptor, needsOneObject);
+ equalsIgnoreCase = createMethod(
+ stringDescriptor, equalsIgnoreCaseMethodName, booleanDescriptor, needsOneString);
+ contentEqualsCharSequence = createMethod(
+ stringDescriptor, contentEqualsMethodName, booleanDescriptor, needsOneCharSequence);
valueOf = createMethod(
- stringDescriptor, valueOfMethodName, stringDescriptor, new DexString[]{objectDescriptor});
+ stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
toString = createMethod(
stringDescriptor, toStringMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
}
@@ -975,14 +989,13 @@
public ReferenceTypeLatticeElement createReferenceTypeLatticeElement(
DexType type, boolean isNullable, AppInfo appInfo) {
- ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
- if (typeLattice != null) {
- return isNullable == typeLattice.isNullable() ? typeLattice
- : typeLattice.getOrCreateDualLattice();
- }
synchronized (type) {
- typeLattice = referenceTypeLatticeElements.get(type);
- if (typeLattice == null) {
+ ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
+ if (typeLattice != null) {
+ typeLattice = isNullable == typeLattice.isNullable() ? typeLattice
+ : typeLattice.getOrCreateDualLattice();
+ assert typeLattice.isNullable() == isNullable;
+ } else {
if (type.isClassType()) {
if (!type.isUnknown() && type.isInterface()) {
typeLattice = new ClassTypeLatticeElement(
@@ -1000,8 +1013,9 @@
}
referenceTypeLatticeElements.put(type, typeLattice);
}
+ assert typeLattice.isNullable() == isNullable;
+ return typeLattice;
}
- return typeLattice;
}
private static <S extends PresortedComparable<S>> void assignSortedIndices(Collection<S> items,
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 6eb69b8..c0413e0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
import com.android.tools.r8.utils.EncodedValueUtils;
import java.util.Arrays;
import org.objectweb.asm.Handle;
@@ -719,9 +720,20 @@
}
public static class DexItemBasedValueString extends NestedDexValue<DexReference> {
+ private final ClassNameComputationInfo classNameComputationInfo;
public DexItemBasedValueString(DexReference value) {
+ this(value, ClassNameComputationInfo.none());
+ }
+
+ public DexItemBasedValueString(
+ DexReference value, ClassNameComputationInfo classNameComputationInfo) {
super(value);
+ this.classNameComputationInfo = classNameComputationInfo;
+ }
+
+ public ClassNameComputationInfo getClassNameComputationInfo() {
+ return classNameComputationInfo;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 3ec7971..2b54a4b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -13,25 +13,38 @@
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;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
public class DexItemBasedConstString extends ConstInstruction {
private final DexReference item;
+ private final ClassNameComputationInfo classNameComputationInfo;
public DexItemBasedConstString(Value dest, DexReference item) {
+ this(dest, item, ClassNameComputationInfo.none());
+ }
+
+ public DexItemBasedConstString(
+ Value dest, DexReference item, ClassNameComputationInfo classNameComputationInfo) {
super(dest);
dest.markNeverNull();
this.item = item;
+ this.classNameComputationInfo = classNameComputationInfo;
}
public static DexItemBasedConstString copyOf(Value newValue, DexItemBasedConstString original) {
- return new DexItemBasedConstString(newValue, original.getItem());
+ return new DexItemBasedConstString(
+ newValue, original.getItem(), original.classNameComputationInfo);
}
public DexReference getItem() {
return item;
}
+ public ClassNameComputationInfo getClassNameComputationInfo() {
+ return classNameComputationInfo;
+ }
+
@Override
public boolean isDexItemBasedConstString() {
return true;
@@ -45,7 +58,10 @@
@Override
public void buildDex(DexBuilder builder) {
int dest = builder.allocatedRegister(outValue(), getNumber());
- builder.add(this, new com.android.tools.r8.code.DexItemBasedConstString(dest, item));
+ builder.add(
+ this,
+ new com.android.tools.r8.code.DexItemBasedConstString(
+ dest, item, classNameComputationInfo));
}
@Override
@@ -99,7 +115,7 @@
@Override
public void buildCf(CfBuilder builder) {
- builder.add(new CfDexItemBasedConstString(item));
+ builder.add(new CfDexItemBasedConstString(item, classNameComputationInfo));
}
@Override
@@ -109,6 +125,6 @@
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
- return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.stringType, false, appInfo);
+ return TypeLatticeElement.stringClassType(appInfo);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 644dfa3..b4ad228 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -726,6 +726,12 @@
return isConstant() && getConstInstruction().isDexItemBasedConstString();
}
+ public boolean isDexItemBasedConstStringThatNeedsToComputeClassName() {
+ return isDexItemBasedConstString()
+ && getConstInstruction().asDexItemBasedConstString()
+ .getClassNameComputationInfo().needsToComputeClassName();
+ }
+
public boolean isConstClass() {
return isConstant() && getConstInstruction().isConstClass();
}
@@ -762,7 +768,9 @@
* Returns whether this value is known to never be <code>null</code>.
*/
public boolean isNeverNull() {
- return neverNull || (definition != null && definition.isNonNull());
+ return neverNull
+ || (definition != null && definition.isNonNull())
+ || (typeLattice.isReference() && typeLattice.nullElement().isDefinitelyNotNull());
}
public boolean canBeNull() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index a658474..8f32081 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -28,7 +28,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.code.Add;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
@@ -45,7 +44,6 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StackValue;
import com.android.tools.r8.ir.code.StackValues;
-import com.android.tools.r8.ir.code.Store;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -385,75 +383,59 @@
private void rewriteIincPatterns() {
for (BasicBlock block : code.blocks) {
- if (block.getInstructions().size() < IINC_PATTERN_SIZE) {
- continue;
- }
- int earliestAddInstrIdx = 2;
- int latestAddInstrIdx = block.getInstructions().size() - 3;
ListIterator<Instruction> it = block.getInstructions().listIterator();
- int instrIdx = -1;
- while (it.hasNext()) {
- ++instrIdx;
- Instruction current = it.next();
- if (instrIdx < earliestAddInstrIdx || latestAddInstrIdx < instrIdx || !current.isAdd()) {
- continue;
- }
- it.previous();
- it.previous();
- it.previous();
- Instruction instruction0 = it.next();
- Instruction instruction1 = it.next();
- Add instruction2 = it.next().asAdd();
- Store instruction3 = it.next().asStore();
-
- // Set bail-out position after Add.
- it.previous();
-
- if (instruction3 == null) {
+ // Test that we have enough instructions for iinc.
+ while (IINC_PATTERN_SIZE <= block.getInstructions().size() - it.nextIndex()) {
+ Instruction loadOrConst1 = it.next();
+ if (!loadOrConst1.isLoad() && !loadOrConst1.isConstNumber()) {
continue;
}
Load load;
ConstNumber constNumber;
-
- if (instruction0.isLoad()) {
- load = instruction0.asLoad();
- constNumber = instruction1.asConstNumber();
+ if (loadOrConst1.isLoad()) {
+ load = loadOrConst1.asLoad();
+ constNumber = it.next().asConstNumber();
} else {
- constNumber = instruction0.asConstNumber();
- load = instruction1.asLoad();
+ load = it.next().asLoad();
+ constNumber = loadOrConst1.asConstNumber();
}
-
+ Instruction add = it.next().asAdd();
+ Instruction store = it.next().asStore();
+ // Reset pointer to load.
+ it.previous();
+ it.previous();
+ it.previous();
+ it.previous();
if (load == null
|| constNumber == null
+ || add == null
+ || store == null
|| constNumber.outValue().getTypeLattice() != TypeLatticeElement.INT) {
+ it.next();
continue;
}
-
int increment = constNumber.getIntValue();
if (increment < Byte.MIN_VALUE || Byte.MAX_VALUE < increment) {
+ it.next();
continue;
}
-
- int register = getLocalRegister(load.inValues().get(0));
- if (register != getLocalRegister(instruction3.outValue())) {
+ if (getLocalRegister(load.src()) != getLocalRegister(store.outValue())) {
+ it.next();
continue;
}
- Position position = instruction2.getPosition();
- if (position != instruction0.getPosition()
- || position != instruction1.getPosition()
- || position != instruction3.getPosition()) {
+ Position position = add.getPosition();
+ if (position != load.getPosition()
+ || position != constNumber.getPosition()
+ || position != store.getPosition()) {
continue;
}
- it.previous();
- it.previous();
- it.previous();
it.remove();
it.next();
it.remove();
it.next();
it.remove();
it.next();
- Inc inc = new Inc(instruction3.outValue(), load.inValues().get(0), increment);
+ Inc inc = new Inc(store.outValue(), load.inValues().get(0), increment);
inc.setPosition(position);
inc.setBlock(block);
it.set(inc);
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 cf3d788..38842da 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -76,6 +76,7 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Suppliers;
import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -178,7 +179,8 @@
assert appInfo.hasLiveness();
AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- this.nonNullTracker = new NonNullTracker(appInfo);
+ this.nonNullTracker =
+ new NonNullTracker(appInfo, libraryMethodsReturningNonNull(appInfo.dexItemFactory));
this.inliner = new Inliner(appViewWithLiveness, this, options);
this.outliner = new Outliner(appInfoWithLiveness, options, this);
this.memberValuePropagation =
@@ -291,6 +293,16 @@
return methods;
}
+ // Library methods listed here are based on their original implementations. That is, we assume
+ // these cannot be overridden.
+ public static Set<DexMethod> libraryMethodsReturningNonNull(DexItemFactory factory) {
+ return ImmutableSet.of(
+ factory.stringMethods.valueOf,
+ factory.classMethods.getName,
+ factory.classMethods.getSimpleName
+ );
+ }
+
private void removeLambdaDeserializationMethods() {
if (lambdaRewriter != null) {
lambdaRewriter.removeLambdaDeserializationMethods(appInfo.classes());
@@ -890,12 +902,6 @@
inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
}
- // Either marked by IdentifierNameStringMarker or propagated from inlinee.
- // Then, make it visible to IdentifierMinifier.
- if (method.getOptimizationInfo().useIdentifierNameString()) {
- feedback.markUseIdentifierNameString(method);
- }
-
if (appInfo.hasLiveness()) {
// Reflection optimization 1. getClass() -> const-class
codeRewriter.rewriteGetClass(code);
@@ -911,6 +917,12 @@
assert code.isConsistentSSA();
}
+ // Either marked by IdentifierNameStringMarker or name reflection, or propagated from inlinee.
+ // Then, make it visible to IdentifierMinifier.
+ if (method.getOptimizationInfo().useIdentifierNameString()) {
+ feedback.markUseIdentifierNameString(method);
+ }
+
if (devirtualizer != null) {
assert code.verifyTypes(appInfo, appView, graphLense());
devirtualizer.devirtualizeInvokeInterface(code, method.method.getHolder());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
index 3ef25f5..5dbdbe1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
@@ -59,7 +59,7 @@
}
@Override
- public void markUseIdentifierNameString(DexEncodedMethod method) {
+ public synchronized void markUseIdentifierNameString(DexEncodedMethod method) {
getOptimizationInfoForUpdating(method).markUseIdentifierNameString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 01555da..7a9ec00 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -531,6 +531,7 @@
encodedMethod.annotations,
encodedMethod.parameterAnnotationsList,
encodedMethod.getCode());
+ newMethod.copyMetadata(encodedMethod);
rewriter.methodMapping.put(encodedMethod.method, callTarget);
// TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
DexCode dexCode = newMethod.getCode().asDexCode();
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 553eaa2..98ee72c 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
@@ -151,8 +151,10 @@
private final Set<DexMethod> libraryMethodsReturningReceiver;
private final InternalOptions options;
- public CodeRewriter(IRConverter converter,
- Set<DexMethod> libraryMethodsReturningReceiver, InternalOptions options) {
+ public CodeRewriter(
+ IRConverter converter,
+ Set<DexMethod> libraryMethodsReturningReceiver,
+ InternalOptions options) {
this.converter = converter;
this.appInfo = converter.appInfo;
this.options = options;
@@ -1531,15 +1533,15 @@
return;
}
AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
+ Set<Value> needToWidenValues = Sets.newIdentityHashSet();
+ Set<Value> needToNarrowValues = Sets.newIdentityHashSet();
InstructionIterator iterator = code.instructionIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
if (invoke.outValue() != null && !invoke.outValue().hasLocalInfo()) {
- boolean isLibraryMethodReturningReceiver =
- libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod());
- if (isLibraryMethodReturningReceiver) {
+ if (libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod())) {
if (checkArgumentType(invoke, invoke.getInvokedMethod(), 0)) {
invoke.outValue().replaceUsers(invoke.arguments().get(0));
invoke.setOutValue(null);
@@ -1554,11 +1556,18 @@
if (definition != null && definition.getOptimizationInfo().returnsArgument()) {
int argumentIndex = definition.getOptimizationInfo().getReturnedArgument();
// Replace the out value of the invoke with the argument and ignore the out value.
- if (argumentIndex != -1 && checkArgumentType(invoke, target.method,
- argumentIndex)) {
+ if (argumentIndex != -1
+ && checkArgumentType(invoke, target.method, argumentIndex)) {
Value argument = invoke.arguments().get(argumentIndex);
- assert invoke.outValue().verifyCompatible(argument.outType());
- invoke.outValue().replaceUsers(argument);
+ Value outValue = invoke.outValue();
+ assert outValue.verifyCompatible(argument.outType());
+ if (argument.getTypeLattice().lessThanOrEqual(
+ outValue.getTypeLattice(), appInfo)) {
+ needToNarrowValues.addAll(outValue.affectedValues());
+ } else {
+ needToWidenValues.addAll(outValue.affectedValues());
+ }
+ outValue.replaceUsers(argument);
invoke.setOutValue(null);
}
}
@@ -1567,6 +1576,17 @@
}
}
}
+ if (!needToWidenValues.isEmpty() || !needToNarrowValues.isEmpty()) {
+ TypeAnalysis analysis = new TypeAnalysis(appInfo, code.method);
+ // If out value of invoke < argument (e.g., losing non-null info), widen users type.
+ if (!needToWidenValues.isEmpty()) {
+ analysis.widening(needToWidenValues);
+ }
+ // Otherwise, i.e., argument has more precise types, narrow users type.
+ if (!needToNarrowValues.isEmpty()) {
+ analysis.narrowing(needToNarrowValues);
+ }
+ }
assert code.isConsistentGraph();
}
@@ -1726,7 +1746,7 @@
}
// Check if the static put is a constant derived from the class holding the method.
- // This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
+ // This checks for java.lang.Class.get*Name.
private boolean isClassNameConstantOf(DexClass clazz, StaticPut put) {
if (put.getField().type != dexItemFactory.stringType) {
return false;
@@ -1740,8 +1760,7 @@
private boolean isClassNameConstantOf(DexClass clazz, Instruction instruction) {
if (instruction.isInvokeVirtual()) {
InvokeVirtual invoke = instruction.asInvokeVirtual();
- if ((invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName
- || invoke.getInvokedMethod() == dexItemFactory.classMethods.getName)
+ if (dexItemFactory.classMethods.isReflectiveNameLookup(invoke.getInvokedMethod())
&& !invoke.inValues().get(0).isPhi()
&& invoke.inValues().get(0).definition.isConstClass()
&& invoke.inValues().get(0).definition.asConstClass().getValue() == clazz.type) {
@@ -1785,7 +1804,9 @@
} else if (inValue.isDexItemBasedConstString()) {
DexItemBasedConstString cnst =
inValue.getConstInstruction().asDexItemBasedConstString();
- encodedField.setStaticValue(new DexItemBasedValueString(cnst.getItem()));
+ assert !cnst.getClassNameComputationInfo().needsToComputeClassName();
+ encodedField.setStaticValue(
+ new DexItemBasedValueString(cnst.getItem(), cnst.getClassNameComputationInfo()));
} else {
assert false;
}
@@ -1886,6 +1907,9 @@
}
DexField field = put.getField();
if (clazz.definesStaticField(field)) {
+ if (put.inValue().isDexItemBasedConstStringThatNeedsToComputeClassName()) {
+ continue;
+ }
if (put.inValue().isConstant()) {
if ((field.type.isClassType() || field.type.isArrayType())
&& put.inValue().isZero()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index bad5456..be94302 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -427,7 +428,8 @@
assert IteratorUtils.peekNext(blockIterator) == block;
// Kick off the tracker to add non-null IRs only to the inlinee blocks.
- new NonNullTracker(appView.appInfo())
+ new NonNullTracker(appView.appInfo(),
+ IRConverter.libraryMethodsReturningNonNull(appView.dexItemFactory()))
.addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
assert !blockIterator.hasNext();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 63d0242..3c27a60 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -647,7 +647,7 @@
context.accessFlags.unsetBridge();
}
- context.copyMetadataFromInlinee(target);
+ context.copyMetadata(target);
code.copyMetadataFromInlinee(inlinee.code);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 29c7979..b82c743 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -178,7 +178,12 @@
DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
if (target != null) {
if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue().canBeNull()) {
- invoke.outValue().markNeverNull();
+ Value knownToBeNonNullValue = invoke.outValue();
+ knownToBeNonNullValue.markNeverNull();
+ TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+ assert typeLattice.isNullable() && typeLattice.isReference();
+ knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
+ affectedValues.addAll(knownToBeNonNullValue.affectedValues());
}
if (target.getOptimizationInfo().returnsConstant()) {
long constant = target.getOptimizationInfo().getReturnedConstant();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 77339e0..1e62c36 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -34,9 +35,11 @@
public class NonNullTracker {
private final AppInfo appInfo;
+ private final Set<DexMethod> libraryMethodsReturningNonNull;
- public NonNullTracker(AppInfo appInfo) {
+ public NonNullTracker(AppInfo appInfo, Set<DexMethod> libraryMethodsReturningNonNull) {
this.appInfo = appInfo;
+ this.libraryMethodsReturningNonNull = libraryMethodsReturningNonNull;
}
@VisibleForTesting
@@ -76,102 +79,48 @@
public void addNonNullInPart(
IRCode code, ListIterator<BasicBlock> blockIterator, Predicate<BasicBlock> blockTester) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
+ Set<Value> knownToBeNonNullValues = Sets.newIdentityHashSet();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (!blockTester.test(block)) {
continue;
}
- // Add non-null after instructions that implicitly indicate receiver/array is not null.
+ // Add non-null after
+ // 1) invocations that call non-overridable library methods that are known to return non null.
+ // 2) instructions that implicitly indicate receiver/array is not null.
+ // TODO(b/71500340): We can add non-null IRs for known-to-be-non-null parameters here.
InstructionListIterator iterator = block.listIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
- if (!throwsOnNullInput(current)) {
- continue;
- }
- Value knownToBeNonNullValue = getNonNullInput(current);
- TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
- // Avoid adding redundant non-null instruction.
- if (knownToBeNonNullValue.isNeverNull() || !isNonNullCandidate(typeLattice)) {
+ if (current.isInvokeMethod()
+ && libraryMethodsReturningNonNull.contains(
+ current.asInvokeMethod().getInvokedMethod())) {
+ Value knownToBeNonNullValue = current.outValue();
+ // Avoid adding redundant non-null instruction.
// Otherwise, we will have something like:
// non_null_rcv <- non-null(rcv)
// ...
// another_rcv <- non-null(non_null_rcv)
- continue;
- }
- // First, if the current block has catch handler, split into two blocks, e.g.,
- //
- // ...x
- // invoke(rcv, ...)
- // ...y
- //
- // ~>
- //
- // ...x
- // invoke(rcv, ...)
- // goto A
- //
- // A: ...y // blockWithNonNullInstruction
- boolean split = block.hasCatchHandlers();
- BasicBlock blockWithNonNullInstruction =
- split ? iterator.split(code, blockIterator) : block;
-
- // Find all users of the original value that are dominated by either the current block
- // or the new split-off block. Since NPE can be explicitly caught, nullness should be
- // propagated through dominance.
- Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
- Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
- Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
- DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
- Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
- for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
- dominatedBlocks.add(dominatee);
- InstructionListIterator dominateeIterator = dominatee.listIterator();
- if (dominatee == blockWithNonNullInstruction && !split) {
- // In the block where the non null instruction will be inserted, skip instructions up
- // to and including the insertion point.
- dominateeIterator.nextUntil(instruction -> instruction == current);
- }
- while (dominateeIterator.hasNext()) {
- Instruction potentialUser = dominateeIterator.next();
- if (users.contains(potentialUser)) {
- dominatedUsers.add(potentialUser);
- }
+ if (knownToBeNonNullValue != null && isNonNullCandidate(knownToBeNonNullValue)) {
+ knownToBeNonNullValues.add(knownToBeNonNullValue);
}
}
- for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
- IntList dominatedPredecessorIndexes =
- findDominatedPredecessorIndexesInPhi(user, knownToBeNonNullValue, dominatedBlocks);
- if (!dominatedPredecessorIndexes.isEmpty()) {
- dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
+ if (throwsOnNullInput(current)) {
+ Value knownToBeNonNullValue = getNonNullInput(current);
+ if (isNonNullCandidate(knownToBeNonNullValue)) {
+ knownToBeNonNullValues.add(knownToBeNonNullValue);
}
}
-
- // Only insert non-null instruction if it is ever used.
- if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
- // Add non-null fake IR, e.g.,
- // ...x
- // invoke(rcv, ...)
- // goto A
- // ...
- // A: non_null_rcv <- non-null(rcv)
- // ...y
- Value nonNullValue = code.createValue(
- typeLattice.asNonNullable(), knownToBeNonNullValue.getLocalInfo());
- affectedValues.addAll(knownToBeNonNullValue.affectedValues());
- NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
- nonNull.setPosition(current.getPosition());
- if (blockWithNonNullInstruction != block) {
- // If we split, add non-null IR on top of the new split block.
- blockWithNonNullInstruction.listIterator().add(nonNull);
- } else {
- // Otherwise, just add it to the current block at the position of the iterator.
- iterator.add(nonNull);
- }
-
- // Replace all users of the original value that are dominated by either the current
- // block or the new split-off block.
- knownToBeNonNullValue.replaceSelectiveUsers(
- nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+ if (!knownToBeNonNullValues.isEmpty()) {
+ addNonNullForValues(
+ code,
+ blockIterator,
+ block,
+ iterator,
+ current,
+ knownToBeNonNullValues,
+ affectedValues);
+ knownToBeNonNullValues.clear();
}
}
@@ -200,9 +149,8 @@
// ...
If theIf = block.exit().asIf();
Value knownToBeNonNullValue = theIf.inValues().get(0);
- TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
- // Avoid adding redundant non-null instruction (or non-null of non-object types).
- if (!knownToBeNonNullValue.isNeverNull() && isNonNullCandidate(typeLattice)) {
+ // Avoid adding redundant non-null instruction.
+ if (isNonNullCandidate(knownToBeNonNullValue)) {
BasicBlock target = theIf.targetFromNonNullObject();
// Ignore uncommon empty blocks.
if (!target.isEmpty()) {
@@ -228,6 +176,7 @@
}
// Avoid adding a non-null for the value without meaningful users.
if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
+ TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
Value nonNullValue = code.createValue(
typeLattice.asNonNullable(), knownToBeNonNullValue.getLocalInfo());
affectedValues.addAll(knownToBeNonNullValue.affectedValues());
@@ -249,6 +198,93 @@
}
}
+ private void addNonNullForValues(
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ BasicBlock block,
+ InstructionListIterator iterator,
+ Instruction current,
+ Set<Value> knownToBeNonNullValues,
+ Set<Value> affectedValues) {
+ // First, if the current block has catch handler, split into two blocks, e.g.,
+ //
+ // ...x
+ // invoke(rcv, ...)
+ // ...y
+ //
+ // ~>
+ //
+ // ...x
+ // invoke(rcv, ...)
+ // goto A
+ //
+ // A: ...y // blockWithNonNullInstruction
+ boolean split = block.hasCatchHandlers();
+ BasicBlock blockWithNonNullInstruction = split ? iterator.split(code, blockIterator) : block;
+ DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+
+ for (Value knownToBeNonNullValue : knownToBeNonNullValues) {
+ // Find all users of the original value that are dominated by either the current block
+ // or the new split-off block. Since NPE can be explicitly caught, nullness should be
+ // propagated through dominance.
+ Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
+ Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
+ Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
+ Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
+ for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
+ dominatedBlocks.add(dominatee);
+ InstructionListIterator dominateeIterator = dominatee.listIterator();
+ if (dominatee == blockWithNonNullInstruction && !split) {
+ // In the block where the non null instruction will be inserted, skip instructions up to
+ // and including the insertion point.
+ dominateeIterator.nextUntil(instruction -> instruction == current);
+ }
+ while (dominateeIterator.hasNext()) {
+ Instruction potentialUser = dominateeIterator.next();
+ if (users.contains(potentialUser)) {
+ dominatedUsers.add(potentialUser);
+ }
+ }
+ }
+ for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
+ IntList dominatedPredecessorIndexes =
+ findDominatedPredecessorIndexesInPhi(user, knownToBeNonNullValue, dominatedBlocks);
+ if (!dominatedPredecessorIndexes.isEmpty()) {
+ dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
+ }
+ }
+
+ // Only insert non-null instruction if it is ever used.
+ if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
+ // Add non-null fake IR, e.g.,
+ // ...x
+ // invoke(rcv, ...)
+ // goto A
+ // ...
+ // A: non_null_rcv <- non-null(rcv)
+ // ...y
+ TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+ Value nonNullValue =
+ code.createValue(typeLattice.asNonNullable(), knownToBeNonNullValue.getLocalInfo());
+ affectedValues.addAll(knownToBeNonNullValue.affectedValues());
+ NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
+ nonNull.setPosition(current.getPosition());
+ if (blockWithNonNullInstruction != block) {
+ // If we split, add non-null IR on top of the new split block.
+ blockWithNonNullInstruction.listIterator().add(nonNull);
+ } else {
+ // Otherwise, just add it to the current block at the position of the iterator.
+ iterator.add(nonNull);
+ }
+
+ // Replace all users of the original value that are dominated by either the current block
+ // or the new split-off block.
+ knownToBeNonNullValue.replaceSelectiveUsers(
+ nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+ }
+ }
+ }
+
private IntList findDominatedPredecessorIndexesInPhi(
Phi user, Value knownToBeNonNullValue, Set<BasicBlock> dominatedBlocks) {
assert user.getOperands().contains(knownToBeNonNullValue);
@@ -274,10 +310,13 @@
return predecessorIndexes;
}
- private boolean isNonNullCandidate(TypeLatticeElement typeLattice) {
+ private boolean isNonNullCandidate(Value knownToBeNonNullValue) {
+ TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
return
+ // redundant
+ !knownToBeNonNullValue.isNeverNull()
// v <- non-null NULL ?!
- !typeLattice.isNull()
+ && !typeLattice.isConstantNull()
// v <- non-null known-to-be-non-null // redundant
&& typeLattice.isNullable()
// e.g., v <- non-null INT ?!
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
new file mode 100644
index 0000000..ecd3a7a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -0,0 +1,117 @@
+// 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;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getCanonicalNameFromDescriptor;
+import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
+import static com.android.tools.r8.utils.DescriptorUtils.getSimpleClassNameFromDescriptor;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption;
+import com.google.common.base.Strings;
+
+public class ReflectionOptimizer {
+
+ public static class ClassNameComputationInfo {
+ public enum ClassNameComputationOption {
+ NONE,
+ NAME, // getName()
+ TYPE_NAME, // getTypeName()
+ CANONICAL_NAME, // getCanonicalName()
+ SIMPLE_NAME; // getSimpleName()
+
+ boolean needsToComputeClassName() {
+ return this != NONE;
+ }
+
+ boolean needsToRegisterTypeReference() {
+ return this == SIMPLE_NAME;
+ }
+ }
+
+ private static final ClassNameComputationInfo DEFAULT_INSTANCE =
+ new ClassNameComputationInfo(ClassNameComputationOption.NONE, 0);
+
+ final ClassNameComputationOption classNameComputationOption;
+ final int arrayDepth;
+
+ public ClassNameComputationInfo(
+ ClassNameComputationOption classNameComputationOption, int arrayDepth) {
+ this.classNameComputationOption = classNameComputationOption;
+ this.arrayDepth = arrayDepth;
+ }
+
+ public static ClassNameComputationInfo none() {
+ return DEFAULT_INSTANCE;
+ }
+
+ public boolean needsToComputeClassName() {
+ return classNameComputationOption.needsToComputeClassName();
+ }
+
+ public boolean needsToRegisterTypeReference() {
+ return classNameComputationOption.needsToRegisterTypeReference();
+ }
+ }
+
+ public static String computeClassName(
+ DexString descriptor, DexClass holder, ClassNameComputationInfo classNameComputationInfo) {
+ return computeClassName(
+ descriptor.toString(),
+ holder,
+ classNameComputationInfo.classNameComputationOption,
+ classNameComputationInfo.arrayDepth);
+ }
+
+ public static String computeClassName(
+ String descriptor,
+ DexClass holder,
+ ClassNameComputationOption classNameComputationOption,
+ int arrayDepth) {
+ String name;
+ switch (classNameComputationOption) {
+ case NAME:
+ name = getClassNameFromDescriptor(descriptor);
+ if (arrayDepth > 0) {
+ name = Strings.repeat("[", arrayDepth) + "L" + name + ";";
+ }
+ break;
+ case TYPE_NAME:
+ // TODO(b/119426668): desugar Type#getTypeName
+ throw new Unreachable("Type#getTypeName not supported yet");
+ // name = getClassNameFromDescriptor(descriptor);
+ // if (arrayDepth > 0) {
+ // name = name + Strings.repeat("[]", arrayDepth);
+ // }
+ // break;
+ case CANONICAL_NAME:
+ name = getCanonicalNameFromDescriptor(descriptor);
+ if (arrayDepth > 0) {
+ name = name + Strings.repeat("[]", arrayDepth);
+ }
+ break;
+ case SIMPLE_NAME:
+ assert holder != null;
+ boolean renamed = !descriptor.equals(holder.type.toDescriptorString());
+ boolean needsToRetrieveInnerName = holder.isMemberClass() || holder.isLocalClass();
+ if (!renamed && needsToRetrieveInnerName) {
+ name = holder.getInnerClassAttributeForThisClass().getInnerName().toString();
+ } else {
+ name = getSimpleClassNameFromDescriptor(descriptor);
+ }
+ if (arrayDepth > 0) {
+ name = name + Strings.repeat("[]", arrayDepth);
+ }
+ break;
+ default:
+ throw new Unreachable(
+ "Unexpected ClassNameComputationOption: '" + classNameComputationOption + "'");
+ }
+ return name;
+ }
+
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index a55015d..3ba9289 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -249,8 +249,10 @@
instructionIterator.previous();
// Unlink all blocks that are dominated by successor.
- DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
- blocksToBeRemoved.addAll(block.unlink(normalSuccessorBlock, dominatorTree));
+ {
+ DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+ blocksToBeRemoved.addAll(block.unlink(normalSuccessorBlock, dominatorTree));
+ }
// Insert constant null before the instruction.
instructionIterator.previous();
@@ -284,6 +286,9 @@
return;
}
if (!appView.dexItemFactory().npeType.isSubtypeOf(guard, appView.appInfo())) {
+ // TODO(christofferqa): Consider updating previous dominator tree instead of
+ // rebuilding it from scratch.
+ DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
blocksToBeRemoved.addAll(block.unlink(target, dominatorTree));
}
});
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 9fec63a..782951d 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
@@ -4,9 +4,10 @@
package com.android.tools.r8.ir.optimize.string;
import static com.android.tools.r8.ir.optimize.CodeRewriter.removeOrReplaceByDebugLocalWrite;
-import static com.android.tools.r8.utils.DescriptorUtils.getCanonicalNameFromDescriptor;
-import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
-import static com.android.tools.r8.utils.DescriptorUtils.getSimpleClassNameFromDescriptor;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.CANONICAL_NAME;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.NAME;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.SIMPLE_NAME;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.computeClassName;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexClass;
@@ -18,20 +19,21 @@
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.base.Strings;
import java.util.function.BiFunction;
import java.util.function.Function;
public class StringOptimizer {
- final ThrowingInfo throwingInfo;
+ private final ThrowingInfo throwingInfo;
public StringOptimizer(InternalOptions options) {
throwingInfo = options.isGeneratingClassFiles()
@@ -43,6 +45,9 @@
// boolean String#startsWith(str)
// boolean String#endsWith(str)
// boolean String#contains(str)
+ // boolean String#equals(str)
+ // boolean String#equalsIgnoreCase(str)
+ // boolean String#contentEquals(str)
public void computeTrivialOperationsOnConstString(IRCode code, DexItemFactory factory) {
if (!code.hasConstString) {
return;
@@ -67,6 +72,12 @@
operatorWithString = (rcv, arg) -> rcv.startsWith(arg) ? 1 : 0;
} else if (invokedMethod == factory.stringMethods.endsWith) {
operatorWithString = (rcv, arg) -> rcv.endsWith(arg) ? 1 : 0;
+ } else if (invokedMethod == factory.stringMethods.equals) {
+ operatorWithString = (rcv, arg) -> rcv.equals(arg) ? 1 : 0;
+ } else if (invokedMethod == factory.stringMethods.equalsIgnoreCase) {
+ operatorWithString = (rcv, arg) -> rcv.equalsIgnoreCase(arg) ? 1 : 0;
+ } else if (invokedMethod == factory.stringMethods.contentEqualsCharSequence) {
+ operatorWithString = (rcv, arg) -> rcv.contentEquals(arg) ? 1 : 0;
}
if (operatorWithNoArg == null && operatorWithString == null) {
continue;
@@ -104,6 +115,7 @@
// Find Class#get*Name() with a constant-class and replace it with a const-string if possible.
public void rewriteClassGetName(IRCode code, AppInfo appInfo) {
+ boolean markUseIdentifierNameString = false;
InstructionIterator it = code.instructionIterator();
while (it.hasNext()) {
Instruction instr = it.next();
@@ -147,29 +159,18 @@
continue;
}
+ DexItemBasedConstString deferred = null;
String name = null;
if (invokedMethod == appInfo.dexItemFactory.classMethods.getName) {
if (code.options.enableMinification) {
- // TODO(b/118536394): Add support minification and pinning.
- // May need store array depth on DexItemBasedConstString.
- // May need enum on DexItemBasedConstString to distinguish name computation.
- continue;
- }
- name = getClassNameFromDescriptor(baseType.toDescriptorString());
- if (arrayDepth > 0) {
- name = Strings.repeat("[", arrayDepth) + "L" + name + ";";
+ deferred = new DexItemBasedConstString(
+ invoke.outValue(), baseType, new ClassNameComputationInfo(NAME, arrayDepth));
+ } else {
+ name = computeClassName(baseType.toDescriptorString(), holder, NAME, arrayDepth);
}
} else if (invokedMethod == appInfo.dexItemFactory.classMethods.getTypeName) {
// TODO(b/119426668): desugar Type#getTypeName
continue;
- // if (code.options.enableMinification) {
- // // TODO(b/118536394): Add support minification and pinning.
- // continue;
- // }
- // name = getClassNameFromDescriptor(baseType.toDescriptorString());
- // if (arrayDepth > 0) {
- // name = name + Strings.repeat("[]", arrayDepth);
- // }
} else if (invokedMethod == appInfo.dexItemFactory.classMethods.getCanonicalName) {
// Always returns null if the target type is local or anonymous class.
if (holder.isLocalClass() || holder.isAnonymousClass()) {
@@ -177,12 +178,14 @@
it.replaceCurrentInstruction(constNull);
} else {
if (code.options.enableMinification) {
- // TODO(b/118536394): Add support minification and pinning.
- continue;
- }
- name = getCanonicalNameFromDescriptor(baseType.toDescriptorString());
- if (arrayDepth > 0) {
- name = name + Strings.repeat("[]", arrayDepth);
+ deferred =
+ new DexItemBasedConstString(
+ invoke.outValue(),
+ baseType,
+ new ClassNameComputationInfo(CANONICAL_NAME, arrayDepth));
+ } else {
+ name = computeClassName(
+ baseType.toDescriptorString(), holder, CANONICAL_NAME, arrayDepth);
}
}
} else if (invokedMethod == appInfo.dexItemFactory.classMethods.getSimpleName) {
@@ -191,16 +194,10 @@
name = "";
} else {
if (code.options.enableMinification) {
- // TODO(b/118536394): Add support minification and pinning.
- continue;
- }
- if (holder.isMemberClass() || holder.isLocalClass()) {
- name = holder.getInnerClassAttributeForThisClass().getInnerName().toString();
+ deferred = new DexItemBasedConstString(
+ invoke.outValue(), baseType, new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
} else {
- name = getSimpleClassNameFromDescriptor(baseType.toDescriptorString());
- }
- if (arrayDepth > 0) {
- name = name + Strings.repeat("[]", arrayDepth);
+ name = computeClassName(baseType.toDescriptorString(), holder, SIMPLE_NAME, arrayDepth);
}
}
}
@@ -210,8 +207,14 @@
ConstString constString =
new ConstString(stringValue, appInfo.dexItemFactory.createString(name), throwingInfo);
it.replaceCurrentInstruction(constString);
+ } else if (deferred != null) {
+ it.replaceCurrentInstruction(deferred);
+ markUseIdentifierNameString = true;
}
}
+ if (markUseIdentifierNameString) {
+ code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
+ }
}
// String#valueOf(null) -> "null"
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 6928d3e..61695f1 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.computeClassName;
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
import com.android.tools.r8.cf.code.CfConstString;
@@ -23,7 +24,6 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardClassFilter;
-import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.List;
import java.util.Map;
@@ -36,23 +36,19 @@
private final AppInfoWithLiveness appInfo;
private final ProguardClassFilter adaptClassStrings;
private final NamingLens lens;
- private final Object2BooleanMap<DexReference> identifierNameStrings;
IdentifierMinifier(
AppInfoWithLiveness appInfo, ProguardClassFilter adaptClassStrings, NamingLens lens) {
this.appInfo = appInfo;
this.adaptClassStrings = adaptClassStrings;
this.lens = lens;
- this.identifierNameStrings = appInfo.identifierNameStrings;
}
void run() {
if (!adaptClassStrings.isEmpty()) {
adaptClassStrings();
}
- if (!identifierNameStrings.isEmpty()) {
- replaceIdentifierNameString();
- }
+ replaceDexItemBasedConstString();
}
private void adaptClassStrings() {
@@ -120,28 +116,35 @@
return originalLiteral;
}
- // TODO(christofferqa): Rename to replaceDexItemBasedConstString.
- private void replaceIdentifierNameString() {
+ private void replaceDexItemBasedConstString() {
for (DexProgramClass clazz : appInfo.classes()) {
// Some const strings could be moved to field's static value (from <clinit>).
for (DexEncodedField field : clazz.staticFields()) {
- replaceIdentifierNameStringInStaticField(field);
+ replaceDexItemBasedConstStringInStaticField(field);
}
- clazz.forEachMethod(this::replaceIdentifierNameStringInMethod);
+ clazz.forEachMethod(this::replaceDexItemBasedConstStringInMethod);
}
}
- private void replaceIdentifierNameStringInStaticField(DexEncodedField encodedField) {
+ private void replaceDexItemBasedConstStringInStaticField(DexEncodedField encodedField) {
assert encodedField.accessFlags.isStatic();
DexValue staticValue = encodedField.getStaticValue();
if (staticValue instanceof DexItemBasedValueString) {
- DexReference original = ((DexItemBasedValueString) staticValue).getValue();
- encodedField.setStaticValue(
- new DexValueString(lens.lookupName(original, appInfo.dexItemFactory)));
+ DexItemBasedValueString dexItemBasedValueString = (DexItemBasedValueString) staticValue;
+ DexReference original = dexItemBasedValueString.getValue();
+ DexString replacement =
+ dexItemBasedValueString.getClassNameComputationInfo().needsToComputeClassName()
+ ? appInfo.dexItemFactory.createString(
+ computeClassName(
+ lens.lookupDescriptor(original.asDexType()),
+ appInfo.definitionFor(original.asDexType()),
+ dexItemBasedValueString.getClassNameComputationInfo()))
+ : lens.lookupName(original, appInfo.dexItemFactory);
+ encodedField.setStaticValue(new DexValueString(replacement));
}
}
- private void replaceIdentifierNameStringInMethod(DexEncodedMethod encodedMethod) {
+ private void replaceDexItemBasedConstStringInMethod(DexEncodedMethod encodedMethod) {
if (!encodedMethod.getOptimizationInfo().useIdentifierNameString()) {
return;
}
@@ -159,8 +162,15 @@
Instruction instruction = instructions[i];
if (instruction instanceof DexItemBasedConstString) {
DexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
- instructions[i] =
- new ConstString(cnst.AA, lens.lookupName(cnst.getItem(), appInfo.dexItemFactory));
+ DexString replacement =
+ cnst.getClassNameComputationInfo().needsToComputeClassName()
+ ? appInfo.dexItemFactory.createString(
+ computeClassName(
+ lens.lookupDescriptor(cnst.getItem().asDexType()),
+ appInfo.definitionFor(cnst.getItem().asDexType()),
+ cnst.getClassNameComputationInfo()))
+ : lens.lookupName(cnst.getItem(), appInfo.dexItemFactory);
+ instructions[i] = new ConstString(cnst.AA, replacement);
}
}
} else {
@@ -170,8 +180,15 @@
CfInstruction instruction = instructions.get(i);
if (instruction.isDexItemBasedConstString()) {
CfDexItemBasedConstString cnst = instruction.asDexItemBasedConstString();
- instructions.set(
- i, new CfConstString(lens.lookupName(cnst.getItem(), appInfo.dexItemFactory)));
+ DexString replacement =
+ cnst.getClassNameComputationInfo().needsToComputeClassName()
+ ? appInfo.dexItemFactory.createString(
+ computeClassName(
+ lens.lookupDescriptor(cnst.getItem().asDexType()),
+ appInfo.definitionFor(cnst.getItem().asDexType()),
+ cnst.getClassNameComputationInfo()))
+ : lens.lookupName(cnst.getItem(), appInfo.dexItemFactory);
+ instructions.set(i, new CfConstString(replacement));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index fc10fe9..1084322 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1392,6 +1392,7 @@
ConsequentRootSet consequentRootSet = ifRuleEvaluator.run(liveTypes);
enqueueRootItems(consequentRootSet.noShrinking);
rootSet.neverInline.addAll(consequentRootSet.neverInline);
+ rootSet.neverClassInline.addAll(consequentRootSet.neverClassInline);
rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
rootSet.addDependentItems(consequentRootSet.dependentNoShrinking);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 3011951..8d68d2e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.List;
import java.util.stream.Collectors;
@@ -87,6 +88,24 @@
subsequentRule.materialize());
}
+ protected ClassInlineRule neverClassInlineRuleForCondition() {
+ return new ClassInlineRule(
+ Origin.unknown(),
+ Position.UNKNOWN,
+ null,
+ getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
+ getClassAccessFlags(),
+ getNegatedClassAccessFlags(),
+ getClassTypeNegated(),
+ getClassType(),
+ getClassNames().materialize(),
+ getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
+ getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+ getInheritanceIsExtends(),
+ ImmutableList.of(),
+ ClassInlineRule.Type.NEVER);
+ }
+
/**
* Consider the following rule, which requests that class Y should be kept if the method X.m() is
* in the final output.
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index c59081e..de0537f 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -327,7 +327,12 @@
application.timing.end();
}
return new ConsequentRootSet(
- neverInline, noShrinking, noOptimization, noObfuscation, dependentNoShrinking);
+ neverInline,
+ neverClassInline,
+ noShrinking,
+ noOptimization,
+ noObfuscation,
+ dependentNoShrinking);
}
/**
@@ -413,6 +418,14 @@
private void materializeIfRule(ProguardIfRule rule) {
ProguardIfRule materializedRule = rule.materialize();
+ // We need to abort class inlining of classes that could be matched by the condition of this
+ // -if rule.
+ ClassInlineRule neverClassInlineRuleForCondition =
+ materializedRule.neverClassInlineRuleForCondition();
+ if (neverClassInlineRuleForCondition != null) {
+ runPerRule(executorService, futures, neverClassInlineRuleForCondition, materializedRule);
+ }
+
// If the condition of the -if rule has any members, then we need to keep these members to
// ensure that the subsequent rule will be applied again in the second round of tree
// shaking.
@@ -957,7 +970,7 @@
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
this.forceInline = Collections.unmodifiableSet(forceInline);
this.neverInline = neverInline;
- this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
+ this.neverClassInline = neverClassInline;
this.neverMerge = Collections.unmodifiableSet(neverMerge);
this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
this.assumedValues = Collections.unmodifiableMap(assumedValues);
@@ -1010,6 +1023,7 @@
// A partial RootSet that becomes live due to the enabled -if rule.
static class ConsequentRootSet {
final Set<DexMethod> neverInline;
+ final Set<DexType> neverClassInline;
final Map<DexDefinition, ProguardKeepRule> noShrinking;
final Set<DexDefinition> noOptimization;
final Set<DexDefinition> noObfuscation;
@@ -1017,11 +1031,13 @@
private ConsequentRootSet(
Set<DexMethod> neverInline,
+ Set<DexType> neverClassInline,
Map<DexDefinition, ProguardKeepRule> noShrinking,
Set<DexDefinition> noOptimization,
Set<DexDefinition> noObfuscation,
Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking) {
this.neverInline = Collections.unmodifiableSet(neverInline);
+ this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
this.noShrinking = Collections.unmodifiableMap(noShrinking);
this.noOptimization = Collections.unmodifiableSet(noOptimization);
this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 8fa85bf..ee11bcf 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -11,6 +11,8 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.nio.file.FileSystemException;
import java.nio.file.Paths;
import java.util.function.Consumer;
@@ -65,6 +67,13 @@
} catch (ResourceException e) {
throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
} catch (AssertionError e) {
+ // Most of our assertions don't have a message, create a wrapper that has the stack as the
+ // message.
+ if (e.getMessage() == null) {
+ StringWriter stack = new StringWriter();
+ e.printStackTrace(new PrintWriter(stack));
+ e = new AssertionError(stack.toString(), e);
+ }
throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()), e);
}
reporter.failIfPendingErrors();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index bd0f8fb..dfe22c7 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import java.util.Map;
import java.util.function.BiConsumer;
import org.junit.Test;
@@ -50,7 +51,7 @@
DexEncodedMethod foo = codeInspector.clazz(mainClass.getName()).method(signature).getMethod();
IRCode irCode =
foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
- NonNullTracker nonNullTracker = new NonNullTracker(appInfo);
+ NonNullTracker nonNullTracker = new NonNullTracker(appInfo, ImmutableSet.of());
nonNullTracker.addNonNull(irCode);
TypeAnalysis analysis = new TypeAnalysis(appInfo, foo);
analysis.widening(foo, irCode);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index e569ad2..0ada569 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableSet;
import java.util.function.Consumer;
import org.junit.Test;
@@ -41,7 +42,7 @@
foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
checkCountOfNonNull(irCode, 0);
- NonNullTracker nonNullTracker = new NonNullTracker(appInfo);
+ NonNullTracker nonNullTracker = new NonNullTracker(appInfo, ImmutableSet.of());
nonNullTracker.addNonNull(irCode);
assertTrue(irCode.isConsistentSSA());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 4e6bb21..592aeae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -12,7 +12,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.ir.optimize.classinliner.builders.BuildersTestClass;
@@ -38,6 +41,7 @@
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
@@ -46,9 +50,9 @@
import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
-import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Iterator;
@@ -73,39 +77,35 @@
this.backend = backend;
}
- private String run(AndroidApp app, Class mainClass) throws IOException {
- if (backend == Backend.DEX) {
- return runOnArt(app, mainClass);
- } else {
- assert backend == Backend.CF;
- return runOnJava(app, mainClass);
- }
- }
-
@Test
public void testTrivial() throws Exception {
- byte[][] classes = {
- ToolHelper.getClassAsBytes(TrivialTestClass.class),
- ToolHelper.getClassAsBytes(TrivialTestClass.Inner.class),
- ToolHelper.getClassAsBytes(ReferencedFields.class),
- ToolHelper.getClassAsBytes(EmptyClass.class),
- ToolHelper.getClassAsBytes(EmptyClassWithInitializer.class),
- ToolHelper.getClassAsBytes(Iface1.class),
- ToolHelper.getClassAsBytes(Iface1Impl.class),
- ToolHelper.getClassAsBytes(Iface2.class),
- ToolHelper.getClassAsBytes(Iface2Impl.class),
- ToolHelper.getClassAsBytes(CycleReferenceAB.class),
- ToolHelper.getClassAsBytes(CycleReferenceBA.class),
- ToolHelper.getClassAsBytes(ClassWithFinal.class)
+ Class<?> main = TrivialTestClass.class;
+ Class<?>[] classes = {
+ TrivialTestClass.class,
+ TrivialTestClass.Inner.class,
+ ReferencedFields.class,
+ EmptyClass.class,
+ EmptyClassWithInitializer.class,
+ Iface1.class,
+ Iface1Impl.class,
+ Iface2.class,
+ Iface2Impl.class,
+ CycleReferenceAB.class,
+ CycleReferenceBA.class,
+ ClassWithFinal.class
};
- AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+ String javaOutput = runOnJava(main);
+ TestRunResult result = testForR8(backend)
+ .addProgramClasses(classes)
+ .addKeepMainRule(main)
+ .addKeepRules(
+ "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+ .addOptionsModification(this::configure)
+ .run(main)
+ .assertSuccessWithOutput(javaOutput);
- String javaOutput = runOnJava(TrivialTestClass.class);
- String output = run(app, TrivialTestClass.class);
- assertEquals(javaOutput, output);
-
- CodeInspector inspector = new CodeInspector(app);
- ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+ CodeInspector inspector = result.inspector();
+ ClassSubject clazz = inspector.clazz(main);
assertEquals(
Collections.singleton("java.lang.StringBuilder"),
@@ -162,28 +162,44 @@
@Test
public void testBuilders() throws Exception {
- byte[][] classes = {
- ToolHelper.getClassAsBytes(BuildersTestClass.class),
- ToolHelper.getClassAsBytes(BuildersTestClass.Pos.class),
- ToolHelper.getClassAsBytes(Tuple.class),
- ToolHelper.getClassAsBytes(Pair.class),
- ToolHelper.getClassAsBytes(PairBuilder.class),
- ToolHelper.getClassAsBytes(ControlFlow.class),
+ Class<?> main = BuildersTestClass.class;
+ Class<?>[] classes = {
+ NeverInline.class,
+ BuildersTestClass.class,
+ BuildersTestClass.Pos.class,
+ Tuple.class,
+ Pair.class,
+ PairBuilder.class,
+ ControlFlow.class,
};
- AndroidApp app = runR8(buildAndroidApp(classes), BuildersTestClass.class);
+ String javaOutput = runOnJava(main);
+ TestRunResult result = testForR8(backend)
+ .addProgramClasses(classes)
+ .enableProguardTestOptions()
+ .enableInliningAnnotations()
+ .addKeepMainRule(main)
+ .addKeepRules(
+ "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+ .addOptionsModification(this::configure)
+ .run(main)
+ .assertSuccessWithOutput(javaOutput);
- String javaOutput = runOnJava(BuildersTestClass.class);
- String output = run(app, BuildersTestClass.class);
- assertEquals(javaOutput, output);
+ CodeInspector inspector = result.inspector();
+ ClassSubject clazz = inspector.clazz(main);
- CodeInspector inspector = new CodeInspector(app);
- ClassSubject clazz = inspector.clazz(BuildersTestClass.class);
-
- assertEquals(
- Sets.newHashSet(
- "com.android.tools.r8.ir.optimize.classinliner.builders.Pair",
- "java.lang.StringBuilder"),
- collectTypes(clazz, "testSimpleBuilder", "void"));
+ for (int i = 1; i <= 3; i++) {
+ Set<String> expected =
+ Sets.newHashSet(
+ "com.android.tools.r8.ir.optimize.classinliner.builders.Pair",
+ "java.lang.StringBuilder");
+ if (backend == Backend.CF && i < 3) {
+ // const-string canonicalization is disabled in CF, which helps ClassInliner identify
+ // PairBuilder as candidate. Concatenated builder calls in test #3 bother that again.
+ expected.add("com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder");
+ }
+ assertEquals(expected,
+ collectTypes(clazz, "testSimpleBuilder" + i, "void"));
+ }
// Note that Pair created instances were also inlined in the following method since
// we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
@@ -193,7 +209,9 @@
Collections.singleton("java.lang.StringBuilder"),
collectTypes(clazz, "testSimpleBuilderWithMultipleBuilds", "void"));
- assertFalse(inspector.clazz(PairBuilder.class).isPresent());
+ if (backend == Backend.DEX) {
+ assertFalse(inspector.clazz(PairBuilder.class).isPresent());
+ }
assertEquals(
Collections.singleton("java.lang.StringBuilder"),
@@ -242,7 +260,6 @@
runOnJavaRaw(mainClass.name, builder.buildClasses().toArray(new byte[2][]));
assertThat(javaResult.stderr, containsString("IncompatibleClassChangeError"));
- assert backend == Backend.DEX || backend == Backend.CF;
// Check that the code fails with an IncompatibleClassChangeError with ART.
ProcessResult result =
backend == Backend.DEX
@@ -253,19 +270,24 @@
@Test
public void testCodeSample() throws Exception {
- byte[][] classes = {
- ToolHelper.getClassAsBytes(C.class),
- ToolHelper.getClassAsBytes(C.L.class),
- ToolHelper.getClassAsBytes(C.F.class),
- ToolHelper.getClassAsBytes(CodeTestClass.class)
+ Class<?> main = CodeTestClass.class;
+ Class<?>[] classes = {
+ C.class,
+ C.L.class,
+ C.F.class,
+ CodeTestClass.class
};
- AndroidApp app = runR8(buildAndroidApp(classes), CodeTestClass.class);
+ String javaOutput = runOnJava(main);
+ TestRunResult result = testForR8(backend)
+ .addProgramClasses(classes)
+ .addKeepMainRule(main)
+ .addKeepRules(
+ "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+ .addOptionsModification(this::configure)
+ .run(main)
+ .assertSuccessWithOutput(javaOutput);
- String javaOutput = runOnJava(CodeTestClass.class);
- String output = run(app, CodeTestClass.class);
- assertEquals(javaOutput, output);
-
- CodeInspector inspector = new CodeInspector(app);
+ CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(C.class);
assertEquals(
@@ -287,22 +309,26 @@
@Test
public void testInvalidatedRoot() throws Exception {
String prefix = "com.android.tools.r8.ir.optimize.classinliner.invalidroot.";
-
- byte[][] classes = {
- ToolHelper.getClassAsBytes(InvalidRootsTestClass.class),
- ToolHelper.getClassAsBytes(InvalidRootsTestClass.A.class),
- ToolHelper.getClassAsBytes(InvalidRootsTestClass.B.class),
- ToolHelper.getClassAsBytes(InvalidRootsTestClass.NeverReturnsNormally.class),
- ToolHelper.getClassAsBytes(InvalidRootsTestClass.InitNeverReturnsNormally.class)
+ Class<?> main = InvalidRootsTestClass.class;
+ Class<?>[] classes = {
+ InvalidRootsTestClass.class,
+ InvalidRootsTestClass.A.class,
+ InvalidRootsTestClass.B.class,
+ InvalidRootsTestClass.NeverReturnsNormally.class,
+ InvalidRootsTestClass.InitNeverReturnsNormally.class
};
- AndroidApp app = runR8(buildAndroidApp(classes), InvalidRootsTestClass.class);
+ String javaOutput = runOnJava(main);
+ TestRunResult result = testForR8(backend)
+ .addProgramClasses(classes)
+ .addKeepMainRule(main)
+ .addKeepRules(
+ "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+ .addOptionsModification(this::configure)
+ .run(main)
+ .assertSuccessWithOutput(javaOutput);
- String javaOutput = runOnJava(InvalidRootsTestClass.class);
- String output = run(app, InvalidRootsTestClass.class);
- assertEquals(javaOutput, output);
-
- CodeInspector inspector = new CodeInspector(app);
- ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
+ CodeInspector inspector = result.inspector();
+ ClassSubject clazz = inspector.clazz(main);
assertEquals(
Sets.newHashSet(prefix + "InvalidRootsTestClass$NeverReturnsNormally"),
@@ -331,20 +357,25 @@
@Test
public void testDesugaredLambdas() throws Exception {
- Assume.assumeFalse(backend == Backend.CF); // No desugaring with CF backend.
- byte[][] classes = {
- ToolHelper.getClassAsBytes(LambdasTestClass.class),
- ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
- ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+ Assume.assumeFalse("No desugaring with CF backend", backend == Backend.CF);
+ Class<?> main = LambdasTestClass.class;
+ Class<?>[] classes = {
+ LambdasTestClass.class,
+ LambdasTestClass.Iface.class,
+ LambdasTestClass.IfaceUtil.class
};
- AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+ String javaOutput = runOnJava(main);
+ TestRunResult result = testForR8(backend)
+ .addProgramClasses(classes)
+ .addKeepMainRule(main)
+ .addKeepRules(
+ "-dontobfuscate", "-allowaccessmodification", "-keepattributes LineNumberTable")
+ .addOptionsModification(this::configure)
+ .run(main)
+ .assertSuccessWithOutput(javaOutput);
- String javaOutput = runOnJava(LambdasTestClass.class);
- String output = run(app, LambdasTestClass.class);
- assertEquals(javaOutput, output);
-
- CodeInspector inspector = new CodeInspector(app);
- ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+ CodeInspector inspector = result.inspector();
+ ClassSubject clazz = inspector.clazz(main);
assertEquals(
Sets.newHashSet(
@@ -391,42 +422,6 @@
.map(fais -> fais.holder().toString());
}
- private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
- AndroidApp compiled =
- compileWithR8(
- app, getProguardConfig(mainClass.getCanonicalName()), this::configure, backend);
-
- // Materialize file for execution.
- Path generatedFile = temp.getRoot().toPath().resolve("classes.jar");
- compiled.writeToZip(generatedFile, outputMode(backend));
-
- assert backend == Backend.DEX || backend == Backend.CF;
-
- String output =
- backend == Backend.DEX
- ? ToolHelper.runArtNoVerificationErrors(
- generatedFile.toString(), mainClass.getCanonicalName())
- : ToolHelper.runJava(generatedFile, mainClass.getCanonicalName()).stdout;
-
- // Compare with Java.
- ToolHelper.ProcessResult javaResult = ToolHelper.runJava(
- ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
-
- if (javaResult.exitCode != 0) {
- System.out.println(javaResult.stdout);
- System.err.println(javaResult.stderr);
- fail("JVM on original program failed for: " + mainClass);
- }
- assertEquals(
- backend == Backend.DEX
- ? "JVM and ART output differ."
- : "Output of original and processed program differ on JVM.",
- javaResult.stdout,
- output);
-
- return compiled;
- }
-
private String getProguardConfig(String main) {
return StringUtils.joinLines(
keepMainProguardConfiguration(main),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
index 1516e14..aa6d4dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.classinliner.builders;
+import com.android.tools.r8.NeverInline;
+
public class BuildersTestClass {
private static int ID = 0;
@@ -17,31 +19,35 @@
public static void main(String[] args) {
BuildersTestClass test = new BuildersTestClass();
- test.testSimpleBuilder();
+ test.testSimpleBuilder1();
test.testSimpleBuilderWithMultipleBuilds();
test.testBuilderConstructors();
test.testWithControlFlow();
test.testWithMoreControlFlow();
}
- private synchronized void testSimpleBuilder() {
+ @NeverInline
+ private void testSimpleBuilder1() {
System.out.println(
- new PairBuilder<String, String>().setFirst("f-" + next()).build().toString());
+ new PairBuilder<String, String>().setFirst("f-" + next()).build());
testSimpleBuilder2();
testSimpleBuilder3();
}
- private synchronized void testSimpleBuilder2() {
+ @NeverInline
+ private void testSimpleBuilder2() {
System.out.println(
- new PairBuilder<String, String>().setSecond("s-" + next()).build().toString());
+ new PairBuilder<String, String>().setSecond("s-" + next()).build());
}
- private synchronized void testSimpleBuilder3() {
+ @NeverInline
+ private void testSimpleBuilder3() {
System.out.println(new PairBuilder<String, String>()
- .setFirst("f-" + next()).setSecond("s-" + next()).build().toString());
+ .setFirst("f-" + next()).setSecond("s-" + next()).build());
}
- private synchronized void testSimpleBuilderWithMultipleBuilds() {
+ @NeverInline
+ private void testSimpleBuilderWithMultipleBuilds() {
PairBuilder<String, String> builder = new PairBuilder<>();
Pair p1 = builder.build();
System.out.println(p1.toString());
@@ -53,13 +59,15 @@
System.out.println(p3.toString());
}
- private synchronized void testBuilderConstructors() {
+ @NeverInline
+ private void testBuilderConstructors() {
System.out.println(new Tuple().toString());
System.out.println(new Tuple(true, (byte) 77, (short) 9977, '#', 42,
987654321123456789L, -12.34f, 43210.98765, "s-" + next() + "-s").toString());
}
- private synchronized void testWithControlFlow() {
+ @NeverInline
+ private void testWithControlFlow() {
ControlFlow flow = new ControlFlow(-1, 2, 7);
for (int k = 0; k < 25; k++) {
if (k % 3 == 0) {
@@ -71,7 +79,8 @@
System.out.println("flow = " + flow.toString());
}
- private synchronized void testWithMoreControlFlow() {
+ @NeverInline
+ private void testWithMoreControlFlow() {
String str = "1234567890";
Pos pos = new Pos();
while (pos.y < str.length()) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
index f078262..6fea6cd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
@@ -134,6 +134,6 @@
inspector.clazz(TestClass.class).method("void", "bar", ImmutableList.of());
Iterator<InstructionSubject> barInstructionIterator =
barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
- assertEquals(6, Streams.stream(barInstructionIterator).count());
+ assertEquals(4, Streams.stream(barInstructionIterator).count());
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
index 09ab7f7..99cce0b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
@@ -153,6 +153,53 @@
// getSimpleName, local
"-Returned-empty-"
);
+ private static final String RENAMED_OUTPUT = StringUtils.lines(
+ // getName
+ "com.android.tools.r8.ir.optimize.reflection.e",
+ // getTypeName
+ // TODO(b/119426668): desugar Type#getTypeName
+ // "com.android.tools.r8.ir.optimize.reflection.e",
+ // getCanonicalName
+ "com.android.tools.r8.ir.optimize.reflection.e",
+ // getSimpleName
+ "e",
+ // getName, inner
+ "com.android.tools.r8.ir.optimize.reflection.c",
+ // getTypeName, inner
+ // TODO(b/119426668): desugar Type#getTypeName
+ // "com.android.tools.r8.ir.optimize.reflection.c",
+ // getCanonicalName, inner
+ "com.android.tools.r8.ir.optimize.reflection.c",
+ // getSimpleName, inner
+ "c",
+ // getName, array
+ "[Lcom.android.tools.r8.ir.optimize.reflection.e;",
+ // getTypeName, array
+ // TODO(b/119426668): desugar Type#getTypeName
+ // "com.android.tools.r8.ir.optimize.reflection.e[]",
+ // getCanonicalName, array
+ "com.android.tools.r8.ir.optimize.reflection.e[]",
+ // getSimpleName, array
+ "e[]",
+ // getName, anonymous
+ "com.android.tools.r8.ir.optimize.reflection.a",
+ // getTypeName, anonymous
+ // TODO(b/119426668): desugar Type#getTypeName
+ // "com.android.tools.r8.ir.optimize.reflection.a",
+ // getCanonicalName, anonymous
+ "-Returned-null-",
+ // getSimpleName, anonymous
+ "-Returned-empty-",
+ // getName, local
+ "com.android.tools.r8.ir.optimize.reflection.b",
+ // getTypeName, local
+ // TODO(b/119426668): desugar Type#getTypeName
+ // "com.android.tools.r8.ir.optimize.reflection.b,
+ // getCanonicalName, local
+ "-Returned-null-",
+ // getSimpleName, local
+ "-Returned-empty-"
+ );
private static final Class<?> MAIN = GetName0Main.class;
public GetNameTest(Backend backend, boolean enableMinification) throws Exception {
@@ -212,18 +259,13 @@
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.addKeepRules("-keep class **.GetName0*")
- .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+ .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+ .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
if (!enableMinification) {
builder.addKeepRules("-dontobfuscate");
}
- TestRunResult result = builder.run(MAIN);
- if (enableMinification) {
- // TODO(b/118536394): Check even renamed names.
- test(result, 11);
- } else {
- result.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 0);
- }
+ TestRunResult result = builder.run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 0);
}
@Test
@@ -235,7 +277,8 @@
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.addKeepRules("-keep,allowobfuscation class **.GetName0*")
- .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+ .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+ .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
if (!enableMinification) {
builder.addKeepRules("-dontobfuscate");
}
@@ -245,12 +288,11 @@
if (backend == Backend.CF) {
return;
}
- // TODO(b/118536394): Check even renamed names.
- test(result, 11);
+ result.assertSuccessWithOutput(RENAMED_OUTPUT);
} else {
result.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 0);
}
+ test(result, 0);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
index ef7a07b..22918c8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTestBase.java
@@ -4,10 +4,13 @@
package com.android.tools.r8.ir.optimize.reflection;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.Streams;
+import java.io.IOException;
+import java.nio.file.Path;
import java.util.Collection;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -19,6 +22,7 @@
final Backend backend;
final boolean enableMinification;
+ Path mapping;
@Parameterized.Parameters(name = "Backend: {0} minification: {1}")
public static Collection<Object[]> data() {
@@ -30,6 +34,11 @@
this.enableMinification = enableMinification;
}
+ Path createNewMappingPath() throws IOException {
+ mapping = temp.newFile(ToolHelper.DEFAULT_PROGUARD_MAP_FILE).toPath();
+ return mapping;
+ }
+
static boolean isNameReflection(DexMethod method) {
return method.getHolder().toDescriptorString().equals(CLASS_DESCRIPTOR)
&& method.getArity() == 0
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index 54015e3..5bc07da 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -88,6 +88,14 @@
"Local[][][]",
"[][][]"
);
+ private static final String RENAMED_OUTPUT = StringUtils.lines(
+ "f",
+ "e",
+ "b",
+ "a",
+ "d[][][]",
+ "[][][]"
+ );
private static final Class<?> MAIN = ClassGetSimpleName.class;
public GetSimpleNameTest(Backend backend, boolean enableMinification) throws Exception {
@@ -146,16 +154,12 @@
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.addKeepRules("-keep class **.ClassGetSimpleName*")
- .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+ .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+ .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
if (!enableMinification) {
builder.addKeepRules("-dontobfuscate");
}
- TestRunResult result = builder.run(MAIN);
- if (enableMinification) {
- // TODO(b/118536394): Check even renamed simple name.
- } else {
- result.assertSuccessWithOutput(JAVA_OUTPUT);
- }
+ TestRunResult result = builder.run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
test(result, 0);
}
@@ -168,7 +172,8 @@
.enableInliningAnnotations()
.addKeepMainRule(MAIN)
.addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
- .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
+ .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+ .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
if (!enableMinification) {
builder.addKeepRules("-dontobfuscate");
}
@@ -178,7 +183,7 @@
if (backend == Backend.CF) {
return;
}
- // TODO(b/118536394): Check even renamed simple name.
+ result.assertSuccessWithOutput(RENAMED_OUTPUT);
} else {
result.assertSuccessWithOutput(JAVA_OUTPUT);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
index 353dd50..284af11 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
@@ -30,7 +30,10 @@
static boolean argCouldBeNull(String arg) {
return "CONST".contains(arg)
&& "prefix".startsWith(arg)
- && "suffix".endsWith(arg);
+ && "suffix".endsWith(arg)
+ && "CONST".equals(arg)
+ && "CONST".equalsIgnoreCase(arg)
+ && "CONST".contentEquals(arg);
}
public static void main(String[] args) {
@@ -39,6 +42,10 @@
System.out.println(s1.contains("CONST"));
System.out.println(s1.startsWith("prefix"));
System.out.println(s1.endsWith("suffix"));
+ System.out.println(s1.equals("prefix-CONST-suffix"));
+ System.out.println(s1.equalsIgnoreCase("PREFIX-const-SUFFIX"));
+ System.out.println(s1.contentEquals("prefix-CONST-suffix"));
+ System.out.println(s1.contentEquals(new StringBuffer("prefix-CONST-suffix")));
}
{
@@ -47,6 +54,10 @@
System.out.println(s2.contains("CONST"));
System.out.println(s2.startsWith("prefix"));
System.out.println(s2.endsWith("suffix"));
+ System.out.println(s2.equals("prefix-CONST-suffix"));
+ System.out.println(s2.equalsIgnoreCase("pre-con-suf"));
+ System.out.println(s2.contentEquals("prefix-CONST-suffix"));
+ System.out.println(s2.contentEquals(new StringBuffer("prefix-CONST-suffix")));
}
{
@@ -75,12 +86,28 @@
"true",
// s1, endsWith
"true",
+ // s1, equals
+ "true",
+ // s1, equalsIgnoreCase
+ "true",
+ // s1, contentEquals(CharSequence)
+ "true",
+ // s1, contentEquals(StringBuffer)
+ "true",
// s2, contains
"false",
// s2, startsWith
"false",
// s2, endsWith
"false",
+ // s2, equals
+ "false",
+ // s2, equalsIgnoreCase
+ "false",
+ // s2, contentEquals(CharSequence)
+ "false",
+ // s2, contentEquals(StringBuffer)
+ "false",
// argCouldBeNull
"false"
);
@@ -107,7 +134,10 @@
&& method.proto.returnType.isBooleanType()
&& (method.name.toString().equals("contains")
|| method.name.toString().equals("startsWith")
- || method.name.toString().equals("endsWith"));
+ || method.name.toString().equals("endsWith")
+ || method.name.toString().equals("equals")
+ || method.name.toString().equals("equalsIgnoreCase")
+ || method.name.toString().equals("contentEquals"));
}
private long countStringContentChecker(MethodSubject method) {
@@ -131,7 +161,7 @@
"boolean", "argCouldBeNull", ImmutableList.of("java.lang.String"));
assertThat(argCouldBeNull, isPresent());
// Because of nullable argument, all checkers should remain.
- assertEquals(3, countStringContentChecker(argCouldBeNull));
+ assertEquals(6, countStringContentChecker(argCouldBeNull));
}
@Test
@@ -143,14 +173,14 @@
.addProgramClasses(CLASSES)
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 6);
+ test(result, 14);
result = testForD8()
.release()
.addProgramClasses(CLASSES)
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 3);
+ test(result, 8);
}
@Test
@@ -162,6 +192,6 @@
.addKeepMainRule(MAIN)
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 3);
+ test(result, 8);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 6642619..3f579c7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -54,13 +54,9 @@
public static void main(String[] args) {
Foo foo = new Foo();
- // TODO(b/118536394): valueOf in getter() can be removed if combined with name reflection
- // optimization, which will replace it with (definitely non-null) const-string.
System.out.println(foo.getter());
// Trivial, it's String.
String str = foo.toString();
- // TODO(b/119449728): But, it's still nullable.
- // valueOf can be removed if the nullability of its return value is modeled.
System.out.println(String.valueOf(str));
if (str != null) {
// With an explicit check, it's non-null String.
@@ -195,6 +191,6 @@
.addKeepRules("-dontobfuscate")
.run(MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 3, 1, 1);
+ test(result, 1, 1, 1);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/classinlining/IfRuleWithClassInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/classinlining/IfRuleWithClassInlining.java
new file mode 100644
index 0000000..caa06d3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/classinlining/IfRuleWithClassInlining.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.classinlining;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfRuleWithClassInlining extends TestBase {
+
+ private final Backend backend;
+ private final boolean enableClassInlining;
+ private final boolean enableIfRule;
+
+ public IfRuleWithClassInlining(
+ Backend backend, boolean enableClassInlining, boolean enableIfRule) {
+ this.backend = backend;
+ this.enableClassInlining = enableClassInlining;
+ this.enableIfRule = enableIfRule;
+ }
+
+ @Parameters(name = "Backend: {0}, class inlining: {1}, with if rule: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(Backend.values(), BooleanUtils.values(), BooleanUtils.values());
+ }
+
+ @Test
+ public void r8Test() throws Exception {
+ String ifRule =
+ StringUtils.lines(
+ "-if class " + StringBox.Builder.class.getTypeName(),
+ "-keep class " + Unused.class.getTypeName());
+ CodeInspector inspector =
+ testForR8(backend)
+ .addInnerClasses(IfRuleWithClassInlining.class)
+ .addKeepMainRule(TestClass.class)
+ // TODO(b/120061431): Should not be needed for this example.
+ .addKeepRules("-allowaccessmodification")
+ .addKeepRules(enableIfRule ? ifRule : "")
+ .addOptionsModification(options -> options.enableClassInlining = enableClassInlining)
+ .compile()
+ .inspector();
+ if (enableIfRule || !enableClassInlining) {
+ assertThat(inspector.clazz(StringBox.Builder.class), isPresent());
+ } else {
+ assertThat(inspector.clazz(StringBox.Builder.class), not(isPresent()));
+ }
+ if (enableIfRule) {
+ assertThat(inspector.clazz(Unused.class), isPresent());
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ StringBox box = StringBox.builder().setString("Hello world").build();
+ System.out.println(box.getString());
+ }
+ }
+
+ static class StringBox {
+
+ static class Builder {
+
+ private String string = null;
+
+ public Builder setString(String string) {
+ this.string = string;
+ return this;
+ }
+
+ public StringBox build() {
+ return new StringBox(string);
+ }
+ }
+
+ private final String string;
+
+ public StringBox(String string) {
+ this.string = string;
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+ }
+
+ static class Unused {}
+}