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