8367002: Missing compiled exception handler for "recursive" exception

Reviewed-by: thartmann, kvn
This commit is contained in:
Dean Long 2025-10-22 22:01:31 +00:00
parent 2a8cbd944b
commit 0744db8366
6 changed files with 159 additions and 11 deletions

View File

@ -1804,10 +1804,11 @@ void Deoptimization::deoptimize(JavaThread* thread, frame fr, DeoptReason reason
deoptimize_single_frame(thread, fr, reason);
}
#if INCLUDE_JVMCI
address Deoptimization::deoptimize_for_missing_exception_handler(nmethod* nm) {
address Deoptimization::deoptimize_for_missing_exception_handler(nmethod* nm, bool make_not_entrant) {
// there is no exception handler for this pc => deoptimize
nm->make_not_entrant(nmethod::InvalidationReason::MISSING_EXCEPTION_HANDLER);
if (make_not_entrant) {
nm->make_not_entrant(nmethod::InvalidationReason::MISSING_EXCEPTION_HANDLER);
}
// Use Deoptimization::deoptimize for all of its side-effects:
// gathering traps statistics, logging...
@ -1821,6 +1822,15 @@ address Deoptimization::deoptimize_for_missing_exception_handler(nmethod* nm) {
frame runtime_frame = thread->last_frame();
frame caller_frame = runtime_frame.sender(&reg_map);
assert(caller_frame.cb()->as_nmethod_or_null() == nm, "expect top frame compiled method");
Deoptimization::deoptimize(thread, caller_frame, Deoptimization::Reason_not_compiled_exception_handler);
if (!nm->is_compiled_by_jvmci()) {
return SharedRuntime::deopt_blob()->unpack_with_exception_in_tls();
}
#if INCLUDE_JVMCI
// JVMCI support
vframe* vf = vframe::new_vframe(&caller_frame, &reg_map, thread);
compiledVFrame* cvf = compiledVFrame::cast(vf);
ScopeDesc* imm_scope = cvf->scope();
@ -1836,16 +1846,15 @@ address Deoptimization::deoptimize_for_missing_exception_handler(nmethod* nm) {
}
}
Deoptimization::deoptimize(thread, caller_frame, Deoptimization::Reason_not_compiled_exception_handler);
MethodData* trap_mdo = get_method_data(thread, methodHandle(thread, nm->method()), true);
if (trap_mdo != nullptr) {
trap_mdo->inc_trap_count(Deoptimization::Reason_not_compiled_exception_handler);
}
#endif
return SharedRuntime::deopt_blob()->unpack_with_exception_in_tls();
}
#endif
void Deoptimization::deoptimize_frame_internal(JavaThread* thread, intptr_t* id, DeoptReason reason) {
assert(thread == Thread::current() ||
@ -2748,10 +2757,10 @@ const char* Deoptimization::_trap_reason_name[] = {
"unstable_if",
"unstable_fused_if",
"receiver_constraint",
"not_compiled_exception_handler",
"short_running_loop" JVMCI_ONLY("_or_aliasing"),
#if INCLUDE_JVMCI
"transfer_to_interpreter",
"not_compiled_exception_handler",
"unresolved",
"jsr_mismatch",
#endif

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2025, 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
@ -117,11 +117,11 @@ class Deoptimization : AllStatic {
Reason_unstable_if, // a branch predicted always false was taken
Reason_unstable_fused_if, // fused two ifs that had each one untaken branch. One is now taken.
Reason_receiver_constraint, // receiver subtype check failed
Reason_not_compiled_exception_handler, // missing compiled exception handler
Reason_short_running_long_loop, // profile reports loop runs for small number of iterations
#if INCLUDE_JVMCI
Reason_aliasing = Reason_short_running_long_loop, // optimistic assumption about aliasing failed
Reason_transfer_to_interpreter, // explicit transferToInterpreter()
Reason_not_compiled_exception_handler,
Reason_unresolved,
Reason_jsr_mismatch,
#endif
@ -184,8 +184,8 @@ class Deoptimization : AllStatic {
// Deoptimizes a frame lazily. Deopt happens on return to the frame.
static void deoptimize(JavaThread* thread, frame fr, DeoptReason reason = Reason_constraint);
static address deoptimize_for_missing_exception_handler(nmethod* nm, bool make_not_entrant);
#if INCLUDE_JVMCI
static address deoptimize_for_missing_exception_handler(nmethod* nm);
static oop get_cached_box(AutoBoxObjectValue* bv, frame* fr, RegisterMap* reg_map, bool& cache_init_error, TRAPS);
#endif

View File

@ -791,7 +791,8 @@ address SharedRuntime::compute_compiled_exc_handler(nmethod* nm, address ret_pc,
if (t != nullptr) {
return nm->code_begin() + t->pco();
} else {
return Deoptimization::deoptimize_for_missing_exception_handler(nm);
bool make_not_entrant = true;
return Deoptimization::deoptimize_for_missing_exception_handler(nm, make_not_entrant);
}
}
#endif // INCLUDE_JVMCI
@ -847,6 +848,15 @@ address SharedRuntime::compute_compiled_exc_handler(nmethod* nm, address ret_pc,
ExceptionHandlerTable table(nm);
HandlerTableEntry *t = table.entry_for(catch_pco, handler_bci, scope_depth);
// If the compiler did not anticipate a recursive exception, resulting in an exception
// thrown from the catch bci, then the compiled exception handler might be missing.
// This is rare. Just deoptimize and let the interpreter handle it.
if (t == nullptr && recursive_exception_occurred) {
bool make_not_entrant = false;
return Deoptimization::deoptimize_for_missing_exception_handler(nm, make_not_entrant);
}
if (t == nullptr && (nm->is_compiled_by_c1() || handler_bci != -1)) {
// Allow abbreviated catch tables. The idea is to allow a method
// to materialize its exceptions without committing to the exact

View File

@ -1581,8 +1581,8 @@
declare_constant(Deoptimization::Reason_unstable_if) \
declare_constant(Deoptimization::Reason_unstable_fused_if) \
declare_constant(Deoptimization::Reason_receiver_constraint) \
declare_constant(Deoptimization::Reason_not_compiled_exception_handler) \
NOT_ZERO(JVMCI_ONLY(declare_constant(Deoptimization::Reason_transfer_to_interpreter))) \
NOT_ZERO(JVMCI_ONLY(declare_constant(Deoptimization::Reason_not_compiled_exception_handler))) \
NOT_ZERO(JVMCI_ONLY(declare_constant(Deoptimization::Reason_unresolved))) \
NOT_ZERO(JVMCI_ONLY(declare_constant(Deoptimization::Reason_jsr_mismatch))) \
declare_constant(Deoptimization::Reason_tenured) \

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2025, 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.
*/
super class IllegalAccessInCatch
version 52:0
{
/*
static int test() {
try {
return 1 / 0;
} catch (jdk.internal.agent.AgentConfigurationError e1) {
try {
return 0;
} catch (IllegalAccessError e2) {
return 1;
}
}
}
*/
static Method test:"()I"
stack 2 locals 1
{
iconst_1;
iconst_0;
try t0;
idiv;
endtry t0;
ireturn;
catch t0 jdk/internal/agent/AgentConfigurationError; // loadable but not accessible from unnamed module
stack_frame_type full;
stack_map class java/lang/Throwable;
try t1;
iconst_0;
ireturn;
endtry t1;
catch t1 java/lang/IllegalAccessError;
stack_frame_type full;
stack_map class java/lang/Throwable;
iconst_1;
ireturn;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2025, 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 8367002
* @summary Compilers might not generate handlers for recursive exceptions
*
* @compile IllegalAccessInCatch.jasm
* @run main/othervm -Xbatch
* -XX:CompileCommand=compileonly,IllegalAccessInCatch*::test
* -XX:-TieredCompilation
* TestAccessErrorInCatch
* @run main/othervm -Xbatch
* -XX:CompileCommand=compileonly,IllegalAccessInCatch*::test
* -XX:TieredStopAtLevel=3
* TestAccessErrorInCatch
*/
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
public class TestAccessErrorInCatch {
public static void main(String[] args) throws Throwable {
Path TEST_CLASSES_DIR = FileSystems.getDefault().getPath(System.getProperty("test.classes"));
byte[] bytes = Files.readAllBytes(TEST_CLASSES_DIR.resolve("IllegalAccessInCatch.class"));
var l = MethodHandles.lookup().defineHiddenClass(bytes, true);
Class<?> anonClass = l.lookupClass();
MethodHandle mh = l.findStatic(anonClass, "test", MethodType.methodType(int.class));
for (int i = 0; i < 16_000; i++) {
invoke(mh);
}
System.out.println(invoke(mh));
}
private static int invoke(MethodHandle mh) throws Throwable {
return (int) mh.invokeExact();
}
}