Provide an option to prefer library desugaring over backporting

This is an intermediate step using the existing configuration file
format for methods on java.util.Objects with additional filtering.

This CL includes tests which should also work when the
configuration file format is changed.

The change to the configuration file is not supposed to be done to
the externally published version.

Bug: 174453232
Bug: 174840626
Change-Id: Ia5f7d931f7693a667a2d79510e6df2eeccb3e919
diff --git a/src/library_desugar/desugar_jdk_libs_alternative_3.json b/src/library_desugar/desugar_jdk_libs_alternative_3.json
index 012d6ce..cb7f478 100644
--- a/src/library_desugar/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/desugar_jdk_libs_alternative_3.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 3,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs_alternative_3",
-  "version": "1.1.1",
+  "version": "1.2.0",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "support_all_callbacks_from_library": false,
@@ -188,6 +188,7 @@
         "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
         "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
         "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
+        "java.util.Objects": "j$.util.Objects",
         "java.util.Optional": "j$.util.Optional",
         "java.util.PrimitiveIterator": "j$.util.PrimitiveIterator",
         "java.util.Spliterator": "j$.util.Spliterator",
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index e58c7d6..d6adc3c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -193,10 +193,13 @@
 
   private static final class RewritableMethods {
 
+    private final AppView<?> appView;
+
     // Map backported method to a provider for creating the actual target method (with code).
     private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
 
     RewritableMethods(InternalOptions options, AppView<?> appView) {
+      this.appView = appView;
 
       if (!options.shouldBackportMethods()) {
         return;
@@ -205,7 +208,7 @@
       DexItemFactory factory = options.itemFactory;
 
       if (options.minApiLevel < AndroidApiLevel.K.getLevel()) {
-        initializeAndroidKMethodProviders(factory);
+        initializeAndroidKMethodProviders(factory, appView);
       }
       if (options.minApiLevel < AndroidApiLevel.N.getLevel()) {
         initializeAndroidNMethodProviders(factory);
@@ -246,7 +249,7 @@
       rewritable.keySet().forEach(consumer);
     }
 
-    private void initializeAndroidKMethodProviders(DexItemFactory factory) {
+    private void initializeAndroidKMethodProviders(DexItemFactory factory, AppView<?> appView) {
       // Byte
       DexType type = factory.boxedByteType;
       // int Byte.compare(byte a, byte b)
@@ -1305,6 +1308,21 @@
     }
 
     private void addProvider(MethodProvider generator) {
+      if (appView.options().desugaredLibraryConfiguration.isSupported(generator.method, appView)) {
+        // TODO(b/174453232): Remove this after the configuration file format has bee updated
+        // with the "rewrite_method" section.
+        if (generator.method.getHolderType() == appView.dexItemFactory().objectsType) {
+          // Still backport the new API level 30 methods.
+          String methodName = generator.method.getName().toString();
+          if (!methodName.equals("requireNonNullElse")
+              && !methodName.equals("requireNonNullElseGet")
+              && !methodName.equals("checkIndex")
+              && !methodName.equals("checkFromToIndex")
+              && !methodName.equals("checkFromIndexSize")) {
+            return;
+          }
+        }
+      }
       MethodProvider replaced = rewritable.put(generator.method, generator);
       assert replaced == null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 62cbc2e..f5ff0b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -163,6 +163,11 @@
   public Map<DexType, DexType> getEmulateLibraryInterface() {
     return emulateLibraryInterface;
   }
+
+  public boolean isSupported(DexMethod method, AppView<?> appView) {
+    return prefixRewritingMapper.hasRewrittenType(method.getHolderType(), appView);
+  }
+
   // If the method is retargeted, answers the retargeted method, else null.
   public DexMethod retargetMethod(DexEncodedMethod method, AppView<?> appView) {
     Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.getName());
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
new file mode 100644
index 0000000..a9590a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -0,0 +1,730 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+import org.hamcrest.Matcher;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class ObjectsTest extends DesugaredLibraryTestBase implements Opcodes {
+
+  private final TestParameters parameters;
+  private final boolean libraryDesugarJavaUtilObjects;
+  private final boolean shrinkDesugaredLibrary = false;
+  private final Path androidJar;
+
+  @Parameters(name = "{0}, libraryDesugarJavaUtilObjects: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values());
+  }
+
+  public ObjectsTest(TestParameters parameters, boolean libraryDesugarJavaUtilObjects) {
+    this.parameters = parameters;
+    this.libraryDesugarJavaUtilObjects = libraryDesugarJavaUtilObjects;
+    // Using desugared library require a compile SDK of 26 or higher.
+    this.androidJar =
+        ToolHelper.getAndroidJar(Ordered.max(parameters.getApiLevel(), AndroidApiLevel.O));
+  }
+
+  DesugaredLibraryConfiguration desugaredLibraryConfiguration(
+      InternalOptions options, boolean libraryCompilation, TestParameters parameters) {
+    return new DesugaredLibraryConfigurationParser(
+            options.dexItemFactory(),
+            options.reporter,
+            libraryCompilation,
+            parameters.getApiLevel().getLevel())
+        .parse(
+            StringResource.fromFile(
+                libraryDesugarJavaUtilObjects
+                    ? ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING_ALTERNATIVE_3
+                    : ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+  }
+
+  private void configurationForProgramCompilation(InternalOptions options) {
+    options.desugaredLibraryConfiguration =
+        desugaredLibraryConfiguration(options, false, parameters);
+  }
+
+  private void configurationForLibraryCompilation(InternalOptions options) {
+    options.desugaredLibraryConfiguration =
+        desugaredLibraryConfiguration(options, true, parameters);
+  }
+
+  private Matcher<MethodSubject> invokesObjectsCompare(String holder) {
+    return invokesMethod(
+        "int",
+        holder,
+        "compare",
+        ImmutableList.of("java.lang.Object", "java.lang.Object", "java.util.Comparator"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsEquals(String holder) {
+    return invokesMethod(
+        "boolean", holder, "equals", ImmutableList.of("java.lang.Object", "java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsDeepEquals(String holder) {
+    return invokesMethod(
+        "boolean", holder, "deepEquals", ImmutableList.of("java.lang.Object", "java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsHash(String holder) {
+    return invokesMethod("int", holder, "hash", ImmutableList.of("java.lang.Object[]"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsHashCode(String holder) {
+    return invokesMethod("int", holder, "hashCode", ImmutableList.of("java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsRequireNonNull(String holder) {
+    return invokesMethod(
+        "java.lang.Object", holder, "requireNonNull", ImmutableList.of("java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsRequireNonNullWithMessage(String holder) {
+    return invokesMethod(
+        "java.lang.Object",
+        holder,
+        "requireNonNull",
+        ImmutableList.of("java.lang.Object", "java.lang.String"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsRequireNonNullWithSupplier(
+      String holder, String Supplier) {
+    return invokesMethod(
+        "java.lang.Object",
+        holder,
+        "requireNonNull",
+        ImmutableList.of("java.lang.Object", Supplier));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsToString(String holder) {
+    return invokesMethod(
+        "java.lang.String", holder, "toString", ImmutableList.of("java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsToStringWithNullDefault(String holder) {
+    return invokesMethod(
+        "java.lang.String",
+        holder,
+        "toString",
+        ImmutableList.of("java.lang.Object", "java.lang.String"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsIsNull(String holder) {
+    return invokesMethod("boolean", holder, "isNull", ImmutableList.of("java.lang.Object"));
+  }
+
+  private Matcher<MethodSubject> invokesObjectsNonNull(String holder) {
+    return invokesMethod("boolean", holder, "nonNull", ImmutableList.of("java.lang.Object"));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClass = inspector.clazz(TestClass.class);
+    assertThat(testClass, isPresent());
+
+    // Objects.equals as added in Android K, so when backporting, this is only backported below K.
+    // However, for library desugaring, the desugaring of Objects.equals happens all the way up to
+    // Android M, as that is grouped with other methods like Objects.requireNonNull which was
+    // added in Android N.
+    boolean invokeJavaUtilObjects =
+        !libraryDesugarJavaUtilObjects
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K)
+            || (libraryDesugarJavaUtilObjects
+                && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+    boolean invokeJDollarUtilObjects =
+        libraryDesugarJavaUtilObjects && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+    boolean invokeJavaUtilObjectsWithSupplier =
+        !libraryDesugarJavaUtilObjects || !parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+    boolean invokeJDollarUtilObjectsWithSupplier =
+        libraryDesugarJavaUtilObjects && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsCompare"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsCompare("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsCompare"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsCompare("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsDeepEquals"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsDeepEquals("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsDeepEquals"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsDeepEquals("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsEquals"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsEquals("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsEquals"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsEquals("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsHash"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsHash("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsHash"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsHash("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsHashCode"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsHashCode("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsHashCode"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsHashCode("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNull"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsRequireNonNull("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNull"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsRequireNonNull("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"),
+        onlyIf(
+            invokeJavaUtilObjects, invokesObjectsRequireNonNullWithMessage("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"),
+        onlyIf(
+            invokeJDollarUtilObjects, invokesObjectsRequireNonNullWithMessage("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNullWithSupplier"),
+        onlyIf(
+            invokeJavaUtilObjectsWithSupplier,
+            invokesObjectsRequireNonNullWithSupplier(
+                "java.util.Objects", "java.util.function.Supplier")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsRequireNonNullWithSupplier"),
+        onlyIf(
+            invokeJDollarUtilObjectsWithSupplier,
+            invokesObjectsRequireNonNullWithSupplier(
+                "j$.util.Objects", "j$.util.function.Supplier")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsToString"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsToString("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsToString"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsToString("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsToStringWithNullDefault"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsToStringWithNullDefault("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsToStringWithNullDefault"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsToStringWithNullDefault("j$.util.Objects")));
+
+    invokeJavaUtilObjects = parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsIsNull"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsIsNull("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsIsNull"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsIsNull("j$.util.Objects")));
+
+    assertThat(
+        testClass.uniqueMethodWithName("objectsNonNull"),
+        onlyIf(invokeJavaUtilObjects, invokesObjectsNonNull("java.util.Objects")));
+    assertThat(
+        testClass.uniqueMethodWithName("objectsNonNull"),
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsNonNull("j$.util.Objects")));
+  }
+
+  @Test
+  public void testD8Cf() throws Exception {
+    // Adjust API level if running on JDK 8. The java.util.Objects methods added in
+    // Android R where added in JDK 9, so setting the the API level to Android P will backport
+    // these methods for JDK 8.
+    AndroidApiLevel apiLevel = parameters.getApiLevel();
+    if (parameters.getRuntime().isCf()
+        && parameters.getRuntime().asCf().getVm() == CfVm.JDK8
+        && apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.R)) {
+      apiLevel = AndroidApiLevel.P;
+    }
+
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addLibraryFiles(androidJar)
+            .addOptionsModification(this::configurationForProgramCompilation)
+            .addInnerClasses(ObjectsTest.class)
+            .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
+            .setMinApi(apiLevel)
+            .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+
+    if (parameters.getRuntime().isDex()) {
+      // Collection keep rules is only implemented in the DEX writer.
+      String desugaredLibraryKeepRules = keepRuleConsumer.get();
+      if (desugaredLibraryKeepRules != null) {
+        assertEquals(0, desugaredLibraryKeepRules.length());
+        desugaredLibraryKeepRules = "-keep class * { *; }";
+      }
+
+      // Convert to DEX without desugaring and run.
+      testForD8()
+          .addLibraryFiles(androidJar)
+          .addProgramFiles(jar)
+          .setMinApi(apiLevel)
+          .disableDesugaring()
+          .compile()
+          .addDesugaredCoreLibraryRunClassPath(
+              (apiLevel_, keepRules, shrink) ->
+                  buildDesugaredLibrary(
+                      apiLevel_,
+                      keepRules,
+                      shrink,
+                      ImmutableList.of(),
+                      this::configurationForLibraryCompilation),
+              parameters.getApiLevel(),
+              desugaredLibraryKeepRules,
+              shrinkDesugaredLibrary)
+          .run(
+              parameters.getRuntime(),
+              TestClass.class,
+              Boolean.toString(libraryDesugarJavaUtilObjects))
+          .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+    } else {
+      // Build the desugared library in class file format.
+      Path desugaredLib =
+          getDesugaredLibraryInCF(parameters, this::configurationForLibraryCompilation);
+
+      // Run on the JVM with desuagred library on classpath.
+      testForJvm()
+          .addProgramFiles(jar)
+          .addRunClasspathFiles(desugaredLib)
+          .run(
+              parameters.getRuntime(),
+              TestClass.class,
+              Boolean.toString(libraryDesugarJavaUtilObjects))
+          .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+    }
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addLibraryFiles(androidJar)
+        .addOptionsModification(this::configurationForProgramCompilation)
+        .addInnerClasses(ObjectsTest.class)
+        .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (apiLevel, keepRules, shrink) ->
+                buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    this::configurationForLibraryCompilation),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .inspect(this::inspect)
+        .run(
+            parameters.getRuntime(),
+            TestClass.class,
+            Boolean.toString(libraryDesugarJavaUtilObjects))
+        .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(parameters.getRuntime().isDex());
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(androidJar)
+        .addOptionsModification(this::configurationForProgramCompilation)
+        .addInnerClasses(ObjectsTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addProgramClassFileData(dumpAndroidRUtilsObjectsMethods())
+        .enableInliningAnnotations()
+        .noMinification()
+        .addKeepRules("-keep class AndroidRUtilsObjectsMethods { *; }")
+        .addKeepRules("-neverinline class AndroidRUtilsObjectsMethods { *; }")
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            (apiLevel, keepRules, shrink) ->
+                buildDesugaredLibrary(
+                    apiLevel,
+                    keepRules,
+                    shrink,
+                    ImmutableList.of(),
+                    this::configurationForLibraryCompilation),
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .inspect(this::inspect)
+        .run(
+            parameters.getRuntime(),
+            TestClass.class,
+            Boolean.toString(libraryDesugarJavaUtilObjects))
+        .assertSuccessWithOutput(expectedOutput(libraryDesugarJavaUtilObjects));
+  }
+
+  private String expectedOutput(boolean objectsRequireNonNullWithSupplierSupported) {
+    return StringUtils.lines(
+        "1",
+        "true",
+        "true",
+        Objects.toString(Objects.hash(1, 2)),
+        "4",
+        "NPE",
+        "Was null",
+        objectsRequireNonNullWithSupplierSupported
+            ? "Supplier said was null"
+            : "Not supported (b/174840626)",
+        "5",
+        "6",
+        "true",
+        "false",
+        "1",
+        "2",
+        "3",
+        "4");
+  }
+
+  static class TestClass {
+    @NeverInline
+    private static void objectsCompare(String s1, String s2) {
+      Comparator<String> stringsNullLast =
+          (o1, o2) -> {
+            if (o1 == null) {
+              return o2 == null ? 0 : 1;
+            }
+            return o2 == null ? -1 : o1.compareTo(o2);
+          };
+      System.out.println(Objects.compare(s1, s2, stringsNullLast));
+    }
+
+    @NeverInline
+    private static void objectsDeepEquals(Object o1, Object o2) {
+      System.out.println(Objects.deepEquals(o1, o2));
+    }
+
+    @NeverInline
+    private static void objectsEquals(Object o1, Object o2) {
+      System.out.println(Objects.equals(o1, o2));
+    }
+
+    @NeverInline
+    private static void objectsHash(Object o1, Object o2) {
+      System.out.println(Objects.hash(o1, o2));
+    }
+
+    @NeverInline
+    private static void objectsHashCode(Object o) {
+      System.out.println(Objects.hashCode(o));
+    }
+
+    @NeverInline
+    private static void objectsRequireNonNull(Object o) {
+      try {
+        System.out.println(Objects.requireNonNull(o));
+      } catch (NullPointerException e) {
+        System.out.println("NPE");
+      }
+    }
+
+    @NeverInline
+    private static void objectsRequireNonNullWithMessage(Object o, String message) {
+      try {
+        System.out.println(Objects.requireNonNull(o, message));
+      } catch (NullPointerException e) {
+        System.out.println(e.getMessage());
+      }
+    }
+
+    @NeverInline
+    private static void objectsRequireNonNullWithSupplier(
+        Object o, Supplier<String> messageSupplier) {
+      try {
+        System.out.println(Objects.requireNonNull(o, messageSupplier));
+      } catch (NullPointerException e) {
+        System.out.println(e.getMessage());
+      }
+    }
+
+    @NeverInline
+    private static void objectsToString(Object o) {
+      System.out.println(Objects.toString(o));
+    }
+
+    @NeverInline
+    private static void objectsToStringWithNullDefault(Object o, String nullDefault) {
+      System.out.println(Objects.toString(o, nullDefault));
+    }
+
+    @NeverInline
+    private static void objectsIsNull(Object o) {
+      System.out.println(Objects.isNull(o));
+    }
+
+    @NeverInline
+    private static void objectsNonNull(Object o) {
+      System.out.println(Objects.nonNull(o));
+    }
+
+    public static void main(String[] args) throws Exception {
+      boolean objectsRequireNonNullWithSupplierSupported = Boolean.parseBoolean(args[0]);
+      // Android K methods.
+      objectsCompare("b", "a");
+      objectsDeepEquals(args, args);
+      objectsEquals(args, args);
+      objectsHash(1, 2);
+      objectsHashCode(4);
+      objectsRequireNonNull(null);
+      objectsRequireNonNullWithMessage(null, "Was null");
+      if (objectsRequireNonNullWithSupplierSupported) {
+        objectsRequireNonNullWithSupplier(null, () -> "Supplier said was null");
+      } else {
+        System.out.println("Not supported (b/174840626)");
+      }
+      objectsToString("5");
+      objectsToStringWithNullDefault(null, "6");
+
+      // Android N methods.
+      objectsIsNull(null);
+      objectsNonNull(null);
+
+      // Android R methods.
+      Class<?> c = Class.forName("AndroidRUtilsObjectsMethods");
+      c.getDeclaredMethod("checkFromIndexSize", int.class, int.class, int.class)
+          .invoke(null, 1, 2, 10);
+      c.getDeclaredMethod("checkFromToIndex", int.class, int.class, int.class)
+          .invoke(null, 2, 4, 10);
+      c.getDeclaredMethod("checkIndex", int.class, int.class).invoke(null, 3, 10);
+      c.getDeclaredMethod("requireNonNullElse", Object.class, Object.class).invoke(null, null, 4);
+      // TODO(b/174840626) Also support requireNonNullElseGet.
+    }
+  }
+
+  /*
+    Dump below is from this source:
+
+    import java.util.function.Supplier;
+    import java.util.Objects;
+
+    public class AndroidRUtilsObjectsMethods {
+      public static void checkFromIndexSize(int fromIndex, int size, int length) {
+        System.out.println(Objects.checkFromIndexSize(fromIndex, size, length));
+      }
+      public static void checkFromToIndex(int fromIndex, int toIndex, int length) {
+        System.out.println(Objects.checkFromToIndex(fromIndex, toIndex, length));
+      }
+      public static void checkIndex(int index, int length) {
+        System.out.println(Objects.checkIndex(index, length));
+      }
+      public static <T> void requireNonNullElse(T obj, T defaultObj) {
+        System.out.println(Objects.requireNonNullElse(obj, defaultObj));
+      }
+      public static <T> void requireNonNullElseGet(T obj, Supplier<? extends T> supplier) {
+        System.out.println(Objects.requireNonNullElse(obj, supplier));
+      }
+    }
+
+    This is added as a dump as it use APIs which are only abailable from JDK 9.
+  */
+  public static byte[] dumpAndroidRUtilsObjectsMethods() throws Exception {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V9, ACC_PUBLIC | ACC_SUPER, "AndroidRUtilsObjectsMethods", null, "java/lang/Object", null);
+
+    classWriter.visitSource("AndroidRUtilsObjectsMethods.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(3, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC, "checkFromIndexSize", "(III)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(5, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ILOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitVarInsn(ILOAD, 2);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "java/util/Objects", "checkFromIndexSize", "(III)I", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(6, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(4, 3);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC, "checkFromToIndex", "(III)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(8, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ILOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitVarInsn(ILOAD, 2);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "java/util/Objects", "checkFromToIndex", "(III)I", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(9, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(4, 3);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "checkIndex", "(II)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(11, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ILOAD, 0);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "java/util/Objects", "checkIndex", "(II)I", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(12, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC,
+              "requireNonNullElse",
+              "(Ljava/lang/Object;Ljava/lang/Object;)V",
+              "<T:Ljava/lang/Object;>(TT;TT;)V",
+              null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(14, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "java/util/Objects",
+          "requireNonNullElse",
+          "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+          false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(15, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 2);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC,
+              "requireNonNullElseGet",
+              "(Ljava/lang/Object;Ljava/util/function/Supplier;)V",
+              "<T:Ljava/lang/Object;>(TT;Ljava/util/function/Supplier<+TT;>;)V",
+              null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(18, label0);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "java/util/Objects",
+          "requireNonNullElse",
+          "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
+          false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(19, label1);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(3, 2);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}