1143 lines
45 KiB
C++
1143 lines
45 KiB
C++
|
/*
|
||
|
* 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 <inttypes.h>
|
||
|
|
||
|
#include <cstdio>
|
||
|
#include <cstring>
|
||
|
#include <iostream>
|
||
|
#include <map>
|
||
|
#include <sstream>
|
||
|
#include <vector>
|
||
|
|
||
|
#include "android-base/logging.h"
|
||
|
#include "android-base/macros.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 UNREACHABLE __builtin_unreachable
|
||
|
|
||
|
// The tag value used on the Java side to tag the current thread.
|
||
|
static constexpr jlong kThreadTag = 3000;
|
||
|
static constexpr const char* kThreadReferree = "3000@0";
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
// Collect sizes of objects (classes) ahead of time, to be able to normalize.
|
||
|
struct ClassData {
|
||
|
jlong size; // Size as reported by GetObjectSize.
|
||
|
jlong serial; // Computed serial that should be printed instead of the size.
|
||
|
};
|
||
|
|
||
|
// Stores a map from tags to ClassData.
|
||
|
static std::map<jlong, ClassData> sClassData;
|
||
|
static size_t sClassDataSerial = 0;
|
||
|
// Large enough number that a collision with a test object is unlikely.
|
||
|
static constexpr jlong kClassDataSerialBase = 123456780000;
|
||
|
|
||
|
// Register a class (or general object) in the class-data map. The serial number is determined by
|
||
|
// the order of calls to this function (so stable Java code leads to stable numbering).
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_Test913_registerClass(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag, jobject obj) {
|
||
|
ClassData data;
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetObjectSize(obj, &data.size))) {
|
||
|
return;
|
||
|
}
|
||
|
data.serial = kClassDataSerialBase + sClassDataSerial++;
|
||
|
// Remove old element, if it exists.
|
||
|
auto old = sClassData.find(tag);
|
||
|
if (old != sClassData.end()) {
|
||
|
sClassData.erase(old);
|
||
|
}
|
||
|
// Now insert the new mapping.
|
||
|
sClassData.insert(std::pair<jlong, ClassData>(tag, data));
|
||
|
}
|
||
|
|
||
|
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<IterationConfig*>(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 HEAP_REFERENCE_OTHER roots because these are vm-internal roots and can vary
|
||
|
// depending on the configuration of the runtime (notably having trampoline tracing will add a
|
||
|
// lot of these).
|
||
|
if (reference_kind == JVMTI_HEAP_REFERENCE_OTHER) {
|
||
|
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 < kThreadTag)) {
|
||
|
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 != kThreadTag) {
|
||
|
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<int32_t>(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 != 0) {
|
||
|
// 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.
|
||
|
auto it = sClassData.find(*tag_ptr);
|
||
|
if (it != sClassData.end()) {
|
||
|
const ClassData& class_data = it->second;
|
||
|
if (class_data.size == size) {
|
||
|
adapted_size = class_data.serial;
|
||
|
} else {
|
||
|
adapted_size = 0xDEADDEAD;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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<std::string> GetLines() const {
|
||
|
std::vector<std::string> ret;
|
||
|
for (const std::unique_ptr<Elem>& 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 ? "<null>" : name);
|
||
|
if (name != nullptr) {
|
||
|
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(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 ? "<null>" : name,
|
||
|
info_.stack_local.slot,
|
||
|
info_.stack_local.location);
|
||
|
if (name != nullptr) {
|
||
|
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(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<Elem> 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<Elem>(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<Elem>(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 == kThreadReferree) {
|
||
|
index = 0;
|
||
|
}
|
||
|
std::string tmp = StringPrintf("array-element@%d", index);
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
tmp));
|
||
|
}
|
||
|
case JVMTI_HEAP_REFERENCE_CLASS_LOADER:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"classloader"));
|
||
|
case JVMTI_HEAP_REFERENCE_SIGNERS:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"signers"));
|
||
|
case JVMTI_HEAP_REFERENCE_PROTECTION_DOMAIN:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"protection-domain"));
|
||
|
case JVMTI_HEAP_REFERENCE_INTERFACE:
|
||
|
return std::unique_ptr<Elem>(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<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
tmp));;
|
||
|
}
|
||
|
case JVMTI_HEAP_REFERENCE_CONSTANT_POOL:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"constant-pool"));
|
||
|
case JVMTI_HEAP_REFERENCE_SUPERCLASS:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"superclass"));
|
||
|
case JVMTI_HEAP_REFERENCE_JNI_GLOBAL:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"jni-global"));
|
||
|
case JVMTI_HEAP_REFERENCE_SYSTEM_CLASS:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"system-class"));
|
||
|
case JVMTI_HEAP_REFERENCE_MONITOR:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"monitor"));
|
||
|
case JVMTI_HEAP_REFERENCE_STACK_LOCAL:
|
||
|
return std::unique_ptr<Elem>(new StackLocalElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
reference_info));
|
||
|
case JVMTI_HEAP_REFERENCE_JNI_LOCAL:
|
||
|
return std::unique_ptr<Elem>(new JNILocalElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
reference_info));
|
||
|
case JVMTI_HEAP_REFERENCE_THREAD:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"thread"));
|
||
|
case JVMTI_HEAP_REFERENCE_OTHER:
|
||
|
return std::unique_ptr<Elem>(new StringElement(referrer,
|
||
|
referree,
|
||
|
size,
|
||
|
length,
|
||
|
"other"));
|
||
|
}
|
||
|
LOG(FATAL) << "Unknown kind";
|
||
|
UNREACHABLE();
|
||
|
}
|
||
|
|
||
|
jint counter_;
|
||
|
const jint stop_after_;
|
||
|
const jint follow_set_;
|
||
|
|
||
|
std::vector<std::unique_ptr<Elem>> lines_;
|
||
|
};
|
||
|
|
||
|
// If jniRef isn't null, add a local and a global ref.
|
||
|
ScopedLocalRef<jobject> 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<std::string> lines = config.GetLines();
|
||
|
jobjectArray ret = CreateObjectArray(env,
|
||
|
static_cast<jint>(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<FindStringCallbacks*>(user_data);
|
||
|
if (*tag_ptr != 0) {
|
||
|
size_t utf_byte_count = ti::CountUtf8Bytes(value, value_length);
|
||
|
std::unique_ptr<char[]> 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<std::string> 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<jint>(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<FindArrayCallbacks*>(user_data);
|
||
|
// The thread object may be reachable from the starting value because of setup in the
|
||
|
// framework (when this test runs as part of CTS). Ignore, we're not testing the thread
|
||
|
// here.)
|
||
|
if (*tag_ptr != 0 && *tag_ptr != kThreadTag) {
|
||
|
std::ostringstream oss;
|
||
|
oss << *tag_ptr
|
||
|
<< '@'
|
||
|
<< class_tag
|
||
|
<< " ("
|
||
|
<< size
|
||
|
<< ", "
|
||
|
<< element_count
|
||
|
<< "x"
|
||
|
<< static_cast<char>(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<size_t>(element_type);
|
||
|
UNREACHABLE();
|
||
|
}
|
||
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(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<size_t>(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<FindFieldCallbacks*>(user_data);
|
||
|
// The thread object may be reachable from the starting value because of setup in the
|
||
|
// framework (when this test runs as part of CTS). Ignore, we're not testing the thread
|
||
|
// here.)
|
||
|
if (*tag_ptr != 0 && *tag_ptr != kThreadTag) {
|
||
|
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<jint>(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<jint>(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<size_t>(count); ++i) {
|
||
|
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(extensions[i].id));
|
||
|
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(extensions[i].short_description));
|
||
|
for (size_t j = 0; j != static_cast<size_t>(extensions[i].param_count); ++j) {
|
||
|
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(extensions[i].params[j].name));
|
||
|
}
|
||
|
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(extensions[i].params));
|
||
|
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(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<size_t>(extension_count); ++i) {
|
||
|
if (strcmp("com.android.art.heap.get_object_heap_id", extensions[i].id) == 0) {
|
||
|
CHECK(gGetObjectHeapIdFn == nullptr);
|
||
|
gGetObjectHeapIdFn = reinterpret_cast<GetObjectHeapId>(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<GetHeapName>(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<IterateThroughHeapExt>(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<unsigned char*>(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<GetObjectHeapIdCallbacks*>(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<GetObjectHeapIdCallbacks*>(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<jint>(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<decltype(callbacks.heap_iteration_callback)>(HeapIterationExtCallback);
|
||
|
|
||
|
jvmtiError ret = gIterateThroughHeapExt(jvmti_env, 0, nullptr, &callbacks, nullptr);
|
||
|
JvmtiErrorToException(env, jvmti_env, ret);
|
||
|
CHECK(gFoundExt);
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT jboolean JNICALL Java_art_Test913_checkInitialized(JNIEnv* env, jclass, jclass c) {
|
||
|
jint status;
|
||
|
jvmtiError error = jvmti_env->GetClassStatus(c, &status);
|
||
|
if (JvmtiErrorToException(env, jvmti_env, error)) {
|
||
|
return false;
|
||
|
}
|
||
|
return (status & JVMTI_CLASS_STATUS_INITIALIZED) != 0;
|
||
|
}
|
||
|
|
||
|
} // namespace Test913Heaps
|
||
|
} // namespace art
|