Create vdex file for dex loaded with InMemoryDexClassLoader

Previous CL introduced a background verification thread for dex bytecode
loaded with InMemoryDexClassLoader. Extend the logic to collect the
results of class verification into an instance of VerifierDeps and dump
it into a vdex file in the app's data folder.

The background thread does not collect full VerifierDeps (e.g.
assignability dependencies, etc), just a bit vector of whether a class
was successfully verified or not.

The vdex format is extended to include boot classpath checksums and the
class loader context it was created for. These are optional and
currently left empty for regular vdex files.

The generated vdex files are treated as a cache with a limited capacity,
currently capped at 8 files. The least recently used file (in terms of
atime reported by stat()) is unlinked if the cache is full and a new
vdex is about to be generated.

Bug: 72131483
Test: art/tools/run-libcore-tests.sh
Test: art/test.py -b -r -t 692 -t 693
Change-Id: I26080d894d34d8f35f00c7925db569f22f008d2c
diff --git a/libdexfile/dex/dex_file.h b/libdexfile/dex/dex_file.h
index 8a96123..e119553 100644
--- a/libdexfile/dex/dex_file.h
+++ b/libdexfile/dex/dex_file.h
@@ -738,6 +738,10 @@
   static uint32_t CalculateChecksum(const uint8_t* begin, size_t size);
   static uint32_t ChecksumMemoryRange(const uint8_t* begin, size_t size);
 
+  // Number of bytes at the beginning of the dex file header which are skipped
+  // when computing the adler32 checksum of the entire file.
+  static constexpr uint32_t kNumNonChecksumBytes = OFFSETOF_MEMBER(DexFile::Header, signature_);
+
   // Returns a human-readable form of the method at an index.
   std::string PrettyMethod(uint32_t method_idx, bool with_signature = true) const;
   // Returns a human-readable form of the field at an index.
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 0cc7d29..43262f2 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -430,6 +430,7 @@
         "liblog",
         "libnativebridge",
         "libnativeloader",
+        "libsigchain_dummy",
         "libunwindstack",
         "libz",
     ],
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index d1ea655..ae1eea5 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -340,10 +340,27 @@
   }
 }
 
+static jstring DexFile_getClassLoaderContext(JNIEnv* env,
+                                            jclass,
+                                            jobject class_loader,
+                                            jobjectArray dex_elements) {
+  CHECK(class_loader != nullptr);
+  constexpr const char* kBaseDir = "";
+  std::unique_ptr<ClassLoaderContext> context =
+  ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);
+  if (context == nullptr || !context->OpenDexFiles(kRuntimeISA, kBaseDir)) {
+    LOG(WARNING) << "Could not establish class loader context";
+    return nullptr;
+  }
+  std::string str_context = context->EncodeContextForOatFile(kBaseDir);
+  return env->NewStringUTF(str_context.c_str());
+}
+
 static void DexFile_verifyInBackgroundNative(JNIEnv* env,
                                              jclass,
                                              jobject cookie,
-                                             jobject class_loader) {
+                                             jobject class_loader,
+                                             jstring class_loader_context) {
   CHECK(cookie != nullptr);
   CHECK(class_loader != nullptr);
 
@@ -356,8 +373,17 @@
   }
   CHECK(oat_file == nullptr) << "Called verifyInBackground on a dex file backed by oat";
 
+  ScopedUtfChars class_loader_context_utf(env, class_loader_context);
+  if (env->ExceptionCheck()) {
+    LOG(ERROR) << "Failed to unwrap class loader context string";
+    return;
+  }
+
   // Hand over to OatFileManager to spawn a verification thread.
-  Runtime::Current()->GetOatFileManager().RunBackgroundVerification(dex_files, class_loader);
+  Runtime::Current()->GetOatFileManager().RunBackgroundVerification(
+      dex_files,
+      class_loader,
+      class_loader_context_utf.c_str());
 }
 
 static jboolean DexFile_closeDexFile(JNIEnv* env, jclass, jobject cookie) {
@@ -913,7 +939,15 @@
                 "[I"
                 "[I"
                 ")Ljava/lang/Object;"),
-  NATIVE_METHOD(DexFile, verifyInBackgroundNative, "(Ljava/lang/Object;Ljava/lang/ClassLoader;)V"),
+  NATIVE_METHOD(DexFile, getClassLoaderContext,
+                "(Ljava/lang/ClassLoader;"
+                "[Ldalvik/system/DexPathList$Element;"
+                ")Ljava/lang/String;"),
+  NATIVE_METHOD(DexFile, verifyInBackgroundNative,
+                "(Ljava/lang/Object;"
+                "Ljava/lang/ClassLoader;"
+                "Ljava/lang/String;"
+                ")V"),
   NATIVE_METHOD(DexFile, isValidCompilerFilter, "(Ljava/lang/String;)Z"),
   NATIVE_METHOD(DexFile, isProfileGuidedCompilerFilter, "(Ljava/lang/String;)Z"),
   NATIVE_METHOD(DexFile,
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index f122e57..b5e7ce8 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -19,6 +19,7 @@
 #include <sstream>
 
 #include <sys/stat.h>
+#include "zlib.h"
 
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
@@ -31,6 +32,7 @@
 #include "base/string_view_cpp20.h"
 #include "base/utils.h"
 #include "class_linker.h"
+#include "class_loader_context.h"
 #include "compiler_filter.h"
 #include "dex/art_dex_file_loader.h"
 #include "dex/dex_file_loader.h"
@@ -42,12 +44,14 @@
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "vdex_file.h"
-#include "class_loader_context.h"
 
 namespace art {
 
 using android::base::StringPrintf;
 
+static constexpr const char* kAnonymousDexPrefix = "Anonymous-DexFile@";
+static constexpr const char* kVdexExtension = ".vdex";
+
 std::ostream& operator << (std::ostream& stream, const OatFileAssistant::OatStatus status) {
   switch (status) {
     case OatFileAssistant::kOatCannotOpen:
@@ -429,6 +433,54 @@
   return kOatUpToDate;
 }
 
+bool OatFileAssistant::AnonymousDexVdexLocation(const std::vector<const DexFile::Header*>& headers,
+                                                InstructionSet isa,
+                                                /* out */ uint32_t* location_checksum,
+                                                /* out */ std::string* dex_location,
+                                                /* out */ std::string* vdex_filename) {
+  uint32_t checksum = adler32(0L, Z_NULL, 0);
+  for (const DexFile::Header* header : headers) {
+    checksum = adler32_combine(checksum,
+                               header->checksum_,
+                               header->file_size_ - DexFile::kNumNonChecksumBytes);
+  }
+  *location_checksum = checksum;
+
+  const std::string& data_dir = Runtime::Current()->GetProcessDataDirectory();
+  if (data_dir.empty() || Runtime::Current()->IsZygote()) {
+    *dex_location = StringPrintf("%s%u", kAnonymousDexPrefix, checksum);
+    return false;
+  }
+  *dex_location = StringPrintf("%s/%s%u.jar", data_dir.c_str(), kAnonymousDexPrefix, checksum);
+
+  std::string odex_filename;
+  std::string error_msg;
+  if (!DexLocationToOdexFilename(*dex_location, isa, &odex_filename, &error_msg)) {
+    LOG(WARNING) << "Could not get odex filename for " << *dex_location << ": " << error_msg;
+    return false;
+  }
+
+  *vdex_filename = GetVdexFilename(odex_filename);
+  return true;
+}
+
+bool OatFileAssistant::IsAnonymousVdexBasename(const std::string& basename) {
+  DCHECK(basename.find('/') == std::string::npos);
+  // `basename` must have format: <kAnonymousDexPrefix><checksum><kVdexExtension>
+  if (basename.size() < strlen(kAnonymousDexPrefix) + strlen(kVdexExtension) + 1 ||
+      !android::base::StartsWith(basename.c_str(), kAnonymousDexPrefix) ||
+      !android::base::EndsWith(basename, kVdexExtension)) {
+    return false;
+  }
+  // Check that all characters between the prefix and extension are decimal digits.
+  for (size_t i = strlen(kAnonymousDexPrefix); i < basename.size() - strlen(kVdexExtension); ++i) {
+    if (!std::isdigit(basename[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
 static bool DexLocationToOdexNames(const std::string& location,
                                    InstructionSet isa,
                                    std::string* odex_filename,
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 83ae3cb..1f3f74f 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -245,6 +245,21 @@
                                        std::string* oat_filename,
                                        std::string* error_msg);
 
+  // Computes the location checksum, dex location and vdex filename by combining
+  // the checksums of the individual dex files. If the data directory of the process
+  // is known, creates an absolute path in that directory and tries to infer path
+  // of a corresponding vdex file. Otherwise only creates a basename dex_location
+  // from the combined checksums. Returns true if all out-arguments have been set.
+  static bool AnonymousDexVdexLocation(const std::vector<const DexFile::Header*>& dex_headers,
+                                       InstructionSet isa,
+                                       /* out */ uint32_t* location_checksum,
+                                       /* out */ std::string* dex_location,
+                                       /* out */ std::string* vdex_filename);
+
+  // Returns true if a filename (given as basename) is a name of a vdex for
+  // anonymous dex file(s) created by AnonymousDexVdexLocation.
+  static bool IsAnonymousVdexBasename(const std::string& basename);
+
  private:
   class OatFileInfo {
    public:
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index 7e5785e..f9823a1 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -19,6 +19,7 @@
 #include <memory>
 #include <queue>
 #include <vector>
+#include <sys/stat.h>
 
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
@@ -50,6 +51,8 @@
 #include "thread-current-inl.h"
 #include "thread_list.h"
 #include "thread_pool.h"
+#include "vdex_file.h"
+#include "verifier/verifier_deps.h"
 #include "well_known_classes.h"
 
 namespace art {
@@ -644,10 +647,90 @@
   return dex_files;
 }
 
+static std::vector<const DexFile::Header*> GetDexFileHeaders(
+    const std::vector<const DexFile*>& dex_files) {
+  std::vector<const DexFile::Header*> headers;
+  headers.reserve(dex_files.size());
+  for (const DexFile* dex_file : dex_files) {
+    headers.push_back(&dex_file->GetHeader());
+  }
+  return headers;
+}
+
+// Check how many vdex files exist in the same directory as the vdex file we are about
+// to write. If more than or equal to kAnonymousVdexCacheSize, unlink the least
+// recently used one(s) (according to stat-reported atime).
+static bool UnlinkLeastRecentlyUsedVdexIfNeeded(const std::string& vdex_path_to_add,
+                                                std::string* error_msg) {
+  if (OS::FileExists(vdex_path_to_add.c_str())) {
+    // File already exists and will be overwritten.
+    // This will not change the number of entries in the cache.
+    return true;
+  }
+
+  auto last_slash = vdex_path_to_add.rfind('/');
+  CHECK(last_slash != std::string::npos);
+  std::string vdex_dir = vdex_path_to_add.substr(0, last_slash + 1);
+
+  if (!OS::DirectoryExists(vdex_dir.c_str())) {
+    // Folder does not exist yet. Cache has zero entries.
+    return true;
+  }
+
+  std::vector<std::pair<time_t, std::string>> cache;
+
+  DIR* c_dir = opendir(vdex_dir.c_str());
+  if (c_dir == nullptr) {
+    *error_msg = "Unable to open " + vdex_dir + " to delete unused vdex files";
+    return false;
+  }
+  for (struct dirent* de = readdir(c_dir); de != nullptr; de = readdir(c_dir)) {
+    if (de->d_type != DT_REG) {
+      continue;
+    }
+    std::string basename = de->d_name;
+    if (!OatFileAssistant::IsAnonymousVdexBasename(basename)) {
+      continue;
+    }
+    std::string fullname = vdex_dir + basename;
+
+    struct stat s;
+    int rc = TEMP_FAILURE_RETRY(stat(fullname.c_str(), &s));
+    if (rc == -1) {
+      *error_msg = "Failed to stat() anonymous vdex file " + fullname;
+      return false;
+    }
+
+    cache.push_back(std::make_pair(s.st_atime, fullname));
+  }
+  CHECK_EQ(0, closedir(c_dir)) << "Unable to close directory.";
+
+  if (cache.size() < OatFileManager::kAnonymousVdexCacheSize) {
+    return true;
+  }
+
+  std::sort(cache.begin(),
+            cache.end(),
+            [](const auto& a, const auto& b) { return a.first < b.first; });
+  for (size_t i = OatFileManager::kAnonymousVdexCacheSize - 1; i < cache.size(); ++i) {
+    if (unlink(cache[i].second.c_str()) != 0) {
+      *error_msg = "Could not unlink anonymous vdex file " + cache[i].second;
+      return false;
+    }
+  }
+
+  return true;
+}
+
 class BackgroundVerificationTask final : public Task {
  public:
-  BackgroundVerificationTask(const std::vector<const DexFile*>& dex_files, jobject class_loader)
-      : dex_files_(dex_files) {
+  BackgroundVerificationTask(const std::vector<const DexFile*>& dex_files,
+                             jobject class_loader,
+                             const char* class_loader_context,
+                             const std::string& vdex_path)
+      : dex_files_(dex_files),
+        class_loader_context_(class_loader_context),
+        vdex_path_(vdex_path) {
     Thread* const self = Thread::Current();
     ScopedObjectAccess soa(self);
     // Create a global ref for `class_loader` because it will be accessed from a different thread.
@@ -662,11 +745,15 @@
   }
 
   void Run(Thread* self) override {
+    std::string error_msg;
     ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+    verifier::VerifierDeps verifier_deps(dex_files_);
 
     // Iterate over all classes and verify them.
     for (const DexFile* dex_file : dex_files_) {
       for (uint32_t cdef_idx = 0; cdef_idx < dex_file->NumClassDefs(); cdef_idx++) {
+        const dex::ClassDef& class_def = dex_file->GetClassDef(cdef_idx);
+
         // Take handles inside the loop. The background verification is low priority
         // and we want to minimize the risk of blocking anyone else.
         ScopedObjectAccess soa(self);
@@ -675,7 +762,7 @@
             soa.Decode<mirror::ClassLoader>(class_loader_)));
         Handle<mirror::Class> h_class(hs.NewHandle<mirror::Class>(class_linker->FindClass(
             self,
-            dex_file->GetClassDescriptor(dex_file->GetClassDef(cdef_idx)),
+            dex_file->GetClassDescriptor(class_def),
             h_loader)));
 
         if (h_class == nullptr) {
@@ -700,8 +787,28 @@
 
         CHECK(h_class->IsVerified() || h_class->IsErroneous())
             << h_class->PrettyDescriptor() << ": state=" << h_class->GetStatus();
+
+        if (h_class->IsVerified()) {
+          verifier_deps.RecordClassVerified(*dex_file, class_def);
+        }
       }
     }
+
+    // Delete old vdex files if there are too many in the folder.
+    if (!UnlinkLeastRecentlyUsedVdexIfNeeded(vdex_path_, &error_msg)) {
+      LOG(ERROR) << "Could not unlink old vdex files " << vdex_path_ << ": " << error_msg;
+      return;
+    }
+
+    // Construct a vdex file and write `verifier_deps` into it.
+    if (!VdexFile::WriteToDisk(vdex_path_,
+                               dex_files_,
+                               verifier_deps,
+                               class_loader_context_,
+                               &error_msg)) {
+      LOG(ERROR) << "Could not write anonymous vdex " << vdex_path_ << ": " << error_msg;
+      return;
+    }
   }
 
   void Finalize() override {
@@ -711,12 +818,15 @@
  private:
   const std::vector<const DexFile*> dex_files_;
   jobject class_loader_;
+  const std::string class_loader_context_;
+  const std::string vdex_path_;
 
   DISALLOW_COPY_AND_ASSIGN(BackgroundVerificationTask);
 };
 
 void OatFileManager::RunBackgroundVerification(const std::vector<const DexFile*>& dex_files,
-                                               jobject class_loader) {
+                                               jobject class_loader,
+                                               const char* class_loader_context) {
   if (Runtime::Current()->IsJavaDebuggable()) {
     // Threads created by ThreadPool ("runtime threads") are not allowed to load
     // classes when debuggable to match class-initialization semantics
@@ -730,13 +840,25 @@
     return;
   }
 
-  if (verification_thread_pool_ == nullptr) {
-    verification_thread_pool_.reset(new ThreadPool("Verification thread pool",
-                                                   /* num_threads= */ 1));
-    verification_thread_pool_->StartWorkers(self);
+  uint32_t location_checksum;
+  std::string dex_location;
+  std::string vdex_path;
+  if (OatFileAssistant::AnonymousDexVdexLocation(GetDexFileHeaders(dex_files),
+                                                 kRuntimeISA,
+                                                 &location_checksum,
+                                                 &dex_location,
+                                                 &vdex_path)) {
+    if (verification_thread_pool_ == nullptr) {
+      verification_thread_pool_.reset(
+          new ThreadPool("Verification thread pool", /* num_threads= */ 1));
+      verification_thread_pool_->StartWorkers(self);
+    }
+    verification_thread_pool_->AddTask(self, new BackgroundVerificationTask(
+        dex_files,
+        class_loader,
+        class_loader_context,
+        vdex_path));
   }
-
-  verification_thread_pool_->AddTask(self, new BackgroundVerificationTask(dex_files, class_loader));
 }
 
 void OatFileManager::WaitForWorkersToBeCreated() {
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index 7535696..e5eae9f 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -106,7 +106,8 @@
 
   // Spawn a background thread which verifies all classes in the given dex files.
   void RunBackgroundVerification(const std::vector<const DexFile*>& dex_files,
-                                 jobject class_loader);
+                                 jobject class_loader,
+                                 const char* class_loader_context);
 
   // Wait for thread pool workers to be created. This is used during shutdown as
   // threads are not allowed to attach while runtime is in shutdown lock.
@@ -118,6 +119,9 @@
   // Wait for all background verification tasks to finish. This is only used by tests.
   void WaitForBackgroundVerificationTasks();
 
+  // Maximum number of anonymous vdex files kept in the process' data folder.
+  static constexpr size_t kAnonymousVdexCacheSize = 8u;
+
  private:
   enum class CheckCollisionResult {
     kSkippedUnsupportedClassLoader,
diff --git a/runtime/runtime.h b/runtime/runtime.h
index e811230..ff4755e 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -593,6 +593,18 @@
     }
   }
 
+  const std::string& GetProcessDataDirectory() const {
+    return process_data_directory_;
+  }
+
+  void SetProcessDataDirectory(const char* data_dir) {
+    if (data_dir == nullptr) {
+      process_data_directory_.clear();
+    } else {
+      process_data_directory_ = data_dir;
+    }
+  }
+
   bool IsDexFileFallbackEnabled() const {
     return allow_dex_file_fallback_;
   }
@@ -1138,6 +1150,9 @@
   // The package of the app running in this process.
   std::string process_package_name_;
 
+  // The data directory of the app running in this process.
+  std::string process_data_directory_;
+
   // Whether threads should dump their native stack on SIGQUIT.
   bool dump_native_stack_on_sig_quit_;
 
diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc
index 61cd982..4b24769 100644
--- a/runtime/vdex_file.cc
+++ b/runtime/vdex_file.cc
@@ -17,6 +17,7 @@
 #include "vdex_file.h"
 
 #include <sys/mman.h>  // For the PROT_* and MAP_* constants.
+#include <sys/stat.h>  // for mkdir()
 
 #include <memory>
 #include <unordered_set>
@@ -27,12 +28,17 @@
 #include "base/leb128.h"
 #include "base/stl_util.h"
 #include "base/unix_file/fd_file.h"
+#include "class_linker.h"
+#include "class_loader_context.h"
 #include "dex/art_dex_file_loader.h"
 #include "dex/class_accessor-inl.h"
-#include "dex/dex_file.h"
 #include "dex/dex_file_loader.h"
 #include "dex_to_dex_decompiler.h"
+#include "gc/heap.h"
+#include "gc/space/image_space.h"
 #include "quicken_info.h"
+#include "runtime.h"
+#include "verifier/verifier_deps.h"
 
 namespace art {
 
@@ -61,9 +67,13 @@
 
 VdexFile::VerifierDepsHeader::VerifierDepsHeader(uint32_t number_of_dex_files,
                                                  uint32_t verifier_deps_size,
-                                                 bool has_dex_section)
+                                                 bool has_dex_section,
+                                                 uint32_t bootclasspath_checksums_size,
+                                                 uint32_t class_loader_context_size)
     : number_of_dex_files_(number_of_dex_files),
-      verifier_deps_size_(verifier_deps_size) {
+      verifier_deps_size_(verifier_deps_size),
+      bootclasspath_checksums_size_(bootclasspath_checksums_size),
+      class_loader_context_size_(class_loader_context_size) {
   memcpy(magic_, kVdexMagic, sizeof(kVdexMagic));
   memcpy(verifier_deps_version_, kVerifierDepsVersion, sizeof(kVerifierDepsVersion));
   if (has_dex_section) {
@@ -317,4 +327,100 @@
   return GetQuickeningInfoAt(quickening_info, quickening_offset);
 }
 
+static std::string ComputeBootClassPathChecksumString() {
+  Runtime* const runtime = Runtime::Current();
+  return gc::space::ImageSpace::GetBootClassPathChecksums(
+          runtime->GetHeap()->GetBootImageSpaces(),
+          runtime->GetClassLinker()->GetBootClassPath());
+}
+
+static bool CreateDirectories(const std::string& child_path, /* out */ std::string* error_msg) {
+  size_t last_slash_pos = child_path.find_last_of('/');
+  CHECK_NE(last_slash_pos, std::string::npos) << "Invalid path: " << child_path;
+  std::string parent_path = child_path.substr(0, last_slash_pos);
+  if (OS::DirectoryExists(parent_path.c_str())) {
+    return true;
+  } else if (CreateDirectories(parent_path, error_msg)) {
+    if (mkdir(parent_path.c_str(), 0700) == 0) {
+      return true;
+    }
+    *error_msg = "Could not create directory " + parent_path;
+    return false;
+  } else {
+    return false;
+  }
+}
+
+bool VdexFile::WriteToDisk(const std::string& path,
+                           const std::vector<const DexFile*>& dex_files,
+                           const verifier::VerifierDeps& verifier_deps,
+                           const std::string& class_loader_context,
+                           std::string* error_msg) {
+  std::vector<uint8_t> verifier_deps_data;
+  verifier_deps.Encode(dex_files, &verifier_deps_data);
+
+  std::string boot_checksum = ComputeBootClassPathChecksumString();
+  DCHECK_NE(boot_checksum, "");
+
+  VdexFile::VerifierDepsHeader deps_header(dex_files.size(),
+                                           verifier_deps_data.size(),
+                                           /* has_dex_section= */ false,
+                                           boot_checksum.size(),
+                                           class_loader_context.size());
+
+  if (!CreateDirectories(path, error_msg)) {
+    return false;
+  }
+
+  std::unique_ptr<File> out(OS::CreateEmptyFileWriteOnly(path.c_str()));
+  if (out == nullptr) {
+    *error_msg = "Could not open " + path + " for writing";
+    return false;
+  }
+
+  if (!out->WriteFully(reinterpret_cast<const char*>(&deps_header), sizeof(deps_header))) {
+    *error_msg = "Could not write vdex header to " + path;
+    out->Unlink();
+    return false;
+  }
+
+  for (const DexFile* dex_file : dex_files) {
+    const uint32_t* checksum_ptr = &dex_file->GetHeader().checksum_;
+    static_assert(sizeof(*checksum_ptr) == sizeof(VdexFile::VdexChecksum));
+    if (!out->WriteFully(reinterpret_cast<const char*>(checksum_ptr),
+                         sizeof(VdexFile::VdexChecksum))) {
+      *error_msg = "Could not write dex checksums to " + path;
+      out->Unlink();
+    return false;
+    }
+  }
+
+  if (!out->WriteFully(reinterpret_cast<const char*>(verifier_deps_data.data()),
+                       verifier_deps_data.size())) {
+    *error_msg = "Could not write verifier deps to " + path;
+    out->Unlink();
+    return false;
+  }
+
+  if (!out->WriteFully(boot_checksum.c_str(), boot_checksum.size())) {
+    *error_msg = "Could not write boot classpath checksum to " + path;
+    out->Unlink();
+    return false;
+  }
+
+  if (!out->WriteFully(class_loader_context.c_str(), class_loader_context.size())) {
+    *error_msg = "Could not write class loader context to " + path;
+    out->Unlink();
+    return false;
+  }
+
+  if (out->FlushClose() != 0) {
+    *error_msg = "Could not flush and close " + path;
+    out->Unlink();
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace art
diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h
index b357d95..88114fb 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -25,11 +25,16 @@
 #include "base/mem_map.h"
 #include "base/os.h"
 #include "dex/compact_offset_table.h"
+#include "dex/dex_file.h"
 #include "quicken_info.h"
 
 namespace art {
 
-class DexFile;
+class ClassLoaderContext;
+
+namespace verifier {
+class VerifierDeps;
+}  // namespace verifier
 
 // VDEX files contain extracted DEX files. The VdexFile class maps the file to
 // memory and provides tools for accessing its individual sections.
@@ -58,11 +63,16 @@
 
 class VdexFile {
  public:
+  using VdexChecksum = uint32_t;
+  using QuickeningTableOffsetType = uint32_t;
+
   struct VerifierDepsHeader {
    public:
     VerifierDepsHeader(uint32_t number_of_dex_files_,
                        uint32_t verifier_deps_size,
-                       bool has_dex_section);
+                       bool has_dex_section,
+                       uint32_t bootclasspath_checksums_size = 0,
+                       uint32_t class_loader_context_size = 0);
 
     const char* GetMagic() const { return reinterpret_cast<const char*>(magic_); }
     const char* GetVerifierDepsVersion() const {
@@ -81,19 +91,31 @@
 
     uint32_t GetVerifierDepsSize() const { return verifier_deps_size_; }
     uint32_t GetNumberOfDexFiles() const { return number_of_dex_files_; }
+    uint32_t GetBootClassPathChecksumStringSize() const { return bootclasspath_checksums_size_; }
+    uint32_t GetClassLoaderContextStringSize() const { return class_loader_context_size_; }
 
     size_t GetSizeOfChecksumsSection() const {
       return sizeof(VdexChecksum) * GetNumberOfDexFiles();
     }
 
+    const VdexChecksum* GetDexChecksumsArray() const {
+      return reinterpret_cast<const VdexChecksum*>(
+          reinterpret_cast<const uint8_t*>(this) + sizeof(VerifierDepsHeader));
+    }
+
+    VdexChecksum GetDexChecksumAtOffset(size_t idx) const {
+      DCHECK_LT(idx, GetNumberOfDexFiles());
+      return GetDexChecksumsArray()[idx];
+    }
+
     static constexpr uint8_t kVdexInvalidMagic[] = { 'w', 'd', 'e', 'x' };
 
    private:
     static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' };
 
     // The format version of the verifier deps header and the verifier deps.
-    // Last update: Add `redefined_classes_`.
-    static constexpr uint8_t kVerifierDepsVersion[] = { '0', '2', '0', '\0' };
+    // Last update: Add boot checksum, class loader context.
+    static constexpr uint8_t kVerifierDepsVersion[] = { '0', '2', '1', '\0' };
 
     // The format version of the dex section header and the dex section, containing
     // both the dex code and the quickening data.
@@ -109,6 +131,8 @@
     uint8_t dex_section_version_[4];
     uint32_t number_of_dex_files_;
     uint32_t verifier_deps_size_;
+    uint32_t bootclasspath_checksums_size_;
+    uint32_t class_loader_context_size_;
   };
 
   struct DexSectionHeader {
@@ -132,7 +156,7 @@
     uint32_t dex_shared_data_size_;
     uint32_t quickening_info_size_;
 
-    friend class VdexFile;  // For updatig quickening_info_size_.
+    friend class VdexFile;  // For updating quickening_info_size_.
   };
 
   size_t GetComputedFileSize() const {
@@ -144,15 +168,14 @@
       size += GetDexSectionHeader().GetDexSectionSize();
       size += GetDexSectionHeader().GetQuickeningInfoSize();
     }
+    size += header.GetBootClassPathChecksumStringSize();
+    size += header.GetClassLoaderContextStringSize();
     return size;
   }
 
   // Note: The file is called "primary" to match the naming with profiles.
   static const constexpr char* kVdexNameInDmFile = "primary.vdex";
 
-  typedef uint32_t VdexChecksum;
-  using QuickeningTableOffsetType = uint32_t;
-
   explicit VdexFile(MemMap&& mmap) : mmap_(std::move(mmap)) {}
 
   // Returns nullptr if the vdex file cannot be opened or is not valid.
@@ -250,13 +273,22 @@
   }
 
   ArrayRef<const uint8_t> GetQuickeningInfo() const {
-    if (GetVerifierDepsHeader().HasDexSection()) {
-      return ArrayRef<const uint8_t>(
-          GetVerifierDepsData().data() + GetVerifierDepsHeader().GetVerifierDepsSize(),
-          GetDexSectionHeader().GetQuickeningInfoSize());
-    } else {
-      return ArrayRef<const uint8_t>();
-    }
+    return ArrayRef<const uint8_t>(
+        GetVerifierDepsData().end(),
+        GetVerifierDepsHeader().HasDexSection()
+            ? GetDexSectionHeader().GetQuickeningInfoSize() : 0);
+  }
+
+  ArrayRef<const uint8_t> GetBootClassPathChecksumData() const {
+    return ArrayRef<const uint8_t>(
+        GetQuickeningInfo().end(),
+        GetVerifierDepsHeader().GetBootClassPathChecksumStringSize());
+  }
+
+  ArrayRef<const uint8_t> GetClassLoaderContextData() const {
+    return ArrayRef<const uint8_t>(
+        GetBootClassPathChecksumData().end(),
+        GetVerifierDepsHeader().GetClassLoaderContextStringSize());
   }
 
   bool IsValid() const {
@@ -300,6 +332,16 @@
     return GetVerifierDepsHeader().HasDexSection();
   }
 
+  // Writes a vdex into `path` and returns true on success.
+  // The vdex will not contain a dex section but will store checksums of `dex_files`,
+  // encoded `verifier_deps`, as well as the current boot class path cheksum and
+  // encoded `class_loader_context`.
+  static bool WriteToDisk(const std::string& path,
+                          const std::vector<const DexFile*>& dex_files,
+                          const verifier::VerifierDeps& verifier_deps,
+                          const std::string& class_loader_context,
+                          std::string* error_msg);
+
  private:
   uint32_t GetQuickeningInfoTableOffset(const uint8_t* source_dex_begin) const;
 
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index bdffda6..b45f143 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -525,18 +525,20 @@
 void VerifierDeps::MaybeRecordVerificationStatus(const DexFile& dex_file,
                                                  const dex::ClassDef& class_def,
                                                  FailureKind failure_kind) {
-  if (failure_kind != FailureKind::kNoFailure) {
-    // The `verified_classes_` bit vector is initialized to `false`.
-    // Only continue if we are about to write `true`.
-    return;
+  // The `verified_classes_` bit vector is initialized to `false`.
+  // Only continue if we are about to write `true`.
+  if (failure_kind == FailureKind::kNoFailure) {
+    VerifierDeps* thread_deps = GetThreadLocalVerifierDeps();
+    if (thread_deps != nullptr) {
+      thread_deps->RecordClassVerified(dex_file, class_def);
+    }
   }
+}
 
-  VerifierDeps* thread_deps = GetThreadLocalVerifierDeps();
-  if (thread_deps != nullptr) {
-    DexFileDeps* dex_deps = thread_deps->GetDexFileDeps(dex_file);
-    DCHECK_EQ(dex_deps->verified_classes_.size(), dex_file.NumClassDefs());
-    dex_deps->verified_classes_[dex_file.GetIndexForClassDef(class_def)] = true;
-  }
+void VerifierDeps::RecordClassVerified(const DexFile& dex_file, const dex::ClassDef& class_def) {
+  DexFileDeps* dex_deps = GetDexFileDeps(dex_file);
+  DCHECK_EQ(dex_deps->verified_classes_.size(), dex_file.NumClassDefs());
+  dex_deps->verified_classes_[dex_file.GetIndexForClassDef(class_def)] = true;
 }
 
 void VerifierDeps::MaybeRecordClassResolution(const DexFile& dex_file,
diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h
index bf9cdc7..5002db0 100644
--- a/runtime/verifier/verifier_deps.h
+++ b/runtime/verifier/verifier_deps.h
@@ -66,6 +66,12 @@
   // same set of dex files.
   void MergeWith(std::unique_ptr<VerifierDeps> other, const std::vector<const DexFile*>& dex_files);
 
+  // Record information that a class was verified.
+  // Note that this function is different from MaybeRecordVerificationStatus() which
+  // looks up thread-local VerifierDeps first.
+  void RecordClassVerified(const DexFile& dex_file, const dex::ClassDef& class_def)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
   // Record the verification status of the class defined in `class_def`.
   static void MaybeRecordVerificationStatus(const DexFile& dex_file,
                                             const dex::ClassDef& class_def,
diff --git a/test/692-vdex-inmem-loader/expected.txt b/test/692-vdex-inmem-loader/expected.txt
index 0990d72..a127604 100644
--- a/test/692-vdex-inmem-loader/expected.txt
+++ b/test/692-vdex-inmem-loader/expected.txt
@@ -1,3 +1,4 @@
 JNI_OnLoad called
 Hello
 Hello
+Hello
diff --git a/test/692-vdex-inmem-loader/src/Main.java b/test/692-vdex-inmem-loader/src/Main.java
index b3a5e58..75aef8b 100644
--- a/test/692-vdex-inmem-loader/src/Main.java
+++ b/test/692-vdex-inmem-loader/src/Main.java
@@ -40,9 +40,15 @@
     return new ClassLoader[] { clA, clB };
   }
 
-  private static void test(ClassLoader loader, boolean invokeMethod) throws Exception {
+  private static void test(ClassLoader loader,
+                           boolean expectedHasVdexFile,
+                           boolean invokeMethod) throws Exception {
+    // If ART created a vdex file, it must have verified all the classes.
+    boolean expectedClassesVerified = expectedHasVdexFile;
+
     waitForVerifier();
-    check(!isDebuggable(), areClassesVerified(loader), "areClassesVerified");
+    check(expectedClassesVerified, areClassesVerified(loader), "areClassesVerified");
+    check(expectedHasVdexFile, hasVdexFile(loader), "areClassesVerified");
 
     if (invokeMethod) {
       loader.loadClass("art.ClassB").getDeclaredMethod("printHello").invoke(null);
@@ -52,21 +58,31 @@
   public static void main(String[] args) throws Exception {
     System.loadLibrary(args[0]);
     ClassLoader[] loaders = null;
+    boolean featureEnabled = !isDebuggable();
 
-    // Test loading both dex files in a single class loader.
-    // Background verification task should verify all their classes.
-    test(singleLoader(), /*invokeMethod*/true);
+    // Data directory not set. Background verification job should not have run
+    // and vdex should not have been created.
+    test(singleLoader(), /*hasVdex*/ false, /*invokeMethod*/ true);
+
+    // Set data directory for this process.
+    setProcessDataDir(DEX_LOCATION);
+
+    // Data directory is now set. Background verification job should have run,
+    // should have verified classes and written results to a vdex.
+    test(singleLoader(), /*hasVdex*/ featureEnabled, /*invokeMethod*/ true);
 
     // Test loading the two dex files with separate class loaders.
     // Background verification task should still verify all classes.
     loaders = multiLoader();
-    test(loaders[0], /*invokeMethod*/false);
-    test(loaders[1], /*invokeMethod*/true);
+    test(loaders[0], /*hasVdex*/ featureEnabled, /*invokeMethod*/ false);
+    test(loaders[1], /*hasVdex*/ featureEnabled, /*invokeMethod*/ true);
   }
 
   private static native boolean isDebuggable();
+  private static native void setProcessDataDir(String path);
   private static native void waitForVerifier();
   private static native boolean areClassesVerified(ClassLoader loader);
+  private static native boolean hasVdexFile(ClassLoader loader);
 
   // Defined in 674-hiddenapi.
   private static native void appendToBootClassLoader(String dexPath, boolean isCorePlatform);
diff --git a/test/692-vdex-inmem-loader/vdex_inmem_loader.cc b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
index f064953..a5d09e9 100644
--- a/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
+++ b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
@@ -29,6 +29,12 @@
   Runtime::Current()->GetOatFileManager().WaitForBackgroundVerificationTasks();
 }
 
+extern "C" JNIEXPORT void JNICALL Java_Main_setProcessDataDir(JNIEnv* env, jclass, jstring jpath) {
+  const char* path = env->GetStringUTFChars(jpath, nullptr);
+  Runtime::Current()->SetProcessDataDirectory(path);
+  env->ReleaseStringUTFChars(jpath, path);
+}
+
 extern "C" JNIEXPORT jboolean JNICALL Java_Main_areClassesVerified(JNIEnv*,
                                                                    jclass,
                                                                    jobject loader) {
@@ -68,5 +74,47 @@
   return all_verified ? JNI_TRUE : JNI_FALSE;
 }
 
+extern "C" JNIEXPORT bool JNICALL Java_Main_hasVdexFile(JNIEnv*,
+                                                        jclass,
+                                                        jobject loader) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::ClassLoader> h_loader = hs.NewHandle(soa.Decode<mirror::ClassLoader>(loader));
+
+  std::vector<const DexFile::Header*> dex_headers;
+  VisitClassLoaderDexFiles(
+      soa,
+      h_loader,
+      [&](const DexFile* dex_file) {
+        dex_headers.push_back(&dex_file->GetHeader());
+        return true;
+      });
+
+  uint32_t location_checksum;
+  std::string dex_location;
+  std::string vdex_filename;
+  std::string error_msg;
+  return OatFileAssistant::AnonymousDexVdexLocation(dex_headers,
+                                                    kRuntimeISA,
+                                                    &location_checksum,
+                                                    &dex_location,
+                                                    &vdex_filename) &&
+         OS::FileExists(vdex_filename.c_str());
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Main_getVdexCacheSize(JNIEnv*, jclass) {
+  return static_cast<jint>(OatFileManager::kAnonymousVdexCacheSize);
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isAnonymousVdexBasename(JNIEnv* env,
+                                                                        jclass,
+                                                                        jstring basename) {
+  if (basename == nullptr) {
+    return JNI_FALSE;
+  }
+  ScopedUtfChars basename_utf(env, basename);
+  return OatFileAssistant::IsAnonymousVdexBasename(basename_utf.c_str()) ? JNI_TRUE : JNI_FALSE;
+}
+
 }  // namespace Test692VdexInmemLoader
 }  // namespace art
diff --git a/test/693-vdex-inmem-loader-evict/expected.txt b/test/693-vdex-inmem-loader-evict/expected.txt
new file mode 100644
index 0000000..6a5618e
--- /dev/null
+++ b/test/693-vdex-inmem-loader-evict/expected.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/693-vdex-inmem-loader-evict/info.txt b/test/693-vdex-inmem-loader-evict/info.txt
new file mode 100644
index 0000000..29a48d1
--- /dev/null
+++ b/test/693-vdex-inmem-loader-evict/info.txt
@@ -0,0 +1,2 @@
+Check that caching of verification results for InMemoryDexClassLoader obeys a cap on the number
+of vdex files in the data folder. Least recently used vdex files should be unlinked.
\ No newline at end of file
diff --git a/test/693-vdex-inmem-loader-evict/src-secondary/gen.sh b/test/693-vdex-inmem-loader-evict/src-secondary/gen.sh
new file mode 100755
index 0000000..96b426d
--- /dev/null
+++ b/test/693-vdex-inmem-loader-evict/src-secondary/gen.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# This script generates dex files which all contain an empty class called
+# MyClassXX, where XX is the ID of the dex file. It prints the first file
+# (ID=01) and a list of checksum/signature bytes for all of the dex files
+# (ID 01 through NUM_FILES).
+# The idea is that the associated test will be able to efficiently generate
+# up to NUM_FILES dex files from the first dex file by substituting its
+# checksum/signature bytes with that of the dex file of a given ID. Note that
+# it is also necessary to replace the ID in the class descriptor, but this
+# script does not help with that.
+
+set -e
+TMP=`mktemp -d`
+NUM_FILES=30
+
+echo '  private static final byte[][] DEX_CHECKSUMS = new byte[][] {'
+for i in $(seq 1 ${NUM_FILES}); do
+  if [ ${i} -lt 10 ]; then
+    suffix=0${i}
+  else
+    suffix=${i}
+  fi
+  (cd "$TMP" && \
+      echo "public class MyClass${suffix} { }" > "$TMP/MyClass${suffix}.java" && \
+      javac -d "${TMP}" "$TMP/MyClass${suffix}.java" && \
+      d8 --output "$TMP" "$TMP/MyClass${suffix}.class" && \
+      mv "$TMP/classes.dex" "$TMP/file${suffix}.dex")
+
+  # Dump bytes 8-32 (checksum + signature) that need to change for other files.
+  checksum=`head -c 32 -z "$TMP/file${suffix}.dex" | tail -c 24 -z | base64`
+  echo '    Base64.getDecoder().decode("'${checksum}'"),'
+done
+echo '  };'
+
+# Dump first dex file as base.
+echo '  private static final byte[] DEX_BYTES_01 = Base64.getDecoder().decode('
+base64 "${TMP}/file01.dex" | sed -E 's/^/    "/' | sed ':a;N;$!ba;s/\n/" +\n/g' | sed -E '$ s/$/");/'
+
+rm -rf "$TMP"
diff --git a/test/693-vdex-inmem-loader-evict/src/Main.java b/test/693-vdex-inmem-loader-evict/src/Main.java
new file mode 100644
index 0000000..7d9da1b
--- /dev/null
+++ b/test/693-vdex-inmem-loader-evict/src/Main.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2019 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.
+*/
+
+import dalvik.system.InMemoryDexClassLoader;
+import java.lang.reflect.Method;
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.Base64;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+
+    if (isDebuggable()) {
+      // Background verification is disabled in debuggable mode. This test makes
+      // no sense then.
+      return;
+    }
+
+    setProcessDataDir(DEX_LOCATION);
+
+    final int maxCacheSize = getVdexCacheSize();
+    final int numDexFiles = DEX_BYTES_CHECKSUMS.length;
+    if (numDexFiles <= maxCacheSize) {
+      throw new IllegalStateException("Not enough dex files to test cache eviction");
+    }
+
+    // Simply load each dex file one by one.
+    check(0, getCurrentCacheSize(), "There should be no vdex files in the beginning");
+    for (int i = 0; i < numDexFiles; ++i) {
+      ClassLoader loader = loadDex(i);
+      waitForVerifier();
+      check(true, hasVdexFile(loader), "Loading dex file should have produced a vdex");
+      check(Math.min(i + 1, maxCacheSize), getCurrentCacheSize(),
+          "Unexpected number of cache entries");
+    }
+
+    // More complicated pattern where some dex files get reused.
+    for (int s = 1; s < numDexFiles; ++s) {
+      for (int i = 0; i < maxCacheSize; ++i) {
+        ClassLoader loader = loadDex(i);
+        waitForVerifier();
+        check(true, hasVdexFile(loader), "Loading dex file should have produced a vdex");
+        check(maxCacheSize, getCurrentCacheSize(), "Unexpected number of cache entries");
+      }
+    }
+  }
+
+  private static native boolean isDebuggable();
+  private static native void setProcessDataDir(String path);
+  private static native void waitForVerifier();
+  private static native boolean hasVdexFile(ClassLoader loader);
+  private static native int getVdexCacheSize();
+  private static native boolean isAnonymousVdexBasename(String basename);
+
+  private static <T> void check(T expected, T actual, String message) {
+    if (!expected.equals(actual)) {
+      System.err.println("ERROR: " + message + " (expected=" + expected.toString() +
+          ", actual=" + actual.toString() + ")");
+    }
+  }
+
+  private static int getCurrentCacheSize() {
+    int count = 0;
+    File folder = new File(DEX_LOCATION, "oat");
+    File[] subfolders = folder.listFiles();
+    if (subfolders.length != 1) {
+      throw new IllegalStateException("Expect only one subfolder - isa");
+    }
+    folder = subfolders[0];
+    for (File f : folder.listFiles()) {
+      if (f.isFile() && isAnonymousVdexBasename(f.getName())) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  private static byte[] createDex(int index) {
+    if (index >= 100) {
+      throw new IllegalArgumentException("Not more than two decimals");
+    }
+
+    // Clone the base dex file. This is the dex file for index 0 (class ID "01").
+    byte[] dex = DEX_BYTES_BASE.clone();
+
+    // Overwrite the checksum and sha1 signature.
+    System.arraycopy(DEX_BYTES_CHECKSUMS[index], 0, dex, DEX_BYTES_CHECKSUM_OFFSET,
+        DEX_BYTES_CHECKSUM_SIZE);
+
+    // Check that the class ID offsets match expectations - they should contains "01".
+    if (dex[DEX_BYTES_CLASS_ID_OFFSET1 + 0] != 0x30 ||
+        dex[DEX_BYTES_CLASS_ID_OFFSET1 + 1] != 0x31 ||
+        dex[DEX_BYTES_CLASS_ID_OFFSET2 + 0] != 0x30 ||
+        dex[DEX_BYTES_CLASS_ID_OFFSET2 + 1] != 0x31) {
+      throw new IllegalStateException("Wrong class name values");
+    }
+
+    // Overwrite class ID.
+    byte str_id1 = (byte) (0x30 + ((index + 1) / 10));
+    byte str_id2 = (byte) (0x30 + ((index + 1) % 10));
+    dex[DEX_BYTES_CLASS_ID_OFFSET1 + 0] = str_id1;
+    dex[DEX_BYTES_CLASS_ID_OFFSET1 + 1] = str_id2;
+    dex[DEX_BYTES_CLASS_ID_OFFSET2 + 0] = str_id1;
+    dex[DEX_BYTES_CLASS_ID_OFFSET2 + 1] = str_id2;
+
+    return dex;
+  }
+
+  private static ClassLoader loadDex(int index) {
+    return new InMemoryDexClassLoader(ByteBuffer.wrap(createDex(index)), /*parent*/ null);
+  }
+
+  private static final String DEX_LOCATION = System.getenv("DEX_LOCATION");
+
+  private static final int DEX_BYTES_CLASS_ID_OFFSET1 = 0xfd;
+  private static final int DEX_BYTES_CLASS_ID_OFFSET2 = 0x11d;
+  private static final int DEX_BYTES_CHECKSUM_OFFSET = 8;
+  private static final int DEX_BYTES_CHECKSUM_SIZE = 24;
+
+  // Dex file for: "public class MyClass01 {}".
+  private static final byte[] DEX_BYTES_BASE = Base64.getDecoder().decode(
+    "ZGV4CjAzNQBHVjDjQ9WQ2TSezZ0exFH00hvlJrenqvNEAgAAcAAAAHhWNBIAAAAAAAAAALABAAAG" +
+    "AAAAcAAAAAMAAACIAAAAAQAAAJQAAAAAAAAAAAAAAAIAAACgAAAAAQAAALAAAAB0AQAA0AAAAOwA" +
+    "AAD0AAAAAQEAABUBAAAlAQAAKAEAAAEAAAACAAAABAAAAAQAAAACAAAAAAAAAAAAAAAAAAAAAQAA" +
+    "AAAAAAAAAAAAAQAAAAEAAAAAAAAAAwAAAAAAAACfAQAAAAAAAAEAAQABAAAA6AAAAAQAAABwEAEA" +
+    "AAAOAAEADgAGPGluaXQ+AAtMTXlDbGFzczAxOwASTGphdmEvbGFuZy9PYmplY3Q7AA5NeUNsYXNz" +
+    "MDEuamF2YQABVgB1fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwi" +
+    "c2hhLTEiOiI4ZjI5NTlkMDExNmMyYjdmZTZlMDUxNWQ3MTQxZTRmMGY0ZTczYzBiIiwidmVyc2lv" +
+    "biI6IjEuNS41LWRldiJ9AAAAAQAAgYAE0AEAAAAAAAAADAAAAAAAAAABAAAAAAAAAAEAAAAGAAAA" +
+    "cAAAAAIAAAADAAAAiAAAAAMAAAABAAAAlAAAAAUAAAACAAAAoAAAAAYAAAABAAAAsAAAAAEgAAAB" +
+    "AAAA0AAAAAMgAAABAAAA6AAAAAIgAAAGAAAA7AAAAAAgAAABAAAAnwEAAAMQAAABAAAArAEAAAAQ" +
+    "AAABAAAAsAEAAA==");
+
+  // Checksum/SHA1 signature diff for classes MyClass01 - MyClassXX.
+  // This is just a convenient way of storing many similar dex files.
+  private static final byte[][] DEX_BYTES_CHECKSUMS = new byte[][] {
+    Base64.getDecoder().decode("R1Yw40PVkNk0ns2dHsRR9NIb5Sa3p6rz"),
+    Base64.getDecoder().decode("i1V1U3C8nexVk4uw185lXZd9kzd82iaA"),
+    Base64.getDecoder().decode("tFPbVPdpzuoDWqH71Ak5HpltBHg0frMU"),
+    Base64.getDecoder().decode("eFSc7dENiK8nxviKBmd/O2s7h/NAj+l/"),
+    Base64.getDecoder().decode("DlUfNQ3cuVrCHRyw/cOFhqEe+0r6wlUP"),
+    Base64.getDecoder().decode("KVaBmdG8Y8kx8ltEPXWyi9OCdL14yeiW"),
+    Base64.getDecoder().decode("K1bioDTHtPwmrPXkvZ0XYCiripH6KsC2"),
+    Base64.getDecoder().decode("oVHctdpHG3YTNeQlVCshTkFKVra9TG4k"),
+    Base64.getDecoder().decode("eVWMFHRY+w4lpn9Uo9jn+eNAmaRK4HEw"),
+    Base64.getDecoder().decode("/lW3Q3U4ot5A2qkhiv4Aj+s8zv7984MA"),
+    Base64.getDecoder().decode("BFRB+4HwRbuD164DB3sVy28dc+Ea5YVQ"),
+    Base64.getDecoder().decode("klQBLEXyr0cviHDHlqFyWPGKaQQnqMiD"),
+    Base64.getDecoder().decode("jlTcJAkpnbDI/E4msuvMyWqKxNMTN0YU"),
+    Base64.getDecoder().decode("vlUOrp4aN0PxcaqQrQmm597P+Ymu5Adt"),
+    Base64.getDecoder().decode("HlXyT1GoJk1m33O8OMaYxqy3K1Byyf1S"),
+    Base64.getDecoder().decode("d1O5toIKjTXNZkgP3p9RiiafhuKw4gUH"),
+    Base64.getDecoder().decode("11RsuG9UrFHPipOj9zjuGU9obctMJbq6"),
+    Base64.getDecoder().decode("dlSW5egObqheoHSRthlR2c2jVKLGQ3QL"),
+    Base64.getDecoder().decode("ulMgQEhC0XMhmKxHtgdURY6B6JEqNb3E"),
+    Base64.getDecoder().decode("YFV08vrcs49xYr1OBhrza5H8Ha86FODz"),
+    Base64.getDecoder().decode("jFKPxTFd3kn6K0p6n8YEPgm0hiozXW1p"),
+    Base64.getDecoder().decode("LlUZdlCXwAn4qksYL6Urw+bZC/fYuJ1T"),
+    Base64.getDecoder().decode("K1SuRt9xZX5lAVtbpMauOWLVXs2KooUA"),
+    Base64.getDecoder().decode("2FJAWIk0JS9EdvkgHjquLL9qdcLeHaRJ"),
+    Base64.getDecoder().decode("YVResABr9IvZLV8eeIhM3TXfGC+Y6/x1"),
+    Base64.getDecoder().decode("UVTrkVGIh8u7FBHgcbS9flI0CY5g2E3m"),
+    Base64.getDecoder().decode("oVIu6RsrT6HgnbPzNGiYZSpKS0cqNi+a"),
+    Base64.getDecoder().decode("2FR/slWq9YC6kJRDEw21RVGmJhr3/uKZ"),
+    Base64.getDecoder().decode("CFbaSi70ZVaumL7zsXWlD/ernHxCZPx6"),
+    Base64.getDecoder().decode("7FTY+T1/qevWQM6Yoe+OwNcUdgcCUomJ"),
+  };
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 34de414..b76bd5c 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -560,6 +560,7 @@
             "690-hiddenapi-same-name-methods",
             "691-hiddenapi-proxy",
             "692-vdex-inmem-loader",
+            "693-vdex-inmem-loader-evict",
             "944-transform-classloaders",
             "999-redefine-hiddenapi"
         ],
@@ -1096,6 +1097,7 @@
                   "690-hiddenapi-same-name-methods",
                   "691-hiddenapi-proxy",
                   "692-vdex-inmem-loader",
+                  "693-vdex-inmem-loader-evict",
                   "999-redefine-hiddenapi",
                   "1000-non-moving-space-stress",
                   "1001-app-image-regions",