/*
* 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, [[maybe_unused]] jclass klass) {
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, [[maybe_unused]] jclass klass, 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,
[[maybe_unused]] jclass klass,
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,
[[maybe_unused]] void * user_data) 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 ;
}
// Ignore system classes, which may come from the JIT compiling a method
// in these classes.
if (reference_kind == JVMTI_HEAP_REFERENCE_SYSTEM_CLASS) {
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 = 0 xDEADDEAD;
}
}
}
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;
ret.reserve(lines_.size());
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, [[maybe_unused]] jclass klass , jobject initial_object) {
struct FindStringCallbacks {
static jint JNICALL FollowReferencesCallback(
[[maybe_unused]] jvmtiHeapReferenceKind reference_kind,
[[maybe_unused]] const jvmtiHeapReferenceInfo* reference_info,
[[maybe_unused]] jlong class_tag,
[[maybe_unused]] jlong referrer_class_tag,
[[maybe_unused]] jlong size,
[[maybe_unused]] jlong* tag_ptr,
[[maybe_unused]] jlong* referrer_tag_ptr,
[[maybe_unused]] jint length,
[[maybe_unused]] void * user_data) {
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::CountModifiedUtf8BytesInUtf16(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, [[maybe_unused]] jclass klass , jobject initial_object) {
struct FindArrayCallbacks {
static jint JNICALL FollowReferencesCallback(
[[maybe_unused]] jvmtiHeapReferenceKind reference_kind,
[[maybe_unused]] const jvmtiHeapReferenceInfo* reference_info,
[[maybe_unused]] jlong class_tag,
[[maybe_unused]] jlong referrer_class_tag,
[[maybe_unused]] jlong size,
[[maybe_unused]] jlong* tag_ptr,
[[maybe_unused]] jlong* referrer_tag_ptr,
[[maybe_unused]] jint length,
[[maybe_unused]] void * user_data) {
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 ;
}
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, [[maybe_unused]] jclass klass , jobject initial_object) {
struct FindFieldCallbacks {
static jint JNICALL FollowReferencesCallback(
[[maybe_unused]] jvmtiHeapReferenceKind reference_kind,
[[maybe_unused]] const jvmtiHeapReferenceInfo* reference_info,
[[maybe_unused]] jlong class_tag,
[[maybe_unused]] jlong referrer_class_tag,
[[maybe_unused]] jlong size,
[[maybe_unused]] jlong* tag_ptr,
[[maybe_unused]] jlong* referrer_tag_ptr,
[[maybe_unused]] jint length,
[[maybe_unused]] void * user_data) {
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([[maybe_unused]] jvmtiEnv* ti_env) {
finishes++;
}
static void JNICALL GarbageCollectionStart([[maybe_unused]] jvmtiEnv* ti_env) {
starts++;
}
extern "C" JNIEXPORT void JNICALL Java_art_Test913_setupGcCallback(
JNIEnv* env, [[maybe_unused]] jclass klass) {
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,
[[maybe_unused]] jclass klass,
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([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass klass) {
jint result = static_cast <jint>(starts);
starts = 0 ;
return result;
}
extern "C" JNIEXPORT jint JNICALL Java_art_Test913_getGcFinishes([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jclass klass) {
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, [[maybe_unused]] jclass klass) {
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, [[maybe_unused]] jclass klass , 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, [[maybe_unused]] jclass klass , 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, [[maybe_unused]] jclass klass , jlong tag, jint heap_id) {
CHECK(gGetObjectHeapIdFn != nullptr);
{
struct GetObjectHeapIdCallbacks {
static jint JNICALL FollowReferencesCallback(
[[maybe_unused]] jvmtiHeapReferenceKind reference_kind,
[[maybe_unused]] const jvmtiHeapReferenceInfo* reference_info,
[[maybe_unused]] jlong class_tag,
[[maybe_unused]] jlong referrer_class_tag,
[[maybe_unused]] jlong size,
jlong* tag_ptr,
[[maybe_unused]] jlong* referrer_tag_ptr,
[[maybe_unused]] jint length,
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([[maybe_unused]] jlong class_tag,
[[maybe_unused]] jlong size,
jlong* tag_ptr,
[[maybe_unused]] jint length,
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([[maybe_unused]] jlong class_tag,
[[maybe_unused]] jlong size,
jlong* tag_ptr,
[[maybe_unused]] jint length,
[[maybe_unused]] void * user_data,
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 ;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-function-type-mismatch"
extern "C" JNIEXPORT void JNICALL Java_art_Test913_iterateThroughHeapExt(
JNIEnv* env, [[maybe_unused]] jclass klass) {
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);
}
#pragma clang diagnostic pop
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
Messung V0.5 in Prozent C=88 H=98 G=93
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-06-29)
¤
*© Formatika GbR, Deutschland