Remove Objects#requireNonNull for definitely non-null reference.
Bug: 124246610
Change-Id: Id8e068ec9d79c5af65ccef94b3864b195132dd18
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 55bc508..977c90b 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
@@ -292,6 +292,13 @@
debugData = null;
}
+ public boolean hasSameOrNoLocal(Value other) {
+ assert other != null;
+ return hasLocalInfo()
+ ? other.getLocalInfo() == this.getLocalInfo()
+ : !other.hasLocalInfo();
+ }
+
public List<Instruction> getDebugLocalStarts() {
if (debugData == null) {
return Collections.emptyList();
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 30712e6..f7baf3e 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
@@ -48,6 +48,7 @@
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsageBuilder;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
@@ -1632,10 +1633,30 @@
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
- if (invoke.outValue() != null && !invoke.outValue().hasLocalInfo()) {
+ Value outValue = invoke.outValue();
+ // TODO(b/124246610): extend to other variants that receive error messages or supplier.
+ if (invoke.getInvokedMethod() == dexItemFactory.objectsMethods.requireNonNull) {
+ Value obj = invoke.arguments().get(0);
+ if ((outValue == null && obj.hasLocalInfo())
+ || (outValue != null && !obj.hasSameOrNoLocal(outValue))) {
+ continue;
+ }
+ Nullability nullability = obj.getTypeLattice().nullability();
+ if (nullability.isDefinitelyNotNull()) {
+ if (outValue != null) {
+ outValue.replaceUsers(obj);
+ needToNarrowValues.addAll(outValue.affectedValues());
+ }
+ iterator.removeOrReplaceByDebugLocalRead();
+ } else if (nullability.isDefinitelyNull()) {
+ // TODO(b/124246610): throw NPE.
+ // Refactor UninstantiatedTypeOptimization#replaceCurrentInstructionWithThrowNull
+ // and move it to iterator.
+ }
+ } else if (outValue != null && !outValue.hasLocalInfo()) {
if (libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod())) {
if (checkArgumentType(invoke, 0)) {
- invoke.outValue().replaceUsers(invoke.arguments().get(0));
+ outValue.replaceUsers(invoke.arguments().get(0));
invoke.setOutValue(null);
}
} else if (appInfoWithLiveness != null) {
@@ -1650,7 +1671,6 @@
// Replace the out value of the invoke with the argument and ignore the out value.
if (argumentIndex >= 0 && checkArgumentType(invoke, argumentIndex)) {
Value argument = invoke.arguments().get(argumentIndex);
- Value outValue = invoke.outValue();
assert outValue.verifyCompatible(argument.outType());
if (argument.getTypeLattice().lessThanOrEqual(
outValue.getTypeLattice(), appInfo)) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
new file mode 100644
index 0000000..7a2a84e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2019, 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.DexMethod;
+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.google.common.collect.Streams;
+import java.util.Objects;
+import org.junit.Test;
+
+class ObjectsRequireNonNullTestMain {
+
+ static class Foo {
+ @NeverInline
+ void bar() {
+ System.out.println("Foo::bar");
+ }
+
+ @NeverInline
+ @Override
+ public String toString() {
+ return "Foo::toString";
+ }
+ }
+
+ @NeverInline
+ static void unknownArg(Foo foo) {
+ // It's unclear the argument is definitely null or not null.
+ Foo checked = Objects.requireNonNull(foo);
+ checked.bar();
+ }
+
+ public static void main(String[] args) {
+ Foo instance = new Foo();
+ // Not removable in debug mode.
+ Object nonNull = Objects.requireNonNull(instance);
+ System.out.println(nonNull);
+ // Removable because associated locals are changed while type casting.
+ Foo checked = Objects.requireNonNull(instance);
+ checked.bar();
+
+ unknownArg(instance);
+ try {
+ unknownArg(null);
+ } catch (NullPointerException npe) {
+ System.out.println("Expected NPE");
+ }
+ }
+}
+
+public class ObjectsRequireNonNullTest extends TestBase {
+ private static final String JAVA_OUTPUT = StringUtils.lines(
+ "Foo::toString",
+ "Foo::bar",
+ "Foo::bar",
+ "Expected NPE"
+ );
+ private static final Class<?> MAIN = ObjectsRequireNonNullTestMain.class;
+
+ @Test
+ public void testJvmOutput() throws Exception {
+ testForJvm().addTestClasspath().run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ private static boolean isObjectsRequireNonNull(DexMethod method) {
+ return method.toSourceString().equals(
+ "java.lang.Object java.util.Objects.requireNonNull(java.lang.Object)");
+ }
+
+ private long countObjectsRequireNonNull(MethodSubject method) {
+ return Streams.stream(method.iterateInstructions(instructionSubject -> {
+ if (instructionSubject.isInvoke()) {
+ return isObjectsRequireNonNull(instructionSubject.getMethod());
+ }
+ return false;
+ })).count();
+ }
+
+ private void test(TestRunResult result, int expectedCount) throws Exception {
+ CodeInspector codeInspector = result.inspector();
+ ClassSubject mainClass = codeInspector.clazz(MAIN);
+ MethodSubject mainMethod = mainClass.mainMethod();
+ assertThat(mainMethod, isPresent());
+ long count = countObjectsRequireNonNull(mainMethod);
+ assertEquals(expectedCount, count);
+
+ MethodSubject unknownArg = mainClass.uniqueMethodWithName("unknownArg");
+ assertThat(unknownArg, isPresent());
+ // Due to the nullable argument, requireNonNull should remain.
+ assertEquals(1, countObjectsRequireNonNull(unknownArg));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ TestRunResult result = testForD8()
+ .debug()
+ .addProgramClassesAndInnerClasses(MAIN)
+ .run(MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 2);
+
+ result = testForD8()
+ .release()
+ .addProgramClassesAndInnerClasses(MAIN)
+ .run(MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 0);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ // CF disables move result optimization.
+ TestRunResult result = testForR8(Backend.DEX)
+ .addProgramClassesAndInnerClasses(MAIN)
+ .enableInliningAnnotations()
+ .addKeepMainRule(MAIN)
+ .run(MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 0);
+ }
+}