Handle MethodHandle call to non-default interface methods

We would check-fail when an invoke-super MethodHandle attempted to
invoke a non-default interface method. This changes the behavior to
correctly throw an IllegalAccessError.

Test: ./test.py --host
Bug: 142059356
Change-Id: I8c214ee589ba71d8ab0dd6434890a3f681031364
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index 5471d38..38bd611 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -20,6 +20,7 @@
 
 #include "class_root.h"
 #include "common_dex_operations.h"
+#include "common_throws.h"
 #include "interpreter/shadow_frame-inl.h"
 #include "jvalue-inl.h"
 #include "mirror/class-inl.h"
@@ -685,7 +686,14 @@
     if (referrer_class == declaring_class) {
       return target_method;
     }
-    if (!declaring_class->IsInterface()) {
+    if (declaring_class->IsInterface()) {
+      if (target_method->IsAbstract()) {
+        std::string msg =
+            "Method " + target_method->PrettyMethod() + " is abstract interface method!";
+        ThrowIllegalAccessException(msg.c_str());
+        return nullptr;
+      }
+    } else {
       ObjPtr<mirror::Class> super_class = referrer_class->GetSuperClass();
       uint16_t vtable_index = target_method->GetMethodIndex();
       DCHECK(super_class != nullptr);
diff --git a/test/956-methodhandles/expected.txt b/test/956-methodhandles/expected.txt
index a8b609b..206ab20 100644
--- a/test/956-methodhandles/expected.txt
+++ b/test/956-methodhandles/expected.txt
@@ -26,3 +26,14 @@
 Don't expect Hi now
 [3, 2, 1]
 [1, 2, 3]
+Trying to call public abstract void java.util.function.Consumer.accept(java.lang.Object)
+Called accept with foo
+Trying to call public default java.util.function.Consumer java.util.function.Consumer.andThen(java.util.function.Consumer)
+Trying to call public abstract void java.util.function.Consumer.accept(java.lang.Object)
+Called accept with bar
+and then bar
+Ignoring and then
+Got hello
+Got hello there
+Called and then with hello there
+Got expected IAE when invoke-special on an abstract interface method
diff --git a/test/956-methodhandles/src/Main.java b/test/956-methodhandles/src/Main.java
index 11d6ead..78f6db7 100644
--- a/test/956-methodhandles/src/Main.java
+++ b/test/956-methodhandles/src/Main.java
@@ -24,12 +24,14 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
-
+import java.util.function.Consumer;
 import other.Chatty;
 
 public class Main {
@@ -108,6 +110,7 @@
     testVariableArity_MethodHandles_bind();
     testRevealDirect();
     testReflectiveCalls();
+    testInterfaceSpecial();
   }
 
   public static void testfindSpecial_invokeSuperBehaviour() throws Throwable {
@@ -1780,4 +1783,73 @@
       }
     }
   }
+
+  public static void testInterfaceSpecial() throws Throwable {
+    final Method acceptMethod = Consumer.class.getDeclaredMethod("accept", Object.class);
+    final Method andThenMethod = Consumer.class.getDeclaredMethod("andThen", Consumer.class);
+    // Proxies
+    Consumer<Object> c = (Consumer<Object>)Proxy.newProxyInstance(
+        Main.class.getClassLoader(),
+        new Class<?>[] { Consumer.class },
+        (p, m, a) -> {
+          System.out.println("Trying to call " + m);
+          if (m.equals(andThenMethod)) {
+            List<Object> args = a == null ? Collections.EMPTY_LIST : Arrays.asList(a);
+            return MethodHandles.lookup()
+                                .findSpecial(Consumer.class,
+                                             m.getName(),
+                                             MethodType.methodType(m.getReturnType(),
+                                                                   m.getParameterTypes()),
+                                             p.getClass())
+                                .bindTo(p)
+                                .invokeWithArguments(args);
+          } else if (m.equals(acceptMethod)) {
+            System.out.println("Called accept with " + a[0]);
+          }
+          return null;
+        });
+    c.accept("foo");
+    Consumer<Object> c2 = c.andThen((Object o) -> { System.out.println("and then " + o); });
+    c2.accept("bar");
+
+    // Non-proxies
+    Consumer<Object> c3 = new Consumer() {
+      public void accept(Object o) {
+        System.out.println("Got " + o);
+      }
+      @Override
+      public Consumer<Object> andThen(Consumer c) {
+        System.out.println("Ignoring and then");
+        return this;
+      }
+    };
+    Consumer<Object> c4 = c3.andThen((x) -> { throw new Error("Failed"); });
+    c4.accept("hello");
+    Consumer<Object> andthen = (Object o) -> { System.out.println("Called and then with " + o);};
+    Consumer<Object> c5 =
+        (Consumer<Object>)MethodHandles.lookup()
+                                       .findSpecial(Consumer.class,
+                                                    andThenMethod.getName(),
+                                                    MethodType.methodType(
+                                                          andThenMethod.getReturnType(),
+                                                          andThenMethod.getParameterTypes()),
+                                                    c3.getClass())
+                                       .bindTo(c3)
+                                       .invoke(andthen);
+    c5.accept("hello there");
+
+    // Failures
+    MethodHandle abstract_target =
+        MethodHandles.lookup()
+                    .findSpecial(Consumer.class,
+                                 acceptMethod.getName(),
+                                 MethodType.methodType(acceptMethod.getReturnType(),
+                                                       acceptMethod.getParameterTypes()),
+                                 c3.getClass());
+    try {
+      abstract_target.invoke(c3, "hello");
+    } catch (IllegalAccessException e) {
+      System.out.println("Got expected IAE when invoke-special on an abstract interface method");
+    }
+  }
 }