diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index e4418631d17..9a1da726f00 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ #include "opto/callGenerator.hpp" #include "opto/castnode.hpp" #include "opto/cfgnode.hpp" +#include "opto/graphKit.hpp" #include "opto/mulnode.hpp" #include "opto/parse.hpp" #include "opto/rootnode.hpp" @@ -909,8 +910,7 @@ void Parse::catch_call_exceptions(ciExceptionHandlerStream& handlers) { if (handler_bci < 0) { // merge with corresponding rethrow node throw_to_exit(make_exception_state(ex_oop)); } else { // Else jump to corresponding handle - push_ex_oop(ex_oop); // Clear stack and push just the oop. - merge_exception(handler_bci); + push_and_merge_exception(handler_bci, ex_oop); } } @@ -1008,13 +1008,10 @@ void Parse::catch_inline_exceptions(SafePointNode* ex_map) { int handler_bci = handler->handler_bci(); if (remaining == 1) { - push_ex_oop(ex_node); // Push exception oop for handler if (PrintOpto && WizardMode) { tty->print_cr(" Catching every inline exception bci:%d -> handler_bci:%d", bci(), handler_bci); } - // If this is a backwards branch in the bytecodes, add safepoint - maybe_add_safepoint(handler_bci); - merge_exception(handler_bci); // jump to handler + push_and_merge_exception(handler_bci, ex_node); // jump to handler return; // No more handling to be done here! } @@ -1039,15 +1036,13 @@ void Parse::catch_inline_exceptions(SafePointNode* ex_map) { const TypeInstPtr* tinst = TypeOopPtr::make_from_klass_unique(klass)->cast_to_ptr_type(TypePtr::NotNull)->is_instptr(); assert(klass->has_subklass() || tinst->klass_is_exact(), "lost exactness"); Node* ex_oop = _gvn.transform(new CheckCastPPNode(control(), ex_node, tinst)); - push_ex_oop(ex_oop); // Push exception oop for handler if (PrintOpto && WizardMode) { tty->print(" Catching inline exception bci:%d -> handler_bci:%d -- ", bci(), handler_bci); klass->print_name(); tty->cr(); } // If this is a backwards branch in the bytecodes, add safepoint - maybe_add_safepoint(handler_bci); - merge_exception(handler_bci); + push_and_merge_exception(handler_bci, ex_oop); } set_control(not_subtype_ctrl); diff --git a/src/hotspot/share/opto/parse.hpp b/src/hotspot/share/opto/parse.hpp index 397a7796f88..42f44f0f7ea 100644 --- a/src/hotspot/share/opto/parse.hpp +++ b/src/hotspot/share/opto/parse.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 @@ -474,8 +474,8 @@ class Parse : public GraphKit { void merge( int target_bci); // Same as plain merge, except that it allocates a new path number. void merge_new_path( int target_bci); - // Merge the current mapping into an exception handler. - void merge_exception(int target_bci); + // Push the exception oop and merge the current mapping into an exception handler. + void push_and_merge_exception(int target_bci, Node* ex_oop); // Helper: Merge the current mapping into the given basic block void merge_common(Block* target, int pnum); // Helper functions for merging individual cells. diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 647e8515b30..683633f6355 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -1651,9 +1651,14 @@ void Parse::merge_new_path(int target_bci) { } //-------------------------merge_exception------------------------------------- -// Merge the current mapping into the basic block starting at bci -// The ex_oop must be pushed on the stack, unlike throw_to_exit. -void Parse::merge_exception(int target_bci) { +// Push the given ex_oop onto the stack, then merge the current mapping into +// the basic block starting at target_bci. +void Parse::push_and_merge_exception(int target_bci, Node* ex_oop) { + // Add the safepoint before trimming the stack and pushing the exception oop. + // We could add the safepoint after, but then the bci would also need to be + // advanced to target_bci first, so the stack state matches. + maybe_add_safepoint(target_bci); + push_ex_oop(ex_oop); // Push exception oop for handler #ifdef ASSERT if (target_bci <= bci()) { C->set_exception_backedge(); diff --git a/test/hotspot/jtreg/compiler/exceptions/TestDebugDuringExceptionCatching.java b/test/hotspot/jtreg/compiler/exceptions/TestDebugDuringExceptionCatching.java new file mode 100644 index 00000000000..999c73fb73a --- /dev/null +++ b/test/hotspot/jtreg/compiler/exceptions/TestDebugDuringExceptionCatching.java @@ -0,0 +1,126 @@ +/* + * 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 compiler.exceptions; + +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; + +import java.lang.classfile.Label; +import java.lang.constant.ClassDesc; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +import jdk.test.lib.Asserts; +import test.java.lang.invoke.lib.InstructionHelper; + +/** + * @test + * @bug 8350208 + * @summary Safepoints added during the processing of exception handlers should never reexecute + * @library /test/lib /test/jdk/java/lang/invoke/common / + * @build test.java.lang.invoke.lib.InstructionHelper + * + * @run main/othervm ${test.main.class} + */ +public class TestDebugDuringExceptionCatching { + + public static class V { + int v; + } + + static final int ITERATIONS = 100; + static final RuntimeException EXCEPTION = new RuntimeException(); + + /** + * Construct something that looks like this: + *
{@code
+ * int snippet(V v) {
+ * int i = 0;
+ * LoopHead: {
+ * if (i >= 100) {
+ * goto LoopEnd;
+ * }
+ * i++;
+ * try {
+ * v.v = 1;
+ * } catch (Throwable) {
+ * // Not really, the LoopHead is the exception Handler
+ * goto LoopHead;
+ * }
+ * }
+ * LoopEnd:
+ * return i;
+ * }
+ * }
+ */
+ static final MethodHandle SNIPPET_HANDLE;
+ static final ClassDesc CLASS_DESC = TestDebugDuringExceptionCatching.class.describeConstable().get();
+ static {
+ SNIPPET_HANDLE = InstructionHelper.buildMethodHandle(MethodHandles.lookup(),
+ "snippet",
+ MethodType.methodType(int.class, V.class),
+ CODE -> {
+ Label loopHead = CODE.newLabel();
+ Label loopEnd = CODE.newLabel();
+ Label tryStart = CODE.newLabel();
+ Label tryEnd = CODE.newLabel();
+ CODE.
+ iconst_0().
+ istore(1).
+ // The loop head should have a RuntimeException as the sole element on the stack
+ getstatic(CLASS_DESC, "EXCEPTION", RuntimeException.class.describeConstable().get()).
+ labelBinding(loopHead).
+ pop().
+ iload(1).
+ ldc(ITERATIONS).
+ if_icmpge(loopEnd).
+ iinc(1, 1).
+ aload(0).
+ iconst_1().
+ labelBinding(tryStart).
+ putfield(V.class.describeConstable().get(), "v", int.class.describeConstable().get()).
+ labelBinding(tryEnd).
+ // The stack is empty here
+ labelBinding(loopEnd).
+ iload(1).
+ ireturn();
+ CODE.exceptionCatchAll(tryStart, tryEnd, loopHead);
+ });
+ }
+
+ @Test
+ private static int testBackwardHandler(V v) throws Throwable {
+ return (int) SNIPPET_HANDLE.invokeExact(v);
+ }
+
+ @Run(test = "testBackwardHandler")
+ public void run() throws Throwable {
+ Asserts.assertEQ(ITERATIONS, testBackwardHandler(null));
+ }
+
+ public static void main(String[] args) {
+ TestFramework.run();
+ }
+}
diff --git a/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java b/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java
index 123cccd53e7..c7185f691b2 100644
--- a/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java
+++ b/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -25,6 +25,7 @@ package test.java.lang.invoke.lib;
import java.lang.classfile.ClassBuilder;
import java.lang.classfile.ClassFile;
+import java.lang.classfile.CodeBuilder;
import java.lang.classfile.TypeKind;
import java.lang.constant.*;
@@ -32,6 +33,7 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import static java.lang.invoke.MethodType.fromMethodDescriptorString;
@@ -135,4 +137,25 @@ public class InstructionHelper {
String classDescStr = sb.insert(sb.length() - 1, suffix).toString();
return ClassDesc.ofDescriptor(classDescStr);
}
+
+ public static MethodHandle buildMethodHandle(MethodHandles.Lookup l, String methodName, MethodType methodType, Consumer super CodeBuilder> builder) {
+ ClassDesc genClassDesc = classDesc(l.lookupClass(), "$Code_" + COUNT.getAndIncrement());
+ return buildMethodHandle(l, genClassDesc, methodName, methodType, builder);
+ }
+
+ private static MethodHandle buildMethodHandle(MethodHandles.Lookup l, ClassDesc classDesc, String methodName, MethodType methodType, Consumer super CodeBuilder> builder) {
+ try {
+ byte[] bytes = ClassFile.of().build(classDesc, classBuilder -> {
+ classBuilder.withMethod(methodName,
+ MethodTypeDesc.ofDescriptor(methodType.toMethodDescriptorString()),
+ ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC,
+ methodBuilder -> methodBuilder.withCode(builder));
+ });
+ Class> clazz = l.defineClass(bytes);
+ return l.findStatic(clazz, methodName, methodType);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ throw new RuntimeException("Failed to buildMethodHandle: " + methodName + " type " + methodType);
+ }
+ }
}