1 /*
  2  * Copyright (c) 2025, 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:+UseArrayFlattening
 33  *                          -XX:+UseFieldFlattening
 34  *                          -XX:+UseAtomicValueFlattening
 35  *                          -XX:+UseNullableValueFlattening
 36  *                          -XX:+PrintInlineLayout
 37  *                          -XX:+PrintFlatArrayLayout
 38  *                          -Xlog:jvmti+table
 39  *                          ValueHeapwalkingTest
 40  */
 41 
 42 import java.lang.ref.Reference;
 43 import java.util.ArrayList;
 44 import java.util.Arrays;
 45 import java.util.List;
 46 import jdk.internal.value.ValueClass;
 47 import jdk.internal.vm.annotation.NullRestricted;
 48 import jdk.internal.vm.annotation.Strict;
 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         @Strict
 67         @NullRestricted
 68         public Value v1;
 69         @Strict
 70         @NullRestricted
 71         public Value v2;
 72         Value2() {
 73             this(0);
 74         }
 75         Value2(int i) {
 76             this.v1 = new Value(i);
 77             this.v2 = new Value(i+1);
 78             super();
 79         }
 80     }
 81 
 82     static value class ValueHolder {
 83         public Value v1;
 84         @Strict
 85         @NullRestricted
 86         public Value v2;
 87         public Value v_null;
 88 
 89         public Value2 v2_1;
 90         @Strict
 91         @NullRestricted
 92         public Value2 v2_2;
 93 
 94         public Value[] v_arr;
 95         public Value2[] v2_arr;
 96 
 97         public ValueHolder(int seed) throws Exception {
 98             v1 = new Value(seed);
 99             v2 = new Value(seed + 1);
100             v_null = null;
101 
102             v2_1 = new Value2(seed + 6);
103             v2_2 = new Value2(seed + 8);
104 
105             v_arr = createValueArray(seed);
106             v2_arr = createValue2Array(seed);
107         }
108     }
109 
110     static Value[] createValueArray(int seed) throws Exception {
111         Value[] arr = (Value[])ValueClass.newNullableAtomicArray(Value.class, 5);
112         for (int i = 0; i < arr.length; i++) {
113             arr[i] = i == 2 ? null : new Value(seed + 10 + i);
114         }
115         return arr;
116     }
117 
118     static Value2[] createValue2Array(int seed) throws Exception {
119         Value2[] arr = (Value2[])ValueClass.newNullRestrictedNonAtomicArray(Value2.class, 5, Value2.class.newInstance());
120         for (int i = 0; i < arr.length; i++) {
121             arr[i] = new Value2(seed + 20 + i * 2);
122         }
123         return arr;
124     }
125 
126     static final int TAG_VALUE_CLASS = 1;
127     static final int TAG_VALUE2_CLASS = 2;
128     static final int TAG_HOLDER_CLASS = 3;
129     static final int TAG_VALUE_ARRAY = 4;
130     static final int TAG_VALUE2_ARRAY = 5;
131 
132     static final int TAG_MIN = TAG_VALUE_CLASS;
133     static final int TAG_MAX = TAG_VALUE2_ARRAY;
134 
135     static String tagStr(int tag) {
136         String suffix = " (tag " + tag + ")";
137         switch (tag) {
138         case TAG_VALUE_CLASS: return "Value class" + suffix;
139         case TAG_VALUE2_CLASS: return "Value2 class" + suffix;
140         case TAG_HOLDER_CLASS: return "ValueHolder class" + suffix;
141         case TAG_VALUE_ARRAY: return "Value[] object" + suffix;
142         case TAG_VALUE2_ARRAY: return "Value2[] object" + suffix;
143         }
144         return "Unknown" + suffix;
145     }
146 
147     static native void setTag(Object object, long tag);
148     static native long getTag(Object object);
149 
150     static native void reset();
151 
152     static native void followReferences();
153 
154     static native void iterateThroughHeap();
155 
156     static native int count(int classTag);
157     static native int refCount(int fromTag, int toTag);
158     static native int primitiveFieldCount(int tag);
159 
160     static native long getMaxTag();
161 
162     static native int getObjectWithTags(long minTag, long maxTag, Object[] objects, long[] tags);
163 
164 
165     // counts non-null elements in the array
166     static <T> int nonNullCount(T[] array) {
167         return (int)Arrays.stream(array).filter(e -> e != null).count();
168     }
169 
170     static void verifyMinCount(int classTag, int minCount) {
171         int count = count(classTag);
172         String msg = tagStr(classTag) + " count: " + count + ", min expected: " + minCount;
173         if (count < minCount) {
174             throw new RuntimeException(msg);
175         }
176         System.out.println(msg);
177     }
178 
179     static void verifyRefCount(int tagFrom, int tagTo, int expectedCount) {
180         int count = refCount(tagFrom, tagTo);
181         String msg = "Ref.count from " + tagStr(tagFrom) + " to " + tagStr(tagTo) + ": "
182                    + count + ", expected: " + expectedCount;
183         if (count !=  expectedCount) {
184             throw new RuntimeException(msg);
185         }
186         System.out.println(msg);
187     }
188 
189     static void verifyPrimitiveFieldCount(int classTag, int expectedCount) {
190         int count = primitiveFieldCount(classTag);
191         String msg = "Primitive field count from " + tagStr(classTag) + ": "
192                    + count + ", expected: " + expectedCount;
193         if (count !=  expectedCount) {
194             throw new RuntimeException(msg);
195         }
196         System.out.println(msg);
197     }
198 
199 
200     static void printObject(Object obj) {
201         printObject("", obj);
202     }
203 
204     static void printObject(String prefix, Object obj) {
205         if (obj == null) {
206             System.out.println(prefix + "null");
207             return;
208           }
209 
210         Class<?> clazz = obj.getClass();
211         System.out.println(prefix + "Object (class " + clazz.getName() + ", tag = " + getTag(obj) + ", class tag = " + getTag(clazz));
212 
213         if (clazz.isArray()) {
214             Class<?> elementType = clazz.getComponentType();
215             int length = java.lang.reflect.Array.getLength(obj);
216             System.out.println(prefix + "Array of " + elementType + ", length = " + length + " [");
217             for (int i = 0; i < length; i++) {
218                 Object element = java.lang.reflect.Array.get(obj, i);
219                 if (elementType.isPrimitive()) {
220                     if (i == 0) {
221                         System.out.print(prefix + "  ");
222                     } else {
223                         System.out.print(", ");
224                     }
225                     System.out.print(prefix + "(" + i + "):" + element);
226                 } else {
227                     System.out.println(prefix + "(" + i + "):" + "NOT primitive");
228                     printObject(prefix + "  ", element);
229                 }
230             }
231             System.out.println(prefix + "]");
232         } else {
233             while (clazz != null && clazz != Object.class) {
234                 Field[] fields = clazz.getDeclaredFields();
235                 for (Field field : fields) {
236                     try {
237                         field.setAccessible(true);
238                         Object value = field.get(obj);
239                         Class<?> fieldType = field.getType();
240                         System.out.println(prefix + "- " + field.getName() + " (" + fieldType + ") = " + value);
241 
242                         if (!fieldType.isPrimitive()) {
243                             printObject(prefix + "  ", value);
244                         }
245                     } catch (IllegalAccessException | java.lang.reflect.InaccessibleObjectException e) {
246                         System.err.println("  Error accessing field " + field.getName() + ": " + e.getMessage());
247                     }
248                 }
249                 clazz = clazz.getSuperclass();
250             }
251         }
252     }
253 
254     public static void main(String[] args) throws Exception {
255         System.loadLibrary("ValueHeapwalkingTest");
256         ValueHolder holder = new ValueHolder(10);
257 
258         setTag(Value.class, TAG_VALUE_CLASS);
259         setTag(Value2.class, TAG_VALUE2_CLASS);
260         setTag(ValueHolder.class, TAG_HOLDER_CLASS);
261         setTag(holder.v_arr, TAG_VALUE_ARRAY);
262         setTag(holder.v2_arr, TAG_VALUE2_ARRAY);
263 
264         reset();
265         System.out.println(">>iterateThroughHeap");
266         iterateThroughHeap();
267         System.out.println("<<iterateThroughHeap");
268 
269         // IterateThroughHeap reports reachable and unreachable objects,
270         // so verify only minimum count.
271         for (int i = TAG_MIN; i <= TAG_MAX; i++) {
272             System.out.println(tagStr(i) + " count: " + count(i));
273         }
274         int reachableValueHolderCount = 1;
275         // v2_1, v2_2, v2_arr
276         int reachableValue2Count = reachableValueHolderCount * (2 + nonNullCount(holder.v2_arr));
277         // ValueHolder: v1, v2, v_arr
278         // Value2: v1, v2
279         int reachableValueCount = reachableValueHolderCount * (2 + nonNullCount(holder.v_arr))
280                                 + reachableValue2Count * 2;
281         verifyMinCount(TAG_VALUE_CLASS, reachableValueCount);
282         verifyMinCount(TAG_VALUE2_CLASS, reachableValue2Count);
283         verifyMinCount(TAG_HOLDER_CLASS, reachableValueHolderCount);
284         // For each Value object 1 primitive field must be reported.
285         verifyPrimitiveFieldCount(TAG_VALUE_CLASS, count(TAG_VALUE_CLASS));
286 
287         reset();  // to reset primitiveFieldCount
288         System.out.println(">>followReferences");
289         followReferences();
290         System.out.println("<<followReferences");
291 
292         long maxTag = getMaxTag();
293 
294         for (int i = TAG_MIN; i <= TAG_MAX; i++) {
295             for (int j = TAG_MIN; j <= TAG_MAX; j++) {
296                 System.out.println("Reference from " + tagStr(i) + " to " + tagStr(j) + ": " + refCount(i, j));
297             }
298         }
299 
300         printObject(holder);
301 
302         // ValueHolder: v1, v2
303         verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE_CLASS, 2);
304         // ValueHolder: v2_1, v2_2
305         verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE_CLASS, 2);
306         // ValueHolder: v_arr
307         verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE_ARRAY, 1);
308         // ValueHolder: v2_arr
309         verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE2_ARRAY, 1);
310 
311         // References from arrays to their elements
312         verifyRefCount(TAG_VALUE_ARRAY, TAG_VALUE_CLASS, nonNullCount(holder.v_arr));
313         verifyRefCount(TAG_VALUE2_ARRAY, TAG_VALUE2_CLASS, nonNullCount(holder.v2_arr));
314 
315         // Each Value2 object must have 2 references to Value object (v1, v2).
316         verifyRefCount(TAG_VALUE2_CLASS, TAG_VALUE_CLASS, reachableValue2Count * 2);
317 
318         // For each Value object 1 primitive field must be reported.
319         verifyPrimitiveFieldCount(TAG_VALUE_CLASS, reachableValueCount);
320 
321         System.out.println(">>followReferences (2)");
322         followReferences();
323         System.out.println("<<followReferences (2)");
324 
325         long maxTag2 = getMaxTag();
326         // no new objects are expected to be tagged
327         if (maxTag != maxTag2) {
328             throw new RuntimeException("maxTag (" + maxTag + ") not equal to maxTag2(" + maxTag2 + ")");
329         }
330 
331         Object[] objects = new Object[1024];
332         long tags[] = new long[1024];
333         System.out.println(">>getObjectWithTags, maxTag = " + maxTag);
334         int count = getObjectWithTags(1, maxTag, objects, tags);
335         System.out.println("getObjectWithTags returned " + count);
336         for (int i = 0; i < count; i++) {
337             System.out.println("  [" + i + "] " + objects[i] + ", tag = " + tags[i]);
338             if (objects[i] == null || tags[i] == 0) {
339                 throw new RuntimeException("unexpected object");
340             }
341         }
342         int expectedTaggedCount = 5 // TAG_VALUE_CLASS/TAG_VALUE2_CLASS/TAG_HOLDER_CLASS/TAG_VALUE_ARRAY/TAG_VALUE2_ARRAY
343                 + reachableValue2Count + reachableValueCount;
344         if (count !=  expectedTaggedCount) {
345             throw new RuntimeException("unexpected getObjectWithTags result: " + count
346                                      + ", expected " + expectedTaggedCount);
347         }
348 
349         Reference.reachabilityFence(holder);
350     }
351 }