Add check that classpath is up to date to getDexOptNeeded

Extend getDexOptNeeded to factor into the decision the expected class
loader context. If the context does not match, oat file assistant and
dexoptanalyzer will advise kDex2OatFromScratch.

Note that this does not currently extend the java side
DexFile.getDexOptNeeded. The calls coming from the java side will continue
to ignore the classpath checks by passing null as the class loader
context.

Bug: 62269291
Test: m test-art-host
Change-Id: Ia01728c06810e418bbcbfe2a774d1f904d2525ba
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 3794f51..83a8e09 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -190,9 +190,13 @@
 
 int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target,
                                       bool profile_changed,
-                                      bool downgrade) {
+                                      bool downgrade,
+                                      ClassLoaderContext* class_loader_context) {
   OatFileInfo& info = GetBestInfo();
-  DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target, profile_changed, downgrade);
+  DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target,
+                                                    profile_changed,
+                                                    downgrade,
+                                                    class_loader_context);
   if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) {
     return dexopt_needed;
   }
@@ -227,7 +231,7 @@
 
 OatFileAssistant::ResultOfAttemptToUpdate
 OatFileAssistant::MakeUpToDate(bool profile_changed,
-                               const std::string& class_loader_context,
+                               ClassLoaderContext* class_loader_context,
                                std::string* error_msg) {
   CompilerFilter::Filter target;
   if (!GetRuntimeCompilerFilterOption(&target, error_msg)) {
@@ -245,7 +249,8 @@
   //   - however, MakeUpToDate will not always succeed (e.g. for primary apks, or for dex files
   //     loaded in other processes). So it boils down to how far do we want to complicate
   //     the logic in order to enable the use of oat files. Maybe its time to try simplify it.
-  switch (info.GetDexOptNeeded(target, profile_changed, /*downgrade*/ false)) {
+  switch (info.GetDexOptNeeded(
+        target, profile_changed, /*downgrade*/ false, class_loader_context)) {
     case kNoDexOptNeeded:
       return kUpdateSucceeded;
 
@@ -643,7 +648,7 @@
 OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChecks(
       OatFileAssistant::OatFileInfo& info,
       CompilerFilter::Filter filter,
-      const std::string& class_loader_context,
+      const ClassLoaderContext* class_loader_context,
       std::string* error_msg) {
   CHECK(error_msg != nullptr);
 
@@ -720,7 +725,10 @@
   args.push_back("--oat-fd=" + std::to_string(oat_file->Fd()));
   args.push_back("--oat-location=" + oat_file_name);
   args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter));
-  args.push_back("--class-loader-context=" + class_loader_context);
+  const std::string dex2oat_context = class_loader_context == nullptr
+        ? OatFile::kSpecialSharedLibrary
+        : class_loader_context->EncodeContextForDex2oat(/*base_dir*/ "");
+  args.push_back("--class-loader-context=" + dex2oat_context);
 
   if (!Dex2Oat(args, error_msg)) {
     // Manually delete the oat and vdex files. This ensures there is no garbage
@@ -1016,31 +1024,40 @@
 }
 
 OatFileAssistant::DexOptNeeded OatFileAssistant::OatFileInfo::GetDexOptNeeded(
-    CompilerFilter::Filter target, bool profile_changed, bool downgrade) {
+    CompilerFilter::Filter target,
+    bool profile_changed,
+    bool downgrade,
+    ClassLoaderContext* context) {
+
   bool compilation_desired = CompilerFilter::IsAotCompilationEnabled(target);
   bool filter_okay = CompilerFilterIsOkay(target, profile_changed, downgrade);
+  bool class_loader_context_okay = ClassLoaderContextIsOkay(context);
 
-  if (filter_okay && Status() == kOatUpToDate) {
-    // The oat file is in good shape as is.
-    return kNoDexOptNeeded;
-  }
+  // Only check the filter and relocation if the class loader context is ok.
+  // If it is not, we will return kDex2OatFromScratch as the compilation needs to be redone.
+  if (class_loader_context_okay) {
+    if (filter_okay && Status() == kOatUpToDate) {
+      // The oat file is in good shape as is.
+      return kNoDexOptNeeded;
+    }
 
-  if (filter_okay && !compilation_desired && Status() == kOatRelocationOutOfDate) {
-    // If no compilation is desired, then it doesn't matter if the oat
-    // file needs relocation. It's in good shape as is.
-    return kNoDexOptNeeded;
-  }
+    if (filter_okay && !compilation_desired && Status() == kOatRelocationOutOfDate) {
+      // If no compilation is desired, then it doesn't matter if the oat
+      // file needs relocation. It's in good shape as is.
+      return kNoDexOptNeeded;
+    }
 
-  if (filter_okay && Status() == kOatRelocationOutOfDate) {
-    return kDex2OatForRelocation;
-  }
+    if (filter_okay && Status() == kOatRelocationOutOfDate) {
+      return kDex2OatForRelocation;
+    }
 
-  if (IsUseable()) {
-    return kDex2OatForFilter;
-  }
+    if (IsUseable()) {
+      return kDex2OatForFilter;
+    }
 
-  if (Status() == kOatBootImageOutOfDate) {
-    return kDex2OatForBootImage;
+    if (Status() == kOatBootImageOutOfDate) {
+      return kDex2OatForBootImage;
+    }
   }
 
   if (oat_file_assistant_->HasOriginalDexFiles()) {
@@ -1090,6 +1107,36 @@
     CompilerFilter::IsAsGoodAs(current, target);
 }
 
+bool OatFileAssistant::OatFileInfo::ClassLoaderContextIsOkay(ClassLoaderContext* context) {
+  if (context == nullptr) {
+    VLOG(oat) << "ClassLoaderContext check ignored: null context";
+    return true;
+  }
+
+  const OatFile* file = GetFile();
+  if (file == nullptr) {
+    return false;
+  }
+
+  size_t dir_index = file->GetLocation().rfind('/');
+  std::string classpath_dir = (dir_index != std::string::npos)
+      ? file->GetLocation().substr(0, dir_index)
+      : "";
+
+  if (!context->OpenDexFiles(oat_file_assistant_->isa_, classpath_dir)) {
+    VLOG(oat) << "ClassLoaderContext check failed: dex files from the context could not be opened";
+    return false;
+  }
+
+  bool result = context->VerifyClassLoaderContextMatch(file->GetClassLoaderContext());
+  if (!result) {
+    VLOG(oat) << "ClassLoaderContext check failed. Context was "
+              << file->GetClassLoaderContext()
+              << ". The expected context is " << context->EncodeContextForOatFile(classpath_dir);
+  }
+  return result;
+}
+
 bool OatFileAssistant::OatFileInfo::IsExecutable() {
   const OatFile* file = GetFile();
   return (file != nullptr && file->IsExecutable());