Allow dex2oat to create a full class loader context

We previously checked that dex2oat sees only a single class loader. This
CL removes the restriction and enables dex2oat to create and compile with
a full class loader context.

Test: m test-art-host
Bug: 38138251
Change-Id: I03e75a75757995ce8ce3addf0bc0a708e18ac050
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 6257c7c..9d52ff0 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -411,16 +411,20 @@
   UsageError("      ");
   UsageError("      The chain is interpreted in the natural 'parent order', meaning that class");
   UsageError("      loader 'i+1' will be the parent of class loader 'i'.");
-  UsageError("      The compilation sources will be added to the classpath of the last class");
-  UsageError("      loader. This allows the compiled dex files to be loaded at runtime in");
-  UsageError("      a class loader that contains other dex files as well (e.g. shared libraries).");
+  UsageError("      The compilation sources will be appended to the classpath of the first class");
+  UsageError("      loader.");
+  UsageError("      ");
+  UsageError("      E.g. if the context is 'PCL[lib1.dex];DLC[lib2.dex]' and ");
+  UsageError("      --dex-file=src.dex then dex2oat will setup a PathClassLoader with classpath ");
+  UsageError("      'lib1.dex:src.dex' and set its parent to a DelegateLastClassLoader with ");
+  UsageError("      classpath 'lib2.dex'.");
   UsageError("      ");
   UsageError("      Note that the compiler will be tolerant if the source dex files specified");
   UsageError("      with --dex-file are found in the classpath. The source dex files will be");
   UsageError("      removed from any class loader's classpath possibly resulting in empty");
   UsageError("      class loaders.");
   UsageError("      ");
-  UsageError("      Example: --classloader-spec=PCL[lib1.dex:lib2.dex];DLC[lib3.dex]");
+  UsageError("      Example: --class-loader-context=PCL[lib1.dex:lib2.dex];DLC[lib3.dex]");
   UsageError("");
   std::cerr << "See log for usage error information\n";
   exit(EXIT_FAILURE);
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index b08b055..3538215 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -1023,4 +1023,16 @@
   RunTest(context.c_str(), kEmptyClassPathKey, /*expected_success*/ true);
 }
 
+TEST_F(Dex2oatClassLoaderContextTest, ChainContext) {
+  std::vector<std::unique_ptr<const DexFile>> dex_files1 = OpenTestDexFiles("Nested");
+  std::vector<std::unique_ptr<const DexFile>> dex_files2 = OpenTestDexFiles("MultiDex");
+
+  std::string context = "PCL[" + GetTestDexFileName("Nested") + "];" +
+      "DLC[" + GetTestDexFileName("MultiDex") + "]";
+  std::string expected_classpath_key = "PCL[" + CreateClassPathWithChecksums(dex_files1) + "];" +
+      "DLC[" + CreateClassPathWithChecksums(dex_files2) + "]";
+
+  RunTest(context.c_str(), expected_classpath_key.c_str(), true);
+}
+
 }  // namespace art
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 678ae8f..90346f0 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -305,24 +305,41 @@
   Thread* self = Thread::Current();
   ScopedObjectAccess soa(self);
 
-  std::vector<const DexFile*> class_path_files;
+  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
 
-  // 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);
+  if (class_loader_chain_.empty()) {
+    return class_linker->CreatePathClassLoader(self, compilation_sources);
   }
 
-  // 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());
+  // Create the class loaders starting from the top most parent (the one on the last position
+  // in the chain) but omit the first class loader which will contain the compilation_sources and
+  // needs special handling.
+  jobject current_parent = nullptr;  // the starting parent is the BootClassLoader.
+  for (size_t i = class_loader_chain_.size() - 1; i > 0; i--) {
+    std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(
+        class_loader_chain_[i].opened_dex_files);
+    current_parent = class_linker->CreateWellKnownClassLoader(
+        self,
+        class_path_files,
+        GetClassLoaderClass(class_loader_chain_[i].type),
+        current_parent);
+  }
 
-  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
-  return class_linker->CreatePathClassLoader(self, class_path_files);
+  // We set up all the parents. Move on to create the first class loader.
+  // Its classpath comes first, followed by compilation sources. This ensures that whenever
+  // we need to resolve classes from it the classpath elements come first.
+
+  std::vector<const DexFile*> first_class_loader_classpath = MakeNonOwningPointerVector(
+      class_loader_chain_[0].opened_dex_files);
+  first_class_loader_classpath.insert(first_class_loader_classpath.end(),
+                                    compilation_sources.begin(),
+                                    compilation_sources.end());
+
+  return class_linker->CreateWellKnownClassLoader(
+      self,
+      first_class_loader_classpath,
+      GetClassLoaderClass(class_loader_chain_[0].type),
+      current_parent);
 }
 
 std::vector<const DexFile*> ClassLoaderContext::FlattenOpenedDexFiles() const {
@@ -623,5 +640,15 @@
   return true;
 }
 
+jclass ClassLoaderContext::GetClassLoaderClass(ClassLoaderType type) {
+  switch (type) {
+    case kPathClassLoader: return WellKnownClasses::dalvik_system_PathClassLoader;
+    case kDelegateLastClassLoader: return WellKnownClasses::dalvik_system_DelegateLastClassLoader;
+    case kInvalidClassLoader: break;  // will fail after the switch.
+  }
+  LOG(FATAL) << "Invalid class loader type " << type;
+  UNREACHABLE();
+}
+
 }  // namespace art
 
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index 8c65e2e..37dd02b 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -60,11 +60,22 @@
   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.
+  // Returns the first class loader from the chain.
+  //
+  // For example: if the context was built from the spec
+  // "ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]..."
+  // the method returns the class loader correponding to ClassLoader1. The parent chain will be
+  // ClassLoader1 --> ClassLoader2 --> ... --> BootClassLoader.
+  //
+  // The compilation sources are appended to the classpath of the first class loader (in the above
+  // example ClassLoader1).
+  //
   // If the context is empty, this method only creates a single PathClassLoader with the
   // given compilation_sources.
+  //
+  // Notes:
+  //   1) the objects are not completely set up. Do not use this outside of tests and the compiler.
+  //   2) should only be called before the first call to OpenDexFiles().
   jobject CreateClassLoader(const std::vector<const DexFile*>& compilation_sources) const;
 
   // Encodes the context as a string suitable to be added in oat files.
@@ -89,6 +100,11 @@
   // The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...
   // ClassLoaderType is either "PCL" (PathClassLoader) or "DLC" (DelegateLastClassLoader).
   // ClasspathElem is the path of dex/jar/apk file.
+  //
+  // The spec represents a class loader chain with the natural interpretation:
+  // ClassLoader1 has ClassLoader2 as parent which has ClassLoader3 as a parent and so on.
+  // The last class loader is assumed to have the BootClassLoader as a parent.
+  //
   // 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);
@@ -168,6 +184,9 @@
   // The returned format can be used when parsing a context spec.
   static const char* GetClassLoaderTypeName(ClassLoaderType type);
 
+  // Returns the WellKnownClass for the given class loader type.
+  static jclass GetClassLoaderClass(ClassLoaderType type);
+
   // 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.
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index 659db4b..1b46f67 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -117,6 +117,24 @@
     ASSERT_FALSE(context->special_shared_library_);
   }
 
+  void VerifyClassLoaderDexFiles(ScopedObjectAccess& soa,
+                                 Handle<mirror::ClassLoader> class_loader,
+                                 jclass type,
+                                 std::vector<const DexFile*>& expected_dex_files)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    ASSERT_TRUE(class_loader->GetClass() == soa.Decode<mirror::Class>(type));
+
+    std::vector<const DexFile*> class_loader_dex_files = GetDexFiles(soa, class_loader);
+    ASSERT_EQ(expected_dex_files.size(), class_loader_dex_files.size());
+
+    for (size_t i = 0; i < expected_dex_files.size(); i++) {
+      ASSERT_EQ(expected_dex_files[i]->GetLocation(),
+                class_loader_dex_files[i]->GetLocation());
+      ASSERT_EQ(expected_dex_files[i]->GetLocationChecksum(),
+                class_loader_dex_files[i]->GetLocationChecksum());
+    }
+  }
+
  private:
   void VerifyClassLoaderInfo(ClassLoaderContext* context,
                              size_t index,
@@ -246,7 +264,7 @@
 
   ScopedObjectAccess soa(Thread::Current());
 
-  StackHandleScope<2> hs(soa.Self());
+  StackHandleScope<1> hs(soa.Self());
   Handle<mirror::ClassLoader> class_loader = hs.NewHandle(
       soa.Decode<mirror::ClassLoader>(jclass_loader));
 
@@ -255,25 +273,17 @@
   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());
+  // For the first class loader the class path dex files must come first and then the
+  // compilation sources.
+  std::vector<const DexFile*> expected_classpath = MakeNonOwningPointerVector(classpath_dex);
+  for (auto& dex : compilation_sources_raw) {
+    expected_classpath.push_back(dex);
   }
 
-  // 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());
-  }
+  VerifyClassLoaderDexFiles(soa,
+                            class_loader,
+                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            expected_classpath);
 }
 
 TEST_F(ClassLoaderContextTest, CreateClassLoaderWithEmptyContext) {
@@ -290,28 +300,90 @@
 
   ScopedObjectAccess soa(Thread::Current());
 
-  StackHandleScope<2> hs(soa.Self());
+  StackHandleScope<1> 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));
+  // An empty context should create a single PathClassLoader with only the compilation sources.
+  VerifyClassLoaderDexFiles(soa,
+                            class_loader,
+                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            compilation_sources_raw);
   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);
-
-  // The compilation sources should be the only files present in the class loader
-  ASSERT_EQ(compilation_sources.size(), class_loader_dex_files.size());
-  for (size_t i = 0; i < compilation_sources.size(); i++) {
-    ASSERT_EQ(compilation_sources[i]->GetLocation(),
-        class_loader_dex_files[i]->GetLocation());
-    ASSERT_EQ(compilation_sources[i]->GetLocationChecksum(),
-        class_loader_dex_files[i]->GetLocationChecksum());
-  }
 }
 
+TEST_F(ClassLoaderContextTest, CreateClassLoaderWithComplexChain) {
+  // Setup the context.
+  std::vector<std::unique_ptr<const DexFile>> classpath_dex_a = OpenTestDexFiles("ForClassLoaderA");
+  std::vector<std::unique_ptr<const DexFile>> classpath_dex_b = OpenTestDexFiles("ForClassLoaderB");
+  std::vector<std::unique_ptr<const DexFile>> classpath_dex_c = OpenTestDexFiles("ForClassLoaderC");
+  std::vector<std::unique_ptr<const DexFile>> classpath_dex_d = OpenTestDexFiles("ForClassLoaderD");
+
+  std::string context_spec =
+      "PCL[" + CreateClassPath(classpath_dex_a) + ":" + CreateClassPath(classpath_dex_b) + "];" +
+      "DLC[" + CreateClassPath(classpath_dex_c) + "];" +
+      "PCL[" + CreateClassPath(classpath_dex_d) + "]";
+
+  std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_spec);
+  ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, ""));
+
+  // Setup the compilation sources.
+  std::vector<std::unique_ptr<const DexFile>> compilation_sources = OpenTestDexFiles("MultiDex");
+  std::vector<const DexFile*> compilation_sources_raw =
+      MakeNonOwningPointerVector(compilation_sources);
+
+  // Create the class loader.
+  jobject jclass_loader = context->CreateClassLoader(compilation_sources_raw);
+  ASSERT_TRUE(jclass_loader != nullptr);
+
+  // Verify the class loader.
+  ScopedObjectAccess soa(Thread::Current());
+
+  StackHandleScope<3> hs(soa.Self());
+  Handle<mirror::ClassLoader> class_loader_1 = hs.NewHandle(
+      soa.Decode<mirror::ClassLoader>(jclass_loader));
+
+  // Verify the first class loader
+
+  // For the first class loader the class path dex files must come first and then the
+  // compilation sources.
+  std::vector<const DexFile*> class_loader_1_dex_files =
+      MakeNonOwningPointerVector(classpath_dex_a);
+  for (auto& dex : classpath_dex_b) {
+    class_loader_1_dex_files.push_back(dex.get());
+  }
+  for (auto& dex : compilation_sources_raw) {
+    class_loader_1_dex_files.push_back(dex);
+  }
+  VerifyClassLoaderDexFiles(soa,
+                            class_loader_1,
+                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            class_loader_1_dex_files);
+
+  // Verify the second class loader
+  Handle<mirror::ClassLoader> class_loader_2 = hs.NewHandle(class_loader_1->GetParent());
+  std::vector<const DexFile*> class_loader_2_dex_files =
+      MakeNonOwningPointerVector(classpath_dex_c);
+  VerifyClassLoaderDexFiles(soa,
+                            class_loader_2,
+                            WellKnownClasses::dalvik_system_DelegateLastClassLoader,
+                            class_loader_2_dex_files);
+
+  // Verify the third class loader
+  Handle<mirror::ClassLoader> class_loader_3 = hs.NewHandle(class_loader_2->GetParent());
+  std::vector<const DexFile*> class_loader_3_dex_files =
+      MakeNonOwningPointerVector(classpath_dex_d);
+  VerifyClassLoaderDexFiles(soa,
+                            class_loader_3,
+                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            class_loader_3_dex_files);
+  // The last class loader should have the BootClassLoader as a parent.
+  ASSERT_TRUE(class_loader_3->GetParent()->GetClass() ==
+      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+}
+
+
 TEST_F(ClassLoaderContextTest, RemoveSourceLocations) {
   std::unique_ptr<ClassLoaderContext> context =
       ClassLoaderContext::Create("PCL[a.dex]");
@@ -338,9 +410,8 @@
   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 = "PCL[" +
-      dex1[0]->GetLocation() + "*" + std::to_string(dex1[0]->GetLocationChecksum()) + ":" +
-      dex2[0]->GetLocation() + "*" + std::to_string(dex2[0]->GetLocationChecksum()) + "]";
+  std::string expected_encoding = "PCL[" + CreateClassPathWithChecksums(dex1) + ":" +
+      CreateClassPathWithChecksums(dex2) + "]";
   ASSERT_EQ(expected_encoding, context->EncodeContextForOatFile(""));
 }
 
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 659c7e4..aae9973 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -589,18 +589,24 @@
 }
 
 std::vector<const DexFile*> CommonRuntimeTestImpl::GetDexFiles(jobject jclass_loader) {
-  std::vector<const DexFile*> ret;
-
   ScopedObjectAccess soa(Thread::Current());
 
-  StackHandleScope<2> hs(soa.Self());
+  StackHandleScope<1> hs(soa.Self());
   Handle<mirror::ClassLoader> class_loader = hs.NewHandle(
       soa.Decode<mirror::ClassLoader>(jclass_loader));
+  return GetDexFiles(soa, class_loader);
+}
 
-  DCHECK_EQ(class_loader->GetClass(),
-            soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader));
-  DCHECK_EQ(class_loader->GetParent()->GetClass(),
-            soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+std::vector<const DexFile*> CommonRuntimeTestImpl::GetDexFiles(
+    ScopedObjectAccess& soa,
+    Handle<mirror::ClassLoader> class_loader) {
+  std::vector<const DexFile*> ret;
+
+  DCHECK(
+      (class_loader->GetClass() ==
+          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) ||
+      (class_loader->GetClass() ==
+          soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader)));
 
   // The class loader is a PathClassLoader which inherits from BaseDexClassLoader.
   // We need to get the DexPathList and loop through it.
@@ -618,6 +624,7 @@
     // 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) {
+      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) {
@@ -757,6 +764,28 @@
   return location;
 }
 
+std::string CommonRuntimeTestImpl::CreateClassPath(
+    const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
+  CHECK(!dex_files.empty());
+  std::string classpath = dex_files[0]->GetLocation();
+  for (size_t i = 1; i < dex_files.size(); i++) {
+    classpath += ":" + dex_files[i]->GetLocation();
+  }
+  return classpath;
+}
+
+std::string CommonRuntimeTestImpl::CreateClassPathWithChecksums(
+    const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
+  CHECK(!dex_files.empty());
+  std::string classpath = dex_files[0]->GetLocation() + "*" +
+      std::to_string(dex_files[0]->GetLocationChecksum());
+  for (size_t i = 1; i < dex_files.size(); i++) {
+    classpath += ":" + dex_files[i]->GetLocation() + "*" +
+        std::to_string(dex_files[i]->GetLocationChecksum());
+  }
+  return classpath;
+}
+
 CheckJniAbortCatcher::CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) {
   vm_->SetCheckJniAbortHook(Hook, &actual_);
 }
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index 5893573..c494513 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -28,6 +28,7 @@
 // TODO: Add inl file and avoid including inl.
 #include "obj_ptr-inl.h"
 #include "os.h"
+#include "scoped_thread_state_change-inl.h"
 
 namespace art {
 
@@ -159,9 +160,12 @@
   const DexFile* java_lang_dex_file_;
   std::vector<const DexFile*> boot_class_path_;
 
-  // Get the dex files from a PathClassLoader. This in order of the dex elements and their dex
-  // arrays.
+  // Get the dex files from a PathClassLoader or DelegateLastClassLoader.
+  // This only looks into the current class loader and does not recurse into the parents.
   std::vector<const DexFile*> GetDexFiles(jobject jclass_loader);
+  std::vector<const DexFile*> GetDexFiles(ScopedObjectAccess& soa,
+                                          Handle<mirror::ClassLoader> class_loader)
+    REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Get the first dex file from a PathClassLoader. Will abort if it is null.
   const DexFile* GetFirstDexFile(jobject jclass_loader);
@@ -176,6 +180,15 @@
   // initializers, initialize well-known classes, and creates the heap thread pool.
   virtual void FinalizeSetup();
 
+  // Creates the class path string for the given dex files (the list of dex file locations
+  // separated by ':').
+  std::string CreateClassPath(
+      const std::vector<std::unique_ptr<const DexFile>>& dex_files);
+  // Same as CreateClassPath but add the dex file checksum after each location. The separator
+  // is '*'.
+  std::string CreateClassPathWithChecksums(
+      const std::vector<std::unique_ptr<const DexFile>>& dex_files);
+
  private:
   static std::string GetCoreFileLocation(const char* suffix);