Add stored class loader context option

Motivation: Enable having a different class loader context during
preopt vs the one stored in the oat file.

Added test.

Bug: 70934104
Bug: 67345922

Test: test-art-host
Change-Id: I6c0851370e0740e5f47faf25a5494022034f6fa4
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 6667fd6..693ead5 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -428,6 +428,10 @@
   UsageError("  --class-loader-context=<string spec>: a string specifying the intended");
   UsageError("      runtime loading context for the compiled dex files.");
   UsageError("");
+  UsageError("  --stored-class-loader-context=<string spec>: a string specifying the intended");
+  UsageError("      runtime loading context that is stored in the oat file. Overrides");
+  UsageError("      --class-loader-context. Note that this ignores the classpath_dir arg.");
+  UsageError("");
   UsageError("      It describes how the class loader chain should be built in order to ensure");
   UsageError("      classes are resolved during dex2aot as they would be resolved at runtime.");
   UsageError("      This spec will be encoded in the oat file. If at runtime the dex file is");
@@ -1260,11 +1264,32 @@
       ParseInstructionSetFeatures(*args.Get(M::TargetInstructionSetFeatures), parser_options.get());
     }
     if (args.Exists(M::ClassLoaderContext)) {
-      class_loader_context_ = ClassLoaderContext::Create(*args.Get(M::ClassLoaderContext));
+      std::string class_loader_context_arg = *args.Get(M::ClassLoaderContext);
+      class_loader_context_ = ClassLoaderContext::Create(class_loader_context_arg);
       if (class_loader_context_ == nullptr) {
         Usage("Option --class-loader-context has an incorrect format: %s",
-              args.Get(M::ClassLoaderContext)->c_str());
+              class_loader_context_arg.c_str());
       }
+      if (args.Exists(M::StoredClassLoaderContext)) {
+        stored_class_loader_context_.reset(new std::string(*args.Get(M::StoredClassLoaderContext)));
+        std::unique_ptr<ClassLoaderContext> temp_context =
+            ClassLoaderContext::Create(*stored_class_loader_context_);
+        if (temp_context == nullptr) {
+          Usage("Option --stored-class-loader-context has an incorrect format: %s",
+                stored_class_loader_context_->c_str());
+        } else if (!class_loader_context_->VerifyClassLoaderContextMatch(
+            *stored_class_loader_context_,
+            /*verify_names*/ false,
+            /*verify_checksums*/ false)) {
+          Usage(
+              "Option --stored-class-loader-context '%s' mismatches --class-loader-context '%s'",
+               stored_class_loader_context_->c_str(),
+               class_loader_context_arg.c_str());
+        }
+      }
+    } else if (args.Exists(M::StoredClassLoaderContext)) {
+      Usage("Option --stored-class-loader-context should only be used if "
+            "--class-loader-context is also specified");
     }
 
     if (!ReadCompilerOptions(args, compiler_options_.get(), &error_msg)) {
@@ -1579,7 +1604,7 @@
 
       if (class_loader_context_ == nullptr) {
         // If no context was specified use the default one (which is an empty PathClassLoader).
-        class_loader_context_ = std::unique_ptr<ClassLoaderContext>(ClassLoaderContext::Default());
+        class_loader_context_ = ClassLoaderContext::Default();
       }
 
       DCHECK_EQ(oat_writers_.size(), 1u);
@@ -1605,8 +1630,15 @@
       }
 
       // Store the class loader context in the oat header.
-      key_value_store_->Put(OatHeader::kClassPathKey,
-                            class_loader_context_->EncodeContextForOatFile(classpath_dir_));
+      // TODO: deprecate this since store_class_loader_context should be enough to cover the users
+      // of classpath_dir as well.
+      std::string class_path_key;
+      if (stored_class_loader_context_ != nullptr) {
+        class_path_key = *stored_class_loader_context_;
+      } else {
+        class_path_key = class_loader_context_->EncodeContextForOatFile(classpath_dir_);
+      }
+      key_value_store_->Put(OatHeader::kClassPathKey, class_path_key);
     }
 
     // Now that we have finalized key_value_store_, start writing the oat file.
@@ -2855,6 +2887,9 @@
   // The spec describing how the class loader should be setup for compilation.
   std::unique_ptr<ClassLoaderContext> class_loader_context_;
 
+  // The class loader context stored in the oat file. May be equal to class_loader_context_.
+  std::unique_ptr<std::string> stored_class_loader_context_;
+
   size_t thread_count_;
   uint64_t start_ns_;
   uint64_t start_cputime_ns_;
diff --git a/dex2oat/dex2oat_options.cc b/dex2oat/dex2oat_options.cc
index 0d68f4f..5843691 100644
--- a/dex2oat/dex2oat_options.cc
+++ b/dex2oat/dex2oat_options.cc
@@ -245,6 +245,9 @@
       .Define("--class-loader-context=_")
           .WithType<std::string>()
           .IntoKey(M::ClassLoaderContext)
+      .Define("--stored-class-loader-context=_")
+          .WithType<std::string>()
+          .IntoKey(M::StoredClassLoaderContext)
       .Define("--compact-dex-level=_")
           .WithType<CompactDexLevel>()
           .WithValueMap({{"none", CompactDexLevel::kCompactDexLevelNone},
diff --git a/dex2oat/dex2oat_options.def b/dex2oat/dex2oat_options.def
index 01f9d94..1a913a9 100644
--- a/dex2oat/dex2oat_options.def
+++ b/dex2oat/dex2oat_options.def
@@ -88,6 +88,7 @@
 DEX2OAT_OPTIONS_KEY (Unit,                           ForceDeterminism)
 DEX2OAT_OPTIONS_KEY (std::string,                    ClasspathDir)
 DEX2OAT_OPTIONS_KEY (std::string,                    ClassLoaderContext)
+DEX2OAT_OPTIONS_KEY (std::string,                    StoredClassLoaderContext)
 DEX2OAT_OPTIONS_KEY (std::string,                    DirtyImageObjects)
 DEX2OAT_OPTIONS_KEY (std::vector<std::string>,       RuntimeOptions)
 DEX2OAT_OPTIONS_KEY (std::string,                    CompilationReason)
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index c890f8be..710a6af 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -2125,4 +2125,33 @@
   EXPECT_EQ(header.GetImageSection(ImageHeader::kSectionArtFields).Size(), 0u);
 }
 
+TEST_F(Dex2oatClassLoaderContextTest, StoredClassLoaderContext) {
+  const std::string out_dir = GetScratchDir();
+  const std::string odex_location = out_dir + "/base.odex";
+  const std::string valid_context = "PCL[" + GetUsedDexLocation() + "]";
+  const std::string stored_context = "PCL[/system/not_real_lib.jar]";
+  // The class path should not be valid and should fail being stored.
+  GenerateOdexForTest(GetTestDexFileName("ManyMethods"),
+                      odex_location,
+                      CompilerFilter::Filter::kQuicken,
+                      { "--class-loader-context=" + stored_context },
+                      true,  // expect_success
+                      false,  // use_fd
+                      [&](const OatFile& oat_file) {
+    EXPECT_NE(oat_file.GetClassLoaderContext(), stored_context);
+    EXPECT_NE(oat_file.GetClassLoaderContext(), valid_context);
+  });
+  // The stored context should match what we expect even though it's invalid.
+  GenerateOdexForTest(GetTestDexFileName("ManyMethods"),
+                      odex_location,
+                      CompilerFilter::Filter::kQuicken,
+                      { "--class-loader-context=" + valid_context,
+                        "--stored-class-loader-context=" + stored_context },
+                      true,  // expect_success
+                      false,  // use_fd
+                      [&](const OatFile& oat_file) {
+    EXPECT_EQ(oat_file.GetClassLoaderContext(), stored_context);
+  });
+}
+
 }  // namespace art
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index e646520..216ad8f 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -649,12 +649,16 @@
   return !location.empty() && location[0] == '/';
 }
 
-bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) const {
-  DCHECK(dex_files_open_attempted_);
-  DCHECK(dex_files_open_result_);
+bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec,
+                                                       bool verify_names,
+                                                       bool verify_checksums) const {
+  if (verify_names || verify_checksums) {
+    DCHECK(dex_files_open_attempted_);
+    DCHECK(dex_files_open_result_);
+  }
 
   ClassLoaderContext expected_context;
-  if (!expected_context.Parse(context_spec, /*parse_checksums*/ true)) {
+  if (!expected_context.Parse(context_spec, verify_checksums)) {
     LOG(WARNING) << "Invalid class loader context: " << context_spec;
     return false;
   }
@@ -693,8 +697,14 @@
       return false;
     }
 
-    DCHECK_EQ(info.classpath.size(), info.checksums.size());
-    DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size());
+    if (verify_checksums) {
+      DCHECK_EQ(info.classpath.size(), info.checksums.size());
+      DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size());
+    }
+
+    if (!verify_names) {
+      continue;
+    }
 
     for (size_t k = 0; k < info.classpath.size(); k++) {
       // Compute the dex location that must be compared.
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index 692a6cd..231acc4 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -104,7 +104,11 @@
   //    - the class loader from the same position have the same classpath
   //      (the order and checksum of the dex files matches)
   // This should be called after OpenDexFiles().
-  bool VerifyClassLoaderContextMatch(const std::string& context_spec) const;
+  // Names are only verified if verify_names is true.
+  // Checksums are only verified if verify_checksums is true.
+  bool VerifyClassLoaderContextMatch(const std::string& context_spec,
+                                     bool verify_names = true,
+                                     bool verify_checksums = true) const;
 
   // Creates the class loader context from the given string.
   // The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...