/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include "android-base/macros.h" #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "jni.h" #include "jvmti.h" // Test infrastructure #include "jni_helper.h" #include "jvmti_helper.h" #include "test_env.h" #include "ti_utf.h" namespace art { namespace Test913Heaps { using android::base::StringPrintf; #define FINAL final #define OVERRIDE override #define UNREACHABLE __builtin_unreachable extern "C" JNIEXPORT void JNICALL Java_art_Test913_forceGarbageCollection( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { jvmtiError ret = jvmti_env->ForceGarbageCollection(); JvmtiErrorToException(env, jvmti_env, ret); } class IterationConfig { public: IterationConfig() {} virtual ~IterationConfig() {} virtual jint Handle(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) = 0; }; static jint JNICALL HeapReferenceCallback(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) { IterationConfig* config = reinterpret_cast(user_data); return config->Handle(reference_kind, reference_info, class_tag, referrer_class_tag, size, tag_ptr, referrer_tag_ptr, length, user_data); } static bool Run(JNIEnv* env, jint heap_filter, jclass klass_filter, jobject initial_object, IterationConfig* config) { jvmtiHeapCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); callbacks.heap_reference_callback = HeapReferenceCallback; jvmtiError ret = jvmti_env->FollowReferences(heap_filter, klass_filter, initial_object, &callbacks, config); return !JvmtiErrorToException(env, jvmti_env, ret); } extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test913_followReferences( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jint heap_filter, jclass klass_filter, jobject initial_object, jint stop_after, jint follow_set, jobject jniRef) { class PrintIterationConfig FINAL : public IterationConfig { public: PrintIterationConfig(jint _stop_after, jint _follow_set) : counter_(0), stop_after_(_stop_after), follow_set_(_follow_set) { } jint Handle(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 ATTRIBUTE_UNUSED) OVERRIDE { jlong tag = *tag_ptr; // Ignore any jni-global roots with untagged classes. These can be from the environment, // or the JIT. if (reference_kind == JVMTI_HEAP_REFERENCE_JNI_GLOBAL && class_tag == 0) { return 0; } // Ignore classes (1000 <= tag < 3000) for thread objects. These can be held by the JIT. if (reference_kind == JVMTI_HEAP_REFERENCE_THREAD && class_tag == 0 && (1000 <= *tag_ptr && *tag_ptr < 3000)) { return 0; } // Ignore stack-locals of untagged threads. That is the environment. if (reference_kind == JVMTI_HEAP_REFERENCE_STACK_LOCAL && reference_info->stack_local.thread_tag != 3000) { return 0; } // Ignore array elements with an untagged source. These are from the environment. if (reference_kind == JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT && *referrer_tag_ptr == 0) { return 0; } // Only check tagged objects. if (tag == 0) { return JVMTI_VISIT_OBJECTS; } Print(reference_kind, reference_info, class_tag, referrer_class_tag, size, tag_ptr, referrer_tag_ptr, length); counter_++; if (counter_ == stop_after_) { return JVMTI_VISIT_ABORT; } if (tag > 0 && tag < 32) { bool should_visit_references = (follow_set_ & (1 << static_cast(tag))) != 0; return should_visit_references ? JVMTI_VISIT_OBJECTS : 0; } return JVMTI_VISIT_OBJECTS; } void Print(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) { std::string referrer_str; if (referrer_tag_ptr == nullptr) { referrer_str = "root@root"; } else { referrer_str = StringPrintf("%" PRId64 "@%" PRId64, *referrer_tag_ptr, referrer_class_tag); } jlong adapted_size = size; if (*tag_ptr >= 1000) { // This is a class or interface, the size of which will be dependent on the architecture. // Do not print the size, but detect known values and "normalize" for the golden file. if ((sizeof(void*) == 4 && size == 172) || (sizeof(void*) == 8 && size == 224)) { adapted_size = 123; } } std::string referree_str = StringPrintf("%" PRId64 "@%" PRId64, *tag_ptr, class_tag); lines_.push_back(CreateElem(referrer_str, referree_str, reference_kind, reference_info, adapted_size, length)); } std::vector GetLines() const { std::vector ret; for (const std::unique_ptr& e : lines_) { ret.push_back(e->Print()); } return ret; } private: // We need to postpone some printing, as required functions are not callback-safe. class Elem { public: Elem(const std::string& referrer, const std::string& referree, jlong size, jint length) : referrer_(referrer), referree_(referree), size_(size), length_(length) {} virtual ~Elem() {} std::string Print() const { return StringPrintf("%s --(%s)--> %s [size=%" PRId64 ", length=%d]", referrer_.c_str(), PrintArrowType().c_str(), referree_.c_str(), size_, length_); } protected: virtual std::string PrintArrowType() const = 0; private: std::string referrer_; std::string referree_; jlong size_; jint length_; }; class JNILocalElement : public Elem { public: JNILocalElement(const std::string& referrer, const std::string& referree, jlong size, jint length, const jvmtiHeapReferenceInfo* reference_info) : Elem(referrer, referree, size, length) { memcpy(&info_, reference_info, sizeof(jvmtiHeapReferenceInfo)); } protected: std::string PrintArrowType() const OVERRIDE { char* name = nullptr; if (info_.jni_local.method != nullptr) { jvmti_env->GetMethodName(info_.jni_local.method, &name, nullptr, nullptr); } // Normalize the thread id, as this depends on the number of other threads // and which thread is running the test. Should be: // jlong thread_id = info_.jni_local.thread_id; // TODO: A pre-pass before the test should be able fetch this number, so it can // be compared explicitly. jlong thread_id = 1; std::string ret = StringPrintf("jni-local[id=%" PRId64 ",tag=%" PRId64 ",depth=%d," "method=%s]", thread_id, info_.jni_local.thread_tag, info_.jni_local.depth, name == nullptr ? "" : name); if (name != nullptr) { jvmti_env->Deallocate(reinterpret_cast(name)); } return ret; } private: const std::string string_; jvmtiHeapReferenceInfo info_; }; class StackLocalElement : public Elem { public: StackLocalElement(const std::string& referrer, const std::string& referree, jlong size, jint length, const jvmtiHeapReferenceInfo* reference_info) : Elem(referrer, referree, size, length) { memcpy(&info_, reference_info, sizeof(jvmtiHeapReferenceInfo)); // Debug code. Try to figure out where bad depth is coming from. if (reference_info->stack_local.depth == 6) { LOG(FATAL) << "Unexpected depth of 6"; } } protected: std::string PrintArrowType() const OVERRIDE { char* name = nullptr; if (info_.stack_local.method != nullptr) { jvmti_env->GetMethodName(info_.stack_local.method, &name, nullptr, nullptr); } // Normalize the thread id, as this depends on the number of other threads // and which thread is running the test. Should be: // jlong thread_id = info_.stack_local.thread_id; // TODO: A pre-pass before the test should be able fetch this number, so it can // be compared explicitly. jlong thread_id = 1; std::string ret = StringPrintf("stack-local[id=%" PRId64 ",tag=%" PRId64 ",depth=%d," "method=%s,vreg=%d,location=% " PRId64 "]", thread_id, info_.stack_local.thread_tag, info_.stack_local.depth, name == nullptr ? "" : name, info_.stack_local.slot, info_.stack_local.location); if (name != nullptr) { jvmti_env->Deallocate(reinterpret_cast(name)); } return ret; } private: const std::string string_; jvmtiHeapReferenceInfo info_; }; // For simple or unimplemented cases. class StringElement : public Elem { public: StringElement(const std::string& referrer, const std::string& referree, jlong size, jint length, const std::string& string) : Elem(referrer, referree, size, length), string_(string) {} protected: std::string PrintArrowType() const OVERRIDE { return string_; } private: const std::string string_; }; static std::unique_ptr CreateElem(const std::string& referrer, const std::string& referree, jvmtiHeapReferenceKind reference_kind, const jvmtiHeapReferenceInfo* reference_info, jlong size, jint length) { switch (reference_kind) { case JVMTI_HEAP_REFERENCE_CLASS: return std::unique_ptr(new StringElement(referrer, referree, size, length, "class")); case JVMTI_HEAP_REFERENCE_FIELD: { std::string tmp = StringPrintf("field@%d", reference_info->field.index); return std::unique_ptr(new StringElement(referrer, referree, size, length, tmp)); } case JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT: { jint index = reference_info->array.index; // Normalize if it's "0@0" -> "3000@1". // TODO: A pre-pass could probably give us this index to check explicitly. if (referrer == "0@0" && referree == "3000@0") { index = 0; } std::string tmp = StringPrintf("array-element@%d", index); return std::unique_ptr(new StringElement(referrer, referree, size, length, tmp)); } case JVMTI_HEAP_REFERENCE_CLASS_LOADER: return std::unique_ptr(new StringElement(referrer, referree, size, length, "classloader")); case JVMTI_HEAP_REFERENCE_SIGNERS: return std::unique_ptr(new StringElement(referrer, referree, size, length, "signers")); case JVMTI_HEAP_REFERENCE_PROTECTION_DOMAIN: return std::unique_ptr(new StringElement(referrer, referree, size, length, "protection-domain")); case JVMTI_HEAP_REFERENCE_INTERFACE: return std::unique_ptr(new StringElement(referrer, referree, size, length, "interface")); case JVMTI_HEAP_REFERENCE_STATIC_FIELD: { std::string tmp = StringPrintf("array-element@%d", reference_info->array.index); return std::unique_ptr(new StringElement(referrer, referree, size, length, tmp));; } case JVMTI_HEAP_REFERENCE_CONSTANT_POOL: return std::unique_ptr(new StringElement(referrer, referree, size, length, "constant-pool")); case JVMTI_HEAP_REFERENCE_SUPERCLASS: return std::unique_ptr(new StringElement(referrer, referree, size, length, "superclass")); case JVMTI_HEAP_REFERENCE_JNI_GLOBAL: return std::unique_ptr(new StringElement(referrer, referree, size, length, "jni-global")); case JVMTI_HEAP_REFERENCE_SYSTEM_CLASS: return std::unique_ptr(new StringElement(referrer, referree, size, length, "system-class")); case JVMTI_HEAP_REFERENCE_MONITOR: return std::unique_ptr(new StringElement(referrer, referree, size, length, "monitor")); case JVMTI_HEAP_REFERENCE_STACK_LOCAL: return std::unique_ptr(new StackLocalElement(referrer, referree, size, length, reference_info)); case JVMTI_HEAP_REFERENCE_JNI_LOCAL: return std::unique_ptr(new JNILocalElement(referrer, referree, size, length, reference_info)); case JVMTI_HEAP_REFERENCE_THREAD: return std::unique_ptr(new StringElement(referrer, referree, size, length, "thread")); case JVMTI_HEAP_REFERENCE_OTHER: return std::unique_ptr(new StringElement(referrer, referree, size, length, "other")); } LOG(FATAL) << "Unknown kind"; UNREACHABLE(); } jint counter_; const jint stop_after_; const jint follow_set_; std::vector> lines_; }; // If jniRef isn't null, add a local and a global ref. ScopedLocalRef jni_local_ref(env, nullptr); jobject jni_global_ref = nullptr; if (jniRef != nullptr) { jni_local_ref.reset(env->NewLocalRef(jniRef)); jni_global_ref = env->NewGlobalRef(jniRef); } PrintIterationConfig config(stop_after, follow_set); if (!Run(env, heap_filter, klass_filter, initial_object, &config)) { return nullptr; } std::vector lines = config.GetLines(); jobjectArray ret = CreateObjectArray(env, static_cast(lines.size()), "java/lang/String", [&](jint i) { return env->NewStringUTF(lines[i].c_str()); }); if (jni_global_ref != nullptr) { env->DeleteGlobalRef(jni_global_ref); } return ret; } extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test913_followReferencesString( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject initial_object) { struct FindStringCallbacks { static jint JNICALL FollowReferencesCallback( jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED, const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED, jlong class_tag ATTRIBUTE_UNUSED, jlong referrer_class_tag ATTRIBUTE_UNUSED, jlong size ATTRIBUTE_UNUSED, jlong* tag_ptr ATTRIBUTE_UNUSED, jlong* referrer_tag_ptr ATTRIBUTE_UNUSED, jint length ATTRIBUTE_UNUSED, void* user_data ATTRIBUTE_UNUSED) { return JVMTI_VISIT_OBJECTS; // Continue visiting. } static jint JNICALL StringValueCallback(jlong class_tag, jlong size, jlong* tag_ptr, const jchar* value, jint value_length, void* user_data) { FindStringCallbacks* p = reinterpret_cast(user_data); if (*tag_ptr != 0) { size_t utf_byte_count = ti::CountUtf8Bytes(value, value_length); std::unique_ptr mod_utf(new char[utf_byte_count + 1]); memset(mod_utf.get(), 0, utf_byte_count + 1); ti::ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length); p->data.push_back(android::base::StringPrintf("%" PRId64 "@%" PRId64 " (%" PRId64 ", '%s')", *tag_ptr, class_tag, size, mod_utf.get())); // Update the tag to test whether that works. *tag_ptr = *tag_ptr + 1; } return 0; } std::vector data; }; jvmtiHeapCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); callbacks.heap_reference_callback = FindStringCallbacks::FollowReferencesCallback; callbacks.string_primitive_value_callback = FindStringCallbacks::StringValueCallback; FindStringCallbacks fsc; jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, initial_object, &callbacks, &fsc); if (JvmtiErrorToException(env, jvmti_env, ret)) { return nullptr; } jobjectArray retArray = CreateObjectArray(env, static_cast(fsc.data.size()), "java/lang/String", [&](jint i) { return env->NewStringUTF(fsc.data[i].c_str()); }); return retArray; } extern "C" JNIEXPORT jstring JNICALL Java_art_Test913_followReferencesPrimitiveArray( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject initial_object) { struct FindArrayCallbacks { static jint JNICALL FollowReferencesCallback( jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED, const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED, jlong class_tag ATTRIBUTE_UNUSED, jlong referrer_class_tag ATTRIBUTE_UNUSED, jlong size ATTRIBUTE_UNUSED, jlong* tag_ptr ATTRIBUTE_UNUSED, jlong* referrer_tag_ptr ATTRIBUTE_UNUSED, jint length ATTRIBUTE_UNUSED, void* user_data ATTRIBUTE_UNUSED) { return JVMTI_VISIT_OBJECTS; // Continue visiting. } static jint JNICALL ArrayValueCallback(jlong class_tag, jlong size, jlong* tag_ptr, jint element_count, jvmtiPrimitiveType element_type, const void* elements, void* user_data) { FindArrayCallbacks* p = reinterpret_cast(user_data); if (*tag_ptr != 0) { std::ostringstream oss; oss << *tag_ptr << '@' << class_tag << " (" << size << ", " << element_count << "x" << static_cast(element_type) << " '"; size_t element_size; switch (element_type) { case JVMTI_PRIMITIVE_TYPE_BOOLEAN: case JVMTI_PRIMITIVE_TYPE_BYTE: element_size = 1; break; case JVMTI_PRIMITIVE_TYPE_CHAR: case JVMTI_PRIMITIVE_TYPE_SHORT: element_size = 2; break; case JVMTI_PRIMITIVE_TYPE_INT: case JVMTI_PRIMITIVE_TYPE_FLOAT: element_size = 4; break; case JVMTI_PRIMITIVE_TYPE_LONG: case JVMTI_PRIMITIVE_TYPE_DOUBLE: element_size = 8; break; default: LOG(FATAL) << "Unknown type " << static_cast(element_type); UNREACHABLE(); } const uint8_t* data = reinterpret_cast(elements); for (size_t i = 0; i != element_size * element_count; ++i) { oss << android::base::StringPrintf("%02x", data[i]); } oss << "')"; if (!p->data.empty()) { p->data += "\n"; } p->data += oss.str(); // Update the tag to test whether that works. *tag_ptr = *tag_ptr + 1; } return 0; } std::string data; }; jvmtiHeapCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); callbacks.heap_reference_callback = FindArrayCallbacks::FollowReferencesCallback; callbacks.array_primitive_value_callback = FindArrayCallbacks::ArrayValueCallback; FindArrayCallbacks fac; jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, initial_object, &callbacks, &fac); if (JvmtiErrorToException(env, jvmti_env, ret)) { return nullptr; } return env->NewStringUTF(fac.data.c_str()); } static constexpr const char* GetPrimitiveTypeName(jvmtiPrimitiveType type) { switch (type) { case JVMTI_PRIMITIVE_TYPE_BOOLEAN: return "boolean"; case JVMTI_PRIMITIVE_TYPE_BYTE: return "byte"; case JVMTI_PRIMITIVE_TYPE_CHAR: return "char"; case JVMTI_PRIMITIVE_TYPE_SHORT: return "short"; case JVMTI_PRIMITIVE_TYPE_INT: return "int"; case JVMTI_PRIMITIVE_TYPE_FLOAT: return "float"; case JVMTI_PRIMITIVE_TYPE_LONG: return "long"; case JVMTI_PRIMITIVE_TYPE_DOUBLE: return "double"; } LOG(FATAL) << "Unknown type " << static_cast(type); UNREACHABLE(); } extern "C" JNIEXPORT jstring JNICALL Java_art_Test913_followReferencesPrimitiveFields( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject initial_object) { struct FindFieldCallbacks { static jint JNICALL FollowReferencesCallback( jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED, const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED, jlong class_tag ATTRIBUTE_UNUSED, jlong referrer_class_tag ATTRIBUTE_UNUSED, jlong size ATTRIBUTE_UNUSED, jlong* tag_ptr ATTRIBUTE_UNUSED, jlong* referrer_tag_ptr ATTRIBUTE_UNUSED, jint length ATTRIBUTE_UNUSED, void* user_data ATTRIBUTE_UNUSED) { return JVMTI_VISIT_OBJECTS; // Continue visiting. } static jint JNICALL PrimitiveFieldValueCallback(jvmtiHeapReferenceKind kind, const jvmtiHeapReferenceInfo* info, jlong class_tag, jlong* tag_ptr, jvalue value, jvmtiPrimitiveType value_type, void* user_data) { FindFieldCallbacks* p = reinterpret_cast(user_data); if (*tag_ptr != 0) { std::ostringstream oss; oss << *tag_ptr << '@' << class_tag << " (" << (kind == JVMTI_HEAP_REFERENCE_FIELD ? "instance, " : "static, ") << GetPrimitiveTypeName(value_type) << ", index=" << info->field.index << ") "; // Be lazy, always print eight bytes. static_assert(sizeof(jvalue) == sizeof(uint64_t), "Unexpected jvalue size"); uint64_t val; memcpy(&val, &value, sizeof(uint64_t)); // To avoid undefined behavior. oss << android::base::StringPrintf("%016" PRIx64, val); if (!p->data.empty()) { p->data += "\n"; } p->data += oss.str(); // Update the tag to test whether that works. *tag_ptr = *tag_ptr + 1; } return 0; } std::string data; }; jvmtiHeapCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); callbacks.heap_reference_callback = FindFieldCallbacks::FollowReferencesCallback; callbacks.primitive_field_callback = FindFieldCallbacks::PrimitiveFieldValueCallback; FindFieldCallbacks ffc; jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, initial_object, &callbacks, &ffc); if (JvmtiErrorToException(env, jvmti_env, ret)) { return nullptr; } return env->NewStringUTF(ffc.data.c_str()); } // This is copied from test 908. Consider moving this to the main shim. static size_t starts = 0; static size_t finishes = 0; static void JNICALL GarbageCollectionFinish(jvmtiEnv* ti_env ATTRIBUTE_UNUSED) { finishes++; } static void JNICALL GarbageCollectionStart(jvmtiEnv* ti_env ATTRIBUTE_UNUSED) { starts++; } extern "C" JNIEXPORT void JNICALL Java_art_Test913_setupGcCallback( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { jvmtiEventCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); callbacks.GarbageCollectionFinish = GarbageCollectionFinish; callbacks.GarbageCollectionStart = GarbageCollectionStart; jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); JvmtiErrorToException(env, jvmti_env, ret); } extern "C" JNIEXPORT void JNICALL Java_art_Test913_enableGcTracking(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jboolean enable) { jvmtiError ret = jvmti_env->SetEventNotificationMode( enable ? JVMTI_ENABLE : JVMTI_DISABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, nullptr); if (JvmtiErrorToException(env, jvmti_env, ret)) { return; } ret = jvmti_env->SetEventNotificationMode( enable ? JVMTI_ENABLE : JVMTI_DISABLE, JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, nullptr); if (JvmtiErrorToException(env, jvmti_env, ret)) { return; } } extern "C" JNIEXPORT jint JNICALL Java_art_Test913_getGcStarts(JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED) { jint result = static_cast(starts); starts = 0; return result; } extern "C" JNIEXPORT jint JNICALL Java_art_Test913_getGcFinishes(JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED) { jint result = static_cast(finishes); finishes = 0; return result; } using GetObjectHeapId = jvmtiError(*)(jvmtiEnv*, jlong, jint*, ...); static GetObjectHeapId gGetObjectHeapIdFn = nullptr; using GetHeapName = jvmtiError(*)(jvmtiEnv*, jint, char**, ...); static GetHeapName gGetHeapNameFn = nullptr; using IterateThroughHeapExt = jvmtiError(*)(jvmtiEnv*, jint, jclass, const jvmtiHeapCallbacks*, const void*); static IterateThroughHeapExt gIterateThroughHeapExt = nullptr; static void FreeExtensionFunctionInfo(jvmtiExtensionFunctionInfo* extensions, jint count) { for (size_t i = 0; i != static_cast(count); ++i) { jvmti_env->Deallocate(reinterpret_cast(extensions[i].id)); jvmti_env->Deallocate(reinterpret_cast(extensions[i].short_description)); for (size_t j = 0; j != static_cast(extensions[i].param_count); ++j) { jvmti_env->Deallocate(reinterpret_cast(extensions[i].params[j].name)); } jvmti_env->Deallocate(reinterpret_cast(extensions[i].params)); jvmti_env->Deallocate(reinterpret_cast(extensions[i].errors)); } } extern "C" JNIEXPORT void JNICALL Java_art_Test913_checkForExtensionApis( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { jint extension_count; jvmtiExtensionFunctionInfo* extensions; jvmtiError result = jvmti_env->GetExtensionFunctions(&extension_count, &extensions); if (JvmtiErrorToException(env, jvmti_env, result)) { return; } for (size_t i = 0; i != static_cast(extension_count); ++i) { if (strcmp("com.android.art.heap.get_object_heap_id", extensions[i].id) == 0) { CHECK(gGetObjectHeapIdFn == nullptr); gGetObjectHeapIdFn = reinterpret_cast(extensions[i].func); CHECK_EQ(extensions[i].param_count, 2); CHECK_EQ(strcmp("tag", extensions[i].params[0].name), 0); CHECK_EQ(extensions[i].params[0].base_type, JVMTI_TYPE_JLONG); CHECK_EQ(extensions[i].params[0].kind, JVMTI_KIND_IN); CHECK_EQ(strcmp("heap_id", extensions[i].params[1].name), 0); CHECK_EQ(extensions[i].params[1].base_type, JVMTI_TYPE_JINT); CHECK_EQ(extensions[i].params[1].kind, JVMTI_KIND_OUT); CHECK_EQ(extensions[i].params[1].null_ok, false); CHECK_EQ(extensions[i].error_count, 1); CHECK(extensions[i].errors != nullptr); CHECK(extensions[i].errors[0] == JVMTI_ERROR_NOT_FOUND); continue; } if (strcmp("com.android.art.heap.get_heap_name", extensions[i].id) == 0) { CHECK(gGetHeapNameFn == nullptr); gGetHeapNameFn = reinterpret_cast(extensions[i].func); CHECK_EQ(extensions[i].param_count, 2); CHECK_EQ(strcmp("heap_id", extensions[i].params[0].name), 0); CHECK_EQ(extensions[i].params[0].base_type, JVMTI_TYPE_JINT); CHECK_EQ(extensions[i].params[0].kind, JVMTI_KIND_IN); CHECK_EQ(strcmp("heap_name", extensions[i].params[1].name), 0); CHECK_EQ(extensions[i].params[1].base_type, JVMTI_TYPE_CCHAR); CHECK_EQ(extensions[i].params[1].kind, JVMTI_KIND_ALLOC_BUF); CHECK_EQ(extensions[i].params[1].null_ok, false); CHECK_EQ(extensions[i].error_count, 1); CHECK(extensions[i].errors != nullptr); CHECK(extensions[i].errors[0] == JVMTI_ERROR_ILLEGAL_ARGUMENT); } if (strcmp("com.android.art.heap.iterate_through_heap_ext", extensions[i].id) == 0) { CHECK(gIterateThroughHeapExt == nullptr); gIterateThroughHeapExt = reinterpret_cast(extensions[i].func); CHECK_EQ(extensions[i].param_count, 4); CHECK_EQ(strcmp("heap_filter", extensions[i].params[0].name), 0); CHECK_EQ(extensions[i].params[0].base_type, JVMTI_TYPE_JINT); CHECK_EQ(extensions[i].params[0].kind, JVMTI_KIND_IN); CHECK_EQ(strcmp("klass", extensions[i].params[1].name), 0); CHECK_EQ(extensions[i].params[1].base_type, JVMTI_TYPE_JCLASS); CHECK_EQ(extensions[i].params[1].kind, JVMTI_KIND_IN); CHECK_EQ(extensions[i].params[1].null_ok, true); CHECK_EQ(strcmp("callbacks", extensions[i].params[2].name), 0); CHECK_EQ(extensions[i].params[2].base_type, JVMTI_TYPE_CVOID); CHECK_EQ(extensions[i].params[2].kind, JVMTI_KIND_IN_PTR); CHECK_EQ(extensions[i].params[2].null_ok, false); CHECK_EQ(strcmp("user_data", extensions[i].params[3].name), 0); CHECK_EQ(extensions[i].params[3].base_type, JVMTI_TYPE_CVOID); CHECK_EQ(extensions[i].params[3].kind, JVMTI_KIND_IN_PTR); CHECK_EQ(extensions[i].params[3].null_ok, true); CHECK_EQ(extensions[i].error_count, 3); CHECK(extensions[i].errors != nullptr); CHECK(extensions[i].errors[0] == JVMTI_ERROR_MUST_POSSESS_CAPABILITY); CHECK(extensions[i].errors[1] == JVMTI_ERROR_INVALID_CLASS); CHECK(extensions[i].errors[2] == JVMTI_ERROR_NULL_POINTER); } } CHECK(gGetObjectHeapIdFn != nullptr); CHECK(gGetHeapNameFn != nullptr); FreeExtensionFunctionInfo(extensions, extension_count); } extern "C" JNIEXPORT jint JNICALL Java_art_Test913_getObjectHeapId( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag) { CHECK(gGetObjectHeapIdFn != nullptr); jint heap_id; jvmtiError result = gGetObjectHeapIdFn(jvmti_env, tag, &heap_id); JvmtiErrorToException(env, jvmti_env, result); return heap_id; } extern "C" JNIEXPORT jstring JNICALL Java_art_Test913_getHeapName( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jint heap_id) { CHECK(gGetHeapNameFn != nullptr); char* heap_name; jvmtiError result = gGetHeapNameFn(jvmti_env, heap_id, &heap_name); if (JvmtiErrorToException(env, jvmti_env, result)) { return nullptr; } jstring ret = env->NewStringUTF(heap_name); jvmti_env->Deallocate(reinterpret_cast(heap_name)); return ret; } extern "C" JNIEXPORT void JNICALL Java_art_Test913_checkGetObjectHeapIdInCallback( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag, jint heap_id) { CHECK(gGetObjectHeapIdFn != nullptr); { struct GetObjectHeapIdCallbacks { static jint JNICALL FollowReferencesCallback( jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED, const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED, jlong class_tag ATTRIBUTE_UNUSED, jlong referrer_class_tag ATTRIBUTE_UNUSED, jlong size ATTRIBUTE_UNUSED, jlong* tag_ptr, jlong* referrer_tag_ptr ATTRIBUTE_UNUSED, jint length ATTRIBUTE_UNUSED, void* user_data) { if (*tag_ptr != 0) { GetObjectHeapIdCallbacks* p = reinterpret_cast(user_data); if (*tag_ptr == p->check_callback_tag) { jint tag_heap_id; jvmtiError result = gGetObjectHeapIdFn(jvmti_env, *tag_ptr, &tag_heap_id); CHECK_EQ(result, JVMTI_ERROR_NONE); CHECK_EQ(tag_heap_id, p->check_callback_id); return JVMTI_VISIT_ABORT; } } return JVMTI_VISIT_OBJECTS; // Continue visiting. } jlong check_callback_tag; jint check_callback_id; }; jvmtiHeapCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); callbacks.heap_reference_callback = GetObjectHeapIdCallbacks::FollowReferencesCallback; GetObjectHeapIdCallbacks ffc; ffc.check_callback_tag = tag; ffc.check_callback_id = heap_id; jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, nullptr, &callbacks, &ffc); if (JvmtiErrorToException(env, jvmti_env, ret)) { return; } } { struct GetObjectHeapIdCallbacks { static jint JNICALL HeapIterationCallback(jlong class_tag ATTRIBUTE_UNUSED, jlong size ATTRIBUTE_UNUSED, jlong* tag_ptr, jint length ATTRIBUTE_UNUSED, void* user_data) { if (*tag_ptr != 0) { GetObjectHeapIdCallbacks* p = reinterpret_cast(user_data); if (*tag_ptr == p->check_callback_tag) { jint tag_heap_id; jvmtiError result = gGetObjectHeapIdFn(jvmti_env, *tag_ptr, &tag_heap_id); CHECK_EQ(result, JVMTI_ERROR_NONE); CHECK_EQ(tag_heap_id, p->check_callback_id); return JVMTI_VISIT_ABORT; } } return 0; // Continue visiting. } jlong check_callback_tag; jint check_callback_id; }; jvmtiHeapCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); callbacks.heap_iteration_callback = GetObjectHeapIdCallbacks::HeapIterationCallback; GetObjectHeapIdCallbacks ffc; ffc.check_callback_tag = tag; ffc.check_callback_id = heap_id; jvmtiError ret = jvmti_env->IterateThroughHeap(0, nullptr, &callbacks, &ffc); if (JvmtiErrorToException(env, jvmti_env, ret)) { return; } } } static bool gFoundExt = false; static jint JNICALL HeapIterationExtCallback(jlong class_tag ATTRIBUTE_UNUSED, jlong size ATTRIBUTE_UNUSED, jlong* tag_ptr, jint length ATTRIBUTE_UNUSED, void* user_data ATTRIBUTE_UNUSED, jint heap_id) { // We expect some tagged objects at or above the threshold, where the expected heap id is // encoded into lowest byte. constexpr jlong kThreshold = 30000000; jlong tag = *tag_ptr; if (tag >= kThreshold) { jint expected_heap_id = static_cast(tag - kThreshold); CHECK_EQ(expected_heap_id, heap_id); gFoundExt = true; } return 0; } extern "C" JNIEXPORT void JNICALL Java_art_Test913_iterateThroughHeapExt( JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { CHECK(gIterateThroughHeapExt != nullptr); jvmtiHeapCallbacks callbacks; memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks)); callbacks.heap_iteration_callback = reinterpret_cast(HeapIterationExtCallback); jvmtiError ret = gIterateThroughHeapExt(jvmti_env, 0, nullptr, &callbacks, nullptr); JvmtiErrorToException(env, jvmti_env, ret); CHECK(gFoundExt); } } // namespace Test913Heaps } // namespace art