Add support for processing class loader contexts
Initial support for recognizing the class loader contexts.
In order to correctly compile dex files which at runtime are loaded with
a non-trivial class loader chain we need to make dex2oat aware of the
precise runtime context.
This CL adds the infrastructure to process arbitrary and arbitrary chain
of class loaders. ClassLoaderContext is able to parse a class loader
spec from a string and create the runtime structure based on it.
The integration with dex2oat and oat file assistant will follow up.
The string specification looks like
"PCL[lib1.dex:lib2.dex];DLC[lib3.dex]"
It describes how the class loader chain should be build in order to
ensure classes are resolved during dex2aot as they would be resolved at
runtime. This spec will be encoded in the oat file. If at runtime the
dex file will be loaded in a different context, the oat file will be
rejected.
The chain is interpreted in the natural 'parent order', meaning that
class loader 'i+1' will be the parent of class loader 'i'. The
compilation sources will be added to the classpath of the last class
loader. This allows the compiled dex files to be loaded at runtime in a
class loader that contains other dex files as well (e.g. shared
libraries).
Note that we accept chains for which the source dex files specified
with --dex-file are found in the classpath. In this case the source dex
files will be removed from the any class loader's classpath possibly
resulting in empty class loaders.
* This is the first CL, which adds the infrastructure for processing
a class loader context. Currently it CHECKS that only a single
PathClassLoader is created.
Test: m test-art-host
Bug: 38138251
Change-Id: I312aa12b5732288f3c1df4746b5775a32e0bfb04
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
new file mode 100644
index 0000000..4643e78
--- /dev/null
+++ b/runtime/class_loader_context_test.cc
@@ -0,0 +1,265 @@
+/*
+ * 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 <gtest/gtest.h>
+
+
+#include "class_loader_context.h"
+#include "common_runtime_test.h"
+
+#include "base/dchecked_vector.h"
+#include "base/stl_util.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "handle_scope-inl.h"
+#include "mirror/class.h"
+#include "mirror/class_loader.h"
+#include "mirror/object-inl.h"
+#include "oat_file_assistant.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread.h"
+#include "well_known_classes.h"
+
+namespace art {
+
+class ClassLoaderContextTest : public CommonRuntimeTest {
+ public:
+ void VerifyContextSize(ClassLoaderContext* context, size_t expected_size) {
+ ASSERT_TRUE(context != nullptr);
+ ASSERT_EQ(expected_size, context->class_loader_chain_.size());
+ }
+
+ void VerifyClassLoaderPCL(ClassLoaderContext* context,
+ size_t index,
+ std::string classpath) {
+ VerifyClassLoaderInfo(
+ context, index, ClassLoaderContext::kPathClassLoader, classpath);
+ }
+
+ void VerifyClassLoaderDLC(ClassLoaderContext* context,
+ size_t index,
+ std::string classpath) {
+ VerifyClassLoaderInfo(
+ context, index, ClassLoaderContext::kDelegateLastClassLoader, classpath);
+ }
+
+ void VerifyOpenDexFiles(
+ ClassLoaderContext* context,
+ size_t index,
+ std::vector<std::vector<std::unique_ptr<const DexFile>>*>& all_dex_files) {
+ ASSERT_TRUE(context != nullptr);
+ ASSERT_TRUE(context->dex_files_open_attempted_);
+ ASSERT_TRUE(context->dex_files_open_result_);
+ ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index];
+ ASSERT_EQ(all_dex_files.size(), info.classpath.size());
+ size_t cur_open_dex_index = 0;
+ for (size_t k = 0; k < all_dex_files.size(); k++) {
+ std::vector<std::unique_ptr<const DexFile>>& dex_files_for_cp_elem = *(all_dex_files[k]);
+ for (size_t i = 0; i < dex_files_for_cp_elem.size(); i++) {
+ ASSERT_LT(cur_open_dex_index, info.opened_dex_files.size());
+
+ std::unique_ptr<const DexFile>& opened_dex_file =
+ info.opened_dex_files[cur_open_dex_index++];
+ std::unique_ptr<const DexFile>& expected_dex_file = dex_files_for_cp_elem[i];
+
+ ASSERT_EQ(expected_dex_file->GetLocation(), opened_dex_file->GetLocation());
+ ASSERT_EQ(expected_dex_file->GetLocationChecksum(), opened_dex_file->GetLocationChecksum());
+ ASSERT_EQ(info.classpath[k], opened_dex_file->GetBaseLocation());
+ }
+ }
+ }
+
+ private:
+ void VerifyClassLoaderInfo(ClassLoaderContext* context,
+ size_t index,
+ ClassLoaderContext::ClassLoaderType type,
+ std::string classpath) {
+ ASSERT_TRUE(context != nullptr);
+ ASSERT_GT(context->class_loader_chain_.size(), index);
+ ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index];
+ ASSERT_EQ(type, info.type);
+ std::vector<std::string> expected_classpath;
+ Split(classpath, ':', &expected_classpath);
+ ASSERT_EQ(expected_classpath, info.classpath);
+ }
+};
+
+TEST_F(ClassLoaderContextTest, ParseValidContextPCL) {
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("PCL[a.dex]");
+ VerifyContextSize(context.get(), 1);
+ VerifyClassLoaderPCL(context.get(), 0, "a.dex");
+}
+
+TEST_F(ClassLoaderContextTest, ParseValidContextDLC) {
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("DLC[a.dex]");
+ VerifyContextSize(context.get(), 1);
+ VerifyClassLoaderDLC(context.get(), 0, "a.dex");
+}
+
+TEST_F(ClassLoaderContextTest, ParseValidContextChain) {
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("PCL[a.dex:b.dex];DLC[c.dex:d.dex];PCL[e.dex]");
+ VerifyContextSize(context.get(), 3);
+ VerifyClassLoaderPCL(context.get(), 0, "a.dex:b.dex");
+ VerifyClassLoaderDLC(context.get(), 1, "c.dex:d.dex");
+ VerifyClassLoaderPCL(context.get(), 2, "e.dex");
+}
+
+TEST_F(ClassLoaderContextTest, ParseValidEmptyContextDLC) {
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("DLC[]");
+ VerifyContextSize(context.get(), 1);
+ VerifyClassLoaderDLC(context.get(), 0, "");
+}
+
+TEST_F(ClassLoaderContextTest, ParseValidContextSpecialSymbol) {
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create(OatFile::kSpecialSharedLibrary);
+ VerifyContextSize(context.get(), 0);
+}
+
+TEST_F(ClassLoaderContextTest, ParseInvalidValidContexts) {
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("ABC[a.dex]"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL[a.dex"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCLa.dex]"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL{a.dex}"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL[a.dex];DLC[b.dex"));
+}
+
+TEST_F(ClassLoaderContextTest, OpenInvalidDexFiles) {
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("PCL[does_not_exist.dex]");
+ VerifyContextSize(context.get(), 1);
+ ASSERT_FALSE(context->OpenDexFiles(InstructionSet::kArm, "."));
+}
+
+TEST_F(ClassLoaderContextTest, OpenValidDexFiles) {
+ std::string multidex_name = GetTestDexFileName("MultiDex");
+ std::vector<std::unique_ptr<const DexFile>> multidex_files = OpenTestDexFiles("MultiDex");
+ std::string myclass_dex_name = GetTestDexFileName("MyClass");
+ std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
+ std::string dex_name = GetTestDexFileName("Main");
+ std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Main");
+
+
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create(
+ "PCL[" + multidex_name + ":" + myclass_dex_name + "];" +
+ "DLC[" + dex_name + "]");
+
+ ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, /*classpath_dir*/ ""));
+
+ VerifyContextSize(context.get(), 2);
+ std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0;
+ all_dex_files0.push_back(&multidex_files);
+ all_dex_files0.push_back(&myclass_dex_files);
+ std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files1;
+ all_dex_files1.push_back(&dex_files);
+
+ VerifyOpenDexFiles(context.get(), 0, all_dex_files0);
+ VerifyOpenDexFiles(context.get(), 1, all_dex_files1);
+}
+
+TEST_F(ClassLoaderContextTest, OpenInvalidDexFilesMix) {
+ std::string dex_name = GetTestDexFileName("Main");
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("PCL[does_not_exist.dex];DLC[" + dex_name + "]");
+ ASSERT_FALSE(context->OpenDexFiles(InstructionSet::kArm, ""));
+}
+
+TEST_F(ClassLoaderContextTest, CreateClassLoader) {
+ std::string dex_name = GetTestDexFileName("Main");
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("PCL[" + dex_name + "]");
+ ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, ""));
+
+ std::vector<std::unique_ptr<const DexFile>> classpath_dex = OpenTestDexFiles("Main");
+ std::vector<std::unique_ptr<const DexFile>> compilation_sources = OpenTestDexFiles("MultiDex");
+
+ std::vector<const DexFile*> compilation_sources_raw =
+ MakeNonOwningPointerVector(compilation_sources);
+ jobject jclass_loader = context->CreateClassLoader(compilation_sources_raw);
+ ASSERT_TRUE(jclass_loader != nullptr);
+
+ ScopedObjectAccess soa(Thread::Current());
+
+ StackHandleScope<2> hs(soa.Self());
+ Handle<mirror::ClassLoader> class_loader = hs.NewHandle(
+ soa.Decode<mirror::ClassLoader>(jclass_loader));
+
+ ASSERT_TRUE(class_loader->GetClass() ==
+ soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader));
+ ASSERT_TRUE(class_loader->GetParent()->GetClass() ==
+ soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+
+
+ std::vector<const DexFile*> class_loader_dex_files = GetDexFiles(jclass_loader);
+ ASSERT_EQ(classpath_dex.size() + compilation_sources.size(), class_loader_dex_files.size());
+
+ // The classpath dex files must come first.
+ for (size_t i = 0; i < classpath_dex.size(); i++) {
+ ASSERT_EQ(classpath_dex[i]->GetLocation(),
+ class_loader_dex_files[i]->GetLocation());
+ ASSERT_EQ(classpath_dex[i]->GetLocationChecksum(),
+ class_loader_dex_files[i]->GetLocationChecksum());
+ }
+
+ // The compilation dex files must come second.
+ for (size_t i = 0, k = classpath_dex.size(); i < compilation_sources.size(); i++, k++) {
+ ASSERT_EQ(compilation_sources[i]->GetLocation(),
+ class_loader_dex_files[k]->GetLocation());
+ ASSERT_EQ(compilation_sources[i]->GetLocationChecksum(),
+ class_loader_dex_files[k]->GetLocationChecksum());
+ }
+}
+
+TEST_F(ClassLoaderContextTest, RemoveSourceLocations) {
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("PCL[a.dex]");
+ dchecked_vector<std::string> classpath_dex;
+ classpath_dex.push_back("a.dex");
+ dchecked_vector<std::string> compilation_sources;
+ compilation_sources.push_back("src.dex");
+
+ // Nothing should be removed.
+ ASSERT_FALSE(context->RemoveLocationsFromClassPaths(compilation_sources));
+ VerifyClassLoaderPCL(context.get(), 0, "a.dex");
+ // Classes should be removed.
+ ASSERT_TRUE(context->RemoveLocationsFromClassPaths(classpath_dex));
+ VerifyClassLoaderPCL(context.get(), 0, "");
+}
+
+TEST_F(ClassLoaderContextTest, EncodeInOatFile) {
+ std::string dex1_name = GetTestDexFileName("Main");
+ std::string dex2_name = GetTestDexFileName("MyClass");
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("PCL[" + dex1_name + ":" + dex2_name + "]");
+ ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, ""));
+
+ std::vector<std::unique_ptr<const DexFile>> dex1 = OpenTestDexFiles("Main");
+ std::vector<std::unique_ptr<const DexFile>> dex2 = OpenTestDexFiles("MyClass");
+ std::string encoding = context->EncodeContextForOatFile("");
+ std::string expected_encoding =
+ dex1[0]->GetLocation() + "*" + std::to_string(dex1[0]->GetLocationChecksum()) + "*" +
+ dex2[0]->GetLocation() + "*" + std::to_string(dex2[0]->GetLocationChecksum()) + "*";
+ ASSERT_EQ(expected_encoding, context->EncodeContextForOatFile(""));
+}
+
+} // namespace art