--- old/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp 2018-05-04 04:03:49.486952085 -0400 +++ new/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp 2018-05-04 04:03:47.926862104 -0400 @@ -3369,11 +3369,21 @@ // r2: receiver // r3: flags + Label no_such_interface; + // Check for private method invocation - indicated by vfinal Label notVFinal; __ tbz(flags, ConstantPoolCacheEntry::is_vfinal_shift, notVFinal); - __ null_check(r2); + // Get receiver klass into r3 - also a null check + __ null_check(r2, oopDesc::klass_offset_in_bytes()); + __ load_klass(r3, r2); + + Label subtype; + __ check_klass_subtype(r3, r0, r4, subtype); + // If we get here the typecheck failed + __ b(no_such_interface); + __ bind(subtype); __ profile_final_call(r0); __ profile_arguments_type(r0, rmethod, r4, true); @@ -3396,7 +3406,7 @@ __ null_check(r2, oopDesc::klass_offset_in_bytes()); __ load_klass(r3, r2); - Label no_such_interface, no_such_method; + Label no_such_method; // Preserve method for throw_AbstractMethodErrorVerbose. __ mov(r16, rmethod); --- old/src/hotspot/cpu/arm/templateTable_arm.cpp 2018-05-04 04:03:54.479240027 -0400 +++ new/src/hotspot/cpu/arm/templateTable_arm.cpp 2018-05-04 04:03:52.939151199 -0400 @@ -4200,19 +4200,32 @@ prepare_invoke(byte_no, Rinterf, Rmethod, Rrecv, Rflags); + + Label no_such_interface; + // Check for private method invocation - indicated by vfinal Label notVFinal; __ tbz(flags, ConstantPoolCacheEntry::is_vfinal_shift, notVFinal); - // do the call - the index is actually the method to call + // Get receiver klass into Rklass - also a null check + __ load_klass(Rklass, Rrecv); - __ null_check(Rrecv, Rtemp); + Label subtype; + __ check_klass_subtype(Rklass, Rinterf, R1_tmp, R0_tmp, subtype); + // If we get here the typecheck failed + __ b(no_such_interface); + __ bind(subtype); + + // do the call - the index is actually the method to call __ profile_final_call(R0_tmp); __ jump_from_interpreted(Rindex); __ bind(notVFinal); + // Get receiver klass into Rklass - also a null check + __ load_klass(Rklass, Rrecv); + // Special case of invokeinterface called for virtual method of // java.lang.Object. See cpCacheOop.cpp for details. // This code isn't produced by javac, but could be produced by @@ -4223,11 +4236,6 @@ invokevirtual_helper(Rmethod, Rrecv, Rflags); __ bind(notMethod); - // Get receiver klass into Rklass - also a null check - __ load_klass(Rklass, Rrecv); - - Label no_such_interface; - // Receiver subtype check against REFC. __ lookup_interface_method(// inputs: rec. class, interface Rklass, Rinterf, noreg, --- old/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp 2018-05-04 04:03:59.283517125 -0400 +++ new/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp 2018-05-04 04:03:57.735427835 -0400 @@ -3585,13 +3585,22 @@ prepare_invoke(byte_no, Rinterface_klass, Rret_addr, Rmethod, Rreceiver, Rflags, Rscratch1); + // Get receiver klass - this is also a null check + __ null_check_throw(Rreceiver, oopDesc::klass_offset_in_bytes(), Rscratch2); + __ load_klass(Rrecv_klass, Rreceiver); + // Check for private method invocation - indicated by vfinal - Label LnotVFinal; + Label LnotVFinal, L_no_such_interface, L_subtype; __ testbitdi(CCR0, R0, Rflags, ConstantPoolCacheEntry::is_vfinal_shift); __ bfalse(CCR0, LnotVFinal); - __ null_check_throw(Rreceiver, -1, Rscratch3); + __ check_klass_subtype(Rrecv_klass, Rinterface_klass, Rscratch, Rscratch1, subtype); + // If we get here the typecheck failed + __ b(L_no_such_interface); + __ bind(subtype); + + // do the call Register Rscratch = Rflags; // Rflags is dead now. @@ -3602,12 +3611,8 @@ __ bind(LnotVFinal); - // Get receiver klass. - __ null_check_throw(Rreceiver, oopDesc::klass_offset_in_bytes(), Rscratch2); - __ load_klass(Rrecv_klass, Rreceiver); - // Check corner case object method. - Label LobjectMethod, L_no_such_interface, Lthrow_ame; + Label LobjectMethod, Lthrow_ame; __ testbitdi(CCR0, R0, Rflags, ConstantPoolCacheEntry::is_forced_virtual_shift); __ btrue(CCR0, LobjectMethod); --- old/src/hotspot/cpu/s390/templateTable_s390.cpp 2018-05-04 04:04:04.079793762 -0400 +++ new/src/hotspot/cpu/s390/templateTable_s390.cpp 2018-05-04 04:04:02.523704011 -0400 @@ -3618,11 +3618,21 @@ // Z_R14 (== Z_bytecode) : return entry // Check for private method invocation - indicated by vfinal - Label notVFinal; + NearLabel notVFinal; __ testbit(flags, ConstantPoolCacheEntry::is_vfinal_shift); __ z_brz(notVFinal); - __ null_check(receiver); + // Get receiver klass into klass - also a null check. + __ load_klass(klass, receiver); + + NearLabel subtype, no_such_interface; + + __ check_klass_subtype(klass, interface, Z_tmp_2, Z_temp_3, subtype); + // If we get here the typecheck failed + __ z_bru(no_such_interface); + __ bind(subtype); + + // do the call __ profile_final_call(Z_tmp_2); __ profile_arguments_type(Z_tmp_2, method, Z_ARG5, true); @@ -3634,7 +3644,7 @@ // java.lang.Object. See cpCacheOop.cpp for details. // This code isn't produced by javac, but could be produced by // another compliant java compiler. - NearLabel notMethod, no_such_interface, no_such_method; + NearLabel notMethod, no_such_method; __ testbit(flags, ConstantPoolCacheEntry::is_forced_virtual_shift); __ z_brz(notMethod); invokevirtual_helper(method, receiver, flags); --- old/src/hotspot/cpu/sparc/templateTable_sparc.cpp 2018-05-04 04:04:08.844068554 -0400 +++ new/src/hotspot/cpu/sparc/templateTable_sparc.cpp 2018-05-04 04:04:07.303979726 -0400 @@ -3056,16 +3056,29 @@ prepare_invoke(byte_no, Rinterface, Rret, Rmethod, O0_recv, O1_flags); + Label L_no_such_interface; + // Check for private method invocation - indicated by vfinal Label notVFinal; - __ set((1 << ConstantPoolCacheEntry::is_vfinal_shift), Rscratch); - __ btst(O1_flags, Rscratch); - __ br(Assembler::zero, false, Assembler::pt, notVFinal); - __ delayed()->nop(); - - __ null_check(O0_recv); - { + __ set((1 << ConstantPoolCacheEntry::is_vfinal_shift), Rscratch); + __ btst(O1_flags, Rscratch); + __ br(Assembler::zero, false, Assembler::pt, notVFinal); + __ delayed()->nop(); + + // get receiver klass - this is also a null check + __ null_check(O0_recv, oopDesc::klass_offset_in_bytes()); + __ load_klass(O0_recv, O2_Klass); + + Label subtype; + Register Rtemp = O1_flags; + __ check_klass_subtype(O2_Klass, Rinterface, Rscratch, Rtemp, subtype); + // If we get here the typecheck failed + __ ba(L_no_such_interface); + __ delayed()->nop(); + __ bind(subtype); + + // do the call Register Rcall = Rinterface; __ mov(Rmethod, G5_method); assert_different_registers(Rcall, G5_method, Gargs, Rret); @@ -3074,9 +3087,9 @@ __ profile_final_call(Rscratch); __ call_from_interpreter(Rcall, Gargs, Rret); } - __ bind(notVFinal); + __ bind(notVFinal); - // get receiver klass + // get receiver klass - this is also a null check __ null_check(O0_recv, oopDesc::klass_offset_in_bytes()); __ load_klass(O0_recv, O2_Klass); @@ -3096,8 +3109,6 @@ Register Rtemp = O1_flags; - Label L_no_such_interface; - // Receiver subtype check against REFC. __ lookup_interface_method(// inputs: rec. class, interface, itable index O2_Klass, Rinterface, noreg, --- old/src/hotspot/cpu/x86/templateTable_x86.cpp 2018-05-04 04:04:13.600342886 -0400 +++ new/src/hotspot/cpu/x86/templateTable_x86.cpp 2018-05-04 04:04:12.060254057 -0400 @@ -3791,15 +3791,29 @@ // rcx: receiver // rdx: flags + Label no_such_interface; // for receiver subtype check + Register recvKlass; // used for exception processing + // Check for private method invocation - indicated by vfinal Label notVFinal; __ movl(rlocals, rdx); __ andl(rlocals, (1 << ConstantPoolCacheEntry::is_vfinal_shift)); __ jcc(Assembler::zero, notVFinal); - // do the call - rbx is actually the method to call + // Get receiver klass into rlocals - also a null check + __ null_check(rcx, oopDesc::klass_offset_in_bytes()); + __ load_klass(rlocals, rcx); - __ null_check(rcx); + Label subtype; + __ check_klass_subtype(rlocals, rax, rbcp, subtype); + // If we get here the typecheck failed + recvKlass = rdx; + __ mov(recvKlass, rlocals); // shuffle receiver class for exception use + __ jmp(no_such_interface); + + __ bind(subtype); + + // do the call - rbx is actually the method to call __ profile_final_call(rdx); __ profile_arguments_type(rdx, rbx, rbcp, true); @@ -3826,7 +3840,7 @@ __ null_check(rcx, oopDesc::klass_offset_in_bytes()); __ load_klass(rdx, rcx); - Label no_such_interface, no_such_method; + Label no_such_method; // Preserve method for throw_AbstractMethodErrorVerbose. __ mov(rcx, rbx); @@ -3888,12 +3902,12 @@ __ restore_locals(); // make sure locals pointer is correct as well (was destroyed) // Pass arguments for generating a verbose error message. #ifdef _LP64 - Register recvKlass = c_rarg1; + recvKlass = c_rarg1; Register method = c_rarg2; if (recvKlass != rdx) { __ movq(recvKlass, rdx); } if (method != rcx) { __ movq(method, rcx); } #else - Register recvKlass = rdx; + recvKlass = rdx; Register method = rcx; #endif __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodErrorVerbose), --- old/src/hotspot/share/oops/cpCache.cpp 2018-05-04 04:04:18.336616065 -0400 +++ new/src/hotspot/share/oops/cpCache.cpp 2018-05-04 04:04:16.792527005 -0400 @@ -187,6 +187,7 @@ method()->size_of_parameters()); set_f2_as_vfinal_method(method()); byte_no = 2; + set_f1(method->method_holder()); // interface klass* break; } else { @@ -251,7 +252,7 @@ // is executed. if (invoke_code != Bytecodes::_invokespecial || !sender_is_interface || method->name() == vmSymbols::object_initializer_name()) { - set_bytecode_1(invoke_code); + set_bytecode_1(invoke_code); } } else if (byte_no == 2) { if (change_to_virtual) { --- old/src/hotspot/share/oops/klassVtable.cpp 2018-05-04 04:04:23.032886938 -0400 +++ new/src/hotspot/share/oops/klassVtable.cpp 2018-05-04 04:04:21.488797877 -0400 @@ -1121,7 +1121,7 @@ inline bool interface_method_needs_itable_index(Method* m) { if (m->is_static()) return false; // e.g., Stream.empty if (m->is_initializer()) return false; // or - if (m->is_private()) return false; // requires invokeSpecial + if (m->is_private()) return false; // uses direct call // If an interface redeclares a method from java.lang.Object, // it should already have a vtable index, don't touch it. // e.g., CharSequence.toString (from initialize_vtable) --- old/src/hotspot/share/opto/doCall.cpp 2018-05-04 04:04:28.673212261 -0400 +++ new/src/hotspot/share/opto/doCall.cpp 2018-05-04 04:04:26.341077747 -0400 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -505,28 +505,36 @@ speculative_receiver_type = receiver_type != NULL ? receiver_type->speculative_type() : NULL; } - // invoke-super-special + // Additional receiver subtype checks for interface calls via invokespecial or invokeinterface. + ciKlass* receiver_constraint = NULL; if (iter().cur_bc_raw() == Bytecodes::_invokespecial && !orig_callee->is_object_initializer()) { ciInstanceKlass* calling_klass = method()->holder(); ciInstanceKlass* sender_klass = calling_klass->is_anonymous() ? calling_klass->host_klass() : calling_klass; if (sender_klass->is_interface()) { - Node* receiver_node = stack(sp() - nargs); - Node* cls_node = makecon(TypeKlassPtr::make(sender_klass)); - Node* bad_type_ctrl = NULL; - Node* casted_receiver = gen_checkcast(receiver_node, cls_node, &bad_type_ctrl); - if (bad_type_ctrl != NULL) { - PreserveJVMState pjvms(this); - set_control(bad_type_ctrl); - uncommon_trap(Deoptimization::Reason_class_check, - Deoptimization::Action_none); - } - if (stopped()) { - return; // MUST uncommon-trap? - } - set_stack(sp() - nargs, casted_receiver); + receiver_constraint = sender_klass; + } + } else if (iter().cur_bc_raw() == Bytecodes::_invokeinterface && orig_callee->is_private()) { + assert(holder->is_interface(), "How did we get a non-interface method here!"); + receiver_constraint = holder; + } + + if (receiver_constraint != NULL) { + Node* receiver_node = stack(sp() - nargs); + Node* cls_node = makecon(TypeKlassPtr::make(receiver_constraint)); + Node* bad_type_ctrl = NULL; + Node* casted_receiver = gen_checkcast(receiver_node, cls_node, &bad_type_ctrl); + if (bad_type_ctrl != NULL) { + PreserveJVMState pjvms(this); + set_control(bad_type_ctrl); + uncommon_trap(Deoptimization::Reason_class_check, + Deoptimization::Action_none); + } + if (stopped()) { + return; // MUST uncommon-trap? } + set_stack(sp() - nargs, casted_receiver); } // Note: It's OK to try to inline a virtual call. --- old/src/hotspot/share/prims/methodHandles.cpp 2018-05-04 04:04:33.333481058 -0400 +++ new/src/hotspot/share/prims/methodHandles.cpp 2018-05-04 04:04:31.737388999 -0400 @@ -297,6 +297,9 @@ } else if (m->is_initializer()) { flags |= IS_CONSTRUCTOR | (JVM_REF_invokeSpecial << REFERENCE_KIND_SHIFT); } else { + // "special" reflects that this is a direct call, not that it + // necessarily originates from an invokespecial. We can also do + // direct calls for private and/or final non-static methods. flags |= IS_METHOD | (JVM_REF_invokeSpecial << REFERENCE_KIND_SHIFT); } break; --- old/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java 2018-05-04 04:04:38.061753778 -0400 +++ new/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java 2018-05-04 04:04:36.413658718 -0400 @@ -84,19 +84,21 @@ switch (refKind) { case REF_invokeSpecial: { member = member.asSpecial(); - 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 + // if caller is an interface we need to adapt to get the + // receiver check inserted + if (callerClass == null) { + throw new InternalError("callerClass must not be null for REF_invokeSpecial"); } - return new Special(mtype, lform, member, checkClass); + LambdaForm lform = preparedLambdaForm(member, callerClass.isInterface()); + return new Special(mtype, lform, member, callerClass); } case REF_invokeInterface: { - LambdaForm lform = preparedLambdaForm(member, callerClass); + // we always adapt 'special' when dealing with interfaces + LambdaForm lform = preparedLambdaForm(member, true); return new Interface(mtype, lform, member, refc); } default: { - LambdaForm lform = preparedLambdaForm(member, callerClass); + LambdaForm lform = preparedLambdaForm(member); return new DirectMethodHandle(mtype, lform, member); } } @@ -166,11 +168,15 @@ * Cache and share this structure among all methods with * the same basicType and refKind. */ - private static LambdaForm preparedLambdaForm(MemberName m, Class callerClass) { + private static LambdaForm preparedLambdaForm(MemberName m, boolean adaptToSpecialIfc) { assert(m.isInvocable()) : m; // call preparedFieldLambdaForm instead MethodType mtype = m.getInvocationType().basicType(); assert(!m.isMethodHandleInvoke()) : m; int which; + // MemberName.getReferenceKind may be different from the 'kind' passed to + // DMH.make. Specifically private/final methods that use a direct call + // have been adapted to REF_invokeSpecial, even though the actual + // invocation mode may be invokevirtual or invokeinterface. switch (m.getReferenceKind()) { case REF_invokeVirtual: which = LF_INVVIRTUAL; break; case REF_invokeStatic: which = LF_INVSTATIC; break; @@ -184,7 +190,7 @@ preparedLambdaForm(mtype, which); which = LF_INVSTATIC_INIT; } - if (which == LF_INVSPECIAL && callerClass != null && callerClass.isInterface()) { + if (which == LF_INVSPECIAL && adaptToSpecialIfc) { which = LF_INVSPECIAL_IFC; } LambdaForm lform = preparedLambdaForm(mtype, which); @@ -196,7 +202,7 @@ } private static LambdaForm preparedLambdaForm(MemberName m) { - return preparedLambdaForm(m, null); + return preparedLambdaForm(m, false); } private static LambdaForm preparedLambdaForm(MethodType mtype, int which) { --- /dev/null 2018-04-28 00:26:07.190086997 -0400 +++ new/test/jdk/java/lang/invoke/FinalVirtualCallFromInterface.java 2018-05-04 04:04:41.201934899 -0400 @@ -0,0 +1,61 @@ +/* + * 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 8010319 + * @summary Regression test for a bug introduced in 8200167 and fixed in 8010319 + * @run main FinalVirtualCallFromInterface + */ + +import java.lang.invoke.*; + +/* + * Test that a MethodHandle for a final method, called from an interface, works correctly. + * With only 8200167 applied this fails with: + * Exception in thread "main" java.lang.InternalError: Should only be invoked on a subclass + * at + * java.base/java.lang.invoke.DirectMethodHandle.checkReceiver(DirectMethodHandle.java:441) + * + * The nestmate update under 8010319 fixes that bug. + */ +public class FinalVirtualCallFromInterface { + + static class Final { + public final void fm() {} + } + + static interface FinalUser { + static void test() throws Throwable { + MethodType mt = MethodType.methodType(void.class); + MethodHandle mh = MethodHandles.lookup().findVirtual(Final.class, "fm", mt); + Final f = new Final(); + mh.invokeExact(f); + mh.invoke(f); + } + } + + public static void main(String[] args) throws Throwable { + FinalUser.test(); + } +} --- /dev/null 2018-04-28 00:26:07.190086997 -0400 +++ new/test/jdk/java/lang/invoke/PrivateInterfaceCall.java 2018-05-04 04:04:45.618189624 -0400 @@ -0,0 +1,251 @@ +/* + * 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 8010319 + * @summary Test direct and MethodHandle access to private interface methods using invokeinterface semantics + * to ensure all receiver typechecks occur as required. + * @comment This complements SpecialInterfaceCall which tests invokespecial semantics. + * @run main/othervm -Xint PrivateInterfaceCall + * @run main/othervm -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=1 PrivateInterfaceCall + * @run main/othervm -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=2 PrivateInterfaceCall + * @run main/othervm -Xbatch -XX:+TieredCompilation -XX:TieredStopAtLevel=3 PrivateInterfaceCall + * @run main/othervm -Xbatch -XX:-TieredCompilation PrivateInterfaceCall + */ + +// This is an adaptation of SpecialInterfaceCall to only use private interface methods and with +// virtual invocation semantics. Because we don't have the same corner cases as for invokespecial +// there's no practical difference between the I3 and I2 cases here. But we do have to ensure the +// correct versions of the methods get executed. +// In addition we add tests that involve calls from nestmates - which also covers the distinction +// between the caller being a class and being an interface. + +import java.lang.invoke.*; + +public class PrivateInterfaceCall { + interface I1 { + private void priv_m() { throw new Error("Should not call this"); }; + } + interface I2 extends I1 { + private void priv_m() { }; + + static void invokeDirect(I2 i) { + i.priv_m(); // generates invokeinterface + } + static void invokeInterfaceMH(I2 i) throws Throwable { + // emulates behaviour of invokeDirect + mh_I2_priv_m_from_I2.invokeExact(i); + } + static void init() throws Throwable { + mh_I2_priv_m_from_I2 = + MethodHandles.lookup().findVirtual(I2.class, "priv_m", MT); + } + } + interface I3 extends I2 { + static void invokeInterfaceMH(I2 i) throws Throwable { + // emulates behaviour of I2.invokeDirect + mh_I2_priv_m_from_I3.invokeExact(i); + } + static void init() throws Throwable { + mh_I2_priv_m_from_I3 = + MethodHandles.lookup().findVirtual(I2.class, "priv_m", MT); + } + } + + // check invocations from nestmates outside the + // inheritance hierarchy - and from a class not interface + static void invokeDirect(I2 i) { + i.priv_m(); // generates invokeinterface + } + static void invokeInterfaceMH(I2 i) throws Throwable { + mh_I2_priv_m_from_PIC.invokeExact(i); + } + + // Valid classes that implement I3 and/or I2 + static class C2 implements I2 { } + static class C3 implements I3 { } + + // Classes that don't implement I2/I3 but do have a + // priv_m method in their hierarchy + static class D1 implements I1 { } + static class E { + private void priv_m() { throw new Error("Should not call this"); } + } + + // This MH acts like the invocation in I2.invokeDirect with caller I2 + static MethodHandle mh_I2_priv_m_from_I2; + + // This MH acts like the invocation in I3.invokeDirect with caller I3 + static MethodHandle mh_I2_priv_m_from_I3; + + // This MH acts like the invocation in PrivateInterfaceCall.invokeDirect + // with caller PrivateInterfaceCall + static MethodHandle mh_I2_priv_m_from_PIC; + + static MethodType MT = MethodType.methodType(void.class); + + static { + try { + mh_I2_priv_m_from_PIC = MethodHandles.lookup().findVirtual(I2.class, "priv_m", MT); + I2.init(); + I3.init(); + } catch (Throwable e) { + throw new Error(e); + } + } + + static void runPositiveTests() { + shouldNotThrow(() -> PrivateInterfaceCall.invokeDirect(new C2())); + shouldNotThrow(() -> PrivateInterfaceCall.invokeDirect(new C3())); + shouldNotThrow(() -> PrivateInterfaceCall.invokeInterfaceMH(new C2())); + shouldNotThrow(() -> PrivateInterfaceCall.invokeInterfaceMH(new C3())); + + shouldNotThrow(() -> I2.invokeDirect(new C2())); + shouldNotThrow(() -> I2.invokeDirect(new C3())); + shouldNotThrow(() -> I2.invokeInterfaceMH(new C2())); + shouldNotThrow(() -> I2.invokeInterfaceMH(new C3())); + + // This looks odd but at runtime the only constraint is that the + // receiver is an I2. In contrast in the invokespecial case the + // receiver must be an I3. + shouldNotThrow(() -> I3.invokeInterfaceMH(unsafeCastI3(new C2()))); + shouldNotThrow(() -> I3.invokeInterfaceMH(new C3())); + } + + static void runNegativeTests() { + System.out.println("ICCE PrivateInterfaceCall.invokeDirect D1"); + shouldThrowICCE(() -> PrivateInterfaceCall.invokeDirect(unsafeCastI2(new D1()))); + System.out.println("ICCE PrivateInterfaceCall.invokeDirect E"); + shouldThrowICCE(() -> PrivateInterfaceCall.invokeDirect(unsafeCastI2(new E()))); + System.out.println("ICCE PrivateInterfaceCall.invokeInterfaceMH D1"); + shouldThrowICCE(() -> PrivateInterfaceCall.invokeInterfaceMH(unsafeCastI2(new D1()))); + System.out.println("ICCE PrivateInterfaceCall.invokeInterfaceMH E"); + shouldThrowICCE(() -> PrivateInterfaceCall.invokeInterfaceMH(unsafeCastI2(new E()))); + + System.out.println("ICCE I2.invokeDirect D1"); + shouldThrowICCE(() -> I2.invokeDirect(unsafeCastI2(new D1()))); + System.out.println("ICCE I2.invokeDirect E"); + shouldThrowICCE(() -> I2.invokeDirect(unsafeCastI2(new E()))); + System.out.println("ICCE I2.invokeInterfaceMH D1"); + shouldThrowICCE(() -> I2.invokeInterfaceMH(unsafeCastI2(new D1()))); + System.out.println("ICCE I2.invokeInterfaceMH E"); + shouldThrowICCE(() -> I2.invokeInterfaceMH(unsafeCastI2(new E()))); + + System.out.println("ICCE I3.invokeInterfaceMH D1"); + shouldThrowICCE(() -> I3.invokeInterfaceMH(unsafeCastI3(new D1()))); + System.out.println("ICCE I3.invokeInterfaceMH E"); + shouldThrowICCE(() -> I3.invokeInterfaceMH(unsafeCastI3(new E()))); + } + + 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, + "does not implement the requested interface", t); + } + + // Depending on whether the exception originates in the linkResolver, the interpreter + // or the compiler, the message can be different - which is unfortunate and could be + // fixed. So we allow the listed reason or else a null message. + static void shouldThrow(Class expectedError, String reason, Test t) { + try { + t.run(); + } catch (Throwable e) { + // Don't accept subclasses as they can hide unexpected failure modes + if (expectedError == e.getClass()) { + String msg = e.getMessage(); + if ((msg != null && msg.contains(reason)) || msg == null) { + // passed + System.out.println("Threw expected: " + e); + return; + } + else { + throw new AssertionError("Wrong exception reason: expected '" + reason + + "', got '" + msg + "'", 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); + } + } + +}