Support loading vdex files without odex.

GetDexoptNeeded with 'verify' as filter will return false when a vdex is
available.

Test: 628-vdex, 820-vdex-multidex, test.py
Bug: 176960283
Change-Id: I3ac2f747d1e7f5331a49a22c94983959e3b60122
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 499d90b..887c992 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -165,9 +165,9 @@
 
   virtual void PreSetup(const std::string& elf_filename) = 0;
 
-  // Setup functions return true on success, false on failure.
   bool Setup(int zip_fd, ArrayRef<const std::string> dex_filenames, std::string* error_msg);
-  bool Setup(const std::vector<const DexFile*>& dex_files);
+
+  void Setup(const std::vector<const DexFile*>& dex_files);
 
   // Setters exposed for ElfOatFile.
 
@@ -465,13 +465,15 @@
   return true;
 }
 
-bool OatFileBase::Setup(const std::vector<const DexFile*>& dex_files) {
+void OatFileBase::Setup(const std::vector<const DexFile*>& dex_files) {
   for (const DexFile* dex_file : dex_files) {
     std::string dex_location = dex_file->GetLocation();
     std::string canonical_location = DexFileLoader::GetDexCanonicalLocation(dex_location.c_str());
 
     // Create an OatDexFile and add it to the owning container.
-    OatDexFile* oat_dex_file = new OatDexFile(this, dex_file, dex_location, canonical_location);
+    OatDexFile* oat_dex_file =
+        new OatDexFile(this, dex_file->Begin(), dex_file->GetLocationChecksum(), dex_location, canonical_location);
+    dex_file->SetOatDexFile(oat_dex_file);
     oat_dex_files_storage_.push_back(oat_dex_file);
 
     // Add the location and canonical location (if different) to the oat_dex_files_ table.
@@ -482,8 +484,6 @@
       oat_dex_files_.Put(canonical_key, oat_dex_file);
     }
   }
-
-  return true;
 }
 
 bool OatFileBase::Setup(int zip_fd,
@@ -1524,17 +1524,76 @@
                                    std::unique_ptr<VdexFile>&& vdex_file,
                                    const std::string& location) {
     std::unique_ptr<OatFileBackedByVdex> oat_file(new OatFileBackedByVdex(location));
-    if (!oat_file->Setup(dex_files, std::move(vdex_file))) {
-      return nullptr;
-    }
+    // SetVdex will take ownership of the VdexFile.
+    oat_file->SetVdex(vdex_file.release());
+    oat_file->SetupHeader(dex_files.size());
+    // Initialize OatDexFiles.
+    oat_file->Setup(dex_files);
     return oat_file.release();
   }
 
-  bool Setup(const std::vector<const DexFile*>& dex_files, std::unique_ptr<VdexFile>&& vdex_file) {
-    DCHECK(!IsExecutable());
+  static OatFileBackedByVdex* Open(int zip_fd,
+                                   std::unique_ptr<VdexFile>&& vdex_file,
+                                   const std::string& dex_location,
+                                   std::string* error_msg) {
+    std::unique_ptr<OatFileBackedByVdex> oat_file(new OatFileBackedByVdex(vdex_file->GetName()));
+    if (vdex_file->HasDexSection()) {
+      uint32_t i = 0;
+      for (const uint8_t* dex_file_start = vdex_file->GetNextDexFileData(nullptr);
+           dex_file_start != nullptr;
+           dex_file_start = vdex_file->GetNextDexFileData(dex_file_start), ++i) {
+        // Create the OatDexFile and add it to the owning container.
+        std::string location = DexFileLoader::GetMultiDexLocation(i, dex_location.c_str());
+        std::string canonical_location = DexFileLoader::GetDexCanonicalLocation(location.c_str());
+        OatDexFile* oat_dex_file = new OatDexFile(oat_file.get(),
+                                                  dex_file_start,
+                                                  vdex_file->GetLocationChecksum(i),
+                                                  location,
+                                                  canonical_location);
+        oat_file->oat_dex_files_storage_.push_back(oat_dex_file);
+
+        std::string_view key(oat_dex_file->GetDexFileLocation());
+        oat_file->oat_dex_files_.Put(key, oat_dex_file);
+        if (canonical_location != location) {
+          std::string_view canonical_key(oat_dex_file->GetCanonicalDexFileLocation());
+          oat_file->oat_dex_files_.Put(canonical_key, oat_dex_file);
+        }
+      }
+      oat_file->SetupHeader(oat_file->oat_dex_files_storage_.size());
+    } else {
+      // No need for any verification when loading dex files as we already have
+      // a vdex file.
+      const ArtDexFileLoader dex_file_loader;
+      bool loaded = false;
+      if (zip_fd != -1) {
+        loaded = dex_file_loader.OpenZip(zip_fd,
+                                         dex_location,
+                                         /*verify=*/ false,
+                                         /*verify_checksum=*/ false,
+                                         error_msg,
+                                         &oat_file->external_dex_files_);
+      } else {
+        loaded = dex_file_loader.Open(dex_location.c_str(),
+                                      dex_location,
+                                      /*verify=*/ false,
+                                      /*verify_checksum=*/ false,
+                                      error_msg,
+                                      &oat_file->external_dex_files_);
+      }
+      if (!loaded) {
+        return nullptr;
+      }
+      oat_file->SetupHeader(oat_file->external_dex_files_.size());
+      oat_file->Setup(MakeNonOwningPointerVector(oat_file->external_dex_files_));
+    }
 
     // SetVdex will take ownership of the VdexFile.
-    SetVdex(vdex_file.release());
+    oat_file->SetVdex(vdex_file.release());
+    return oat_file.release();
+  }
+
+  void SetupHeader(size_t number_of_dex_files) {
+    DCHECK(!IsExecutable());
 
     // Create a fake OatHeader with a key store containing only the compiler
     // filter (it helps debugging and is required by
@@ -1543,39 +1602,15 @@
         InstructionSetFeatures::FromCppDefines();
     SafeMap<std::string, std::string> store;
     store.Put(OatHeader::kCompilerFilter, CompilerFilter::NameOfFilter(CompilerFilter::kVerify));
+    store.Put(OatHeader::kConcurrentCopying,
+              kUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
     oat_header_.reset(OatHeader::Create(kRuntimeISA,
                                         isa_features.get(),
-                                        dex_files.size(),
+                                        number_of_dex_files,
                                         &store));
     const uint8_t* begin = reinterpret_cast<const uint8_t*>(oat_header_.get());
     SetBegin(begin);
     SetEnd(begin + oat_header_->GetHeaderSize());
-
-    // Load VerifierDeps from VDEX and copy bit vectors of verified classes.
-    ArrayRef<const uint8_t> deps_data = GetVdexFile()->GetVerifierDepsData();
-    if (!verifier::VerifierDeps::ParseVerifiedClasses(dex_files,
-                                                      deps_data,
-                                                      &verified_classes_per_dex_)) {
-      return false;
-    }
-
-    // Initialize OatDexFiles.
-    if (!OatFileBase::Setup(dex_files)) {
-      return false;
-    }
-
-    return true;
-  }
-
-  bool IsClassVerifiedInVdex(const OatDexFile& oat_dex_file, uint16_t class_def_index) const {
-    // Determine the index of the DexFile, assuming the order of OatDexFiles
-    // in `oat_dex_files_storage_` is the same.
-    const std::vector<const OatDexFile*>& oat_dex_files = GetOatDexFiles();
-    auto oat_dex_file_it = std::find(oat_dex_files.begin(), oat_dex_files.end(), &oat_dex_file);
-    DCHECK(oat_dex_file_it != oat_dex_files.end());
-    size_t dex_index = oat_dex_file_it - oat_dex_files.begin();
-    // Check the bitvector of verified classes from the vdex.
-    return verified_classes_per_dex_[dex_index][class_def_index];
   }
 
  protected:
@@ -1611,7 +1646,6 @@
 
  private:
   std::unique_ptr<OatHeader> oat_header_;
-  std::vector<std::vector<bool>> verified_classes_per_dex_;
 
   DISALLOW_COPY_AND_ASSIGN(OatFileBackedByVdex);
 };
@@ -1638,13 +1672,12 @@
 
   std::string vdex_filename = GetVdexFilename(oat_filename);
 
-  // Check that the files even exist, fast-fail.
+  // Check that the vdex file even exists, fast-fail. We don't check the odex
+  // file as we use the absence of an odex file for test the functionality of
+  // vdex-only.
   if (!OS::FileExists(vdex_filename.c_str())) {
     *error_msg = StringPrintf("File %s does not exist.", vdex_filename.c_str());
     return nullptr;
-  } else if (!OS::FileExists(oat_filename.c_str())) {
-    *error_msg = StringPrintf("File %s does not exist.", oat_filename.c_str());
-    return nullptr;
   }
 
   // Try dlopen first, as it is required for native debuggability. This will fail fast if dlopen is
@@ -1725,6 +1758,14 @@
   return OatFileBackedByVdex::Open(dex_files, std::move(vdex_file), location);
 }
 
+OatFile* OatFile::OpenFromVdex(int zip_fd,
+                               std::unique_ptr<VdexFile>&& vdex_file,
+                               const std::string& location,
+                               std::string* error_msg) {
+  CheckLocation(location);
+  return OatFileBackedByVdex::Open(zip_fd, std::move(vdex_file), location, error_msg);
+}
+
 OatFile::OatFile(const std::string& location, bool is_executable)
     : location_(location),
       vdex_(nullptr),
@@ -1916,15 +1957,15 @@
 }
 
 OatDexFile::OatDexFile(const OatFile* oat_file,
-                       const DexFile* dex_file,
+                       const uint8_t* dex_file_pointer,
+                       uint32_t dex_file_location_checksum,
                        const std::string& dex_file_location,
                        const std::string& canonical_dex_file_location)
     : oat_file_(oat_file),
       dex_file_location_(dex_file_location),
       canonical_dex_file_location_(canonical_dex_file_location),
-      dex_file_location_checksum_(dex_file->GetLocationChecksum()),
-      dex_file_pointer_(reinterpret_cast<const uint8_t*>(dex_file)) {
-  dex_file->SetOatDexFile(this);
+      dex_file_location_checksum_(dex_file_location_checksum),
+      dex_file_pointer_(dex_file_pointer) {
   DCHECK(IsBackedByVdexOnly());
 }
 
@@ -1968,13 +2009,12 @@
 }
 
 OatFile::OatClass OatDexFile::GetOatClass(uint16_t class_def_index) const {
-  // If this is an OatFileBackedByVdex, initialize the OatClass using the vdex's VerifierDeps.
   if (IsBackedByVdexOnly()) {
-    bool is_vdex_verified = down_cast<const OatFileBackedByVdex*>(oat_file_)->IsClassVerifiedInVdex(
-        *this,
-        class_def_index);
+    // If there is only a vdex file, return that the class is not ready. The
+    // caller will have to call `VdexFile::ComputeClassStatus` to compute the
+    // actual class status, because we need to do the assignability type checks.
     return OatFile::OatClass(oat_file_,
-                             is_vdex_verified ? ClassStatus::kVerified : ClassStatus::kNotReady,
+                             ClassStatus::kNotReady,
                              /* type= */ kOatClassNoneCompiled,
                              /* bitmap_size= */ 0u,
                              /* bitmap_pointer= */ nullptr,
@@ -2283,4 +2323,8 @@
   CHECK(Runtime::Current()->IsAotCompiler());
 }
 
+bool OatFile::IsBackedByVdexOnly() const {
+  return oat_dex_files_storage_.size() >= 1 && oat_dex_files_storage_[0]->IsBackedByVdexOnly();
+}
+
 }  // namespace art
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index d4f4d95..a22e043 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -163,6 +163,16 @@
                                std::unique_ptr<VdexFile>&& vdex_file,
                                const std::string& location);
 
+  // Initialize OatFile instance from an already loaded VdexFile. The dex files
+  // will be opened through `zip_fd` or `dex_location` if `zip_fd` is -1.
+  static OatFile* OpenFromVdex(int zip_fd,
+                               std::unique_ptr<VdexFile>&& vdex_file,
+                               const std::string& location,
+                               std::string* error_msg);
+
+  // Return whether the `OatFile` uses a vdex-only file.
+  bool IsBackedByVdexOnly() const;
+
   virtual ~OatFile();
 
   bool IsExecutable() const {
@@ -467,6 +477,7 @@
   friend class OatClass;
   friend class art::OatDexFile;
   friend class OatDumper;  // For GetBase and GetLimit
+  friend class OatFileBackedByVdex;
   friend class OatFileBase;
   DISALLOW_COPY_AND_ASSIGN(OatFile);
 };
@@ -582,7 +593,8 @@
   // Create an OatDexFile wrapping an existing DexFile. Will set the OatDexFile
   // pointer in the DexFile.
   OatDexFile(const OatFile* oat_file,
-             const DexFile* dex_file,
+             const uint8_t* dex_file_pointer,
+             uint32_t dex_file_checksum,
              const std::string& dex_file_location,
              const std::string& canonical_dex_file_location);
 
@@ -607,6 +619,7 @@
 
   friend class OatFile;
   friend class OatFileBase;
+  friend class OatFileBackedByVdex;
   DISALLOW_COPY_AND_ASSIGN(OatDexFile);
 };
 
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 8cfb8d5..4b0783f 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -99,6 +99,8 @@
       only_load_system_executable_(only_load_system_executable),
       odex_(this, /*is_oat_location=*/ false),
       oat_(this, /*is_oat_location=*/ true),
+      vdex_for_odex_(this, /*is_oat_location=*/ false),
+      vdex_for_oat_(this, /*is_oat_location=*/ true),
       zip_fd_(zip_fd) {
   CHECK(dex_location != nullptr) << "OatFileAssistant: null dex location";
 
@@ -122,6 +124,8 @@
   std::string odex_file_name;
   if (DexLocationToOdexFilename(dex_location_, isa_, &odex_file_name, &error_msg)) {
     odex_.Reset(odex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd);
+    std::string vdex_file_name = GetVdexFilename(odex_file_name);
+    vdex_for_odex_.Reset(vdex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd);
   } else {
     LOG(WARNING) << "Failed to determine odex file name: " << error_msg;
   }
@@ -131,6 +135,8 @@
     std::string oat_file_name;
     if (DexLocationToOatFilename(dex_location_, isa_, &oat_file_name, &error_msg)) {
       oat_.Reset(oat_file_name, /*use_fd=*/ false);
+      std::string vdex_file_name = GetVdexFilename(oat_file_name);
+      vdex_for_oat_.Reset(vdex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd);
     } else {
       LOG(WARNING) << "Failed to determine oat file name for dex location "
                    << dex_location_ << ": " << error_msg;
@@ -406,7 +412,9 @@
   CompilerFilter::Filter current_compiler_filter = file.GetCompilerFilter();
 
   // Verify the image checksum
-  if (CompilerFilter::DependsOnImageChecksum(current_compiler_filter)) {
+  if (file.IsBackedByVdexOnly()) {
+    VLOG(oat) << "Image checksum test skipped for vdex file " << file.GetLocation();
+  } else if (CompilerFilter::DependsOnImageChecksum(current_compiler_filter)) {
     if (!ValidateBootClassPathChecksums(file)) {
       VLOG(oat) << "Oat image checksum does not match image checksum.";
       return kOatBootImageOutOfDate;
@@ -633,26 +641,41 @@
     // apps gets installed or when they load private, secondary dex file.
     // For apps on the system partition the odex location will not be
     // writable and thus the oat location might be more up to date.
+
+    // If the odex is not useable, and we have a useable vdex, return the vdex
+    // instead.
+    if (!odex_.IsUseable() && vdex_for_odex_.IsUseable()) {
+      return vdex_for_odex_;
+    }
     return odex_;
   }
 
   // We cannot write to the odex location. This must be a system app.
 
-  // If the oat location is usable take it.
+  // If the oat location is useable take it.
   if (oat_.IsUseable()) {
     return oat_;
   }
 
-  // The oat file is not usable but the odex file might be up to date.
+  // The oat file is not useable but the odex file might be up to date.
   // This is an indication that we are dealing with an up to date prebuilt
   // (that doesn't need relocation).
-  if (odex_.Status() == kOatUpToDate) {
+  if (odex_.IsUseable()) {
     return odex_;
   }
 
+  // Look for a useable vdex file.
+  if (vdex_for_oat_.IsUseable()) {
+    return vdex_for_oat_;
+  }
+  if (vdex_for_odex_.IsUseable()) {
+    return vdex_for_odex_;
+  }
+
   // We got into the worst situation here:
-  // - the oat location is not usable
+  // - the oat location is not useable
   // - the prebuild odex location is not up to date
+  // - the vdex-only file is not useable
   // - and we don't have the original dex file anymore (stripped).
   // Pick the odex if it exists, or the oat if not.
   return (odex_.Status() == kOatCannotOpen) ? oat_ : odex_;
@@ -705,47 +728,7 @@
     status_attempted_ = true;
     const OatFile* file = GetFile();
     if (file == nullptr) {
-      // Check to see if there is a vdex file we can make use of.
-      std::string error_msg;
-      std::string vdex_filename = GetVdexFilename(filename_);
-      std::unique_ptr<VdexFile> vdex;
-      if (use_fd_) {
-        if (vdex_fd_ >= 0) {
-          struct stat s;
-          int rc = TEMP_FAILURE_RETRY(fstat(vdex_fd_, &s));
-          if (rc == -1) {
-            error_msg = StringPrintf("Failed getting length of the vdex file %s.", strerror(errno));
-          } else {
-            vdex = VdexFile::Open(vdex_fd_,
-                                  s.st_size,
-                                  vdex_filename,
-                                  /*writable=*/ false,
-                                  /*low_4gb=*/ false,
-                                  /*unquicken=*/ false,
-                                  &error_msg);
-          }
-        }
-      } else {
-        vdex = VdexFile::Open(vdex_filename,
-                              /*writable=*/ false,
-                              /*low_4gb=*/ false,
-                              /*unquicken=*/ false,
-                              &error_msg);
-      }
-      if (vdex == nullptr) {
-        status_ = kOatCannotOpen;
-        VLOG(oat) << "unable to open vdex file " << vdex_filename << ": " << error_msg;
-      } else {
-        if (oat_file_assistant_->DexChecksumUpToDate(*vdex, &error_msg)) {
-          // The vdex file does not contain enough information to determine
-          // whether it is up to date with respect to the boot image, so we
-          // assume it is out of date.
-          VLOG(oat) << error_msg;
-          status_ = kOatBootImageOutOfDate;
-        } else {
-          status_ = kOatDexOutOfDate;
-        }
-      }
+      status_ = kOatCannotOpen;
     } else {
       status_ = oat_file_assistant_->GivenOatFileStatus(*file);
       VLOG(oat) << file->GetLocation() << " is " << status_
@@ -792,46 +775,87 @@
 
 const OatFile* OatFileAssistant::OatFileInfo::GetFile() {
   CHECK(!file_released_) << "GetFile called after oat file released.";
-  if (!load_attempted_) {
-    load_attempted_ = true;
-    if (filename_provided_) {
-      bool executable = oat_file_assistant_->load_executable_;
-      if (executable && oat_file_assistant_->only_load_system_executable_) {
-        executable = LocationIsOnSystem(filename_.c_str());
-      }
-      VLOG(oat) << "Loading " << filename_ << " with executable: " << executable;
-      std::string error_msg;
-      if (use_fd_) {
-        if (oat_fd_ >= 0 && vdex_fd_ >= 0) {
-          ArrayRef<const std::string> dex_locations(&oat_file_assistant_->dex_location_,
-                                                    /*size=*/ 1u);
-          file_.reset(OatFile::Open(zip_fd_,
-                                    vdex_fd_,
-                                    oat_fd_,
-                                    filename_.c_str(),
-                                    executable,
-                                    /*low_4gb=*/ false,
-                                    dex_locations,
-                                    /*reservation=*/ nullptr,
-                                    &error_msg));
+  if (load_attempted_) {
+    return file_.get();
+  }
+  load_attempted_ = true;
+  if (!filename_provided_) {
+    return nullptr;
+  }
+
+  std::string error_msg;
+  bool executable = oat_file_assistant_->load_executable_;
+  if (android::base::EndsWith(filename_, kVdexExtension)) {
+    executable = false;
+    // Check to see if there is a vdex file we can make use of.
+    std::unique_ptr<VdexFile> vdex;
+    if (use_fd_) {
+      if (vdex_fd_ >= 0) {
+        struct stat s;
+        int rc = TEMP_FAILURE_RETRY(fstat(vdex_fd_, &s));
+        if (rc == -1) {
+          error_msg = StringPrintf("Failed getting length of the vdex file %s.", strerror(errno));
+        } else {
+          vdex = VdexFile::Open(vdex_fd_,
+                                s.st_size,
+                                filename_,
+                                /*writable=*/ false,
+                                /*low_4gb=*/ false,
+                                /*unquicken=*/ false,
+                                &error_msg);
         }
-      } else {
-        file_.reset(OatFile::Open(/*zip_fd=*/ -1,
-                                  filename_.c_str(),
+      }
+    } else {
+      vdex = VdexFile::Open(filename_,
+                            /*writable=*/ false,
+                            /*low_4gb=*/ false,
+                            /*unquicken=*/ false,
+                            &error_msg);
+    }
+    if (vdex == nullptr) {
+      VLOG(oat) << "unable to open vdex file " << filename_ << ": " << error_msg;
+    } else {
+      file_.reset(OatFile::OpenFromVdex(zip_fd_,
+                                        std::move(vdex),
+                                        oat_file_assistant_->dex_location_,
+                                        &error_msg));
+    }
+  } else {
+    if (executable && oat_file_assistant_->only_load_system_executable_) {
+      executable = LocationIsOnSystem(filename_.c_str());
+    }
+    VLOG(oat) << "Loading " << filename_ << " with executable: " << executable;
+    if (use_fd_) {
+      if (oat_fd_ >= 0 && vdex_fd_ >= 0) {
+        ArrayRef<const std::string> dex_locations(&oat_file_assistant_->dex_location_,
+                                                  /*size=*/ 1u);
+        file_.reset(OatFile::Open(zip_fd_,
+                                  vdex_fd_,
+                                  oat_fd_,
                                   filename_.c_str(),
                                   executable,
                                   /*low_4gb=*/ false,
-                                  oat_file_assistant_->dex_location_,
+                                  dex_locations,
+                                  /*reservation=*/ nullptr,
                                   &error_msg));
       }
-      if (file_.get() == nullptr) {
-        VLOG(oat) << "OatFileAssistant test for existing oat file "
-          << filename_ << ": " << error_msg;
-      } else {
-        VLOG(oat) << "Successfully loaded " << filename_ << " with executable: " << executable;
-      }
+    } else {
+      file_.reset(OatFile::Open(/*zip_fd=*/ -1,
+                                filename_.c_str(),
+                                filename_.c_str(),
+                                executable,
+                                /*low_4gb=*/ false,
+                                oat_file_assistant_->dex_location_,
+                                &error_msg));
     }
   }
+  if (file_.get() == nullptr) {
+    VLOG(oat) << "OatFileAssistant test for existing oat file "
+              << filename_
+              << ": " << error_msg;
+  } else {
+    VLOG(oat) << "Successfully loaded " << filename_ << " with executable: " << executable;
+  }
   return file_.get();
 }
 
@@ -859,6 +883,11 @@
     return true;
   }
 
+  if (file->IsBackedByVdexOnly()) {
+    // Only a vdex file, we don't depend on the class loader context.
+    return true;
+  }
+
   if (!CompilerFilter::IsVerificationEnabled(file->GetCompilerFilter())) {
     // If verification is not enabled we don't need to verify the class loader context and we
     // assume it's ok.
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 8784875..e771dcc 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -430,9 +430,19 @@
   bool required_dex_checksums_found_;
   bool has_original_dex_files_;
 
+  // The AOT-compiled file of an app when the APK of the app is in /data.
   OatFileInfo odex_;
+  // The AOT-compiled file of an app when the APK of the app is on a read-only partition
+  // (for example /system).
   OatFileInfo oat_;
 
+  // The vdex-only file next to `odex_` when `odex_' cannot be used (for example
+  // it is out of date).
+  OatFileInfo vdex_for_odex_;
+  // The vdex-only file next to 'oat_` when `oat_' cannot be used (for example
+  // it is out of date).
+  OatFileInfo vdex_for_oat_;
+
   // File descriptor corresponding to apk, dex file, or zip.
   int zip_fd_;
 
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 902e5af..16d3ce0 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -501,13 +501,15 @@
                                       vdex_fd.get(),
                                       /* oat_fd= */ -1,
                                       zip_fd.get());
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForBootImage,
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
+  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
       GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForBootImage,
+  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
       GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
-  EXPECT_EQ(OatFileAssistant::kOatBootImageOutOfDate, oat_file_assistant.OdexFileStatus());
+  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
   EXPECT_TRUE(oat_file_assistant.HasDexFiles());
 }
@@ -564,7 +566,7 @@
   EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
 }
 
-// Case: We have a DEX file and up-to-date (ODEX) VDEX file for it, but no
+// Case: We have a DEX file and up-to-date VDEX file for it, but no
 // ODEX file.
 TEST_F(OatFileAssistantTest, VdexUpToDateNoOdex) {
   std::string dex_location = GetScratchDir() + "/VdexUpToDateNoOdex.jar";
@@ -579,12 +581,9 @@
 
   OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
 
-  // Even though the vdex file is up to date, because we don't have the oat
-  // file, we can't know that the vdex depends on the boot image and is up to
-  // date with respect to the boot image. Instead we must assume the vdex file
-  // depends on the boot image and is out of date with respect to the boot
-  // image.
-  EXPECT_EQ(-OatFileAssistant::kDex2OatForBootImage,
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+      GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
+  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
       GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
 
   // Make sure we don't crash in this case when we dump the status. We don't
@@ -594,9 +593,9 @@
   VerifyOptimizationStatus(
       &oat_file_assistant,
       dex_location,
-      "run-from-apk",
+      "verify",
       "unknown",
-      "io-error-no-oat");
+      "up-to-date");
 }
 
 // Case: We have a DEX file and empty VDEX and ODEX files.
@@ -637,12 +636,7 @@
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
   OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
 
-  // Even though the vdex file is up to date, because we don't have the oat
-  // file, we can't know that the vdex depends on the boot image and is up to
-  // date with respect to the boot image. Instead we must assume the vdex file
-  // depends on the boot image and is out of date with respect to the boot
-  // image.
-  EXPECT_EQ(OatFileAssistant::kDex2OatForBootImage,
+  EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
       GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
 }
 
@@ -831,11 +825,11 @@
   ASSERT_TRUE(scoped_non_writable.IsSuccessful());
 
   OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
-  EXPECT_EQ(OatFileAssistant::kDex2OatForBootImage,
+  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
       GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
-  EXPECT_EQ(OatFileAssistant::kDex2OatForBootImage,
+  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
       GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
-  EXPECT_EQ(OatFileAssistant::kDex2OatForBootImage,
+  EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
       GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
 
   EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
@@ -846,9 +840,9 @@
   VerifyOptimizationStatus(
       &oat_file_assistant,
       dex_location,
-      "run-from-apk-fallback",
+      "verify",
       "unknown",
-      "boot-image-more-recent");
+      "up-to-date");
 }
 
 // Case: We have a DEX file and a verify-at-runtime OAT file out of date with
@@ -1454,9 +1448,12 @@
   odex_dir = odex_dir + std::string(GetInstructionSetString(kRuntimeISA));
   mkdir(odex_dir.c_str(), 0700);
   std::string oat_location = odex_dir + "/" + filebase + ".odex";
+  std::string vdex_location = odex_dir + "/" + filebase + ".vdex";
   std::string art_location = odex_dir + "/" + filebase + ".art";
   // Clean up in case previous run crashed.
   remove(oat_location.c_str());
+  remove(vdex_location.c_str());
+  remove(art_location.c_str());
 
   // Start the runtime to initialize the system's class loader.
   Thread::Current()->TransitionFromSuspendedToRunnable();
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index f03e77f..8a0f550 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -185,6 +185,11 @@
   DCHECK(error_msg != nullptr);
   DCHECK(context != nullptr);
 
+  if (oat_file->IsBackedByVdexOnly()) {
+    // Only a vdex file, we don't depend on the class loader context.
+    return true;
+  }
+
   if (!CompilerFilter::IsVerificationEnabled(oat_file->GetCompilerFilter())) {
     // If verification is not enabled we don't need to check if class loader context matches
     // as the oat file is either extracted or assumed verified.
@@ -272,7 +277,7 @@
     // Proceed with oat file loading.
     std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());
     VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()="
-              << reinterpret_cast<uintptr_t>(oat_file.get())
+              << (oat_file != nullptr ? oat_file->GetLocation() : "")
               << " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")";
 
     CHECK(oat_file == nullptr || odex_location == oat_file->GetLocation())
diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h
index 97655f6..6e11728 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -384,6 +384,12 @@
   ClassStatus ComputeClassStatus(Thread* self, Handle<mirror::Class> cls) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Return the name of the underlying `MemMap` of the vdex file, typically the
+  // location on disk of the vdex file.
+  const std::string& GetName() const {
+    return mmap_.GetName();
+  }
+
  private:
   uint32_t GetQuickeningInfoTableOffset(const uint8_t* source_dex_begin) const;
 
diff --git a/test/677-fsi/expected-stderr.txt b/test/677-fsi/expected-stderr.txt
index f74e4de..35c3918 100644
--- a/test/677-fsi/expected-stderr.txt
+++ b/test/677-fsi/expected-stderr.txt
@@ -1 +1,2 @@
 oat file has dex code, but APK has uncompressed dex code
+oat file has dex code, but APK has uncompressed dex code
diff --git a/test/820-vdex-multidex/expected-stderr.txt b/test/820-vdex-multidex/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/820-vdex-multidex/expected-stderr.txt
diff --git a/test/820-vdex-multidex/expected-stdout.txt b/test/820-vdex-multidex/expected-stdout.txt
new file mode 100644
index 0000000..e965047
--- /dev/null
+++ b/test/820-vdex-multidex/expected-stdout.txt
@@ -0,0 +1 @@
+Hello
diff --git a/test/820-vdex-multidex/info.txt b/test/820-vdex-multidex/info.txt
new file mode 100644
index 0000000..f6b87ea
--- /dev/null
+++ b/test/820-vdex-multidex/info.txt
@@ -0,0 +1 @@
+Test that vdex logic works with multidex.
diff --git a/test/820-vdex-multidex/run b/test/820-vdex-multidex/run
new file mode 100644
index 0000000..3f6dc3c
--- /dev/null
+++ b/test/820-vdex-multidex/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+exec ${RUN} -Xcompiler-option --compiler-filter=verify --vdex "${@}"
diff --git a/test/820-vdex-multidex/src-multidex/Foo.java b/test/820-vdex-multidex/src-multidex/Foo.java
new file mode 100644
index 0000000..6d18efb
--- /dev/null
+++ b/test/820-vdex-multidex/src-multidex/Foo.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Foo {
+}
diff --git a/test/820-vdex-multidex/src/Main.java b/test/820-vdex-multidex/src/Main.java
new file mode 100644
index 0000000..164e784
--- /dev/null
+++ b/test/820-vdex-multidex/src/Main.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) {
+    System.out.println("Hello");
+  }
+}
+
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 78310b6..9e85a73 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -993,7 +993,8 @@
   if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
     vdex_cmdline="${dex2oat_cmdline} ${VDEX_ARGS} --input-vdex=$DEX_LOCATION/oat/$ISA/$name.vdex --output-vdex=$DEX_LOCATION/oat/$ISA/$name.vdex"
   elif [ "$TEST_VDEX" = "y" ]; then
-    vdex_cmdline="${dex2oat_cmdline} ${VDEX_ARGS} --input-vdex=$DEX_LOCATION/oat/$ISA/$name.vdex"
+    # We delete the odex file so that the runtime only picks up the vdex file.
+    vdex_cmdline="rm $DEX_LOCATION/oat/$ISA/$name.odex"
   elif [ "$TEST_DM" = "y" ]; then
     dex2oat_cmdline="${dex2oat_cmdline} --copy-dex-files=false --output-vdex=$DEX_LOCATION/oat/$ISA/primary.vdex"
     dm_cmdline="zip -qj $DEX_LOCATION/oat/$ISA/$name.dm $DEX_LOCATION/oat/$ISA/primary.vdex"
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 33d7c70..13ed23f 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1139,6 +1139,7 @@
                   "810-checker-invoke-super-default",
                   "811-checker-invoke-super-secondary",
                   "817-hiddenapi",
+                  "820-vdex-multidex",
                   "999-redefine-hiddenapi",
                   "1000-non-moving-space-stress",
                   "1001-app-image-regions",