Add support for processing class loader contexts

Initial support for recognizing the class loader contexts.

In order to correctly compile dex files which at runtime are loaded with
a non-trivial class loader chain we need to make dex2oat aware of the
precise runtime context.

This CL adds the infrastructure to process arbitrary and arbitrary chain
of class loaders. ClassLoaderContext is able to parse a class loader
spec from a string and create the runtime structure based on it.

The integration with dex2oat and oat file assistant will follow up.

The string specification looks like
"PCL[lib1.dex:lib2.dex];DLC[lib3.dex]"

It describes how the class loader chain should be build in order to
ensure classes are resolved during dex2aot as they would be resolved at
runtime. This spec will be encoded in the oat file. If at runtime the
dex file will be loaded in a different context, the oat file will be
rejected.

The chain is interpreted in the natural 'parent order', meaning that
class loader 'i+1' will be the parent of class loader 'i'. The
compilation sources will be added to the classpath of the last class
loader. This allows the compiled dex files to be loaded at runtime in a
class loader that contains other dex files as well (e.g. shared
libraries).

Note that we accept chains for which the source dex files specified
with --dex-file are found in the classpath. In this case the source dex
files will be removed from the any class loader's classpath possibly
resulting in empty class loaders.

* This is the first CL, which adds the infrastructure for processing
a class loader context. Currently it CHECKS that only a single
PathClassLoader is created.

Test: m test-art-host
Bug: 38138251
Change-Id: I312aa12b5732288f3c1df4746b5775a32e0bfb04
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index b6ffcc5..ff4f901 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -104,6 +104,7 @@
 
 ART_GTEST_atomic_method_ref_map_test_DEX_DEPS := Interfaces
 ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit ForClassLoaderA ForClassLoaderB ForClassLoaderC ForClassLoaderD Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode
+ART_GTEST_class_loader_context_test_DEX_DEPS := Main MultiDex MyClass
 ART_GTEST_class_table_test_DEX_DEPS := XandY
 ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex
 ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 0dfc60d..8d15c34 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -48,6 +48,7 @@
         "cha.cc",
         "check_jni.cc",
         "class_linker.cc",
+        "class_loader_context.cc",
         "class_table.cc",
         "code_simulator_container.cc",
         "common_throws.cc",
@@ -542,6 +543,7 @@
         "base/unix_file/fd_file_test.cc",
         "cha_test.cc",
         "class_linker_test.cc",
+        "class_loader_context_test.cc",
         "class_table_test.cc",
         "compiler_filter_test.cc",
         "dex_file_test.cc",
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
new file mode 100644
index 0000000..5cbcd8f
--- /dev/null
+++ b/runtime/class_loader_context.cc
@@ -0,0 +1,285 @@
+/*
+ * 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.
+ */
+
+#include "class_loader_context.h"
+
+#include "base/dchecked_vector.h"
+#include "base/stl_util.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "oat_file_assistant.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread.h"
+
+namespace art {
+
+static constexpr char kPathClassLoaderString[] = "PCL";
+static constexpr char kDelegateLastClassLoaderString[] = "DLC";
+static constexpr char kClassLoaderOpeningMark = '[';
+static constexpr char kClassLoaderClosingMark = ']';
+static constexpr char kClassLoaderSep = ';';
+static constexpr char kClasspathSep = ':';
+
+ClassLoaderContext::ClassLoaderContext()
+    : special_shared_library_(false),
+      dex_files_open_attempted_(false),
+      dex_files_open_result_(false) {}
+
+std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Create(const std::string& spec) {
+  std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext());
+  if (result->Parse(spec)) {
+    return result;
+  } else {
+    return nullptr;
+  }
+}
+
+// The expected format is: "ClassLoaderType1[ClasspathElem1:ClasspathElem2...]".
+bool ClassLoaderContext::ParseClassLoaderSpec(const std::string& class_loader_spec,
+                                              ClassLoaderType class_loader_type) {
+  const char* class_loader_type_str = GetClassLoaderTypeName(class_loader_type);
+  size_t type_str_size = strlen(class_loader_type_str);
+
+  CHECK_EQ(0, class_loader_spec.compare(0, type_str_size, class_loader_type_str));
+
+  // Check the opening and closing markers.
+  if (class_loader_spec[type_str_size] != kClassLoaderOpeningMark) {
+    return false;
+  }
+  if (class_loader_spec[class_loader_spec.length() - 1] != kClassLoaderClosingMark) {
+    return false;
+  }
+
+  // At this point we know the format is ok; continue and extract the classpath.
+  // Note that class loaders with an empty class path are allowed.
+  std::string classpath = class_loader_spec.substr(type_str_size + 1,
+                                                   class_loader_spec.length() - type_str_size - 2);
+
+  class_loader_chain_.push_back(ClassLoaderInfo(class_loader_type));
+  Split(classpath, kClasspathSep, &class_loader_chain_.back().classpath);
+
+  return true;
+}
+
+// Extracts the class loader type from the given spec.
+// Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not
+// recognized.
+ClassLoaderContext::ClassLoaderType
+ClassLoaderContext::ExtractClassLoaderType(const std::string& class_loader_spec) {
+  const ClassLoaderType kValidTypes[] = {kPathClassLoader, kDelegateLastClassLoader};
+  for (const ClassLoaderType& type : kValidTypes) {
+    const char* type_str = GetClassLoaderTypeName(type);
+    if (class_loader_spec.compare(0, strlen(type_str), type_str) == 0) {
+      return type;
+    }
+  }
+  return kInvalidClassLoader;
+}
+
+// The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...
+// ClassLoaderType is either "PCL" (PathClassLoader) or "DLC" (DelegateLastClassLoader).
+// ClasspathElem is the path of dex/jar/apk file.
+bool ClassLoaderContext::Parse(const std::string& spec) {
+  if (spec.empty()) {
+    LOG(ERROR) << "Empty string passed to Parse";
+    return false;
+  }
+  // Stop early if we detect the special shared library, which may be passed as the classpath
+  // for dex2oat when we want to skip the shared libraries check.
+  if (spec == OatFile::kSpecialSharedLibrary) {
+    LOG(INFO) << "The ClassLoaderContext is a special shared library.";
+    special_shared_library_ = true;
+    return true;
+  }
+
+  std::vector<std::string> class_loaders;
+  Split(spec, kClassLoaderSep, &class_loaders);
+
+  for (const std::string& class_loader : class_loaders) {
+    ClassLoaderType type = ExtractClassLoaderType(class_loader);
+    if (type == kInvalidClassLoader) {
+      LOG(ERROR) << "Invalid class loader type: " << class_loader;
+      return false;
+    }
+    if (!ParseClassLoaderSpec(class_loader, type)) {
+      LOG(ERROR) << "Invalid class loader spec: " << class_loader;
+      return false;
+    }
+  }
+  return true;
+}
+
+// Opens requested class path files and appends them to opened_dex_files. If the dex files have
+// been stripped, this opens them from their oat files (which get added to opened_oat_files).
+bool ClassLoaderContext::OpenDexFiles(InstructionSet isa, const std::string& classpath_dir) {
+  CHECK(!dex_files_open_attempted_) << "OpenDexFiles should not be called twice";
+
+  dex_files_open_attempted_ = true;
+  // Assume we can open all dex files. If not, we will set this to false as we go.
+  dex_files_open_result_ = true;
+
+  if (special_shared_library_) {
+    // Nothing to open if the context is a special shared library.
+    return true;
+  }
+
+  // Note that we try to open all dex files even if some fail.
+  // We may get resource-only apks which we cannot load.
+  // TODO(calin): Refine the dex opening interface to be able to tell if an archive contains
+  // no dex files. So that we can distinguish the real failures...
+  for (ClassLoaderInfo& info : class_loader_chain_) {
+    for (const std::string& cp_elem : info.classpath) {
+      // If path is relative, append it to the provided base directory.
+      std::string location = cp_elem;
+      if (location[0] != '/') {
+        location = classpath_dir + '/' + location;
+      }
+      std::string error_msg;
+      // When opening the dex files from the context we expect their checksum to match their
+      // contents. So pass true to verify_checksum.
+      if (!DexFile::Open(location.c_str(),
+                         location.c_str(),
+                         /*verify_checksum*/ true,
+                         &error_msg,
+                         &info.opened_dex_files)) {
+        // If we fail to open the dex file because it's been stripped, try to open the dex file
+        // from its corresponding oat file.
+        // This could happen when we need to recompile a pre-build whose dex code has been stripped.
+        // (for example, if the pre-build is only quicken and we want to re-compile it
+        // speed-profile).
+        // TODO(calin): Use the vdex directly instead of going through the oat file.
+        OatFileAssistant oat_file_assistant(location.c_str(), isa, false);
+        std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile());
+        std::vector<std::unique_ptr<const DexFile>> oat_dex_files;
+        if (oat_file != nullptr &&
+            OatFileAssistant::LoadDexFiles(*oat_file, location, &oat_dex_files)) {
+          info.opened_oat_files.push_back(std::move(oat_file));
+          info.opened_dex_files.insert(info.opened_dex_files.end(),
+                                       std::make_move_iterator(oat_dex_files.begin()),
+                                       std::make_move_iterator(oat_dex_files.end()));
+        } else {
+          LOG(WARNING) << "Could not open dex files from location: " << location;
+          dex_files_open_result_ = false;
+        }
+      }
+    }
+  }
+
+  return dex_files_open_result_;
+}
+
+bool ClassLoaderContext::RemoveLocationsFromClassPaths(
+    const dchecked_vector<std::string>& locations) {
+  CHECK(!dex_files_open_attempted_)
+      << "RemoveLocationsFromClasspaths cannot be call after OpenDexFiles";
+
+  std::set<std::string> canonical_locations;
+  for (const std::string& location : locations) {
+    canonical_locations.insert(DexFile::GetDexCanonicalLocation(location.c_str()));
+  }
+  bool removed_locations = false;
+  for (ClassLoaderInfo& info : class_loader_chain_) {
+    size_t initial_size = info.classpath.size();
+    auto kept_it = std::remove_if(
+        info.classpath.begin(),
+        info.classpath.end(),
+        [canonical_locations](const std::string& location) {
+            return ContainsElement(canonical_locations,
+                                   DexFile::GetDexCanonicalLocation(location.c_str()));
+        });
+    info.classpath.erase(kept_it, info.classpath.end());
+    if (initial_size != info.classpath.size()) {
+      removed_locations = true;
+    }
+  }
+  return removed_locations;
+}
+
+std::string ClassLoaderContext::EncodeContextForOatFile(const std::string& base_dir) const {
+  CheckDexFilesOpened("EncodeContextForOatFile");
+  if (special_shared_library_) {
+    return OatFile::kSpecialSharedLibrary;
+  }
+
+  if (class_loader_chain_.empty()) {
+    return "";
+  }
+
+  // TODO(calin): Transition period: assume we only have a classloader until
+  // the oat file assistant implements the full class loader check.
+  CHECK_EQ(1u, class_loader_chain_.size());
+
+  return OatFile::EncodeDexFileDependencies(MakeNonOwningPointerVector(
+      class_loader_chain_[0].opened_dex_files), base_dir);
+}
+
+jobject ClassLoaderContext::CreateClassLoader(
+    const std::vector<const DexFile*>& compilation_sources) const {
+  CheckDexFilesOpened("CreateClassLoader");
+
+  Thread* self = Thread::Current();
+  ScopedObjectAccess soa(self);
+
+  std::vector<const DexFile*> class_path_files;
+
+  // TODO(calin): Transition period: assume we only have a classloader until
+  // the oat file assistant implements the full class loader check.
+  if (!class_loader_chain_.empty()) {
+    CHECK_EQ(1u, class_loader_chain_.size());
+    CHECK_EQ(kPathClassLoader, class_loader_chain_[0].type);
+    class_path_files = MakeNonOwningPointerVector(class_loader_chain_[0].opened_dex_files);
+  }
+
+  // Classpath: first the class-path given; then the dex files we'll compile.
+  // Thus we'll resolve the class-path first.
+  class_path_files.insert(class_path_files.end(),
+                          compilation_sources.begin(),
+                          compilation_sources.end());
+
+  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+  return class_linker->CreatePathClassLoader(self, class_path_files);
+}
+
+std::vector<const DexFile*> ClassLoaderContext::FlattenOpenedDexFiles() const {
+  CheckDexFilesOpened("FlattenOpenedDexFiles");
+
+  std::vector<const DexFile*> result;
+  for (const ClassLoaderInfo& info : class_loader_chain_) {
+    for (const std::unique_ptr<const DexFile>& dex_file : info.opened_dex_files) {
+      result.push_back(dex_file.get());
+    }
+  }
+  return result;
+}
+
+const char* ClassLoaderContext::GetClassLoaderTypeName(ClassLoaderType type) {
+  switch (type) {
+    case kPathClassLoader: return kPathClassLoaderString;
+    case kDelegateLastClassLoader: return kDelegateLastClassLoaderString;
+    default:
+      LOG(FATAL) << "Invalid class loader type " << type;
+      UNREACHABLE();
+  }
+}
+
+void ClassLoaderContext::CheckDexFilesOpened(const std::string& calling_method) const {
+  CHECK(dex_files_open_attempted_)
+      << "Dex files were not successfully opened before the call to " << calling_method
+      << "attempt=" << dex_files_open_attempted_ << ", result=" << dex_files_open_result_;
+}
+}  // namespace art
+
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
new file mode 100644
index 0000000..4af5017
--- /dev/null
+++ b/runtime/class_loader_context.h
@@ -0,0 +1,150 @@
+/*
+ * 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_CONTEXT_H_
+#define ART_RUNTIME_CLASS_LOADER_CONTEXT_H_
+
+#include <string>
+#include <vector>
+
+#include "arch/instruction_set.h"
+#include "base/dchecked_vector.h"
+#include "jni.h"
+
+namespace art {
+
+class DexFile;
+class OatFile;
+
+// Utility class which holds the class loader context used during compilation/verification.
+class ClassLoaderContext {
+ public:
+  // Creates an empty context (with no class loaders).
+  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
+  // use for the relative class paths.
+  // Returns true if all dex files where successfully opened.
+  // It may be called only once per ClassLoaderContext. The second call will abort.
+  //
+  // Note that a "false" return could mean that either an apk/jar contained no dex files or
+  // that we hit a I/O or checksum mismatch error.
+  // TODO(calin): Currently there's no easy way to tell the difference.
+  //
+  // TODO(calin): we're forced to complicate the flow in this class with a different
+  // OpenDexFiles step because the current dex2oat flow requires the dex files be opened before
+  // the class loader is created. Consider reworking the dex2oat part.
+  bool OpenDexFiles(InstructionSet isa, const std::string& classpath_dir);
+
+  // Remove the specified compilation sources from all classpaths present in this context.
+  // Should only be called before the first call to OpenDexFiles().
+  bool RemoveLocationsFromClassPaths(const dchecked_vector<std::string>& compilation_sources);
+
+  // Creates the entire class loader hierarchy according to the current context.
+  // The compilation sources are appended to the classpath of the top class loader
+  // (i.e the class loader whose parent is the BootClassLoader).
+  // Should only be called if OpenDexFiles() returned true.
+  jobject CreateClassLoader(const std::vector<const DexFile*>& compilation_sources) const;
+
+  // Encodes the context as a string suitable to be added in oat files.
+  // (so that it can be read and verified at runtime against the actual class
+  // loader hierarchy).
+  // Should only be called if OpenDexFiles() returned true.
+  // E.g. if the context is PCL[a.dex:b.dex] this will return "a.dex*a_checksum*b.dex*a_checksum".
+  std::string EncodeContextForOatFile(const std::string& base_dir) const;
+
+  // Flattens the opened dex files into the given vector.
+  // Should only be called if OpenDexFiles() returned true.
+  std::vector<const DexFile*> FlattenOpenedDexFiles() const;
+
+  // Creates the class loader context from the given string.
+  // The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...
+  // ClassLoaderType is either "PCL" (PathClassLoader) or "DLC" (DelegateLastClassLoader).
+  // ClasspathElem is the path of dex/jar/apk file.
+  // Note that we allowed class loaders with an empty class path in order to support a custom
+  // class loader for the source dex files.
+  static std::unique_ptr<ClassLoaderContext> Create(const std::string& spec);
+
+ private:
+  enum ClassLoaderType {
+    kInvalidClassLoader = 0,
+    kPathClassLoader = 1,
+    kDelegateLastClassLoader = 2
+  };
+
+  struct ClassLoaderInfo {
+    // The type of this class loader.
+    ClassLoaderType type;
+    // The list of class path elements that this loader loads.
+    // Note that this list may contain relative paths.
+    std::vector<std::string> classpath;
+    // After OpenDexFiles is called this holds the opened dex files.
+    std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
+    // After OpenDexFiles, in case some of the dex files were opened from their oat files
+    // this holds the list of opened oat files.
+    std::vector<std::unique_ptr<OatFile>> opened_oat_files;
+
+    explicit ClassLoaderInfo(ClassLoaderType cl_type) : type(cl_type) {}
+  };
+
+  // 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);
+
+  // Attempts to parse a single class loader spec for the given class_loader_type.
+  // If successful the class loader spec will be added to the chain.
+  // Returns whether or not the operation was successful.
+  bool ParseClassLoaderSpec(const std::string& class_loader_spec,
+                            ClassLoaderType class_loader_type);
+
+  // Extracts the class loader type from the given spec.
+  // Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not
+  // recognized.
+  static ClassLoaderType ExtractClassLoaderType(const std::string& class_loader_spec);
+
+  // Returns the string representation of the class loader type.
+  // 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.
+  std::vector<ClassLoaderInfo> class_loader_chain_;
+
+  // Whether or not the class loader context should be ignored at runtime when loading the oat
+  // files. When true, dex2oat will use OatFile::kSpecialSharedLibrary as the classpath key in
+  // the oat file.
+  // TODO(calin): Can we get rid of this and cover all relevant use cases?
+  // (e.g. packages using prebuild system packages as shared libraries b/36480683)
+  bool special_shared_library_;
+
+  // Whether or not OpenDexFiles() was called.
+  bool dex_files_open_attempted_;
+  // The result of the last OpenDexFiles() operation.
+  bool dex_files_open_result_;
+
+  friend class ClassLoaderContextTest;
+
+  DISALLOW_COPY_AND_ASSIGN(ClassLoaderContext);
+};
+
+}  // namespace art
+#endif  // ART_RUNTIME_CLASS_LOADER_CONTEXT_H_
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
new file mode 100644
index 0000000..4643e78
--- /dev/null
+++ b/runtime/class_loader_context_test.cc
@@ -0,0 +1,265 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+
+#include "class_loader_context.h"
+#include "common_runtime_test.h"
+
+#include "base/dchecked_vector.h"
+#include "base/stl_util.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "handle_scope-inl.h"
+#include "mirror/class.h"
+#include "mirror/class_loader.h"
+#include "mirror/object-inl.h"
+#include "oat_file_assistant.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread.h"
+#include "well_known_classes.h"
+
+namespace art {
+
+class ClassLoaderContextTest : public CommonRuntimeTest {
+ public:
+  void VerifyContextSize(ClassLoaderContext* context, size_t expected_size) {
+    ASSERT_TRUE(context != nullptr);
+    ASSERT_EQ(expected_size, context->class_loader_chain_.size());
+  }
+
+  void VerifyClassLoaderPCL(ClassLoaderContext* context,
+                            size_t index,
+                            std::string classpath) {
+    VerifyClassLoaderInfo(
+        context, index, ClassLoaderContext::kPathClassLoader, classpath);
+  }
+
+  void VerifyClassLoaderDLC(ClassLoaderContext* context,
+                            size_t index,
+                            std::string classpath) {
+    VerifyClassLoaderInfo(
+        context, index, ClassLoaderContext::kDelegateLastClassLoader, classpath);
+  }
+
+  void VerifyOpenDexFiles(
+      ClassLoaderContext* context,
+      size_t index,
+      std::vector<std::vector<std::unique_ptr<const DexFile>>*>& all_dex_files) {
+    ASSERT_TRUE(context != nullptr);
+    ASSERT_TRUE(context->dex_files_open_attempted_);
+    ASSERT_TRUE(context->dex_files_open_result_);
+    ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index];
+    ASSERT_EQ(all_dex_files.size(), info.classpath.size());
+    size_t cur_open_dex_index = 0;
+    for (size_t k = 0; k < all_dex_files.size(); k++) {
+      std::vector<std::unique_ptr<const DexFile>>& dex_files_for_cp_elem = *(all_dex_files[k]);
+      for (size_t i = 0; i < dex_files_for_cp_elem.size(); i++) {
+        ASSERT_LT(cur_open_dex_index, info.opened_dex_files.size());
+
+        std::unique_ptr<const DexFile>& opened_dex_file =
+            info.opened_dex_files[cur_open_dex_index++];
+        std::unique_ptr<const DexFile>& expected_dex_file = dex_files_for_cp_elem[i];
+
+        ASSERT_EQ(expected_dex_file->GetLocation(), opened_dex_file->GetLocation());
+        ASSERT_EQ(expected_dex_file->GetLocationChecksum(), opened_dex_file->GetLocationChecksum());
+        ASSERT_EQ(info.classpath[k], opened_dex_file->GetBaseLocation());
+      }
+    }
+  }
+
+ private:
+  void VerifyClassLoaderInfo(ClassLoaderContext* context,
+                             size_t index,
+                             ClassLoaderContext::ClassLoaderType type,
+                             std::string classpath) {
+    ASSERT_TRUE(context != nullptr);
+    ASSERT_GT(context->class_loader_chain_.size(), index);
+    ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index];
+    ASSERT_EQ(type, info.type);
+    std::vector<std::string> expected_classpath;
+    Split(classpath, ':', &expected_classpath);
+    ASSERT_EQ(expected_classpath, info.classpath);
+  }
+};
+
+TEST_F(ClassLoaderContextTest, ParseValidContextPCL) {
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("PCL[a.dex]");
+  VerifyContextSize(context.get(), 1);
+  VerifyClassLoaderPCL(context.get(), 0, "a.dex");
+}
+
+TEST_F(ClassLoaderContextTest, ParseValidContextDLC) {
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("DLC[a.dex]");
+  VerifyContextSize(context.get(), 1);
+  VerifyClassLoaderDLC(context.get(), 0, "a.dex");
+}
+
+TEST_F(ClassLoaderContextTest, ParseValidContextChain) {
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("PCL[a.dex:b.dex];DLC[c.dex:d.dex];PCL[e.dex]");
+  VerifyContextSize(context.get(), 3);
+  VerifyClassLoaderPCL(context.get(), 0, "a.dex:b.dex");
+  VerifyClassLoaderDLC(context.get(), 1, "c.dex:d.dex");
+  VerifyClassLoaderPCL(context.get(), 2, "e.dex");
+}
+
+TEST_F(ClassLoaderContextTest, ParseValidEmptyContextDLC) {
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("DLC[]");
+  VerifyContextSize(context.get(), 1);
+  VerifyClassLoaderDLC(context.get(), 0, "");
+}
+
+TEST_F(ClassLoaderContextTest, ParseValidContextSpecialSymbol) {
+  std::unique_ptr<ClassLoaderContext> context =
+    ClassLoaderContext::Create(OatFile::kSpecialSharedLibrary);
+  VerifyContextSize(context.get(), 0);
+}
+
+TEST_F(ClassLoaderContextTest, ParseInvalidValidContexts) {
+  ASSERT_TRUE(nullptr == ClassLoaderContext::Create("ABC[a.dex]"));
+  ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL"));
+  ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL[a.dex"));
+  ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCLa.dex]"));
+  ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL{a.dex}"));
+  ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL[a.dex];DLC[b.dex"));
+}
+
+TEST_F(ClassLoaderContextTest, OpenInvalidDexFiles) {
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("PCL[does_not_exist.dex]");
+  VerifyContextSize(context.get(), 1);
+  ASSERT_FALSE(context->OpenDexFiles(InstructionSet::kArm, "."));
+}
+
+TEST_F(ClassLoaderContextTest, OpenValidDexFiles) {
+  std::string multidex_name = GetTestDexFileName("MultiDex");
+  std::vector<std::unique_ptr<const DexFile>> multidex_files = OpenTestDexFiles("MultiDex");
+  std::string myclass_dex_name = GetTestDexFileName("MyClass");
+  std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
+  std::string dex_name = GetTestDexFileName("Main");
+  std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Main");
+
+
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create(
+          "PCL[" + multidex_name + ":" + myclass_dex_name + "];" +
+          "DLC[" + dex_name + "]");
+
+  ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, /*classpath_dir*/ ""));
+
+  VerifyContextSize(context.get(), 2);
+  std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0;
+  all_dex_files0.push_back(&multidex_files);
+  all_dex_files0.push_back(&myclass_dex_files);
+  std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files1;
+  all_dex_files1.push_back(&dex_files);
+
+  VerifyOpenDexFiles(context.get(), 0, all_dex_files0);
+  VerifyOpenDexFiles(context.get(), 1, all_dex_files1);
+}
+
+TEST_F(ClassLoaderContextTest, OpenInvalidDexFilesMix) {
+  std::string dex_name = GetTestDexFileName("Main");
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("PCL[does_not_exist.dex];DLC[" + dex_name + "]");
+  ASSERT_FALSE(context->OpenDexFiles(InstructionSet::kArm, ""));
+}
+
+TEST_F(ClassLoaderContextTest, CreateClassLoader) {
+  std::string dex_name = GetTestDexFileName("Main");
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("PCL[" + dex_name + "]");
+  ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, ""));
+
+  std::vector<std::unique_ptr<const DexFile>> classpath_dex = OpenTestDexFiles("Main");
+  std::vector<std::unique_ptr<const DexFile>> compilation_sources = OpenTestDexFiles("MultiDex");
+
+  std::vector<const DexFile*> compilation_sources_raw =
+      MakeNonOwningPointerVector(compilation_sources);
+  jobject jclass_loader = context->CreateClassLoader(compilation_sources_raw);
+  ASSERT_TRUE(jclass_loader != nullptr);
+
+  ScopedObjectAccess soa(Thread::Current());
+
+  StackHandleScope<2> hs(soa.Self());
+  Handle<mirror::ClassLoader> class_loader = hs.NewHandle(
+      soa.Decode<mirror::ClassLoader>(jclass_loader));
+
+  ASSERT_TRUE(class_loader->GetClass() ==
+      soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader));
+  ASSERT_TRUE(class_loader->GetParent()->GetClass() ==
+      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+
+
+  std::vector<const DexFile*> class_loader_dex_files = GetDexFiles(jclass_loader);
+  ASSERT_EQ(classpath_dex.size() + compilation_sources.size(), class_loader_dex_files.size());
+
+  // The classpath dex files must come first.
+  for (size_t i = 0; i < classpath_dex.size(); i++) {
+    ASSERT_EQ(classpath_dex[i]->GetLocation(),
+              class_loader_dex_files[i]->GetLocation());
+    ASSERT_EQ(classpath_dex[i]->GetLocationChecksum(),
+              class_loader_dex_files[i]->GetLocationChecksum());
+  }
+
+  // The compilation dex files must come second.
+  for (size_t i = 0, k = classpath_dex.size(); i < compilation_sources.size(); i++, k++) {
+    ASSERT_EQ(compilation_sources[i]->GetLocation(),
+              class_loader_dex_files[k]->GetLocation());
+    ASSERT_EQ(compilation_sources[i]->GetLocationChecksum(),
+              class_loader_dex_files[k]->GetLocationChecksum());
+  }
+}
+
+TEST_F(ClassLoaderContextTest, RemoveSourceLocations) {
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("PCL[a.dex]");
+  dchecked_vector<std::string> classpath_dex;
+  classpath_dex.push_back("a.dex");
+  dchecked_vector<std::string> compilation_sources;
+  compilation_sources.push_back("src.dex");
+
+  // Nothing should be removed.
+  ASSERT_FALSE(context->RemoveLocationsFromClassPaths(compilation_sources));
+  VerifyClassLoaderPCL(context.get(), 0, "a.dex");
+  // Classes should be removed.
+  ASSERT_TRUE(context->RemoveLocationsFromClassPaths(classpath_dex));
+  VerifyClassLoaderPCL(context.get(), 0, "");
+}
+
+TEST_F(ClassLoaderContextTest, EncodeInOatFile) {
+  std::string dex1_name = GetTestDexFileName("Main");
+  std::string dex2_name = GetTestDexFileName("MyClass");
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("PCL[" + dex1_name + ":" + dex2_name + "]");
+  ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, ""));
+
+  std::vector<std::unique_ptr<const DexFile>> dex1 = OpenTestDexFiles("Main");
+  std::vector<std::unique_ptr<const DexFile>> dex2 = OpenTestDexFiles("MyClass");
+  std::string encoding = context->EncodeContextForOatFile("");
+  std::string expected_encoding =
+      dex1[0]->GetLocation() + "*" + std::to_string(dex1[0]->GetLocationChecksum()) + "*" +
+      dex2[0]->GetLocation() + "*" + std::to_string(dex2[0]->GetLocationChecksum()) + "*";
+  ASSERT_EQ(expected_encoding, context->EncodeContextForOatFile(""));
+}
+
+}  // namespace art
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 888de45..2ed30df 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -1577,7 +1577,7 @@
 static constexpr char kDexClassPathEncodingSeparator = '*';
 
 std::string OatFile::EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files,
-                                               std::string& base_dir) {
+                                               const std::string& base_dir) {
   std::ostringstream out;
 
   for (const DexFile* dex_file : dex_files) {
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 66ed44f..6393e09 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -299,7 +299,7 @@
   // Create a dependency list (dex locations and checksums) for the given dex files.
   // Removes dex file paths prefixed with base_dir to convert them back to relative paths.
   static std::string EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files,
-                                               std::string& base_dir);
+                                               const std::string& base_dir);
 
   // Finds the associated oat class for a dex_file and descriptor. Returns an invalid OatClass on
   // error and sets found to false.
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 4820feb..c876657 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -298,28 +298,38 @@
 }
 
 std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles(
-    const OatFile& oat_file, const char* dex_location) {
+    const OatFile &oat_file, const char *dex_location) {
   std::vector<std::unique_ptr<const DexFile>> dex_files;
+  if (LoadDexFiles(oat_file, dex_location, &dex_files)) {
+    return dex_files;
+  } else {
+    return std::vector<std::unique_ptr<const DexFile>>();
+  }
+}
 
+bool OatFileAssistant::LoadDexFiles(
+    const OatFile &oat_file,
+    const std::string& dex_location,
+    std::vector<std::unique_ptr<const DexFile>>* out_dex_files) {
   // Load the main dex file.
   std::string error_msg;
   const OatFile::OatDexFile* oat_dex_file = oat_file.GetOatDexFile(
-      dex_location, nullptr, &error_msg);
+      dex_location.c_str(), nullptr, &error_msg);
   if (oat_dex_file == nullptr) {
     LOG(WARNING) << error_msg;
-    return std::vector<std::unique_ptr<const DexFile>>();
+    return false;
   }
 
   std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
   if (dex_file.get() == nullptr) {
     LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg;
-    return std::vector<std::unique_ptr<const DexFile>>();
+    return false;
   }
-  dex_files.push_back(std::move(dex_file));
+  out_dex_files->push_back(std::move(dex_file));
 
   // Load the rest of the multidex entries
-  for (size_t i = 1; ; i++) {
-    std::string multidex_dex_location = DexFile::GetMultiDexLocation(i, dex_location);
+  for (size_t i = 1;; i++) {
+    std::string multidex_dex_location = DexFile::GetMultiDexLocation(i, dex_location.c_str());
     oat_dex_file = oat_file.GetOatDexFile(multidex_dex_location.c_str(), nullptr);
     if (oat_dex_file == nullptr) {
       // There are no more multidex entries to load.
@@ -329,11 +339,11 @@
     dex_file = oat_dex_file->OpenDexFile(&error_msg);
     if (dex_file.get() == nullptr) {
       LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg;
-      return std::vector<std::unique_ptr<const DexFile>>();
+      return false;
     }
-    dex_files.push_back(std::move(dex_file));
+    out_dex_files->push_back(std::move(dex_file));
   }
-  return dex_files;
+  return true;
 }
 
 bool OatFileAssistant::HasOriginalDexFiles() {
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 03d9ca3..92d87ea 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -207,6 +207,13 @@
   static std::vector<std::unique_ptr<const DexFile>> LoadDexFiles(
       const OatFile& oat_file, const char* dex_location);
 
+  // Same as `std::vector<std::unique_ptr<const DexFile>> LoadDexFiles(...)` with the difference:
+  //   - puts the dex files in the given vector
+  //   - returns whether or not all dex files were successfully opened
+  static bool LoadDexFiles(const OatFile& oat_file,
+                           const std::string& dex_location,
+                           std::vector<std::unique_ptr<const DexFile>>* out_dex_files);
+
   // Returns true if there are dex files in the original dex location that can
   // be compiled with dex2oat for this dex location.
   // Returns false if there is no original dex file, or if the original dex