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_linker.cc b/runtime/class_linker.cc
index a19085f..1eae661 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -45,6 +45,7 @@
 #include "base/value_object.h"
 #include "cha.h"
 #include "class_linker-inl.h"
+#include "class_loader_utils.h"
 #include "class_table-inl.h"
 #include "compiler_callbacks.h"
 #include "debugger.h"
@@ -2485,27 +2486,6 @@
   return ClassPathEntry(nullptr, nullptr);
 }
 
-// Returns true if the given class loader is either a PathClassLoader or a DexClassLoader.
-// (they both have the same behaviour with respect to class lockup order)
-static bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                   Handle<mirror::ClassLoader> class_loader)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  mirror::Class* class_loader_class = class_loader->GetClass();
-  return
-      (class_loader_class ==
-          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) ||
-      (class_loader_class ==
-          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader));
-}
-
-static bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                      Handle<mirror::ClassLoader> class_loader)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  mirror::Class* class_loader_class = class_loader->GetClass();
-  return class_loader_class ==
-      soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader);
-}
-
 bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
                                                 Thread* self,
                                                 const char* descriptor,
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
 
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index 9727a3b..81c8903 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -22,7 +22,9 @@
 
 #include "arch/instruction_set.h"
 #include "base/dchecked_vector.h"
-#include "jni.h"
+#include "handle_scope.h"
+#include "mirror/class_loader.h"
+#include "scoped_thread_state_change.h"
 
 namespace art {
 
@@ -35,6 +37,8 @@
   // Creates an empty context (with no class loaders).
   ClassLoaderContext();
 
+  ~ClassLoaderContext();
+
   // Opens requested class path files and appends them to ClassLoaderInfo::opened_dex_files.
   // If the dex files have been stripped, the method opens them from their oat files which are added
   // to ClassLoaderInfo::opened_oat_files. The 'classpath_dir' argument specifies the directory to
@@ -93,6 +97,16 @@
       std::vector<uint32_t>* out_checksums,
       bool* out_is_special_shared_library);
 
+  // Creates a context for the given class_loader and dex_elements.
+  // The method will walk the parent chain starting from `class_loader` and add their dex files
+  // to the current class loaders chain. The `dex_elements` will be added at the end of the
+  // classpath belonging to the `class_loader` argument.
+  // The ownership of the opened dex files will be retained by the given `class_loader`.
+  // If there are errors in processing the class loader chain (e.g. unsupported elements) the
+  // method returns null.
+  static std::unique_ptr<ClassLoaderContext> CreateContextForClassLoader(jobject class_loader,
+                                                                         jobjectArray dex_elements);
+
  private:
   enum ClassLoaderType {
     kInvalidClassLoader = 0,
@@ -118,6 +132,13 @@
     explicit ClassLoaderInfo(ClassLoaderType cl_type) : type(cl_type) {}
   };
 
+  // Constructs an empty context.
+  // `owns_the_dex_files` specifies whether or not the context will own the opened dex files
+  // present in the class loader chain. If `owns_the_dex_files` is true then OpenDexFiles cannot
+  // be called on this context (dex_files_open_attempted_ and dex_files_open_result_ will be set
+  // to true as well)
+  explicit ClassLoaderContext(bool owns_the_dex_files);
+
   // Reads the class loader spec in place and returns true if the spec is valid and the
   // compilation context was constructed.
   bool Parse(const std::string& spec, bool parse_checksums = false);
@@ -129,6 +150,19 @@
                             ClassLoaderType class_loader_type,
                             bool parse_checksums = false);
 
+  // CHECKs that the dex files were opened (OpenDexFiles was called and set dex_files_open_result_
+  // to true). Aborts if not. The `calling_method` is used in the log message to identify the source
+  // of the call.
+  void CheckDexFilesOpened(const std::string& calling_method) const;
+
+  // 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.
+  bool AddInfoToContextFromClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+                                       Handle<mirror::ClassLoader> class_loader,
+                                       Handle<mirror::ObjectArray<mirror::Object>> dex_elements)
+  REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Extracts the class loader type from the given spec.
   // Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not
   // recognized.
@@ -138,9 +172,6 @@
   // The returned format can be used when parsing a context spec.
   static const char* GetClassLoaderTypeName(ClassLoaderType type);
 
-  // CHECKs that the dex files were opened (OpenDexFiles was called). Aborts if not.
-  void CheckDexFilesOpened(const std::string& calling_method) const;
-
   // The class loader chain represented as a vector.
   // The parent of class_loader_chain_[i] is class_loader_chain_[i++].
   // The parent of the last element is assumed to be the boot class loader.
@@ -158,6 +189,13 @@
   // The result of the last OpenDexFiles() operation.
   bool dex_files_open_result_;
 
+  // Whether or not the context owns the opened dex and oat files.
+  // If true, the opened dex files will be de-allocated when the context is destructed.
+  // If false, the objects will continue to be alive.
+  // Note that for convenience the the opened dex/oat files are stored as unique pointers
+  // which will release their ownership in the destructor based on this flag.
+  const bool owns_the_dex_files_;
+
   friend class ClassLoaderContextTest;
 
   DISALLOW_COPY_AND_ASSIGN(ClassLoaderContext);
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index 03eb0e4..50905c4 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -45,18 +45,32 @@
 
   void VerifyClassLoaderPCL(ClassLoaderContext* context,
                             size_t index,
-                            std::string classpath) {
+                            const std::string& classpath) {
     VerifyClassLoaderInfo(
         context, index, ClassLoaderContext::kPathClassLoader, classpath);
   }
 
   void VerifyClassLoaderDLC(ClassLoaderContext* context,
                             size_t index,
-                            std::string classpath) {
+                            const std::string& classpath) {
     VerifyClassLoaderInfo(
         context, index, ClassLoaderContext::kDelegateLastClassLoader, classpath);
   }
 
+  void VerifyClassLoaderPCLFromTestDex(ClassLoaderContext* context,
+                                       size_t index,
+                                       const std::string& test_name) {
+    VerifyClassLoaderFromTestDex(
+        context, index, ClassLoaderContext::kPathClassLoader, test_name);
+  }
+
+  void VerifyClassLoaderDLCFromTestDex(ClassLoaderContext* context,
+                                       size_t index,
+                                       const std::string& test_name) {
+    VerifyClassLoaderFromTestDex(
+        context, index, ClassLoaderContext::kDelegateLastClassLoader, test_name);
+  }
+
   void VerifyOpenDexFiles(
       ClassLoaderContext* context,
       size_t index,
@@ -83,11 +97,23 @@
     }
   }
 
+  std::unique_ptr<ClassLoaderContext> CreateContextForClassLoader(jobject class_loader) {
+    return ClassLoaderContext::CreateContextForClassLoader(class_loader, nullptr);
+  }
+
+  void VerifyContextForClassLoader(ClassLoaderContext* context) {
+    ASSERT_TRUE(context != nullptr);
+    ASSERT_TRUE(context->dex_files_open_attempted_);
+    ASSERT_TRUE(context->dex_files_open_result_);
+    ASSERT_FALSE(context->owns_the_dex_files_);
+    ASSERT_FALSE(context->special_shared_library_);
+  }
+
  private:
   void VerifyClassLoaderInfo(ClassLoaderContext* context,
                              size_t index,
                              ClassLoaderContext::ClassLoaderType type,
-                             std::string classpath) {
+                             const std::string& classpath) {
     ASSERT_TRUE(context != nullptr);
     ASSERT_GT(context->class_loader_chain_.size(), index);
     ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index];
@@ -96,6 +122,18 @@
     Split(classpath, ':', &expected_classpath);
     ASSERT_EQ(expected_classpath, info.classpath);
   }
+
+  void VerifyClassLoaderFromTestDex(ClassLoaderContext* context,
+                                    size_t index,
+                                    ClassLoaderContext::ClassLoaderType type,
+                                    const std::string& test_name) {
+    std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles(test_name.c_str());
+    std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files;
+    all_dex_files.push_back(&dex_files);
+
+    VerifyClassLoaderInfo(context, index, type, GetTestDexFileName(test_name.c_str()));
+    VerifyOpenDexFiles(context, index, all_dex_files);
+  }
 };
 
 TEST_F(ClassLoaderContextTest, ParseValidContextPCL) {
@@ -334,4 +372,34 @@
   ASSERT_TRUE(checksums.empty());
 }
 
+// TODO(calin) add a test which creates the context for a class loader together with dex_elements.
+TEST_F(ClassLoaderContextTest, CreateContextForClassLoader) {
+  // The chain is
+  //    ClassLoaderA (PathClassLoader)
+  //       ^
+  //       |
+  //    ClassLoaderB (DelegateLastClassLoader)
+  //       ^
+  //       |
+  //    ClassLoaderC (PathClassLoader)
+  //       ^
+  //       |
+  //    ClassLoaderD (DelegateLastClassLoader)
+
+  jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
+  jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a);
+  jobject class_loader_c = LoadDexInPathClassLoader("ForClassLoaderC", class_loader_b);
+  jobject class_loader_d = LoadDexInDelegateLastClassLoader("ForClassLoaderD", class_loader_c);
+
+  std::unique_ptr<ClassLoaderContext> context = CreateContextForClassLoader(class_loader_d);
+
+  VerifyContextForClassLoader(context.get());
+  VerifyContextSize(context.get(), 4);
+
+  VerifyClassLoaderDLCFromTestDex(context.get(), 0, "ForClassLoaderD");
+  VerifyClassLoaderPCLFromTestDex(context.get(), 1, "ForClassLoaderC");
+  VerifyClassLoaderDLCFromTestDex(context.get(), 2, "ForClassLoaderB");
+  VerifyClassLoaderPCLFromTestDex(context.get(), 3, "ForClassLoaderA");
+}
+
 }  // namespace art
diff --git a/runtime/class_loader_utils.h b/runtime/class_loader_utils.h
new file mode 100644
index 0000000..d160a51
--- /dev/null
+++ b/runtime/class_loader_utils.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_CLASS_LOADER_UTILS_H_
+#define ART_RUNTIME_CLASS_LOADER_UTILS_H_
+
+#include "handle_scope.h"
+#include "mirror/class_loader.h"
+#include "scoped_thread_state_change-inl.h"
+#include "well_known_classes.h"
+
+namespace art {
+
+// Returns true if the given class loader is either a PathClassLoader or a DexClassLoader.
+// (they both have the same behaviour with respect to class lockup order)
+static bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+                                   Handle<mirror::ClassLoader> class_loader)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  mirror::Class* class_loader_class = class_loader->GetClass();
+  return
+      (class_loader_class ==
+          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) ||
+      (class_loader_class ==
+          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader));
+}
+
+static bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+                                      Handle<mirror::ClassLoader> class_loader)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  mirror::Class* class_loader_class = class_loader->GetClass();
+  return class_loader_class ==
+      soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader);
+}
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_CLASS_LOADER_UTILS_H_