Merge "ART: Add GetClassLoaderClasses"
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 580acb7..cad674e 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -645,6 +645,10 @@
   mirror::Class* GetHoldingClassOfCopiedMethod(ArtMethod* method)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Returns null if not found.
+  ClassTable* ClassTableForClassLoader(ObjPtr<mirror::ClassLoader> class_loader)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   struct DexCacheData {
     // Weak root to the DexCache. Note: Do not decode this unnecessarily or else class unloading may
     // not work properly.
@@ -1039,10 +1043,6 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(Locks::classlinker_classes_lock_);
 
-  // Returns null if not found.
-  ClassTable* ClassTableForClassLoader(ObjPtr<mirror::ClassLoader> class_loader)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   // Insert a new class table if not found.
   ClassTable* InsertClassTableForClassLoader(ObjPtr<mirror::ClassLoader> class_loader)
       REQUIRES_SHARED(Locks::mutator_lock_)
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 367b60d..3825c41 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -537,7 +537,7 @@
                                           jobject initiating_loader,
                                           jint* class_count_ptr,
                                           jclass** classes_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetClassLoaderClasses(env, initiating_loader, class_count_ptr, classes_ptr);
   }
 
   static jvmtiError GetClassSignature(jvmtiEnv* env,
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index 0d1704c..d1324bc 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -32,7 +32,10 @@
 #include "ti_class.h"
 
 #include "art_jvmti.h"
+#include "class_table-inl.h"
+#include "class_linker.h"
 #include "jni_internal.h"
+#include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
 
@@ -328,4 +331,90 @@
   return ERR(NONE);
 }
 
+jvmtiError ClassUtil::GetClassLoaderClasses(jvmtiEnv* env,
+                                            jobject initiating_loader,
+                                            jint* class_count_ptr,
+                                            jclass** classes_ptr) {
+  UNUSED(env, initiating_loader, class_count_ptr, classes_ptr);
+
+  if (class_count_ptr == nullptr || classes_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  art::Thread* self = art::Thread::Current();
+  if (!self->GetJniEnv()->IsInstanceOf(initiating_loader,
+                                       art::WellKnownClasses::java_lang_ClassLoader)) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (self->GetJniEnv()->IsInstanceOf(initiating_loader,
+                                      art::WellKnownClasses::java_lang_BootClassLoader)) {
+    // Need to use null for the BootClassLoader.
+    initiating_loader = nullptr;
+  }
+
+  art::ScopedObjectAccess soa(self);
+  art::ObjPtr<art::mirror::ClassLoader> class_loader =
+      soa.Decode<art::mirror::ClassLoader>(initiating_loader);
+
+  art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
+
+  art::ReaderMutexLock mu(self, *art::Locks::classlinker_classes_lock_);
+
+  art::ClassTable* class_table = class_linker->ClassTableForClassLoader(class_loader);
+  if (class_table == nullptr) {
+    // Nothing loaded.
+    *class_count_ptr = 0;
+    *classes_ptr = nullptr;
+    return ERR(NONE);
+  }
+
+  struct ClassTableCount {
+    bool operator()(art::ObjPtr<art::mirror::Class> klass) {
+      DCHECK(klass != nullptr);
+      ++count;
+      return true;
+    }
+
+    size_t count = 0;
+  };
+  ClassTableCount ctc;
+  class_table->Visit(ctc);
+
+  if (ctc.count == 0) {
+    // Nothing loaded.
+    *class_count_ptr = 0;
+    *classes_ptr = nullptr;
+    return ERR(NONE);
+  }
+
+  unsigned char* data;
+  jvmtiError data_result = env->Allocate(ctc.count * sizeof(jclass), &data);
+  if (data_result != ERR(NONE)) {
+    return data_result;
+  }
+  jclass* class_array = reinterpret_cast<jclass*>(data);
+
+  struct ClassTableFill {
+    bool operator()(art::ObjPtr<art::mirror::Class> klass)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      DCHECK(klass != nullptr);
+      DCHECK_LT(count, ctc_ref.count);
+      local_class_array[count++] = soa_ptr->AddLocalReference<jclass>(klass);
+      return true;
+    }
+
+    jclass* local_class_array;
+    const ClassTableCount& ctc_ref;
+    art::ScopedObjectAccess* soa_ptr;
+    size_t count;
+  };
+  ClassTableFill ctf = { class_array, ctc, &soa, 0 };
+  class_table->Visit(ctf);
+  DCHECK_EQ(ctc.count, ctf.count);
+
+  *class_count_ptr = ctc.count;
+  *classes_ptr = class_array;
+
+  return ERR(NONE);
+}
+
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_class.h b/runtime/openjdkjvmti/ti_class.h
index 577fc8e..7a0fafb 100644
--- a/runtime/openjdkjvmti/ti_class.h
+++ b/runtime/openjdkjvmti/ti_class.h
@@ -65,6 +65,11 @@
 
   static jvmtiError GetClassLoader(jvmtiEnv* env, jclass klass, jobject* classloader_ptr);
 
+  static jvmtiError GetClassLoaderClasses(jvmtiEnv* env,
+                                          jobject initiating_loader,
+                                          jint* class_count_ptr,
+                                          jclass** classes_ptr);
+
   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/test/912-classes/classes.cc b/test/912-classes/classes.cc
index 69301c7..a22d1d7 100644
--- a/test/912-classes/classes.cc
+++ b/test/912-classes/classes.cc
@@ -222,5 +222,24 @@
   return classloader;
 }
 
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_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, 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;
+}
+
 }  // namespace Test912Classes
 }  // namespace art
diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt
index 44c861a..a95a465 100644
--- a/test/912-classes/expected.txt
+++ b/test/912-classes/expected.txt
@@ -43,3 +43,19 @@
 class [Ljava.lang.String; null
 interface Main$InfA dalvik.system.PathClassLoader
 class $Proxy0 dalvik.system.PathClassLoader
+
+boot <- src <- src-ex (A,B)
+912-classes-ex.jar+ -> 912-classes.jar+ -> 
+[class A, class B, class java.lang.Object]
+912-classes.jar+ -> 
+[class B, class java.lang.Object]
+
+boot <- src (B) <- src-ex (A, List)
+912-classes-ex.jar+ -> 912-classes.jar+ -> 
+[class A, class java.lang.Object, interface java.util.List]
+912-classes.jar+ -> 
+[class B, class java.lang.Object]
+
+boot <- src+src-ex (A,B)
+912-classes.jar+ -> 
+[class A, class B, class java.lang.Object]
diff --git a/test/912-classes/src-ex/A.java b/test/912-classes/src-ex/A.java
new file mode 100644
index 0000000..64acb2f
--- /dev/null
+++ b/test/912-classes/src-ex/A.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2017 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 A {
+}
\ No newline at end of file
diff --git a/test/912-classes/src/B.java b/test/912-classes/src/B.java
new file mode 100644
index 0000000..f1458c3
--- /dev/null
+++ b/test/912-classes/src/B.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2017 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 B {
+}
\ No newline at end of file
diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java
index e627d42..ea3c49c 100644
--- a/test/912-classes/src/Main.java
+++ b/test/912-classes/src/Main.java
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Proxy;
 import java.util.Arrays;
+import java.util.Comparator;
 
 public class Main {
   public static void main(String[] args) throws Exception {
@@ -76,6 +78,8 @@
     testClassLoader(String[].class);
     testClassLoader(InfA.class);
     testClassLoader(getProxyClass());
+
+    testClassLoaderClasses();
   }
 
   private static Class<?> proxyClass = null;
@@ -151,6 +155,95 @@
     }
   }
 
+  private static void testClassLoaderClasses() throws Exception {
+    ClassLoader boot = ClassLoader.getSystemClassLoader().getParent();
+    while (boot.getParent() != null) {
+      boot = boot.getParent();
+    }
+
+    System.out.println();
+    System.out.println("boot <- src <- src-ex (A,B)");
+    ClassLoader cl1 = create(create(boot, DEX1), DEX2);
+    Class.forName("B", false, cl1);
+    Class.forName("A", false, cl1);
+    printClassLoaderClasses(cl1);
+
+    System.out.println();
+    System.out.println("boot <- src (B) <- src-ex (A, List)");
+    ClassLoader cl2 = create(create(boot, DEX1), DEX2);
+    Class.forName("A", false, cl2);
+    Class.forName("java.util.List", false, cl2);
+    Class.forName("B", false, cl2.getParent());
+    printClassLoaderClasses(cl2);
+
+    System.out.println();
+    System.out.println("boot <- src+src-ex (A,B)");
+    ClassLoader cl3 = create(boot, DEX1, DEX2);
+    Class.forName("B", false, cl3);
+    Class.forName("A", false, cl3);
+    printClassLoaderClasses(cl3);
+
+    // Check that the boot classloader dumps something non-empty.
+    Class<?>[] bootClasses = getClassLoaderClasses(boot);
+    if (bootClasses.length == 0) {
+      throw new RuntimeException("No classes initiated by boot classloader.");
+    }
+    // Check that at least java.util.List is loaded.
+    boolean foundList = false;
+    for (Class<?> c : bootClasses) {
+      if (c == java.util.List.class) {
+        foundList = true;
+        break;
+      }
+    }
+    if (!foundList) {
+      System.out.println(Arrays.toString(bootClasses));
+      throw new RuntimeException("Could not find class java.util.List.");
+    }
+  }
+
+  private static void printClassLoaderClasses(ClassLoader cl) {
+    for (;;) {
+      if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) {
+        break;
+      }
+
+      ClassLoader saved = cl;
+      for (;;) {
+        if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) {
+          break;
+        }
+        String s = cl.toString();
+        int index1 = s.indexOf("zip file");
+        int index2 = s.indexOf(']', index1);
+        if (index2 < 0) {
+          throw new RuntimeException("Unexpected classloader " + s);
+        }
+        String zip_file = s.substring(index1, index2);
+        int index3 = zip_file.indexOf('"');
+        int index4 = zip_file.indexOf('"', index3 + 1);
+        if (index4 < 0) {
+          throw new RuntimeException("Unexpected classloader " + s);
+        }
+        String paths = zip_file.substring(index3 + 1, index4);
+        String pathArray[] = paths.split(":");
+        for (String path : pathArray) {
+          int index5 = path.lastIndexOf('/');
+          System.out.print(path.substring(index5 + 1));
+          System.out.print('+');
+        }
+        System.out.print(" -> ");
+        cl = cl.getParent();
+      }
+      System.out.println();
+      Class<?> classes[] = getClassLoaderClasses(saved);
+      Arrays.sort(classes, new ClassNameComparator());
+      System.out.println(Arrays.toString(classes));
+
+      cl = saved.getParent();
+    }
+  }
+
   private static native boolean isModifiableClass(Class<?> c);
   private static native String[] getClassSignature(Class<?> c);
 
@@ -161,12 +254,14 @@
 
   private static native Object[] getClassFields(Class<?> c);
   private static native Object[] getClassMethods(Class<?> c);
-  private static native Class[] getImplementedInterfaces(Class<?> c);
+  private static native Class<?>[] getImplementedInterfaces(Class<?> c);
 
   private static native int getClassStatus(Class<?> c);
 
   private static native Object getClassLoader(Class<?> c);
 
+  private static native Class<?>[] getClassLoaderClasses(ClassLoader cl);
+
   private static class TestForNonInit {
     public static double dummy = Math.random();  // So it can't be compile-time initialized.
   }
@@ -188,4 +283,23 @@
   }
   public abstract static class ClassC implements InfA, InfC {
   }
+
+  private static final String DEX1 = System.getenv("DEX_LOCATION") + "/912-classes.jar";
+  private static final String DEX2 = System.getenv("DEX_LOCATION") + "/912-classes-ex.jar";
+
+  private static ClassLoader create(ClassLoader parent, String... elements) throws Exception {
+    // Note: We use a PathClassLoader, as we do not care about code performance. We only load
+    //       the classes, and they're empty.
+    Class<?> pathClassLoaderClass = Class.forName("dalvik.system.PathClassLoader");
+    Constructor<?> pathClassLoaderInit = pathClassLoaderClass.getConstructor(String.class,
+                                                                             ClassLoader.class);
+    String path = String.join(":", elements);
+    return (ClassLoader) pathClassLoaderInit.newInstance(path, parent);
+  }
+
+  private static class ClassNameComparator implements Comparator<Class<?>> {
+    public int compare(Class<?> c1, Class<?> c2) {
+      return c1.getName().compareTo(c2.getName());
+    }
+  }
 }