8074292: nsk/jdb/kill/kill001: generateOopMap.cpp assert(bb->is_reachable()) failed

Co-authored-by: Tom Rodriguez <never@openjdk.org>
Reviewed-by: sspitsyn, pchilanomate, dlong, never
This commit is contained in:
Coleen Phillimore 2026-04-17 18:17:52 +00:00
parent 0a8dbed7ef
commit 2ce2d5886b
10 changed files with 228 additions and 49 deletions

View File

@ -36,6 +36,7 @@
#include "interpreter/interpreter.hpp"
#include "interpreter/interpreterRuntime.hpp"
#include "interpreter/linkResolver.hpp"
#include "interpreter/oopMapCache.hpp"
#include "interpreter/templateTable.hpp"
#include "jvm_io.h"
#include "logging/log.hpp"
@ -1527,4 +1528,17 @@ bool InterpreterRuntime::is_preemptable_call(address entry_point) {
entry_point == CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache) ||
entry_point == CAST_FROM_FN_PTR(address, InterpreterRuntime::_new);
}
void InterpreterRuntime::generate_oop_map_alot() {
JavaThread* current = JavaThread::current();
LastFrameAccessor last_frame(current);
if (last_frame.is_interpreted_frame()) {
ResourceMark rm(current);
InterpreterOopMap mask;
methodHandle mh(current, last_frame.method());
int bci = last_frame.bci();
log_info(generateoopmap)("Generating oopmap for method %s at bci %d", mh->name_and_sig_as_C_string(), bci);
OopMapCache::compute_one_oop_map(mh, bci, &mask);
}
}
#endif // ASSERT

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2026, 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
@ -173,6 +173,8 @@ private:
// Virtual Thread Preemption
DEBUG_ONLY(static bool is_preemptable_call(address entry_point);)
DEBUG_ONLY(static void generate_oop_map_alot();)
};

View File

@ -92,11 +92,8 @@ class OopMapForCacheEntry: public GenerateOopMap {
};
OopMapForCacheEntry::OopMapForCacheEntry(const methodHandle& method, int bci, OopMapCacheEntry* entry) : GenerateOopMap(method) {
_bci = bci;
_entry = entry;
_stack_top = -1;
}
OopMapForCacheEntry::OopMapForCacheEntry(const methodHandle& method, int bci, OopMapCacheEntry* entry) :
GenerateOopMap(method, /*all_exception_edges*/ true), _entry(entry), _bci(bci), _stack_top(-1) { }
bool OopMapForCacheEntry::compute_map(Thread* current) {
@ -107,6 +104,11 @@ bool OopMapForCacheEntry::compute_map(Thread* current) {
} else {
ResourceMark rm;
if (!GenerateOopMap::compute_map(current)) {
// If compute_map fails, print the exception message, which is generated if
// this is a JavaThread, otherwise compute_map calls fatal so we don't get here.
if (exception() != nullptr) {
exception()->print();
}
fatal("Unrecoverable verification or out-of-memory error");
return false;
}
@ -315,6 +317,9 @@ void OopMapCacheEntry::fill(const methodHandle& method, int bci) {
} else {
OopMapForCacheEntry gen(method, bci, this);
if (!gen.compute_map(Thread::current())) {
if (gen.exception() != nullptr) {
gen.exception()->print();
}
fatal("Unrecoverable verification or out-of-memory error");
}
}

View File

@ -1168,42 +1168,46 @@ void GenerateOopMap::interp_bb(BasicBlock *bb) {
}
void GenerateOopMap::do_exception_edge(BytecodeStream* itr) {
// Only check exception edge, if bytecode can trap
if (!Bytecodes::can_trap(itr->code())) return;
switch (itr->code()) {
case Bytecodes::_aload_0:
// These bytecodes can trap for rewriting. We need to assume that
// they do not throw exceptions to make the monitor analysis work.
return;
case Bytecodes::_ireturn:
case Bytecodes::_lreturn:
case Bytecodes::_freturn:
case Bytecodes::_dreturn:
case Bytecodes::_areturn:
case Bytecodes::_return:
// If the monitor stack height is not zero when we leave the method,
// then we are either exiting with a non-empty stack or we have
// found monitor trouble earlier in our analysis. In either case,
// assume an exception could be taken here.
if (_monitor_top == 0) {
// Only check exception edge, if bytecode can trap or if async exceptions can be thrown
// from any bytecode in the interpreter when single stepping.
if (!_all_exception_edges) {
if (!Bytecodes::can_trap(itr->code())) return;
switch (itr->code()) {
case Bytecodes::_aload_0:
// These bytecodes can trap for rewriting. We need to assume that
// they do not throw exceptions to make the monitor analysis work.
return;
}
break;
case Bytecodes::_monitorexit:
// If the monitor stack height is bad_monitors, then we have detected a
// monitor matching problem earlier in the analysis. If the
// monitor stack height is 0, we are about to pop a monitor
// off of an empty stack. In either case, the bytecode
// could throw an exception.
if (_monitor_top != bad_monitors && _monitor_top != 0) {
return;
}
break;
case Bytecodes::_ireturn:
case Bytecodes::_lreturn:
case Bytecodes::_freturn:
case Bytecodes::_dreturn:
case Bytecodes::_areturn:
case Bytecodes::_return:
// If the monitor stack height is not zero when we leave the method,
// then we are either exiting with a non-empty stack or we have
// found monitor trouble earlier in our analysis. In either case,
// assume an exception could be taken here.
if (_monitor_top == 0) {
return;
}
break;
default:
break;
case Bytecodes::_monitorexit:
// If the monitor stack height is bad_monitors, then we have detected a
// monitor matching problem earlier in the analysis. If the
// monitor stack height is 0, we are about to pop a monitor
// off of an empty stack. In either case, the bytecode
// could throw an exception.
if (_monitor_top != bad_monitors && _monitor_top != 0) {
return;
}
break;
default:
break;
}
}
if (_has_exceptions) {
@ -2055,12 +2059,12 @@ void GenerateOopMap::print_time() {
//
// ============ Main Entry Point ===========
//
GenerateOopMap::GenerateOopMap(const methodHandle& method) {
GenerateOopMap::GenerateOopMap(const methodHandle& method, bool all_exception_edges) :
// We have to initialize all variables here, that can be queried directly
_method = method;
_max_locals=0;
_init_vars = nullptr;
}
_method(method),
_max_locals(0),
_all_exception_edges(all_exception_edges),
_init_vars(nullptr) {}
bool GenerateOopMap::compute_map(Thread* current) {
#ifndef PRODUCT
@ -2187,7 +2191,7 @@ void GenerateOopMap::result_for_basicblock(int bci) {
// Find basicblock and report results
BasicBlock* bb = get_basic_block_containing(bci);
guarantee(bb != nullptr, "no basic block for bci");
assert(bb->is_reachable(), "getting result from unreachable basicblock %d", bci);
assert(bb->is_reachable(), "getting result from unreachable basicblock at bci %d", bci);
bb->set_changed(true);
interp_bb(bb);
}

View File

@ -307,6 +307,7 @@ class GenerateOopMap {
bool _did_relocation; // was relocation necessary
bool _monitor_safe; // The monitors in this method have been determined
// to be safe.
bool _all_exception_edges; // All bytecodes can reach containing exception handler.
// Working Cell type state
int _state_len; // Size of states
@ -455,7 +456,7 @@ class GenerateOopMap {
friend class RelocCallback;
public:
GenerateOopMap(const methodHandle& method);
GenerateOopMap(const methodHandle& method, bool all_exception_edges);
// Compute the map - returns true on success and false on error.
bool compute_map(Thread* current);
@ -516,7 +517,7 @@ class ResolveOopMapConflicts: public GenerateOopMap {
#endif
public:
ResolveOopMapConflicts(const methodHandle& method) : GenerateOopMap(method) { }
ResolveOopMapConflicts(const methodHandle& method) : GenerateOopMap(method, true) { }
methodHandle do_potential_rewrite(TRAPS);
};
@ -535,7 +536,7 @@ class GeneratePairingInfo: public GenerateOopMap {
CellTypeState* stack,
int stack_top) {}
public:
GeneratePairingInfo(const methodHandle& method) : GenerateOopMap(method) {};
GeneratePairingInfo(const methodHandle& method) : GenerateOopMap(method, false) {};
// Call compute_map() to generate info.
};

View File

@ -871,6 +871,9 @@ const int ObjectAlignmentInBytes = 8;
develop(bool, TimeOopMap, false, \
"Time calls to GenerateOopMap::compute_map() in sum") \
\
develop(bool, GenerateOopMapALot, false, \
"Generate interpreter oopmaps at all safepoints") \
\
develop(bool, TraceFinalizerRegistration, false, \
"Trace registration of final references") \
\

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2026, 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
@ -23,6 +23,7 @@
*/
#include "gc/shared/collectedHeap.inline.hpp"
#include "interpreter/interpreterRuntime.hpp"
#include "logging/log.hpp"
#include "memory/resourceArea.hpp"
#include "memory/universe.hpp"
@ -65,6 +66,10 @@ VMEntryWrapper::~VMEntryWrapper() {
if (VerifyStack) {
InterfaceSupport::verify_stack();
}
// Verify interpreter oopmap generation
if (GenerateOopMapALot) {
InterpreterRuntime::generate_oop_map_alot();
}
}
VMNativeEntryWrapper::VMNativeEntryWrapper() {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, 2026, 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
@ -37,6 +37,7 @@
* -XX:+IgnoreUnrecognizedVMOptions -XX:+VerifyStack
* -XX:TieredStopAtLevel=3
* TestAccessErrorInCatch
* @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+GenerateOopMapALot TestAccessErrorInCatch
*/
import java.lang.invoke.MethodHandle;

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2026, 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 8074292
* @summary Reproduce the bb->is_reachable() assert with GetSetLocals call after async exception.
* @library /vmTestbase
* /test/lib
* @compile -g kill003a.java
* @run driver
* nsk.jdb.kill.kill003.kill003
* -arch=${os.family}-${os.simpleArch}
* -waittime=5
* -verbose
* -debugee.vmkind=java
* -transport.address=dynamic
* -jdb=${test.jdk}/bin/jdb
* -java.options="${test.vm.opts} ${test.java.opts}"
* -workdir=.
* -debugee.vmkeys="${test.vm.opts} ${test.java.opts}"
*/
package nsk.jdb.kill.kill003;
import nsk.share.*;
import nsk.share.jdb.*;
import java.io.*;
import java.util.*;
public class kill003 extends JdbTest {
public static void main (String argv[]) {
debuggeeClass = DEBUGGEE_CLASS;
firstBreak = FIRST_BREAK;
new kill003().runTest(argv);
}
static final String PACKAGE_NAME = "nsk.jdb.kill.kill003";
static final String TEST_CLASS = PACKAGE_NAME + ".kill003";
static final String DEBUGGEE_CLASS = TEST_CLASS + "a";
static final String FIRST_BREAK = DEBUGGEE_CLASS + ".main";
static final String MYTHREAD = "MyThread";
static final String DEBUGGEE_THREAD = PACKAGE_NAME + "." + MYTHREAD;
static final String DEBUGGEE_EXCEPTION = DEBUGGEE_CLASS + ".exception";
protected void runCases() {
String[] reply;
String[] threads;
// At this point we are at the breakpoint triggered by the firstBreak in main
// after creating all the threads. Get the list of debuggee threads.
threads = jdb.getThreadIdsByName("main");
// Stopped at kill.main, so step into synchronized block
reply = jdb.receiveReplyFor(JdbCommand.next);
if (threads.length != 1) {
log.complain("jdb should report " + 1 + " instance of " + DEBUGGEE_THREAD);
log.complain("Found: " + threads.length);
success = false;
}
// Execution is at a bytecode that is not expected to handle an async exception. Throw one here
// to make sure it gets handled without crashing. The exception will be delivered at the next
// bytecode that can handle the async exception.
reply = jdb.receiveReplyForWithMessageWait(JdbCommand.kill + threads[0] + " " + DEBUGGEE_EXCEPTION,
"killed");
// Continue the debuggee - the async exception will be delivered to the debuggee.
reply = jdb.receiveReplyFor(JdbCommand.cont);
// Ask the debuggee for its local variables at the bytecode where the async exception was delivered, which
// should be reachable.
reply = jdb.receiveReplyForWithMessageWait(JdbCommand.locals, "Local variables");
if (jdb.terminated()) {
throw new Failure("Debuggee exited");
}
// The lack of exception handler in the debuggee should cause it to exit when continued.
jdb.contToExit(1);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2026, 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.
*/
package nsk.jdb.kill.kill003;
import nsk.share.jdb.*;
/* This is debuggee application */
public class kill003a {
static NullPointerException exception = new NullPointerException();
public static void main(String args[]) {
synchronized (args) {
}
System.out.println("done");
System.exit(JdbTest.JCK_STATUS_BASE);
}
}