From 1d883c2f2a06baeb3fe40cfb5e7d3b8782a2128a Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Thu, 18 Dec 2025 11:51:23 -0800 Subject: [PATCH 1/3] 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/3] 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 3011119b192f69a7b9ca015f9762884e5c1e7ae7 Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Fri, 19 Dec 2025 17:15:27 -0800 Subject: [PATCH 3/3] heapwalk_dup_classes --- .../HeapwalkDupClasses.java | 97 +++++++++++++ .../libHeapwalkDupClasses.cpp | 129 ++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapwalkDupClasses/HeapwalkDupClasses.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapwalkDupClasses/libHeapwalkDupClasses.cpp diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapwalkDupClasses/HeapwalkDupClasses.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapwalkDupClasses/HeapwalkDupClasses.java new file mode 100644 index 00000000000..93c7afb6c29 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapwalkDupClasses/HeapwalkDupClasses.java @@ -0,0 +1,97 @@ +/* + * 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 + * @bug 8368799 + * @summary Verify heapwalking API does not report array classes several times. + * @requires vm.jvmti + * @modules java.base/jdk.internal.vm.annotation java.base/jdk.internal.value + * @enablePreview + * @run main/othervm/native -agentlib:HeapwalkDupClasses HeapwalkDupClasses + */ + +import java.lang.ref.Reference; +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +public class HeapwalkDupClasses { + + static native int tagWithFollowReferences(long tag); + static native int tagWithIterateOverReachableObjects(long tag); + static native Object[] getObjectsWithTags(long tag); + + public static void main(String[] args) throws Exception { + System.loadLibrary("HeapwalkDupClasses"); + + Integer instance = new Integer(0); + Object[] testObjects = new Object[] { + new Integer[5], + ValueClass.newNullableAtomicArray(Integer.class, 5), + ValueClass.newNullRestrictedNonAtomicArray(Integer.class, 5, instance) + }; + + for (long tag = 1; tag <= 2; tag++) { + int taggedClasses; + if (tag == 1) { + System.out.println("FollowReferences"); + taggedClasses = tagWithFollowReferences(tag); + } else { + System.out.println("IterateOverReachableObjects"); + taggedClasses = tagWithIterateOverReachableObjects(tag); + } + System.out.println("Tagged " + taggedClasses + " classes"); + + Object[] taggedObjects = getObjectsWithTags(tag); + System.out.println("Tagged objects (total " + taggedObjects.length + "):"); + + int duplicates = 0; + boolean foundTestObjectClass[] = new boolean[testObjects.length]; + + for (int i = 0; i < taggedObjects.length; i++) { + System.out.println("[" + i + "] " + taggedObjects[i]); + for (int j = 0; j < i; j++) { + if (taggedObjects[i].equals(taggedObjects[j])) { + duplicates++; + System.out.println(" ERROR: duplicate (" + j + ")"); + } + } + for (int j = 0; j < testObjects.length; j++) { + if (taggedObjects[i].equals(testObjects[j].getClass())) { + foundTestObjectClass[j] = true; + System.out.println(" FOUND expected array class"); + } + } + } + if (duplicates != 0) { + throw new RuntimeException("Found " + duplicates + " duplicate classes"); + } + for (int i = 0; i < foundTestObjectClass.length; i++) { + if (!foundTestObjectClass[i]) { + throw new RuntimeException("Expected class not found: " + testObjects[i].getClass()); + } + } + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapwalkDupClasses/libHeapwalkDupClasses.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapwalkDupClasses/libHeapwalkDupClasses.cpp new file mode 100644 index 00000000000..d9a8fa8e7ff --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapwalkDupClasses/libHeapwalkDupClasses.cpp @@ -0,0 +1,129 @@ +/* + * 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. + */ + +#include +#include "jvmti_common.hpp" + +static jvmtiEnv *jvmti = nullptr; +struct CallbackData { + jlong tag; + jint counter; + + CallbackData(jlong tag): tag(tag), counter(0) {} +}; + +static jint JNICALL +heap_reference_callback(jvmtiHeapReferenceKind reference_kind, + const jvmtiHeapReferenceInfo* reference_info, + jlong class_tag, + jlong referrer_class_tag, + jlong size, + jlong* tag_ptr, + jlong* referrer_tag_ptr, + jint length, + void* user_data) { + if (reference_kind == JVMTI_HEAP_REFERENCE_SYSTEM_CLASS) { + CallbackData* data = (CallbackData*)user_data; + data->counter++; + *tag_ptr = data->tag; + } + return JVMTI_VISIT_OBJECTS; +} + +extern "C" JNIEXPORT jint JNICALL +Java_HeapwalkDupClasses_tagWithFollowReferences(JNIEnv* jni, jclass clazz, jlong tag) { + jvmtiHeapCallbacks callbacks = {}; + callbacks.heap_reference_callback = heap_reference_callback; + + CallbackData data(tag); + + jvmtiError err = jvmti->FollowReferences(0 /* filter nothing */, + nullptr /* no class filter */, + nullptr /* no initial object, follow roots */, + &callbacks, + &data); + check_jvmti_error(err, "FollowReferences failed"); + + return data.counter; +} + +static jvmtiIterationControl JNICALL +heap_root_callback(jvmtiHeapRootKind root_kind, + jlong class_tag, + jlong size, + jlong* tag_ptr, + void* user_data) { + if (root_kind == JVMTI_HEAP_ROOT_SYSTEM_CLASS) { + CallbackData* data = (CallbackData*)user_data; + data->counter++; + *tag_ptr = data->tag; + } + return JVMTI_ITERATION_CONTINUE; +} + +extern "C" JNIEXPORT jint JNICALL +Java_HeapwalkDupClasses_tagWithIterateOverReachableObjects(JNIEnv* jni, jclass clazz, jlong tag) { + CallbackData data(tag); + jvmtiError err = jvmti->IterateOverReachableObjects(heap_root_callback, + nullptr, + nullptr, + &data); + check_jvmti_error(err, "IterateOverReachableObjects failed"); + + return data.counter; +} + +extern "C" JNIEXPORT jobjectArray JNICALL +Java_HeapwalkDupClasses_getObjectsWithTags(JNIEnv* jni, jclass clazz, jlong tag) { + jlong tags[1] = {tag}; + + jint count = 0; + jobject* objects = nullptr; + + jvmtiError err = jvmti->GetObjectsWithTags(1, tags, + &count, &objects, nullptr); + check_jvmti_error(err, "GetObjectsWithTags failed"); + + jclass object_klass = jni->FindClass("java/lang/Object"); + jobjectArray array = jni->NewObjectArray(count, object_klass, nullptr); + + for (jint i = 0; i < count; i++) { + jni->SetObjectArrayElement(array, i, objects[i]); + } + + deallocate(jvmti, jni, objects); + + return array; +} + +extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + if (vm->GetEnv(reinterpret_cast(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) { + LOG("Could not initialize JVMTI\n"); + abort(); + } + jvmtiCapabilities capabilities; + memset(&capabilities, 0, sizeof(capabilities)); + capabilities.can_tag_objects = 1; + check_jvmti_error(jvmti->AddCapabilities(&capabilities), "adding capabilities"); + return JVMTI_ERROR_NONE; +}