From 1d883c2f2a06baeb3fe40cfb5e7d3b8782a2128a Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Thu, 18 Dec 2025 11:51:23 -0800 Subject: [PATCH 1/4] ctor_debug --- src/java.se/share/data/jdwp/jdwp.spec | 27 ++ .../sun/tools/jdi/ObjectReferenceImpl.java | 12 +- .../native/libjdwp/ObjectReferenceImpl.c | 61 ++- .../sun/jdi/valhalla/CtorDebuggingTest.java | 385 ++++++++++++++++++ 4 files changed, 482 insertions(+), 3 deletions(-) create mode 100644 test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java diff --git a/src/java.se/share/data/jdwp/jdwp.spec b/src/java.se/share/data/jdwp/jdwp.spec index 11e3160ec97..324bd8d67b8 100644 --- a/src/java.se/share/data/jdwp/jdwp.spec +++ b/src/java.se/share/data/jdwp/jdwp.spec @@ -1801,6 +1801,33 @@ JDWP "Java(tm) Debug Wire Protocol" (Error VM_DEAD) ) ) + (Command IsSameObject=11 + "Determines whether two objects refer to the same Java object." + (Out + (object object1 "The object ID") + (object object2 "The object ID") + ) + (Reply + (boolean isSameObject "true if the objects refer to the same Java object; false otherwise") + ) + (ErrorSet + (Error INVALID_OBJECT) + (Error VM_DEAD) + ) + ) + (Command ObjectHashCode=12 + "Returns hash code for an object." + (Out + (object object "The object ID") + ) + (Reply + (int hashCode "hash code value for the object") + ) + (ErrorSet + (Error INVALID_OBJECT) + (Error VM_DEAD) + ) + ) ) (CommandSet StringReference=10 diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java index 1f9024b7cd2..3f6b70597b2 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java @@ -147,8 +147,16 @@ public boolean vmNotSuspended(VMAction action) { public boolean equals(Object obj) { if (obj instanceof ObjectReferenceImpl other) { - return (ref() == other.ref()) && - super.equals(obj); + if (ref() == other.ref() && super.equals(obj)) { + return true; + } + // We can get equal value objects with different IDs. + // TODO: do it only for value objects. + try { + return JDWP.ObjectReference.IsSameObject.process(vm, this, other).isSameObject; + } catch (JDWPException exc) { + throw exc.toJDIException(); + } } else { return false; } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c b/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c index 1c50c20b868..7f1d26d30f1 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c @@ -357,6 +357,63 @@ referringObjects(PacketInputStream *in, PacketOutputStream *out) return JNI_TRUE; } +static jboolean +isSameObjectImpl(PacketInputStream *in, PacketOutputStream *out) +{ + jlong id1; + jlong id2; + jobject ref1; + jobject ref2; + JNIEnv *env; + + env = getEnv(); + id1 = inStream_readObjectID(in); + id2 = inStream_readObjectID(in); + if (inStream_error(in)) { + return JNI_TRUE; + } + + if (id1 == NULL_OBJECT_ID || id2 == NULL_OBJECT_ID) { + outStream_setError(out, JDWP_ERROR(INVALID_OBJECT)); + return JNI_TRUE; + } + + ref1 = commonRef_idToRef(env, id1); + ref2 = commonRef_idToRef(env, id2); + (void)outStream_writeBoolean(out, isSameObject(env, ref1, ref2)); + + commonRef_idToRef_delete(env, ref1); + commonRef_idToRef_delete(env, ref2); + + return JNI_TRUE; +} + +static jboolean +objectHashCodeImpl(PacketInputStream *in, PacketOutputStream *out) +{ + jlong id; + jobject ref; + JNIEnv *env; + + env = getEnv(); + id = inStream_readObjectID(in); + if (inStream_error(in)) { + return JNI_TRUE; + } + + if (id == NULL_OBJECT_ID) { + outStream_setError(out, JDWP_ERROR(INVALID_OBJECT)); + return JNI_TRUE; + } + + ref = commonRef_idToRef(env, id); + (void)outStream_writeInt(out, objectHashCode(ref)); + + commonRef_idToRef_delete(env, ref); + + return JNI_TRUE; +} + Command ObjectReference_Commands[] = { {referenceType, "ReferenceType"}, {getValues, "GetValues"}, @@ -367,7 +424,9 @@ Command ObjectReference_Commands[] = { {disableCollection, "DisableCollection"}, {enableCollection, "EnableCollection"}, {isCollected, "IsCollected"}, - {referringObjects, "ReferringObjects"} + {referringObjects, "ReferringObjects"}, + {isSameObjectImpl, "IsSameObject"}, + {objectHashCodeImpl, "ObjectHashCode"} }; DEBUG_DISPATCH_DEFINE_CMDSET(ObjectReference) diff --git a/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java b/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java new file mode 100644 index 00000000000..4213237b5e8 --- /dev/null +++ b/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2025, 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 id=xcomp-core + * @summary Test value class constructor debugging + * @library .. + * @enablePreview + * + * @comment No other references + * @run main CtorDebuggingTest + * + * @comment All references exist + * @run main CtorDebuggingTest 1 2 3 + * + * @comment No reference at step 2 + * @run main CtorDebuggingTest 1 3 + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +import com.sun.jdi.ClassType; +import com.sun.jdi.Field; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.IntegerValue; +import com.sun.jdi.Location; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.event.ClassPrepareEvent; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.EventSet; +import com.sun.jdi.event.VMDisconnectEvent; +import com.sun.jdi.request.BreakpointRequest; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.EventRequestManager; + +public class CtorDebuggingTest extends TestScaffold { + + static value class Value { + int x; + int y; + Value(int x, int y) { + this.x = x; // @1 breakpoint + this.y = y; // @2 breakpoint + System.out.println("."); // @3 breakpoint + } + } + + static class TargetApp { + public static Value v1; + public static Value v2; + public static Value v3; + + public static void main(String[] args) throws Exception { + // ensure the class is loaded + Class.forName(Value.class.getName()); + List argList = Arrays.asList(args); + if (argList.contains("1")) { + v1 = new Value(0, 0); + } + if (argList.contains("2")) { + v2 = new Value(3, 0); + } + if (argList.contains("3")) { + v3 = new Value(3, 6); + } + System.out.println(">>main"); // @prepared breakpoint + Value v = new Value(3, 6); + System.out.println("< map. + // Example: + // System.out.println("BP is here"); // @my_breakpoint breakpoint + public static Map parseBreakpoints(String filePath) { + return parseTags("breakpoint", filePath); + } + + public static Map parseTags(String tag, String filePath) { + final String regexp = "\\@(.*?) " + tag; + Pattern pattern = Pattern.compile(regexp); + int lineNum = 1; + Map result = new HashMap<>(); + try { + for (String line: Files.readAllLines(Paths.get(filePath))) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + result.put(matcher.group(1), lineNum); + } + lineNum++; + } + } catch (IOException ex) { + throw new RuntimeException("failed to parse " + filePath, ex); + } + return result; + } + + public static String getTestSourcePath(String fileName) { + return Paths.get(System.getProperty("test.src")).resolve(fileName).toString(); + } + + public static String getThisTestFile() { + return System.getProperty("test.file"); + } + + // TestScaffold is not very good in handling multiple breakpoints. + // this halper class is a listener which resumes debuggee after breakpoints. + class MultiBreakpointHandler extends TargetAdapter { + boolean needToResume = false; + // the map stores "this" in all breakpoints + Map thisObjects = new HashMap<>(); + + @Override + public void eventSetComplete(EventSet set) { + if (needToResume) { + set.resume(); + needToResume = false; + } + } + + BreakpointRequest addBreakpoint(Location loc, ObjectReference filterObject) { + final BreakpointRequest request = eventRequestManager().createBreakpointRequest(loc); + if (filterObject != null) { + request.addInstanceFilter(filterObject); + } + request.enable(); + + TargetAdapter adapter = new TargetAdapter() { + @Override + public void breakpointReached(BreakpointEvent event) { + if (request.equals(event.request())) { + ObjectReference thisObject = getThisObject(event); + System.out.println("BreakpointEvent: " + event + + " (instanceFilter: " + valueString(filterObject) + ")" + + ", this = " + valueString(thisObject)); + thisObjects.put((BreakpointRequest)event.request(), thisObject); + needToResume = true; + removeThisListener(); + } + } + }; + + addListener(adapter); + + System.out.println("Breakpoint added: " + loc + + " (instanceFilter: " + valueString(filterObject) + ")"); + + return request; + } + + // Resumes the debuggee and go through all breackpoints until the location specified is reached. + void resumeTo(Location loc) { + final BreakpointRequest request = eventRequestManager().createBreakpointRequest(loc); + request.enable(); + + class EventNotification { + boolean completed = false; + boolean disconnected = false; + } + final EventNotification en = new EventNotification(); + + TargetAdapter adapter = new TargetAdapter() { + public void eventReceived(Event event) { + if (request.equals(event.request())) { + synchronized (en) { + en.completed = true; + en.notifyAll(); + } + removeThisListener(); + } else if (event instanceof VMDisconnectEvent) { + synchronized (en) { + en.disconnected = true; + en.notifyAll(); + } + removeThisListener(); + } + } + }; + + addListener(adapter); + // this must be the last listener (as it resumes the debuggee) + addListener(this); + + try { + synchronized (en) { + vm().resume(); + while (!en.completed && !en.disconnected) { + en.wait(); + } + } + } catch (InterruptedException e) { + } + + removeListener(this); + + if (en.disconnected) { + throw new RuntimeException("VM Disconnected before requested event occurred"); + } + } + + // check if the breakpoint was hit. + boolean breakpointHit(BreakpointRequest bkpt) { + return thisObjects.containsKey(bkpt); + } + + ObjectReference thisAtBreakpoint(BreakpointRequest bkpt) { + return thisObjects.get(bkpt); + } + } + + @Override + protected void runTests() throws Exception { + BreakpointEvent bpe = startToMain(TargetApp.class.getName()); + ClassType targetClass = (ClassType)bpe.location().declaringType(); + + Map breakpoints = parseBreakpoints(getThisTestFile()); + System.out.println("breakpoints:"); + for (var entry : breakpoints.entrySet()) { + System.out.println(" tag " + entry.getKey() + ", line " + entry.getValue()); + } + + Location locPrepared = findLocation(targetClass, breakpoints.get("prepared")); + Location locDone = findLocation(targetClass, breakpoints.get("done")); + + resumeTo(locPrepared); + System.out.println("PREPARED"); + + ClassType valueClass = (ClassType)findReferenceType(Value.class.getName()); + System.out.println(Value.class.getName() + ": " + valueClass); + xField = valueClass.fieldByName("x"); + yField = valueClass.fieldByName("y"); + + ObjectReference v1 = getStaticFieldObject(targetClass, "v1"); + ObjectReference v2 = getStaticFieldObject(targetClass, "v2"); + ObjectReference v3 = getStaticFieldObject(targetClass, "v3"); + + MultiBreakpointHandler breakpointHandler = new MultiBreakpointHandler(); + + Location loc1 = findLocation(valueClass, breakpoints.get("1")); + BreakpointRequest bkpt1 = breakpointHandler.addBreakpoint(loc1, null); + BreakpointRequest bkpt1_v1 = v1 == null ? null : breakpointHandler.addBreakpoint(loc1, v1); + BreakpointRequest bkpt1_v2 = v2 == null ? null : breakpointHandler.addBreakpoint(loc1, v2); + BreakpointRequest bkpt1_v3 = v3 == null ? null : breakpointHandler.addBreakpoint(loc1, v3); + + Location loc2 = findLocation(valueClass, breakpoints.get("2")); + BreakpointRequest bkpt2 = breakpointHandler.addBreakpoint(loc2, null); + BreakpointRequest bkpt2_v1 = v1 == null ? null : breakpointHandler.addBreakpoint(loc2, v1); + BreakpointRequest bkpt2_v2 = v2 == null ? null : breakpointHandler.addBreakpoint(loc2, v2); + BreakpointRequest bkpt2_v3 = v3 == null ? null : breakpointHandler.addBreakpoint(loc2, v3); + + Location loc3 = findLocation(valueClass, breakpoints.get("3")); + BreakpointRequest bkpt3 = breakpointHandler.addBreakpoint(loc3, null); + BreakpointRequest bkpt3_v1 = v1 == null ? null : breakpointHandler.addBreakpoint(loc3, v1); + BreakpointRequest bkpt3_v2 = v2 == null ? null : breakpointHandler.addBreakpoint(loc3, v2); + BreakpointRequest bkpt3_v3 = v3 == null ? null : breakpointHandler.addBreakpoint(loc3, v3); + + breakpointHandler.resumeTo(locDone); + + System.out.println("DONE"); + + if (v1 == null && v2 == null && v3 == null) { + // No other references. + // ObjectID is generated at the 1st breakpoint (reference to heap object being constructed), + // and later we get the same oop (although it's content is changing). + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1), breakpointHandler.thisAtBreakpoint(bkpt2)); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2), breakpointHandler.thisAtBreakpoint(bkpt3)); + // There is no breakpoints with instance filter. + } else if (v1 != null && v2 != null && v3 != null) { + // Existing references to value objects with the same content as the object being constructed. + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1), v1); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v1), v1); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v2), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v3), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2), v2); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2_v1), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2_v2), v2); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2_v3), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3), v3); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v1), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v2), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v3), v3); + } else if (v1 != null && v2 == null && v3 != null) { + // At 2nd breakpoint new ObjectID is generated. + ObjectReference thisAt2 = breakpointHandler.thisAtBreakpoint(bkpt2); + assertNotEquals(thisAt2, null); + assertNotEquals(thisAt2, v1); + // Now thisAt2 has the same content as v3. + assertEquals(thisAt2, v3); + // At breakpoint 1 this == v1. + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1), v1); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v1), v1); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v3), null); + // At breakpoint 3 this == v3. + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3), v3); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v1), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v3), v3); + } else { + throw new RuntimeException("Unknown test case"); + } + + resumeToVMDisconnect(); + } +} From 207aca494b61e39430f8bccbf02e5cc0d36a360a Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Thu, 18 Dec 2025 11:56:41 -0800 Subject: [PATCH 2/4] Revert "ctor_debug" This reverts commit 1d883c2f2a06baeb3fe40cfb5e7d3b8782a2128a. --- src/java.se/share/data/jdwp/jdwp.spec | 27 -- .../sun/tools/jdi/ObjectReferenceImpl.java | 12 +- .../native/libjdwp/ObjectReferenceImpl.c | 61 +-- .../sun/jdi/valhalla/CtorDebuggingTest.java | 385 ------------------ 4 files changed, 3 insertions(+), 482 deletions(-) delete mode 100644 test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java diff --git a/src/java.se/share/data/jdwp/jdwp.spec b/src/java.se/share/data/jdwp/jdwp.spec index 324bd8d67b8..11e3160ec97 100644 --- a/src/java.se/share/data/jdwp/jdwp.spec +++ b/src/java.se/share/data/jdwp/jdwp.spec @@ -1801,33 +1801,6 @@ JDWP "Java(tm) Debug Wire Protocol" (Error VM_DEAD) ) ) - (Command IsSameObject=11 - "Determines whether two objects refer to the same Java object." - (Out - (object object1 "The object ID") - (object object2 "The object ID") - ) - (Reply - (boolean isSameObject "true if the objects refer to the same Java object; false otherwise") - ) - (ErrorSet - (Error INVALID_OBJECT) - (Error VM_DEAD) - ) - ) - (Command ObjectHashCode=12 - "Returns hash code for an object." - (Out - (object object "The object ID") - ) - (Reply - (int hashCode "hash code value for the object") - ) - (ErrorSet - (Error INVALID_OBJECT) - (Error VM_DEAD) - ) - ) ) (CommandSet StringReference=10 diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java index 3f6b70597b2..1f9024b7cd2 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java @@ -147,16 +147,8 @@ public boolean vmNotSuspended(VMAction action) { public boolean equals(Object obj) { if (obj instanceof ObjectReferenceImpl other) { - if (ref() == other.ref() && super.equals(obj)) { - return true; - } - // We can get equal value objects with different IDs. - // TODO: do it only for value objects. - try { - return JDWP.ObjectReference.IsSameObject.process(vm, this, other).isSameObject; - } catch (JDWPException exc) { - throw exc.toJDIException(); - } + return (ref() == other.ref()) && + super.equals(obj); } else { return false; } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c b/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c index 7f1d26d30f1..1c50c20b868 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c @@ -357,63 +357,6 @@ referringObjects(PacketInputStream *in, PacketOutputStream *out) return JNI_TRUE; } -static jboolean -isSameObjectImpl(PacketInputStream *in, PacketOutputStream *out) -{ - jlong id1; - jlong id2; - jobject ref1; - jobject ref2; - JNIEnv *env; - - env = getEnv(); - id1 = inStream_readObjectID(in); - id2 = inStream_readObjectID(in); - if (inStream_error(in)) { - return JNI_TRUE; - } - - if (id1 == NULL_OBJECT_ID || id2 == NULL_OBJECT_ID) { - outStream_setError(out, JDWP_ERROR(INVALID_OBJECT)); - return JNI_TRUE; - } - - ref1 = commonRef_idToRef(env, id1); - ref2 = commonRef_idToRef(env, id2); - (void)outStream_writeBoolean(out, isSameObject(env, ref1, ref2)); - - commonRef_idToRef_delete(env, ref1); - commonRef_idToRef_delete(env, ref2); - - return JNI_TRUE; -} - -static jboolean -objectHashCodeImpl(PacketInputStream *in, PacketOutputStream *out) -{ - jlong id; - jobject ref; - JNIEnv *env; - - env = getEnv(); - id = inStream_readObjectID(in); - if (inStream_error(in)) { - return JNI_TRUE; - } - - if (id == NULL_OBJECT_ID) { - outStream_setError(out, JDWP_ERROR(INVALID_OBJECT)); - return JNI_TRUE; - } - - ref = commonRef_idToRef(env, id); - (void)outStream_writeInt(out, objectHashCode(ref)); - - commonRef_idToRef_delete(env, ref); - - return JNI_TRUE; -} - Command ObjectReference_Commands[] = { {referenceType, "ReferenceType"}, {getValues, "GetValues"}, @@ -424,9 +367,7 @@ Command ObjectReference_Commands[] = { {disableCollection, "DisableCollection"}, {enableCollection, "EnableCollection"}, {isCollected, "IsCollected"}, - {referringObjects, "ReferringObjects"}, - {isSameObjectImpl, "IsSameObject"}, - {objectHashCodeImpl, "ObjectHashCode"} + {referringObjects, "ReferringObjects"} }; DEBUG_DISPATCH_DEFINE_CMDSET(ObjectReference) diff --git a/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java b/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java deleted file mode 100644 index 4213237b5e8..00000000000 --- a/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright (c) 2025, 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 id=xcomp-core - * @summary Test value class constructor debugging - * @library .. - * @enablePreview - * - * @comment No other references - * @run main CtorDebuggingTest - * - * @comment All references exist - * @run main CtorDebuggingTest 1 2 3 - * - * @comment No reference at step 2 - * @run main CtorDebuggingTest 1 3 - */ - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.regex.Pattern; -import java.util.regex.Matcher; - -import com.sun.jdi.ClassType; -import com.sun.jdi.Field; -import com.sun.jdi.IncompatibleThreadStateException; -import com.sun.jdi.IntegerValue; -import com.sun.jdi.Location; -import com.sun.jdi.ObjectReference; -import com.sun.jdi.ReferenceType; -import com.sun.jdi.StackFrame; -import com.sun.jdi.ThreadReference; -import com.sun.jdi.event.BreakpointEvent; -import com.sun.jdi.event.ClassPrepareEvent; -import com.sun.jdi.event.Event; -import com.sun.jdi.event.EventSet; -import com.sun.jdi.event.VMDisconnectEvent; -import com.sun.jdi.request.BreakpointRequest; -import com.sun.jdi.request.EventRequest; -import com.sun.jdi.request.EventRequestManager; - -public class CtorDebuggingTest extends TestScaffold { - - static value class Value { - int x; - int y; - Value(int x, int y) { - this.x = x; // @1 breakpoint - this.y = y; // @2 breakpoint - System.out.println("."); // @3 breakpoint - } - } - - static class TargetApp { - public static Value v1; - public static Value v2; - public static Value v3; - - public static void main(String[] args) throws Exception { - // ensure the class is loaded - Class.forName(Value.class.getName()); - List argList = Arrays.asList(args); - if (argList.contains("1")) { - v1 = new Value(0, 0); - } - if (argList.contains("2")) { - v2 = new Value(3, 0); - } - if (argList.contains("3")) { - v3 = new Value(3, 6); - } - System.out.println(">>main"); // @prepared breakpoint - Value v = new Value(3, 6); - System.out.println("< map. - // Example: - // System.out.println("BP is here"); // @my_breakpoint breakpoint - public static Map parseBreakpoints(String filePath) { - return parseTags("breakpoint", filePath); - } - - public static Map parseTags(String tag, String filePath) { - final String regexp = "\\@(.*?) " + tag; - Pattern pattern = Pattern.compile(regexp); - int lineNum = 1; - Map result = new HashMap<>(); - try { - for (String line: Files.readAllLines(Paths.get(filePath))) { - Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - result.put(matcher.group(1), lineNum); - } - lineNum++; - } - } catch (IOException ex) { - throw new RuntimeException("failed to parse " + filePath, ex); - } - return result; - } - - public static String getTestSourcePath(String fileName) { - return Paths.get(System.getProperty("test.src")).resolve(fileName).toString(); - } - - public static String getThisTestFile() { - return System.getProperty("test.file"); - } - - // TestScaffold is not very good in handling multiple breakpoints. - // this halper class is a listener which resumes debuggee after breakpoints. - class MultiBreakpointHandler extends TargetAdapter { - boolean needToResume = false; - // the map stores "this" in all breakpoints - Map thisObjects = new HashMap<>(); - - @Override - public void eventSetComplete(EventSet set) { - if (needToResume) { - set.resume(); - needToResume = false; - } - } - - BreakpointRequest addBreakpoint(Location loc, ObjectReference filterObject) { - final BreakpointRequest request = eventRequestManager().createBreakpointRequest(loc); - if (filterObject != null) { - request.addInstanceFilter(filterObject); - } - request.enable(); - - TargetAdapter adapter = new TargetAdapter() { - @Override - public void breakpointReached(BreakpointEvent event) { - if (request.equals(event.request())) { - ObjectReference thisObject = getThisObject(event); - System.out.println("BreakpointEvent: " + event - + " (instanceFilter: " + valueString(filterObject) + ")" - + ", this = " + valueString(thisObject)); - thisObjects.put((BreakpointRequest)event.request(), thisObject); - needToResume = true; - removeThisListener(); - } - } - }; - - addListener(adapter); - - System.out.println("Breakpoint added: " + loc - + " (instanceFilter: " + valueString(filterObject) + ")"); - - return request; - } - - // Resumes the debuggee and go through all breackpoints until the location specified is reached. - void resumeTo(Location loc) { - final BreakpointRequest request = eventRequestManager().createBreakpointRequest(loc); - request.enable(); - - class EventNotification { - boolean completed = false; - boolean disconnected = false; - } - final EventNotification en = new EventNotification(); - - TargetAdapter adapter = new TargetAdapter() { - public void eventReceived(Event event) { - if (request.equals(event.request())) { - synchronized (en) { - en.completed = true; - en.notifyAll(); - } - removeThisListener(); - } else if (event instanceof VMDisconnectEvent) { - synchronized (en) { - en.disconnected = true; - en.notifyAll(); - } - removeThisListener(); - } - } - }; - - addListener(adapter); - // this must be the last listener (as it resumes the debuggee) - addListener(this); - - try { - synchronized (en) { - vm().resume(); - while (!en.completed && !en.disconnected) { - en.wait(); - } - } - } catch (InterruptedException e) { - } - - removeListener(this); - - if (en.disconnected) { - throw new RuntimeException("VM Disconnected before requested event occurred"); - } - } - - // check if the breakpoint was hit. - boolean breakpointHit(BreakpointRequest bkpt) { - return thisObjects.containsKey(bkpt); - } - - ObjectReference thisAtBreakpoint(BreakpointRequest bkpt) { - return thisObjects.get(bkpt); - } - } - - @Override - protected void runTests() throws Exception { - BreakpointEvent bpe = startToMain(TargetApp.class.getName()); - ClassType targetClass = (ClassType)bpe.location().declaringType(); - - Map breakpoints = parseBreakpoints(getThisTestFile()); - System.out.println("breakpoints:"); - for (var entry : breakpoints.entrySet()) { - System.out.println(" tag " + entry.getKey() + ", line " + entry.getValue()); - } - - Location locPrepared = findLocation(targetClass, breakpoints.get("prepared")); - Location locDone = findLocation(targetClass, breakpoints.get("done")); - - resumeTo(locPrepared); - System.out.println("PREPARED"); - - ClassType valueClass = (ClassType)findReferenceType(Value.class.getName()); - System.out.println(Value.class.getName() + ": " + valueClass); - xField = valueClass.fieldByName("x"); - yField = valueClass.fieldByName("y"); - - ObjectReference v1 = getStaticFieldObject(targetClass, "v1"); - ObjectReference v2 = getStaticFieldObject(targetClass, "v2"); - ObjectReference v3 = getStaticFieldObject(targetClass, "v3"); - - MultiBreakpointHandler breakpointHandler = new MultiBreakpointHandler(); - - Location loc1 = findLocation(valueClass, breakpoints.get("1")); - BreakpointRequest bkpt1 = breakpointHandler.addBreakpoint(loc1, null); - BreakpointRequest bkpt1_v1 = v1 == null ? null : breakpointHandler.addBreakpoint(loc1, v1); - BreakpointRequest bkpt1_v2 = v2 == null ? null : breakpointHandler.addBreakpoint(loc1, v2); - BreakpointRequest bkpt1_v3 = v3 == null ? null : breakpointHandler.addBreakpoint(loc1, v3); - - Location loc2 = findLocation(valueClass, breakpoints.get("2")); - BreakpointRequest bkpt2 = breakpointHandler.addBreakpoint(loc2, null); - BreakpointRequest bkpt2_v1 = v1 == null ? null : breakpointHandler.addBreakpoint(loc2, v1); - BreakpointRequest bkpt2_v2 = v2 == null ? null : breakpointHandler.addBreakpoint(loc2, v2); - BreakpointRequest bkpt2_v3 = v3 == null ? null : breakpointHandler.addBreakpoint(loc2, v3); - - Location loc3 = findLocation(valueClass, breakpoints.get("3")); - BreakpointRequest bkpt3 = breakpointHandler.addBreakpoint(loc3, null); - BreakpointRequest bkpt3_v1 = v1 == null ? null : breakpointHandler.addBreakpoint(loc3, v1); - BreakpointRequest bkpt3_v2 = v2 == null ? null : breakpointHandler.addBreakpoint(loc3, v2); - BreakpointRequest bkpt3_v3 = v3 == null ? null : breakpointHandler.addBreakpoint(loc3, v3); - - breakpointHandler.resumeTo(locDone); - - System.out.println("DONE"); - - if (v1 == null && v2 == null && v3 == null) { - // No other references. - // ObjectID is generated at the 1st breakpoint (reference to heap object being constructed), - // and later we get the same oop (although it's content is changing). - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1), breakpointHandler.thisAtBreakpoint(bkpt2)); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2), breakpointHandler.thisAtBreakpoint(bkpt3)); - // There is no breakpoints with instance filter. - } else if (v1 != null && v2 != null && v3 != null) { - // Existing references to value objects with the same content as the object being constructed. - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1), v1); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v1), v1); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v2), null); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v3), null); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2), v2); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2_v1), null); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2_v2), v2); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2_v3), null); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3), v3); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v1), null); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v2), null); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v3), v3); - } else if (v1 != null && v2 == null && v3 != null) { - // At 2nd breakpoint new ObjectID is generated. - ObjectReference thisAt2 = breakpointHandler.thisAtBreakpoint(bkpt2); - assertNotEquals(thisAt2, null); - assertNotEquals(thisAt2, v1); - // Now thisAt2 has the same content as v3. - assertEquals(thisAt2, v3); - // At breakpoint 1 this == v1. - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1), v1); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v1), v1); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v3), null); - // At breakpoint 3 this == v3. - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3), v3); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v1), null); - assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v3), v3); - } else { - throw new RuntimeException("Unknown test case"); - } - - resumeToVMDisconnect(); - } -} From 88ac48b4f4856d3d24e155e02d42785abcdec247 Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Thu, 18 Dec 2025 11:58:32 -0800 Subject: [PATCH 3/4] fix --- src/java.se/share/data/jdwp/jdwp.spec | 27 ++ .../sun/tools/jdi/ObjectReferenceImpl.java | 12 +- .../native/libjdwp/ObjectReferenceImpl.c | 61 ++- .../sun/jdi/valhalla/CtorDebuggingTest.java | 385 ++++++++++++++++++ 4 files changed, 482 insertions(+), 3 deletions(-) create mode 100644 test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java diff --git a/src/java.se/share/data/jdwp/jdwp.spec b/src/java.se/share/data/jdwp/jdwp.spec index 11e3160ec97..324bd8d67b8 100644 --- a/src/java.se/share/data/jdwp/jdwp.spec +++ b/src/java.se/share/data/jdwp/jdwp.spec @@ -1801,6 +1801,33 @@ JDWP "Java(tm) Debug Wire Protocol" (Error VM_DEAD) ) ) + (Command IsSameObject=11 + "Determines whether two objects refer to the same Java object." + (Out + (object object1 "The object ID") + (object object2 "The object ID") + ) + (Reply + (boolean isSameObject "true if the objects refer to the same Java object; false otherwise") + ) + (ErrorSet + (Error INVALID_OBJECT) + (Error VM_DEAD) + ) + ) + (Command ObjectHashCode=12 + "Returns hash code for an object." + (Out + (object object "The object ID") + ) + (Reply + (int hashCode "hash code value for the object") + ) + (ErrorSet + (Error INVALID_OBJECT) + (Error VM_DEAD) + ) + ) ) (CommandSet StringReference=10 diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java index 1f9024b7cd2..3f6b70597b2 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java @@ -147,8 +147,16 @@ public boolean vmNotSuspended(VMAction action) { public boolean equals(Object obj) { if (obj instanceof ObjectReferenceImpl other) { - return (ref() == other.ref()) && - super.equals(obj); + if (ref() == other.ref() && super.equals(obj)) { + return true; + } + // We can get equal value objects with different IDs. + // TODO: do it only for value objects. + try { + return JDWP.ObjectReference.IsSameObject.process(vm, this, other).isSameObject; + } catch (JDWPException exc) { + throw exc.toJDIException(); + } } else { return false; } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c b/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c index 1c50c20b868..7f1d26d30f1 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/ObjectReferenceImpl.c @@ -357,6 +357,63 @@ referringObjects(PacketInputStream *in, PacketOutputStream *out) return JNI_TRUE; } +static jboolean +isSameObjectImpl(PacketInputStream *in, PacketOutputStream *out) +{ + jlong id1; + jlong id2; + jobject ref1; + jobject ref2; + JNIEnv *env; + + env = getEnv(); + id1 = inStream_readObjectID(in); + id2 = inStream_readObjectID(in); + if (inStream_error(in)) { + return JNI_TRUE; + } + + if (id1 == NULL_OBJECT_ID || id2 == NULL_OBJECT_ID) { + outStream_setError(out, JDWP_ERROR(INVALID_OBJECT)); + return JNI_TRUE; + } + + ref1 = commonRef_idToRef(env, id1); + ref2 = commonRef_idToRef(env, id2); + (void)outStream_writeBoolean(out, isSameObject(env, ref1, ref2)); + + commonRef_idToRef_delete(env, ref1); + commonRef_idToRef_delete(env, ref2); + + return JNI_TRUE; +} + +static jboolean +objectHashCodeImpl(PacketInputStream *in, PacketOutputStream *out) +{ + jlong id; + jobject ref; + JNIEnv *env; + + env = getEnv(); + id = inStream_readObjectID(in); + if (inStream_error(in)) { + return JNI_TRUE; + } + + if (id == NULL_OBJECT_ID) { + outStream_setError(out, JDWP_ERROR(INVALID_OBJECT)); + return JNI_TRUE; + } + + ref = commonRef_idToRef(env, id); + (void)outStream_writeInt(out, objectHashCode(ref)); + + commonRef_idToRef_delete(env, ref); + + return JNI_TRUE; +} + Command ObjectReference_Commands[] = { {referenceType, "ReferenceType"}, {getValues, "GetValues"}, @@ -367,7 +424,9 @@ Command ObjectReference_Commands[] = { {disableCollection, "DisableCollection"}, {enableCollection, "EnableCollection"}, {isCollected, "IsCollected"}, - {referringObjects, "ReferringObjects"} + {referringObjects, "ReferringObjects"}, + {isSameObjectImpl, "IsSameObject"}, + {objectHashCodeImpl, "ObjectHashCode"} }; DEBUG_DISPATCH_DEFINE_CMDSET(ObjectReference) diff --git a/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java b/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java new file mode 100644 index 00000000000..4213237b5e8 --- /dev/null +++ b/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2025, 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 id=xcomp-core + * @summary Test value class constructor debugging + * @library .. + * @enablePreview + * + * @comment No other references + * @run main CtorDebuggingTest + * + * @comment All references exist + * @run main CtorDebuggingTest 1 2 3 + * + * @comment No reference at step 2 + * @run main CtorDebuggingTest 1 3 + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +import com.sun.jdi.ClassType; +import com.sun.jdi.Field; +import com.sun.jdi.IncompatibleThreadStateException; +import com.sun.jdi.IntegerValue; +import com.sun.jdi.Location; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.StackFrame; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.event.ClassPrepareEvent; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.EventSet; +import com.sun.jdi.event.VMDisconnectEvent; +import com.sun.jdi.request.BreakpointRequest; +import com.sun.jdi.request.EventRequest; +import com.sun.jdi.request.EventRequestManager; + +public class CtorDebuggingTest extends TestScaffold { + + static value class Value { + int x; + int y; + Value(int x, int y) { + this.x = x; // @1 breakpoint + this.y = y; // @2 breakpoint + System.out.println("."); // @3 breakpoint + } + } + + static class TargetApp { + public static Value v1; + public static Value v2; + public static Value v3; + + public static void main(String[] args) throws Exception { + // ensure the class is loaded + Class.forName(Value.class.getName()); + List argList = Arrays.asList(args); + if (argList.contains("1")) { + v1 = new Value(0, 0); + } + if (argList.contains("2")) { + v2 = new Value(3, 0); + } + if (argList.contains("3")) { + v3 = new Value(3, 6); + } + System.out.println(">>main"); // @prepared breakpoint + Value v = new Value(3, 6); + System.out.println("< map. + // Example: + // System.out.println("BP is here"); // @my_breakpoint breakpoint + public static Map parseBreakpoints(String filePath) { + return parseTags("breakpoint", filePath); + } + + public static Map parseTags(String tag, String filePath) { + final String regexp = "\\@(.*?) " + tag; + Pattern pattern = Pattern.compile(regexp); + int lineNum = 1; + Map result = new HashMap<>(); + try { + for (String line: Files.readAllLines(Paths.get(filePath))) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + result.put(matcher.group(1), lineNum); + } + lineNum++; + } + } catch (IOException ex) { + throw new RuntimeException("failed to parse " + filePath, ex); + } + return result; + } + + public static String getTestSourcePath(String fileName) { + return Paths.get(System.getProperty("test.src")).resolve(fileName).toString(); + } + + public static String getThisTestFile() { + return System.getProperty("test.file"); + } + + // TestScaffold is not very good in handling multiple breakpoints. + // this halper class is a listener which resumes debuggee after breakpoints. + class MultiBreakpointHandler extends TargetAdapter { + boolean needToResume = false; + // the map stores "this" in all breakpoints + Map thisObjects = new HashMap<>(); + + @Override + public void eventSetComplete(EventSet set) { + if (needToResume) { + set.resume(); + needToResume = false; + } + } + + BreakpointRequest addBreakpoint(Location loc, ObjectReference filterObject) { + final BreakpointRequest request = eventRequestManager().createBreakpointRequest(loc); + if (filterObject != null) { + request.addInstanceFilter(filterObject); + } + request.enable(); + + TargetAdapter adapter = new TargetAdapter() { + @Override + public void breakpointReached(BreakpointEvent event) { + if (request.equals(event.request())) { + ObjectReference thisObject = getThisObject(event); + System.out.println("BreakpointEvent: " + event + + " (instanceFilter: " + valueString(filterObject) + ")" + + ", this = " + valueString(thisObject)); + thisObjects.put((BreakpointRequest)event.request(), thisObject); + needToResume = true; + removeThisListener(); + } + } + }; + + addListener(adapter); + + System.out.println("Breakpoint added: " + loc + + " (instanceFilter: " + valueString(filterObject) + ")"); + + return request; + } + + // Resumes the debuggee and go through all breackpoints until the location specified is reached. + void resumeTo(Location loc) { + final BreakpointRequest request = eventRequestManager().createBreakpointRequest(loc); + request.enable(); + + class EventNotification { + boolean completed = false; + boolean disconnected = false; + } + final EventNotification en = new EventNotification(); + + TargetAdapter adapter = new TargetAdapter() { + public void eventReceived(Event event) { + if (request.equals(event.request())) { + synchronized (en) { + en.completed = true; + en.notifyAll(); + } + removeThisListener(); + } else if (event instanceof VMDisconnectEvent) { + synchronized (en) { + en.disconnected = true; + en.notifyAll(); + } + removeThisListener(); + } + } + }; + + addListener(adapter); + // this must be the last listener (as it resumes the debuggee) + addListener(this); + + try { + synchronized (en) { + vm().resume(); + while (!en.completed && !en.disconnected) { + en.wait(); + } + } + } catch (InterruptedException e) { + } + + removeListener(this); + + if (en.disconnected) { + throw new RuntimeException("VM Disconnected before requested event occurred"); + } + } + + // check if the breakpoint was hit. + boolean breakpointHit(BreakpointRequest bkpt) { + return thisObjects.containsKey(bkpt); + } + + ObjectReference thisAtBreakpoint(BreakpointRequest bkpt) { + return thisObjects.get(bkpt); + } + } + + @Override + protected void runTests() throws Exception { + BreakpointEvent bpe = startToMain(TargetApp.class.getName()); + ClassType targetClass = (ClassType)bpe.location().declaringType(); + + Map breakpoints = parseBreakpoints(getThisTestFile()); + System.out.println("breakpoints:"); + for (var entry : breakpoints.entrySet()) { + System.out.println(" tag " + entry.getKey() + ", line " + entry.getValue()); + } + + Location locPrepared = findLocation(targetClass, breakpoints.get("prepared")); + Location locDone = findLocation(targetClass, breakpoints.get("done")); + + resumeTo(locPrepared); + System.out.println("PREPARED"); + + ClassType valueClass = (ClassType)findReferenceType(Value.class.getName()); + System.out.println(Value.class.getName() + ": " + valueClass); + xField = valueClass.fieldByName("x"); + yField = valueClass.fieldByName("y"); + + ObjectReference v1 = getStaticFieldObject(targetClass, "v1"); + ObjectReference v2 = getStaticFieldObject(targetClass, "v2"); + ObjectReference v3 = getStaticFieldObject(targetClass, "v3"); + + MultiBreakpointHandler breakpointHandler = new MultiBreakpointHandler(); + + Location loc1 = findLocation(valueClass, breakpoints.get("1")); + BreakpointRequest bkpt1 = breakpointHandler.addBreakpoint(loc1, null); + BreakpointRequest bkpt1_v1 = v1 == null ? null : breakpointHandler.addBreakpoint(loc1, v1); + BreakpointRequest bkpt1_v2 = v2 == null ? null : breakpointHandler.addBreakpoint(loc1, v2); + BreakpointRequest bkpt1_v3 = v3 == null ? null : breakpointHandler.addBreakpoint(loc1, v3); + + Location loc2 = findLocation(valueClass, breakpoints.get("2")); + BreakpointRequest bkpt2 = breakpointHandler.addBreakpoint(loc2, null); + BreakpointRequest bkpt2_v1 = v1 == null ? null : breakpointHandler.addBreakpoint(loc2, v1); + BreakpointRequest bkpt2_v2 = v2 == null ? null : breakpointHandler.addBreakpoint(loc2, v2); + BreakpointRequest bkpt2_v3 = v3 == null ? null : breakpointHandler.addBreakpoint(loc2, v3); + + Location loc3 = findLocation(valueClass, breakpoints.get("3")); + BreakpointRequest bkpt3 = breakpointHandler.addBreakpoint(loc3, null); + BreakpointRequest bkpt3_v1 = v1 == null ? null : breakpointHandler.addBreakpoint(loc3, v1); + BreakpointRequest bkpt3_v2 = v2 == null ? null : breakpointHandler.addBreakpoint(loc3, v2); + BreakpointRequest bkpt3_v3 = v3 == null ? null : breakpointHandler.addBreakpoint(loc3, v3); + + breakpointHandler.resumeTo(locDone); + + System.out.println("DONE"); + + if (v1 == null && v2 == null && v3 == null) { + // No other references. + // ObjectID is generated at the 1st breakpoint (reference to heap object being constructed), + // and later we get the same oop (although it's content is changing). + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1), breakpointHandler.thisAtBreakpoint(bkpt2)); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2), breakpointHandler.thisAtBreakpoint(bkpt3)); + // There is no breakpoints with instance filter. + } else if (v1 != null && v2 != null && v3 != null) { + // Existing references to value objects with the same content as the object being constructed. + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1), v1); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v1), v1); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v2), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v3), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2), v2); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2_v1), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2_v2), v2); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt2_v3), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3), v3); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v1), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v2), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v3), v3); + } else if (v1 != null && v2 == null && v3 != null) { + // At 2nd breakpoint new ObjectID is generated. + ObjectReference thisAt2 = breakpointHandler.thisAtBreakpoint(bkpt2); + assertNotEquals(thisAt2, null); + assertNotEquals(thisAt2, v1); + // Now thisAt2 has the same content as v3. + assertEquals(thisAt2, v3); + // At breakpoint 1 this == v1. + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1), v1); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v1), v1); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt1_v3), null); + // At breakpoint 3 this == v3. + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3), v3); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v1), null); + assertEquals(breakpointHandler.thisAtBreakpoint(bkpt3_v3), v3); + } else { + throw new RuntimeException("Unknown test case"); + } + + resumeToVMDisconnect(); + } +} From ff1803df295357a159115d213aee487f2e7621c9 Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Thu, 18 Dec 2025 12:41:59 -0800 Subject: [PATCH 4/4] tabs --- .../sun/jdi/valhalla/CtorDebuggingTest.java | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java b/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java index 4213237b5e8..bbcda1918ac 100644 --- a/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java +++ b/test/jdk/com/sun/jdi/valhalla/CtorDebuggingTest.java @@ -74,7 +74,7 @@ static value class Value { Value(int x, int y) { this.x = x; // @1 breakpoint this.y = y; // @2 breakpoint - System.out.println("."); // @3 breakpoint + System.out.println("."); // @3 breakpoint } } @@ -84,23 +84,23 @@ static class TargetApp { public static Value v3; public static void main(String[] args) throws Exception { - // ensure the class is loaded - Class.forName(Value.class.getName()); - List argList = Arrays.asList(args); - if (argList.contains("1")) { + // ensure the class is loaded + Class.forName(Value.class.getName()); + List argList = Arrays.asList(args); + if (argList.contains("1")) { v1 = new Value(0, 0); } - if (argList.contains("2")) { + if (argList.contains("2")) { v2 = new Value(3, 0); } - if (argList.contains("3")) { + if (argList.contains("3")) { v3 = new Value(3, 6); } - System.out.println(">>main"); // @prepared breakpoint + System.out.println(">>main"); // @prepared breakpoint Value v = new Value(3, 6); - System.out.println("< map. // Example: // System.out.println("BP is here"); // @my_breakpoint breakpoint public static Map parseBreakpoints(String filePath) { - return parseTags("breakpoint", filePath); - } + return parseTags("breakpoint", filePath); + } public static Map parseTags(String tag, String filePath) { final String regexp = "\\@(.*?) " + tag; - Pattern pattern = Pattern.compile(regexp); + Pattern pattern = Pattern.compile(regexp); int lineNum = 1; - Map result = new HashMap<>(); + Map result = new HashMap<>(); try { for (String line: Files.readAllLines(Paths.get(filePath))) { - Matcher matcher = pattern.matcher(line); + Matcher matcher = pattern.matcher(line); if (matcher.find()) { result.put(matcher.group(1), lineNum); }