Model java.util.Objects methods
Fixes: 186082885
Change-Id: Iab93202d8fcaedf15a5c79fa5760387ac1b9711f
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 646fca7..acd8b41 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -555,6 +555,7 @@
public final RecordMembers recordMembers = new RecordMembers();
public final ShortMembers shortMembers = new ShortMembers();
public final StringMembers stringMembers = new StringMembers();
+ public final SupplierMembers supplierMembers = new SupplierMembers();
public final DoubleMembers doubleMembers = new DoubleMembers();
public final ThrowableMethods throwableMethods = new ThrowableMethods();
public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
@@ -1340,11 +1341,29 @@
public class ObjectsMethods {
+ public final DexMethod equals =
+ createMethod(objectsType, createProto(booleanType, objectType, objectType), "equals");
+ public final DexMethod hashCode =
+ createMethod(objectsType, createProto(intType, objectType), "hashCode");
+ public final DexMethod isNull =
+ createMethod(objectsType, createProto(booleanType, objectType), "isNull");
+ public final DexMethod nonNull =
+ createMethod(objectsType, createProto(booleanType, objectType), "nonNull");
public final DexMethod requireNonNull;
public final DexMethod requireNonNullWithMessage;
public final DexMethod requireNonNullWithMessageSupplier;
+ public final DexMethod requireNonNullElse =
+ createMethod(
+ objectsType, createProto(objectType, objectType, objectType), "requireNonNullElse");
+ public final DexMethod requireNonNullElseGet =
+ createMethod(
+ objectsType,
+ createProto(objectType, objectType, supplierType),
+ "requireNonNullElseGet");
public final DexMethod toStringWithObject =
createMethod(objectsType, createProto(stringType, objectType), "toString");
+ public final DexMethod toStringWithObjectAndNullDefault =
+ createMethod(objectsType, createProto(stringType, objectType, stringType), "toString");
private ObjectsMethods() {
DexString requireNonNullMethodName = createString("requireNonNull");
@@ -1365,7 +1384,13 @@
public boolean isRequireNonNullMethod(DexMethod method) {
return method == requireNonNull
|| method == requireNonNullWithMessage
- || method == requireNonNullWithMessageSupplier;
+ || method == requireNonNullWithMessageSupplier
+ || method == requireNonNullElse
+ || method == requireNonNullElseGet;
+ }
+
+ public boolean isToStringMethod(DexMethod method) {
+ return method == toStringWithObject || method == toStringWithObjectAndNullDefault;
}
public Iterable<DexMethod> requireNonNullMethods() {
@@ -1883,6 +1908,13 @@
}
}
+ public class SupplierMembers extends LibraryMembers {
+
+ public final DexMethod get = createMethod(supplierType, createProto(objectType), "get");
+
+ private SupplierMembers() {}
+ }
+
public class PolymorphicMethods {
private final DexProto signature = createProto(objectType, objectArrayType);
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index d0db14f..ba4d653 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -50,6 +50,10 @@
throw new Unreachable();
}
+ public byte byteAt(int index) {
+ return content[index];
+ }
+
/** DexString is a leaf item so we directly define its compareTo which avoids overhead. */
@Override
public int compareTo(DexString other) {
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 d0e0027..7bd9cf9 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
@@ -304,7 +304,7 @@
return true;
}
InvokeMethod replacement;
- if (appView.options().canUseRequireNonNull()) {
+ if (appView.options().canUseJavaUtilObjectsRequireNonNull()) {
DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
replacement = new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver));
} else {
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 32b1f29..14a7d45 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
@@ -107,10 +107,18 @@
void replaceCurrentInstructionWithConstClass(
AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
+ default void replaceCurrentInstructionWithConstFalse(IRCode code) {
+ replaceCurrentInstructionWithConstInt(code, 0);
+ }
+
void replaceCurrentInstructionWithConstInt(IRCode code, int value);
void replaceCurrentInstructionWithConstString(AppView<?> appView, IRCode code, DexString value);
+ default void replaceCurrentInstructionWithConstTrue(IRCode code) {
+ replaceCurrentInstructionWithConstInt(code, 1);
+ }
+
default void replaceCurrentInstructionWithConstString(
AppView<?> appView, IRCode code, String value) {
replaceCurrentInstructionWithConstString(
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index d076960..02c1ad0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -190,6 +190,10 @@
return getArgument(0);
}
+ public Value getLastArgument() {
+ return getArgument(arguments().size() - 1);
+ }
+
public int requiredArgumentRegisters() {
int registers = 0;
for (Value inValue : inValues) {
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 85d90e6..d582fdb 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
@@ -578,14 +578,24 @@
}
public void replaceUsers(Value newValue) {
+ replaceUsers(newValue, null);
+ }
+
+ public void replaceUsers(Value newValue, Set<Value> affectedValues) {
if (this == newValue) {
return;
}
for (Instruction user : uniqueUsers()) {
user.replaceValue(this, newValue);
+ if (affectedValues != null && user.hasOutValue()) {
+ affectedValues.add(user.outValue);
+ }
}
for (Phi user : uniquePhiUsers()) {
user.replaceOperand(this, newValue);
+ if (affectedValues != null) {
+ affectedValues.add(user);
+ }
}
if (debugData != null) {
for (Instruction user : debugData.users) {
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 32ec969..36d70df 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
@@ -305,7 +305,7 @@
if (invokedMethod == dexItemFactory.npeMethods.init) {
message = null;
} else if (invokedMethod == dexItemFactory.npeMethods.initWithMessage) {
- if (!appView.options().canUseRequireNonNull()) {
+ if (!appView.options().canUseJavaUtilObjectsRequireNonNull()) {
continue;
}
message = constructorCall.getArgument(1);
@@ -3348,7 +3348,7 @@
assert theIf == block.exit();
iterator.previous();
Instruction instruction;
- if (appView.options().canUseRequireNonNull()) {
+ if (appView.options().canUseJavaUtilObjectsRequireNonNull()) {
if (message != null) {
DexMethod requireNonNullMethod =
appView.dexItemFactory().objectsMethods.requireNonNullWithMessage;
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 e034587..41a298a 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
@@ -776,7 +776,7 @@
InstructionListIterator iterator = throwBlock.listIterator(code);
iterator.setInsertionPosition(invoke.getPosition());
- if (appView.options().canUseRequireNonNull()) {
+ if (appView.options().canUseJavaUtilObjectsRequireNonNull()) {
DexMethod requireNonNullMethod = appView.dexItemFactory().objectsMethods.requireNonNull;
iterator.add(new InvokeStatic(requireNonNullMethod, null, ImmutableList.of(receiver)));
} else {
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 831236b..08199da 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
@@ -8,12 +8,17 @@
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.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeElement;
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.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
import java.util.Set;
public class ObjectsMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -21,12 +26,14 @@
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
private final ObjectsMethods objectsMethods;
+ private final InternalOptions options;
ObjectsMethodOptimizer(AppView<?> appView) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
this.appView = appView;
this.dexItemFactory = dexItemFactory;
this.objectsMethods = dexItemFactory.objectsMethods;
+ this.options = appView.options();
}
@Override
@@ -41,24 +48,152 @@
InvokeMethod invoke,
DexClassAndMethod singleTarget,
Set<Value> affectedValues) {
- if (objectsMethods.isRequireNonNullMethod(singleTarget.getReference())) {
- optimizeRequireNonNull(instructionIterator, invoke, affectedValues);
- } else if (singleTarget.getReference() == objectsMethods.toStringWithObject) {
- optimizeToStringWithObject(code, instructionIterator, invoke, affectedValues);
+ DexMethod singleTargetReference = singleTarget.getReference();
+ switch (singleTargetReference.getName().byteAt(0)) {
+ case 'e':
+ if (singleTargetReference == objectsMethods.equals) {
+ optimizeEquals(code, instructionIterator, invoke);
+ }
+ break;
+ case 'h':
+ if (singleTargetReference == objectsMethods.hashCode) {
+ optimizeHashCode(code, instructionIterator, invoke);
+ }
+ break;
+ case 'i':
+ if (singleTargetReference == objectsMethods.isNull) {
+ optimizeIsNull(code, instructionIterator, invoke);
+ }
+ break;
+ case 'n':
+ if (singleTargetReference == objectsMethods.nonNull) {
+ optimizeNonNull(code, instructionIterator, invoke);
+ }
+ break;
+ case 'r':
+ if (objectsMethods.isRequireNonNullMethod(singleTargetReference)) {
+ optimizeRequireNonNull(instructionIterator, invoke, affectedValues, singleTarget);
+ }
+ break;
+ case 't':
+ if (objectsMethods.isToStringMethod(singleTargetReference)) {
+ optimizeToStringWithObject(
+ code, instructionIterator, invoke, affectedValues, singleTarget);
+ }
+ break;
+ default:
+ // Intentionally empty.
+ break;
+ }
+ }
+
+ private void optimizeEquals(
+ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+ Value aValue = invoke.getFirstArgument();
+ Value bValue = invoke.getLastArgument();
+ if (aValue.isAlwaysNull(appView)) {
+ // Optimize Objects.equals(null, b) into true if b is null, false if b is never null, and
+ // Objects.isNull(b) otherwise.
+ if (bValue.isAlwaysNull(appView)) {
+ instructionIterator.replaceCurrentInstructionWithConstTrue(code);
+ } else if (bValue.isNeverNull()) {
+ instructionIterator.replaceCurrentInstructionWithConstFalse(code);
+ } else if (options.canUseJavaUtilObjectsIsNull()) {
+ instructionIterator.replaceCurrentInstruction(
+ InvokeStatic.builder()
+ .setMethod(objectsMethods.isNull)
+ .setOutValue(invoke.outValue())
+ .setSingleArgument(bValue)
+ .build());
+ }
+ } else if (aValue.isNeverNull()) {
+ // Optimize Objects.equals(nonNull, b) into nonNull.equals(b).
+ instructionIterator.replaceCurrentInstruction(
+ InvokeVirtual.builder()
+ .setMethod(dexItemFactory.objectMembers.equals)
+ .setOutValue(invoke.outValue())
+ .setArguments(ImmutableList.of(aValue, bValue))
+ .build());
+ }
+ }
+
+ private void optimizeHashCode(
+ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+ Value inValue = invoke.getFirstArgument();
+ if (inValue.isAlwaysNull(appView)) {
+ // Optimize Objects.hashCode(null) into 0.
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
+ } else if (inValue.isNeverNull()) {
+ // Optimize Objects.hashCode(nonNull) into nonNull.hashCode().
+ instructionIterator.replaceCurrentInstruction(
+ InvokeVirtual.builder()
+ .setMethod(dexItemFactory.objectMembers.hashCode)
+ .setOutValue(invoke.outValue())
+ .setSingleArgument(inValue)
+ .build());
+ }
+ }
+
+ private void optimizeIsNull(
+ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+ Value inValue = invoke.getFirstArgument();
+ if (inValue.isAlwaysNull(appView)) {
+ // Optimize Objects.isNull(null) into true.
+ instructionIterator.replaceCurrentInstructionWithConstTrue(code);
+ } else if (inValue.isNeverNull()) {
+ // Optimize Objects.isNull(nonNull) into false.
+ instructionIterator.replaceCurrentInstructionWithConstFalse(code);
+ }
+ }
+
+ private void optimizeNonNull(
+ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+ Value inValue = invoke.getFirstArgument();
+ if (inValue.isAlwaysNull(appView)) {
+ // Optimize Objects.nonNull(null) into false.
+ instructionIterator.replaceCurrentInstructionWithConstFalse(code);
+ } else if (inValue.isNeverNull()) {
+ // Optimize Objects.nonNull(nonNull) into true.
+ instructionIterator.replaceCurrentInstructionWithConstTrue(code);
}
}
private void optimizeRequireNonNull(
- InstructionListIterator instructionIterator, InvokeMethod invoke, Set<Value> affectedValues) {
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ Set<Value> affectedValues,
+ DexClassAndMethod singleTarget) {
+ if (invoke.hasOutValue() && invoke.outValue().hasLocalInfo()) {
+ // Replacing the out-value with an in-value would change debug info.
+ return;
+ }
+
Value inValue = invoke.getFirstArgument();
- if (inValue.getType().isDefinitelyNotNull()) {
- Value outValue = invoke.outValue();
- if (outValue != null) {
- affectedValues.addAll(outValue.affectedValues());
- outValue.replaceUsers(inValue);
+ if (inValue.isNeverNull()) {
+ // Optimize Objects.requireNonNull*(nonNull, ...) into nonNull.
+ if (invoke.hasOutValue()) {
+ invoke.outValue().replaceUsers(inValue, affectedValues);
}
- // TODO(b/152853271): Debugging information is lost here (DebugLocalWrite may be required).
instructionIterator.removeOrReplaceByDebugLocalRead();
+ } else if (inValue.isAlwaysNull(appView)) {
+ if (singleTarget.getReference() == objectsMethods.requireNonNullElse) {
+ // Optimize Objects.requireNonNullElse(null, defaultObj) into defaultObj.
+ if (invoke.hasOutValue()) {
+ invoke.outValue().replaceUsers(invoke.getLastArgument(), affectedValues);
+ }
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ } else if (singleTarget.getReference() == objectsMethods.requireNonNullElseGet) {
+ // Optimize Objects.requireNonNullElseGet(null, supplier) into supplier.get().
+ if (invoke.hasOutValue()) {
+ invoke.outValue().replaceUsers(invoke.getLastArgument(), affectedValues);
+ }
+ instructionIterator.replaceCurrentInstruction(
+ InvokeVirtual.builder()
+ .setMethod(dexItemFactory.supplierMembers.get)
+ .setOutValue(invoke.outValue())
+ .setSingleArgument(invoke.getLastArgument())
+ .build());
+ }
}
}
@@ -66,15 +201,24 @@
IRCode code,
InstructionListIterator instructionIterator,
InvokeMethod invoke,
- Set<Value> affectedValues) {
+ Set<Value> affectedValues,
+ DexClassAndMethod singleTarget) {
Value object = invoke.getFirstArgument();
TypeElement type = object.getType();
// Optimize Objects.toString(null) into "null".
if (type.isDefinitelyNull()) {
- instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
- if (invoke.hasOutValue()) {
- affectedValues.addAll(invoke.outValue().affectedValues());
+ if (singleTarget.getReference() == objectsMethods.toStringWithObject) {
+ if (invoke.hasOutValue()) {
+ affectedValues.addAll(invoke.outValue().affectedValues());
+ }
+ instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
+ } else {
+ assert singleTarget.getReference() == objectsMethods.toStringWithObjectAndNullDefault;
+ if (invoke.hasOutValue()) {
+ invoke.outValue().replaceUsers(invoke.getLastArgument(), affectedValues);
+ }
+ instructionIterator.removeOrReplaceByDebugLocalRead();
}
return;
}
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 3416a5b..ff812b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1561,7 +1561,11 @@
return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
}
- public boolean canUseRequireNonNull() {
+ public boolean canUseJavaUtilObjectsIsNull() {
+ return isGeneratingDex() && hasMinApi(AndroidApiLevel.N);
+ }
+
+ public boolean canUseJavaUtilObjectsRequireNonNull() {
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 0577d2f..79bfbde 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1754,7 +1754,12 @@
|| parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
}
- public static boolean canUseRequireNonNull(TestParameters parameters) {
+ public static boolean canUseJavaUtilObjectsIsNull(TestParameters parameters) {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+ }
+
+ public static boolean canUseJavaUtilObjectsRequireNonNull(TestParameters parameters) {
return parameters.isDexRuntime()
&& parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index efddc67..4607423 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -526,7 +526,7 @@
// Android K methods.
objectsCompare("b", "a");
objectsDeepEquals(args, new Object());
- objectsEquals(args, new Object());
+ objectsEquals(System.currentTimeMillis() > 0 ? args : null, new Object());
objectsHash(1, 2);
objectsHashCode(4);
objectsRequireNonNull(System.currentTimeMillis() >= 0 ? null : new Object());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SyntheticInlineNullCheckPositionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SyntheticInlineNullCheckPositionTest.java
index 4eadc56..b5588bd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/SyntheticInlineNullCheckPositionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/SyntheticInlineNullCheckPositionTest.java
@@ -71,7 +71,7 @@
.assertFailureWithErrorThatThrows(NullPointerException.class)
.inspectStackTrace(
stackTrace -> {
- if (canUseRequireNonNull(parameters)) {
+ if (canUseJavaUtilObjectsIsNull(parameters)) {
assertThat(
stackTrace,
isSameExceptForSpecificLineNumber(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsEqualsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsEqualsTest.java
new file mode 100644
index 0000000..109c991
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsEqualsTest.java
@@ -0,0 +1,123 @@
+// Copyright (c) 2021, 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.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ObjectsEqualsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .build();
+ }
+
+ public ObjectsEqualsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject testNonNullArgumentsMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNonNullArguments");
+ assertThat(testNonNullArgumentsMethodSubject, isPresent());
+ assertThat(
+ testNonNullArgumentsMethodSubject,
+ not(invokesMethodWithHolderAndName("java.util.Objects", "equals")));
+ assertThat(
+ testNonNullArgumentsMethodSubject,
+ invokesMethodWithHolderAndName("java.lang.Object", "equals"));
+
+ MethodSubject testNullAndNullArgumentsMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNullAndNullArguments");
+ assertThat(testNullAndNullArgumentsMethodSubject, isPresent());
+ assertThat(
+ testNullAndNullArgumentsMethodSubject, not(invokesMethodWithName("equals")));
+
+ MethodSubject testNullAndNonNullArgumentsMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNullAndNonNullArguments");
+ assertThat(testNullAndNonNullArgumentsMethodSubject, isPresent());
+ assertThat(
+ testNullAndNonNullArgumentsMethodSubject, not(invokesMethodWithName("equals")));
+
+ MethodSubject testNullAndMaybeNullArgumentsMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNullAndMaybeNullArguments");
+ assertThat(testNullAndMaybeNullArgumentsMethodSubject, isPresent());
+ assertThat(
+ testNullAndMaybeNullArgumentsMethodSubject,
+ notIf(invokesMethodWithName("equals"), canUseJavaUtilObjectsIsNull(parameters)));
+ assertThat(
+ testNullAndMaybeNullArgumentsMethodSubject,
+ onlyIf(canUseJavaUtilObjectsIsNull(parameters), invokesMethodWithName("isNull")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("false", "true", "false", "false");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ testNonNullArguments();
+ testNullAndNullArguments();
+ testNullAndNonNullArguments();
+ testNullAndMaybeNullArguments();
+ }
+
+ @NeverInline
+ static void testNonNullArguments() {
+ System.out.println(Objects.equals(new Object(), new Object()));
+ }
+
+ @NeverInline
+ static void testNullAndNullArguments() {
+ System.out.println(Objects.equals(null, null));
+ }
+
+ @NeverInline
+ static void testNullAndNonNullArguments() {
+ System.out.println(Objects.equals(null, new Object()));
+ }
+
+ @NeverInline
+ static void testNullAndMaybeNullArguments() {
+ System.out.println(
+ Objects.equals(null, System.currentTimeMillis() > 0 ? new Object() : null));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsHashCodeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsHashCodeTest.java
new file mode 100644
index 0000000..8ed1eac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsHashCodeTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2021, 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.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ObjectsHashCodeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ObjectsHashCodeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject testNonNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNonNullArgument");
+ assertThat(testNonNullArgumentMethodSubject, isPresent());
+ assertThat(
+ testNonNullArgumentMethodSubject,
+ not(invokesMethodWithHolderAndName("java.util.Objects", "hashCode")));
+
+ MethodSubject testNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNullArgument");
+ assertThat(testNullArgumentMethodSubject, isPresent());
+ assertThat(testNullArgumentMethodSubject, not(invokesMethodWithName("hashCode")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42", "0");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ testNonNullArgument();
+ testNullArgument();
+ }
+
+ @NeverInline
+ static void testNonNullArgument() {
+ System.out.println(Objects.hashCode(new A()));
+ }
+
+ @NeverInline
+ static void testNullArgument() {
+ System.out.println(Objects.hashCode(null));
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ @Override
+ public int hashCode() {
+ return System.currentTimeMillis() > 0 ? 42 : -1;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsIsNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsIsNullTest.java
new file mode 100644
index 0000000..6064fec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsIsNullTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, 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.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ObjectsIsNullTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ObjectsIsNullTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject testNonNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNonNullArgument");
+ assertThat(testNonNullArgumentMethodSubject, isPresent());
+ assertThat(testNonNullArgumentMethodSubject, not(invokesMethodWithName("isNull")));
+
+ MethodSubject testNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNullArgument");
+ assertThat(testNullArgumentMethodSubject, isPresent());
+ assertThat(testNullArgumentMethodSubject, not(invokesMethodWithName("isNull")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("false", "true");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ testNonNullArgument();
+ testNullArgument();
+ }
+
+ @NeverInline
+ static void testNonNullArgument() {
+ System.out.println(Objects.isNull(new Object()));
+ }
+
+ @NeverInline
+ static void testNullArgument() {
+ System.out.println(Objects.isNull(null));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsNonNullTest.java
new file mode 100644
index 0000000..d4b4d35
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsNonNullTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, 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.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ObjectsNonNullTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ObjectsNonNullTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject testNonNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNonNullArgument");
+ assertThat(testNonNullArgumentMethodSubject, isPresent());
+ assertThat(testNonNullArgumentMethodSubject, not(invokesMethodWithName("nonNull")));
+
+ MethodSubject testNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNullArgument");
+ assertThat(testNullArgumentMethodSubject, isPresent());
+ assertThat(testNullArgumentMethodSubject, not(invokesMethodWithName("nonNull")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("true", "false");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ testNonNullArgument();
+ testNullArgument();
+ }
+
+ @NeverInline
+ static void testNonNullArgument() {
+ System.out.println(Objects.nonNull(new Object()));
+ }
+
+ @NeverInline
+ static void testNullArgument() {
+ System.out.println(Objects.nonNull(null));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseGetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseGetTest.java
new file mode 100644
index 0000000..e0a4c6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseGetTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, 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.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.function.Supplier;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ObjectsRequireNonNullElseGetTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+ .build();
+ }
+
+ public ObjectsRequireNonNullElseGetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getProgramClassFileData())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject testNonNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNonNullArgument");
+ assertThat(testNonNullArgumentMethodSubject, isPresent());
+ assertThat(
+ testNonNullArgumentMethodSubject,
+ not(invokesMethodWithName("requireNonNullElseGet")));
+
+ MethodSubject testNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNullArgument");
+ assertThat(testNullArgumentMethodSubject, isPresent());
+ assertThat(
+ testNullArgumentMethodSubject,
+ not(invokesMethodWithName("requireNonNullElseGet")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo", "Bar");
+ }
+
+ private byte[] getProgramClassFileData() throws IOException {
+ return transformer(Main.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(Mock.class), descriptor(Objects.class))
+ .transform();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ testNonNullArgument();
+ testNullArgument();
+ }
+
+ @NeverInline
+ static void testNonNullArgument() {
+ System.out.println(Mock.requireNonNullElseGet("Foo", null));
+ }
+
+ @NeverInline
+ static void testNullArgument() {
+ System.out.println(Mock.requireNonNullElseGet(null, () -> "Bar"));
+ }
+ }
+
+ // References to this class are rewritten to java.util.Objects by transformation.
+ static class Mock {
+
+ public static Object requireNonNullElseGet(Object obj, Supplier<?> supplier) {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseTest.java
new file mode 100644
index 0000000..39acb1e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsRequireNonNullElseTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, 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.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ObjectsRequireNonNullElseTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ObjectsRequireNonNullElseTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getProgramClassFileData())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject testNonNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNonNullArgument");
+ assertThat(testNonNullArgumentMethodSubject, isPresent());
+ assertThat(
+ testNonNullArgumentMethodSubject,
+ not(invokesMethodWithName("requireNonNullElse")));
+
+ MethodSubject testNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNullArgument");
+ assertThat(testNullArgumentMethodSubject, isPresent());
+ assertThat(
+ testNullArgumentMethodSubject, not(invokesMethodWithName("requireNonNullElse")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo", "Bar");
+ }
+
+ private byte[] getProgramClassFileData() throws IOException {
+ return transformer(Main.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(Mock.class), descriptor(Objects.class))
+ .transform();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ testNonNullArgument();
+ testNullArgument();
+ }
+
+ @NeverInline
+ static void testNonNullArgument() {
+ System.out.println(Mock.requireNonNullElse("Foo", ":-("));
+ }
+
+ @NeverInline
+ static void testNullArgument() {
+ System.out.println(Mock.requireNonNullElse(null, "Bar"));
+ }
+ }
+
+ // References to this class are rewritten to java.util.Objects by transformation.
+ static class Mock {
+
+ public static Object requireNonNullElse(Object obj, Object defaultObj) {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsToStringWithNullDefaultTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsToStringWithNullDefaultTest.java
new file mode 100644
index 0000000..fe974a5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/ObjectsToStringWithNullDefaultTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, 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.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ObjectsToStringWithNullDefaultTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimes()
+ .withDexRuntimes()
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+ .build();
+ }
+
+ public ObjectsToStringWithNullDefaultTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject testNonNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNonNullArgument");
+ assertThat(testNonNullArgumentMethodSubject, isPresent());
+ assertThat(testNonNullArgumentMethodSubject, not(invokesMethodWithName("toString")));
+
+ MethodSubject testNullArgumentMethodSubject =
+ mainClassSubject.uniqueMethodWithName("testNullArgument");
+ assertThat(testNullArgumentMethodSubject, isPresent());
+ assertThat(testNullArgumentMethodSubject, not(invokesMethodWithName("toString")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo", "Bar");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ testNonNullArgument();
+ testNullArgument();
+ }
+
+ @NeverInline
+ static void testNonNullArgument() {
+ System.out.println(Objects.toString("Foo", ":-("));
+ }
+
+ @NeverInline
+ static void testNullArgument() {
+ System.out.println(Objects.toString(null, "Bar"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
index f975e36..18618ca 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeInstanceFieldValueTest.java
@@ -70,7 +70,7 @@
MethodSubject mainMethodSubject = testClassSubject.mainMethod();
assertThat(mainMethodSubject, isPresent());
- if (canUseRequireNonNull(parameters)) {
+ if (canUseJavaUtilObjectsIsNull(parameters)) {
assertThat(mainMethodSubject, invokesMethodWithName("requireNonNull"));
} else {
assertThat(mainMethodSubject, invokesMethodWithName("getClass"));
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 2b57e91..10a68c1 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
@@ -149,6 +149,11 @@
};
}
+ public static Matcher<MethodSubject> invokesMethodWithHolderAndName(
+ String holderType, String name) {
+ return invokesMethod(null, holderType, name, null);
+ }
+
public static Matcher<MethodSubject> invokesMethodWithName(String name) {
return invokesMethod(null, null, name, null);
}