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 c890f8b..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