Support background verification of secondary dex files.
Re-use the infrastructure for background verification of in-memory dex
files to also trigger for secondary dex files.
Test: 692-vdex-secondary-loader
Bug: 160294863
Change-Id: I754d519b6a903c51e439ccab100d2d8f22f45df3
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 3811ed9..1c4adab 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -326,27 +326,10 @@
return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}
-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(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,
- jstring class_loader_context) {
+ jobject class_loader) {
CHECK(cookie != nullptr);
CHECK(class_loader != nullptr);
@@ -359,17 +342,10 @@
}
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,
- class_loader_context_utf.c_str());
+ class_loader);
}
static jboolean DexFile_closeDexFile(JNIEnv* env, jclass, jobject cookie) {
@@ -954,14 +930,9 @@
"Ljava/lang/ClassLoader;"
"[Ldalvik/system/DexPathList$Element;"
")Ljava/lang/Object;"),
- 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"),
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index 8547d54..7a82899 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -21,6 +21,7 @@
#include <vector>
#include <sys/stat.h>
+#include "android-base/file.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
@@ -469,16 +470,6 @@
return headers;
}
-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;
-}
-
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
std::vector<MemMap>&& dex_mem_maps,
jobject class_loader,
@@ -602,6 +593,12 @@
// recently used one(s) (according to stat-reported atime).
static bool UnlinkLeastRecentlyUsedVdexIfNeeded(const std::string& vdex_path_to_add,
std::string* error_msg) {
+ std::string basename = android::base::Basename(vdex_path_to_add);
+ if (!OatFileAssistant::IsAnonymousVdexBasename(basename)) {
+ // File is not for in memory dex files.
+ return true;
+ }
+
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.
@@ -628,7 +625,7 @@
if (de->d_type != DT_REG) {
continue;
}
- std::string basename = de->d_name;
+ basename = de->d_name;
if (!OatFileAssistant::IsAnonymousVdexBasename(basename)) {
continue;
}
@@ -666,10 +663,8 @@
public:
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);
@@ -757,15 +752,13 @@
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,
- const char* class_loader_context) {
+ jobject class_loader) {
Runtime* const runtime = Runtime::Current();
Thread* const self = Thread::Current();
@@ -786,23 +779,40 @@
return;
}
- std::string dex_location;
- std::string vdex_path;
- if (OatFileAssistant::AnonymousDexVdexLocation(GetDexFileHeaders(dex_files),
- kRuntimeISA,
- &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));
+ if (dex_files.size() < 1) {
+ // Nothing to verify.
+ return;
}
+
+ std::string dex_location = dex_files[0]->GetLocation();
+ const std::string& data_dir = Runtime::Current()->GetProcessDataDirectory();
+ if (!android::base::StartsWith(dex_location, data_dir)) {
+ // For now, we only run background verification for secondary dex files.
+ // Running it for primary or split APKs could have some undesirable
+ // side-effects, like overloading the device on app startup.
+ return;
+ }
+
+ std::string error_msg;
+ std::string odex_filename;
+ if (!OatFileAssistant::DexLocationToOdexFilename(dex_location,
+ kRuntimeISA,
+ &odex_filename,
+ &error_msg)) {
+ LOG(WARNING) << "Could not get odex filename for " << dex_location << ": " << error_msg;
+ return;
+ }
+
+ std::string vdex_filename = GetVdexFilename(odex_filename);
+ 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,
+ vdex_filename));
}
void OatFileManager::WaitForWorkersToBeCreated() {
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index 11eabe2..294a916 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -124,8 +124,7 @@
// 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,
- const char* class_loader_context);
+ 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.
diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc
index 02a1563..4e54c0d 100644
--- a/runtime/vdex_file.cc
+++ b/runtime/vdex_file.cc
@@ -269,7 +269,8 @@
// Write checksum section.
for (const DexFile* dex_file : dex_files) {
- const uint32_t* checksum_ptr = &dex_file->GetHeader().checksum_;
+ uint32_t checksum = dex_file->GetLocationChecksum();
+ const uint32_t* checksum_ptr = &checksum;
static_assert(sizeof(*checksum_ptr) == sizeof(VdexFile::VdexChecksum));
if (!out->WriteFully(reinterpret_cast<const char*>(checksum_ptr),
sizeof(VdexFile::VdexChecksum))) {
diff --git a/test/692-vdex-inmem-loader/vdex_inmem_loader.cc b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
index aeb478e..1c7b6e8 100644
--- a/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
+++ b/test/692-vdex-inmem-loader/vdex_inmem_loader.cc
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "base/file_utils.h"
#include "class_loader_utils.h"
#include "jni.h"
#include "nativehelper/scoped_utf_chars.h"
@@ -81,23 +82,27 @@
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> h_loader = hs.NewHandle(soa.Decode<mirror::ClassLoader>(loader));
- std::vector<const DexFile::Header*> dex_headers;
+ std::vector<const DexFile*> dex_files;
VisitClassLoaderDexFiles(
soa,
h_loader,
[&](const DexFile* dex_file) {
- dex_headers.push_back(&dex_file->GetHeader());
+ dex_files.push_back(dex_file);
return true;
});
- std::string dex_location;
- std::string vdex_filename;
+ std::string dex_location = dex_files[0]->GetLocation();
+ std::string odex_filename;
std::string error_msg;
- return OatFileAssistant::AnonymousDexVdexLocation(dex_headers,
- kRuntimeISA,
- &dex_location,
- &vdex_filename) &&
- OS::FileExists(vdex_filename.c_str());
+ if (!OatFileAssistant::DexLocationToOdexFilename(dex_location,
+ kRuntimeISA,
+ &odex_filename,
+ &error_msg)) {
+ LOG(WARNING) << "Could not get odex filename for " << dex_location << ": " << error_msg;
+ return false;
+ }
+
+ return OS::FileExists(GetVdexFilename(odex_filename).c_str());
}
extern "C" JNIEXPORT jboolean JNICALL Java_Main_isBackedByOatFile(JNIEnv*,
diff --git a/test/692-vdex-secondary-loader/expected-stderr.txt b/test/692-vdex-secondary-loader/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/692-vdex-secondary-loader/expected-stderr.txt
diff --git a/test/692-vdex-secondary-loader/expected-stdout.txt b/test/692-vdex-secondary-loader/expected-stdout.txt
new file mode 100644
index 0000000..a127604
--- /dev/null
+++ b/test/692-vdex-secondary-loader/expected-stdout.txt
@@ -0,0 +1,4 @@
+JNI_OnLoad called
+Hello
+Hello
+Hello
diff --git a/test/692-vdex-secondary-loader/info.txt b/test/692-vdex-secondary-loader/info.txt
new file mode 100644
index 0000000..db23562
--- /dev/null
+++ b/test/692-vdex-secondary-loader/info.txt
@@ -0,0 +1,3 @@
+Test that dex files loaded with PathClassClassLoader get verified and the verification results
+cached in a vdex file. Subsequent loads should initialize an instance of
+OatFile using the data in the vdex.
diff --git a/test/692-vdex-secondary-loader/run b/test/692-vdex-secondary-loader/run
new file mode 100644
index 0000000..0732095
--- /dev/null
+++ b/test/692-vdex-secondary-loader/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Disable dex2oat of secondary dex files.
+exec ${RUN} $@ --no-secondary-compilation
diff --git a/test/692-vdex-secondary-loader/src-ex/art/ClassA.java b/test/692-vdex-secondary-loader/src-ex/art/ClassA.java
new file mode 100644
index 0000000..7236216
--- /dev/null
+++ b/test/692-vdex-secondary-loader/src-ex/art/ClassA.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class ClassA {
+ public static String getHello() {
+ return "Hello";
+ }
+}
diff --git a/test/692-vdex-secondary-loader/src-ex/art/ClassB.java b/test/692-vdex-secondary-loader/src-ex/art/ClassB.java
new file mode 100644
index 0000000..9d62c07
--- /dev/null
+++ b/test/692-vdex-secondary-loader/src-ex/art/ClassB.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class ClassB {
+ public static void printHello() {
+ System.out.println(ClassA.getHello());
+ }
+}
diff --git a/test/692-vdex-secondary-loader/src/Main.java b/test/692-vdex-secondary-loader/src/Main.java
new file mode 100644
index 0000000..ed53faf
--- /dev/null
+++ b/test/692-vdex-secondary-loader/src/Main.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+import dalvik.system.PathClassLoader;
+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 + ")");
+ throw new Error("");
+ }
+ }
+
+ private static ClassLoader singleLoader() {
+ return new PathClassLoader(DEX_EXTRA, /*parent*/null);
+ }
+
+ private static void test(ClassLoader loader,
+ boolean expectedHasVdexFile,
+ boolean expectedBackedByOat,
+ boolean invokeMethod) throws Exception {
+ // If ART created a vdex file, it must have verified all the classes.
+ // That happens if and only if we expect a vdex at the end of the test but
+ // do not expect it to have been loaded.
+ boolean expectedClassesVerified = expectedHasVdexFile && !expectedBackedByOat;
+
+ waitForVerifier();
+ check(expectedClassesVerified, areClassesVerified(loader), "areClassesVerified");
+ check(expectedHasVdexFile, hasVdexFile(loader), "hasVdexFile");
+ check(expectedBackedByOat, isBackedByOatFile(loader), "isBackedByOatFile");
+ check(expectedBackedByOat, areClassesPreverified(loader), "areClassesPreverified");
+
+ if (invokeMethod) {
+ loader.loadClass("art.ClassB").getDeclaredMethod("printHello").invoke(null);
+ }
+
+ if (expectedBackedByOat) {
+ String filter = getCompilerFilter(loader.loadClass("art.ClassB"));
+ if (!("verify".equals(filter))) {
+ throw new Error("Expected verify, got " + filter);
+ }
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+
+ // Feature is disabled in debuggable mode because runtime threads are not
+ // allowed to load classes.
+ boolean featureEnabled = !isDebuggable();
+
+ // SDK version not set. Background verification job should not have run
+ // and vdex should not have been created.
+ test(singleLoader(), /*hasVdex*/ false, /*backedByOat*/ false, /*invokeMethod*/ true);
+
+ // Feature only enabled for target SDK version Q and later.
+ setTargetSdkVersion(/* Q */ 29);
+
+ // SDK version directory is now set. Background verification job should have run,
+ // should have verified classes and written results to a vdex.
+ test(singleLoader(), /*hasVdex*/ featureEnabled, /*backedByOat*/ false, /*invokeMethod*/ true);
+ test(singleLoader(), /*hasVdex*/ featureEnabled, /*backedByOat*/ featureEnabled,
+ /*invokeMethod*/ true);
+ }
+
+ private static native boolean isDebuggable();
+ private static native int setTargetSdkVersion(int version);
+ private static native void waitForVerifier();
+ private static native boolean areClassesVerified(ClassLoader loader);
+ private static native boolean hasVdexFile(ClassLoader loader);
+ private static native boolean isBackedByOatFile(ClassLoader loader);
+ private static native boolean areClassesPreverified(ClassLoader loader);
+ private static native String getCompilerFilter(Class cls);
+
+ private static final String DEX_LOCATION = System.getenv("DEX_LOCATION");
+ private static final String DEX_EXTRA =
+ new File(DEX_LOCATION, "692-vdex-secondary-loader-ex.jar").getAbsolutePath();
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index a5aea43..b1ad919 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1128,6 +1128,7 @@
"689-zygote-jit-deopt",
"690-hiddenapi-same-name-methods",
"691-hiddenapi-proxy",
+ "692-vdex-secondary-loader",
"692-vdex-inmem-loader",
"693-vdex-inmem-loader-evict",
"723-string-init-range",