[art] Add classpath check to DexFile.getDexOptNeeded

Extend DexFile.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.

(cherry picked from commit baafa55aede7cc43a27d76bd88ccc02152123ecf)

Bug: 62269291
Test: m test-art-host
Merged-In: I3a8e9321fe602712fac3595b790168b271918f10
Change-Id: I3a8e9321fe602712fac3595b790168b271918f10
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 08d38d5..3a2c7fc 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -19,6 +19,7 @@
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "compiler_filter.h"
+#include "class_loader_context.h"
 #include "dex_file.h"
 #include "noop_compiler_callbacks.h"
 #include "oat_file_assistant.h"
@@ -175,7 +176,15 @@
         oat_fd_ = std::stoi(option.substr(strlen("--oat-fd=")).ToString(), nullptr, 0);
       } else if (option.starts_with("--vdex-fd")) {
         vdex_fd_ = std::stoi(option.substr(strlen("--vdex-fd=")).ToString(), nullptr, 0);
-      } else { Usage("Unknown argument '%s'", option.data()); }
+      } else if (option.starts_with("--class-loader-context=")) {
+        std::string context_str = option.substr(strlen("--class-loader-context=")).ToString();
+        class_loader_context_ = ClassLoaderContext::Create(context_str);
+        if (class_loader_context_ == nullptr) {
+          Usage("Invalid --class-loader-context '%s'", context_str.c_str());
+        }
+      } else {
+        Usage("Unknown argument '%s'", option.data());
+      }
     }
 
     if (image_.empty()) {
@@ -255,9 +264,8 @@
       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_);
+        compiler_filter_, assume_profile_changed_, downgrade_, class_loader_context_.get());
 
     // Convert OatFileAssitant codes to dexoptanalyzer codes.
     switch (dexoptNeeded) {
@@ -280,6 +288,7 @@
   std::string dex_file_;
   InstructionSet isa_;
   CompilerFilter::Filter compiler_filter_;
+  std::unique_ptr<ClassLoaderContext> class_loader_context_;
   bool assume_profile_changed_;
   bool downgrade_;
   std::string image_;
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 4ab8908..4512227 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -23,6 +23,7 @@
 #include "base/logging.h"
 #include "base/stl_util.h"
 #include "class_linker.h"
+#include <class_loader_context.h>
 #include "common_throws.h"
 #include "compiler_filter.h"
 #include "dex_file-inl.h"
@@ -459,6 +460,7 @@
                             const char* filename,
                             const char* instruction_set,
                             const char* compiler_filter_name,
+                            const char* class_loader_context,
                             bool profile_changed,
                             bool downgrade) {
   if ((filename == nullptr) || !OS::FileExists(filename)) {
@@ -485,6 +487,19 @@
     return -1;
   }
 
+  std::unique_ptr<ClassLoaderContext> context = nullptr;
+  if (class_loader_context != nullptr) {
+    context = ClassLoaderContext::Create(class_loader_context);
+
+    if (context == nullptr) {
+      ScopedLocalRef<jclass> iae(env, env->FindClass("java/lang/IllegalArgumentException"));
+      std::string message(StringPrintf("Class loader context '%s' is invalid.",
+                                       class_loader_context));
+      env->ThrowNew(iae.get(), message.c_str());
+      return -1;
+    }
+  }
+
   // TODO: Verify the dex location is well formed, and throw an IOException if
   // not?
 
@@ -495,8 +510,10 @@
     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);
+  return oat_file_assistant.GetDexOptNeeded(filter,
+                                            profile_changed,
+                                            downgrade,
+                                            context.get());
 }
 
 static jstring DexFile_getDexFileStatus(JNIEnv* env,
@@ -532,6 +549,7 @@
                                     jstring javaFilename,
                                     jstring javaInstructionSet,
                                     jstring javaTargetCompilerFilter,
+                                    jstring javaClassLoaderContext,
                                     jboolean newProfile,
                                     jboolean downgrade) {
   ScopedUtfChars filename(env, javaFilename);
@@ -549,10 +567,16 @@
     return -1;
   }
 
+  NullableScopedUtfChars class_loader_context(env, javaClassLoaderContext);
+  if (env->ExceptionCheck()) {
+    return -1;
+  }
+
   return GetDexOptNeeded(env,
                          filename.c_str(),
                          instruction_set.c_str(),
                          target_compiler_filter.c_str(),
+                         class_loader_context.c_str(),
                          newProfile == JNI_TRUE,
                          downgrade == JNI_TRUE);
 }
@@ -731,7 +755,7 @@
   NATIVE_METHOD(DexFile, getClassNameList, "(Ljava/lang/Object;)[Ljava/lang/String;"),
   NATIVE_METHOD(DexFile, isDexOptNeeded, "(Ljava/lang/String;)Z"),
   NATIVE_METHOD(DexFile, getDexOptNeeded,
-                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)I"),
+                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)I"),
   NATIVE_METHOD(DexFile, openDexFileNative,
                 "(Ljava/lang/String;"
                 "Ljava/lang/String;"
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index a7fe9b1..fb61228 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -1168,12 +1168,13 @@
 
   const OatFile* file = GetFile();
   if (file == nullptr) {
-    return false;
+    // No oat file means we have nothing to verify.
+    return true;
   }
 
-  size_t dir_index = file->GetLocation().rfind('/');
+  size_t dir_index = oat_file_assistant_->dex_location_.rfind('/');
   std::string classpath_dir = (dir_index != std::string::npos)
-      ? file->GetLocation().substr(0, dir_index)
+      ? oat_file_assistant_->dex_location_.substr(0, dir_index)
       : "";
 
   if (!context->OpenDexFiles(oat_file_assistant_->isa_, classpath_dir)) {
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index d99036d..6d14971 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -1466,6 +1466,33 @@
                   default_filter, false, false, updated_context.get()));
 }
 
+TEST_F(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) {
+  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;
+
+  // A relative context simulates a dependent split context.
+  std::unique_ptr<ClassLoaderContext> relative_context =
+      ClassLoaderContext::Create("PCL[ContextDex.jar]");
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+            oat_file_assistant.GetDexOptNeeded(
+                default_filter, false, false, relative_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