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();
diff --git a/test/1946-list-descriptors/descriptors.cc b/test/1946-list-descriptors/descriptors.cc
new file mode 100644
index 0000000..01b306d
--- /dev/null
+++ b/test/1946-list-descriptors/descriptors.cc
@@ -0,0 +1,140 @@
+/*
+ * 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 "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1946Descriptors {
+
+typedef jvmtiError (*GetDescriptorList)(jvmtiEnv* env, jobject loader, jint* cnt, char*** descs);
+
+struct DescriptorData {
+  GetDescriptorList get_descriptor_list;
+};
+
+template <typename T>
+static void Dealloc(T* t) {
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename ...Rest>
+static void Dealloc(T* t, Rest... rs) {
+  Dealloc(t);
+  Dealloc(rs...);
+}
+
+static void Cleanup(char** data, jint cnt) {
+  for (jint i = 0; i < cnt; i++) {
+    Dealloc(data[i]);
+  }
+  Dealloc(data);
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test1946_getClassloaderDescriptors(
+    JNIEnv* env, jclass, jobject loader) {
+  DescriptorData* data = nullptr;
+  if (JvmtiErrorToException(
+      env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return nullptr;
+  }
+  if (data == nullptr || data->get_descriptor_list == nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Alloc tracking data not initialized.");
+    return nullptr;
+  }
+  char** classes = nullptr;
+  jint cnt = -1;
+  if (JvmtiErrorToException(env, jvmti_env, data->get_descriptor_list(jvmti_env,
+                                                                      loader,
+                                                                      &cnt,
+                                                                      &classes))) {
+    return nullptr;
+  }
+  ScopedLocalRef<jobjectArray> arr(env, env->NewObjectArray(cnt,
+                                                            env->FindClass("java/lang/String"),
+                                                            nullptr));
+  if (env->ExceptionCheck()) {
+    Cleanup(classes, cnt);
+    return nullptr;
+  }
+
+  for (jint i = 0; i < cnt; i++) {
+    env->SetObjectArrayElement(arr.get(), i, env->NewStringUTF(classes[i]));
+    if (env->ExceptionCheck()) {
+      Cleanup(classes, cnt);
+      return nullptr;
+    }
+  }
+  Cleanup(classes, cnt);
+  return arr.release();
+}
+
+static void DeallocParams(jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(params[i].name);
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1946_initializeTest(JNIEnv* env, jclass) {
+  void* old_data = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) {
+    return;
+  } else if (old_data != nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
+    return;
+  }
+  DescriptorData* data = nullptr;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->Allocate(sizeof(DescriptorData),
+                                                reinterpret_cast<unsigned char**>(&data)))) {
+    return;
+  }
+  memset(data, 0, sizeof(DescriptorData));
+  // Get the extensions.
+  jint n_ext = 0;
+  jvmtiExtensionFunctionInfo* infos = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) {
+    return;
+  }
+  for (jint i = 0; i < n_ext; i++) {
+    jvmtiExtensionFunctionInfo* cur_info = &infos[i];
+    if (strcmp("com.android.art.class.get_class_loader_class_descriptors", cur_info->id) == 0) {
+      data->get_descriptor_list = reinterpret_cast<GetDescriptorList>(cur_info->func);
+    }
+    // Cleanup the cur_info
+    DeallocParams(cur_info->params, cur_info->param_count);
+    Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors);
+  }
+  // Cleanup the array.
+  Dealloc(infos);
+  if (data->get_descriptor_list == nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Unable to find memory tracking extensions.");
+    return;
+  }
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data));
+  return;
+}
+
+}  // namespace Test1946Descriptors
+}  // namespace art
diff --git a/test/1946-list-descriptors/expected.txt b/test/1946-list-descriptors/expected.txt
new file mode 100644
index 0000000..53e0935
--- /dev/null
+++ b/test/1946-list-descriptors/expected.txt
@@ -0,0 +1 @@
+Passed!
diff --git a/test/1946-list-descriptors/info.txt b/test/1946-list-descriptors/info.txt
new file mode 100644
index 0000000..924e0b3
--- /dev/null
+++ b/test/1946-list-descriptors/info.txt
@@ -0,0 +1 @@
+Tests the jvmti-extension to get the classes contained in class-loaders.
diff --git a/test/1946-list-descriptors/run b/test/1946-list-descriptors/run
new file mode 100755
index 0000000..c6e62ae
--- /dev/null
+++ b/test/1946-list-descriptors/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-run "$@" --jvmti
diff --git a/test/1946-list-descriptors/src-art/art/Test1946.java b/test/1946-list-descriptors/src-art/art/Test1946.java
new file mode 100644
index 0000000..3e5ec65
--- /dev/null
+++ b/test/1946-list-descriptors/src-art/art/Test1946.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package art;
+
+import java.util.*;
+import java.lang.reflect.*;
+import java.nio.ByteBuffer;
+import dalvik.system.InMemoryDexClassLoader;
+
+public class Test1946 {
+  // Base64 encoded dex file containing the following classes. Note the class E cannot be loaded.
+  // public class A {}
+  // public class B {}
+  // public class C {}
+  // public class D {}
+  // public class E extends ClassNotThere {}
+  private static final byte[] TEST_CLASSES = Base64.getDecoder().decode(
+      "ZGV4CjAzNQDzTO8rVDlKlz80vQF4NLYV5MjMMjHlOtRoAwAAcAAAAHhWNBIAAAAAAAAAAOACAAAO" +
+      "AAAAcAAAAAgAAACoAAAAAQAAAMgAAAAAAAAAAAAAAAcAAADUAAAABQAAAAwBAAC8AQAArAEAACQC" +
+      "AAAsAgAANAIAADwCAABEAgAATAIAAFQCAABZAgAAXgIAAGMCAAB0AgAAeQIAAH4CAACSAgAABgAA" +
+      "AAcAAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAANAAAABwAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA" +
+      "AgAAAAAAAAADAAAAAAAAAAQAAAAAAAAABQAAAAAAAAAGAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAB" +
+      "AAAAAAAAAK4CAAAAAAAAAQAAAAAAAAAGAAAAAAAAAAIAAAAAAAAAuAIAAAAAAAACAAAAAAAAAAYA" +
+      "AAAAAAAAAwAAAAAAAADCAgAAAAAAAAQAAAAAAAAABgAAAAAAAAAEAAAAAAAAAMwCAAAAAAAABQAA" +
+      "AAAAAAADAAAAAAAAAAUAAAAAAAAA1gIAAAAAAAABAAEAAQAAAJUCAAAEAAAAcBAGAAAADgABAAEA" +
+      "AQAAAJoCAAAEAAAAcBAGAAAADgABAAEAAQAAAJ8CAAAEAAAAcBAGAAAADgABAAEAAQAAAKQCAAAE" +
+      "AAAAcBAGAAAADgABAAEAAQAAAKkCAAAEAAAAcBADAAAADgAGPGluaXQ+AAZBLmphdmEABkIuamF2" +
+      "YQAGQy5qYXZhAAZELmphdmEABkUuamF2YQADTEE7AANMQjsAA0xDOwAPTENsYXNzTm90VGhlcmU7" +
+      "AANMRDsAA0xFOwASTGphdmEvbGFuZy9PYmplY3Q7AAFWAAEABw4AAQAHDgABAAcOAAEABw4AAQAH" +
+      "DgAAAAEAAICABKwDAAABAAGAgATEAwAAAQACgIAE3AMAAAEABICABPQDAAABAAWAgASMBAsAAAAA" +
+      "AAAAAQAAAAAAAAABAAAADgAAAHAAAAACAAAACAAAAKgAAAADAAAAAQAAAMgAAAAFAAAABwAAANQA" +
+      "AAAGAAAABQAAAAwBAAABIAAABQAAAKwBAAACIAAADgAAACQCAAADIAAABQAAAJUCAAAAIAAABQAA" +
+      "AK4CAAAAEAAAAQAAAOACAAA=");
+  public class TMP1 {}
+  public class TMP2 {}
+  public class TMP3 extends ArrayList {}
+
+  private static void check(boolean b, String msg) {
+    if (!b) {
+      throw new Error("Test failed! " + msg);
+    }
+  }
+
+  private static <T> void checkEq(T[] full, T[] sub, String msg) {
+    List<T> f = Arrays.asList(full);
+    check(full.length == sub.length, "not equal length");
+    msg = Arrays.toString(full) + " is not same as " + Arrays.toString(sub) + ": " + msg;
+    check(Arrays.asList(full).containsAll(Arrays.asList(sub)), msg);
+  }
+
+  private static <T> void checkSubset(T[] full, T[] sub, String msg) {
+    msg = Arrays.toString(full) + " does not contain all of " + Arrays.toString(sub) + ": " + msg;
+    check(Arrays.asList(full).containsAll(Arrays.asList(sub)), msg);
+  }
+
+  public static void run() throws Exception {
+    initializeTest();
+    // Check a few random classes in BCP.
+    checkSubset(getClassloaderDescriptors(null),
+        new String[] { "Ljava/lang/String;", "Ljava/util/TreeSet;" },
+        "Missing entries for null classloader.");
+    // Make sure that null is the same as BootClassLoader
+    checkEq(getClassloaderDescriptors(null),
+        getClassloaderDescriptors(Object.class.getClassLoader()), "Object not in bcp!");
+    // Check the current class loader gets expected classes.
+    checkSubset(getClassloaderDescriptors(Test1946.class.getClassLoader()),
+        new String[] {
+          "Lart/Test1946;",
+          "Lart/Test1946$TMP1;",
+          "Lart/Test1946$TMP2;",
+          "Lart/Test1946$TMP3;"
+        },
+        "Missing entries for current class classloader.");
+    // Check that the result is exactly what we expect and includes classes that fail verification.
+    checkEq(getClassloaderDescriptors(makeClassLoaderFrom(TEST_CLASSES,
+            ClassLoader.getSystemClassLoader())),
+        new String[] { "LA;", "LB;", "LC;", "LD;", "LE;" },
+        "Unexpected classes in custom classloader");
+    checkEq(getClassloaderDescriptors(makeClassLoaderFrom(TEST_CLASSES,
+            Object.class.getClassLoader())),
+        new String[] { "LA;", "LB;", "LC;", "LD;", "LE;" },
+        "Unexpected classes in custom classloader");
+    checkEq(getClassloaderDescriptors(makeClassLoaderFrom(TEST_CLASSES,
+            Test1946.class.getClassLoader())),
+        new String[] { "LA;", "LB;", "LC;", "LD;", "LE;" },
+        "Unexpected classes in custom classloader");
+    // Check we only get 1 copy of each descriptor.
+    checkEq(getClassloaderDescriptors(makeClassLoaderFrom(Arrays.asList(TEST_CLASSES, TEST_CLASSES),
+            Test1946.class.getClassLoader())),
+        new String[] { "LA;", "LB;", "LC;", "LD;", "LE;" },
+        "Unexpected classes in custom classloader");
+    System.out.println("Passed!");
+  }
+
+  private static ClassLoader makeClassLoaderFrom(byte[] data, ClassLoader parent) throws Exception {
+    return new InMemoryDexClassLoader(ByteBuffer.wrap(data), parent);
+  }
+
+  private static ClassLoader makeClassLoaderFrom(List<byte[]> data, ClassLoader parent)
+      throws Exception {
+    ArrayList<ByteBuffer> bufs = new ArrayList<>();
+    for (byte[] d : data) {
+      bufs.add(ByteBuffer.wrap(d));
+    }
+    return new InMemoryDexClassLoader(bufs.toArray(new ByteBuffer[0]), parent);
+  }
+
+  private static native void initializeTest();
+  private static native String[] getClassloaderDescriptors(ClassLoader loader);
+}
diff --git a/test/1946-list-descriptors/src/Main.java b/test/1946-list-descriptors/src/Main.java
new file mode 100644
index 0000000..7d6f7ce
--- /dev/null
+++ b/test/1946-list-descriptors/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1946.run();
+  }
+}
diff --git a/test/1946-list-descriptors/src/art/Test1946.java b/test/1946-list-descriptors/src/art/Test1946.java
new file mode 100644
index 0000000..9636957
--- /dev/null
+++ b/test/1946-list-descriptors/src/art/Test1946.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package art;
+
+public class Test1946 {
+  public static void run() {
+    System.out.println("Failed! This should not be run. The test is in src-art/art/Test1946.java");
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 902f4ed..5558cd4 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -267,6 +267,7 @@
         "1941-dispose-stress/dispose_stress.cc",
         "1942-suspend-raw-monitor-exit/native_suspend_monitor.cc",
         "1943-suspend-raw-monitor-wait/native_suspend_monitor.cc",
+        "1946-list-descriptors/descriptors.cc",
     ],
     header_libs: [
         "jni_headers",