1 /*
  2  * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test
 26  * @summary Tests heapwalking API (FollowReferences, IterateThroughHeap, GetObjectsWithTags) for value objects.
 27  * @requires vm.jvmti
 28  * @modules java.base/jdk.internal.vm.annotation java.base/jdk.internal.value
 29  * @enablePreview
 30  * @run main/othervm/native -agentlib:ValueHeapwalkingTest
 31  *                          -XX:+UnlockDiagnosticVMOptions
 32  *                          -XX:+UnlockExperimentalVMOptions
 33  *                          -XX:+UseArrayFlattening
 34  *                          -XX:+UseFieldFlattening
 35  *                          -XX:+UseNullFreeAtomicValueFlattening
 36  *                          -XX:+UseNullableAtomicValueFlattening
 37  *                          -XX:+PrintInlineLayout
 38  *                          -XX:+PrintFlatArrayLayout
 39  *                          -Xlog:jvmti+table
 40  *                          ValueHeapwalkingTest
 41  */
 42 
 43 import java.lang.ref.Reference;
 44 import java.util.ArrayList;
 45 import java.util.Arrays;
 46 import java.util.List;
 47 import jdk.internal.value.ValueClass;
 48 import jdk.internal.vm.annotation.NullRestricted;
 49 
 50 import java.lang.reflect.Field;
 51 
 52 public class ValueHeapwalkingTest {
 53 
 54     static value class Value {
 55         int v;
 56         Value() {
 57             this(0);
 58         }
 59         Value(int v) {
 60             this.v = v;
 61         }
 62     }
 63 
 64     // flat object has flat field (address of the field is the same as address of the object)
 65     static value class Value2 {
 66         @NullRestricted
 67         public Value v1;
 68         @NullRestricted
 69         public Value v2;
 70         Value2() {
 71             this(0);
 72         }
 73         Value2(int i) {
 74             this.v1 = new Value(i);
 75             this.v2 = new Value(i+1);
 76             super();
 77         }
 78     }
 79 
 80     static value class ValueHolder {
 81         public Value v1;
 82         @NullRestricted
 83         public Value v2;
 84         public Value v_null;
 85 
 86         public Value2 v2_1;
 87         @NullRestricted
 88         public Value2 v2_2;
 89 
 90         public Value[] v_arr;
 91         public Value2[] v2_arr;
 92 
 93         public ValueHolder(int seed) throws Exception {
 94             v1 = new Value(seed);
 95             v2 = new Value(seed + 1);
 96             v_null = null;
 97 
 98             v2_1 = new Value2(seed + 6);
 99             v2_2 = new Value2(seed + 8);
100 
101             v_arr = createValueArray(seed);
102             v2_arr = createValue2Array(seed);
103         }
104     }
105 
106     static Value[] createValueArray(int seed) throws Exception {
107         Value[] arr = (Value[])ValueClass.newNullableAtomicArray(Value.class, 5);
108         for (int i = 0; i < arr.length; i++) {
109             arr[i] = i == 2 ? null : new Value(seed + 10 + i);
110         }
111         return arr;
112     }
113 
114     static Value2[] createValue2Array(int seed) throws Exception {
115         Value2[] arr = (Value2[])ValueClass.newNullRestrictedNonAtomicArray(Value2.class, 5, Value2.class.newInstance());
116         for (int i = 0; i < arr.length; i++) {
117             arr[i] = new Value2(seed + 20 + i * 2);
118         }
119         return arr;
120     }
121 
122     static final int TAG_VALUE_CLASS = 1;
123     static final int TAG_VALUE2_CLASS = 2;
124     static final int TAG_HOLDER_CLASS = 3;
125     static final int TAG_VALUE_ARRAY = 4;
126     static final int TAG_VALUE2_ARRAY = 5;
127 
128     static final int TAG_MIN = TAG_VALUE_CLASS;
129     static final int TAG_MAX = TAG_VALUE2_ARRAY;
130 
131     static String tagStr(int tag) {
132         String suffix = " (tag " + tag + ")";
133         switch (tag) {
134         case TAG_VALUE_CLASS: return "Value class" + suffix;
135         case TAG_VALUE2_CLASS: return "Value2 class" + suffix;
136         case TAG_HOLDER_CLASS: return "ValueHolder class" + suffix;
137         case TAG_VALUE_ARRAY: return "Value[] object" + suffix;
138         case TAG_VALUE2_ARRAY: return "Value2[] object" + suffix;
139         }
140         return "Unknown" + suffix;
141     }
142 
143     static native void setTag(Object object, long tag);
144     static native long getTag(Object object);
145 
146     static native void reset();
147 
148     static native void followReferences();
149 
150     static native void iterateThroughHeap();
151 
152     static native int count(int classTag);
153     static native int refCount(int fromTag, int toTag);
154     static native int primitiveFieldCount(int tag);
155 
156     static native long getMaxTag();
157 
158     static native int getObjectWithTags(long minTag, long maxTag, Object[] objects, long[] tags);
159 
160 
161     // counts non-null elements in the array
162     static <T> int nonNullCount(T[] array) {
163         return (int)Arrays.stream(array).filter(e -> e != null).count();
164     }
165 
166     static void verifyMinCount(int classTag, int minCount) {
167         int count = count(classTag);
168         String msg = tagStr(classTag) + " count: " + count + ", min expected: " + minCount;
169         if (count < minCount) {
170             throw new RuntimeException(msg);
171         }
172         System.out.println(msg);
173     }
174 
175     static void verifyRefCount(int tagFrom, int tagTo, int expectedCount) {
176         int count = refCount(tagFrom, tagTo);
177         String msg = "Ref.count from " + tagStr(tagFrom) + " to " + tagStr(tagTo) + ": "
178                    + count + ", expected: " + expectedCount;
179         if (count !=  expectedCount) {
180             throw new RuntimeException(msg);
181         }
182         System.out.println(msg);
183     }
184 
185     static void verifyPrimitiveFieldCount(int classTag, int expectedCount) {
186         int count = primitiveFieldCount(classTag);
187         String msg = "Primitive field count from " + tagStr(classTag) + ": "
188                    + count + ", expected: " + expectedCount;
189         if (count !=  expectedCount) {
190             throw new RuntimeException(msg);
191         }
192         System.out.println(msg);
193     }
194 
195 
196     static void printObject(Object obj) {
197         printObject("", obj);
198     }
199 
200     static void printObject(String prefix, Object obj) {
201         if (obj == null) {
202             System.out.println(prefix + "null");
203             return;
204           }
205 
206         Class<?> clazz = obj.getClass();
207         System.out.println(prefix + "Object (class " + clazz.getName() + ", tag = " + getTag(obj) + ", class tag = " + getTag(clazz));
208 
209         if (clazz.isArray()) {
210             Class<?> elementType = clazz.getComponentType();
211             int length = java.lang.reflect.Array.getLength(obj);
212             System.out.println(prefix + "Array of " + elementType + ", length = " + length + " [");
213             for (int i = 0; i < length; i++) {
214                 Object element = java.lang.reflect.Array.get(obj, i);
215                 if (elementType.isPrimitive()) {
216                     if (i == 0) {
217                         System.out.print(prefix + "  ");
218                     } else {
219                         System.out.print(", ");
220                     }
221                     System.out.print(prefix + "(" + i + "):" + element);
222                 } else {
223                     System.out.println(prefix + "(" + i + "):" + "NOT primitive");
224                     printObject(prefix + "  ", element);
225                 }
226             }
227             System.out.println(prefix + "]");
228         } else {
229             while (clazz != null && clazz != Object.class) {
230                 Field[] fields = clazz.getDeclaredFields();
231                 for (Field field : fields) {
232                     try {
233                         field.setAccessible(true);
234                         Object value = field.get(obj);
235                         Class<?> fieldType = field.getType();
236                         System.out.println(prefix + "- " + field.getName() + " (" + fieldType + ") = " + value);
237 
238                         if (!fieldType.isPrimitive()) {
239                             printObject(prefix + "  ", value);
240                         }
241                     } catch (IllegalAccessException | java.lang.reflect.InaccessibleObjectException e) {
242                         System.err.println("  Error accessing field " + field.getName() + ": " + e.getMessage());
243                     }
244                 }
245                 clazz = clazz.getSuperclass();
246             }
247         }
248     }
249 
250     public static void main(String[] args) throws Exception {
251         System.loadLibrary("ValueHeapwalkingTest");
252         ValueHolder holder = new ValueHolder(10);
253 
254         setTag(Value.class, TAG_VALUE_CLASS);
255         setTag(Value2.class, TAG_VALUE2_CLASS);
256         setTag(ValueHolder.class, TAG_HOLDER_CLASS);
257         setTag(holder.v_arr, TAG_VALUE_ARRAY);
258         setTag(holder.v2_arr, TAG_VALUE2_ARRAY);
259 
260         reset();
261         System.out.println(">>iterateThroughHeap");
262         iterateThroughHeap();
263         System.out.println("<<iterateThroughHeap");
264 
265         // IterateThroughHeap reports reachable and unreachable objects,
266         // so verify only minimum count.
267         for (int i = TAG_MIN; i <= TAG_MAX; i++) {
268             System.out.println(tagStr(i) + " count: " + count(i));
269         }
270         int reachableValueHolderCount = 1;
271         // v2_1, v2_2, v2_arr
272         int reachableValue2Count = reachableValueHolderCount * (2 + nonNullCount(holder.v2_arr));
273         // ValueHolder: v1, v2, v_arr
274         // Value2: v1, v2
275         int reachableValueCount = reachableValueHolderCount * (2 + nonNullCount(holder.v_arr))
276                                 + reachableValue2Count * 2;
277         verifyMinCount(TAG_VALUE_CLASS, reachableValueCount);
278         verifyMinCount(TAG_VALUE2_CLASS, reachableValue2Count);
279         verifyMinCount(TAG_HOLDER_CLASS, reachableValueHolderCount);
280         // For each Value object 1 primitive field must be reported.
281         verifyPrimitiveFieldCount(TAG_VALUE_CLASS, count(TAG_VALUE_CLASS));
282 
283         reset();  // to reset primitiveFieldCount
284         System.out.println(">>followReferences");
285         followReferences();
286         System.out.println("<<followReferences");
287 
288         long maxTag = getMaxTag();
289 
290         for (int i = TAG_MIN; i <= TAG_MAX; i++) {
291             for (int j = TAG_MIN; j <= TAG_MAX; j++) {
292                 System.out.println("Reference from " + tagStr(i) + " to " + tagStr(j) + ": " + refCount(i, j));
293             }
294         }
295 
296         printObject(holder);
297 
298         // ValueHolder: v1, v2
299         verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE_CLASS, 2);
300         // ValueHolder: v2_1, v2_2
301         verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE_CLASS, 2);
302         // ValueHolder: v_arr
303         verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE_ARRAY, 1);
304         // ValueHolder: v2_arr
305         verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE2_ARRAY, 1);
306 
307         // References from arrays to their elements
308         verifyRefCount(TAG_VALUE_ARRAY, TAG_VALUE_CLASS, nonNullCount(holder.v_arr));
309         verifyRefCount(TAG_VALUE2_ARRAY, TAG_VALUE2_CLASS, nonNullCount(holder.v2_arr));
310 
311         // Each Value2 object must have 2 references to Value object (v1, v2).
312         verifyRefCount(TAG_VALUE2_CLASS, TAG_VALUE_CLASS, reachableValue2Count * 2);
313 
314         // For each Value object 1 primitive field must be reported.
315         verifyPrimitiveFieldCount(TAG_VALUE_CLASS, reachableValueCount);
316 
317         System.out.println(">>followReferences (2)");
318         followReferences();
319         System.out.println("<<followReferences (2)");
320 
321         long maxTag2 = getMaxTag();
322         // no new objects are expected to be tagged
323         if (maxTag != maxTag2) {
324             throw new RuntimeException("maxTag (" + maxTag + ") not equal to maxTag2(" + maxTag2 + ")");
325         }
326 
327         Object[] objects = new Object[1024];
328         long tags[] = new long[1024];
329         System.out.println(">>getObjectWithTags, maxTag = " + maxTag);
330         int count = getObjectWithTags(1, maxTag, objects, tags);
331         System.out.println("getObjectWithTags returned " + count);
332         for (int i = 0; i < count; i++) {
333             System.out.println("  [" + i + "] " + objects[i] + ", tag = " + tags[i]);
334             if (objects[i] == null || tags[i] == 0) {
335                 throw new RuntimeException("unexpected object");
336             }
337         }
338         int expectedTaggedCount = 5 // TAG_VALUE_CLASS/TAG_VALUE2_CLASS/TAG_HOLDER_CLASS/TAG_VALUE_ARRAY/TAG_VALUE2_ARRAY
339                 + reachableValue2Count + reachableValueCount;
340         if (count !=  expectedTaggedCount) {
341             throw new RuntimeException("unexpected getObjectWithTags result: " + count
342                                      + ", expected " + expectedTaggedCount);
343         }
344 
345         Reference.reachabilityFence(holder);
346     }
347 }