| /* |
| * 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 <stdio.h> |
| |
| #include <mutex> |
| #include <vector> |
| |
| #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 "scoped_local_ref.h" |
| #include "scoped_utf_chars.h" |
| #include "test_env.h" |
| |
| namespace art { |
| namespace Test912Classes { |
| |
| extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912_isModifiableClass( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jboolean res = JNI_FALSE; |
| jvmtiError result = jvmti_env->IsModifiableClass(klass, &res); |
| JvmtiErrorToException(env, jvmti_env, result); |
| return res; |
| } |
| |
| extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassSignature( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| char* sig; |
| char* gen; |
| jvmtiError result = jvmti_env->GetClassSignature(klass, &sig, &gen); |
| if (JvmtiErrorToException(env, jvmti_env, result)) { |
| return nullptr; |
| } |
| |
| auto callback = [&](jint i) { |
| if (i == 0) { |
| return sig == nullptr ? nullptr : env->NewStringUTF(sig); |
| } else { |
| return gen == nullptr ? nullptr : env->NewStringUTF(gen); |
| } |
| }; |
| jobjectArray ret = CreateObjectArray(env, 2, "java/lang/String", callback); |
| |
| // Need to deallocate the strings. |
| if (sig != nullptr) { |
| jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(sig)); |
| } |
| if (gen != nullptr) { |
| jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(gen)); |
| } |
| |
| return ret; |
| } |
| |
| extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912_isInterface( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jboolean is_interface = JNI_FALSE; |
| jvmtiError result = jvmti_env->IsInterface(klass, &is_interface); |
| JvmtiErrorToException(env, jvmti_env, result); |
| return is_interface; |
| } |
| |
| extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912_isArrayClass( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jboolean is_array_class = JNI_FALSE; |
| jvmtiError result = jvmti_env->IsArrayClass(klass, &is_array_class); |
| JvmtiErrorToException(env, jvmti_env, result); |
| return is_array_class; |
| } |
| |
| extern "C" JNIEXPORT jint JNICALL Java_art_Test912_getClassModifiers( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jint mod; |
| jvmtiError result = jvmti_env->GetClassModifiers(klass, &mod); |
| JvmtiErrorToException(env, jvmti_env, result); |
| return mod; |
| } |
| |
| extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassFields( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jint count = 0; |
| jfieldID* fields = nullptr; |
| jvmtiError result = jvmti_env->GetClassFields(klass, &count, &fields); |
| if (JvmtiErrorToException(env, jvmti_env, result)) { |
| return nullptr; |
| } |
| |
| auto callback = [&](jint i) { |
| jint modifiers; |
| // Ignore any errors for simplicity. |
| jvmti_env->GetFieldModifiers(klass, fields[i], &modifiers); |
| constexpr jint kStatic = 0x8; |
| return env->ToReflectedField(klass, |
| fields[i], |
| (modifiers & kStatic) != 0 ? JNI_TRUE : JNI_FALSE); |
| }; |
| jobjectArray ret = CreateObjectArray(env, count, "java/lang/Object", callback); |
| if (fields != nullptr) { |
| jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); |
| } |
| return ret; |
| } |
| |
| extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassMethods( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jint count = 0; |
| jmethodID* methods = nullptr; |
| jvmtiError result = jvmti_env->GetClassMethods(klass, &count, &methods); |
| if (JvmtiErrorToException(env, jvmti_env, result)) { |
| return nullptr; |
| } |
| |
| auto callback = [&](jint i) { |
| jint modifiers; |
| // Ignore any errors for simplicity. |
| jvmti_env->GetMethodModifiers(methods[i], &modifiers); |
| constexpr jint kStatic = 0x8; |
| return env->ToReflectedMethod(klass, |
| methods[i], |
| (modifiers & kStatic) != 0 ? JNI_TRUE : JNI_FALSE); |
| }; |
| jobjectArray ret = CreateObjectArray(env, count, "java/lang/Object", callback); |
| if (methods != nullptr) { |
| jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(methods)); |
| } |
| return ret; |
| } |
| |
| extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getImplementedInterfaces( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jint count = 0; |
| jclass* classes = nullptr; |
| jvmtiError result = jvmti_env->GetImplementedInterfaces(klass, &count, &classes); |
| if (JvmtiErrorToException(env, jvmti_env, result)) { |
| return nullptr; |
| } |
| |
| auto callback = [&](jint i) { |
| return classes[i]; |
| }; |
| jobjectArray ret = CreateObjectArray(env, count, "java/lang/Class", callback); |
| if (classes != nullptr) { |
| jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(classes)); |
| } |
| return ret; |
| } |
| |
| extern "C" JNIEXPORT jint JNICALL Java_art_Test912_getClassStatus( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jint status; |
| jvmtiError result = jvmti_env->GetClassStatus(klass, &status); |
| JvmtiErrorToException(env, jvmti_env, result); |
| return status; |
| } |
| |
| extern "C" JNIEXPORT jobject JNICALL Java_art_Test912_getClassLoader( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jobject classloader; |
| jvmtiError result = jvmti_env->GetClassLoader(klass, &classloader); |
| JvmtiErrorToException(env, jvmti_env, result); |
| return classloader; |
| } |
| |
| extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassLoaderClasses( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jobject jclassloader) { |
| jint count = 0; |
| jclass* classes = nullptr; |
| jvmtiError result = jvmti_env->GetClassLoaderClasses(jclassloader, &count, &classes); |
| if (JvmtiErrorToException(env, jvmti_env, result)) { |
| return nullptr; |
| } |
| |
| auto callback = [&](jint i) { |
| return classes[i]; |
| }; |
| jobjectArray ret = CreateObjectArray(env, count, "java/lang/Class", callback); |
| if (classes != nullptr) { |
| jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(classes)); |
| } |
| return ret; |
| } |
| |
| extern "C" JNIEXPORT jintArray JNICALL Java_art_Test912_getClassVersion( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| jint major, minor; |
| jvmtiError result = jvmti_env->GetClassVersionNumbers(klass, &minor, &major); |
| if (JvmtiErrorToException(env, jvmti_env, result)) { |
| return nullptr; |
| } |
| |
| jintArray int_array = env->NewIntArray(2); |
| if (int_array == nullptr) { |
| return nullptr; |
| } |
| jint buf[2] = { major, minor }; |
| env->SetIntArrayRegion(int_array, 0, 2, buf); |
| |
| return int_array; |
| } |
| |
| static std::string GetClassName(jvmtiEnv* jenv, JNIEnv* jni_env, jclass klass) { |
| char* name; |
| jvmtiError result = jenv->GetClassSignature(klass, &name, nullptr); |
| if (result != JVMTI_ERROR_NONE) { |
| if (jni_env != nullptr) { |
| JvmtiErrorToException(jni_env, jenv, result); |
| } else { |
| printf("Failed to get class signature.\n"); |
| } |
| return ""; |
| } |
| |
| std::string tmp(name); |
| jenv->Deallocate(reinterpret_cast<unsigned char*>(name)); |
| |
| return tmp; |
| } |
| |
| static void EnableEvents(JNIEnv* env, |
| jboolean enable, |
| decltype(jvmtiEventCallbacks().ClassLoad) class_load, |
| decltype(jvmtiEventCallbacks().ClassPrepare) class_prepare) { |
| if (enable == JNI_FALSE) { |
| jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, |
| JVMTI_EVENT_CLASS_LOAD, |
| nullptr); |
| if (JvmtiErrorToException(env, jvmti_env, ret)) { |
| return; |
| } |
| ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, |
| JVMTI_EVENT_CLASS_PREPARE, |
| nullptr); |
| JvmtiErrorToException(env, jvmti_env, ret); |
| return; |
| } |
| |
| jvmtiEventCallbacks callbacks; |
| memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); |
| callbacks.ClassLoad = class_load; |
| callbacks.ClassPrepare = class_prepare; |
| jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); |
| if (JvmtiErrorToException(env, jvmti_env, ret)) { |
| return; |
| } |
| |
| ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, |
| JVMTI_EVENT_CLASS_LOAD, |
| nullptr); |
| if (JvmtiErrorToException(env, jvmti_env, ret)) { |
| return; |
| } |
| ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, |
| JVMTI_EVENT_CLASS_PREPARE, |
| nullptr); |
| JvmtiErrorToException(env, jvmti_env, ret); |
| } |
| |
| static std::mutex gEventsMutex; |
| static std::vector<std::string> gEvents; |
| |
| extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassLoadMessages( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) { |
| std::lock_guard<std::mutex> guard(gEventsMutex); |
| jobjectArray ret = CreateObjectArray(env, |
| static_cast<jint>(gEvents.size()), |
| "java/lang/String", |
| [&](jint i) { |
| return env->NewStringUTF(gEvents[i].c_str()); |
| }); |
| gEvents.clear(); |
| return ret; |
| } |
| |
| class ClassLoadPreparePrinter { |
| public: |
| static void JNICALL ClassLoadCallback(jvmtiEnv* jenv, |
| JNIEnv* jni_env, |
| jthread thread, |
| jclass klass) { |
| std::string name = GetClassName(jenv, jni_env, klass); |
| if (name == "") { |
| return; |
| } |
| std::string thread_name = GetThreadName(jenv, jni_env, thread); |
| if (thread_name == "") { |
| return; |
| } |
| if (thread_name_filter_ != "" && thread_name_filter_ != thread_name) { |
| return; |
| } |
| |
| std::lock_guard<std::mutex> guard(gEventsMutex); |
| gEvents.push_back(android::base::StringPrintf("Load: %s on %s", |
| name.c_str(), |
| thread_name.c_str())); |
| } |
| |
| static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv, |
| JNIEnv* jni_env, |
| jthread thread, |
| jclass klass) { |
| std::string name = GetClassName(jenv, jni_env, klass); |
| if (name == "") { |
| return; |
| } |
| std::string thread_name = GetThreadName(jenv, jni_env, thread); |
| if (thread_name == "") { |
| return; |
| } |
| if (thread_name_filter_ != "" && thread_name_filter_ != thread_name) { |
| return; |
| } |
| std::string cur_thread_name = GetThreadName(jenv, jni_env, nullptr); |
| |
| std::lock_guard<std::mutex> guard(gEventsMutex); |
| gEvents.push_back(android::base::StringPrintf("Prepare: %s on %s (cur=%s)", |
| name.c_str(), |
| thread_name.c_str(), |
| cur_thread_name.c_str())); |
| } |
| |
| static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) { |
| jvmtiThreadInfo info; |
| jvmtiError result = jenv->GetThreadInfo(thread, &info); |
| if (result != JVMTI_ERROR_NONE) { |
| if (jni_env != nullptr) { |
| JvmtiErrorToException(jni_env, jenv, result); |
| } else { |
| printf("Failed to get thread name.\n"); |
| } |
| return ""; |
| } |
| |
| std::string tmp(info.name); |
| jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name)); |
| jni_env->DeleteLocalRef(info.context_class_loader); |
| jni_env->DeleteLocalRef(info.thread_group); |
| |
| return tmp; |
| } |
| |
| static std::string thread_name_filter_; |
| }; |
| std::string ClassLoadPreparePrinter::thread_name_filter_; |
| |
| extern "C" JNIEXPORT void JNICALL Java_art_Test912_enableClassLoadPreparePrintEvents( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean enable, jthread thread) { |
| if (thread != nullptr) { |
| ClassLoadPreparePrinter::thread_name_filter_ = |
| ClassLoadPreparePrinter::GetThreadName(jvmti_env, env, thread); |
| } else { |
| ClassLoadPreparePrinter::thread_name_filter_ = ""; |
| } |
| |
| EnableEvents(env, |
| enable, |
| ClassLoadPreparePrinter::ClassLoadCallback, |
| ClassLoadPreparePrinter::ClassPrepareCallback); |
| } |
| |
| class ClassLoadPrepareEquality { |
| public: |
| static constexpr const char* kClassName = "Lart/Test912$ClassE;"; |
| static constexpr const char* kStorageFieldName = "STATIC"; |
| static constexpr const char* kStorageFieldSig = "Ljava/lang/Object;"; |
| static constexpr const char* kStorageWeakFieldName = "WEAK"; |
| static constexpr const char* kStorageWeakFieldSig = "Ljava/lang/ref/Reference;"; |
| static constexpr const char* kWeakClassName = "java/lang/ref/WeakReference"; |
| static constexpr const char* kWeakInitSig = "(Ljava/lang/Object;)V"; |
| static constexpr const char* kWeakGetSig = "()Ljava/lang/Object;"; |
| |
| static void JNICALL ClassLoadCallback(jvmtiEnv* jenv, |
| JNIEnv* jni_env, |
| jthread thread ATTRIBUTE_UNUSED, |
| jclass klass) { |
| std::string name = GetClassName(jenv, jni_env, klass); |
| if (name == kClassName) { |
| found_ = true; |
| stored_class_ = jni_env->NewGlobalRef(klass); |
| weakly_stored_class_ = jni_env->NewWeakGlobalRef(klass); |
| // The following is bad and relies on implementation details. But otherwise a test would be |
| // a lot more complicated. |
| local_stored_class_ = jni_env->NewLocalRef(klass); |
| // Store the value into a field in the heap. |
| SetOrCompare(jni_env, klass, true); |
| } |
| } |
| |
| static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv, |
| JNIEnv* jni_env, |
| jthread thread ATTRIBUTE_UNUSED, |
| jclass klass) { |
| std::string name = GetClassName(jenv, jni_env, klass); |
| if (name == kClassName) { |
| CHECK(stored_class_ != nullptr); |
| CHECK(jni_env->IsSameObject(stored_class_, klass)); |
| CHECK(jni_env->IsSameObject(weakly_stored_class_, klass)); |
| CHECK(jni_env->IsSameObject(local_stored_class_, klass)); |
| // Look up the value in a field in the heap. |
| SetOrCompare(jni_env, klass, false); |
| compared_ = true; |
| } |
| } |
| |
| static void SetOrCompare(JNIEnv* jni_env, jobject value, bool set) { |
| CHECK(storage_class_ != nullptr); |
| |
| // Simple direct storage. |
| jfieldID field = jni_env->GetStaticFieldID(storage_class_, kStorageFieldName, kStorageFieldSig); |
| CHECK(field != nullptr); |
| |
| if (set) { |
| jni_env->SetStaticObjectField(storage_class_, field, value); |
| CHECK(!jni_env->ExceptionCheck()); |
| } else { |
| ScopedLocalRef<jobject> stored(jni_env, jni_env->GetStaticObjectField(storage_class_, field)); |
| CHECK(jni_env->IsSameObject(value, stored.get())); |
| } |
| |
| // Storage as a reference. |
| ScopedLocalRef<jclass> weak_ref_class(jni_env, jni_env->FindClass(kWeakClassName)); |
| CHECK(weak_ref_class.get() != nullptr); |
| jfieldID weak_field = jni_env->GetStaticFieldID(storage_class_, |
| kStorageWeakFieldName, |
| kStorageWeakFieldSig); |
| CHECK(weak_field != nullptr); |
| if (set) { |
| // Create a WeakReference. |
| jmethodID weak_init = jni_env->GetMethodID(weak_ref_class.get(), "<init>", kWeakInitSig); |
| CHECK(weak_init != nullptr); |
| ScopedLocalRef<jobject> weak_obj(jni_env, jni_env->NewObject(weak_ref_class.get(), |
| weak_init, |
| value)); |
| CHECK(weak_obj.get() != nullptr); |
| jni_env->SetStaticObjectField(storage_class_, weak_field, weak_obj.get()); |
| CHECK(!jni_env->ExceptionCheck()); |
| } else { |
| // Check the reference value. |
| jmethodID get_referent = jni_env->GetMethodID(weak_ref_class.get(), "get", kWeakGetSig); |
| CHECK(get_referent != nullptr); |
| ScopedLocalRef<jobject> weak_obj(jni_env, jni_env->GetStaticObjectField(storage_class_, |
| weak_field)); |
| CHECK(weak_obj.get() != nullptr); |
| ScopedLocalRef<jobject> weak_referent(jni_env, jni_env->CallObjectMethod(weak_obj.get(), |
| get_referent)); |
| CHECK(weak_referent.get() != nullptr); |
| CHECK(jni_env->IsSameObject(value, weak_referent.get())); |
| } |
| } |
| |
| static void CheckFound() { |
| CHECK(found_); |
| CHECK(compared_); |
| } |
| |
| static void Free(JNIEnv* env) { |
| if (stored_class_ != nullptr) { |
| env->DeleteGlobalRef(stored_class_); |
| DCHECK(weakly_stored_class_ != nullptr); |
| env->DeleteWeakGlobalRef(weakly_stored_class_); |
| // Do not attempt to delete the local ref. It will be out of date by now. |
| } |
| } |
| |
| static jclass storage_class_; |
| |
| private: |
| static jobject stored_class_; |
| static jweak weakly_stored_class_; |
| static jobject local_stored_class_; |
| static bool found_; |
| static bool compared_; |
| }; |
| jclass ClassLoadPrepareEquality::storage_class_ = nullptr; |
| jobject ClassLoadPrepareEquality::stored_class_ = nullptr; |
| jweak ClassLoadPrepareEquality::weakly_stored_class_ = nullptr; |
| jobject ClassLoadPrepareEquality::local_stored_class_ = nullptr; |
| bool ClassLoadPrepareEquality::found_ = false; |
| bool ClassLoadPrepareEquality::compared_ = false; |
| |
| extern "C" JNIEXPORT void JNICALL Java_art_Test912_setEqualityEventStorageClass( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { |
| ClassLoadPrepareEquality::storage_class_ = |
| reinterpret_cast<jclass>(env->NewGlobalRef(klass)); |
| } |
| |
| extern "C" JNIEXPORT void JNICALL Java_art_Test912_enableClassLoadPrepareEqualityEvents( |
| JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) { |
| EnableEvents(env, |
| b, |
| ClassLoadPrepareEquality::ClassLoadCallback, |
| ClassLoadPrepareEquality::ClassPrepareCallback); |
| if (b == JNI_FALSE) { |
| ClassLoadPrepareEquality::Free(env); |
| ClassLoadPrepareEquality::CheckFound(); |
| env->DeleteGlobalRef(ClassLoadPrepareEquality::storage_class_); |
| ClassLoadPrepareEquality::storage_class_ = nullptr; |
| } |
| } |
| |
| } // namespace Test912Classes |
| } // namespace art |