Verify InMemoryDexClassLoader classes in a background thread

When dex bytecode is loaded using InMemoryDexClassLoader, automatically
spawn a background thread which performs bytecode verification on every
class.

Bug: 72131483
Test: art/tools/run-libcore-tests.sh
Test: art/test.py -b -r -t 692
Change-Id: Iad54f510de02cd073e68d775d34b7dd5bdef304e
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index feaf619..d1ea655 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -340,6 +340,26 @@
   }
 }
 
+static void DexFile_verifyInBackgroundNative(JNIEnv* env,
+                                             jclass,
+                                             jobject cookie,
+                                             jobject class_loader) {
+  CHECK(cookie != nullptr);
+  CHECK(class_loader != nullptr);
+
+  // Extract list of dex files from the cookie.
+  std::vector<const DexFile*> dex_files;
+  const OatFile* oat_file;
+  if (!ConvertJavaArrayToDexFiles(env, cookie, dex_files, oat_file)) {
+    Thread::Current()->AssertPendingException();
+    return;
+  }
+  CHECK(oat_file == nullptr) << "Called verifyInBackground on a dex file backed by oat";
+
+  // Hand over to OatFileManager to spawn a verification thread.
+  Runtime::Current()->GetOatFileManager().RunBackgroundVerification(dex_files, class_loader);
+}
+
 static jboolean DexFile_closeDexFile(JNIEnv* env, jclass, jobject cookie) {
   std::vector<const DexFile*> dex_files;
   const OatFile* oat_file;
@@ -893,6 +913,7 @@
                 "[I"
                 "[I"
                 ")Ljava/lang/Object;"),
+  NATIVE_METHOD(DexFile, verifyInBackgroundNative, "(Ljava/lang/Object;Ljava/lang/ClassLoader;)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_manager.cc b/runtime/oat_file_manager.cc
index b03551b..47a6f99 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -39,6 +39,7 @@
 #include "gc/scoped_gc_critical_section.h"
 #include "gc/space/image_space.h"
 #include "handle_scope-inl.h"
+#include "jni/java_vm_ext.h"
 #include "jni/jni_internal.h"
 #include "mirror/class_loader.h"
 #include "mirror/object-inl.h"
@@ -48,6 +49,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
+#include "thread_pool.h"
 #include "well_known_classes.h"
 
 namespace art {
@@ -642,6 +644,113 @@
   return dex_files;
 }
 
+class BackgroundVerificationTask final : public Task {
+ public:
+  BackgroundVerificationTask(const std::vector<const DexFile*>& dex_files, jobject class_loader)
+      : dex_files_(dex_files) {
+    Thread* const self = Thread::Current();
+    ScopedObjectAccess soa(self);
+    // Create a global ref for `class_loader` because it will be accessed from a different thread.
+    class_loader_ = soa.Vm()->AddGlobalRef(self, soa.Decode<mirror::ClassLoader>(class_loader));
+    CHECK(class_loader_ != nullptr);
+  }
+
+  ~BackgroundVerificationTask() {
+    Thread* const self = Thread::Current();
+    ScopedObjectAccess soa(self);
+    soa.Vm()->DeleteGlobalRef(self, class_loader_);
+  }
+
+  void Run(Thread* self) override {
+    ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+
+    // 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++) {
+        // 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);
+        StackHandleScope<2> hs(self);
+        Handle<mirror::ClassLoader> h_loader(hs.NewHandle(
+            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)),
+            h_loader)));
+
+        if (h_class == nullptr) {
+          CHECK(self->IsExceptionPending());
+          self->ClearException();
+          continue;
+        }
+
+        if (&h_class->GetDexFile() != dex_file) {
+          // There is a different class in the class path or a parent class loader
+          // with the same descriptor. This `h_class` is not resolvable, skip it.
+          continue;
+        }
+
+        CHECK(h_class->IsResolved()) << h_class->PrettyDescriptor();
+        class_linker->VerifyClass(self, h_class);
+        if (h_class->IsErroneous()) {
+          // ClassLinker::VerifyClass throws, which isn't useful here.
+          CHECK(soa.Self()->IsExceptionPending());
+          soa.Self()->ClearException();
+        }
+
+        CHECK(h_class->IsVerified() || h_class->IsErroneous())
+            << h_class->PrettyDescriptor() << ": state=" << h_class->GetStatus();
+      }
+    }
+  }
+
+  void Finalize() override {
+    delete this;
+  }
+
+ private:
+  const std::vector<const DexFile*> dex_files_;
+  jobject class_loader_;
+
+  DISALLOW_COPY_AND_ASSIGN(BackgroundVerificationTask);
+};
+
+void OatFileManager::RunBackgroundVerification(const std::vector<const DexFile*>& dex_files,
+                                               jobject class_loader) {
+  Thread* const self = Thread::Current();
+  if (Runtime::Current()->IsShuttingDown(self)) {
+    // Not allowed to create new threads during runtime shutdown.
+    return;
+  }
+
+  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));
+}
+
+void OatFileManager::WaitForWorkersToBeCreated() {
+  DCHECK(!Runtime::Current()->IsShuttingDown(Thread::Current()))
+      << "Cannot create new threads during runtime shutdown";
+  if (verification_thread_pool_ != nullptr) {
+    verification_thread_pool_->WaitForWorkersToBeCreated();
+  }
+}
+
+void OatFileManager::DeleteThreadPool() {
+  verification_thread_pool_.reset(nullptr);
+}
+
+void OatFileManager::WaitForBackgroundVerificationTasks() {
+  Thread* const self = Thread::Current();
+  if (verification_thread_pool_ != nullptr) {
+    verification_thread_pool_->Wait(self, /* do_work= */ true, /* may_hold_locks= */ false);
+  }
+}
+
 void OatFileManager::SetOnlyUseSystemOatFiles(bool assert_no_files_loaded) {
   ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
   if (assert_no_files_loaded) {
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index 9c6c04b..24d8e42 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -38,6 +38,7 @@
 class ClassLoaderContext;
 class DexFile;
 class OatFile;
+class ThreadPool;
 
 // Class for dealing with oat file management.
 //
@@ -103,6 +104,20 @@
 
   void SetOnlyUseSystemOatFiles(bool assert_no_files_loaded);
 
+  // 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);
+
+  // 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.
+  void WaitForWorkersToBeCreated();
+
+  // If allocated, delete a thread pool of background verification threads.
+  void DeleteThreadPool();
+
+  // Wait for all background verification tasks to finish. This is only used by tests.
+  void WaitForBackgroundVerificationTasks();
+
  private:
   enum class CheckCollisionResult {
     kSkippedUnsupportedClassLoader,
@@ -143,6 +158,9 @@
   // is not on /system, don't load it "executable".
   bool only_use_system_oat_files_;
 
+  // Single-thread pool used to run the verifier in the background.
+  std::unique_ptr<ThreadPool> verification_thread_pool_;
+
   DISALLOW_COPY_AND_ASSIGN(OatFileManager);
 };
 
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index c978b83..c4e4b61 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -364,17 +364,19 @@
         << "\n";
   }
 
+  // Wait for the workers of thread pools to be created since there can't be any
+  // threads attaching during shutdown.
   WaitForThreadPoolWorkersToStart();
-
   if (jit_ != nullptr) {
-    // Wait for the workers to be created since there can't be any threads attaching during
-    // shutdown.
     jit_->WaitForWorkersToBeCreated();
     // Stop the profile saver thread before marking the runtime as shutting down.
     // The saver will try to dump the profiles before being sopped and that
     // requires holding the mutator lock.
     jit_->StopProfileSaver();
   }
+  if (oat_file_manager_ != nullptr) {
+    oat_file_manager_->WaitForWorkersToBeCreated();
+  }
 
   {
     ScopedTrace trace2("Wait for shutdown cond");
@@ -419,6 +421,9 @@
     // JIT compiler threads.
     jit_->DeleteThreadPool();
   }
+  if (oat_file_manager_ != nullptr) {
+    oat_file_manager_->DeleteThreadPool();
+  }
   DeleteThreadPool();
   CHECK(thread_pool_ == nullptr);
 
diff --git a/test/674-hiddenapi/src-art/Main.java b/test/674-hiddenapi/src-art/Main.java
index d6a8c6d..c92d352 100644
--- a/test/674-hiddenapi/src-art/Main.java
+++ b/test/674-hiddenapi/src-art/Main.java
@@ -14,18 +14,12 @@
  * limitations under the License.
  */
 
-import dalvik.system.InMemoryDexClassLoader;
 import dalvik.system.PathClassLoader;
-import dalvik.system.VMRuntime;
 import java.io.File;
-import java.io.InputStream;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
-import java.nio.ByteBuffer;
 import java.nio.file.Files;
 import java.util.Arrays;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 
 public class Main {
   // This needs to be kept in sync with DexDomain in ChildClass.
@@ -52,6 +46,9 @@
     // As a side effect, we also cannot test Platform->Platform and later
     // Platform->CorePlatform as the former succeeds in verifying linkage usages
     // that should fail in the latter.
+    // We also cannot use InMemoryDexClassLoader because it runs verification in
+    // a background thread and being able to dynamically change the configuration
+    // (like list of exemptions) would require proper thread synchronization.
 
     // Run test with both parent and child dex files loaded with class loaders.
     // The expectation is that hidden members in parent should be visible to
@@ -104,7 +101,7 @@
     // Load child dex if it is not in boot class path.
     ClassLoader childLoader = null;
     if (childDomain == DexDomain.Application) {
-      childLoader = new InMemoryDexClassLoader(readDexFile(DEX_CHILD), parentLoader);
+      childLoader = new PathClassLoader(DEX_CHILD, parentLoader);
     } else {
       if (parentLoader != BOOT_CLASS_LOADER) {
         throw new IllegalStateException(
@@ -150,22 +147,6 @@
     }
   }
 
-  // Helper to read dex file into memory.
-  private static ByteBuffer readDexFile(String jarFileName) throws Exception {
-    ZipFile zip = new ZipFile(new File(jarFileName));
-    ZipEntry entry = zip.getEntry("classes.dex");
-    InputStream is = zip.getInputStream(entry);
-    int offset = 0;
-    int size = (int) entry.getSize();
-    ByteBuffer buffer = ByteBuffer.allocate(size);
-    while (is.available() > 0) {
-      is.read(buffer.array(), offset, size - offset);
-    }
-    is.close();
-    zip.close();
-    return buffer;
-  }
-
   // Copy native library to a new file with a unique name so it does not
   // conflict with other loaded instance of the same binary file.
   private static String createNativeLibCopy(DexDomain parentDomain, DexDomain childDomain,
diff --git a/test/692-vdex-inmem-loader/expected.txt b/test/692-vdex-inmem-loader/expected.txt
new file mode 100644
index 0000000..0990d72
--- /dev/null
+++ b/test/692-vdex-inmem-loader/expected.txt
@@ -0,0 +1,3 @@
+JNI_OnLoad called
+Hello
+Hello
diff --git a/test/692-vdex-inmem-loader/info.txt b/test/692-vdex-inmem-loader/info.txt
new file mode 100644
index 0000000..435b9c5
--- /dev/null
+++ b/test/692-vdex-inmem-loader/info.txt
@@ -0,0 +1,3 @@
+Test that dex files loaded with InMemoryDexClassLoader get verified and the verification results
+cached in a vdex file in the app's data folder. Subsequent loads should initialize an instance of
+OatFile using the data in the vdex.
\ No newline at end of file
diff --git a/test/692-vdex-inmem-loader/src-ex/DummyClass.java b/test/692-vdex-inmem-loader/src-ex/DummyClass.java
new file mode 100644
index 0000000..443d1fe
--- /dev/null
+++ b/test/692-vdex-inmem-loader/src-ex/DummyClass.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+public class DummyClass {
+}
diff --git a/test/692-vdex-inmem-loader/src-secondary/art/ClassA.java b/test/692-vdex-inmem-loader/src-secondary/art/ClassA.java
new file mode 100644
index 0000000..2b2bc5d
--- /dev/null
+++ b/test/692-vdex-inmem-loader/src-secondary/art/ClassA.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+package art;
+
+public class ClassA {
+  public static String getHello() {
+    return "Hello";
+  }
+}
diff --git a/test/692-vdex-inmem-loader/src-secondary/art/ClassB.java b/test/692-vdex-inmem-loader/src-secondary/art/ClassB.java
new file mode 100644
index 0000000..45c450e
--- /dev/null
+++ b/test/692-vdex-inmem-loader/src-secondary/art/ClassB.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+package art;
+
+public class ClassB {
+  public static void printHello() {
+    System.out.println(ClassA.getHello());
+  }
+}
diff --git a/test/692-vdex-inmem-loader/src-secondary/gen.sh b/test/692-vdex-inmem-loader/src-secondary/gen.sh
new file mode 100755
index 0000000..67df40e
--- /dev/null
+++ b/test/692-vdex-inmem-loader/src-secondary/gen.sh
@@ -0,0 +1,37 @@
+#!/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.
+
+set -e
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+TMP=`mktemp -d`
+
+CLASS_A="art/ClassA"
+CLASS_B="art/ClassB"
+
+(cd "$TMP" && \
+    javac -d "${TMP}" "$DIR/${CLASS_A}.java" "$DIR/${CLASS_B}.java" && \
+    d8 --output . "$TMP/${CLASS_A}.class" &&
+    mv "$TMP/classes.dex" "$TMP/classesA.dex" &&
+    d8 --output . "$TMP/${CLASS_B}.class" &&
+    mv "$TMP/classes.dex" "$TMP/classesB.dex")
+
+echo '  private static final byte[] DEX_BYTES_A = Base64.getDecoder().decode('
+base64 "${TMP}/classesA.dex" | sed -E 's/^/    "/' | sed ':a;N;$!ba;s/\n/" +\n/g' | sed -E '$ s/$/");/'
+
+echo '  private static final byte[] DEX_BYTES_B = Base64.getDecoder().decode('
+base64 "${TMP}/classesB.dex" | sed -E 's/^/    "/' | sed ':a;N;$!ba;s/\n/" +\n/g' | sed -E '$ s/$/");/'
+
+rm -rf "$TMP"
diff --git a/test/692-vdex-inmem-loader/src/Main.java b/test/692-vdex-inmem-loader/src/Main.java
new file mode 100644
index 0000000..bfdf16a
--- /dev/null
+++ b/test/692-vdex-inmem-loader/src/Main.java
@@ -0,0 +1,108 @@
+/*
+ * 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 {
+  private static void check(boolean expected, boolean actual, String message) {
+    if (expected != actual) {
+      System.err.println(
+          "ERROR: " + message + " (expected=" + expected + ", actual=" + actual + ")");
+    }
+  }
+
+  private static ClassLoader singleLoader() {
+    return new InMemoryDexClassLoader(
+        new ByteBuffer[] { ByteBuffer.wrap(DEX_BYTES_A), ByteBuffer.wrap(DEX_BYTES_B) },
+        /*parent*/null);
+  }
+
+  private static ClassLoader[] multiLoader() {
+    ClassLoader clA = new InMemoryDexClassLoader(ByteBuffer.wrap(DEX_BYTES_A), /*parent*/ null);
+    ClassLoader clB = new InMemoryDexClassLoader(ByteBuffer.wrap(DEX_BYTES_B), /*parent*/ clA);
+    return new ClassLoader[] { clA, clB };
+  }
+
+  private static void test(ClassLoader loader, boolean invokeMethod) throws Exception {
+    waitForVerifier();
+    check(true, areClassesVerified(loader), "areClassesVerified");
+
+    if (invokeMethod) {
+      loader.loadClass("art.ClassB").getDeclaredMethod("printHello").invoke(null);
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+    ClassLoader[] loaders = null;
+
+    // Test loading both dex files in a single class loader.
+    // Background verification task should verify all their classes.
+    test(singleLoader(), /*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);
+  }
+
+  private static native void waitForVerifier();
+  private static native boolean areClassesVerified(ClassLoader loader);
+
+  // Defined in 674-hiddenapi.
+  private static native void appendToBootClassLoader(String dexPath, boolean isCorePlatform);
+
+  private static final String DEX_LOCATION = System.getenv("DEX_LOCATION");
+  private static final String DEX_EXTRA =
+      new File(DEX_LOCATION, "692-vdex-inmem-loader-ex.jar").getAbsolutePath();
+
+  private static final byte[] DEX_BYTES_A = Base64.getDecoder().decode(
+    "ZGV4CjAzNQBxYu/tdPfiHaRPYr5yaT6ko9V/xMinr1OwAgAAcAAAAHhWNBIAAAAAAAAAABwCAAAK" +
+    "AAAAcAAAAAQAAACYAAAAAgAAAKgAAAAAAAAAAAAAAAMAAADAAAAAAQAAANgAAAC4AQAA+AAAADAB" +
+    "AAA4AQAARQEAAEwBAABPAQAAXQEAAHEBAACFAQAAiAEAAJIBAAAEAAAABQAAAAYAAAAHAAAAAwAA" +
+    "AAIAAAAAAAAABwAAAAMAAAAAAAAAAAABAAAAAAAAAAAACAAAAAEAAQAAAAAAAAAAAAEAAAABAAAA" +
+    "AAAAAAEAAAAAAAAACQIAAAAAAAABAAAAAAAAACwBAAADAAAAGgACABEAAAABAAEAAQAAACgBAAAE" +
+    "AAAAcBACAAAADgATAA4AFQAOAAY8aW5pdD4AC0NsYXNzQS5qYXZhAAVIZWxsbwABTAAMTGFydC9D" +
+    "bGFzc0E7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwABVgAIZ2V0SGVs" +
+    "bG8AdX5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoi" +
+    "OTY2MDhmZDdiYmNjZGQyMjc2Y2Y4OTI4M2QyYjgwY2JmYzRmYzgxYyIsInZlcnNpb24iOiIxLjUu" +
+    "NC1kZXYifQAAAAIAAIGABJACAQn4AQAAAAAADAAAAAAAAAABAAAAAAAAAAEAAAAKAAAAcAAAAAIA" +
+    "AAAEAAAAmAAAAAMAAAACAAAAqAAAAAUAAAADAAAAwAAAAAYAAAABAAAA2AAAAAEgAAACAAAA+AAA" +
+    "AAMgAAACAAAAKAEAAAIgAAAKAAAAMAEAAAAgAAABAAAACQIAAAMQAAABAAAAGAIAAAAQAAABAAAA" +
+    "HAIAAA==");
+  private static final byte[] DEX_BYTES_B = Base64.getDecoder().decode(
+    "ZGV4CjAzNQB+hWvce73hXt7ZVNgp9RAyMLSwQzsWUjV4AwAAcAAAAHhWNBIAAAAAAAAAAMwCAAAQ" +
+    "AAAAcAAAAAcAAACwAAAAAwAAAMwAAAABAAAA8AAAAAUAAAD4AAAAAQAAACABAAA4AgAAQAEAAI4B" +
+    "AACWAQAAowEAAKYBAAC0AQAAwgEAANkBAADtAQAAAQIAABUCAAAYAgAAHAIAACYCAAArAgAANwIA" +
+    "AEACAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAAAgAAAAQAAAAAAAAACQAAAAYAAAAAAAAA" +
+    "CgAAAAYAAACIAQAABQACAAwAAAAAAAAACwAAAAEAAQAAAAAAAQABAA0AAAACAAIADgAAAAMAAQAA" +
+    "AAAAAQAAAAEAAAADAAAAAAAAAAEAAAAAAAAAtwIAAAAAAAABAAEAAQAAAHwBAAAEAAAAcBAEAAAA" +
+    "DgACAAAAAgAAAIABAAAKAAAAYgAAAHEAAAAAAAwBbiADABAADgATAA4AFQAOlgAAAAABAAAABAAG" +
+    "PGluaXQ+AAtDbGFzc0IuamF2YQABTAAMTGFydC9DbGFzc0E7AAxMYXJ0L0NsYXNzQjsAFUxqYXZh" +
+    "L2lvL1ByaW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsA" +
+    "EkxqYXZhL2xhbmcvU3lzdGVtOwABVgACVkwACGdldEhlbGxvAANvdXQACnByaW50SGVsbG8AB3By" +
+    "aW50bG4AdX5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0x" +
+    "IjoiOTY2MDhmZDdiYmNjZGQyMjc2Y2Y4OTI4M2QyYjgwY2JmYzRmYzgxYyIsInZlcnNpb24iOiIx" +
+    "LjUuNC1kZXYifQAAAAIAAYGABMACAQnYAgAAAAAAAAAOAAAAAAAAAAEAAAAAAAAAAQAAABAAAABw" +
+    "AAAAAgAAAAcAAACwAAAAAwAAAAMAAADMAAAABAAAAAEAAADwAAAABQAAAAUAAAD4AAAABgAAAAEA" +
+    "AAAgAQAAASAAAAIAAABAAQAAAyAAAAIAAAB8AQAAARAAAAEAAACIAQAAAiAAABAAAACOAQAAACAA" +
+    "AAEAAAC3AgAAAxAAAAEAAADIAgAAABAAAAEAAADMAgAA");
+}
diff --git a/test/692-vdex-inmem-loader/vdex_inmem_loader.cc b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
new file mode 100644
index 0000000..f064953
--- /dev/null
+++ b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "class_loader_utils.h"
+#include "jni.h"
+#include "nativehelper/scoped_utf_chars.h"
+#include "oat_file_assistant.h"
+#include "oat_file_manager.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread.h"
+
+namespace art {
+namespace Test692VdexInmemLoader {
+
+extern "C" JNIEXPORT void JNICALL Java_Main_waitForVerifier(JNIEnv*, jclass) {
+  Runtime::Current()->GetOatFileManager().WaitForBackgroundVerificationTasks();
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_areClassesVerified(JNIEnv*,
+                                                                   jclass,
+                                                                   jobject loader) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<2> hs(soa.Self());
+  Handle<mirror::ClassLoader> h_loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(loader)));
+
+  std::vector<const DexFile*> dex_files;
+  VisitClassLoaderDexFiles(
+      soa,
+      h_loader,
+      [&](const DexFile* dex_file) {
+        dex_files.push_back(dex_file);
+        return true;
+      });
+
+  MutableHandle<mirror::Class> h_class(hs.NewHandle<mirror::Class>(nullptr));
+  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+
+  bool is_first = true;
+  bool all_verified = false;
+  for (const DexFile* dex_file : dex_files) {
+    for (uint16_t cdef_idx = 0; cdef_idx < dex_file->NumClassDefs(); ++cdef_idx) {
+      const char* desc = dex_file->GetClassDescriptor(dex_file->GetClassDef(cdef_idx));
+      h_class.Assign(class_linker->FindClass(soa.Self(), desc, h_loader));
+      CHECK(h_class != nullptr) << "Could not find class " << desc;
+      bool is_verified = h_class->IsVerified();
+      if (is_first) {
+        all_verified = is_verified;
+        is_first = false;
+      } else if (all_verified != is_verified) {
+        // Classes should either all or none be verified.
+        LOG(ERROR) << "areClassesVerified is inconsistent";
+      }
+    }
+  }
+  return all_verified ? JNI_TRUE : JNI_FALSE;
+}
+
+}  // namespace Test692VdexInmemLoader
+}  // namespace art
diff --git a/test/Android.bp b/test/Android.bp
index 5460b3a..3e56442 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -492,6 +492,7 @@
         "664-aget-verifier/aget-verifier.cc",
         "667-jit-jni-stub/jit_jni_stub_test.cc",
         "674-hiddenapi/hiddenapi.cc",
+        "692-vdex-inmem-loader/vdex_inmem_loader.cc",
         "708-jit-cache-churn/jit.cc",
         "800-smali/jni.cc",
         "909-attach-agent/disallow_debugging.cc",
diff --git a/test/knownfailures.json b/test/knownfailures.json
index e78e002..f77999b 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -559,6 +559,7 @@
             "674-hiddenapi",
             "690-hiddenapi-same-name-methods",
             "691-hiddenapi-proxy",
+            "692-vdex-inmem-loader",
             "944-transform-classloaders",
             "999-redefine-hiddenapi"
         ],
@@ -1094,6 +1095,7 @@
                   "688-shared-library",
                   "690-hiddenapi-same-name-methods",
                   "691-hiddenapi-proxy",
+                  "692-vdex-inmem-loader",
                   "999-redefine-hiddenapi",
                   "1000-non-moving-space-stress",
                   "1001-app-image-regions",