mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-21 12:20:29 +00:00
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:
parent
0a8dbed7ef
commit
2ce2d5886b
@ -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
|
||||
|
||||
@ -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();)
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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.
|
||||
};
|
||||
|
||||
@ -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") \
|
||||
\
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
|
||||
106
test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003.java
Normal file
106
test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user