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",