8200167: Validate more special case invocations
authordholmes
Mon, 30 Apr 2018 21:56:54 -0400
changeset 49935 2ace90aec488
parent 49934 44839fbb20db
child 49936 4da7dce7e2bf
child 56503 bd559ca9899d
8200167: Validate more special case invocations Reviewed-by: acorn, vlivanov, dholmes Contributed-by: John Rose <john.rose@oracle.com>, Vladimir Ivanov <vladimir.x.ivanov@oracle.com>, Tobias Hartmann <tobias.hartmann@oracle.com>
src/hotspot/share/c1/c1_Canonicalizer.cpp
src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java
src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
src/java.base/share/classes/java/lang/invoke/LambdaForm.java
src/java.base/share/classes/java/lang/invoke/MethodHandles.java
src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java
test/jdk/java/lang/invoke/I4Special.jcod
test/jdk/java/lang/invoke/SpecialInterfaceCall.java
--- a/src/hotspot/share/c1/c1_Canonicalizer.cpp	Mon Apr 30 18:10:24 2018 -0700
+++ b/src/hotspot/share/c1/c1_Canonicalizer.cpp	Mon Apr 30 21:56:54 2018 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -648,7 +648,7 @@
 void Canonicalizer::do_NewObjectArray (NewObjectArray*  x) {}
 void Canonicalizer::do_NewMultiArray  (NewMultiArray*   x) {}
 void Canonicalizer::do_CheckCast      (CheckCast*       x) {
-  if (x->klass()->is_loaded()) {
+  if (x->klass()->is_loaded() && !x->is_invokespecial_receiver_check()) {
     Value obj = x->obj();
     ciType* klass = obj->exact_type();
     if (klass == NULL) klass = obj->declared_type();
--- a/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java	Mon Apr 30 18:10:24 2018 -0700
+++ b/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java	Mon Apr 30 21:56:54 2018 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -72,26 +72,30 @@
     }
 
     // Factory methods:
-    static DirectMethodHandle make(byte refKind, Class<?> receiver, MemberName member) {
+    static DirectMethodHandle make(byte refKind, Class<?> refc, MemberName member, Class<?> callerClass) {
         MethodType mtype = member.getMethodOrFieldType();
         if (!member.isStatic()) {
-            if (!member.getDeclaringClass().isAssignableFrom(receiver) || member.isConstructor())
+            if (!member.getDeclaringClass().isAssignableFrom(refc) || member.isConstructor())
                 throw new InternalError(member.toString());
-            mtype = mtype.insertParameterTypes(0, receiver);
+            mtype = mtype.insertParameterTypes(0, refc);
         }
         if (!member.isField()) {
             switch (refKind) {
                 case REF_invokeSpecial: {
                     member = member.asSpecial();
-                    LambdaForm lform = preparedLambdaForm(member);
-                    return new Special(mtype, lform, member);
+                    LambdaForm lform = preparedLambdaForm(member, callerClass);
+                    Class<?> checkClass = refc;  // Class to use for receiver type check
+                    if (callerClass != null) {
+                        checkClass = callerClass;  // potentially strengthen to caller class
+                    }
+                    return new Special(mtype, lform, member, checkClass);
                 }
                 case REF_invokeInterface: {
-                    LambdaForm lform = preparedLambdaForm(member);
-                    return new Interface(mtype, lform, member, receiver);
+                    LambdaForm lform = preparedLambdaForm(member, callerClass);
+                    return new Interface(mtype, lform, member, refc);
                 }
                 default: {
-                    LambdaForm lform = preparedLambdaForm(member);
+                    LambdaForm lform = preparedLambdaForm(member, callerClass);
                     return new DirectMethodHandle(mtype, lform, member);
                 }
             }
@@ -108,11 +112,11 @@
             }
         }
     }
-    static DirectMethodHandle make(Class<?> receiver, MemberName member) {
+    static DirectMethodHandle make(Class<?> refc, MemberName member) {
         byte refKind = member.getReferenceKind();
         if (refKind == REF_invokeSpecial)
             refKind =  REF_invokeVirtual;
-        return make(refKind, receiver, member);
+        return make(refKind, refc, member, null /* no callerClass context */);
     }
     static DirectMethodHandle make(MemberName member) {
         if (member.isConstructor())
@@ -161,7 +165,7 @@
      * Cache and share this structure among all methods with
      * the same basicType and refKind.
      */
-    private static LambdaForm preparedLambdaForm(MemberName m) {
+    private static LambdaForm preparedLambdaForm(MemberName m, Class<?> callerClass) {
         assert(m.isInvocable()) : m;  // call preparedFieldLambdaForm instead
         MethodType mtype = m.getInvocationType().basicType();
         assert(!m.isMethodHandleInvoke()) : m;
@@ -179,6 +183,9 @@
             preparedLambdaForm(mtype, which);
             which = LF_INVSTATIC_INIT;
         }
+        if (which == LF_INVSPECIAL && callerClass != null && callerClass.isInterface()) {
+            which = LF_INVSPECIAL_IFC;
+        }
         LambdaForm lform = preparedLambdaForm(mtype, which);
         maybeCompile(lform, m);
         assert(lform.methodType().dropParameterTypes(0, 1)
@@ -187,6 +194,10 @@
         return lform;
     }
 
+    private static LambdaForm preparedLambdaForm(MemberName m) {
+        return preparedLambdaForm(m, null);
+    }
+
     private static LambdaForm preparedLambdaForm(MethodType mtype, int which) {
         LambdaForm lform = mtype.form().cachedLambdaForm(which);
         if (lform != null)  return lform;
@@ -197,13 +208,16 @@
     static LambdaForm makePreparedLambdaForm(MethodType mtype, int which) {
         boolean needsInit = (which == LF_INVSTATIC_INIT);
         boolean doesAlloc = (which == LF_NEWINVSPECIAL);
-        boolean needsReceiverCheck = (which == LF_INVINTERFACE);
+        boolean needsReceiverCheck = (which == LF_INVINTERFACE ||
+                                      which == LF_INVSPECIAL_IFC);
+
         String linkerName;
         LambdaForm.Kind kind;
         switch (which) {
         case LF_INVVIRTUAL:    linkerName = "linkToVirtual";   kind = DIRECT_INVOKE_VIRTUAL;     break;
         case LF_INVSTATIC:     linkerName = "linkToStatic";    kind = DIRECT_INVOKE_STATIC;      break;
         case LF_INVSTATIC_INIT:linkerName = "linkToStatic";    kind = DIRECT_INVOKE_STATIC_INIT; break;
+        case LF_INVSPECIAL_IFC:linkerName = "linkToSpecial";   kind = DIRECT_INVOKE_SPECIAL_IFC; break;
         case LF_INVSPECIAL:    linkerName = "linkToSpecial";   kind = DIRECT_INVOKE_SPECIAL;     break;
         case LF_INVINTERFACE:  linkerName = "linkToInterface"; kind = DIRECT_INVOKE_INTERFACE;   break;
         case LF_NEWINVSPECIAL: linkerName = "linkToSpecial";   kind = DIRECT_NEW_INVOKE_SPECIAL; break;
@@ -376,8 +390,10 @@
 
     /** This subclass represents invokespecial instructions. */
     static class Special extends DirectMethodHandle {
-        private Special(MethodType mtype, LambdaForm form, MemberName member) {
+        private final Class<?> caller;
+        private Special(MethodType mtype, LambdaForm form, MemberName member, Class<?> caller) {
             super(mtype, form, member);
+            this.caller = caller;
         }
         @Override
         boolean isInvokeSpecial() {
@@ -385,7 +401,15 @@
         }
         @Override
         MethodHandle copyWith(MethodType mt, LambdaForm lf) {
-            return new Special(mt, lf, member);
+            return new Special(mt, lf, member, caller);
+        }
+        Object checkReceiver(Object recv) {
+            if (!caller.isInstance(recv)) {
+                String msg = String.format("Receiver class %s is not a subclass of caller class %s",
+                                           recv.getClass().getName(), caller.getName());
+                throw new IncompatibleClassChangeError(msg);
+            }
+            return recv;
         }
     }
 
@@ -401,17 +425,23 @@
         MethodHandle copyWith(MethodType mt, LambdaForm lf) {
             return new Interface(mt, lf, member, refc);
         }
-
+        @Override
         Object checkReceiver(Object recv) {
             if (!refc.isInstance(recv)) {
-                String msg = String.format("Class %s does not implement the requested interface %s",
-                        recv.getClass().getName(), refc.getName());
+                String msg = String.format("Receiver class %s does not implement the requested interface %s",
+                                           recv.getClass().getName(), refc.getName());
                 throw new IncompatibleClassChangeError(msg);
             }
             return recv;
         }
     }
 
+    /** Used for interface receiver type checks, by Interface and Special modes. */
+    Object checkReceiver(Object recv) {
+        throw new InternalError("Should only be invoked on a subclass");
+    }
+
+
     /** This subclass handles constructor references. */
     static class Constructor extends DirectMethodHandle {
         final MemberName initMethod;
@@ -823,10 +853,10 @@
                             MemberName.getFactory()
                                     .resolveOrFail(REF_getField, member, DirectMethodHandle.class, NoSuchMethodException.class));
                 case NF_checkReceiver:
-                    member = new MemberName(Interface.class, "checkReceiver", OBJ_OBJ_TYPE, REF_invokeVirtual);
+                    member = new MemberName(DirectMethodHandle.class, "checkReceiver", OBJ_OBJ_TYPE, REF_invokeVirtual);
                     return new NamedFunction(
                         MemberName.getFactory()
-                            .resolveOrFail(REF_invokeVirtual, member, Interface.class, NoSuchMethodException.class));
+                            .resolveOrFail(REF_invokeVirtual, member, DirectMethodHandle.class, NoSuchMethodException.class));
                 default:
                     throw newInternalError("Unknown function: " + func);
             }
--- a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java	Mon Apr 30 18:10:24 2018 -0700
+++ b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java	Mon Apr 30 21:56:54 2018 -0400
@@ -674,6 +674,7 @@
             case DIRECT_NEW_INVOKE_SPECIAL: // fall-through
             case DIRECT_INVOKE_INTERFACE:   // fall-through
             case DIRECT_INVOKE_SPECIAL:     // fall-through
+            case DIRECT_INVOKE_SPECIAL_IFC: // fall-through
             case DIRECT_INVOKE_STATIC:      // fall-through
             case DIRECT_INVOKE_STATIC_INIT: // fall-through
             case DIRECT_INVOKE_VIRTUAL:     return resolveFrom(name, invokerType, DirectMethodHandle.Holder.class);
--- a/src/java.base/share/classes/java/lang/invoke/LambdaForm.java	Mon Apr 30 18:10:24 2018 -0700
+++ b/src/java.base/share/classes/java/lang/invoke/LambdaForm.java	Mon Apr 30 21:56:54 2018 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -291,6 +291,7 @@
         LINK_TO_CALL_SITE("linkToCallSite"),
         DIRECT_INVOKE_VIRTUAL("DMH.invokeVirtual", "invokeVirtual"),
         DIRECT_INVOKE_SPECIAL("DMH.invokeSpecial", "invokeSpecial"),
+        DIRECT_INVOKE_SPECIAL_IFC("DMH.invokeSpecialIFC", "invokeSpecialIFC"),
         DIRECT_INVOKE_STATIC("DMH.invokeStatic", "invokeStatic"),
         DIRECT_NEW_INVOKE_SPECIAL("DMH.newInvokeSpecial", "newInvokeSpecial"),
         DIRECT_INVOKE_INTERFACE("DMH.invokeInterface", "invokeInterface"),
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Mon Apr 30 18:10:24 2018 -0700
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Mon Apr 30 21:56:54 2018 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -2267,27 +2267,27 @@
         }
 
         /** Check access and get the requested method. */
-        private MethodHandle getDirectMethod(byte refKind, Class<?> refc, MemberName method, Class<?> callerClass) throws IllegalAccessException {
+        private MethodHandle getDirectMethod(byte refKind, Class<?> refc, MemberName method, Class<?> boundCallerClass) throws IllegalAccessException {
             final boolean doRestrict    = true;
             final boolean checkSecurity = true;
-            return getDirectMethodCommon(refKind, refc, method, checkSecurity, doRestrict, callerClass);
+            return getDirectMethodCommon(refKind, refc, method, checkSecurity, doRestrict, boundCallerClass);
         }
         /** Check access and get the requested method, for invokespecial with no restriction on the application of narrowing rules. */
-        private MethodHandle getDirectMethodNoRestrictInvokeSpecial(Class<?> refc, MemberName method, Class<?> callerClass) throws IllegalAccessException {
+        private MethodHandle getDirectMethodNoRestrictInvokeSpecial(Class<?> refc, MemberName method, Class<?> boundCallerClass) throws IllegalAccessException {
             final boolean doRestrict    = false;
             final boolean checkSecurity = true;
-            return getDirectMethodCommon(REF_invokeSpecial, refc, method, checkSecurity, doRestrict, callerClass);
+            return getDirectMethodCommon(REF_invokeSpecial, refc, method, checkSecurity, doRestrict, boundCallerClass);
         }
         /** Check access and get the requested method, eliding security manager checks. */
-        private MethodHandle getDirectMethodNoSecurityManager(byte refKind, Class<?> refc, MemberName method, Class<?> callerClass) throws IllegalAccessException {
+        private MethodHandle getDirectMethodNoSecurityManager(byte refKind, Class<?> refc, MemberName method, Class<?> boundCallerClass) throws IllegalAccessException {
             final boolean doRestrict    = true;
             final boolean checkSecurity = false;  // not needed for reflection or for linking CONSTANT_MH constants
-            return getDirectMethodCommon(refKind, refc, method, checkSecurity, doRestrict, callerClass);
+            return getDirectMethodCommon(refKind, refc, method, checkSecurity, doRestrict, boundCallerClass);
         }
         /** Common code for all methods; do not call directly except from immediately above. */
         private MethodHandle getDirectMethodCommon(byte refKind, Class<?> refc, MemberName method,
                                                    boolean checkSecurity,
-                                                   boolean doRestrict, Class<?> callerClass) throws IllegalAccessException {
+                                                   boolean doRestrict, Class<?> boundCallerClass) throws IllegalAccessException {
             checkMethod(refKind, refc, method);
             // Optionally check with the security manager; this isn't needed for unreflect* calls.
             if (checkSecurity)
@@ -2325,25 +2325,25 @@
                 checkMethod(refKind, refc, method);
             }
 
-            DirectMethodHandle dmh = DirectMethodHandle.make(refKind, refc, method);
+            DirectMethodHandle dmh = DirectMethodHandle.make(refKind, refc, method, lookupClass());
             MethodHandle mh = dmh;
-            // Optionally narrow the receiver argument to refc using restrictReceiver.
+            // Optionally narrow the receiver argument to lookupClass using restrictReceiver.
             if ((doRestrict && refKind == REF_invokeSpecial) ||
                     (MethodHandleNatives.refKindHasReceiver(refKind) && restrictProtectedReceiver(method))) {
                 mh = restrictReceiver(method, dmh, lookupClass());
             }
-            mh = maybeBindCaller(method, mh, callerClass);
+            mh = maybeBindCaller(method, mh, boundCallerClass);
             mh = mh.setVarargs(method);
             return mh;
         }
         private MethodHandle maybeBindCaller(MemberName method, MethodHandle mh,
-                                             Class<?> callerClass)
+                                             Class<?> boundCallerClass)
                                              throws IllegalAccessException {
             if (allowedModes == TRUSTED || !MethodHandleNatives.isCallerSensitive(method))
                 return mh;
             Class<?> hostClass = lookupClass;
             if (!hasPrivateAccess())  // caller must have private access
-                hostClass = callerClass;  // callerClass came from a security manager style stack walk
+                hostClass = boundCallerClass;  // boundCallerClass came from a security manager style stack walk
             MethodHandle cbmh = MethodHandleImpl.bindCaller(mh, hostClass);
             // Note: caller will apply varargs after this step happens.
             return cbmh;
--- a/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java	Mon Apr 30 18:10:24 2018 -0700
+++ b/src/java.base/share/classes/java/lang/invoke/MethodTypeForm.java	Mon Apr 30 21:56:54 2018 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -85,7 +85,8 @@
             LF_GWT                     = 17,  // guardWithTest
             LF_TF                      = 18,  // tryFinally
             LF_LOOP                    = 19,  // loop
-            LF_LIMIT                   = 20;
+            LF_INVSPECIAL_IFC          = 20,  // DMH invokeSpecial of (private) interface method
+            LF_LIMIT                   = 21;
 
     /** Return the type corresponding uniquely (1-1) to this MT-form.
      *  It might have any primitive returns or arguments, but will have no references except Object.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/I4Special.jcod	Mon Apr 30 21:56:54 2018 -0400
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// invokeDirect is modified to use invokespecial instead of invokevirtual
+
+class SpecialInterfaceCall$I4 {
+  0xCAFEBABE;
+  0; // minor version
+  55; // version
+  [] { // Constant Pool
+    ; // first element is empty
+    Method #3 #13; // #1
+    class #15; // #2
+    class #16; // #3
+    class #17; // #4
+    Utf8 "invokeDirect"; // #5
+    Utf8 "I4"; // #6
+    Utf8 "InnerClasses"; // #7
+    Utf8 "(LSpecialInterfaceCall$I4;)V"; // #8
+    Utf8 "Code"; // #9
+    Utf8 "LineNumberTable"; // #10
+    Utf8 "SourceFile"; // #11
+    Utf8 "SpecialInterfaceCall.java"; // #12
+    NameAndType #19 #20; // #13
+    class #21; // #14
+    Utf8 "SpecialInterfaceCall$I4"; // #15
+    Utf8 "java/lang/Object"; // #16
+    Utf8 "SpecialInterfaceCall$I1"; // #17
+    Utf8 "I1"; // #18
+    Utf8 "toString"; // #19
+    Utf8 "()Ljava/lang/String;"; // #20
+    Utf8 "SpecialInterfaceCall"; // #21
+  } // Constant Pool
+
+  0x0600; // access
+  #2;// this_cpx
+  #3;// super_cpx
+
+  [] { // Interfaces
+    #4;
+  } // Interfaces
+
+  [] { // fields
+  } // fields
+
+  [] { // methods
+    { // Member
+      0x0009; // access
+      #5; // name_cpx
+      #8; // sig_cpx
+      [] { // Attributes
+        Attr(#9) { // Code
+          1; // max_stack
+          2; // max_locals
+          Bytes[]{
+//            0x2AB600014CB1;
+            0x2AB700014CB1;  // invokespecial
+          };
+          [] { // Traps
+          } // end Traps
+          [] { // Attributes
+            Attr(#10) { // LineNumberTable
+              [] { // LineNumberTable
+                0  77;
+                5  78;
+              }
+            } // end LineNumberTable
+          } // Attributes
+        } // end Code
+      } // Attributes
+    } // Member
+  } // methods
+
+  [] { // Attributes
+    Attr(#11) { // SourceFile
+      #12;
+    } // end SourceFile
+    ;
+    Attr(#7) { // InnerClasses
+      [] { // InnerClasses
+        #2 #14 #6 1544;
+        #4 #14 #18 1544;
+      }
+    } // end InnerClasses
+  } // Attributes
+} // end class SpecialInterfaceCall$I4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/SpecialInterfaceCall.java	Mon Apr 30 21:56:54 2018 -0400
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8200167
+ * @summary Test direct and MethodHandle access to interface methods using invokespecial semantics
+ * @compile SpecialInterfaceCall.java
+ * @compile I4Special.jcod
+ * @run main/othervm -Xint SpecialInterfaceCall
+ * @run main/othervm -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=1 SpecialInterfaceCall
+ * @run main/othervm -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=2 SpecialInterfaceCall
+ * @run main/othervm -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=3 SpecialInterfaceCall
+ * @run main/othervm -Xbatch -XX:-TieredCompilation SpecialInterfaceCall
+ */
+
+import java.lang.invoke.*;
+
+public class SpecialInterfaceCall {
+    interface I1 {
+        default void pub_m() {};
+        private void priv_m() {};
+    }
+    interface I2 extends I1 {
+        // This needs to be a public method to avoid access control issues,
+        // but logically we treat it as private and emulate invokespecial
+        // using MethodHandles.
+        default void pub_m() {};
+
+        private void priv_m() {};
+
+        static void invokeDirect(I2 i) {
+            i.priv_m(); // generates invokespecial
+        }
+        static void invokeSpecialMH(I2 i) throws Throwable {
+            // emulates behaviour of invokeDirect
+            mh_I2_priv_m_from_I2.invokeExact(i);
+        }
+        // special case of invoking an Object method via an interface
+        static void invokeSpecialObjectMH(I2 i) throws Throwable {
+            // emulates invokespecial of I1.toString on i, which resolves
+            // to Object.toString
+            String s = (String) mh_I1_toString_from_I2.invokeExact(i);
+        }
+    }
+    interface I3 extends I2 {
+        // Must take an I3 here rather than I2 else we get
+        // WrongMethodTypeException: expected (I3)void but found (I2)void
+        // Statically the receiver type is bounded by the caller type.
+        static void invokeSpecialMH(I3 i) throws Throwable {
+            // emulates an invokespecial of ((I2)i).pub_m()
+            mh_I2_pub_m_from_I3.invokeExact(i);
+        }
+    }
+    // This interface acts like I2 but we define a directInvoke method
+    // that we will rewrite the bytecode of to use invokespecial
+    interface I4 extends I1 {
+        static void invokeDirect(I4 i) {
+            String s = i.toString();
+        }
+    }
+
+    // Concrete classes
+    static class C1 implements I1 { }
+    static class C2 implements I2 { }
+    static class C3 implements I3 { }
+    static class C4 implements I4 { }
+
+    // Classes that don't implement I2/I3 but do have a
+    // priv_m/pub_m method in their hierarchy
+    static class D1 implements I1 { }
+    static class E {
+        public void pub_m() {}
+        private void priv_m() {}
+    }
+
+    // This MH acts like the direct invokespecial in I2.invokeDirect
+    static final MethodHandle mh_I2_priv_m_from_I2;
+
+    // This MH acts like an invokespecial of I2.pub_m from I3
+    static final MethodHandle mh_I2_pub_m_from_I3;
+
+    // This MH acts likes an invokespecial of I1.toString from I2
+    static final MethodHandle mh_I1_toString_from_I2;
+    static {
+        try {
+            MethodType mt = MethodType.methodType(void.class);
+            MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+            mh_I2_priv_m_from_I2 = lookup.findSpecial(I2.class, "priv_m", mt, I2.class);
+            mh_I2_pub_m_from_I3 = lookup.findSpecial(I2.class, "pub_m", mt, I3.class);
+
+            mt = MethodType.methodType(String.class);
+            mh_I1_toString_from_I2 = lookup.findSpecial(I1.class, "toString", mt, I2.class);
+
+        } catch (Throwable e) {
+            throw new Error(e);
+        }
+    }
+
+    static void runPositiveTests() {
+        shouldNotThrow(() -> I2.invokeDirect(new C2()));
+        shouldNotThrow(() -> I2.invokeDirect(new C3()));
+        shouldNotThrow(() -> I2.invokeSpecialMH(new C2()));
+        shouldNotThrow(() -> I2.invokeSpecialMH(new C3()));
+
+        shouldNotThrow(() -> I3.invokeSpecialMH(new C3()));
+
+        shouldNotThrow(() -> I4.invokeDirect(new C4()));
+    }
+
+    static void runNegativeTests() {
+        System.out.println("IAE I2.invokeDirect D1");
+        shouldThrowIAE(() -> I2.invokeDirect(unsafeCastI2(new D1())));
+        System.out.println("IAE I2.invokeDirect E");
+        shouldThrowIAE(() -> I2.invokeDirect(unsafeCastI2(new E())));
+        System.out.println("ICCE I2.invokeMH D1");
+        shouldThrowICCE(() -> I2.invokeSpecialMH(unsafeCastI2(new D1())));
+        System.out.println("ICCE I2.invokeMH E");
+        shouldThrowICCE(() -> I2.invokeSpecialMH(unsafeCastI2(new E())));
+        System.out.println("ICCE I3.invokeMH D1");
+        shouldThrowICCE(() -> I3.invokeSpecialMH(unsafeCastI3(new D1())));
+        System.out.println("ICCE I3.invokeMH E");
+        shouldThrowICCE(() -> I3.invokeSpecialMH(unsafeCastI3(new E())));
+        System.out.println("ICCE I3.invokeMH C2");
+        shouldThrowICCE(() -> I3.invokeSpecialMH(unsafeCastI3(new C2())));
+        System.out.println("ICCE I4.invokeDirect C1");
+        shouldThrowIAE(() -> I4.invokeDirect(unsafeCastI4(new C1())));
+        System.out.println("ICCE I2.invokeObjectMH C1");
+        shouldThrowICCE(() -> I2.invokeSpecialObjectMH(unsafeCastI2(new C1())));
+
+    }
+
+    static void warmup() {
+        for (int i = 0; i < 20_000; i++) {
+            runPositiveTests();
+        }
+    }
+
+    public static void main(String[] args) throws Throwable {
+        System.out.println("UNRESOLVED:");
+        runNegativeTests();
+        runPositiveTests();
+
+        System.out.println("RESOLVED:");
+        runNegativeTests();
+
+        System.out.println("WARMUP:");
+        warmup();
+
+        System.out.println("COMPILED:");
+        runNegativeTests();
+        runPositiveTests();
+    }
+
+    static interface Test {
+        void run() throws Throwable;
+    }
+
+    static void shouldThrowICCE(Test t) {
+        shouldThrow(IncompatibleClassChangeError.class,
+                    "is not a subclass of caller class", t);
+    }
+
+    static void shouldThrowIAE(Test t) {
+        shouldThrow(IllegalAccessError.class,
+                    "must be the current class or a subtype of interface", t);
+    }
+
+    static void shouldThrow(Class<?> expectedError, String reason, Test t) {
+        try {
+            t.run();
+        } catch (Throwable e) {
+            if (expectedError.isInstance(e)) {
+                if (e.getMessage().contains(reason)) {
+                    // passed
+                    System.out.println("Threw expected: " + e);
+                    return;
+                }
+                else {
+                    throw new AssertionError("Wrong exception reason: expected '" + reason
+                                             + "', got '" + e.getMessage() + "'", e);
+                }
+            } else {
+                String msg = String.format("Wrong exception thrown: expected=%s; thrown=%s",
+                                           expectedError.getName(), e.getClass().getName());
+                throw new AssertionError(msg, e);
+            }
+        }
+        throw new AssertionError("No exception thrown: expected " + expectedError.getName());
+    }
+
+    static void shouldNotThrow(Test t) {
+        try {
+            t.run();
+            // passed
+        } catch (Throwable e) {
+            throw new AssertionError("Exception was thrown: ", e);
+        }
+    }
+
+    // Note: these unsafe casts are only possible for interface types
+
+    static I2 unsafeCastI2(Object obj) {
+        try {
+            MethodHandle mh = MethodHandles.identity(Object.class);
+            mh = MethodHandles.explicitCastArguments(mh, mh.type().changeReturnType(I2.class));
+            return (I2)mh.invokeExact((Object) obj);
+        } catch (Throwable e) {
+            throw new Error(e);
+        }
+    }
+
+    static I3 unsafeCastI3(Object obj) {
+        try {
+            MethodHandle mh = MethodHandles.identity(Object.class);
+            mh = MethodHandles.explicitCastArguments(mh, mh.type().changeReturnType(I3.class));
+            return (I3)mh.invokeExact((Object) obj);
+        } catch (Throwable e) {
+            throw new Error(e);
+        }
+    }
+
+    static I4 unsafeCastI4(Object obj) {
+        try {
+            MethodHandle mh = MethodHandles.identity(Object.class);
+            mh = MethodHandles.explicitCastArguments(mh, mh.type().changeReturnType(I4.class));
+            return (I4)mh.invokeExact((Object) obj);
+        } catch (Throwable e) {
+            throw new Error(e);
+        }
+    }
+
+}