Canonicalize shared libraries in ClassLoaderContext::CreateClassLoader.

bug:111174995
Test: class_loader_context_test
Change-Id: I547b8d5660ac5cb600d09b8546062e4b054f0eb7
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 7ca6f86..7457b3e 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -17,6 +17,7 @@
 #include "class_loader_context.h"
 
 #include <android-base/parseint.h>
+#include <android-base/strings.h>
 
 #include "art_field-inl.h"
 #include "base/casts.h"
@@ -618,13 +619,28 @@
   UNREACHABLE();
 }
 
+static std::string FlattenClasspath(const std::vector<std::string>& classpath) {
+  return android::base::Join(classpath, ':');
+}
+
 static ObjPtr<mirror::ClassLoader> CreateClassLoaderInternal(
     Thread* self,
     ScopedObjectAccess& soa,
     const ClassLoaderContext::ClassLoaderInfo& info,
+    bool for_shared_library,
+    VariableSizedHandleScope& map_scope,
+    std::map<std::string, Handle<mirror::ClassLoader>>& canonicalized_libraries,
     bool add_compilation_sources,
     const std::vector<const DexFile*>& compilation_sources)
       REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (for_shared_library) {
+    // Check if the shared library has already been created.
+    auto search = canonicalized_libraries.find(FlattenClasspath(info.classpath));
+    if (search != canonicalized_libraries.end()) {
+      return search->second.Get();
+    }
+  }
+
   StackHandleScope<3> hs(self);
   MutableHandle<mirror::ObjectArray<mirror::ClassLoader>> libraries(
       hs.NewHandle<mirror::ObjectArray<mirror::ClassLoader>>(nullptr));
@@ -641,6 +657,9 @@
                          self,
                          soa,
                          *info.shared_libraries[i].get(),
+                         /* for_shared_library= */ true,
+                         map_scope,
+                         canonicalized_libraries,
                          /* add_compilation_sources= */ false,
                          compilation_sources));
     }
@@ -650,7 +669,14 @@
   if (info.parent != nullptr) {
     // We should only add the compilation sources to the first class loader.
     parent.Assign(CreateClassLoaderInternal(
-        self, soa, *info.parent.get(), /* add_compilation_sources= */ false, compilation_sources));
+        self,
+        soa,
+        *info.parent.get(),
+        /* for_shared_library= */ false,
+        map_scope,
+        canonicalized_libraries,
+        /* add_compilation_sources= */ false,
+        compilation_sources));
   }
   std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(
       info.opened_dex_files);
@@ -664,12 +690,18 @@
   }
   Handle<mirror::Class> loader_class = hs.NewHandle<mirror::Class>(
       soa.Decode<mirror::Class>(GetClassLoaderClass(info.type)));
-  return Runtime::Current()->GetClassLinker()->CreateWellKnownClassLoader(
-      self,
-      class_path_files,
-      loader_class,
-      libraries,
-      parent);
+  ObjPtr<mirror::ClassLoader> loader =
+      Runtime::Current()->GetClassLinker()->CreateWellKnownClassLoader(
+          self,
+          class_path_files,
+          loader_class,
+          libraries,
+          parent);
+  if (for_shared_library) {
+    canonicalized_libraries[FlattenClasspath(info.classpath)] =
+        map_scope.NewHandle<mirror::ClassLoader>(loader);
+  }
+  return loader;
 }
 
 jobject ClassLoaderContext::CreateClassLoader(
@@ -686,11 +718,19 @@
     return class_linker->CreatePathClassLoader(self, compilation_sources);
   }
 
-  // Create the class loader of the parent.
+  // Create a map of canonicalized shared libraries. As we're holding objects,
+  // we're creating a variable size handle scope to put handles in the map.
+  VariableSizedHandleScope map_scope(self);
+  std::map<std::string, Handle<mirror::ClassLoader>> canonicalized_libraries;
+
+  // Create the class loader.
   ObjPtr<mirror::ClassLoader> loader =
       CreateClassLoaderInternal(self,
                                 soa,
                                 *class_loader_chain_.get(),
+                                /* for_shared_library= */ false,
+                                map_scope,
+                                canonicalized_libraries,
                                 /* add_compilation_sources= */ true,
                                 compilation_sources);
   // Make it a global ref and return.
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index 37cef81..71f40ac 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -89,7 +89,10 @@
   // If the context is empty, this method only creates a single PathClassLoader with the
   // given compilation_sources.
   //
-  // Notes:
+  // Shared libraries found in the chain will be canonicalized based on the dex files they
+  // contain.
+  //
+  // Implementation 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;
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index e4aae47..0756982 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -933,6 +933,91 @@
   VerifyClassLoaderPCL(context.get(), 0, "");
 }
 
+TEST_F(ClassLoaderContextTest, CreateClassLoaderWithSameSharedLibraries) {
+  // 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::string context_spec =
+      "PCL[" + CreateClassPath(classpath_dex_a) + "]{" +
+      "PCL[" + CreateClassPath(classpath_dex_b) + "]};" +
+      "PCL[" + CreateClassPath(classpath_dex_c) + "]{" +
+      "PCL[" + CreateClassPath(classpath_dex_b) + "]}";
+
+  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<6> hs(soa.Self());
+  Handle<mirror::ClassLoader> class_loader_1 = hs.NewHandle(
+      soa.Decode<mirror::ClassLoader>(jclass_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 : 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 its shared library.
+  ArtField* field =
+      jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_sharedLibraryLoaders);
+  ObjPtr<mirror::Object> raw_shared_libraries = field->GetObject(class_loader_1.Get());
+  ASSERT_TRUE(raw_shared_libraries != nullptr);
+
+  Handle<mirror::ObjectArray<mirror::ClassLoader>> shared_libraries(
+      hs.NewHandle(raw_shared_libraries->AsObjectArray<mirror::ClassLoader>()));
+  ASSERT_EQ(shared_libraries->GetLength(), 1);
+
+  Handle<mirror::ClassLoader> class_loader_2 = hs.NewHandle(shared_libraries->Get(0));
+  std::vector<const DexFile*> class_loader_2_dex_files =
+      MakeNonOwningPointerVector(classpath_dex_b);
+  VerifyClassLoaderDexFiles(soa,
+                            class_loader_2,
+                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            class_loader_2_dex_files);
+
+  // Verify the parent.
+  Handle<mirror::ClassLoader> class_loader_3 = hs.NewHandle(class_loader_1->GetParent());
+  std::vector<const DexFile*> class_loader_3_dex_files =
+      MakeNonOwningPointerVector(classpath_dex_c);
+  VerifyClassLoaderDexFiles(soa,
+                            class_loader_3,
+                            WellKnownClasses::dalvik_system_PathClassLoader,
+                            class_loader_3_dex_files);
+
+  // Verify its shared library is the same as the child.
+  raw_shared_libraries = field->GetObject(class_loader_3.Get());
+  ASSERT_TRUE(raw_shared_libraries != nullptr);
+  Handle<mirror::ObjectArray<mirror::ClassLoader>> shared_libraries_2(
+      hs.NewHandle(raw_shared_libraries->AsObjectArray<mirror::ClassLoader>()));
+  ASSERT_EQ(shared_libraries_2->GetLength(), 1);
+  ASSERT_EQ(shared_libraries_2->Get(0), class_loader_2.Get());
+
+  // Class loaders should have the BootClassLoader as a parent.
+  ASSERT_TRUE(class_loader_2->GetParent()->GetClass() ==
+      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+  ASSERT_TRUE(class_loader_3->GetParent()->GetClass() ==
+      soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+}
+
 TEST_F(ClassLoaderContextTest, EncodeInOatFile) {
   std::string dex1_name = GetTestDexFileName("Main");
   std::string dex2_name = GetTestDexFileName("MyClass");