diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index e7b35b121a2..cd0a062ebc8 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -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 diff --git a/src/hotspot/share/interpreter/interpreterRuntime.hpp b/src/hotspot/share/interpreter/interpreterRuntime.hpp index 70ceeb0b2af..3228027fa93 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.hpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.hpp @@ -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();) }; diff --git a/src/hotspot/share/interpreter/oopMapCache.cpp b/src/hotspot/share/interpreter/oopMapCache.cpp index 5a1ad7b7883..af45f7f9bed 100644 --- a/src/hotspot/share/interpreter/oopMapCache.cpp +++ b/src/hotspot/share/interpreter/oopMapCache.cpp @@ -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"); } } diff --git a/src/hotspot/share/oops/generateOopMap.cpp b/src/hotspot/share/oops/generateOopMap.cpp index 09912aeaf63..56f3e7e0325 100644 --- a/src/hotspot/share/oops/generateOopMap.cpp +++ b/src/hotspot/share/oops/generateOopMap.cpp @@ -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); } diff --git a/src/hotspot/share/oops/generateOopMap.hpp b/src/hotspot/share/oops/generateOopMap.hpp index 783e295f08a..f0fdfeda57f 100644 --- a/src/hotspot/share/oops/generateOopMap.hpp +++ b/src/hotspot/share/oops/generateOopMap.hpp @@ -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. }; diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index ac1ddec7cbc..9d38a44cbd5 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -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") \ \ diff --git a/src/hotspot/share/runtime/interfaceSupport.cpp b/src/hotspot/share/runtime/interfaceSupport.cpp index 11a7d9fd41f..6ccf63b4c5e 100644 --- a/src/hotspot/share/runtime/interfaceSupport.cpp +++ b/src/hotspot/share/runtime/interfaceSupport.cpp @@ -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() { diff --git a/test/hotspot/jtreg/compiler/exceptions/TestAccessErrorInCatch.java b/test/hotspot/jtreg/compiler/exceptions/TestAccessErrorInCatch.java index fa81fa93f11..89dfae5c4da 100644 --- a/test/hotspot/jtreg/compiler/exceptions/TestAccessErrorInCatch.java +++ b/test/hotspot/jtreg/compiler/exceptions/TestAccessErrorInCatch.java @@ -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; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003.java b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003.java new file mode 100644 index 00000000000..840e8def8bc --- /dev/null +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003.java @@ -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); + } +} diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003a.java b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003a.java new file mode 100644 index 00000000000..1e72e02a560 --- /dev/null +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003a.java @@ -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); + } +}