Backport Objects.requireNonNull with a Supplier

Bug: 190633902
Change-Id: I63db398f4bf6a1fe87b071f27b3e1d0bc8c5e22a
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index b4b0d5a..0a23a51 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -352,7 +352,7 @@
 
   @Override
   public void print(CfConstNull constNull) {
-    throw new Unimplemented(constNull.getClass().getSimpleName());
+    printNewInstruction("CfConstNull");
   }
 
   @Override
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 a708509..7591a68 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
@@ -202,6 +202,12 @@
         initializeStreamMethodProviders(factory);
       }
 
+      if (appView.rewritePrefix.hasRewrittenType(factory.supplierType, appView)) {
+        // TODO(b/191188594): Consider adding the Objects method from R here, or instead
+        //  rely on desugared library to support them.
+        initializeObjectsMethodProviders(factory);
+      }
+
       // These are currently not implemented at any API level in Android.
       initializeJava9MethodProviders(factory);
       initializeJava10MethodProviders(factory);
@@ -1336,6 +1342,19 @@
           new MethodGenerator(method, BackportedMethods::StreamMethods_ofNullable, "ofNullable"));
     }
 
+    private void initializeObjectsMethodProviders(DexItemFactory factory) {
+      // Objects
+      DexType type = factory.objectsType;
+
+      // Objects.requireNonNull(Object, Supplier)
+      DexString name = factory.createString("requireNonNull");
+      DexProto proto =
+          factory.createProto(factory.objectType, factory.objectType, factory.supplierType);
+      DexMethod method = factory.createMethod(type, proto, name);
+      addProvider(
+          new MethodGenerator(method, BackportedMethods::ObjectsMethods_requireNonNullSupplier));
+    }
+
     private void addProvider(MethodProvider generator) {
       MethodProvider replaced = rewritable.put(generator.method, generator);
       assert replaced == null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 3f7a705..9291e1c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// 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.
 
@@ -14,6 +14,7 @@
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfCmp;
+import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFrame;
@@ -6803,6 +6804,91 @@
         ImmutableList.of());
   }
 
+  public static CfCode ObjectsMethods_requireNonNullSupplier(
+      InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        3,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfIf(If.Type.NE, ValueType.OBJECT, label6),
+            label1,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfIf(If.Type.EQ, ValueType.OBJECT, label3),
+            label2,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/util/function/Supplier;"),
+                    options.itemFactory.createProto(options.itemFactory.objectType),
+                    options.itemFactory.createString("get")),
+                true),
+            new CfCheckCast(options.itemFactory.stringType),
+            new CfGoto(label4),
+            label3,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(
+                          options.itemFactory.createType("Ljava/util/function/Supplier;"))
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfConstNull(),
+            label4,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(
+                          options.itemFactory.createType("Ljava/util/function/Supplier;"))
+                    }),
+                new ArrayDeque<>(
+                    Arrays.asList(FrameType.initialized(options.itemFactory.stringType)))),
+            new CfStore(ValueType.OBJECT, 2),
+            label5,
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.voidType, options.itemFactory.stringType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label6,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(
+                          options.itemFactory.createType("Ljava/util/function/Supplier;"))
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfReturn(ValueType.OBJECT),
+            label7),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode ObjectsMethods_toString(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RequiredNonNullWithSupplierTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RequiredNonNullWithSupplierTest.java
index 6e0e8c9..2918a7f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RequiredNonNullWithSupplierTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RequiredNonNullWithSupplierTest.java
@@ -6,9 +6,10 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import java.util.Objects;
-import org.junit.Assume;
+import java.util.function.Supplier;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -16,13 +17,15 @@
 @RunWith(Parameterized.class)
 public class RequiredNonNullWithSupplierTest extends DesugaredLibraryTestBase {
 
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("SuppliedString2", "OK");
+
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
 
   @Parameterized.Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
   public static List<Object[]> data() {
     return buildParameters(
-        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public RequiredNonNullWithSupplierTest(
@@ -33,7 +36,13 @@
 
   @Test
   public void testRequiredNonNullWithSupplierTest() throws Exception {
-    Assume.assumeFalse("TODO(b/190633902): Fix the wrapper issue", true);
+    if (parameters.isCfRuntime()) {
+      testForJvm()
+          .addInnerClasses(RequiredNonNullWithSupplierTest.class)
+          .run(parameters.getRuntime(), Executor.class)
+          .assertSuccessWithOutput(EXPECTED_OUTPUT);
+      return;
+    }
     KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
     testForD8()
         .addInnerClasses(RequiredNonNullWithSupplierTest.class)
@@ -46,7 +55,7 @@
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutputLines("SuppliedString2");
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   static class Executor {
@@ -56,9 +65,19 @@
       Objects.requireNonNull(o, () -> "SuppliedString");
       try {
         Objects.requireNonNull(null, () -> "SuppliedString2");
+        throw new AssertionError("Unexpected");
       } catch (NullPointerException e) {
         System.out.println(e.getMessage());
       }
+      try {
+        Objects.requireNonNull(null, (Supplier<String>) null);
+        throw new AssertionError("Unexpected");
+      } catch (NullPointerException e) {
+        // Normally we would want to print the exception message, but some ART versions have a bug
+        // where they erroneously calls supplier.get() on a null reference which produces the NPE
+        // but with an ART-defined message. See b/147419222.
+        System.out.println("OK");
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 2b2bdc6..94aa27c 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -75,7 +75,7 @@
 
   @Override
   protected int getYear() {
-    return 2020;
+    return 2021;
   }
 
   private static CfInstruction rewriteToJava9API(
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethods.java
index 97e896e..566d8f2 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/ObjectsMethods.java
@@ -71,6 +71,18 @@
     return obj;
   }
 
+  public static <T> T requireNonNullSupplier(T obj, Supplier<String> messageSupplier) {
+    if (obj == null) {
+      // While calling `messageSupplier.get()` unconditionally would produce the correct behavior,
+      // some ART versions add an exception message to seemingly-unintended null dereferences along
+      // the lines of "Attempted to invoke interface method Supplier.get() on a null reference"
+      // which we don't want to expose as the reference implementation has a null message.
+      String message = messageSupplier != null ? messageSupplier.get() : null;
+      throw new NullPointerException(message);
+    }
+    return obj;
+  }
+
   public static <T> T requireNonNullElse(T obj, T defaultObj) {
     if (obj != null) return obj;
     return Objects.requireNonNull(defaultObj, "defaultObj");