Support FDs for class loader context dex files

When compiling secondary dex files, dex2oat/dexoptanalyzer must open
all dex files in the given class loader context. However, these tools
do not have the SELinux permission to open app data files and instead
rely on installd to open them and pass file descriptors via command
line arguments.

This patch extends ClassLoaderContext::OpenDexFiles to support opening
dex files from a provided list of FDs, assuming the order corresponds
to the flattened class loader context. FDs can be passed to dex2oat/
dexoptanalyzer using a new '--class-loader-context-fds=' command line
argument. The format is a colon-separated list of integers.

dexoptanalyzer is also extended with a separate mode in which
dexopt-needed analysis is not performed, only the class loader context
is flattened and list of its dex files is printed to standard output
as a colon-separated list of paths. This mode is enabled with
'--flatten-class-loader-context' and is used by installd to obtain a
list of files it should open for dex2oat/dexoptanalyzer.

Bug: 126674985
Test: atest installd_dexopt_test
Change-Id: I46a671c90d14ad8615508c106a88ac1ee8a4ef28
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index d04fa4e..e09b391 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -460,7 +460,7 @@
   UsageError("      --dex-file=src.dex then dex2oat will setup a PathClassLoader with classpath ");
   UsageError("      'lib1.dex:src.dex' and set its parent to a DelegateLastClassLoader with ");
   UsageError("      classpath 'lib2.dex'.");
-  UsageError("      ");
+  UsageError("");
   UsageError("      Note that the compiler will be tolerant if the source dex files specified");
   UsageError("      with --dex-file are found in the classpath. The source dex files will be");
   UsageError("      removed from any class loader's classpath possibly resulting in empty");
@@ -468,6 +468,10 @@
   UsageError("");
   UsageError("      Example: --class-loader-context=PCL[lib1.dex:lib2.dex];DLC[lib3.dex]");
   UsageError("");
+  UsageError("  --class-loader-context-fds=<fds>: a colon-separated list of file descriptors");
+  UsageError("      for dex files in --class-loader-context. Their order must be the same as");
+  UsageError("      dex files in flattened class loader context.");
+  UsageError("");
   UsageError("  --dirty-image-objects=<directory-path>: list of known dirty objects in the image.");
   UsageError("      The image writer will group them together.");
   UsageError("");
@@ -1171,6 +1175,17 @@
         Usage("Option --class-loader-context has an incorrect format: %s",
               class_loader_context_arg.c_str());
       }
+      if (args.Exists(M::ClassLoaderContextFds)) {
+        std::string str_fds_arg = *args.Get(M::ClassLoaderContextFds);
+        std::vector<std::string> str_fds = android::base::Split(str_fds_arg, ":");
+        for (const std::string& str_fd : str_fds) {
+          class_loader_context_fds_.push_back(std::stoi(str_fd, nullptr, 0));
+          if (class_loader_context_fds_.back() < 0) {
+            Usage("Option --class-loader-context-fds has incorrect format: %s",
+                str_fds_arg.c_str());
+          }
+        }
+      }
       if (args.Exists(M::StoredClassLoaderContext)) {
         const std::string stored_context_arg = *args.Get(M::StoredClassLoaderContext);
         stored_class_loader_context_ = ClassLoaderContext::Create(stored_context_arg);
@@ -1506,7 +1521,9 @@
       // (because the encoding adds the dex checksum...)
       // TODO(calin): consider redesigning this so we don't have to open the dex files before
       // creating the actual class loader.
-      if (!class_loader_context_->OpenDexFiles(runtime_->GetInstructionSet(), classpath_dir_)) {
+      if (!class_loader_context_->OpenDexFiles(runtime_->GetInstructionSet(),
+                                               classpath_dir_,
+                                               class_loader_context_fds_)) {
         // Do not abort if we couldn't open files from the classpath. They might be
         // apks without dex files and right now are opening flow will fail them.
         LOG(WARNING) << "Failed to open classpath dex files";
@@ -2690,6 +2707,10 @@
   // The spec describing how the class loader should be setup for compilation.
   std::unique_ptr<ClassLoaderContext> class_loader_context_;
 
+  // Optional list of file descriptors corresponding to dex file locations in
+  // flattened `class_loader_context_`.
+  std::vector<int> class_loader_context_fds_;
+
   // The class loader context stored in the oat file. May be equal to class_loader_context_.
   std::unique_ptr<ClassLoaderContext> stored_class_loader_context_;
 
diff --git a/dex2oat/dex2oat_options.cc b/dex2oat/dex2oat_options.cc
index 783b326..4a19fb1 100644
--- a/dex2oat/dex2oat_options.cc
+++ b/dex2oat/dex2oat_options.cc
@@ -238,6 +238,9 @@
       .Define("--class-loader-context=_")
           .WithType<std::string>()
           .IntoKey(M::ClassLoaderContext)
+      .Define("--class-loader-context-fds=_")
+          .WithType<std::string>()
+          .IntoKey(M::ClassLoaderContextFds)
       .Define("--stored-class-loader-context=_")
           .WithType<std::string>()
           .IntoKey(M::StoredClassLoaderContext)
diff --git a/dex2oat/dex2oat_options.def b/dex2oat/dex2oat_options.def
index 0b77859..0717e1a 100644
--- a/dex2oat/dex2oat_options.def
+++ b/dex2oat/dex2oat_options.def
@@ -86,6 +86,7 @@
 DEX2OAT_OPTIONS_KEY (std::string,                    ClasspathDir)
 DEX2OAT_OPTIONS_KEY (std::string,                    InvocationFile)
 DEX2OAT_OPTIONS_KEY (std::string,                    ClassLoaderContext)
+DEX2OAT_OPTIONS_KEY (std::string,                    ClassLoaderContextFds)
 DEX2OAT_OPTIONS_KEY (std::string,                    StoredClassLoaderContext)
 DEX2OAT_OPTIONS_KEY (std::string,                    DirtyImageObjects)
 DEX2OAT_OPTIONS_KEY (std::vector<std::string>,       RuntimeOptions)
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 988c612..b411118 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <iostream>
 #include <string>
 #include <string_view>
 
@@ -44,6 +45,11 @@
   kDex2OatForBootImageOdex = 4,
   kDex2OatForFilterOdex = 5,
 
+  // Success return code when executed with --flatten-class-loader-context.
+  // Success is typically signalled with a zero but we use a non-colliding
+  // code to communicate that the flattening code path was taken.
+  kFlattenClassLoaderContextSuccess = 50,
+
   kErrorInvalidArguments = 101,
   kErrorCannotCreateRuntime = 102,
   kErrorUnknownDexOptNeeded = 103
@@ -116,6 +122,17 @@
   UsageError("  --downgrade: optional, if the purpose of dexopt is to downgrade the dex file");
   UsageError("       By default, dexopt considers upgrade case.");
   UsageError("");
+  UsageError("  --class-loader-context=<string spec>: a string specifying the intended");
+  UsageError("      runtime loading context for the compiled dex files.");
+  UsageError("");
+  UsageError("  --class-loader-context-fds=<fds>: a colon-separated list of file descriptors");
+  UsageError("      for dex files in --class-loader-context. Their order must be the same as");
+  UsageError("      dex files in flattened class loader context.");
+  UsageError("");
+  UsageError("  --flatten-class-loader-context: parse --class-loader-context, flatten it and");
+  UsageError("      print a colon-separated list of its dex files to standard output. Dexopt");
+  UsageError("      needed analysis is not performed when this option is set.");
+  UsageError("");
   UsageError("Return code:");
   UsageError("  To make it easier to integrate with the internal tools this command will make");
   UsageError("    available its result (dexoptNeeded) as the exit/return code. i.e. it will not");
@@ -139,6 +156,7 @@
 class DexoptAnalyzer final {
  public:
   DexoptAnalyzer() :
+      only_flatten_context_(false),
       assume_profile_changed_(false),
       downgrade_(false) {}
 
@@ -206,6 +224,18 @@
         }
       } else if (StartsWith(option, "--class-loader-context=")) {
         context_str_ = std::string(option.substr(strlen("--class-loader-context=")));
+      } else if (StartsWith(option, "--class-loader-context-fds=")) {
+        std::string str_context_fds_arg =
+            std::string(option.substr(strlen("--class-loader-context-fds=")));
+        std::vector<std::string> str_fds = android::base::Split(str_context_fds_arg, ":");
+        for (const std::string& str_fd : str_fds) {
+          context_fds_.push_back(std::stoi(str_fd, nullptr, 0));
+          if (context_fds_.back() < 0) {
+            Usage("Invalid --class-loader-context-fds %s", str_context_fds_arg.c_str());
+          }
+        }
+      } else if (option == "--flatten-class-loader-context") {
+        only_flatten_context_ = true;
       } else {
         Usage("Unknown argument '%s'", raw_option);
       }
@@ -224,7 +254,7 @@
     }
   }
 
-  bool CreateRuntime() {
+  bool CreateRuntime() const {
     RuntimeOptions options;
     // The image could be custom, so make sure we explicitly pass it.
     std::string img = "-Ximage:" + image_;
@@ -257,7 +287,7 @@
     return true;
   }
 
-  int GetDexOptNeeded() {
+  int GetDexOptNeeded() const {
     if (!CreateRuntime()) {
       return kErrorCannotCreateRuntime;
     }
@@ -288,8 +318,11 @@
       return kNoDexOptNeeded;
     }
 
-    int dexoptNeeded = oat_file_assistant->GetDexOptNeeded(
-        compiler_filter_, assume_profile_changed_, downgrade_, class_loader_context.get());
+    int dexoptNeeded = oat_file_assistant->GetDexOptNeeded(compiler_filter_,
+                                                           assume_profile_changed_,
+                                                           downgrade_,
+                                                           class_loader_context.get(),
+                                                           context_fds_);
 
     // Convert OatFileAssitant codes to dexoptanalyzer codes.
     switch (dexoptNeeded) {
@@ -306,11 +339,35 @@
     }
   }
 
+  int FlattenClassLoaderContext() const {
+    DCHECK(only_flatten_context_);
+    if (context_str_.empty()) {
+      return kErrorInvalidArguments;
+    }
+
+    std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str_);
+    if (context == nullptr) {
+      Usage("Invalid --class-loader-context '%s'", context_str_.c_str());
+    }
+
+    std::cout << context->FlattenDexPaths() << std::flush;
+    return kFlattenClassLoaderContextSuccess;
+  }
+
+  int Run() const {
+    if (only_flatten_context_) {
+      return FlattenClassLoaderContext();
+    } else {
+      return GetDexOptNeeded();
+    }
+  }
+
  private:
   std::string dex_file_;
   InstructionSet isa_;
   CompilerFilter::Filter compiler_filter_;
   std::string context_str_;
+  bool only_flatten_context_;
   bool assume_profile_changed_;
   bool downgrade_;
   std::string image_;
@@ -319,6 +376,7 @@
   int vdex_fd_ = -1;
   // File descriptor corresponding to apk, dex_file, or zip.
   int zip_fd_ = -1;
+  std::vector<int> context_fds_;
 };
 
 static int dexoptAnalyze(int argc, char** argv) {
@@ -326,7 +384,7 @@
 
   // Parse arguments. Argument mistakes will lead to exit(kErrorInvalidArguments) in UsageError.
   analyzer.ParseArgs(argc, argv);
-  return analyzer.GetDexOptNeeded();
+  return analyzer.Run();
 }
 
 }  // namespace art
diff --git a/libdexfile/dex/art_dex_file_loader.cc b/libdexfile/dex/art_dex_file_loader.cc
index e1471f1..d17c61e 100644
--- a/libdexfile/dex/art_dex_file_loader.cc
+++ b/libdexfile/dex/art_dex_file_loader.cc
@@ -226,19 +226,44 @@
                             bool verify_checksum,
                             std::string* error_msg,
                             std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
-  ScopedTrace trace(std::string("Open dex file ") + std::string(location));
-  DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr";
   uint32_t magic;
   File fd = OpenAndReadMagic(filename, &magic, error_msg);
   if (fd.Fd() == -1) {
     DCHECK(!error_msg->empty());
     return false;
   }
+  return OpenWithMagic(
+      magic, fd.Release(), location, verify, verify_checksum, error_msg, dex_files);
+}
+
+bool ArtDexFileLoader::Open(int fd,
+                            const std::string& location,
+                            bool verify,
+                            bool verify_checksum,
+                            std::string* error_msg,
+                            std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
+  uint32_t magic;
+  if (!ReadMagicAndReset(fd, &magic, error_msg)) {
+    DCHECK(!error_msg->empty());
+    return false;
+  }
+  return OpenWithMagic(magic, fd, location, verify, verify_checksum, error_msg, dex_files);
+}
+
+bool ArtDexFileLoader::OpenWithMagic(uint32_t magic,
+                                     int fd,
+                                     const std::string& location,
+                                     bool verify,
+                                     bool verify_checksum,
+                                     std::string* error_msg,
+                                     std::vector<std::unique_ptr<const DexFile>>* dex_files) const {
+  ScopedTrace trace(std::string("Open dex file ") + std::string(location));
+  DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr";
   if (IsZipMagic(magic)) {
-    return OpenZip(fd.Release(), location, verify, verify_checksum, error_msg, dex_files);
+    return OpenZip(fd, location, verify, verify_checksum, error_msg, dex_files);
   }
   if (IsMagicValid(magic)) {
-    std::unique_ptr<const DexFile> dex_file(OpenFile(fd.Release(),
+    std::unique_ptr<const DexFile> dex_file(OpenFile(fd,
                                                      location,
                                                      verify,
                                                      verify_checksum,
@@ -251,7 +276,7 @@
       return false;
     }
   }
-  *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename);
+  *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", location.c_str());
   return false;
 }
 
diff --git a/libdexfile/dex/art_dex_file_loader.h b/libdexfile/dex/art_dex_file_loader.h
index d41eac5..bf38527 100644
--- a/libdexfile/dex/art_dex_file_loader.h
+++ b/libdexfile/dex/art_dex_file_loader.h
@@ -73,13 +73,19 @@
                                       bool verify_checksum,
                                       std::string* error_msg) const;
 
-  // Opens all .dex files found in the file, guessing the container format based on file extension.
+  // Opens all .dex files found in the file, guessing the container format based on file magic.
   bool Open(const char* filename,
             const std::string& location,
             bool verify,
             bool verify_checksum,
             std::string* error_msg,
             std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
+  bool Open(int fd,
+            const std::string& location,
+            bool verify,
+            bool verify_checksum,
+            std::string* error_msg,
+            std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
 
   // Open a single dex file from an fd. This function closes the fd.
   std::unique_ptr<const DexFile> OpenDex(int fd,
@@ -98,6 +104,14 @@
                std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
 
  private:
+  bool OpenWithMagic(uint32_t magic,
+                     int fd,
+                     const std::string& location,
+                     bool verify,
+                     bool verify_checksum,
+                     std::string* error_msg,
+                     std::vector<std::unique_ptr<const DexFile>>* dex_files) const;
+
   std::unique_ptr<const DexFile> OpenFile(int fd,
                                           const std::string& location,
                                           bool verify,
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index c5988f6..61843f4 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -365,7 +365,9 @@
 
 // Opens requested class path files and appends them to opened_dex_files. If the dex files have
 // been stripped, this opens them from their oat files (which get added to opened_oat_files).
-bool ClassLoaderContext::OpenDexFiles(InstructionSet isa, const std::string& classpath_dir) {
+bool ClassLoaderContext::OpenDexFiles(InstructionSet isa,
+                                      const std::string& classpath_dir,
+                                      const std::vector<int>& fds) {
   if (dex_files_open_attempted_) {
     // Do not attempt to re-open the files if we already tried.
     return dex_files_open_result_;
@@ -388,6 +390,7 @@
   std::vector<ClassLoaderInfo*> work_list;
   CHECK(class_loader_chain_ != nullptr);
   work_list.push_back(class_loader_chain_.get());
+  size_t dex_file_index = 0;
   while (!work_list.empty()) {
     ClassLoaderInfo* info = work_list.back();
     work_list.pop_back();
@@ -399,34 +402,59 @@
         location = classpath_dir + (classpath_dir.back() == '/' ? "" : "/") + location;
       }
 
+      // If file descriptors were provided for the class loader context dex paths,
+      // get the descriptor which correponds to this dex path. We assume the `fds`
+      // vector follows the same order as a flattened class loader context.
+      int fd = -1;
+      if (!fds.empty()) {
+        if (dex_file_index >= fds.size()) {
+          LOG(WARNING) << "Number of FDs is smaller than number of dex files in the context";
+          dex_files_open_result_ = false;
+          return false;
+        }
+
+        fd = fds[dex_file_index++];
+        DCHECK_GE(fd, 0);
+      }
+
       std::string error_msg;
       // When opening the dex files from the context we expect their checksum to match their
       // contents. So pass true to verify_checksum.
-      if (!dex_file_loader.Open(location.c_str(),
-                                location.c_str(),
-                                Runtime::Current()->IsVerificationEnabled(),
-                                /*verify_checksum=*/ true,
-                                &error_msg,
-                                &info->opened_dex_files)) {
-        // If we fail to open the dex file because it's been stripped, try to open the dex file
-        // from its corresponding oat file.
-        // This could happen when we need to recompile a pre-build whose dex code has been stripped.
-        // (for example, if the pre-build is only quicken and we want to re-compile it
-        // speed-profile).
-        // TODO(calin): Use the vdex directly instead of going through the oat file.
-        OatFileAssistant oat_file_assistant(location.c_str(), isa, false);
-        std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile());
-        std::vector<std::unique_ptr<const DexFile>> oat_dex_files;
-        if (oat_file != nullptr &&
-            OatFileAssistant::LoadDexFiles(*oat_file, location, &oat_dex_files)) {
-          info->opened_oat_files.push_back(std::move(oat_file));
-          info->opened_dex_files.insert(info->opened_dex_files.end(),
-                                        std::make_move_iterator(oat_dex_files.begin()),
-                                        std::make_move_iterator(oat_dex_files.end()));
-        } else {
-          LOG(WARNING) << "Could not open dex files from location: " << location;
-          dex_files_open_result_ = false;
+      if (fd < 0) {
+        if (!dex_file_loader.Open(location.c_str(),
+                                  location.c_str(),
+                                  Runtime::Current()->IsVerificationEnabled(),
+                                  /*verify_checksum=*/ true,
+                                  &error_msg,
+                                  &info->opened_dex_files)) {
+          // If we fail to open the dex file because it's been stripped, try to
+          // open the dex file from its corresponding oat file.
+          // This could happen when we need to recompile a pre-build whose dex
+          // code has been stripped (for example, if the pre-build is only
+          // quicken and we want to re-compile it speed-profile).
+          // TODO(calin): Use the vdex directly instead of going through the oat file.
+          OatFileAssistant oat_file_assistant(location.c_str(), isa, false);
+          std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile());
+          std::vector<std::unique_ptr<const DexFile>> oat_dex_files;
+          if (oat_file != nullptr &&
+              OatFileAssistant::LoadDexFiles(*oat_file, location, &oat_dex_files)) {
+            info->opened_oat_files.push_back(std::move(oat_file));
+            info->opened_dex_files.insert(info->opened_dex_files.end(),
+                                          std::make_move_iterator(oat_dex_files.begin()),
+                                          std::make_move_iterator(oat_dex_files.end()));
+          } else {
+            LOG(WARNING) << "Could not open dex files from location: " << location;
+            dex_files_open_result_ = false;
+          }
         }
+      } else if (!dex_file_loader.Open(fd,
+                                       location.c_str(),
+                                       Runtime::Current()->IsVerificationEnabled(),
+                                       /*verify_checksum=*/ true,
+                                       &error_msg,
+                                       &info->opened_dex_files)) {
+        LOG(WARNING) << "Could not open dex files from fd " << fd << " for location: " << location;
+        dex_files_open_result_ = false;
       }
     }
 
@@ -450,6 +478,14 @@
     AddToWorkList(info, work_list);
   }
 
+  // Check that if file descriptors were provided, there were exactly as many
+  // as we have encountered while iterating over this class loader context.
+  if (dex_file_index != fds.size()) {
+    LOG(WARNING) << fds.size() << " FDs provided but only " << dex_file_index
+        << " dex files are in the class loader context";
+    dex_files_open_result_ = false;
+  }
+
   return dex_files_open_result_;
 }
 
@@ -759,6 +795,25 @@
   return result;
 }
 
+std::string ClassLoaderContext::FlattenDexPaths() const {
+  if (class_loader_chain_ == nullptr) {
+    return "";
+  }
+
+  std::vector<std::string> result;
+  std::vector<ClassLoaderInfo*> work_list;
+  work_list.push_back(class_loader_chain_.get());
+  while (!work_list.empty()) {
+    ClassLoaderInfo* info = work_list.back();
+    work_list.pop_back();
+    for (const std::string& dex_path : info->classpath) {
+      result.push_back(dex_path);
+    }
+    AddToWorkList(info, work_list);
+  }
+  return FlattenClasspath(result);
+}
+
 const char* ClassLoaderContext::GetClassLoaderTypeName(ClassLoaderType type) {
   switch (type) {
     case kPathClassLoader: return kPathClassLoaderString;
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index 5a89c4e..f8387ce 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -57,6 +57,10 @@
   // Returns true if all dex files where successfully opened.
   // It may be called only once per ClassLoaderContext. Subsequent calls will return the same
   // result without doing anything.
+  // If `context_fds` is an empty vector, files will be opened using the class path locations as
+  // filenames. Otherwise `context_fds` is expected to contain file descriptors to class path dex
+  // files, following the order of dex file locations in a flattened class loader context. If their
+  // number (size of `context_fds`) does not match the number of dex files, OpenDexFiles will fail.
   //
   // This will replace the class path locations with the locations of the opened dex files.
   // (Note that one dex file can contain multidexes. Each multidex will be added to the classpath
@@ -69,7 +73,9 @@
   // TODO(calin): we're forced to complicate the flow in this class with a different
   // OpenDexFiles step because the current dex2oat flow requires the dex files be opened before
   // the class loader is created. Consider reworking the dex2oat part.
-  bool OpenDexFiles(InstructionSet isa, const std::string& classpath_dir);
+  bool OpenDexFiles(InstructionSet isa,
+                    const std::string& classpath_dir,
+                    const std::vector<int>& context_fds = std::vector<int>());
 
   // Remove the specified compilation sources from all classpaths present in this context.
   // Should only be called before the first call to OpenDexFiles().
@@ -118,6 +124,10 @@
   // Should only be called if OpenDexFiles() returned true.
   std::vector<const DexFile*> FlattenOpenedDexFiles() const;
 
+  // Return a colon-separated list of dex file locations from this class loader
+  // context after flattening.
+  std::string FlattenDexPaths() const;
+
   // Verifies that the current context is identical to the context encoded as `context_spec`.
   // Identical means:
   //    - the number and type of the class loaders from the chain matches
@@ -127,8 +137,8 @@
   // Names are only verified if verify_names is true.
   // Checksums are only verified if verify_checksums is true.
   VerificationResult VerifyClassLoaderContextMatch(const std::string& context_spec,
-                                     bool verify_names = true,
-                                     bool verify_checksums = true) const;
+                                                   bool verify_names = true,
+                                                   bool verify_checksums = true) const;
 
   // Creates the class loader context from the given string.
   // The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index ca09339..5529434 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -181,12 +181,14 @@
 int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target,
                                       bool profile_changed,
                                       bool downgrade,
-                                      ClassLoaderContext* class_loader_context) {
+                                      ClassLoaderContext* class_loader_context,
+                                      const std::vector<int>& context_fds) {
   OatFileInfo& info = GetBestInfo();
   DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target,
                                                     profile_changed,
                                                     downgrade,
-                                                    class_loader_context);
+                                                    class_loader_context,
+                                                    context_fds);
   if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) {
     return dexopt_needed;
   }
@@ -748,10 +750,11 @@
     CompilerFilter::Filter target,
     bool profile_changed,
     bool downgrade,
-    ClassLoaderContext* context) {
+    ClassLoaderContext* context,
+    const std::vector<int>& context_fds) {
 
   bool filter_okay = CompilerFilterIsOkay(target, profile_changed, downgrade);
-  bool class_loader_context_okay = ClassLoaderContextIsOkay(context);
+  bool class_loader_context_okay = ClassLoaderContextIsOkay(context, context_fds);
 
   // 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.
@@ -838,7 +841,8 @@
     CompilerFilter::IsAsGoodAs(current, target);
 }
 
-bool OatFileAssistant::OatFileInfo::ClassLoaderContextIsOkay(ClassLoaderContext* context) {
+bool OatFileAssistant::OatFileInfo::ClassLoaderContextIsOkay(ClassLoaderContext* context,
+                                                             const std::vector<int>& context_fds) {
   if (context == nullptr) {
     VLOG(oat) << "ClassLoaderContext check ignored: null context";
     return true;
@@ -855,12 +859,11 @@
       ? oat_file_assistant_->dex_location_.substr(0, dir_index)
       : "";
 
-  if (!context->OpenDexFiles(oat_file_assistant_->isa_, classpath_dir)) {
+  if (!context->OpenDexFiles(oat_file_assistant_->isa_, classpath_dir, context_fds)) {
     VLOG(oat) << "ClassLoaderContext check failed: dex files from the context could not be opened";
     return false;
   }
 
-
   const bool result = context->VerifyClassLoaderContextMatch(file->GetClassLoaderContext()) !=
       ClassLoaderContext::VerificationResult::kMismatch;
   if (!result) {
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index def55b8..85e5917 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -146,7 +146,8 @@
   int GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
                       bool profile_changed = false,
                       bool downgrade = false,
-                      ClassLoaderContext* context = nullptr);
+                      ClassLoaderContext* context = nullptr,
+                      const std::vector<int>& context_fds = std::vector<int>());
 
   // Returns true if there is up-to-date code for this dex location,
   // irrespective of the compiler filter of the up-to-date code.
@@ -288,7 +289,8 @@
     DexOptNeeded GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
                                  bool profile_changed,
                                  bool downgrade,
-                                 ClassLoaderContext* context);
+                                 ClassLoaderContext* context,
+                                 const std::vector<int>& context_fds);
 
     // Returns the loaded file.
     // Loads the file if needed. Returns null if the file failed to load.
@@ -329,7 +331,7 @@
     // compiler filter.
     bool CompilerFilterIsOkay(CompilerFilter::Filter target, bool profile_changed, bool downgrade);
 
-    bool ClassLoaderContextIsOkay(ClassLoaderContext* context);
+    bool ClassLoaderContextIsOkay(ClassLoaderContext* context, const std::vector<int>& context_fds);
 
     // Release the loaded oat file.
     // Returns null if the oat file hasn't been loaded.