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/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index fc72bbd..51a67ca 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -229,6 +229,8 @@
     if (oat_file_assistant.IsInBootClassPath()) {
       return kNoDexOptNeeded;
     }
+
+    // TODO(calin): Pass the class loader context as an argument to dexoptanalyzer. b/62269291.
     int dexoptNeeded = oat_file_assistant.GetDexOptNeeded(
         compiler_filter_, assume_profile_changed_, downgrade_);
 
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 07dfb65..d40e6d9 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -493,6 +493,8 @@
   if (oat_file_assistant.IsInBootClassPath()) {
     return OatFileAssistant::kNoDexOptNeeded;
   }
+
+  // TODO(calin): Extend DexFile.getDexOptNeeded to accept the class loader context. b/62269291.
   return oat_file_assistant.GetDexOptNeeded(filter, profile_changed, downgrade);
 }
 
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 6515cfa..075875b 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -1604,6 +1604,10 @@
   return GetOatHeader().GetCompilerFilter();
 }
 
+std::string OatFile::GetClassLoaderContext() const {
+  return GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey);
+};
+
 OatFile::OatClass OatFile::FindOatClass(const DexFile& dex_file,
                                         uint16_t class_def_idx,
                                         bool* found) {
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index e13126b..04cb3a0 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -115,6 +115,8 @@
 
   CompilerFilter::Filter GetCompilerFilter() const;
 
+  std::string GetClassLoaderContext() const;
+
   const std::string& GetLocation() const {
     return location_;
   }
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());
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 5eec943..6dc3c19 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -26,6 +26,7 @@
 #include "base/scoped_flock.h"
 #include "base/unix_file/fd_file.h"
 #include "compiler_filter.h"
+#include "class_loader_context.h"
 #include "oat_file.h"
 #include "os.h"
 
@@ -164,7 +165,8 @@
   // the oat file in the odex location.
   int GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
                       bool profile_changed = false,
-                      bool downgrade = false);
+                      bool downgrade = false,
+                      ClassLoaderContext* context = nullptr);
 
   // Returns true if there is up-to-date code for this dex location,
   // irrespective of the compiler filter of the up-to-date code.
@@ -194,7 +196,7 @@
   // to a string describing why there was a failure or the update was not
   // attempted. error_msg must not be null.
   ResultOfAttemptToUpdate MakeUpToDate(bool profile_changed,
-                                       const std::string& class_loader_context,
+                                       ClassLoaderContext* class_loader_context,
                                        std::string* error_msg);
 
   // Returns an oat file that can be used for loading dex files.
@@ -330,7 +332,8 @@
     // compiler filter.
     DexOptNeeded GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
                                  bool profile_changed,
-                                 bool downgrade);
+                                 bool downgrade,
+                                 ClassLoaderContext* context);
 
     // Returns the loaded file.
     // Loads the file if needed. Returns null if the file failed to load.
@@ -367,6 +370,8 @@
     // compiler filter.
     bool CompilerFilterIsOkay(CompilerFilter::Filter target, bool profile_changed, bool downgrade);
 
+    bool ClassLoaderContextIsOkay(ClassLoaderContext* context);
+
     // Release the loaded oat file.
     // Returns null if the oat file hasn't been loaded.
     //
@@ -404,7 +409,7 @@
   // attempted. error_msg must not be null.
   ResultOfAttemptToUpdate GenerateOatFileNoChecks(OatFileInfo& info,
                                                   CompilerFilter::Filter target,
-                                                  const std::string& class_loader_context,
+                                                  const ClassLoaderContext* class_loader_context,
                                                   std::string* error_msg);
 
   // Return info for the best oat file.
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index e048177..3ecd1b5 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -40,6 +40,7 @@
 namespace art {
 
 static const std::string kSpecialSharedLibrary = "&";
+static ClassLoaderContext* kSpecialSharedLibraryContext = nullptr;
 
 class OatFileAssistantTest : public DexoptTest {};
 
@@ -121,7 +122,7 @@
   // Trying to make the oat file up to date should not fail or crash.
   std::string error_msg;
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-          oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
+          oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg));
 
   // Trying to get the best oat file should fail, but not crash.
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
@@ -774,7 +775,7 @@
   std::string error_msg;
   Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) <<
           error_msg;
 
   EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
@@ -955,7 +956,7 @@
   // We should get kUpdateSucceeded from MakeUpToDate since there's nothing
   // that can be done in this situation.
   ASSERT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg));
 
   // Verify it didn't create an oat in the default location (dalvik-cache).
   OatFileAssistant ofm(dex_location.c_str(), kRuntimeISA, false);
@@ -1034,7 +1035,7 @@
   std::string error_msg;
   Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg));
   EXPECT_TRUE(error_msg.empty());
 }
 
@@ -1181,7 +1182,7 @@
   std::string error_msg;
   Runtime::Current()->AddCompilerOption("--compiler-filter=quicken");
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) <<
           error_msg;
   EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
       oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken));
@@ -1190,7 +1191,7 @@
 
   Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg))
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg))
           << error_msg;
   EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
       oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken));
@@ -1199,7 +1200,7 @@
 
   Runtime::Current()->AddCompilerOption("--compiler-filter=bogus");
   EXPECT_EQ(OatFileAssistant::kUpdateNotAttempted,
-      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg));
 }
 
 TEST(OatFileAssistantUtilsTest, DexLocationToOdexFilename) {
@@ -1259,7 +1260,7 @@
       OatFileAssistant::kDefaultCompilerFilterForDexLoading;
   std::string error_msg;
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) <<
           error_msg;
   EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
             oat_file_assistant.GetDexOptNeeded(default_filter));
@@ -1277,7 +1278,7 @@
   const CompilerFilter::Filter default_filter =
       OatFileAssistant::kDefaultCompilerFilterForDexLoading;
   std::string error_msg;
-  int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg);
+  int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg);
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
   EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
       oat_file_assistant.GetDexOptNeeded(default_filter));
@@ -1299,19 +1300,52 @@
       OatFileAssistant::kDefaultCompilerFilterForDexLoading;
   std::string error_msg;
   std::string context_str = "PCL[" + context_location + "]";
-  int status = oat_file_assistant.MakeUpToDate(false, context_str, &error_msg);
+  std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str);
+  ASSERT_TRUE(context != nullptr);
+  ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, ""));
+
+  int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg);
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
   EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
-  oat_file_assistant.GetDexOptNeeded(default_filter));
+            oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get()));
+
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
   EXPECT_NE(nullptr, oat_file.get());
-  std::unique_ptr<ClassLoaderContext> context =
-      ClassLoaderContext::Create(context_str);
-  context->OpenDexFiles(kRuntimeISA, "");
   EXPECT_EQ(context->EncodeContextForOatFile(""),
       oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
 }
 
+TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  std::string context_location = GetScratchDir() + "/ContextDex.jar";
+  Copy(GetDexSrc1(), dex_location);
+  Copy(GetDexSrc2(), context_location);
+
+  OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
+
+  const CompilerFilter::Filter default_filter =
+      OatFileAssistant::kDefaultCompilerFilterForDexLoading;
+  std::string error_msg;
+  std::string context_str = "PCL[" + context_location + "]";
+  std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str);
+  ASSERT_TRUE(context != nullptr);
+  ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, ""));
+
+  int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg);
+  EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get()));
+
+  // Update the context by overriding the jar file.
+  Copy(GetMultiDexSrc2(), context_location);
+  std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str);
+  ASSERT_TRUE(updated_context != nullptr);
+  // DexOptNeeded should advise compilation from scratch.
+  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+            oat_file_assistant.GetDexOptNeeded(
+                  default_filter, false, false, updated_context.get()));
+}
+
 // TODO: More Tests:
 //  * Test class linker falls back to unquickened dex for DexNoOat
 //  * Test class linker falls back to unquickened dex for MultiDexNoOat
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index 499f356..516c833 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -361,8 +361,7 @@
 
   // If the pat file loading context matches the context used during compilation then we accept
   // the oat file without addition checks
-  if (context->VerifyClassLoaderContextMatch(
-      oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey))) {
+  if (context->VerifyClassLoaderContextMatch(oat_file->GetClassLoaderContext())) {
     return false;
   }
 
@@ -426,12 +425,9 @@
     // Update the oat file on disk if we can, based on the --compiler-filter
     // option derived from the current runtime options.
     // This may fail, but that's okay. Best effort is all that matters here.
-
-    const std::string& dex2oat_context = context == nullptr
-        ? OatFile::kSpecialSharedLibrary
-        : context->EncodeContextForDex2oat(/*base_dir*/ "");
-    switch (oat_file_assistant.MakeUpToDate(
-        /*profile_changed*/false, dex2oat_context, /*out*/ &error_msg)) {
+    switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/false,
+                                            context.get(),
+                                            /*out*/ &error_msg)) {
       case OatFileAssistant::kUpdateFailed:
         LOG(WARNING) << error_msg;
         break;