Open in-memory dex files into a single DexFile object

Refactor the logic of opening dex from ByteBuffers so that all buffers
are opened at once into the same DexFile/Element object. This effectively
enables multidex in InMemoryDexClassLoader and will make it possible to
generate a single vdex file for the whole class loader instead of one
vdex per dex ByteBuffer.

Bug: 72131483
Test: art/tools/run-libcore-tests.sh
Change-Id: I5fb939072812745a2df0f323c502a1cf92069ed5
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 4f9cf70..feaf619 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -28,7 +28,7 @@
 #include "base/utils.h"
 #include "base/zip_archive.h"
 #include "class_linker.h"
-#include <class_loader_context.h>
+#include "class_loader_context.h"
 #include "common_throws.h"
 #include "compiler_filter.h"
 #include "dex/art_dex_file_loader.h"
@@ -214,54 +214,79 @@
   return dex_file.release();
 }
 
-static jobject CreateSingleDexFileCookie(JNIEnv* env, MemMap&& data) {
-  std::unique_ptr<const DexFile> dex_file(CreateDexFile(env, std::move(data)));
-  if (dex_file.get() == nullptr) {
-    DCHECK(env->ExceptionCheck());
-    return nullptr;
+struct ScopedIntArrayAccessor {
+ public:
+  ScopedIntArrayAccessor(JNIEnv* env, jintArray arr) : env_(env), array_(arr) {
+    elements_ = env_->GetIntArrayElements(array_, /* isCopy= */ nullptr);
+    CHECK(elements_ != nullptr);
   }
+
+  ~ScopedIntArrayAccessor() {
+    env_->ReleaseIntArrayElements(array_, elements_, JNI_ABORT);
+  }
+
+  jint Get(jsize index) const { return elements_[index]; }
+
+ private:
+  JNIEnv* env_;
+  jintArray array_;
+  jint* elements_;
+};
+
+static jobject DexFile_openInMemoryDexFilesNative(JNIEnv* env,
+                                                  jclass,
+                                                  jobjectArray buffers,
+                                                  jobjectArray arrays,
+                                                  jintArray jstarts,
+                                                  jintArray jends) {
+  jsize buffers_length = env->GetArrayLength(buffers);
+  CHECK_EQ(buffers_length, env->GetArrayLength(arrays));
+  CHECK_EQ(buffers_length, env->GetArrayLength(jstarts));
+  CHECK_EQ(buffers_length, env->GetArrayLength(jends));
+
+  ScopedIntArrayAccessor starts(env, jstarts);
+  ScopedIntArrayAccessor ends(env, jends);
+
+  // Allocate memory for dex files and copy data from ByteBuffers.
   std::vector<std::unique_ptr<const DexFile>> dex_files;
-  dex_files.push_back(std::move(dex_file));
-  return ConvertDexFilesToJavaArray(env, nullptr, dex_files);
-}
+  dex_files.reserve(buffers_length);
+  for (jsize i = 0; i < buffers_length; ++i) {
+    jobject buffer = env->GetObjectArrayElement(buffers, i);
+    jbyteArray array = reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(arrays, i));
+    jint start = starts.Get(i);
+    jint end = ends.Get(i);
 
-static jobject DexFile_createCookieWithDirectBuffer(JNIEnv* env,
-                                                    jclass,
-                                                    jobject buffer,
-                                                    jint start,
-                                                    jint end) {
-  uint8_t* base_address = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
-  if (base_address == nullptr) {
-    ScopedObjectAccess soa(env);
-    ThrowWrappedIOException("dexFileBuffer not direct");
-    return nullptr;
+    MemMap dex_data = AllocateDexMemoryMap(env, start, end);
+    if (!dex_data.IsValid()) {
+      DCHECK(Thread::Current()->IsExceptionPending());
+      return nullptr;
+    }
+
+    if (array == nullptr) {
+      // Direct ByteBuffer
+      uint8_t* base_address = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
+      if (base_address == nullptr) {
+        ScopedObjectAccess soa(env);
+        ThrowWrappedIOException("dexFileBuffer not direct");
+        return nullptr;
+      }
+      size_t length = static_cast<size_t>(end - start);
+      memcpy(dex_data.Begin(), base_address + start, length);
+    } else {
+      // ByteBuffer backed by a byte array
+      jbyte* destination = reinterpret_cast<jbyte*>(dex_data.Begin());
+      env->GetByteArrayRegion(array, start, end - start, destination);
+    }
+
+    std::unique_ptr<const DexFile> dex_file(CreateDexFile(env, std::move(dex_data)));
+    if (dex_file == nullptr) {
+      DCHECK(env->ExceptionCheck());
+      return nullptr;
+    }
+    dex_files.push_back(std::move(dex_file));
   }
 
-  MemMap dex_mem_map = AllocateDexMemoryMap(env, start, end);
-  if (!dex_mem_map.IsValid()) {
-    DCHECK(Thread::Current()->IsExceptionPending());
-    return nullptr;
-  }
-
-  size_t length = static_cast<size_t>(end - start);
-  memcpy(dex_mem_map.Begin(), base_address + start, length);
-  return CreateSingleDexFileCookie(env, std::move(dex_mem_map));
-}
-
-static jobject DexFile_createCookieWithArray(JNIEnv* env,
-                                             jclass,
-                                             jbyteArray buffer,
-                                             jint start,
-                                             jint end) {
-  MemMap dex_mem_map = AllocateDexMemoryMap(env, start, end);
-  if (!dex_mem_map.IsValid()) {
-    DCHECK(Thread::Current()->IsExceptionPending());
-    return nullptr;
-  }
-
-  auto destination = reinterpret_cast<jbyte*>(dex_mem_map.Begin());
-  env->GetByteArrayRegion(buffer, start, end - start, destination);
-  return CreateSingleDexFileCookie(env, std::move(dex_mem_map));
+  return ConvertDexFilesToJavaArray(env, /* oat_file= */ nullptr, dex_files);
 }
 
 // TODO(calin): clean up the unused parameters (here and in libcore).
@@ -338,8 +363,8 @@
     for (const DexFile* dex_file : dex_files) {
       if (dex_file != nullptr) {
         RemoveNativeDebugInfoForDex(soa.Self(), dex_file);
-        // Only delete the dex file if the dex cache is not found to prevent runtime crashes if there
-        // are calls to DexFile.close while the ART DexFile is still in use.
+        // Only delete the dex file if the dex cache is not found to prevent runtime crashes
+        // if there are calls to DexFile.close while the ART DexFile is still in use.
         if (!class_linker->IsDexFileRegistered(soa.Self(), *dex_file)) {
           // Clear the element in the array so that we can call close again.
           long_dex_files->Set(i, 0);
@@ -862,9 +887,12 @@
                 "Ljava/lang/ClassLoader;"
                 "[Ldalvik/system/DexPathList$Element;"
                 ")Ljava/lang/Object;"),
-  NATIVE_METHOD(DexFile, createCookieWithDirectBuffer,
-                "(Ljava/nio/ByteBuffer;II)Ljava/lang/Object;"),
-  NATIVE_METHOD(DexFile, createCookieWithArray, "([BII)Ljava/lang/Object;"),
+  NATIVE_METHOD(DexFile, openInMemoryDexFilesNative,
+                "([Ljava/nio/ByteBuffer;"
+                "[[B"
+                "[I"
+                "[I"
+                ")Ljava/lang/Object;"),
   NATIVE_METHOD(DexFile, isValidCompilerFilter, "(Ljava/lang/String;)Z"),
   NATIVE_METHOD(DexFile, isProfileGuidedCompilerFilter, "(Ljava/lang/String;)Z"),
   NATIVE_METHOD(DexFile,