Merge "Fix shared libraries not being reported via Reporter" am: 3098ae7952

Change-Id: Ie2b225f65418e971bf024c2b12364ed59fff62a9
diff --git a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
index d0a449b..4a9e8631 100644
--- a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
+++ b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
@@ -24,8 +24,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import sun.misc.CompoundEnumeration;
 
 /**
@@ -134,28 +137,17 @@
      * Reports the current class loader chain to the registered {@code reporter}.
      */
     private void reportClassLoaderChain() {
-        ArrayList<ClassLoader> classLoadersChain = new ArrayList<>();
-        ArrayList<String> classPaths = new ArrayList<>();
-
-        classLoadersChain.add(this);
-        classPaths.add(String.join(File.pathSeparator, pathList.getDexPaths()));
-
-        ClassLoader bootClassLoader = ClassLoader.getSystemClassLoader().getParent();
-        ClassLoader current = getParent();
-
-        while (current != null && current != bootClassLoader) {
-            classLoadersChain.add(current);
-            if (current instanceof BaseDexClassLoader) {
-                BaseDexClassLoader bdcCurrent = (BaseDexClassLoader) current;
-                classPaths.add(String.join(File.pathSeparator, bdcCurrent.pathList.getDexPaths()));
-            } else {
-                // We can't determine the classpath for arbitrary class loaders.
-                classPaths.add(null);
-            }
-            current = current.getParent();
+        String[] classPathAndClassLoaderContexts = computeClassLoaderContextsNative();
+        if (classPathAndClassLoaderContexts.length == 0) {
+            return;
         }
-
-        reporter.report(classLoadersChain, classPaths);
+        Map<String, String> dexFileMapping =
+                new HashMap<>(classPathAndClassLoaderContexts.length / 2);
+        for (int i = 0; i < classPathAndClassLoaderContexts.length; i += 2) {
+            dexFileMapping.put(classPathAndClassLoaderContexts[i],
+                    classPathAndClassLoaderContexts[i + 1]);
+        }
+        reporter.report(Collections.unmodifiableMap(dexFileMapping));
     }
 
     /**
@@ -373,19 +365,16 @@
     @libcore.api.CorePlatformApi
     public interface Reporter {
         /**
-         * Reports the construction of a BaseDexClassLoader and provides information about the
-         * class loader chain.
+         * Reports the construction of a BaseDexClassLoader and provides opaque information about
+         * the class loader chain. For example, if the childmost ClassLoader in the chain:
+         * {@quote BaseDexClassLoader { foo.dex } -> BaseDexClassLoader { base.apk } 
+         *    -> BootClassLoader } was just initialized then the load of {@code "foo.dex"} would be
+         * reported with a classLoaderContext of {@code "PCL[];PCL[base.apk]"}.
          *
-         * @param classLoadersChain the chain of class loaders used during the construction of the
-         *     class loader. The first element is the BaseDexClassLoader being constructed,
-         *     the second element is its parent, and so on.
-         * @param classPaths the class paths of the class loaders present in
-         *     {@param classLoadersChain}. The first element corresponds to the first class
-         *     loader and so on. A classpath is represented as a list of dex files separated by
-         *     {@code File.pathSeparator}. If the class loader is not a BaseDexClassLoader the
-         *     classpath will be null.
+         * @param contextsMap A map from dex file paths to the class loader context used to load
+         *     each dex file.
          */
         @libcore.api.CorePlatformApi
-        void report(List<ClassLoader> classLoadersChain, List<String> classPaths);
+        void report(Map<String, String> contextsMap);
     }
 }
diff --git a/luni/src/test/java/libcore/dalvik/system/BaseDexClassLoaderTest.java b/luni/src/test/java/libcore/dalvik/system/BaseDexClassLoaderTest.java
index 1642a11..ba7c232 100644
--- a/luni/src/test/java/libcore/dalvik/system/BaseDexClassLoaderTest.java
+++ b/luni/src/test/java/libcore/dalvik/system/BaseDexClassLoaderTest.java
@@ -33,7 +33,9 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -48,18 +50,15 @@
 @RunWith(JUnit4.class)
 public final class BaseDexClassLoaderTest {
     private static class Reporter implements BaseDexClassLoader.Reporter {
-        public final List<ClassLoader> classLoaders = new ArrayList<>();
-        public final List<String> loadedDexPaths = new ArrayList<>();
+        public final Map<String, String> loadedDexMapping = new HashMap<String, String>();
 
         @Override
-        public void report(List<ClassLoader> loaders, List<String> dexPaths) {
-            classLoaders.addAll(loaders);
-            loadedDexPaths.addAll(dexPaths);
+        public void report(Map<String, String> contextMap) {
+            loadedDexMapping.putAll(contextMap);
         }
 
         void reset() {
-            classLoaders.clear();
-            loadedDexPaths.clear();
+            loadedDexMapping.clear();
         }
     }
 
@@ -122,17 +121,9 @@
         BaseDexClassLoader cl1 = new PathClassLoader(jar.getPath(),
             ClassLoader.getSystemClassLoader());
 
-        // Verify the reported data.
-        assertEquals(2, reporter.loadedDexPaths.size());
-        assertEquals(2, reporter.classLoaders.size());
-
-        // First class loader should be the one loading the files
-        assertEquals(jar.getPath(), reporter.loadedDexPaths.get(0));
-        assertEquals(cl1, reporter.classLoaders.get(0));
-
-        // Second class loader should be the system class loader.
-        // Don't check the actual classpath as that might vary based on system properties.
-        assertEquals(ClassLoader.getSystemClassLoader(), reporter.classLoaders.get(1));
+        // Verify the reported data. The only class loader context should be two empty PCLs
+        // (the system class loader is a PCL)
+        assertEquals(Map.of(jar.getPath(), "PCL[];PCL[]"), reporter.loadedDexMapping);
     }
 
     @Test
@@ -141,16 +132,10 @@
         ClassLoader unknownLoader = new ClassLoader(ClassLoader.getSystemClassLoader()) {};
         BaseDexClassLoader cl1 = new PathClassLoader(jar.getPath(), unknownLoader);
 
-        assertEquals(3, reporter.loadedDexPaths.size());
-        assertEquals(3, reporter.classLoaders.size());
-
-        assertEquals(jar.getPath(), reporter.loadedDexPaths.get(0));
-        assertEquals(cl1, reporter.classLoaders.get(0));
-
-        assertNull(reporter.loadedDexPaths.get(1));
-        assertEquals(unknownLoader, reporter.classLoaders.get(1));
-
-        assertEquals(ClassLoader.getSystemClassLoader(), reporter.classLoaders.get(2));
+        // Verify the dex path gets reported, but with no class loader context due to the foreign
+        // class loader.
+        assertEquals(Map.of(jar.getPath(), "=UnsupportedClassLoaderContext="),
+                reporter.loadedDexMapping);
     }
 
     @Test
@@ -158,8 +143,7 @@
         BaseDexClassLoader cl1 = new PathClassLoader(jar.getPath(),
             ClassLoader.getSystemClassLoader());
 
-        assertEquals(2, reporter.loadedDexPaths.size());
-        assertEquals(2, reporter.classLoaders.size());
+        assertEquals(Map.of(jar.getPath(), "PCL[];PCL[]"), reporter.loadedDexMapping);
 
         // Check we don't report after the reporter is unregistered.
         unregisterReporter();
@@ -169,8 +153,67 @@
         BaseDexClassLoader cl2 = new PathClassLoader(jar.getPath(), pcl);
 
         // Verify nothing reported
-        assertEquals(0, reporter.loadedDexPaths.size());
-        assertEquals(0, reporter.classLoaders.size());
+        assertEquals(Map.<String, String>of(), reporter.loadedDexMapping);
+    }
+
+    @Test
+    public void testReporting_multipleJars() throws Exception {
+        // Load the jar file using a PathClassLoader.
+        BaseDexClassLoader cl1 = new PathClassLoader(
+            String.join(File.pathSeparator, jar.getPath(), jar2.getPath()),
+            ClassLoader.getSystemClassLoader());
+
+        // The first class loader context should be two empty PCLs (the system class loader is a
+        // PCL) and the second should be the same, but the bottom classloader contains the first
+        // jar.
+        assertEquals(Map.of(jar.getPath(), "PCL[];PCL[]",
+                            jar2.getPath(), "PCL[" + jar.getPath() + "];PCL[]"),
+                reporter.loadedDexMapping);
+    }
+
+    @Test
+    public void testReporting_withSharedLibraries() throws Exception {
+        final ClassLoader parent = ClassLoader.getSystemClassLoader();
+        final ClassLoader sharedLoaders[] = new ClassLoader[] {
+            new PathClassLoader(jar2.getPath(), /* librarySearchPath */ null, parent),
+        };
+        // Reset so we don't get load reports from creating the shared library CL
+        reporter.reset();
+
+        BaseDexClassLoader bdcl = new PathClassLoader(jar.getPath(), null, parent, sharedLoaders);
+
+        // Verify the loaded dex file contains jar2 encoded as a shared library in its encoded class
+        // loader context.
+        assertEquals(
+                Map.of(jar.getPath(), "PCL[]{PCL[" + jar2.getPath() + "];PCL[]};PCL[]"),
+                reporter.loadedDexMapping);
+    }
+
+    @Test
+    public void testReporting_multipleJars_withSharedLibraries() throws Exception {
+        final ClassLoader parent = ClassLoader.getSystemClassLoader();
+        final String sharedJarPath = resourcesMap.get("parent.jar").getAbsolutePath();
+        final ClassLoader sharedLoaders[] = new ClassLoader[] {
+            new PathClassLoader(sharedJarPath, /* librarySearchPath */ null, parent),
+        };
+        // Reset so we don't get load reports from creating the shared library CL
+        reporter.reset();
+
+        BaseDexClassLoader bdcl = new PathClassLoader(
+                String.join(File.pathSeparator, jar.getPath(), jar2.getPath()),
+                null, parent, sharedLoaders);
+
+        final String contextSuffix = "{PCL[" + sharedJarPath + "];PCL[]};PCL[]";
+
+        assertEquals(Map.of(jar.getPath(), "PCL[]" + contextSuffix,
+                            jar2.getPath(), "PCL[" + jar.getPath() + "]" + contextSuffix),
+                reporter.loadedDexMapping);
+    }
+
+    @Test
+    public void testReporting_emptyPath() throws Exception {
+        BaseDexClassLoader cl1 = new PathClassLoader("", ClassLoader.getSystemClassLoader());
+        assertEquals(Map.<String, String>of(), reporter.loadedDexMapping);
     }
 
     /* package */ static List<String> readResources(ClassLoader cl, String resourceName)
diff --git a/mmodules/core_platform_api/api/platform/current-api.txt b/mmodules/core_platform_api/api/platform/current-api.txt
index 707e3a3..1ca16bc 100644
--- a/mmodules/core_platform_api/api/platform/current-api.txt
+++ b/mmodules/core_platform_api/api/platform/current-api.txt
@@ -535,7 +535,7 @@
   }
 
   public static interface BaseDexClassLoader.Reporter {
-    method public void report(java.util.List<java.lang.ClassLoader>, java.util.List<java.lang.String>);
+    method public void report(java.util.Map<java.lang.String,java.lang.String>);
   }
 
   public final class BlockGuard {