Regression test for b/143628636

Bug: 143628636
Bug: 143684659
Bug: 143686005
Change-Id: Iad2263cb621b44123878e733eed563572c57ea54
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 12c444f..25387f6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -68,6 +68,7 @@
   // Set to true to run compilation in a single thread and without randomly shuffling the input.
   // This makes life easier when running R8 in a debugger.
   public static final boolean DETERMINISTIC_DEBUGGING = false;
+
   public enum LineNumberOptimization {
     OFF,
     ON
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java b/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java
new file mode 100644
index 0000000..945e1af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/DefaultInterfaceIssue143628636Test.java
@@ -0,0 +1,133 @@
+// 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 com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Reproduction for issue b/143628636.
+ *
+ * <p>There are a lot of conditions that need to be in place to trigger this issue. The root issue
+ * is resolution incorrectly pruning out a valid candidate. However, mostly code will not even get
+ * to that place as a single target is typically found prior to it, and then, if the incorrect
+ * pruning of the lookup targets takes place, that needs to further cause invalid call site
+ * information to be propagated, which in turn will cause inlining to inline a method where it
+ * should not. It is exceedingly unlikely that this test will continue to be a regression test as
+ * any one of the above aspects could and likely will be changed in the code base.
+ */
+@RunWith(Parameterized.class)
+public class DefaultInterfaceIssue143628636Test extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public DefaultInterfaceIssue143628636Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableInliningAnnotations()
+        .enableClassInliningAnnotations()
+        .enableMergeAnnotations()
+        .addInnerClasses(DefaultInterfaceIssue143628636Test.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(I.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("2", "5");
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  public interface A {
+
+    @NeverInline
+    default void f(
+        // This parameter is needed otherwise the info recording bails out. (See b/143684659).
+        int z) {
+      // In the end the issue will manifest as a class-cast exception because B.g() is inlined here
+      // due to the incorrect conclusion that the only possible receiver type is B.
+      System.out.println(g() + z);
+    }
+
+    int g();
+  }
+
+  // This intermediate interface is needed to cause lookupInterfaceTargets to return null as I
+  // is pinned (why does it return null for a pinned holder is questionable, filed b/143686005).
+  // The return of null, will cause the outer lookup to hit a different lookup case where the
+  // refined receiver (here I) will cause the non-subtype method A.f to be pruned.
+  public interface I extends A {}
+
+  // Make sure this class and the call to h() are never eliminated. It is the *partial* info
+  // propagated from h() to f() that results in incorrect call-site optimization info.
+  @NeverMerge
+  @NeverClassInline
+  public static class B implements A {
+    public final int x;
+
+    public B(int x) {
+      this.x = x;
+    }
+
+    @Override
+    public int g() {
+      return x;
+    }
+
+    @NeverInline
+    public void h() {
+      // This will lookup A.f and propagate that the receiver is known to be B.
+      f(x);
+    }
+  }
+
+  // Make sure this class and the call to h() are never eliminated. It is the *missing* info
+  // propagated from h() to f() that results in incorrect call-site optimization info.
+  @NeverMerge
+  @NeverClassInline
+  public static class C implements I {
+    public final I i;
+    public final int y;
+
+    public C(I i, int y) {
+      this.i = i;
+      this.y = y;
+    }
+
+    @Override
+    public int g() {
+      return y;
+    }
+
+    @NeverInline
+    public void h() {
+      // Due to the refined receiver type I used here, the lookup targets will omit A.f !
+      // Thus the info propagation does not propagate that I (and thus C) may be a receiver type.
+      i.f(y);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new B(args.length + 1).h();
+      new C(args.length == 42 ? null : new C(null, args.length + 2), args.length + 3).h();
+    }
+  }
+}