Remove unused string builders
Bug: 174285670
Change-Id: Iabcfa27c8689807677347e3ee108c2997e3ed224
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 ce97d08..8f46be4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1135,6 +1135,8 @@
public final DexMethod requireNonNull;
public final DexMethod requireNonNullWithMessage;
public final DexMethod requireNonNullWithMessageSupplier;
+ public final DexMethod toStringWithObject =
+ createMethod(objectsType, createProto(stringType, objectType), "toString");
private ObjectsMethods() {
DexString requireNonNullMethodName = createString("requireNonNull");
@@ -1549,6 +1551,8 @@
public final DexMethod toString;
private final Set<DexMethod> appendMethods;
+ private final Set<DexMethod> appendPrimitiveMethods;
+
private StringBuildingMethods(DexType receiver) {
DexString append = createString("append");
@@ -1567,7 +1571,6 @@
appendObject = createMethod(receiver, createProto(receiver, objectType), append);
appendString = createMethod(receiver, createProto(receiver, stringType), append);
appendStringBuffer = createMethod(receiver, createProto(receiver, stringBufferType), append);
-
charSequenceConstructor =
createMethod(receiver, createProto(voidType, charSequenceType), constructorMethodName);
defaultConstructor = createMethod(receiver, createProto(voidType), constructorMethodName);
@@ -1592,6 +1595,9 @@
appendObject,
appendString,
appendStringBuffer);
+ appendPrimitiveMethods =
+ ImmutableSet.of(
+ appendBoolean, appendChar, appendInt, appendDouble, appendFloat, appendLong);
constructorMethods =
ImmutableSet.of(
charSequenceConstructor, defaultConstructor, intConstructor, stringConstructor);
@@ -1603,6 +1609,22 @@
return appendMethods.contains(method);
}
+ public boolean isAppendObjectMethod(DexMethod method) {
+ return method == appendObject;
+ }
+
+ public boolean isAppendPrimitiveMethod(DexMethod method) {
+ return appendPrimitiveMethods.contains(method);
+ }
+
+ public boolean isAppendStringMethod(DexMethod method) {
+ return method == appendString;
+ }
+
+ public boolean isConstructorMethod(DexMethod method) {
+ return constructorMethods.contains(method);
+ }
+
public boolean constructorInvokeIsSideEffectFree(InvokeMethod invoke) {
DexMethod invokedMethod = invoke.getInvokedMethod();
if (invokedMethod == charSequenceConstructor) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index a3f8421..c9c5c3a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -275,6 +275,18 @@
}
@Override
+ public void replaceCurrentInstructionWithConstString(
+ AppView<?> appView, IRCode code, DexString value) {
+ if (current == null) {
+ throw new IllegalStateException();
+ }
+
+ // Replace the instruction by const-string.
+ ConstString constString = code.createStringConstant(appView, value, current.getLocalInfo());
+ replaceCurrentInstruction(constString);
+ }
+
+ @Override
public void replaceCurrentInstructionWithStaticGet(
AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
if (current == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index cb33b18..db3f81e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -53,6 +53,12 @@
}
@Override
+ public void replaceCurrentInstructionWithConstString(
+ AppView<?> appView, IRCode code, DexString value) {
+ instructionIterator.replaceCurrentInstructionWithConstString(appView, code, value);
+ }
+
+ @Override
public void replaceCurrentInstructionWithStaticGet(
AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
instructionIterator.replaceCurrentInstructionWithStaticGet(
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 792018b..493c560 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -96,6 +96,14 @@
void replaceCurrentInstructionWithConstInt(IRCode code, int value);
+ void replaceCurrentInstructionWithConstString(AppView<?> appView, IRCode code, DexString value);
+
+ default void replaceCurrentInstructionWithConstString(
+ AppView<?> appView, IRCode code, String value) {
+ replaceCurrentInstructionWithConstString(
+ appView, code, appView.dexItemFactory().createString(value));
+ }
+
void replaceCurrentInstructionWithStaticGet(
AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 6daa9a4..9d7d69b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -30,7 +30,9 @@
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.collect.ImmutableList;
import java.util.BitSet;
+import java.util.Collections;
import java.util.List;
public abstract class InvokeMethod extends Invoke {
@@ -238,4 +240,30 @@
}
return false;
}
+
+ abstract static class Builder<B extends Builder<B, I>, I extends InvokeMethod>
+ extends BuilderBase<B, I> {
+
+ protected DexMethod method;
+ protected List<Value> arguments = Collections.emptyList();
+
+ public B setArguments(List<Value> arguments) {
+ assert arguments != null;
+ this.arguments = arguments;
+ return self();
+ }
+
+ public B setSingleArgument(Value argument) {
+ return setArguments(ImmutableList.of(argument));
+ }
+
+ public B setMethod(DexMethod method) {
+ this.method = method;
+ return self();
+ }
+
+ public B setMethod(DexClassAndMethod method) {
+ return setMethod(method.getReference());
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 3139d20..1b3aef3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -27,8 +27,6 @@
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.google.common.collect.ImmutableList;
-import java.util.Collections;
import java.util.List;
public class InvokeStatic extends InvokeMethod {
@@ -235,29 +233,7 @@
.classInitializationMayHaveSideEffectsInContext(appView, context);
}
- public static class Builder extends BuilderBase<Builder, InvokeStatic> {
-
- private DexMethod method;
- private List<Value> arguments = Collections.emptyList();
-
- public Builder setArguments(List<Value> arguments) {
- assert arguments != null;
- this.arguments = arguments;
- return this;
- }
-
- public Builder setSingleArgument(Value argument) {
- return setArguments(ImmutableList.of(argument));
- }
-
- public Builder setMethod(DexMethod method) {
- this.method = method;
- return this;
- }
-
- public Builder setMethod(DexClassAndMethod method) {
- return setMethod(method.getReference());
- }
+ public static class Builder extends InvokeMethod.Builder<Builder, InvokeStatic> {
@Override
public InvokeStatic build() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index d4f737e..e063b8f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -33,6 +33,10 @@
super(target, result, arguments);
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public boolean getInterfaceBit() {
return false;
@@ -167,4 +171,17 @@
return ClassInitializationAnalysis.InstructionUtils.forInvokeVirtual(
this, clazz, context, appView, mode, assumption);
}
+
+ public static class Builder extends InvokeMethod.Builder<Builder, InvokeVirtual> {
+
+ @Override
+ public InvokeVirtual build() {
+ return amend(new InvokeVirtual(method, outValue, arguments));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 889089f..4f4e2c6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -73,6 +73,12 @@
}
@Override
+ public void replaceCurrentInstructionWithConstString(
+ AppView<?> appView, IRCode code, DexString value) {
+ currentBlockIterator.replaceCurrentInstructionWithConstString(appView, code, value);
+ }
+
+ @Override
public void replaceCurrentInstructionWithStaticGet(
AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
currentBlockIterator.replaceCurrentInstructionWithStaticGet(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index 69bdef8..2c93f77 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -19,7 +19,7 @@
import com.android.tools.r8.ir.code.Value;
import java.util.Set;
-public class BooleanMethodOptimizer implements LibraryMethodModelCollection {
+public class BooleanMethodOptimizer extends StatelessLibraryMethodModelCollection {
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
index 4323220..5ee7017 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -19,7 +19,8 @@
import com.android.tools.r8.ir.code.Value;
import java.util.Set;
-public class EnumMethodOptimizer implements LibraryMethodModelCollection {
+public class EnumMethodOptimizer extends StatelessLibraryMethodModelCollection {
+
private final AppView<?> appView;
EnumMethodOptimizer(AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index d6c8ada..5c38b07 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -35,7 +35,7 @@
/** The library types that are modeled. */
private final Set<DexType> modeledLibraryTypes = Sets.newIdentityHashSet();
- private final Map<DexType, LibraryMethodModelCollection> libraryMethodModelCollections =
+ private final Map<DexType, LibraryMethodModelCollection<?>> libraryMethodModelCollections =
new IdentityHashMap<>();
public LibraryMemberOptimizer(AppView<?> appView) {
@@ -43,6 +43,7 @@
register(new BooleanMethodOptimizer(appView));
register(new ObjectMethodOptimizer(appView));
register(new ObjectsMethodOptimizer(appView));
+ register(new StringBuilderMethodOptimizer(appView));
register(new StringMethodOptimizer(appView));
if (appView.enableWholeProgramOptimizations()) {
// Subtyping is required to prove the enum class is a subtype of java.lang.Enum.
@@ -98,9 +99,9 @@
return modeledLibraryTypes.contains(type);
}
- private void register(LibraryMethodModelCollection optimizer) {
+ private void register(LibraryMethodModelCollection<?> optimizer) {
DexType modeledType = optimizer.getType();
- LibraryMethodModelCollection existing =
+ LibraryMethodModelCollection<?> existing =
libraryMethodModelCollections.put(modeledType, optimizer);
assert existing == null;
modeledLibraryTypes.add(modeledType);
@@ -114,13 +115,16 @@
MethodProcessingId methodProcessingId) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
InstructionListIterator instructionIterator = code.instructionListIterator();
+ Map<LibraryMethodModelCollection<?>, LibraryMethodModelCollection.State> optimizationStates =
+ new IdentityHashMap<>();
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
if (instruction.isInvokeMethod()) {
InvokeMethod invoke = instruction.asInvokeMethod();
DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
if (singleTarget != null) {
- optimizeInvoke(code, instructionIterator, invoke, singleTarget, affectedValues);
+ optimizeInvoke(
+ code, instructionIterator, invoke, singleTarget, affectedValues, optimizationStates);
}
}
}
@@ -134,10 +138,15 @@
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues) {
- LibraryMethodModelCollection optimizer =
+ Set<Value> affectedValues,
+ Map<LibraryMethodModelCollection<?>, LibraryMethodModelCollection.State> optimizationStates) {
+ LibraryMethodModelCollection<?> optimizer =
libraryMethodModelCollections.getOrDefault(
singleTarget.getHolderType(), NopLibraryMethodModelCollection.getInstance());
- optimizer.optimize(code, instructionIterator, invoke, singleTarget, affectedValues);
+ LibraryMethodModelCollection.State optimizationState =
+ optimizationStates.computeIfAbsent(
+ optimizer, LibraryMethodModelCollection::createInitialState);
+ optimizer.optimize(
+ code, instructionIterator, invoke, singleTarget, affectedValues, optimizationState);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
index 843e9ab..463f7e7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
@@ -10,10 +10,15 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.library.LibraryMethodModelCollection.State;
import java.util.Set;
/** Used to model the behavior of library methods for optimization purposes. */
-public interface LibraryMethodModelCollection {
+public interface LibraryMethodModelCollection<T extends State> {
+
+ default T createInitialState() {
+ return null;
+ }
/**
* The library class whose methods are being modeled by this collection of models. As an example,
@@ -30,5 +35,20 @@
InstructionListIterator instructionIterator,
InvokeMethod invoke,
DexClassAndMethod singleTarget,
- Set<Value> affectedValues);
+ Set<Value> affectedValues,
+ T state);
+
+ @SuppressWarnings("unchecked")
+ default void optimize(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ Set<Value> affectedValues,
+ Object state) {
+ optimize(code, instructionIterator, invoke, singleTarget, affectedValues, (T) state);
+ }
+
+ /** Thread local optimization state to allow caching, etc. */
+ interface State {}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index 55d1dc3..70ace2b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -36,6 +36,7 @@
.put(dexItemFactory.npeMethods.initWithMessage, alwaysTrue())
.put(dexItemFactory.objectMembers.constructor, alwaysTrue())
.put(dexItemFactory.objectMembers.getClass, alwaysTrue())
+ .put(dexItemFactory.stringBuilderMethods.toString, alwaysTrue())
.put(dexItemFactory.stringMembers.hashCode, alwaysTrue());
putAll(builder, dexItemFactory.classMethods.getNames, alwaysTrue());
putAll(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
index f8847d3..aa93f3c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -17,7 +17,7 @@
import com.android.tools.r8.shaking.ProguardConfiguration;
import java.util.Set;
-public class LogMethodOptimizer implements LibraryMethodModelCollection {
+public class LogMethodOptimizer extends StatelessLibraryMethodModelCollection {
private static final int VERBOSE = 2;
private static final int DEBUG = 3;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
index f852393..2eeb165 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/NopLibraryMethodModelCollection.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.ir.code.Value;
import java.util.Set;
-public class NopLibraryMethodModelCollection implements LibraryMethodModelCollection {
+public class NopLibraryMethodModelCollection extends StatelessLibraryMethodModelCollection {
private static final NopLibraryMethodModelCollection INSTANCE =
new NopLibraryMethodModelCollection();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
index f16af45..c325500 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectMethodOptimizer.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.ir.code.Value;
import java.util.Set;
-public class ObjectMethodOptimizer implements LibraryMethodModelCollection {
+public class ObjectMethodOptimizer extends StatelessLibraryMethodModelCollection {
private final DexItemFactory dexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index 8fcc1ac..9d0c8c8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexItemFactory.ObjectsMethods;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -14,12 +15,17 @@
import com.android.tools.r8.ir.code.Value;
import java.util.Set;
-public class ObjectsMethodOptimizer implements LibraryMethodModelCollection {
+public class ObjectsMethodOptimizer extends StatelessLibraryMethodModelCollection {
+ private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
+ private final ObjectsMethods objectsMethods;
ObjectsMethodOptimizer(AppView<?> appView) {
- this.dexItemFactory = appView.dexItemFactory();
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.appView = appView;
+ this.dexItemFactory = dexItemFactory;
+ this.objectsMethods = dexItemFactory.objectsMethods;
}
@Override
@@ -34,14 +40,16 @@
InvokeMethod invoke,
DexClassAndMethod singleTarget,
Set<Value> affectedValues) {
- if (dexItemFactory.objectsMethods.isRequireNonNullMethod(singleTarget.getReference())) {
+ if (objectsMethods.isRequireNonNullMethod(singleTarget.getReference())) {
optimizeRequireNonNull(instructionIterator, invoke, affectedValues);
+ } else if (singleTarget.getReference() == objectsMethods.toStringWithObject) {
+ optimizeToStringWithObject(code, instructionIterator, invoke, affectedValues);
}
}
private void optimizeRequireNonNull(
InstructionListIterator instructionIterator, InvokeMethod invoke, Set<Value> affectedValues) {
- Value inValue = invoke.inValues().get(0);
+ Value inValue = invoke.getFirstArgument();
if (inValue.getType().isDefinitelyNotNull()) {
Value outValue = invoke.outValue();
if (outValue != null) {
@@ -52,4 +60,18 @@
instructionIterator.removeOrReplaceByDebugLocalRead();
}
}
+
+ private void optimizeToStringWithObject(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ Set<Value> affectedValues) {
+ Value object = invoke.getFirstArgument();
+ if (object.getType().isDefinitelyNull()) {
+ instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
+ if (invoke.hasOutValue()) {
+ affectedValues.addAll(invoke.outValue().affectedValues());
+ }
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
new file mode 100644
index 0000000..6754170
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2019, 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.library;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.library.StatelessLibraryMethodModelCollection.State;
+import java.util.Set;
+
+public abstract class StatelessLibraryMethodModelCollection
+ implements LibraryMethodModelCollection<State> {
+
+ @Override
+ public final State createInitialState() {
+ return null;
+ }
+
+ public abstract void optimize(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ Set<Value> affectedValues);
+
+ @Override
+ public final void optimize(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ Set<Value> affectedValues,
+ State state) {
+ assert state == null;
+ optimize(code, instructionIterator, invoke, singleTarget, affectedValues);
+ }
+
+ static class State implements LibraryMethodModelCollection.State {}
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
new file mode 100644
index 0000000..a88bae9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
@@ -0,0 +1,236 @@
+// Copyright (c) 2020, 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.library;
+
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexItemFactory.StringBuildingMethods;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+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.library.StringBuilderMethodOptimizer.State;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
+import java.util.Set;
+
+public class StringBuilderMethodOptimizer implements LibraryMethodModelCollection<State> {
+
+ private final DexItemFactory dexItemFactory;
+ private final InternalOptions options;
+ private final StringBuildingMethods stringBuilderMethods;
+
+ StringBuilderMethodOptimizer(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.dexItemFactory = dexItemFactory;
+ this.options = appView.options();
+ this.stringBuilderMethods = dexItemFactory.stringBuilderMethods;
+ }
+
+ @Override
+ public State createInitialState() {
+ return new State();
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.stringBuilderType;
+ }
+
+ @Override
+ public void optimize(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ Set<Value> affectedValues,
+ State state) {
+ if (invoke.isInvokeMethodWithReceiver()) {
+ InvokeMethodWithReceiver invokeWithReceiver = invoke.asInvokeMethodWithReceiver();
+ if (stringBuilderMethods.isAppendMethod(singleTarget.getReference())) {
+ optimizeAppend(instructionIterator, invokeWithReceiver, singleTarget, state);
+ }
+ }
+ }
+
+ private void optimizeAppend(
+ InstructionListIterator instructionIterator,
+ InvokeMethodWithReceiver invoke,
+ DexClassAndMethod singleTarget,
+ State state) {
+ if (!state.isUnusedBuilder(invoke.getReceiver())) {
+ return;
+ }
+ if (invoke.hasOutValue()) {
+ invoke.outValue().replaceUsers(invoke.getReceiver());
+ }
+ DexMethod appendMethod = singleTarget.getReference();
+ if (stringBuilderMethods.isAppendPrimitiveMethod(appendMethod)
+ || stringBuilderMethods.isAppendStringMethod(appendMethod)) {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ } else if (stringBuilderMethods.isAppendObjectMethod(appendMethod)) {
+ Value object = invoke.getArgument(1);
+ if (object.isNeverNull()) {
+ instructionIterator.replaceCurrentInstruction(
+ InvokeVirtual.builder()
+ .setSingleArgument(object)
+ .setMethod(dexItemFactory.objectMembers.toString)
+ .build());
+ } else if (options.canUseJavaUtilObjects()) {
+ instructionIterator.replaceCurrentInstruction(
+ InvokeStatic.builder()
+ .setSingleArgument(object)
+ .setMethod(dexItemFactory.objectsMethods.toStringWithObject)
+ .build());
+ // Allow the java.util.Objects optimizer to optimize the newly added toString().
+ instructionIterator.previous();
+ }
+ }
+ }
+
+ class State implements LibraryMethodModelCollection.State {
+
+ final Reference2BooleanMap<Value> unusedBuilders = new Reference2BooleanOpenHashMap<>();
+
+ boolean isUnusedBuilder(Value value) {
+ if (!unusedBuilders.containsKey(value)) {
+ computeIsUnusedBuilder(value);
+ assert unusedBuilders.containsKey(value);
+ }
+ return unusedBuilders.getBoolean(value);
+ }
+
+ private void computeIsUnusedBuilder(Value value) {
+ assert !unusedBuilders.containsKey(value);
+
+ Set<Value> aliases = Sets.newIdentityHashSet();
+ boolean isUnused = computeAllAliasesIfUnusedStringBuilder(value, aliases);
+ aliases.forEach(alias -> unusedBuilders.put(alias, isUnused));
+ }
+
+ /**
+ * Adds all the aliases of the given StringBuilder value to {@param aliases}, or returns false
+ * if all aliases were not found (e.g., due to a phi user).
+ */
+ private boolean computeAllAliasesIfUnusedStringBuilder(Value value, Set<Value> aliases) {
+ WorkList<Value> worklist = WorkList.newIdentityWorkList(value);
+ while (worklist.hasNext()) {
+ Value alias = worklist.next();
+ aliases.add(alias);
+
+ if (unusedBuilders.containsKey(alias)) {
+ assert !unusedBuilders.getBoolean(alias);
+ return false;
+ }
+
+ // Don't track phi aliases.
+ if (alias.hasPhiUsers()) {
+ return false;
+ }
+
+ // Analyze root, if any.
+ if (alias.isPhi()) {
+ return false;
+ }
+
+ Instruction definition = alias.definition;
+ switch (definition.opcode()) {
+ case NEW_INSTANCE:
+ assert definition.asNewInstance().clazz == dexItemFactory.stringBuilderType;
+ break;
+
+ case INVOKE_VIRTUAL:
+ {
+ InvokeVirtual invoke = definition.asInvokeVirtual();
+ if (!stringBuilderMethods.isAppendMethod(invoke.getInvokedMethod())) {
+ // Unhandled definition.
+ return false;
+ }
+ worklist.addIfNotSeen(invoke.getReceiver());
+ }
+ break;
+
+ default:
+ // Unhandled definition.
+ return false;
+ }
+
+ // Analyze all users.
+ for (Instruction user : alias.uniqueUsers()) {
+ switch (user.opcode()) {
+ case INVOKE_DIRECT:
+ {
+ InvokeDirect invoke = user.asInvokeDirect();
+
+ // Only allow invokes where the string builder value is the receiver.
+ if (invoke.arguments().lastIndexOf(alias) > 0) {
+ return false;
+ }
+
+ // Only allow invoke-direct instructions that target the string builder constructor.
+ if (!stringBuilderMethods.isConstructorMethod(invoke.getInvokedMethod())) {
+ return false;
+ }
+ }
+ break;
+
+ case INVOKE_VIRTUAL:
+ {
+ InvokeVirtual invoke = user.asInvokeVirtual();
+
+ // Only allow invokes where the string builder value is the receiver.
+ if (invoke.arguments().lastIndexOf(alias) > 0) {
+ return false;
+ }
+
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+
+ // Allow calls to append(), but make sure to introduce the newly introduced alias,
+ // if append() has an out-value.
+ if (stringBuilderMethods.isAppendMethod(invokedMethod)) {
+ if (invoke.hasOutValue()) {
+ worklist.addIfNotSeen(invoke.outValue());
+ }
+ break;
+ }
+
+ // Allow calls to toString().
+ if (invokedMethod == stringBuilderMethods.toString) {
+ // Only allow unused StringBuilders.
+ if (invoke.hasOutValue() && invoke.outValue().hasNonDebugUsers()) {
+ return false;
+ }
+ break;
+ }
+
+ // Invoke to unhandled method, give up.
+ return false;
+ }
+
+ default:
+ // Unhandled user, give up.
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index 18ad3e6..c8d04ae 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -18,7 +18,7 @@
import com.android.tools.r8.ir.code.Value;
import java.util.Set;
-public class StringMethodOptimizer implements LibraryMethodModelCollection {
+public class StringMethodOptimizer extends StatelessLibraryMethodModelCollection {
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index cd9fe4e..a61662c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.NumberConversion;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
@@ -215,10 +216,9 @@
private Set<Value> findAllLocalBuilders() {
// During the first iteration, collect builders that are locally created.
// TODO(b/114002137): Make sure new-instance is followed by <init> before any other calls.
- for (Instruction instr : code.instructions()) {
- if (instr.isNewInstance()
- && optimizationConfiguration.isBuilderType(instr.asNewInstance().clazz)) {
- Value builder = instr.asNewInstance().dest();
+ for (NewInstance newInstance : code.<NewInstance>instructions(Instruction::isNewInstance)) {
+ if (optimizationConfiguration.isBuilderType(newInstance.clazz)) {
+ Value builder = newInstance.asNewInstance().dest();
assert !builderToStringCounts.containsKey(builder);
builderToStringCounts.put(builder, 0);
}
@@ -228,24 +228,21 @@
}
int concatenationCount = 0;
// During the second iteration, count builders' usage.
- for (Instruction instr : code.instructions()) {
- if (instr.isInvokeMethod()) {
- InvokeMethod invoke = instr.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (optimizationConfiguration.isAppendMethod(invokedMethod)) {
- concatenationCount++;
- // The analysis might be overwhelmed.
- if (concatenationCount > CONCATENATION_THRESHOLD) {
- return ImmutableSet.of();
- }
- } else if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
- assert invoke.arguments().size() == 1;
- Value receiver = invoke.getArgument(0).getAliasedValue();
- for (Value builder : collectAllLinkedBuilders(receiver)) {
- if (builderToStringCounts.containsKey(builder)) {
- int count = builderToStringCounts.getInt(builder);
- builderToStringCounts.put(builder, count + 1);
- }
+ for (InvokeMethod invoke : code.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (optimizationConfiguration.isAppendMethod(invokedMethod)) {
+ concatenationCount++;
+ // The analysis might be overwhelmed.
+ if (concatenationCount > CONCATENATION_THRESHOLD) {
+ return ImmutableSet.of();
+ }
+ } else if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
+ assert invoke.arguments().size() == 1;
+ Value receiver = invoke.getArgument(0).getAliasedValue();
+ for (Value builder : collectAllLinkedBuilders(receiver)) {
+ if (builderToStringCounts.containsKey(builder)) {
+ int count = builderToStringCounts.getInt(builder);
+ builderToStringCounts.put(builder, count + 1);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index fb8655a..239c409 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1587,6 +1587,10 @@
return intermediate || hasMinApi(AndroidApiLevel.L);
}
+ public boolean canUseJavaUtilObjects() {
+ return (isGeneratingClassFiles() && !cfToCfDesugar) || hasMinApi(AndroidApiLevel.K);
+ }
+
public boolean canUseRequireNonNull() {
return isGeneratingDex() && hasMinApi(AndroidApiLevel.K);
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 7f140a7..e1d4c9e 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1702,6 +1702,11 @@
return AndroidApiLevel.L;
}
+ public static boolean canUseJavaUtilObjects(TestParameters parameters) {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
+ }
+
public static boolean canUseRequireNonNull(TestParameters parameters) {
return parameters.isDexRuntime()
&& parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CannonicalizeWithInline.java b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
similarity index 61%
rename from src/test/java/com/android/tools/r8/debuginfo/CannonicalizeWithInline.java
rename to src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
index ff58696..395f29f 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/CannonicalizeWithInline.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debuginfo;
+import com.android.tools.r8.AssumeMayHaveSideEffects;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
@@ -15,7 +17,7 @@
import org.junit.Assert;
import org.junit.Test;
-public class CannonicalizeWithInline extends TestBase {
+public class CanonicalizeWithInline extends TestBase {
private int getNumberOfDebugInfos(Path file) throws IOException {
DexSection[] dexSections = DexParser.parseMapFrom(file);
@@ -28,25 +30,23 @@
}
@Test
- public void testCannonicalize() throws Exception {
- Class clazzA = ClassA.class;
- Class clazzB = ClassB.class;
+ public void testCanonicalize() throws Exception {
+ Class<?> clazzA = ClassA.class;
+ Class<?> clazzB = ClassB.class;
- R8TestCompileResult result = testForR8(Backend.DEX)
- .addProgramClasses(clazzA, clazzB)
- .addKeepRules(
- "-keepattributes SourceFile,LineNumberTable",
- "-keep class ** {\n" +
- "public void call(int);\n" +
- "}"
- )
- // String concatenation optimization will remove dead builders in foobar.
- .addOptionsModification(o -> o.enableStringConcatenationOptimization = false)
- .compile();
+ R8TestCompileResult result =
+ testForR8(Backend.DEX)
+ .addProgramClasses(clazzA, clazzB)
+ .addKeepRules(
+ "-keepattributes SourceFile,LineNumberTable",
+ "-keep class ** {\n" + "public void call(int);\n" + "}")
+ .enableInliningAnnotations()
+ .enableSideEffectAnnotations()
+ .compile();
Path classesPath = temp.getRoot().toPath();
result.app.write(classesPath, OutputMode.DexIndexed);
- int numberOfDebugInfos = getNumberOfDebugInfos(
- Paths.get(temp.getRoot().getCanonicalPath(), "classes.dex"));
+ int numberOfDebugInfos =
+ getNumberOfDebugInfos(Paths.get(temp.getRoot().getCanonicalPath(), "classes.dex"));
Assert.assertEquals(1, numberOfDebugInfos);
}
@@ -57,11 +57,17 @@
public static class ClassA {
public void call(int a) {
- foobar(a);
+ foobar(a);
}
private String foobar(int a) {
- String s = "aFoobar" + a;
+ return doSomething(a);
+ }
+
+ @AssumeMayHaveSideEffects
+ @NeverInline
+ private String doSomething(int a) {
+ String s = "bFoobar" + a;
return s;
}
}
@@ -73,6 +79,12 @@
}
private String foobar(int a) {
+ return doSomething(a);
+ }
+
+ @AssumeMayHaveSideEffects
+ @NeverInline
+ private String doSomething(int a) {
String s = "bFoobar" + a;
return s;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderFromCharSequenceWithAppendObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderFromCharSequenceWithAppendObjectTest.java
new file mode 100644
index 0000000..46dfe04
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderFromCharSequenceWithAppendObjectTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.string;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedStringBuilderFromCharSequenceWithAppendObjectTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnusedStringBuilderFromCharSequenceWithAppendObjectTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ // TODO(christofferqa): Should succeed with output;
+ // .assertSuccessWithOutputLines(
+ // "CustomCharSequence.length()",
+ // "CustomCharSequence.length()",
+ // "CustomCharSequence.length()",
+ // "CustomCharSequence.charAt(0)");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new StringBuilder(new CustomCharSequence());
+ }
+ }
+
+ static class CustomCharSequence implements CharSequence {
+
+ @Override
+ public int length() {
+ System.out.println("CustomCharSequence.length()");
+ return 1;
+ }
+
+ @Override
+ public char charAt(int i) {
+ if (i != 0) {
+ throw new RuntimeException();
+ }
+ System.out.println("CustomCharSequence.charAt(0)");
+ return 0;
+ }
+
+ @Override
+ public CharSequence subSequence(int i, int i1) {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendDefinitelyNullObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendDefinitelyNullObjectTest.java
new file mode 100644
index 0000000..f5f615d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendDefinitelyNullObjectTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.string;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.instantiatesClass;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedStringBuilderWithAppendDefinitelyNullObjectTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnusedStringBuilderWithAppendDefinitelyNullObjectTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
+ assertThat(mainMethod, not(instantiatesClass(StringBuilder.class)));
+ assertThat(mainMethod, not(invokesMethodWithName("toString")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Object o = null;
+ new StringBuilder().append(o).toString();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendMaybeNullObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendMaybeNullObjectTest.java
new file mode 100644
index 0000000..7d23755
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendMaybeNullObjectTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.string;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.instantiatesClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedStringBuilderWithAppendMaybeNullObjectTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnusedStringBuilderWithAppendMaybeNullObjectTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
+ assertThat(
+ mainMethod,
+ notIf(instantiatesClass(StringBuilder.class), canUseJavaUtilObjects(parameters)));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A a = System.currentTimeMillis() < 0 ? new A() : null;
+ new StringBuilder().append(a).toString();
+ }
+ }
+
+ static class A {
+
+ @Override
+ public String toString() {
+ return "A";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectSideEffectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectSideEffectTest.java
new file mode 100644
index 0000000..934dfd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectSideEffectTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.string;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.instantiatesClass;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedStringBuilderWithAppendObjectSideEffectTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnusedStringBuilderWithAppendObjectSideEffectTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
+ assertThat(mainMethod, not(instantiatesClass(StringBuilder.class)));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new StringBuilder().append(new A()).toString();
+ }
+ }
+
+ static class A {
+
+ @Override
+ public String toString() {
+ System.out.println("A");
+ return "A";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java
index 25918c8..72c3721 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java
@@ -4,12 +4,14 @@
package com.android.tools.r8.ir.optimize.string;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.instantiatesClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -36,10 +38,12 @@
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(
- inspector ->
- // TODO(b/174285670): StringBuilder should be removed.
- assertThat(
- inspector.clazz(Main.class).mainMethod(), invokesMethodWithName("append")))
+ inspector -> {
+ MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
+ assertThat(
+ mainMethod,
+ notIf(instantiatesClass(StringBuilder.class), canUseJavaUtilObjects(parameters)));
+ })
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithEmptyOutput();
}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index fa7b74e..986312c 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -73,6 +73,12 @@
}
@Override
+ public void replaceCurrentInstructionWithConstString(
+ AppView<?> appView, IRCode code, DexString value) {
+ throw new Unimplemented();
+ }
+
+ @Override
public void replaceCurrentInstructionWithStaticGet(
AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues) {
throw new Unimplemented();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index ed4d158..654f3a1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -44,6 +44,37 @@
};
}
+ public static Matcher<MethodSubject> instantiatesClass(Class<?> clazz) {
+ return instantiatesClass(clazz.getTypeName());
+ }
+
+ public static Matcher<MethodSubject> instantiatesClass(String clazz) {
+ return new TypeSafeMatcher<MethodSubject>() {
+ @Override
+ protected boolean matchesSafely(MethodSubject subject) {
+ if (!subject.isPresent()) {
+ return false;
+ }
+ if (!subject.getMethod().hasCode()) {
+ return false;
+ }
+ return subject
+ .streamInstructions()
+ .anyMatch(instruction -> instruction.isNewInstance(clazz));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("instantiates class `" + clazz + "`");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MethodSubject subject, Description description) {
+ description.appendText("method did not");
+ }
+ };
+ }
+
public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) {
if (!targetSubject.isPresent()) {
throw new IllegalArgumentException();