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