Regression test for inlining of an invoke-interface

Bug: 71629503
Change-Id: Ifd25bd11917e315443f28b76f87c86e771719618
diff --git a/src/test/examples/inlining_with_proxy/Main.java b/src/test/examples/inlining_with_proxy/Main.java
new file mode 100644
index 0000000..c658673
--- /dev/null
+++ b/src/test/examples/inlining_with_proxy/Main.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2018, 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 inlining_with_proxy;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+public class Main {
+
+  public static void main(String[] args) {
+    // We need to keep Bar so that Foo has a single implementation class.
+    Bar bar = new Bar();
+    Object proxy = createProxyOfFoo(bar);
+
+    Foo foo = (Foo) proxy;
+
+    // 1st call to foo: since we don't know if foo is null or not, we cannot inline this call.
+    foo.foo();
+
+    // Insert some code to make sure both invokes of foo are in separate basic blocks.
+    // TODO(shertz) this should no longer be required when our type analysis manages invokes in the
+    // same block.
+    if (args != null && args.length > 0) {
+      System.out.println(args[0]);
+    } else {
+      System.out.println("No args");
+    }
+
+    // 2nd call to foo: at this point we know that it is non-null (otherwise the previous call would
+    // have thrown a NPE and leaves this method). However we do not know the exact type of foo,
+    // despite of class Bar being the only implementation of Foo at compilation time. Indeed, the
+    // actual receiver type is the Proxy class we created above.
+    foo.foo();
+
+    // We insert a 3rd call so that the 'double-inlining' condition allows the inlining of the
+    // 2nd call.
+    foo.foo();
+  }
+
+  private static Object createProxyOfFoo(final Object obj) {
+    Object proxyInstance = Proxy
+        .newProxyInstance(Foo.class.getClassLoader(), new Class[]{Foo.class},
+            new InvocationHandler() {
+              @Override
+              public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
+                System.out.println("Invoke " + method + " through proxy");
+                return null;
+              }
+            });
+    System.out.println("Created proxy class " + proxyInstance.getClass().getName()
+        + " which is a runtime implementation of Foo in addition to " + obj.getClass().getName());
+    return proxyInstance;
+  }
+
+  interface Foo {
+    void foo();
+  }
+
+  // This is the ONLY implementation of Foo (except the one created at runtime with Proxy).
+  static class Bar implements Foo {
+
+    @Override
+    public void foo() {
+      System.out.println("Bar.foo");
+    }
+  }
+
+}
diff --git a/src/test/examples/inlining_with_proxy/keep-rules.txt b/src/test/examples/inlining_with_proxy/keep-rules.txt
new file mode 100644
index 0000000..d36a4a3
--- /dev/null
+++ b/src/test/examples/inlining_with_proxy/keep-rules.txt
@@ -0,0 +1,11 @@
+# Copyright (c) 2018, 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class * {
+  public static void main(...);
+}
+
+-dontobfuscate
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8StaticInlining.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java
similarity index 73%
rename from src/test/java/com/android/tools/r8/ir/optimize/R8StaticInlining.java
rename to src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java
index ca047e6..7011006 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8StaticInlining.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java
@@ -12,41 +12,31 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
-import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Collection;
 import org.junit.Assume;
+import org.junit.Ignore;
 import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 /**
  * This test verifies that semantic of class initialization is preserved when a static method
  * invocation is inlined.
  */
-@RunWith(Parameterized.class)
-public class R8StaticInlining extends TestBase {
+// TODO(shertz) add CF output
+public class R8InliningRegressionTests extends TestBase {
 
-  // TODO(shertz) add CF output
-  @Parameters(name = "{0}_{1}")
-  public static Collection<String[]> data() {
-    return ImmutableList.of(
-        new String[]{"staticinlining", "staticinlining.Main"}
-    );
-  }
-
-  private final String folder;
-  private final String mainClass;
-
-  public R8StaticInlining(String folder, String mainClass) {
-    this.folder = folder;
-    this.mainClass = mainClass;
+  @Test
+  public void testStaticInlining_b71524812() throws Exception {
+    buildAndTest("staticinlining", "staticinlining.Main");
   }
 
   @Test
-  public void testInliningOfStatic() throws Exception {
+  @Ignore("b/71629503")
+  public void testInterfaceInlining_b71629503() throws Exception {
+    buildAndTest("inlining_with_proxy", "inlining_with_proxy.Main");
+  }
+
+  private void buildAndTest(String folder, String mainClass) throws Exception {
     Assume.assumeTrue(ToolHelper.artSupported());
 
     Path proguardRules = Paths.get(ToolHelper.EXAMPLES_DIR, folder, "keep-rules.txt");