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);
   }