Create a class loader context starting from an existing ClassLoader

Extend ClassLoaderContext to be able to generate a context from an
existing class loader.

This will be used in extending the duplicate class check to cover
DelegateLastClassLoaders.

Most of the functionality is migrated from OatFileManager with some
cleanups consisting of extra docs and more conservative checks on the
integrity of the class loader chain.

Test: m test-art-host
Bug: 38138251
Change-Id: If7c18cb75bfc9e6784676f96a666bf13b04c8b8b
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 2bed1d5..a6e5c21 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -16,14 +16,20 @@
 
 #include "class_loader_context.h"
 
+#include "art_field-inl.h"
 #include "base/dchecked_vector.h"
 #include "base/stl_util.h"
 #include "class_linker.h"
+#include "class_loader_utils.h"
 #include "dex_file.h"
+#include "handle_scope-inl.h"
+#include "jni_internal.h"
 #include "oat_file_assistant.h"
+#include "obj_ptr-inl.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
+#include "well_known_classes.h"
 
 namespace art {
 
@@ -38,7 +44,29 @@
 ClassLoaderContext::ClassLoaderContext()
     : special_shared_library_(false),
       dex_files_open_attempted_(false),
-      dex_files_open_result_(false) {}
+      dex_files_open_result_(false),
+      owns_the_dex_files_(false) {}
+
+ClassLoaderContext::ClassLoaderContext(bool owns_the_dex_files)
+    : special_shared_library_(false),
+      dex_files_open_attempted_(true),
+      dex_files_open_result_(true),
+      owns_the_dex_files_(owns_the_dex_files) {}
+
+ClassLoaderContext::~ClassLoaderContext() {
+  if (!owns_the_dex_files_) {
+    // If the context does not own the dex/oat files release the unique pointers to
+    // make sure we do not de-allocate them.
+    for (ClassLoaderInfo& info : class_loader_chain_) {
+      for (std::unique_ptr<OatFile>& oat_file : info.opened_oat_files) {
+        oat_file.release();
+      }
+      for (std::unique_ptr<const DexFile>& dex_file : info.opened_dex_files) {
+        dex_file.release();
+      }
+    }
+  }
+}
 
 std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Create(const std::string& spec) {
   std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext());
@@ -356,5 +384,221 @@
   *out_checksums = info.checksums;
   return true;
 }
+
+// Collects the dex files from the give Java dex_file object. Only the dex files with
+// at least 1 class are collected. If a null java_dex_file is passed this method does nothing.
+static bool CollectDexFilesFromJavaDexFile(ObjPtr<mirror::Object> java_dex_file,
+                                           ArtField* const cookie_field,
+                                           std::vector<const DexFile*>* out_dex_files)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (java_dex_file == nullptr) {
+    return true;
+  }
+  // On the Java side, the dex files are stored in the cookie field.
+  mirror::LongArray* long_array = cookie_field->GetObject(java_dex_file)->AsLongArray();
+  if (long_array == nullptr) {
+    // This should never happen so log a warning.
+    LOG(ERROR) << "Unexpected null cookie";
+    return false;
+  }
+  int32_t long_array_size = long_array->GetLength();
+  // Index 0 from the long array stores the oat file. The dex files start at index 1.
+  for (int32_t j = 1; j < long_array_size; ++j) {
+    const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>(
+        long_array->GetWithoutChecks(j)));
+    if (cp_dex_file != nullptr && cp_dex_file->NumClassDefs() > 0) {
+      // TODO(calin): It's unclear why the dex files with no classes are skipped here and when
+      // cp_dex_file can be null.
+      out_dex_files->push_back(cp_dex_file);
+    }
+  }
+  return true;
+}
+
+// Collects all the dex files loaded by the given class loader.
+// Returns true for success or false if an unexpected state is discovered (e.g. a null dex cookie,
+// a null list of dex elements or a null dex element).
+static bool CollectDexFilesFromSupportedClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+                                                    Handle<mirror::ClassLoader> class_loader,
+                                                    std::vector<const DexFile*>* out_dex_files)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+  CHECK(IsPathOrDexClassLoader(soa, class_loader) || IsDelegateLastClassLoader(soa, class_loader));
+
+  // All supported class loaders inherit from BaseDexClassLoader.
+  // We need to get the DexPathList and loop through it.
+  ArtField* const cookie_field =
+      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
+  ArtField* const dex_file_field =
+      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+  ObjPtr<mirror::Object> dex_path_list =
+      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)->
+          GetObject(class_loader.Get());
+  CHECK(cookie_field != nullptr);
+  CHECK(dex_file_field != nullptr);
+  if (dex_path_list == nullptr) {
+    // This may be null if the current class loader is under construction and it does not
+    // have its fields setup yet.
+    return true;
+  }
+  // DexPathList has an array dexElements of Elements[] which each contain a dex file.
+  ObjPtr<mirror::Object> dex_elements_obj =
+      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
+          GetObject(dex_path_list);
+  // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look
+  // at the mCookie which is a DexFile vector.
+  if (dex_elements_obj == nullptr) {
+    // TODO(calin): It's unclear if we should just assert here. For now be prepared for the worse
+    // and assume we have no elements.
+    return true;
+  } else {
+    StackHandleScope<1> hs(soa.Self());
+    Handle<mirror::ObjectArray<mirror::Object>> dex_elements(
+        hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>()));
+    for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
+      mirror::Object* element = dex_elements->GetWithoutChecks(i);
+      if (element == nullptr) {
+        // Should never happen, log an error and break.
+        // TODO(calin): It's unclear if we should just assert here.
+        // This code was propagated to oat_file_manager from the class linker where it would
+        // throw a NPE. For now, return false which will mark this class loader as unsupported.
+        LOG(ERROR) << "Unexpected null in the dex element list";
+        return false;
+      }
+      ObjPtr<mirror::Object> dex_file = dex_file_field->GetObject(element);
+      if (!CollectDexFilesFromJavaDexFile(dex_file, cookie_field, out_dex_files)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool GetDexFilesFromDexElementsArray(
+    ScopedObjectAccessAlreadyRunnable& soa,
+    Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
+    std::vector<const DexFile*>* out_dex_files) REQUIRES_SHARED(Locks::mutator_lock_) {
+  DCHECK(dex_elements != nullptr);
+
+  ArtField* const cookie_field =
+      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
+  ArtField* const dex_file_field =
+      jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+  ObjPtr<mirror::Class> const element_class = soa.Decode<mirror::Class>(
+      WellKnownClasses::dalvik_system_DexPathList__Element);
+  ObjPtr<mirror::Class> const dexfile_class = soa.Decode<mirror::Class>(
+      WellKnownClasses::dalvik_system_DexFile);
+
+  for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
+    mirror::Object* element = dex_elements->GetWithoutChecks(i);
+    // We can hit a null element here because this is invoked with a partially filled dex_elements
+    // array from DexPathList. DexPathList will open each dex sequentially, each time passing the
+    // list of dex files which were opened before.
+    if (element == nullptr) {
+      continue;
+    }
+
+    // We support this being dalvik.system.DexPathList$Element and dalvik.system.DexFile.
+    // TODO(calin): Code caried over oat_file_manager: supporting both classes seem to be
+    // a historical glitch. All the java code opens dex files using an array of Elements.
+    ObjPtr<mirror::Object> dex_file;
+    if (element_class == element->GetClass()) {
+      dex_file = dex_file_field->GetObject(element);
+    } else if (dexfile_class == element->GetClass()) {
+      dex_file = element;
+    } else {
+      LOG(ERROR) << "Unsupported element in dex_elements: "
+                 << mirror::Class::PrettyClass(element->GetClass());
+      return false;
+    }
+
+    if (!CollectDexFilesFromJavaDexFile(dex_file, cookie_field, out_dex_files)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Adds the `class_loader` info to the `context`.
+// The dex file present in `dex_elements` array (if not null) will be added at the end of
+// the classpath.
+// This method is recursive (w.r.t. the class loader parent) and will stop once it reaches the
+// BootClassLoader. Note that the class loader chain is expected to be short.
+bool ClassLoaderContext::AddInfoToContextFromClassLoader(
+      ScopedObjectAccessAlreadyRunnable& soa,
+      Handle<mirror::ClassLoader> class_loader,
+      Handle<mirror::ObjectArray<mirror::Object>> dex_elements)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (ClassLinker::IsBootClassLoader(soa, class_loader.Get())) {
+    // Nothing to do for the boot class loader as we don't add its dex files to the context.
+    return true;
+  }
+
+  ClassLoaderContext::ClassLoaderType type;
+  if (IsPathOrDexClassLoader(soa, class_loader)) {
+    type = kPathClassLoader;
+  } else if (IsDelegateLastClassLoader(soa, class_loader)) {
+    type = kDelegateLastClassLoader;
+  } else {
+    LOG(WARNING) << "Unsupported class loader";
+    return false;
+  }
+
+  // Inspect the class loader for its dex files.
+  std::vector<const DexFile*> dex_files_loaded;
+  CollectDexFilesFromSupportedClassLoader(soa, class_loader, &dex_files_loaded);
+
+  // If we have a dex_elements array extract its dex elements now.
+  // This is used in two situations:
+  //   1) when a new ClassLoader is created DexPathList will open each dex file sequentially
+  //      passing the list of already open dex files each time. This ensures that we see the
+  //      correct context even if the ClassLoader under construction is not fully build.
+  //   2) when apk splits are loaded on the fly, the framework will load their dex files by
+  //      appending them to the current class loader. When the new code paths are loaded in
+  //      BaseDexClassLoader, the paths already present in the class loader will be passed
+  //      in the dex_elements array.
+  if (dex_elements != nullptr) {
+    GetDexFilesFromDexElementsArray(soa, dex_elements, &dex_files_loaded);
+  }
+
+  class_loader_chain_.push_back(ClassLoaderContext::ClassLoaderInfo(type));
+  ClassLoaderInfo& info = class_loader_chain_.back();
+  for (const DexFile* dex_file : dex_files_loaded) {
+    info.classpath.push_back(dex_file->GetLocation());
+    info.checksums.push_back(dex_file->GetLocationChecksum());
+    info.opened_dex_files.emplace_back(dex_file);
+  }
+
+  // We created the ClassLoaderInfo for the current loader. Move on to its parent.
+
+  StackHandleScope<1> hs(Thread::Current());
+  Handle<mirror::ClassLoader> parent = hs.NewHandle(class_loader->GetParent());
+
+  // Note that dex_elements array is null here. The elements are considered to be part of the
+  // current class loader and are not passed to the parents.
+  ScopedNullHandle<mirror::ObjectArray<mirror::Object>> null_dex_elements;
+  return AddInfoToContextFromClassLoader(soa, parent, null_dex_elements);
+}
+
+std::unique_ptr<ClassLoaderContext> ClassLoaderContext::CreateContextForClassLoader(
+    jobject class_loader,
+    jobjectArray dex_elements) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<2> hs(soa.Self());
+  Handle<mirror::ClassLoader> h_class_loader =
+      hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
+  Handle<mirror::ObjectArray<mirror::Object>> h_dex_elements =
+      hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(dex_elements));
+
+  CHECK(h_class_loader != nullptr);
+
+  std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext(/*owns_the_dex_files*/ false));
+  if (result->AddInfoToContextFromClassLoader(soa, h_class_loader, h_dex_elements)) {
+    return result;
+  } else {
+    return nullptr;
+  }
+}
+
 }  // namespace art