ART: Add GetClassLoaderClasses

Add support for GetClassLoaderClasses, extracting all classes a
given classloader initiated. Add test.

Bug: 31684578
Test: m test-art-host-run-test-912-classes
Change-Id: I0aba27fb1674baf1263c0a19dc2dcce7af013760
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());
+    }
+  }
 }