8350208: CTW: GraphKit::add_safepoint_edges asserts "not enough operands for reexecution"

Co-authored-by: Quan Anh Mai <qamai@openjdk.org>
Reviewed-by: mhaessig, vlivanov
This commit is contained in:
Dean Long 2026-04-03 04:08:21 +00:00
parent 95e90299b4
commit 4253db2252
5 changed files with 166 additions and 17 deletions

View File

@ -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);

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
@ -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.

View File

@ -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();

View File

@ -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:
* <pre>{@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;
* }
* }</pre>
*/
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();
}
}

View File

@ -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);
}
}
}