Support InMemoryDexClassLoader in ClassLoaderContext

Add new class loader tag IMC to class loader context spec which
represents InMemoryDexClassLoader. A special case is required to not
attempt to open its dex files as the dex location does not correspond
to a real file path. This is achieved by setting load-attempted variable
to 'true' when encountering IMC whilst parsing a spec. Context with IMC
can still have opened dex files if it was created from an existing class
loader.

Bug: 72131483
Test: m test-art-host-gtest-class_loader_context_text
Change-Id: Ic64065819018a1e56dee0f65405d26beb8fd7bbd
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index c7cf43f..1dd856e 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -9460,7 +9460,9 @@
   CHECK(self->GetJniEnv()->IsSameObject(loader_class,
                                         WellKnownClasses::dalvik_system_PathClassLoader) ||
         self->GetJniEnv()->IsSameObject(loader_class,
-                                        WellKnownClasses::dalvik_system_DelegateLastClassLoader));
+                                        WellKnownClasses::dalvik_system_DelegateLastClassLoader) ||
+        self->GetJniEnv()->IsSameObject(loader_class,
+                                        WellKnownClasses::dalvik_system_InMemoryDexClassLoader));
 
   // SOAAlreadyRunnable is protected, and we need something to add a global reference.
   // We could move the jobject to the callers, but all call-sites do this...
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 61843f4..165b42d 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -44,6 +44,7 @@
 
 static constexpr char kPathClassLoaderString[] = "PCL";
 static constexpr char kDelegateLastClassLoaderString[] = "DLC";
+static constexpr char kInMemoryDexClassLoaderString[] = "IMC";
 static constexpr char kClassLoaderOpeningMark = '[';
 static constexpr char kClassLoaderClosingMark = ']';
 static constexpr char kClassLoaderSharedLibraryOpeningMark = '{';
@@ -154,6 +155,24 @@
   if (class_loader_type == kInvalidClassLoader) {
     return nullptr;
   }
+
+  // InMemoryDexClassLoader's dex location is always bogus. Special-case it.
+  if (class_loader_type == kInMemoryDexClassLoader) {
+    if (parse_checksums) {
+      // Make sure that OpenDexFiles() will never be attempted on this context
+      // because the dex locations of IMC do not correspond to real files.
+      CHECK(!dex_files_open_attempted_ || !dex_files_open_result_)
+          << "Parsing spec not supported when context created from a ClassLoader object";
+      dex_files_open_attempted_ = true;
+      dex_files_open_result_ = false;
+    } else {
+      // Checksums are not provided and dex locations themselves have no meaning
+      // (although we keep them in the spec to simplify parsing). Treat this as
+      // an unknown class loader.
+      return nullptr;
+    }
+  }
+
   const char* class_loader_type_str = GetClassLoaderTypeName(class_loader_type);
   size_t type_str_size = strlen(class_loader_type_str);
 
@@ -192,6 +211,7 @@
       if (!android::base::ParseUint(dex_file_with_checksum[1].c_str(), &checksum)) {
         return nullptr;
       }
+
       info->classpath.push_back(dex_file_with_checksum[0]);
       info->checksums.push_back(checksum);
     }
@@ -261,7 +281,9 @@
 // recognized.
 ClassLoaderContext::ClassLoaderType
 ClassLoaderContext::ExtractClassLoaderType(const std::string& class_loader_spec) {
-  const ClassLoaderType kValidTypes[] = {kPathClassLoader, kDelegateLastClassLoader};
+  const ClassLoaderType kValidTypes[] = { kPathClassLoader,
+                                          kDelegateLastClassLoader,
+                                          kInMemoryDexClassLoader };
   for (const ClassLoaderType& type : kValidTypes) {
     const char* type_str = GetClassLoaderTypeName(type);
     if (class_loader_spec.compare(0, strlen(type_str), type_str) == 0) {
@@ -649,6 +671,8 @@
       return WellKnownClasses::dalvik_system_PathClassLoader;
     case ClassLoaderContext::kDelegateLastClassLoader:
       return WellKnownClasses::dalvik_system_DelegateLastClassLoader;
+    case ClassLoaderContext::kInMemoryDexClassLoader:
+      return WellKnownClasses::dalvik_system_InMemoryDexClassLoader;
     case ClassLoaderContext::kInvalidClassLoader: break;  // will fail after the switch.
   }
   LOG(FATAL) << "Invalid class loader type " << type;
@@ -818,6 +842,7 @@
   switch (type) {
     case kPathClassLoader: return kPathClassLoaderString;
     case kDelegateLastClassLoader: return kDelegateLastClassLoaderString;
+    case kInMemoryDexClassLoader: return kInMemoryDexClassLoaderString;
     default:
       LOG(FATAL) << "Invalid class loader type " << type;
       UNREACHABLE();
@@ -867,7 +892,9 @@
                                                     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));
+  CHECK(IsPathOrDexClassLoader(soa, class_loader) ||
+        IsDelegateLastClassLoader(soa, class_loader) ||
+        IsInMemoryDexClassLoader(soa, class_loader));
 
   // All supported class loaders inherit from BaseDexClassLoader.
   // We need to get the DexPathList and loop through it.
@@ -986,6 +1013,8 @@
     type = kPathClassLoader;
   } else if (IsDelegateLastClassLoader(soa, class_loader)) {
     type = kDelegateLastClassLoader;
+  } else if (IsInMemoryDexClassLoader(soa, class_loader)) {
+    type = kInMemoryDexClassLoader;
   } else {
     LOG(WARNING) << "Unsupported class loader";
     return false;
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index f8387ce..224b4d6 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -45,7 +45,8 @@
   enum ClassLoaderType {
     kInvalidClassLoader = 0,
     kPathClassLoader = 1,
-    kDelegateLastClassLoader = 2
+    kDelegateLastClassLoader = 2,
+    kInMemoryDexClassLoader = 3
   };
 
   ~ClassLoaderContext();
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index 3c5f1ef..7fba830 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -61,6 +61,13 @@
         context, index, ClassLoaderContext::kDelegateLastClassLoader, classpath);
   }
 
+  void VerifyClassLoaderIMC(ClassLoaderContext* context,
+                            size_t index,
+                            const std::string& classpath) {
+    VerifyClassLoaderInfo(
+        context, index, ClassLoaderContext::kInMemoryDexClassLoader, classpath);
+  }
+
   void VerifyClassLoaderSharedLibraryPCL(ClassLoaderContext* context,
                                          size_t loader_index,
                                          size_t shared_library_index,
@@ -70,6 +77,15 @@
         classpath);
   }
 
+  void VerifyClassLoaderSharedLibraryIMC(ClassLoaderContext* context,
+                                         size_t loader_index,
+                                         size_t shared_library_index,
+                                         const std::string& classpath) {
+    VerifyClassLoaderInfoSL(
+        context, loader_index, shared_library_index, ClassLoaderContext::kInMemoryDexClassLoader,
+        classpath);
+  }
+
   void VerifySharedLibrariesSize(ClassLoaderContext* context,
                                  size_t loader_index,
                                  size_t expected_size) {
@@ -102,6 +118,13 @@
         context, index, ClassLoaderContext::kDelegateLastClassLoader, test_name);
   }
 
+  void VerifyClassLoaderIMCFromTestDex(ClassLoaderContext* context,
+                                       size_t index,
+                                       const std::string& test_name) {
+    VerifyClassLoaderFromTestDex(
+        context, index, ClassLoaderContext::kInMemoryDexClassLoader, test_name);
+  }
+
   enum class LocationCheck {
     kEquals,
     kEndsWith
@@ -249,19 +272,24 @@
 }
 
 TEST_F(ClassLoaderContextTest, ParseValidContextPCL) {
-  std::unique_ptr<ClassLoaderContext> context =
-      ClassLoaderContext::Create("PCL[a.dex]");
+  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]");
+  std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create("DLC[a.dex]");
   VerifyContextSize(context.get(), 1);
   VerifyClassLoaderDLC(context.get(), 0, "a.dex");
 }
 
+TEST_F(ClassLoaderContextTest, ParseInvalidContextIMC) {
+  // IMC is treated as an unknown class loader unless a checksum is provided.
+  // This is because the dex location is always bogus.
+  std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create("IMC[a.dex]");
+  ASSERT_TRUE(context == nullptr);
+}
+
 TEST_F(ClassLoaderContextTest, ParseValidContextChain) {
   std::unique_ptr<ClassLoaderContext> context =
       ClassLoaderContext::Create("PCL[a.dex:b.dex];DLC[c.dex:d.dex];PCL[e.dex]");
@@ -408,7 +436,6 @@
     return;
   }
 
-
   std::unique_ptr<ClassLoaderContext> context =
       ClassLoaderContext::Create(
           "PCL[" + multidex_name + ":" + myclass_dex_name + "];" +
@@ -469,6 +496,19 @@
   ASSERT_FALSE(context->OpenDexFiles(InstructionSet::kArm, ""));
 }
 
+TEST_F(ClassLoaderContextTest, OpenDexFilesForIMCFails) {
+  std::unique_ptr<ClassLoaderContext> context;
+  std::string dex_name = GetTestDexFileName("Main");
+
+  context = ParseContextWithChecksums("PCL[" + dex_name + "*111]");
+  VerifyContextSize(context.get(), 1);
+  ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, "."));
+
+  context = ParseContextWithChecksums("IMC[" + dex_name + "*111]");
+  VerifyContextSize(context.get(), 1);
+  ASSERT_FALSE(context->OpenDexFiles(InstructionSet::kArm, "."));
+}
+
 TEST_F(ClassLoaderContextTest, CreateClassLoader) {
   std::string dex_name = GetTestDexFileName("Main");
   std::unique_ptr<ClassLoaderContext> context =
@@ -1079,6 +1119,34 @@
   VerifyClassLoaderPCLFromTestDex(context.get(), 3, "ForClassLoaderA");
 }
 
+TEST_F(ClassLoaderContextTest, CreateContextForClassLoaderIMC) {
+  // The chain is
+  //    ClassLoaderA (PathClassLoader)
+  //       ^
+  //       |
+  //    ClassLoaderB (InMemoryDexClassLoader)
+  //       ^
+  //       |
+  //    ClassLoaderC (InMemoryDexClassLoader)
+  //       ^
+  //       |
+  //    ClassLoaderD (DelegateLastClassLoader)
+
+  jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
+  jobject class_loader_b = LoadDexInInMemoryDexClassLoader("ForClassLoaderB", class_loader_a);
+  jobject class_loader_c = LoadDexInInMemoryDexClassLoader("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");
+  VerifyClassLoaderIMCFromTestDex(context.get(), 1, "ForClassLoaderC");
+  VerifyClassLoaderIMCFromTestDex(context.get(), 2, "ForClassLoaderB");
+  VerifyClassLoaderPCLFromTestDex(context.get(), 3, "ForClassLoaderA");
+}
 
 TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextFirstElement) {
   std::string context_spec = "PCL[]";
@@ -1133,6 +1201,22 @@
             ClassLoaderContext::VerificationResult::kMismatch);
 }
 
+TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextWithIMCMatch) {
+  std::string context_spec = "PCL[a.dex*123:b.dex*456];DLC[c.dex*890];IMC[d.dex*111]";
+  std::unique_ptr<ClassLoaderContext> context = ParseContextWithChecksums(context_spec);
+  // Pretend that we successfully open the dex files to pass the DCHECKS.
+  // (as it's much easier to test all the corner cases without relying on actual dex files).
+  PretendContextOpenedDexFiles(context.get());
+
+  VerifyContextSize(context.get(), 3);
+  VerifyClassLoaderPCL(context.get(), 0, "a.dex:b.dex");
+  VerifyClassLoaderDLC(context.get(), 1, "c.dex");
+  VerifyClassLoaderIMC(context.get(), 2, "d.dex");
+
+  ASSERT_EQ(context->VerifyClassLoaderContextMatch(context_spec),
+            ClassLoaderContext::VerificationResult::kVerifies);
+}
+
 TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchSpecial) {
   std::string context_spec = "&";
   std::unique_ptr<ClassLoaderContext> context = ParseContextWithChecksums(context_spec);
@@ -1200,6 +1284,25 @@
             ClassLoaderContext::VerificationResult::kMismatch);
 }
 
+TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchWithIMCSL) {
+  std::string context_spec =
+      "IMC[a.dex*123:b.dex*456]{IMC[d.dex*321];IMC[e.dex*654]#IMC[f.dex*098:g.dex*999]}"
+      ";DLC[c.dex*890]";
+  std::unique_ptr<ClassLoaderContext> context = ParseContextWithChecksums(context_spec);
+  // Pretend that we successfully open the dex files to pass the DCHECKS.
+  // (as it's much easier to test all the corner cases without relying on actual dex files).
+  PretendContextOpenedDexFiles(context.get());
+
+  VerifyContextSize(context.get(), 2);
+  VerifyClassLoaderIMC(context.get(), 0, "a.dex:b.dex");
+  VerifyClassLoaderDLC(context.get(), 1, "c.dex");
+  VerifyClassLoaderSharedLibraryIMC(context.get(), 0, 0, "d.dex");
+  VerifyClassLoaderSharedLibraryIMC(context.get(), 0, 1, "f.dex:g.dex");
+
+  ASSERT_EQ(context->VerifyClassLoaderContextMatch(context_spec),
+            ClassLoaderContext::VerificationResult::kVerifies);
+}
+
 TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncoding) {
   jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
   jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a);
@@ -1223,6 +1326,29 @@
             ClassLoaderContext::VerificationResult::kVerifies);
 }
 
+TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncodingIMC) {
+  jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
+  jobject class_loader_b = LoadDexInInMemoryDexClassLoader("ForClassLoaderB", class_loader_a);
+  jobject class_loader_c = LoadDexInInMemoryDexClassLoader("ForClassLoaderC", class_loader_b);
+  jobject class_loader_d = LoadDexInDelegateLastClassLoader("ForClassLoaderD", class_loader_c);
+
+  std::unique_ptr<ClassLoaderContext> context = CreateContextForClassLoader(class_loader_d);
+
+  std::string context_with_no_base_dir = context->EncodeContextForOatFile("");
+  ASSERT_EQ(context->VerifyClassLoaderContextMatch(context_with_no_base_dir),
+            ClassLoaderContext::VerificationResult::kVerifies);
+
+  std::string dex_location = GetTestDexFileName("ForClassLoaderA");
+  size_t pos = dex_location.rfind('/');
+  ASSERT_NE(std::string::npos, pos);
+  std::string parent = dex_location.substr(0, pos);
+
+  std::string context_with_base_dir = context->EncodeContextForOatFile(parent);
+  ASSERT_NE(context_with_base_dir, context_with_no_base_dir);
+  ASSERT_EQ(context->VerifyClassLoaderContextMatch(context_with_base_dir),
+            ClassLoaderContext::VerificationResult::kVerifies);
+}
+
 TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncodingMultidex) {
   jobject class_loader = LoadDexInPathClassLoader("MultiDex", nullptr);
 
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index ca410d9..87c7ba6 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -331,6 +331,13 @@
                                        parent_loader);
 }
 
+jobject CommonRuntimeTestImpl::LoadDexInInMemoryDexClassLoader(const std::string& dex_name,
+                                                               jobject parent_loader) {
+  return LoadDexInWellKnownClassLoader(dex_name,
+                                       WellKnownClasses::dalvik_system_InMemoryDexClassLoader,
+                                       parent_loader);
+}
+
 void CommonRuntimeTestImpl::FillHeap(Thread* self,
                                      ClassLinker* class_linker,
                                      VariableSizedHandleScope* handle_scope) {
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index ab90fc5..9b1e653 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -125,6 +125,7 @@
                                    jobject parent_loader,
                                    jobject shared_libraries = nullptr);
   jobject LoadDexInDelegateLastClassLoader(const std::string& dex_name, jobject parent_loader);
+  jobject LoadDexInInMemoryDexClassLoader(const std::string& dex_name, jobject parent_loader);
   jobject LoadDexInWellKnownClassLoader(const std::string& dex_name,
                                         jclass loader_class,
                                         jobject parent_loader,