Merge "Load in-memory dex into a single DexFile/Element object"
diff --git a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
index 663a7c8..9cf5f80 100644
--- a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
+++ b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
@@ -172,8 +172,9 @@
      */
     public BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) {
         super(parent);
-        this.pathList = new DexPathList(this, dexFiles, librarySearchPath);
         this.sharedLibraryLoaders = null;
+        this.pathList = new DexPathList(this, librarySearchPath);
+        this.pathList.initByteBufferDexPath(dexFiles);
     }
 
     @Override
diff --git a/dalvik/src/main/java/dalvik/system/DexFile.java b/dalvik/src/main/java/dalvik/system/DexFile.java
index 535257f..8d32931 100644
--- a/dalvik/src/main/java/dalvik/system/DexFile.java
+++ b/dalvik/src/main/java/dalvik/system/DexFile.java
@@ -102,15 +102,16 @@
      * @param elements
      *            the temporary dex path list elements from DexPathList.makeElements
      */
-    DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
+    DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements)
+            throws IOException {
         mCookie = openDexFile(fileName, null, 0, loader, elements);
         mInternalCookie = mCookie;
         mFileName = fileName;
         //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
     }
 
-    DexFile(ByteBuffer buf) throws IOException {
-        mCookie = openInMemoryDexFile(buf);
+    DexFile(ByteBuffer[] bufs) throws IOException {
+        mCookie = openInMemoryDexFiles(bufs);
         mInternalCookie = mCookie;
         mFileName = null;
     }
@@ -369,16 +370,23 @@
                                  elements);
     }
 
-    private static Object openInMemoryDexFile(ByteBuffer buf) throws IOException {
-        if (buf.isDirect()) {
-            return createCookieWithDirectBuffer(buf, buf.position(), buf.limit());
-        } else {
-            return createCookieWithArray(buf.array(), buf.position(), buf.limit());
+    private static Object openInMemoryDexFiles(ByteBuffer[] bufs) throws IOException {
+        // Preprocess the ByteBuffers for openInMemoryDexFilesNative. We extract
+        // the backing array (non-direct buffers only) and start/end positions
+        // so that the native method does not have to call Java methods anymore.
+        byte[][] arrays = new byte[bufs.length][];
+        int[] starts = new int[bufs.length];
+        int[] ends = new int[bufs.length];
+        for (int i = 0; i < bufs.length; ++i) {
+            arrays[i] = bufs[i].isDirect() ? null : bufs[i].array();
+            starts[i] = bufs[i].position();
+            ends[i] = bufs[i].limit();
         }
+        return openInMemoryDexFilesNative(bufs, arrays, starts, ends);
     }
 
-    private static native Object createCookieWithDirectBuffer(ByteBuffer buf, int start, int end);
-    private static native Object createCookieWithArray(byte[] buf, int start, int end);
+    private static native Object openInMemoryDexFilesNative(ByteBuffer[] bufs, byte[][] arrays,
+            int[] starts, int[] ends);
 
     /*
      * Returns true if the dex file is backed by a valid oat file.
diff --git a/dalvik/src/main/java/dalvik/system/DexPathList.java b/dalvik/src/main/java/dalvik/system/DexPathList.java
index 6340b77..fc57dc0 100644
--- a/dalvik/src/main/java/dalvik/system/DexPathList.java
+++ b/dalvik/src/main/java/dalvik/system/DexPathList.java
@@ -49,8 +49,10 @@
  *
  * <p>This class also contains methods to use these lists to look up
  * classes and resources.</p>
+ *
+ * @hide
  */
-/*package*/ final class DexPathList {
+public final class DexPathList {
     private static final String DEX_SUFFIX = ".dex";
     private static final String zipSeparator = "!/";
 
@@ -99,33 +101,16 @@
      *
      * @param dexFiles the bytebuffers containing the dex files that we should load classes from.
      */
-    public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles,
-            String librarySearchPath) {
+    public DexPathList(ClassLoader definingContext, String librarySearchPath) {
         if (definingContext == null) {
             throw new NullPointerException("definingContext == null");
         }
-        if (dexFiles == null) {
-            throw new NullPointerException("dexFiles == null");
-        }
-        if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) {
-            throw new NullPointerException("dexFiles contains a null Buffer!");
-        }
 
         this.definingContext = definingContext;
-
         this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
         this.systemNativeLibraryDirectories =
                 splitPaths(System.getProperty("java.library.path"), true);
         this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
-
-        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
-        this.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions);
-        if (suppressedExceptions.size() > 0) {
-            this.dexElementsSuppressedExceptions =
-                    suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
-        } else {
-            dexElementsSuppressedExceptions = null;
-        }
     }
 
     /**
@@ -261,6 +246,41 @@
     }
 
     /**
+     * For InMemoryDexClassLoader. Initializes {@code dexElements} with dex files
+     * loaded from {@code dexFiles} buffers.
+     *
+     * @param dexFiles ByteBuffers containing raw dex data. Apks are not supported.
+     */
+    /* package */ void initByteBufferDexPath(ByteBuffer[] dexFiles) {
+        if (dexFiles == null) {
+            throw new NullPointerException("dexFiles == null");
+        }
+        if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) {
+            throw new NullPointerException("dexFiles contains a null Buffer!");
+        }
+        if (dexElements != null || dexElementsSuppressedExceptions != null) {
+            throw new IllegalStateException("Should only be called once");
+        }
+
+        final List<IOException> suppressedExceptions = new ArrayList<IOException>();
+
+        try {
+            Element[] null_elements = null;
+            DexFile dex = new DexFile(dexFiles);
+            dexElements = new Element[] { new Element(dex) };
+        } catch (IOException suppressed) {
+            System.logE("Unable to load dex files", suppressed);
+            suppressedExceptions.add(suppressed);
+            dexElements = new Element[0];
+        }
+
+        if (suppressedExceptions.size() > 0) {
+            dexElementsSuppressedExceptions = suppressedExceptions.toArray(
+                    new IOException[suppressedExceptions.size()]);
+        }
+    }
+
+    /**
      * Splits the given dex path string into elements using the path
      * separator, pruning out any elements that do not refer to existing
      * and readable files.
@@ -301,14 +321,16 @@
         return result;
     }
 
+    // This method is not used anymore. Kept around only because there are many legacy users of it.
+    @SuppressWarnings("unused")
     @UnsupportedAppUsage
-    private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
+    public static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
             List<IOException> suppressedExceptions) {
         Element[] elements = new Element[dexFiles.length];
         int elementPos = 0;
         for (ByteBuffer buf : dexFiles) {
             try {
-                DexFile dex = new DexFile(buf);
+                DexFile dex = new DexFile(new ByteBuffer[] { buf });
                 elements[elementPos++] = new Element(dex);
             } catch (IOException suppressed) {
                 System.logE("Unable to load dex file: " + buf, suppressed);
diff --git a/luni/src/test/java/libcore/dalvik/system/InMemoryDexClassLoaderTest.java b/luni/src/test/java/libcore/dalvik/system/InMemoryDexClassLoaderTest.java
index ef87ee5..1012c9f 100644
--- a/luni/src/test/java/libcore/dalvik/system/InMemoryDexClassLoaderTest.java
+++ b/luni/src/test/java/libcore/dalvik/system/InMemoryDexClassLoaderTest.java
@@ -24,11 +24,13 @@
 import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.nio.file.Files;
+import java.util.ArrayList;
 import libcore.io.Streams;
 import junit.framework.TestCase;
 
 import dalvik.system.BaseDexClassLoader;
 import dalvik.system.InMemoryDexClassLoader;
+import dalvik.system.DexPathList;
 
 /**
  * Tests for the class {@link InMemoryDexClassLoader}.
@@ -424,6 +426,20 @@
         classLoader.loadClass("test.TestMethods");
     }
 
+    /**
+     * DexPathList.makeInMemoryDexElements() is a legacy code path not used by
+     * InMemoryDexClassLoader anymore but heavily used by 3p apps. Test that it still works.
+     */
+    public void testMakeInMemoryDexElements() throws Exception {
+        ArrayList<IOException> exceptions = new ArrayList<>();
+        Object[] elements = DexPathList.makeInMemoryDexElements(
+                new ByteBuffer[] { readFileToByteBufferDirect(dex1),
+                                   readFileToByteBufferIndirect(dex2) },
+                exceptions);
+        assertEquals(2, elements.length);
+        assertTrue(exceptions.isEmpty());
+    }
+
     private static File makeEmptyFile(File directory, String name) throws IOException {
         assertTrue(directory.mkdirs());
         File result = new File(directory, name);