# HG changeset patch # User goetz # Date 1518078229 -3600 # Thu Feb 08 09:23:49 2018 +0100 # Node ID c5d3669e383a0b7d55c68ace34dcd6a03c246549 # Parent 3271310a6af7827751d8ff5812050e9039035c71 8197405: Improve messages of AbstractMethodErrors and IncompatibleClassChangeErrors. Reviewed-by: coleenp, dholmes, mdoerr, njian diff --git a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp --- a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp @@ -293,7 +293,8 @@ // throw exception __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_AbstractMethodError)); + InterpreterRuntime::throw_AbstractMethodErrorWithMethod), + rmethod); // the call_VM checks for exception, so we should never return here. __ should_not_reach_here(); diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp --- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp @@ -3440,6 +3440,8 @@ Label no_such_interface, no_such_method; + // Preserve method for throw_AbstractMethodErrorVerbose. + __ mov(r16, rmethod); // Receiver subtype check against REFC. // Superklass in r0. Subklass in r3. Blows rscratch2, r13 __ lookup_interface_method(// inputs: rec. class, interface, itable index @@ -3460,8 +3462,10 @@ __ subw(rmethod, rmethod, Method::itable_index_max); __ negw(rmethod, rmethod); + // Preserve recvKlass for throw_AbstractMethodErrorVerbose. + __ mov(rlocals, r3); __ lookup_interface_method(// inputs: rec. class, interface, itable index - r3, r0, rmethod, + rlocals, r0, rmethod, // outputs: method, scan temp. reg rmethod, r13, no_such_interface); @@ -3490,7 +3494,8 @@ // throw exception __ restore_bcp(); // bcp must be correct for exception handler (was destroyed) __ restore_locals(); // make sure locals pointer is correct as well (was destroyed) - __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError)); + // Pass arguments for generating a verbose error message. + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodErrorVerbose), r3, r16); // the call_VM checks for exception, so we should never return here. __ should_not_reach_here(); @@ -3498,8 +3503,9 @@ // throw exception __ restore_bcp(); // bcp must be correct for exception handler (was destroyed) __ restore_locals(); // make sure locals pointer is correct as well (was destroyed) + // Pass arguments for generating a verbose error message. __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_IncompatibleClassChangeError)); + InterpreterRuntime::throw_IncompatibleClassChangeErrorVerbose), r3, r0); // the call_VM checks for exception, so we should never return here. __ should_not_reach_here(); return; diff --git a/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp --- a/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp @@ -201,7 +201,12 @@ __ br(rscratch1); __ bind(L_no_such_interface); - __ far_jump(RuntimeAddress(StubRoutines::throw_IncompatibleClassChangeError_entry())); + // Handle IncompatibleClassChangeError in itable stubs. + // More detailed error message. + // We force resolving of the call site by jumping to the "handle + // wrong method" stub, and so let the interpreter runtime do all the + // dirty work. + __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); __ flush(); diff --git a/src/hotspot/cpu/arm/vtableStubs_arm.cpp b/src/hotspot/cpu/arm/vtableStubs_arm.cpp --- a/src/hotspot/cpu/arm/vtableStubs_arm.cpp +++ b/src/hotspot/cpu/arm/vtableStubs_arm.cpp @@ -158,8 +158,13 @@ __ bind(L_no_such_interface); - assert(StubRoutines::throw_IncompatibleClassChangeError_entry() != NULL, "check initialization order"); - __ jump(StubRoutines::throw_IncompatibleClassChangeError_entry(), relocInfo::runtime_call_type, Rtemp); + // Handle IncompatibleClassChangeError in itable stubs. + // More detailed error message. + // We force resolving of the call site by jumping to the "handle + // wrong method" stub, and so let the interpreter runtime do all the + // dirty work. + assert(SharedRuntime::get_handle_wrong_method_stub() != NULL, "check initialization order"); + __ jump(SharedRuntime::get_handle_wrong_method_stub(), relocInfo::runtime_call_type, Rtemp); masm->flush(); diff --git a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp --- a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp @@ -452,8 +452,8 @@ // This is not a leaf but we have a JavaFrameAnchor now and we will // check (create) exceptions afterward so this is ok. - __ call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError), - R16_thread); + __ call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodErrorWithMethod), + R16_thread, R19_method); // Pop the C frame and restore LR. __ pop_frame(); diff --git a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp --- a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp @@ -3688,11 +3688,15 @@ // Vtable entry was NULL => Throw abstract method error. __ bind(Lthrow_ame); - call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError)); + // Pass arguments for generating a verbose error message. + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodErrorVerbose), + Rrecv_klass, Rmethod); // Interface was not found => Throw incompatible class change error. __ bind(L_no_such_interface); - call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_IncompatibleClassChangeError)); + // Pass arguments for generating a verbose error message. + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_IncompatibleClassChangeErrorVerbose), + Rrecv_klass, Rinterface_klass); DEBUG_ONLY( __ should_not_reach_here(); ) // Special case of invokeinterface called for virtual method of diff --git a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp --- a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp +++ b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp @@ -2105,7 +2105,7 @@ // blocking or unlocking. // An OOP result (handle) is done specially in the slow-path code. //-------------------------------------------------------------------- - switch (ret_type) { //GLGLGL + switch (ret_type) { case T_VOID: break; // Nothing to do! case T_FLOAT: break; // Got it where we want it (unless slow-path) case T_DOUBLE: break; // Got it where we want it (unless slow-path) diff --git a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp --- a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp @@ -458,7 +458,8 @@ __ save_return_pc(); // Save Z_R14. __ push_frame_abi160(0); // Without new frame the RT call could overwrite the saved Z_R14. - __ call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError), Z_thread); + __ call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodErrorWithMethod), + Z_thread, Z_method); __ pop_frame(); __ restore_return_pc(); // Restore Z_R14. @@ -686,7 +687,7 @@ return entry; } -address TemplateInterpreterGenerator::generate_deopt_entry_for (TosState state, +address TemplateInterpreterGenerator::generate_deopt_entry_for(TosState state, int step, address continuation) { address entry = __ pc(); diff --git a/src/hotspot/cpu/s390/templateTable_s390.cpp b/src/hotspot/cpu/s390/templateTable_s390.cpp --- a/src/hotspot/cpu/s390/templateTable_s390.cpp +++ b/src/hotspot/cpu/s390/templateTable_s390.cpp @@ -3742,8 +3742,12 @@ // Throw exception. __ restore_bcp(); // Bcp must be correct for exception handler (was destroyed). __ restore_locals(); // Make sure locals pointer is correct as well (was destroyed). + // Pass arguments for generating a verbose error message. + __ z_lgr(Z_tmp_1, method); // Prevent register clash. __ call_VM(noreg, - CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError)); + CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_AbstractMethodErrorVerbose), + klass, Z_tmp_1); // The call_VM checks for exception, so we should never return here. __ should_not_reach_here(); @@ -3752,8 +3756,11 @@ // Throw exception. __ restore_bcp(); // Bcp must be correct for exception handler (was destroyed). __ restore_locals(); // Make sure locals pointer is correct as well (was destroyed). + // Pass arguments for generating a verbose error message. __ call_VM(noreg, - CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_IncompatibleClassChangeError)); + CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_IncompatibleClassChangeErrorVerbose), + klass, interface); // The call_VM checks for exception, so we should never return here. __ should_not_reach_here(); diff --git a/src/hotspot/cpu/sparc/templateInterpreterGenerator_sparc.cpp b/src/hotspot/cpu/sparc/templateInterpreterGenerator_sparc.cpp --- a/src/hotspot/cpu/sparc/templateInterpreterGenerator_sparc.cpp +++ b/src/hotspot/cpu/sparc/templateInterpreterGenerator_sparc.cpp @@ -191,7 +191,7 @@ address entry = __ pc(); // abstract method entry // throw exception - __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError)); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodErrorWithMethod), G5_method); // the call_VM checks for exception, so we should never return here. __ should_not_reach_here(); return entry; diff --git a/src/hotspot/cpu/sparc/templateTable_sparc.cpp b/src/hotspot/cpu/sparc/templateTable_sparc.cpp --- a/src/hotspot/cpu/sparc/templateTable_sparc.cpp +++ b/src/hotspot/cpu/sparc/templateTable_sparc.cpp @@ -3137,8 +3137,10 @@ __ sub(Rindex, Method::itable_index_max, Rindex); __ neg(Rindex); + // Preserve O2_Klass for throw_AbstractMethodErrorVerbose + __ mov(O2_Klass, O4); __ lookup_interface_method(// inputs: rec. class, interface, itable index - O2_Klass, Rinterface, Rindex, + O4, Rinterface, Rindex, // outputs: method, scan temp reg, temp reg G5_method, Rscratch, Rtemp, L_no_such_interface); @@ -3147,7 +3149,9 @@ { Label ok; __ br_notnull_short(G5_method, Assembler::pt, ok); - call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError)); + // Pass arguments for generating a verbose error message. + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodErrorVerbose), + O2_Klass, Rmethod); __ should_not_reach_here(); __ bind(ok); } @@ -3160,7 +3164,9 @@ __ call_from_interpreter(Rcall, Gargs, Rret); __ bind(L_no_such_interface); - call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_IncompatibleClassChangeError)); + // Pass arguments for generating a verbose error message. + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_IncompatibleClassChangeErrorVerbose), + O2_Klass, Rinterface); __ should_not_reach_here(); } @@ -3536,7 +3542,7 @@ void TemplateTable::_breakpoint() { // Note: We get here even if we are single stepping.. - // jbug inists on setting breakpoints at every bytecode + // jbug insists on setting breakpoints at every bytecode // even if we are in single step mode. transition(vtos, vtos); diff --git a/src/hotspot/cpu/sparc/vtableStubs_sparc.cpp b/src/hotspot/cpu/sparc/vtableStubs_sparc.cpp --- a/src/hotspot/cpu/sparc/vtableStubs_sparc.cpp +++ b/src/hotspot/cpu/sparc/vtableStubs_sparc.cpp @@ -212,7 +212,12 @@ __ delayed()->nop(); __ bind(L_no_such_interface); - AddressLiteral icce(StubRoutines::throw_IncompatibleClassChangeError_entry()); + // Handle IncompatibleClassChangeError in itable stubs. + // More detailed error message. + // We force resolving of the call site by jumping to the "handle + // wrong method" stub, and so let the interpreter runtime do all the + // dirty work. + AddressLiteral icce(SharedRuntime::get_handle_wrong_method_stub()); __ jump_to(icce, G3_scratch); __ delayed()->restore(); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp @@ -4425,7 +4425,7 @@ * c_rarg0 - x address * c_rarg1 - x length * c_rarg2 - y address - * c_rarg3 - y lenth + * c_rarg3 - y length * not Win64 * c_rarg4 - z address * c_rarg5 - z length diff --git a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp --- a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp +++ b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp @@ -1354,7 +1354,7 @@ __ restore_locals(); // make sure locals pointer is correct as well (was destroyed) // throw exception - __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError)); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodErrorWithMethod), rbx); // the call_VM checks for exception, so we should never return here. __ should_not_reach_here(); diff --git a/src/hotspot/cpu/x86/templateTable_x86.cpp b/src/hotspot/cpu/x86/templateTable_x86.cpp --- a/src/hotspot/cpu/x86/templateTable_x86.cpp +++ b/src/hotspot/cpu/x86/templateTable_x86.cpp @@ -3872,6 +3872,8 @@ Label no_such_interface, no_such_method; + // Preserve method for throw_AbstractMethodErrorVerbose. + __ mov(rcx, rbx); // Receiver subtype check against REFC. // Superklass in rax. Subklass in rdx. Blows rcx, rdi. __ lookup_interface_method(// inputs: rec. class, interface, itable index @@ -3893,8 +3895,10 @@ __ subl(rbx, Method::itable_index_max); __ negl(rbx); + // Preserve recvKlass for throw_AbstractMethodErrorVerbose. + __ mov(rlocals, rdx); __ lookup_interface_method(// inputs: rec. class, interface, itable index - rdx, rax, rbx, + rlocals, rax, rbx, // outputs: method, scan temp. reg rbx, rbcp, no_such_interface); @@ -3926,8 +3930,19 @@ __ pop(rbx); // pop return address (pushed by prepare_invoke) __ restore_bcp(); // rbcp must be correct for exception handler (was destroyed) __ restore_locals(); // make sure locals pointer is correct as well (was destroyed) - __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodError)); - // the call_VM checks for exception, so we should never return here. + // Pass arguments for generating a verbose error message. +#ifdef _LP64 + Register recvKlass = c_rarg1; + Register method = c_rarg2; + if (recvKlass != rdx) { __ movq(recvKlass, rdx); } + if (method != rcx) { __ movq(method, rcx); } +#else + Register recvKlass = rdx; + Register method = rcx; +#endif + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_AbstractMethodErrorVerbose), + recvKlass, method); + // The call_VM checks for exception, so we should never return here. __ should_not_reach_here(); __ bind(no_such_interface); @@ -3935,8 +3950,10 @@ __ pop(rbx); // pop return address (pushed by prepare_invoke) __ restore_bcp(); // rbcp must be correct for exception handler (was destroyed) __ restore_locals(); // make sure locals pointer is correct as well (was destroyed) - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_IncompatibleClassChangeError)); + // Pass arguments for generating a verbose error message. + LP64_ONLY( if (recvKlass != rdx) { __ movq(recvKlass, rdx); } ) + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::throw_IncompatibleClassChangeErrorVerbose), + recvKlass, rax); // the call_VM checks for exception, so we should never return here. __ should_not_reach_here(); } diff --git a/src/hotspot/cpu/x86/vtableStubs_x86_32.cpp b/src/hotspot/cpu/x86/vtableStubs_x86_32.cpp --- a/src/hotspot/cpu/x86/vtableStubs_x86_32.cpp +++ b/src/hotspot/cpu/x86/vtableStubs_x86_32.cpp @@ -212,7 +212,12 @@ __ jmp(Address(method, Method::from_compiled_offset())); __ bind(L_no_such_interface); - __ jump(RuntimeAddress(StubRoutines::throw_IncompatibleClassChangeError_entry())); + // Handle IncompatibleClassChangeError in itable stubs. + // More detailed error message. + // We force resolving of the call site by jumping to the "handle + // wrong method" stub, and so let the interpreter runtime do all the + // dirty work. + __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); __ flush(); diff --git a/src/hotspot/cpu/x86/vtableStubs_x86_64.cpp b/src/hotspot/cpu/x86/vtableStubs_x86_64.cpp --- a/src/hotspot/cpu/x86/vtableStubs_x86_64.cpp +++ b/src/hotspot/cpu/x86/vtableStubs_x86_64.cpp @@ -182,10 +182,10 @@ const Register method = rbx; __ load_klass(recv_klass_reg, j_rarg0); // restore recv_klass_reg __ lookup_interface_method(// inputs: rec. class, interface, itable index - recv_klass_reg, holder_klass_reg, itable_index, - // outputs: method, scan temp. reg - method, temp_reg, - L_no_such_interface); + recv_klass_reg, holder_klass_reg, itable_index, + // outputs: method, scan temp. reg + method, temp_reg, + L_no_such_interface); // If we take a trap while this arg is on the stack we will not // be able to walk the stack properly. This is not an issue except @@ -213,7 +213,12 @@ __ jmp(Address(method, Method::from_compiled_offset())); __ bind(L_no_such_interface); - __ jump(RuntimeAddress(StubRoutines::throw_IncompatibleClassChangeError_entry())); + // Handle IncompatibleClassChangeError in itable stubs. + // More detailed error message. + // We force resolving of the call site by jumping to the "handle + // wrong method" stub, and so let the interpreter runtime do all the + // dirty work. + __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); __ flush(); diff --git a/src/hotspot/share/interpreter/bytecodeInterpreter.cpp b/src/hotspot/share/interpreter/bytecodeInterpreter.cpp --- a/src/hotspot/share/interpreter/bytecodeInterpreter.cpp +++ b/src/hotspot/share/interpreter/bytecodeInterpreter.cpp @@ -2588,17 +2588,19 @@ if (ki->interface_klass() == iclass) break; } // If the interface isn't found, this class doesn't implement this - // interface. The link resolver checks this but only for the first + // interface. The link resolver checks this but only for the first // time this interface is called. if (i == int2->itable_length()) { - VM_JAVA_ERROR(vmSymbols::java_lang_IncompatibleClassChangeError(), "", note_no_trap); + CALL_VM(InterpreterRuntime::throw_IncompatibleClassChangeErrorVerbose(THREAD, rcvr->klass(), iclass), + handle_exception); } int mindex = interface_method->itable_index(); itableMethodEntry* im = ki->first_method_entry(rcvr->klass()); callee = im[mindex].method(); if (callee == NULL) { - VM_JAVA_ERROR(vmSymbols::java_lang_AbstractMethodError(), "", note_no_trap); + CALL_VM(InterpreterRuntime::throw_AbstractMethodErrorVerbose(THREAD, rcvr->klass(), interface_method), + handle_exception); } // Profile virtual call. diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -486,8 +486,8 @@ ResourceMark rm(thread); stringStream tempst; tempst.print("interpreter method <%s>\n" - " at bci %d for thread " INTPTR_FORMAT, - h_method->print_value_string(), current_bci, p2i(thread)); + " at bci %d for thread " INTPTR_FORMAT " (%s)", + h_method->print_value_string(), current_bci, p2i(thread), thread->name()); Exceptions::log_exception(h_exception, tempst); } // Don't go paging in something which won't be used. @@ -581,11 +581,45 @@ THROW(vmSymbols::java_lang_AbstractMethodError()); IRT_END +// This method is called from the "abstract_entry" of the interpreter. +// At that point, the arguments have already been removed from the stack +// and therefore we don't have the receiver object at our fingertips. (Though, +// on some platforms the receiver still resides in a register...). Thus, +// we have no choice but print an error message not containing the receiver +// type. +IRT_ENTRY(void, InterpreterRuntime::throw_AbstractMethodErrorWithMethod(JavaThread* thread, + Method* missingMethod)) + ResourceMark rm(thread); + assert(missingMethod != NULL, "sanity"); + methodHandle m(thread, missingMethod); + LinkResolver::throw_abstract_method_error(m, THREAD); +IRT_END + +IRT_ENTRY(void, InterpreterRuntime::throw_AbstractMethodErrorVerbose(JavaThread* thread, + Klass* recvKlass, + Method* missingMethod)) + ResourceMark rm(thread); + methodHandle mh = methodHandle(thread, missingMethod); + LinkResolver::throw_abstract_method_error(mh, recvKlass, THREAD); +IRT_END + IRT_ENTRY(void, InterpreterRuntime::throw_IncompatibleClassChangeError(JavaThread* thread)) THROW(vmSymbols::java_lang_IncompatibleClassChangeError()); IRT_END +IRT_ENTRY(void, InterpreterRuntime::throw_IncompatibleClassChangeErrorVerbose(JavaThread* thread, + Klass* recvKlass, + Klass* interfaceKlass)) + ResourceMark rm(thread); + char buf[1000]; + buf[0] = '\0'; + jio_snprintf(buf, sizeof(buf), + "Class %s does not implement the requested interface %s", + recvKlass ? recvKlass->external_name() : "NULL", + interfaceKlass ? interfaceKlass->external_name() : "NULL"); + THROW_MSG(vmSymbols::java_lang_IncompatibleClassChangeError(), buf); +IRT_END //------------------------------------------------------------------------------------------------------------------------ // Fields diff --git a/src/hotspot/share/interpreter/interpreterRuntime.hpp b/src/hotspot/share/interpreter/interpreterRuntime.hpp --- a/src/hotspot/share/interpreter/interpreterRuntime.hpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.hpp @@ -118,7 +118,15 @@ // Exceptions thrown by the interpreter static void throw_AbstractMethodError(JavaThread* thread); + static void throw_AbstractMethodErrorWithMethod(JavaThread* thread, Method* oop); + static void throw_AbstractMethodErrorVerbose(JavaThread* thread, + Klass* recvKlass, + Method* missingMethod); + static void throw_IncompatibleClassChangeError(JavaThread* thread); + static void throw_IncompatibleClassChangeErrorVerbose(JavaThread* thread, + Klass* resc, + Klass* interfaceKlass); static void throw_StackOverflowError(JavaThread* thread); static void throw_delayed_StackOverflowError(JavaThread* thread); static void throw_ArrayIndexOutOfBoundsException(JavaThread* thread, char* name, jint index); diff --git a/src/hotspot/share/interpreter/linkResolver.cpp b/src/hotspot/share/interpreter/linkResolver.cpp --- a/src/hotspot/share/interpreter/linkResolver.cpp +++ b/src/hotspot/share/interpreter/linkResolver.cpp @@ -1343,8 +1343,7 @@ // do lookup based on receiver klass using the vtable index if (resolved_method->method_holder()->is_interface()) { // default or miranda method - vtable_index = vtable_index_of_interface_method(resolved_klass, - resolved_method); + vtable_index = vtable_index_of_interface_method(resolved_klass, resolved_method); assert(vtable_index >= 0 , "we should have valid vtable index at this point"); selected_method = methodHandle(THREAD, recv_klass->method_at_vtable(vtable_index)); @@ -1354,7 +1353,7 @@ assert(!resolved_method->has_itable_index(), ""); vtable_index = resolved_method->vtable_index(); // We could get a negative vtable_index for final methods, - // because as an optimization they are they are never put in the vtable, + // because as an optimization they are never put in the vtable, // unless they override an existing method. // If we do get a negative, it means the resolved method is the the selected // method, and it can never be changed by an override. @@ -1368,20 +1367,13 @@ // check if method exists if (selected_method.is_null()) { - ResourceMark rm(THREAD); - THROW_MSG(vmSymbols::java_lang_AbstractMethodError(), - Method::name_and_sig_as_C_string(resolved_klass, - resolved_method->name(), - resolved_method->signature())); + throw_abstract_method_error(resolved_method, recv_klass, CHECK); } // check if abstract if (check_null_and_abstract && selected_method->is_abstract()) { - ResourceMark rm(THREAD); - THROW_MSG(vmSymbols::java_lang_AbstractMethodError(), - Method::name_and_sig_as_C_string(resolved_klass, - selected_method->name(), - selected_method->signature())); + // Pass arguments for generating a verbose error message. + throw_abstract_method_error(resolved_method, selected_method, recv_klass, CHECK); } if (log_develop_is_enabled(Trace, vtables)) { @@ -1437,53 +1429,46 @@ // do lookup based on receiver klass // This search must match the linktime preparation search for itable initialization // to correctly enforce loader constraints for interface method inheritance - methodHandle sel_method = lookup_instance_method_in_klasses(recv_klass, + methodHandle selected_method = lookup_instance_method_in_klasses(recv_klass, resolved_method->name(), resolved_method->signature(), CHECK); - if (sel_method.is_null() && !check_null_and_abstract) { + if (selected_method.is_null() && !check_null_and_abstract) { // In theory this is a harmless placeholder value, but // in practice leaving in null affects the nsk default method tests. // This needs further study. - sel_method = resolved_method; + selected_method = resolved_method; } // check if method exists - if (sel_method.is_null()) { - ResourceMark rm(THREAD); - THROW_MSG(vmSymbols::java_lang_AbstractMethodError(), - Method::name_and_sig_as_C_string(recv_klass, - resolved_method->name(), - resolved_method->signature())); + if (selected_method.is_null()) { + // Pass arguments for generating a verbose error message. + throw_abstract_method_error(resolved_method, recv_klass, CHECK); } // check access - // Throw Illegal Access Error if sel_method is not public. - if (!sel_method->is_public()) { + // Throw Illegal Access Error if selected_method is not public. + if (!selected_method->is_public()) { ResourceMark rm(THREAD); THROW_MSG(vmSymbols::java_lang_IllegalAccessError(), Method::name_and_sig_as_C_string(recv_klass, - sel_method->name(), - sel_method->signature())); + selected_method->name(), + selected_method->signature())); } // check if abstract - if (check_null_and_abstract && sel_method->is_abstract()) { - ResourceMark rm(THREAD); - THROW_MSG(vmSymbols::java_lang_AbstractMethodError(), - Method::name_and_sig_as_C_string(recv_klass, - sel_method->name(), - sel_method->signature())); + if (check_null_and_abstract && selected_method->is_abstract()) { + throw_abstract_method_error(resolved_method, selected_method, recv_klass, CHECK); } if (log_develop_is_enabled(Trace, itables)) { trace_method_resolution("invokeinterface selected method: receiver-class:", - recv_klass, resolved_klass, sel_method, true); + recv_klass, resolved_klass, selected_method, true); } // setup result if (!resolved_method->has_itable_index()) { int vtable_index = resolved_method->vtable_index(); - assert(vtable_index == sel_method->vtable_index(), "sanity check"); - result.set_virtual(resolved_klass, recv_klass, resolved_method, sel_method, vtable_index, CHECK); + assert(vtable_index == selected_method->vtable_index(), "sanity check"); + result.set_virtual(resolved_klass, recv_klass, resolved_method, selected_method, vtable_index, CHECK); } else { int itable_index = resolved_method()->itable_index(); - result.set_interface(resolved_klass, recv_klass, resolved_method, sel_method, itable_index, CHECK); + result.set_interface(resolved_klass, recv_klass, resolved_method, selected_method, itable_index, CHECK); } } @@ -1773,3 +1758,38 @@ result.set_handle(resolved_method, resolved_appendix, resolved_method_type, THREAD); Exceptions::wrap_dynamic_exception(CHECK); } + +// Selected method is abstract. +void LinkResolver::throw_abstract_method_error(const methodHandle& resolved_method, + const methodHandle& selected_method, + Klass *recv_klass, TRAPS) { + Klass *resolved_klass = resolved_method->method_holder(); + ResourceMark rm(THREAD); + stringStream ss; + + if (recv_klass != NULL) { + ss.print("Receiver class %s does not define or inherit an " + "implementation of the", + recv_klass->external_name()); + } else { + ss.print("Missing implementation of"); + } + + assert(resolved_method.not_null(), "Sanity"); + ss.print(" resolved method %s%s%s%s of %s %s.", + resolved_method->is_abstract() ? "abstract " : "", + resolved_method->is_private() ? "private " : "", + resolved_method->name()->as_C_string(), + resolved_method->signature()->as_C_string(), + resolved_klass->external_kind(), + resolved_klass->external_name()); + + if (selected_method.not_null() && !(resolved_method == selected_method)) { + ss.print(" Selected method is %s%s%s.", + selected_method->is_abstract() ? "abstract " : "", + selected_method->is_private() ? "private " : "", + selected_method->name_and_sig_as_C_string()); + } + + THROW_MSG(vmSymbols::java_lang_AbstractMethodError(), ss.as_string()); +} diff --git a/src/hotspot/share/interpreter/linkResolver.hpp b/src/hotspot/share/interpreter/linkResolver.hpp --- a/src/hotspot/share/interpreter/linkResolver.hpp +++ b/src/hotspot/share/interpreter/linkResolver.hpp @@ -347,5 +347,19 @@ static void resolve_invoke(CallInfo& result, Handle& recv, const methodHandle& attached_method, Bytecodes::Code byte, TRAPS); + + public: + // Only resolved method known. + static void throw_abstract_method_error(const methodHandle& resolved_method, TRAPS) { + throw_abstract_method_error(resolved_method, NULL, NULL, CHECK); + } + // Resolved method and receiver klass know. + static void throw_abstract_method_error(const methodHandle& resolved_method, Klass *recv_klass, TRAPS) { + throw_abstract_method_error(resolved_method, NULL, recv_klass, CHECK); + } + // Selected method is abstract. + static void throw_abstract_method_error(const methodHandle& resolved_method, + const methodHandle& selected_method, + Klass *recv_klass, TRAPS); }; #endif // SHARE_VM_INTERPRETER_LINKRESOLVER_HPP diff --git a/src/hotspot/share/oops/klass.cpp b/src/hotspot/share/oops/klass.cpp --- a/src/hotspot/share/oops/klass.cpp +++ b/src/hotspot/share/oops/klass.cpp @@ -636,12 +636,17 @@ return name()->as_klass_external_name(); } - const char* Klass::signature_name() const { if (name() == NULL) return ""; return name()->as_C_string(); } +const char* Klass::external_kind() const { + if (is_interface()) return "interface"; + if (is_abstract()) return "abstract class"; + return "class"; +} + // Unless overridden, modifier_flags is 0. jint Klass::compute_modifier_flags(TRAPS) const { return 0; diff --git a/src/hotspot/share/oops/klass.hpp b/src/hotspot/share/oops/klass.hpp --- a/src/hotspot/share/oops/klass.hpp +++ b/src/hotspot/share/oops/klass.hpp @@ -546,6 +546,9 @@ const char* class_loader_and_module_name() const; + // Returns "interface", "abstract class" or "class". + const char* external_kind() const; + // type testing operations #ifdef ASSERT protected: diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -838,9 +838,15 @@ if (vt_stub->is_abstract_method_error(pc)) { assert(!vt_stub->is_vtable_stub(), "should never see AbstractMethodErrors from vtable-type VtableStubs"); Events::log_exception(thread, "AbstractMethodError at " INTPTR_FORMAT, p2i(pc)); - return StubRoutines::throw_AbstractMethodError_entry(); + // Instead of throwing the abstract method error here directly, we re-resolve + // and will throw the AbstractMethodError during resolve. As a result, we'll + // get a more detailed error message. + return SharedRuntime::get_handle_wrong_method_stub(); } else { Events::log_exception(thread, "NullPointerException at vtable entry " INTPTR_FORMAT, p2i(pc)); + // Assert that the signal comes from the expected location in stub code. + assert(vt_stub->is_null_pointer_exception(pc), + "obtained signal from unexpected location in stub code"); return StubRoutines::throw_NullPointerException_at_call_entry(); } } else { @@ -1453,7 +1459,29 @@ // Handle abstract method call JRT_BLOCK_ENTRY(address, SharedRuntime::handle_wrong_method_abstract(JavaThread* thread)) - return StubRoutines::throw_AbstractMethodError_entry(); + // Verbose error message for AbstractMethodError. + // Get the called method from the invoke bytecode. + vframeStream vfst(thread, true); + assert(!vfst.at_end(), "Java frame must exist"); + methodHandle caller(vfst.method()); + Bytecode_invoke invoke(caller, vfst.bci()); + DEBUG_ONLY( invoke.verify(); ) + + // Find the compiled caller frame. + RegisterMap reg_map(thread); + frame stubFrame = thread->last_frame(); + assert(stubFrame.is_runtime_frame(), "must be"); + frame callerFrame = stubFrame.sender(®_map); + assert(callerFrame.is_compiled_frame(), "must be"); + + // Install exception and return forward entry. + JRT_BLOCK + methodHandle callee = invoke.static_target(thread); + assert(!callee.is_null() && invoke.has_receiver(), "or we should not be here"); + oop recv = callerFrame.retrieve_receiver(®_map); + LinkResolver::throw_abstract_method_error(callee, recv->klass(), thread); + JRT_BLOCK_END + return StubRoutines::forward_exception_entry(); JRT_END diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME1_E.jasm b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME1_E.jasm new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME1_E.jasm @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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. + */ + +/* Method aFunctionOfMyInterface:"()Ljava/lang/String;" is missing in this implementation to cause error. */ + +class AME1_E extends AME1_B implements AME1_C { + + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method AME1_B."":"()V"; + return; + } + + public Method firstAbstractMethod:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + public Method secondAbstractMethod:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + /* Missing to cause error. + public Method anAbstractMethod:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + */ + + public Method firstFunctionOfMyInterface0:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + public Method secondFunctionOfMyInterface0:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + public Method firstFunctionOfMyInterface:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + public Method secondFunctionOfMyInterface:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + /* Missing to cause error. + public Method aFunctionOfMyInterface:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + */ +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME2_C.jasm b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME2_C.jasm new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME2_C.jasm @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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. + */ + +/* Method aFunctionOfMyInterface() is missing in this implementation to cause error. */ + +class AME2_C extends AME2_B { + + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method AME2_B."":"()V"; + return; + } + + public Method fun2:"()V" + stack 0 locals 1 + { + return; + } +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME3_C.jasm b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME3_C.jasm new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME3_C.jasm @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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. + */ + +/* Method ma() is missing in this implementation to cause error. */ + +class AME3_C extends AME3_B { + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method AME3_B."":()V; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME4_E.jasm b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME4_E.jasm new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME4_E.jasm @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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. + */ + + +/* Method ma() is missing in this implementation to cause error. */ + +class AME4_E extends AME4_B { + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method AME4_B."":()V; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME5_B.jasm b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME5_B.jasm new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME5_B.jasm @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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. + */ + +/* Method mc() is missing in this implementation to cause error. */ + +class AME5_B extends AME5_A { + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method AME5_A."":()V; + return; + } + + public Method ma:"()V" + stack 2 locals 1 + { + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + ldc String "B.ma() "; + invokevirtual Method java/io/PrintStream.print:"(Ljava/lang/String;)V"; + return; + } + + public Method mb:"()V" + stack 2 locals 1 + { + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + ldc String "B.mb() "; + invokevirtual Method java/io/PrintStream.print:"(Ljava/lang/String;)V"; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME6_B.jasm b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME6_B.jasm new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AME6_B.jasm @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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. + */ + +/* Method mc() is missing in this implementation to cause error. */ + +class AME6_B implements AME6_A { + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + public Method ma:"()V" + stack 2 locals 1 + { + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + ldc String "B.ma() "; + invokevirtual Method java/io/PrintStream.print:"(Ljava/lang/String;)V"; + return; + } + + public Method mb:"()V" + stack 2 locals 1 + { + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + ldc String "B.mb() "; + invokevirtual Method java/io/PrintStream.print:"(Ljava/lang/String;)V"; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AbstractMethodErrorTest.java b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AbstractMethodErrorTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/AbstractMethodError/AbstractMethodErrorTest.java @@ -0,0 +1,892 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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 + * @summary Check that the verbose message of the AME is printed correctly. + * @requires !(os.arch=="arm") + * @library /test/lib / + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller sun.hotspot.WhiteBox sun.hotspot.WhiteBox$WhiteBoxPermission + * @compile AbstractMethodErrorTest.java + * @compile AME1_E.jasm AME2_C.jasm AME3_C.jasm AME4_E.jasm AME5_B.jasm AME6_B.jasm + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-BackgroundCompilation -XX:-Inline + * -XX:CompileCommand=exclude,AbstractMethodErrorTest::test_ame1 + * AbstractMethodErrorTest + */ + +import sun.hotspot.WhiteBox; +import compiler.whitebox.CompilerWhiteBoxTest; +import java.lang.reflect.Method; + +// This test assembles an errorneous installation of classes. +// First, compile the test by @compile. This results in a legal set +// of classes. +// Then, with jasm, generate incompatible classes that overwrite +// the class files in the build directory. +// Last, call the real test throwing an AbstractMethodError and +// check the message generated. +public class AbstractMethodErrorTest { + + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + private static boolean enableChecks = true; + + public static void setup_test() { + // Assure all exceptions are loaded. + new AbstractMethodError(); + new IncompatibleClassChangeError(); + + enableChecks = false; + // Warmup + System.out.println("warmup:"); + test_ame5_compiled_vtable_stub(); + test_ame6_compiled_itable_stub(); + enableChecks = true; + + // Compile + try { + Method method = AbstractMethodErrorTest.class.getMethod("test_ame5_compiled_vtable_stub"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException(method.getName() + " is not compiled"); + } + method = AbstractMethodErrorTest.class.getMethod("test_ame6_compiled_itable_stub"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException(method.getName() + " is not compiled"); + } + method = AME5_C.class.getMethod("c"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException("AME5_C." + method.getName() + " is not compiled"); + } + method = AME5_D.class.getMethod("c"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException("AME5_D." + method.getName() + " is not compiled"); + } + method = AME5_E.class.getMethod("c"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException("AME5_E." + method.getName() + " is not compiled"); + } + method = AME6_C.class.getMethod("c"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException("AME6_C." + method.getName() + " is not compiled"); + } + method = AME6_D.class.getMethod("c"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException("AME6_D." + method.getName() + " is not compiled"); + } + method = AME6_E.class.getMethod("c"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException("AME6_E." + method.getName() + " is not compiled"); + } + } catch (NoSuchMethodException e) { } + } + + private static String expectedErrorMessageAME1_1 = + "Missing implementation of resolved method abstract " + + "anAbstractMethod()Ljava/lang/String; of abstract class AME1_B."; + private static String expectedErrorMessageAME1_2 = + "Receiver class AME1_E does not define or inherit an implementation of the " + + "resolved method abstract aFunctionOfMyInterface()Ljava/lang/String; of " + + "interface AME1_C."; + + public static void test_ame1() { + AME1_B objectAbstract = new AME1_D(); + AME1_C objectInterface = new AME1_D(); + objectInterface.secondFunctionOfMyInterface(); + objectAbstract.anAbstractMethod(); + objectInterface.aFunctionOfMyInterface(); + + try { + objectAbstract = new AME1_E(); + // AbstractMethodError gets thrown in the interpreter at: + // InterpreterGenerator::generate_abstract_entry + objectAbstract.anAbstractMethod(); + throw new RuntimeException("Expected AbstractRuntimeError was not thrown."); + } catch (AbstractMethodError e) { + String errorMsg = e.getMessage(); + if (errorMsg == null) { + throw new RuntimeException("Caught AbstractMethodError with empty message."); + } else if (!errorMsg.equals(expectedErrorMessageAME1_1)) { + System.out.println("Expected: " + expectedErrorMessageAME1_1 + "\n" + + "but got: " + errorMsg); + throw new RuntimeException("Wrong error message of AbstractMethodError."); + } + } catch (RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException("Caught unexpected exception: " + e); + } + + try { + objectInterface = new AME1_E(); + // AbstractMethodError gets thrown in: + // TemplateTable::invokeinterface or C-Interpreter loop + objectInterface.aFunctionOfMyInterface(); + throw new RuntimeException("Expected AbstractRuntimeError was not thrown."); + } catch (AbstractMethodError e) { + String errorMsg = e.getMessage(); + if (errorMsg == null) { + throw new RuntimeException("Caught AbstractMethodError with empty message."); + } else if (!errorMsg.equals(expectedErrorMessageAME1_2)) { + // Thrown via InterpreterRuntime::throw_AbstractMethodErrorVerbose(). + System.out.println("Expected: " + expectedErrorMessageAME1_2 + "\n" + + "but got: " + errorMsg); + throw new RuntimeException("Wrong error message of AbstractMethodError."); + } + } catch (Throwable e) { + throw new RuntimeException("Caught unexpected exception: " + e); + } + } + + private static String expectedErrorMessageAME2_Interpreted = + "Missing implementation of resolved method abstract " + + "aFunctionOfMyInterface()V of interface AME2_A."; + private static String expectedErrorMessageAME2_Compiled = + "Receiver class AME2_C does not define or inherit an implementation of the resolved method " + + "abstract aFunctionOfMyInterface()V of interface AME2_A."; + + public AbstractMethodErrorTest() throws InstantiationException, IllegalAccessException { + try { + AME2_B myAbstract = new ImplementsAllFunctions(); + myAbstract.fun2(); + myAbstract.aFunctionOfMyInterface(); + + // AME2_C does not implement the method + // aFunctionOfMyInterface(). Expected runtime behavior is + // throwing an AbstractMethodError. + // The error will be thrown via throw_AbstractMethodErrorWithMethod() + // if the template interpreter calls an abstract method by + // entering the abstract method entry. + myAbstract = new AME2_C(); + myAbstract.fun2(); + myAbstract.aFunctionOfMyInterface(); + } catch (SecurityException e) { + e.printStackTrace(); + } + } + + // Loop so that method gets eventually compiled/osred. + public static void test_ame2() throws Exception { + boolean seenInterpreted = false; + boolean seenCompiled = false; + + // Loop to test both, the interpreted and the compiled case. + for (int i = 0; i < 10000 && !(seenInterpreted && seenCompiled); ++i) { + try { + // Supposed to throw AME with verbose message. + new AbstractMethodErrorTest(); + + throw new RuntimeException("Expected AbstractMethodError was not thrown."); + } catch (AbstractMethodError e) { + String errorMsg = e.getMessage(); + + // Check the message obtained. + if (errorMsg == null) { + throw new RuntimeException("Caught AbstractMethodError with empty message."); + } else if (errorMsg.equals(expectedErrorMessageAME2_Interpreted)) { + seenInterpreted = true; + } else if (errorMsg.equals(expectedErrorMessageAME2_Compiled)) { + // Sparc and the other platforms behave differently here: + // Sparc throws the exception via SharedRuntime::handle_wrong_method_abstract(), + // x86, ppc and s390 via LinkResolver::runtime_resolve_virtual_method(). Thus, + // sparc misses the test case for LinkResolver::runtime_resolve_virtual_method(). + seenCompiled = true; + } else { + System.out.println("Expected: " + expectedErrorMessageAME2_Interpreted + "\n" + + "or: " + expectedErrorMessageAME2_Compiled + "\n" + + "but got: " + errorMsg); + throw new RuntimeException("Wrong error message of AbstractMethodError."); + } + } + } + if (!(seenInterpreted && seenCompiled)) { + if (seenInterpreted) { System.out.println("Saw interpreted message."); } + if (seenCompiled) { System.out.println("Saw compiled message."); } + throw new RuntimeException("Test did not produce wrong error messages for AbstractMethodError, " + + "but it did not test both cases (interpreted and compiled)."); + } + } + + private static String expectedErrorMessageAME3_1 = + "Receiver class AME3_C does not define or inherit an implementation of the resolved method " + + "ma()V of class AME3_A. Selected method is abstract AME3_B.ma()V."; + + // Testing abstract class that extends a class that has an implementation. + // Loop so that method gets eventually compiled/osred. + public static void test_ame3_1() throws Exception { + AME3_A c = new AME3_C(); + + try { + // Supposed to throw AME with verbose message. + c.ma(); + + throw new RuntimeException("Expected AbstractMethodError was not thrown."); + } catch (AbstractMethodError e) { + String errorMsg = e.getMessage(); + + // Check the message obtained. + if (errorMsg == null) { + throw new RuntimeException("Caught AbstractMethodError with empty message."); + } else if (errorMsg.equals(expectedErrorMessageAME3_1)) { + // Expected test case thrown via LinkResolver::runtime_resolve_virtual_method(). + } else { + System.out.println("Expected: " + expectedErrorMessageAME3_1 + "\n" + + "but got: " + errorMsg); + throw new RuntimeException("Wrong error message of AbstractMethodError."); + } + } + } + + private static String expectedErrorMessageAME3_2 = + "Receiver class AME3_C does not define or inherit an implementation of " + + "the resolved method abstract ma()V of abstract class AME3_B."; + + // Testing abstract class that extends a class that has an implementation. + // Loop so that method gets eventually compiled/osred. + public static void test_ame3_2() throws Exception { + AME3_C c = new AME3_C(); + + try { + // Supposed to throw AME with verbose message. + c.ma(); + + throw new RuntimeException("Expected AbstractMethodError was not thrown."); + } catch (AbstractMethodError e) { + String errorMsg = e.getMessage(); + + // Check the message obtained. + if (errorMsg == null) { + throw new RuntimeException("Caught AbstractMethodError with empty message."); + } else if (errorMsg.equals(expectedErrorMessageAME3_2)) { + // Expected test case thrown via LinkResolver::runtime_resolve_virtual_method(). + } else { + System.out.println("Expected: " + expectedErrorMessageAME3_2 + "\n" + + "but got: " + errorMsg); + throw new RuntimeException("Wrong error message of AbstractMethodError."); + } + } + } + + private static String expectedErrorMessageAME4 = + "Missing implementation of resolved method abstract ma()V of " + + "abstract class AME4_B."; + + // Testing abstract class that extends a class that has an implementation. + public static void test_ame4() throws Exception { + AME4_C c = new AME4_C(); + AME4_D d = new AME4_D(); + AME4_E e = new AME4_E(); // Errorneous. + + AME4_A a; + try { + // Test: calls errorneous e.ma() in the last iteration. + final int iterations = 10; + for (int i = 0; i < iterations; i++) { + a = e; + if (i % 2 == 0 && i < iterations - 1) { + a = c; + } + if (i % 2 == 1 && i < iterations - 1) { + a = d; + } + + // AbstractMethodError gets thrown in the interpreter at: + // InterpreterGenerator::generate_abstract_entry + a.ma(); + } + + throw new RuntimeException("Expected AbstractMethodError was not thrown."); + } catch (AbstractMethodError exc) { + System.out.println(); + String errorMsg = exc.getMessage(); + + // Check the message obtained. + if (enableChecks && errorMsg == null) { + throw new RuntimeException("Caught AbstractMethodError with empty message."); + } else if (errorMsg.equals(expectedErrorMessageAME4)) { + // Expected test case. + } else if (enableChecks) { + System.out.println("Expected: " + expectedErrorMessageAME4 + "\n" + + "but got: " + errorMsg); + throw new RuntimeException("Wrong error message of AbstractMethodError."); + } + } + } + + private static String expectedErrorMessageAME5_VtableStub = + "Receiver class AME5_B does not define or inherit an implementation of the resolved method abstract mc()V " + + "of abstract class AME5_A."; + + // AbstractMethodErrors detected in vtable stubs. + // Note: How can we verify that we really stepped through the vtable stub? + // - Bimorphic inlining should not happen since we have no profiling data when + // we compile the method + // - As a result, an inline cache call should be generated + // - This inline cache call is patched into a real vtable call at the first + // re-resolve, which happens constantly during the first 10 iterations of the loop. + // => we should be fine! :-) + public static void test_ame5_compiled_vtable_stub() { + // Allocated the objects we need and call a valid method. + boolean caught_ame = false; + AME5_B b = new AME5_B(); + AME5_C c = new AME5_C(); + AME5_D d = new AME5_D(); + AME5_E e = new AME5_E(); + b.ma(); + c.ma(); + d.ma(); + e.ma(); + + try { + final int iterations = 10; + // Test: calls b.c() in the last iteration. + for (int i = 0; i < iterations; i++) { + AME5_A a = b; + if (i % 3 == 0 && i < iterations - 1) { + a = c; + } + if (i % 3 == 1 && i < iterations - 1) { + a = d; + } + if (i % 3 == 2 && i < iterations - 1) { + a = e; + } + + a.mc(); + } + System.out.println(); + } catch (AbstractMethodError exc) { + caught_ame = true; + System.out.println(); + String errorMsg = exc.getMessage(); + if (enableChecks && errorMsg == null) { + System.out.println(exc); + throw new RuntimeException("Empty error message of AbstractMethodError."); + } + if (enableChecks && + !errorMsg.equals(expectedErrorMessageAME5_VtableStub)) { + // Thrown via SharedRuntime::handle_wrong_method_abstract(). + System.out.println("Expected: " + expectedErrorMessageAME5_VtableStub + "\n" + + "but got: " + errorMsg); + System.out.println(exc); + throw new RuntimeException("Wrong error message of AbstractMethodError."); + } + if (enableChecks) { + System.out.println("Passed with message: " + errorMsg); + } + } catch (Throwable exc) { + + throw exc; + } + + // Check that we got the exception at some point. + if (enableChecks && !caught_ame) { + throw new RuntimeException("Expected AbstractMethodError was not thrown."); + } + } + + private static String expectedErrorMessageAME6_ItableStub = + "Receiver class AME6_B does not define or inherit an implementation of the resolved" + + " method abstract mc()V of interface AME6_A."; + + // ------------------------------------------------------------------------- + // AbstractMethodErrors detected in itable stubs. + // Note: How can we verify that we really stepped through the itable stub? + // - Bimorphic inlining should not happen since we have no profiling data when + // we compile the method + // - As a result, an inline cache call should be generated + // - This inline cache call is patched into a real vtable call at the first + // re-resolve, which happens constantly during the first 10 iterations of the loop. + // => we should be fine! :-) + public static void test_ame6_compiled_itable_stub() { + // Allocated the objects we need and call a valid method. + boolean caught_ame = false; + AME6_B b = new AME6_B(); + AME6_C c = new AME6_C(); + AME6_D d = new AME6_D(); + AME6_E e = new AME6_E(); + b.ma(); + c.ma(); + d.ma(); + e.ma(); + + try { + final int iterations = 10; + // Test: calls b.c() in the last iteration. + for (int i = 0; i < iterations; i++) { + AME6_A a = b; + if (i % 3 == 0 && i < iterations - 1) { + a = c; + } + if (i % 3 == 1 && i < iterations - 1) { + a = d; + } + if (i % 3 == 2 && i < iterations - 1) { + a = e; + } + a.mc(); + } + System.out.println(); + } catch (AbstractMethodError exc) { + caught_ame = true; + System.out.println(); + String errorMsg = exc.getMessage(); + if (enableChecks && errorMsg == null) { + System.out.println(exc); + throw new RuntimeException("Empty error message of AbstractMethodError."); + } + if (enableChecks && + !errorMsg.equals(expectedErrorMessageAME6_ItableStub)) { + // Thrown via LinkResolver::runtime_resolve_interface_method(). + System.out.println("Expected: " + expectedErrorMessageAME6_ItableStub + "\n" + + "but got: " + errorMsg); + System.out.println(exc); + throw new RuntimeException("Wrong error message of AbstractMethodError."); + } + if (enableChecks) { + System.out.println("Passed with message: " + errorMsg); + } + } catch (Throwable exc) { + throw exc; + } + + // Check that we got the exception at some point. + if (enableChecks && !caught_ame) { + throw new RuntimeException("Expected AbstractMethodError was not thrown."); + } + } + + + public static void main(String[] args) throws Exception { + setup_test(); + test_ame1(); + test_ame2(); + test_ame3_1(); + test_ame3_2(); + test_ame4(); + test_ame5_compiled_vtable_stub(); + test_ame6_compiled_itable_stub(); + } +} + +// Helper classes to test abstract method error. +// +// Errorneous versions of these classes are implemented in java +// assembler. + + +// ------------------------------------------------------------------------- +// This error should be detected interpreted. +// +// Class hierachy: +// +// C // interface, defines aFunctionOfMyInterface() +// | +// A | // interface +// | | +// B | // abstract class, defines anAbstractMethod() +// \ / +// E // errorneous class implementation lacks methods C::aFunctionOfMyInterface() +// B::anAbstractMethod() +interface AME1_A { + + public String firstFunctionOfMyInterface0(); + + public String secondFunctionOfMyInterface0(); +} + +abstract class AME1_B implements AME1_A { + + abstract public String firstAbstractMethod(); + + abstract public String secondAbstractMethod(); + + abstract public String anAbstractMethod(); +} + +interface AME1_C { + + public String firstFunctionOfMyInterface(); + + public String secondFunctionOfMyInterface(); + + public String aFunctionOfMyInterface(); +} + +class AME1_D extends AME1_B implements AME1_C { + + public AME1_D() { + } + + public String firstAbstractMethod() { + return this.getClass().getName(); + } + + public String secondAbstractMethod() { + return this.getClass().getName(); + } + + public String anAbstractMethod() { + return this.getClass().getName(); + } + + public String firstFunctionOfMyInterface0() { + return this.getClass().getName(); + } + + public String secondFunctionOfMyInterface0() { + return this.getClass().getName(); + } + + public String firstFunctionOfMyInterface() { + return this.getClass().getName(); + } + + public String secondFunctionOfMyInterface() { + return this.getClass().getName(); + } + + public String aFunctionOfMyInterface() { + return this.getClass().getName(); + } +} + +class AME1_E extends AME1_B implements AME1_C { + + public AME1_E() { + } + + public String firstAbstractMethod() { + return this.getClass().getName(); + } + + public String secondAbstractMethod() { + return this.getClass().getName(); + } + + // This method is missing in the .jasm implementation. + public String anAbstractMethod() { + return this.getClass().getName(); + } + + public String firstFunctionOfMyInterface0() { + return this.getClass().getName(); + } + + public String secondFunctionOfMyInterface0() { + return this.getClass().getName(); + } + + public String firstFunctionOfMyInterface() { + return this.getClass().getName(); + } + + public String secondFunctionOfMyInterface() { + return this.getClass().getName(); + } + + // This method is missing in the .jasm implementation. + public String aFunctionOfMyInterface() { + return this.getClass().getName(); + } +} + +// ------------------------------------------------------------------------- +// This error should be detected interpreted. +// +// Class hierachy: +// +// A // an interface declaring aFunctionOfMyInterface() +// | +// B // an abstract class +// | +// C // errorneous implementation lacks method A::aFunctionOfMyInterface() +// +interface AME2_A { + public void aFunctionOfMyInterface(); +} + +abstract class AME2_B implements AME2_A { + abstract public void fun2(); +} + +class ImplementsAllFunctions extends AME2_B { + + public ImplementsAllFunctions() {} + + public void fun2() { + //System.out.print("You called public void ImplementsAllFunctions::fun2().\n"); + } + + public void aFunctionOfMyInterface() { + //System.out.print("You called public void ImplementsAllFunctions::aFunctionOfMyInterface()\n"); + } +} + +class AME2_C extends AME2_B { + + public AME2_C() {} + + public void fun2() { + //System.out.print("You called public void AME2_C::fun2().\n"); + } + + // This method is missing in the .jasm implementation. + public void aFunctionOfMyInterface() { + //System.out.print("You called public void AME2_C::aFunctionOfMyInterface()\n"); + } +} + +// ----------------------------------------------------------------------- +// Test AbstractMethod error shadowing existing implementation. +// +// Class hierachy: +// +// A // a class implementing m() +// | +// B // an abstract class defining m() abstract +// | +// C // an errorneous class lacking an implementation of m() +// +class AME3_A { + public void ma() { + System.out.print("A.ma() "); + } +} + +abstract class AME3_B extends AME3_A { + public abstract void ma(); +} + +class AME3_C extends AME3_B { + // This method is missing in the .jasm implementation. + public void ma() { + System.out.print("C.ma() "); + } +} + +// ----------------------------------------------------------------------- +// Test AbstractMethod error shadowing existing implementation. In +// this test there are several subclasses of the abstract class. +// +// Class hierachy: +// +// A // A: a class implementing ma() +// | +// B // B: an abstract class defining ma() abstract +// / | \ +// C D E // E: an errorneous class lacking an implementation of ma() +// +class AME4_A { + public void ma() { + System.out.print("A.ma() "); + } +} + +abstract class AME4_B extends AME4_A { + public abstract void ma(); +} + +class AME4_C extends AME4_B { + public void ma() { + System.out.print("C.ma() "); + } +} + +class AME4_D extends AME4_B { + public void ma() { + System.out.print("D.ma() "); + } +} + +class AME4_E extends AME4_B { + // This method is missing in the .jasm implementation. + public void ma() { + System.out.print("E.ma() "); + } +} + +// ------------------------------------------------------------------------- +// This error should be detected while processing the vtable stub. +// +// Class hierachy: +// +// A__ // abstract +// /|\ \ +// C D E \ +// B // Bad class, missing method implementation. +// +// Test: +// - Call D.mc() / E.mc() / F.mc() several times to force real vtable call constrution +// - Call errorneous B.mc() in the end to raise the AbstraceMethodError + +abstract class AME5_A { + abstract void ma(); + abstract void mb(); + abstract void mc(); +} + +class AME5_B extends AME5_A { + void ma() { + System.out.print("B.ma() "); + } + + void mb() { + System.out.print("B.mb() "); + } + + // This method is missing in the .jasm implementation. + void mc() { + System.out.print("B.mc() "); + } +} + +class AME5_C extends AME5_A { + void ma() { + System.out.print("C.ma() "); + } + + void mb() { + System.out.print("C.mb() "); + } + + void mc() { + System.out.print("C.mc() "); + } +} + +class AME5_D extends AME5_A { + void ma() { + System.out.print("D.ma() "); + } + + void mb() { + System.out.print("D.mb() "); + } + + void mc() { + System.out.print("D.mc() "); + } +} + +class AME5_E extends AME5_A { + void ma() { + System.out.print("E.ma() "); + } + + void mb() { + System.out.print("E.mb() "); + } + + void mc() { + System.out.print("E.mc() "); + } +} + +//------------------------------------------------------------------------- +// Test AbstractMethod error detected while processing +// the itable stub. +// +// Class hierachy: +// +// A__ (interface) +// /|\ \ +// C D E \ +// B (bad class, missing method) +// +// Test: +// - Call D.mc() / E.mc() / F.mc() several times to force real itable call constrution +// - Call errorneous B.mc() in the end to raise the AbstraceMethodError + +interface AME6_A { + abstract void ma(); + abstract void mb(); + abstract void mc(); +} + +class AME6_B implements AME6_A { + public void ma() { + System.out.print("B.ma() "); + } + + public void mb() { + System.out.print("B.mb() "); + } + + // This method is missing in the .jasm implementation. + public void mc() { + System.out.print("B.mc() "); + } +} + +class AME6_C implements AME6_A { + public void ma() { + System.out.print("C.ma() "); + } + + public void mb() { + System.out.print("C.mb() "); + } + + public void mc() { + System.out.print("C.mc() "); + } +} + +class AME6_D implements AME6_A { + public void ma() { + System.out.print("D.ma() "); + } + + public void mb() { + System.out.print("D.mb() "); + } + + public void mc() { + System.out.print("D.mc() "); + } +} + +class AME6_E implements AME6_A { + public void ma() { + System.out.print("E.ma() "); + } + + public void mb() { + System.out.print("E.mb() "); + } + + public void mc() { + System.out.print("E.mc() "); + } +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/IncompatibleClassChangeError/ICC_B.jasm b/test/hotspot/jtreg/runtime/exceptionMsgs/IncompatibleClassChangeError/ICC_B.jasm new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/IncompatibleClassChangeError/ICC_B.jasm @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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. + */ + +class ICC_B implements ICC_iA { + + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + public Method a:"()V" + stack 2 locals 1 + { + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + ldc String "B.a()"; + invokevirtual Method java/io/PrintStream.print:"(Ljava/lang/String;)V"; + return; + } + + public Method b:"()V" + stack 2 locals 1 + { + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + ldc String "B.b()"; + invokevirtual Method java/io/PrintStream.print:"(Ljava/lang/String;)V"; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/IncompatibleClassChangeError/ImplementsSomeInterfaces.jasm b/test/hotspot/jtreg/runtime/exceptionMsgs/IncompatibleClassChangeError/ImplementsSomeInterfaces.jasm new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/IncompatibleClassChangeError/ImplementsSomeInterfaces.jasm @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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. + */ + + + + + +class ImplementsSomeInterfaces extends AbstractICCE0 { + + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method AbstractICCE0."":"()V"; + return; + } + + public Method firstAbstractMethod:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + public Method secondAbstractMethod:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + public Method firstFunctionOfMyInterface0:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + public Method secondFunctionOfMyInterface0:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + public Method firstFunctionOfMyInterface:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } + + public Method secondFunctionOfMyInterface:"()Ljava/lang/String;" + stack 1 locals 1 + { + aload_0; + invokevirtual Method java/lang/Object.getClass:"()Ljava/lang/Class;"; + invokevirtual Method java/lang/Class.getName:"()Ljava/lang/String;"; + areturn; + } +} diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/IncompatibleClassChangeError/IncompatibleClassChangeErrorTest.java b/test/hotspot/jtreg/runtime/exceptionMsgs/IncompatibleClassChangeError/IncompatibleClassChangeErrorTest.java new file mode 100644 --- /dev/null +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/IncompatibleClassChangeError/IncompatibleClassChangeErrorTest.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 SAP SE. 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 + * @summary Check that the verbose message of ICCE is printed correctly. + * The test forces errors in vtable stubs and interpreter. + * @requires !(os.arch=="arm") + * @library /test/lib / + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller sun.hotspot.WhiteBox sun.hotspot.WhiteBox$WhiteBoxPermission + * @compile IncompatibleClassChangeErrorTest.java + * @compile ImplementsSomeInterfaces.jasm ICC_B.jasm + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-BackgroundCompilation -XX:-Inline + * -XX:CompileCommand=exclude,IncompatibleClassChangeErrorTest::test_iccInt + * IncompatibleClassChangeErrorTest + */ + +import sun.hotspot.WhiteBox; +import compiler.whitebox.CompilerWhiteBoxTest; +import java.lang.reflect.Method; + +// This test assembles an errorneous installation of classes. +// First, compile the test by @compile. This results in a legal set +// of classes. +// Then, with jasm, generate incompatible classes that overwrite +// the class files in the build directory. +// Last, call the real tests throwing IncompatibleClassChangeErrors +// and check the messages generated. +public class IncompatibleClassChangeErrorTest { + + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + private static boolean enableChecks = true; + + private static String expectedErrorMessageInterpreted = + "Class ImplementsSomeInterfaces " + + "does not implement the requested interface InterfaceICCE1"; + private static String expectedErrorMessageCompiled = + "Class ICC_B does not implement the requested interface ICC_iB"; + // old message: "vtable stub" + + public static void setup_test() { + // Assure all exceptions are loaded. + new AbstractMethodError(); + new IncompatibleClassChangeError(); + + enableChecks = false; + // Warmup + System.out.println("warmup:"); + test_iccInt(); + test_icc_compiled_itable_stub(); + enableChecks = true; + + // Compile + try { + Method method = IncompatibleClassChangeErrorTest.class.getMethod("test_icc_compiled_itable_stub"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException(method.getName() + " is not compiled"); + } + method = ICC_C.class.getMethod("b"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException("ICC_C." + method.getName() + " is not compiled"); + } + method = ICC_D.class.getMethod("b"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException("ICC_D." + method.getName() + " is not compiled"); + } + method = ICC_E.class.getMethod("b"); + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!WHITE_BOX.isMethodCompiled(method)) { + throw new RuntimeException("ICC_E." + method.getName() + " is not compiled"); + } + } catch (NoSuchMethodException e) { } + System.out.println("warmup done."); + } + + // Should never be compiled. + public static void test_iccInt() { + boolean caught_icc = false; + try { + InterfaceICCE1 objectInterface = new ImplementsSomeInterfaces(); + // IncompatibleClassChangeError gets thrown in + // - TemplateTable::invokeinterface() + // - LinkResolver::runtime_resolve_interface_method() + objectInterface.aFunctionOfMyInterface(); + } catch (IncompatibleClassChangeError e) { + String errorMsg = e.getMessage(); + if (enableChecks && !errorMsg.equals(expectedErrorMessageInterpreted)) { + System.out.println("Expected: " + expectedErrorMessageInterpreted + "\n" + + "but got: " + errorMsg); + throw new RuntimeException("Wrong error message of IncompatibleClassChangeError."); + } + caught_icc = true; + } catch (Throwable e) { + throw new RuntimeException("Caught unexpected exception: " + e); + } + + // Check we got the exception. + if (!caught_icc) { + throw new RuntimeException("Expected IncompatibleClassChangeError was not thrown."); + } + } + + // ------------------------------------------------------------------------- + // Test AbstractMethodErrors detected in itable stubs. + // Note: How can we verify that we really stepped through the vtable stub? + // - Bimorphic inlining should not happen since we have no profiling data when + // we compile the method + // - As a result, an inline cache call should be generated + // - This inline cache call is patched into a real vtable call at the first + // re-resolve, which happens constantly during the first 10 iterations of the loop. + // => we should be fine! :-) + public static void test_icc_compiled_itable_stub() { + // Allocated the objects we need and call a valid method. + boolean caught_icc = false; + ICC_B b = new ICC_B(); + ICC_C c = new ICC_C(); + ICC_D d = new ICC_D(); + ICC_E e = new ICC_E(); + b.a(); + c.a(); + d.a(); + e.a(); + + try { + final int iterations = 10; + // Test: calls b.b() in the last iteration. + for (int i = 0; i < iterations; i++) { + ICC_iB a = b; + if (i % 3 == 0 && i < iterations - 1) { + a = c; + } + if (i % 3 == 1 && i < iterations - 1) { + a = d; + } + if (i % 3 == 2 && i < iterations - 1) { + a = e; + } + a.b(); + } + } catch (AbstractMethodError exc) { + // It's a subclass of IncompatibleClassChangeError, so we must catch this first. + System.out.println(); + System.out.println(exc); + if (enableChecks) { + String errorMsg = exc.getMessage(); + if (errorMsg == null) { + throw new RuntimeException("Caught unexpected AbstractMethodError with empty message."); + } + throw new RuntimeException("Caught unexpected AbstractMethodError."); + } + } catch (IncompatibleClassChangeError exc) { + caught_icc = true; + System.out.println(); + String errorMsg = exc.getMessage(); + if (enableChecks && errorMsg == null) { + System.out.println(exc); + throw new RuntimeException("Empty error message of IncompatibleClassChangeError."); + } + if (enableChecks && + !errorMsg.equals(expectedErrorMessageCompiled)) { + System.out.println("Expected: " + expectedErrorMessageCompiled + "\n" + + "but got: " + errorMsg); + System.out.println(exc); + throw new RuntimeException("Wrong error message of IncompatibleClassChangeError."); + } + if (enableChecks) { + System.out.println("Passed with message: " + errorMsg); + } + } catch (Throwable exc) { + throw exc; // new RuntimeException("Caught unexpected exception: " + exc); + } + + // Check we got the exception at some point. + if (enableChecks && !caught_icc) { + throw new RuntimeException("Expected IncompatibleClassChangeError was not thrown."); + } + } + + public static void main(String[] args) throws Exception { + setup_test(); + test_iccInt(); + test_icc_compiled_itable_stub(); + } +} + + +// Helper classes to test incompatible class change in interpreter. +// +// The test also contains .jasm files with implementations +// of the classes that shall generate the errors. + + +// I0 // interface defining aFunctionOfMyInterface() +// | +// | I1 // interface +// | | +// A0 | // abstract class +// \ / +// C // class not implementing I1 and +// not implementing I0::aFunctionOfMyInterface() +// +// Test is expected to throw error because of missing interface and not +// because of missing method. + +interface InterfaceICCE0 { + public String firstFunctionOfMyInterface0(); + public String secondFunctionOfMyInterface0(); +} + +interface InterfaceICCE1 { + + public String firstFunctionOfMyInterface(); + + public String secondFunctionOfMyInterface(); + + public String aFunctionOfMyInterface(); +} + +abstract class AbstractICCE0 implements InterfaceICCE0 { + abstract public String firstAbstractMethod(); + abstract public String secondAbstractMethod(); + + abstract public String anAbstractMethod(); +} + +class ImplementsSomeInterfaces extends + AbstractICCE0 + // This interface is missing in the .jasm implementation. + implements InterfaceICCE1 +{ + + public String firstAbstractMethod() { + return this.getClass().getName(); + } + + public String secondAbstractMethod() { + return this.getClass().getName(); + } + + // This method is missing in the .jasm implementation. + public String anAbstractMethod() { + return this.getClass().getName(); + } + + public String firstFunctionOfMyInterface0() { + return this.getClass().getName(); + } + + public String secondFunctionOfMyInterface0() { + return this.getClass().getName(); + } + + public String firstFunctionOfMyInterface() { + return this.getClass().getName(); + } + + public String secondFunctionOfMyInterface() { + return this.getClass().getName(); + } + + // This method is missing in the .jasm implementation. + public String aFunctionOfMyInterface() { + return this.getClass().getName(); + } +} + +// Helper classes to test incompatible class change in itable stub. +// +// Class hierachy: +// +// iA,iB (interfaces) +// /|\ \ +// C D E \ +// B (bad class, missing interface implementation) + +interface ICC_iA { + public void a(); +} + +interface ICC_iB { + public void b(); +} + +// This is the errorneous class. A variant of it not +// implementing ICC_iB is copied into the test before +// it is run. +class ICC_B implements ICC_iA, + // This interface is missing in the .jasm implementation. + ICC_iB { + public void a() { + System.out.print("B.a() "); + } + + public void b() { + System.out.print("B.b() "); + } +} + +class ICC_C implements ICC_iA, ICC_iB { + public void a() { + System.out.print("C.a() "); + } + + public void b() { + System.out.print("C.b() "); + } +} + +class ICC_D implements ICC_iA, ICC_iB { + public void a() { + System.out.print("D.a() "); + } + + public void b() { + System.out.print("D.b() "); + } +} + +class ICC_E implements ICC_iA, ICC_iB { + public void a() { + System.out.print("E.a() "); + } + + public void b() { + System.out.print("E.b() "); + } +}