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; +}