Add get_class_loader_class_descriptors JVMTI extension method
This adds a new JVMTI extension:
com.android.art.class.get_class_loader_class_descriptors. This will
enumerate all of the classes known to be loadable with a given
class-loader as the defining class loader. The function gets this by
looking at all dex-files associated with the given class-loader.
Bug: 73504235
Test: ./test.py --host -j50
Change-Id: Ie54223cd40109056396ba609f92b6c02d81dd4ab
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
index 4d54d75..d510ae5 100644
--- a/openjdkjvmti/ti_class.cc
+++ b/openjdkjvmti/ti_class.cc
@@ -69,7 +69,7 @@
#include "scoped_thread_state_change-inl.h"
#include "thread-current-inl.h"
#include "thread_list.h"
-#include "ti_class_loader.h"
+#include "ti_class_loader-inl.h"
#include "ti_phase.h"
#include "ti_redefine.h"
#include "utils.h"
@@ -862,6 +862,108 @@
return ERR(NONE);
}
+// Copies unique class descriptors into the classes list from dex_files.
+static jvmtiError CopyClassDescriptors(jvmtiEnv* env,
+ const std::vector<const art::DexFile*>& dex_files,
+ /*out*/jint* count_ptr,
+ /*out*/char*** classes) {
+ jvmtiError res = OK;
+ std::set<art::StringPiece> unique_descriptors;
+ std::vector<const char*> descriptors;
+ auto add_descriptor = [&](const char* desc) {
+ // Don't add duplicates.
+ if (res == OK && unique_descriptors.find(desc) == unique_descriptors.end()) {
+ // The desc will remain valid since we hold a ref to the class_loader.
+ unique_descriptors.insert(desc);
+ descriptors.push_back(CopyString(env, desc, &res).release());
+ }
+ };
+ for (const art::DexFile* dex_file : dex_files) {
+ uint32_t num_defs = dex_file->NumClassDefs();
+ for (uint32_t i = 0; i < num_defs; i++) {
+ add_descriptor(dex_file->GetClassDescriptor(dex_file->GetClassDef(i)));
+ }
+ }
+ char** out_data = nullptr;
+ if (res == OK) {
+ res = env->Allocate(sizeof(char*) * descriptors.size(),
+ reinterpret_cast<unsigned char**>(&out_data));
+ }
+ if (res != OK) {
+ env->Deallocate(reinterpret_cast<unsigned char*>(out_data));
+ // Failed to allocate. Cleanup everything.
+ for (const char* data : descriptors) {
+ env->Deallocate(reinterpret_cast<unsigned char*>(const_cast<char*>(data)));
+ }
+ descriptors.clear();
+ return res;
+ }
+ // Everything is good.
+ memcpy(out_data, descriptors.data(), sizeof(char*) * descriptors.size());
+ *count_ptr = static_cast<jint>(descriptors.size());
+ *classes = out_data;
+ return OK;
+}
+
+jvmtiError ClassUtil::GetClassLoaderClassDescriptors(jvmtiEnv* env,
+ jobject loader,
+ /*out*/jint* count_ptr,
+ /*out*/char*** classes) {
+ art::Thread* self = art::Thread::Current();
+ if (env == nullptr) {
+ return ERR(INVALID_ENVIRONMENT);
+ } else if (self == nullptr) {
+ return ERR(UNATTACHED_THREAD);
+ } else if (count_ptr == nullptr || classes == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ if (loader == nullptr ||
+ jnienv->IsInstanceOf(loader, art::WellKnownClasses::java_lang_BootClassLoader)) {
+ // We can just get the dex files directly for the boot class path.
+ return CopyClassDescriptors(env,
+ art::Runtime::Current()->GetClassLinker()->GetBootClassPath(),
+ count_ptr,
+ classes);
+ }
+ if (!jnienv->IsInstanceOf(loader, art::WellKnownClasses::java_lang_ClassLoader)) {
+ return ERR(ILLEGAL_ARGUMENT);
+ } else if (!jnienv->IsInstanceOf(loader,
+ art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
+ LOG(ERROR) << "GetClassLoaderClassDescriptors is only implemented for BootClassPath and "
+ << "dalvik.system.BaseDexClassLoader class loaders";
+ // TODO Possibly return OK With no classes would be better since these ones cannot have any
+ // real classes associated with them.
+ return ERR(NOT_IMPLEMENTED);
+ }
+
+ art::ScopedObjectAccess soa(self);
+ art::StackHandleScope<1> hs(self);
+ art::Handle<art::mirror::ClassLoader> class_loader(
+ hs.NewHandle(soa.Decode<art::mirror::ClassLoader>(loader)));
+ std::vector<const art::DexFile*> dex_files;
+ ClassLoaderHelper::VisitDexFileObjects(
+ self,
+ class_loader,
+ [&] (art::ObjPtr<art::mirror::Object> dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ art::StackHandleScope<2> hs(self);
+ art::Handle<art::mirror::Object> h_dex_file(hs.NewHandle(dex_file));
+ art::Handle<art::mirror::LongArray> cookie(
+ hs.NewHandle(ClassLoaderHelper::GetDexFileCookie(h_dex_file)));
+ size_t num_elements = cookie->GetLength();
+ // We need to skip over the oat_file that's the first element. The other elements are all
+ // dex files.
+ for (size_t i = 1; i < num_elements; i++) {
+ dex_files.push_back(
+ reinterpret_cast<const art::DexFile*>(static_cast<uintptr_t>(cookie->Get(i))));
+ }
+ // Iterate over all dex files.
+ return true;
+ });
+ // We hold the loader so the dex files won't go away until after this call at worst.
+ return CopyClassDescriptors(env, dex_files, count_ptr, classes);
+}
+
jvmtiError ClassUtil::GetClassLoaderClasses(jvmtiEnv* env,
jobject initiating_loader,
jint* class_count_ptr,
diff --git a/openjdkjvmti/ti_class.h b/openjdkjvmti/ti_class.h
index dd99e36..7e427a0 100644
--- a/openjdkjvmti/ti_class.h
+++ b/openjdkjvmti/ti_class.h
@@ -75,6 +75,11 @@
jint* class_count_ptr,
jclass** classes_ptr);
+ static jvmtiError GetClassLoaderClassDescriptors(jvmtiEnv* env,
+ jobject loader,
+ jint* count_ptr,
+ char*** classes);
+
static jvmtiError IsInterface(jvmtiEnv* env, jclass klass, jboolean* is_interface_ptr);
static jvmtiError IsArrayClass(jvmtiEnv* env, jclass klass, jboolean* is_array_class_ptr);
diff --git a/openjdkjvmti/ti_class_loader-inl.h b/openjdkjvmti/ti_class_loader-inl.h
new file mode 100644
index 0000000..95278f4
--- /dev/null
+++ b/openjdkjvmti/ti_class_loader-inl.h
@@ -0,0 +1,76 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h. The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_LOADER_INL_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_LOADER_INL_H_
+
+#include "ti_class_loader.h"
+#include "art_field-inl.h"
+#include "handle.h"
+#include "handle_scope.h"
+#include "jni_internal.h"
+#include "mirror/object.h"
+#include "mirror/object_array-inl.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+template<typename Visitor>
+inline void ClassLoaderHelper::VisitDexFileObjects(art::Thread* self,
+ art::Handle<art::mirror::ClassLoader> loader,
+ const Visitor& visitor) {
+ art::StackHandleScope<1> hs(self);
+ art::ArtField* element_dex_file_field = art::jni::DecodeArtField(
+ art::WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+
+ art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(
+ hs.NewHandle(GetDexElementList(self, loader)));
+ if (dex_elements_list == nullptr) {
+ return;
+ }
+
+ size_t num_elements = dex_elements_list->GetLength();
+ // Iterate over the DexPathList$Element to find the right one
+ for (size_t i = 0; i < num_elements; i++) {
+ art::ObjPtr<art::mirror::Object> current_element = dex_elements_list->Get(i);
+ CHECK(!current_element.IsNull());
+ art::ObjPtr<art::mirror::Object> dex_file(element_dex_file_field->GetObject(current_element));
+ if (!dex_file.IsNull()) {
+ if (!visitor(dex_file)) {
+ return;
+ }
+ }
+ }
+}
+
+} // namespace openjdkjvmti
+
+#endif // ART_OPENJDKJVMTI_TI_CLASS_LOADER_INL_H_
diff --git a/openjdkjvmti/ti_class_loader.cc b/openjdkjvmti/ti_class_loader.cc
index d594d6e..3df5de9 100644
--- a/openjdkjvmti/ti_class_loader.cc
+++ b/openjdkjvmti/ti_class_loader.cc
@@ -29,7 +29,7 @@
* questions.
*/
-#include "ti_class_loader.h"
+#include "ti_class_loader-inl.h"
#include <limits>
@@ -134,45 +134,28 @@
return new_cookie.Get();
}
-// TODO This should return the actual source java.lang.DexFile object for the klass being loaded.
-art::ObjPtr<art::mirror::Object> ClassLoaderHelper::FindSourceDexFileObject(
- art::Thread* self, art::Handle<art::mirror::ClassLoader> loader) {
- const char* dex_path_list_element_array_name = "[Ldalvik/system/DexPathList$Element;";
- const char* dex_path_list_element_name = "Ldalvik/system/DexPathList$Element;";
- const char* dex_file_name = "Ldalvik/system/DexFile;";
- const char* dex_path_list_name = "Ldalvik/system/DexPathList;";
- const char* dex_class_loader_name = "Ldalvik/system/BaseDexClassLoader;";
+art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> ClassLoaderHelper::GetDexElementList(
+ art::Thread* self,
+ art::Handle<art::mirror::ClassLoader> loader) {
+ art::StackHandleScope<4> hs(self);
- CHECK(!self->IsExceptionPending());
- art::StackHandleScope<5> hs(self);
- art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
-
- art::Handle<art::mirror::ClassLoader> null_loader(hs.NewHandle<art::mirror::ClassLoader>(
- nullptr));
- art::Handle<art::mirror::Class> base_dex_loader_class(hs.NewHandle(class_linker->FindClass(
- self, dex_class_loader_name, null_loader)));
+ art::Handle<art::mirror::Class>
+ base_dex_loader_class(hs.NewHandle(self->DecodeJObject(
+ art::WellKnownClasses::dalvik_system_BaseDexClassLoader)->AsClass()));
// Get all the ArtFields so we can look in the BaseDexClassLoader
- art::ArtField* path_list_field = base_dex_loader_class->FindDeclaredInstanceField(
- "pathList", dex_path_list_name);
- CHECK(path_list_field != nullptr);
-
+ art::ArtField* path_list_field = art::jni::DecodeArtField(
+ art::WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList);
art::ArtField* dex_path_list_element_field =
- class_linker->FindClass(self, dex_path_list_name, null_loader)
- ->FindDeclaredInstanceField("dexElements", dex_path_list_element_array_name);
- CHECK(dex_path_list_element_field != nullptr);
-
- art::ArtField* element_dex_file_field =
- class_linker->FindClass(self, dex_path_list_element_name, null_loader)
- ->FindDeclaredInstanceField("dexFile", dex_file_name);
- CHECK(element_dex_file_field != nullptr);
+ art::jni::DecodeArtField(art::WellKnownClasses::dalvik_system_DexPathList_dexElements);
// Check if loader is a BaseDexClassLoader
art::Handle<art::mirror::Class> loader_class(hs.NewHandle(loader->GetClass()));
// Currently only base_dex_loader is allowed to actually define classes but if this changes in the
// future we should make sure to support all class loader types.
if (!loader_class->IsSubClass(base_dex_loader_class.Get())) {
- LOG(ERROR) << "The classloader is not a BaseDexClassLoader which is currently the only "
+ LOG(ERROR) << "The classloader " << loader_class->PrettyClass() << " is not a "
+ << base_dex_loader_class->PrettyClass() << " which is currently the only "
<< "supported class loader type!";
return nullptr;
}
@@ -180,28 +163,28 @@
art::Handle<art::mirror::Object> path_list(
hs.NewHandle(path_list_field->GetObject(loader.Get())));
CHECK(path_list != nullptr);
- CHECK(!self->IsExceptionPending());
- art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(hs.NewHandle(
- dex_path_list_element_field->GetObject(path_list.Get())->
- AsObjectArray<art::mirror::Object>()));
- CHECK(!self->IsExceptionPending());
- CHECK(dex_elements_list != nullptr);
- size_t num_elements = dex_elements_list->GetLength();
- // Iterate over the DexPathList$Element to find the right one
- for (size_t i = 0; i < num_elements; i++) {
- art::ObjPtr<art::mirror::Object> current_element = dex_elements_list->Get(i);
- CHECK(!current_element.IsNull());
- // TODO It would be cleaner to put the art::DexFile into the dalvik.system.DexFile the class
- // comes from but it is more annoying because we would need to find this class. It is not
- // necessary for proper function since we just need to be in front of the classes old dex file
- // in the path.
- art::ObjPtr<art::mirror::Object> first_dex_file(
- element_dex_file_field->GetObject(current_element));
- if (!first_dex_file.IsNull()) {
- return first_dex_file;
- }
- }
- return nullptr;
+ art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list =
+ dex_path_list_element_field->GetObject(path_list.Get())->AsObjectArray<art::mirror::Object>();
+ return dex_elements_list;
+}
+
+// TODO This should return the actual source java.lang.DexFile object for the klass being loaded.
+art::ObjPtr<art::mirror::Object> ClassLoaderHelper::FindSourceDexFileObject(
+ art::Thread* self, art::Handle<art::mirror::ClassLoader> loader) {
+ art::ObjPtr<art::mirror::Object> res = nullptr;
+ VisitDexFileObjects(self,
+ loader,
+ [&] (art::ObjPtr<art::mirror::Object> dex_file) {
+ res = dex_file;
+ // Just stop at the first one.
+ // TODO It would be cleaner to put the art::DexFile into the
+ // dalvik.system.DexFile the class comes from but it is more annoying
+ // because we would need to find this class. It is not necessary for proper
+ // function since we just need to be in front of the classes old dex file in
+ // the path.
+ return false;
+ });
+ return res;
}
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_class_loader.h b/openjdkjvmti/ti_class_loader.h
index ceb7b33..5c9497b 100644
--- a/openjdkjvmti/ti_class_loader.h
+++ b/openjdkjvmti/ti_class_loader.h
@@ -82,6 +82,14 @@
art::Thread* self, art::Handle<art::mirror::ClassLoader> loader)
REQUIRES_SHARED(art::Locks::mutator_lock_);
+ // Calls visitor on each java.lang.DexFile associated with the given loader. The visitor should
+ // return true to continue on to the next DexFile or false to stop iterating.
+ template<typename Visitor>
+ static inline void VisitDexFileObjects(art::Thread* self,
+ art::Handle<art::mirror::ClassLoader> loader,
+ const Visitor& visitor)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+
static art::ObjPtr<art::mirror::LongArray> GetDexFileCookie(
art::Handle<art::mirror::Object> java_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_);
@@ -93,6 +101,11 @@
static void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
art::ObjPtr<art::mirror::LongArray> new_cookie)
REQUIRES(art::Roles::uninterruptible_) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+ static art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> GetDexElementList(
+ art::Thread* self, art::Handle<art::mirror::ClassLoader> loader)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
};
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index 79a8cd6..5b1a16c 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -36,6 +36,7 @@
#include "art_jvmti.h"
#include "events.h"
#include "ti_allocator.h"
+#include "ti_class.h"
#include "ti_ddms.h"
#include "ti_heap.h"
#include "thread-inl.h"
@@ -226,6 +227,31 @@
return error;
}
+ // GetClassLoaderClassDescriptors extension
+ error = add_extension(
+ reinterpret_cast<jvmtiExtensionFunction>(ClassUtil::GetClassLoaderClassDescriptors),
+ "com.android.art.class.get_class_loader_class_descriptors",
+ "Retrieves a list of all the classes (as class descriptors) that the given class loader is"
+ " capable of being the defining class loader for. The return format is a list of"
+ " null-terminated descriptor strings of the form \"L/java/lang/Object;\". Each descriptor"
+ " will be in the list at most once. If the class_loader is null the bootclassloader will be"
+ " used. If the class_loader is not null it must either be a java.lang.BootClassLoader, a"
+ " dalvik.system.BaseDexClassLoader or a derived type. The data_out list and all elements"
+ " must be deallocated by the caller.",
+ {
+ { "class_loader", JVMTI_KIND_IN, JVMTI_TYPE_JOBJECT, true },
+ { "class_descriptor_count_out", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
+ { "data_out", JVMTI_KIND_ALLOC_ALLOC_BUF, JVMTI_TYPE_CCHAR, false },
+ },
+ {
+ ERR(NULL_POINTER),
+ ERR(ILLEGAL_ARGUMENT),
+ ERR(OUT_OF_MEMORY),
+ ERR(NOT_IMPLEMENTED),
+ });
+ if (error != ERR(NONE)) {
+ return error;
+ }
// Copy into output buffer.
*extension_count_ptr = ext_vector.size();