Reproduce b/139432507: double-inliner misses simple inlining candidates.

If a certain method is double-inlining candidate (by calling a
double-inlining target) but itself is an inlining candidate as a single
caller, that method is inlined into the caller while its body still has
potentially inlinable method call.

Double-inliner, later, reprocesses those double callers, which has been
already inlined into the single caller. It should either reprocess that
single caller or allow double-inlining target to be inlined in the
beginning.

Bug: 139432507
Change-Id: I4005bb5bc499f9e0444c00e7af3b63c3fb722307
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index f0dd1c1..8123f4f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1094,14 +1094,24 @@
   }
 
   protected long countCall(MethodSubject method, String className, String methodName) {
-    return Streams.stream(method.iterateInstructions(instructionSubject -> {
+    return method.streamInstructions().filter(instructionSubject -> {
       if (instructionSubject.isInvoke()) {
         DexMethod invokedMethod = instructionSubject.getMethod();
         return invokedMethod.holder.toString().contains(className)
             && invokedMethod.name.toString().contains(methodName);
       }
       return false;
-    })).count();
+    }).count();
+  }
+
+  protected long countCall(MethodSubject method, String methodName) {
+    return method.streamInstructions().filter(instructionSubject -> {
+      if (instructionSubject.isInvoke()) {
+        DexMethod invokedMethod = instructionSubject.getMethod();
+        return invokedMethod.name.toString().contains(methodName);
+      }
+      return false;
+    }).count();
   }
 
   public enum MinifyMode {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index c974e78..99cd060 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -157,6 +157,13 @@
     return self();
   }
 
+  public T allowAccessModification(boolean allowAccessModification) {
+    if (allowAccessModification) {
+      return addKeepRules("-allowaccessmodification");
+    }
+    return self();
+  }
+
   public T addKeepAttributes(String... attributes) {
     return addKeepRules("-keepattributes " + String.join(",", attributes));
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningNullCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningNullCheckTest.java
new file mode 100644
index 0000000..5c9eb22
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/DoubleInliningNullCheckTest.java
@@ -0,0 +1,89 @@
+// 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.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DoubleInliningNullCheckTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public DoubleInliningNullCheckTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DoubleInliningNullCheckTest.class)
+        .addKeepMainRule(TestClass.class)
+        .noMinification()
+        .setMinApi(parameters.getRuntime())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true")
+        .inspect(codeInspector -> {
+          ClassSubject main = codeInspector.clazz(TestClass.class);
+          assertThat(main, isPresent());
+          MethodSubject mainMethod = main.mainMethod();
+          assertThat(mainMethod, isPresent());
+          // TODO(b/139432507): can be 0 if it's inlined to contains and indexOf.
+          assertEquals(2, countCall(mainMethod, "checkParameterIsNotNull"));
+        });
+  }
+
+  static class TestClass {
+    public static void main(String... args) {
+      String[] array = new String[] { "1", "2", "3" };
+      System.out.println(ArrayUtil.contains(array, "3"));
+    }
+  }
+
+  static class ArrayUtil {
+    public static void throwParameterIsNull(String paramName) {
+      throw new NullPointerException(paramName);
+    }
+
+    // Has double callers, indexOf and contains
+    public static void checkParameterIsNotNull(Object param, String paramName) {
+      if (param == null) {
+        throwParameterIsNull(paramName);
+      }
+    }
+
+    // Has single caller, contains
+    public static int indexOf(Object[] array, Object subject) {
+      checkParameterIsNotNull(array, "array");
+      for (int i = 0; i < array.length; i++) {
+        if (array[i].equals(subject)) {
+          return i;
+        }
+      }
+      return -1;
+    }
+
+    // Has single caller, TestClass#main
+    public static boolean contains(Object[] array, Object subject) {
+      checkParameterIsNotNull(array, "array");
+      return indexOf(array, subject) != -1;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
new file mode 100644
index 0000000..c055493
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -0,0 +1,107 @@
+// 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.kotlin;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collection;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+
+public class KotlinIntrinsicsInlineTest extends AbstractR8KotlinTestBase {
+  private static final String FOLDER = "intrinsics";
+  private static final String MAIN = FOLDER + ".InlineKt";
+
+  @Parameterized.Parameters(name = "{0} target: {1}, allowAccessModification: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().build(),
+        KotlinTargetVersion.values(),
+        BooleanUtils.values());
+  }
+
+  private final TestParameters parameters;
+
+  public KotlinIntrinsicsInlineTest(
+      TestParameters parameters,
+      KotlinTargetVersion targetVersion,
+      boolean allowAccessModification) {
+    super(targetVersion, allowAccessModification);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void b139432507() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(getKotlinJarFile(FOLDER))
+        .addKeepRules(StringUtils.lines(
+            "-keepclasseswithmembers class " + MAIN + "{",
+            "  public static *** *(...);",
+            "}"))
+        .allowAccessModification(allowAccessModification)
+        .noMinification()
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(inspector -> {
+          ClassSubject main = inspector.clazz(MAIN);
+          assertThat(main, isPresent());
+
+          // Note that isSupported itself has a parameter whose null check would be inlined
+          // with -allowaccessmodification.
+          MethodSubject isSupported = main.uniqueMethodWithName("isSupported");
+          assertThat(isSupported, isPresent());
+          assertEquals(
+              allowAccessModification ? 0 : 1,
+              countCall(isSupported, "checkParameterIsNotNull"));
+
+          // In general cases, null check won't be invoked only once or twice, hence no subtle
+          // situation in double inlining.
+          MethodSubject containsArray = main.uniqueMethodWithName("containsArray");
+          assertThat(containsArray, isPresent());
+          assertEquals(0, countCall(containsArray, "checkParameterIsNotNull"));
+        });
+  }
+
+  @Test
+  public void b139432507_isSupported() throws Exception {
+    testSingle("isSupported");
+  }
+
+  @Ignore("b/139432507: due to double inlining, checkParameterIsNotNull is not fully inlined.")
+  @Test
+  public void b139432507_containsArray() throws Exception {
+    testSingle("containsArray");
+  }
+
+  private void testSingle(String methodName) throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(getKotlinJarFile(FOLDER))
+        .addKeepRules(StringUtils.lines(
+            "-keepclasseswithmembers class " + MAIN + "{",
+            "  public static *** " + methodName + "(...);",
+            "}"))
+        .allowAccessModification(allowAccessModification)
+        .noMinification()
+        .setMinApi(parameters.getRuntime())
+        .compile()
+        .inspect(inspector -> {
+          ClassSubject main = inspector.clazz(MAIN);
+          assertThat(main, isPresent());
+
+          MethodSubject method = main.uniqueMethodWithName(methodName);
+          assertThat(method, isPresent());
+          assertEquals(
+              allowAccessModification, countCall(method, "checkParameterIsNotNull") == 0);
+        });
+  }
+}
diff --git a/src/test/kotlinR8TestResources/intrinsics/inline.kt b/src/test/kotlinR8TestResources/intrinsics/inline.kt
new file mode 100644
index 0000000..3234dea
--- /dev/null
+++ b/src/test/kotlinR8TestResources/intrinsics/inline.kt
@@ -0,0 +1,12 @@
+// 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 intrinsics
+
+fun isSupported(flag: String): Boolean {
+  return flag in arrayOf("--foo", "--bar", "--baz")
+}
+
+fun containsArray() {
+  println("1" in arrayOf("1", "2", "3"))
+}