Allow dex2oat to create a full class loader context
We previously checked that dex2oat sees only a single class loader. This
CL removes the restriction and enables dex2oat to create and compile with
a full class loader context.
Test: m test-art-host
Bug: 38138251
Change-Id: I03e75a75757995ce8ce3addf0bc0a708e18ac050
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 6257c7c..9d52ff0 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -411,16 +411,20 @@
UsageError(" ");
UsageError(" The chain is interpreted in the natural 'parent order', meaning that class");
UsageError(" loader 'i+1' will be the parent of class loader 'i'.");
- UsageError(" The compilation sources will be added to the classpath of the last class");
- UsageError(" loader. This allows the compiled dex files to be loaded at runtime in");
- UsageError(" a class loader that contains other dex files as well (e.g. shared libraries).");
+ UsageError(" The compilation sources will be appended to the classpath of the first class");
+ UsageError(" loader.");
+ UsageError(" ");
+ UsageError(" E.g. if the context is 'PCL[lib1.dex];DLC[lib2.dex]' and ");
+ UsageError(" --dex-file=src.dex then dex2oat will setup a PathClassLoader with classpath ");
+ UsageError(" 'lib1.dex:src.dex' and set its parent to a DelegateLastClassLoader with ");
+ UsageError(" classpath 'lib2.dex'.");
UsageError(" ");
UsageError(" Note that the compiler will be tolerant if the source dex files specified");
UsageError(" with --dex-file are found in the classpath. The source dex files will be");
UsageError(" removed from any class loader's classpath possibly resulting in empty");
UsageError(" class loaders.");
UsageError(" ");
- UsageError(" Example: --classloader-spec=PCL[lib1.dex:lib2.dex];DLC[lib3.dex]");
+ UsageError(" Example: --class-loader-context=PCL[lib1.dex:lib2.dex];DLC[lib3.dex]");
UsageError("");
std::cerr << "See log for usage error information\n";
exit(EXIT_FAILURE);
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index b08b055..3538215 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -1023,4 +1023,16 @@
RunTest(context.c_str(), kEmptyClassPathKey, /*expected_success*/ true);
}
+TEST_F(Dex2oatClassLoaderContextTest, ChainContext) {
+ std::vector<std::unique_ptr<const DexFile>> dex_files1 = OpenTestDexFiles("Nested");
+ std::vector<std::unique_ptr<const DexFile>> dex_files2 = OpenTestDexFiles("MultiDex");
+
+ std::string context = "PCL[" + GetTestDexFileName("Nested") + "];" +
+ "DLC[" + GetTestDexFileName("MultiDex") + "]";
+ std::string expected_classpath_key = "PCL[" + CreateClassPathWithChecksums(dex_files1) + "];" +
+ "DLC[" + CreateClassPathWithChecksums(dex_files2) + "]";
+
+ RunTest(context.c_str(), expected_classpath_key.c_str(), true);
+}
+
} // namespace art
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 678ae8f..90346f0 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -305,24 +305,41 @@
Thread* self = Thread::Current();
ScopedObjectAccess soa(self);
- std::vector<const DexFile*> class_path_files;
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
- // TODO(calin): Transition period: assume we only have a classloader until
- // the oat file assistant implements the full class loader check.
- if (!class_loader_chain_.empty()) {
- CHECK_EQ(1u, class_loader_chain_.size());
- CHECK_EQ(kPathClassLoader, class_loader_chain_[0].type);
- class_path_files = MakeNonOwningPointerVector(class_loader_chain_[0].opened_dex_files);
+ if (class_loader_chain_.empty()) {
+ return class_linker->CreatePathClassLoader(self, compilation_sources);
}
- // Classpath: first the class-path given; then the dex files we'll compile.
- // Thus we'll resolve the class-path first.
- class_path_files.insert(class_path_files.end(),
- compilation_sources.begin(),
- compilation_sources.end());
+ // Create the class loaders starting from the top most parent (the one on the last position
+ // in the chain) but omit the first class loader which will contain the compilation_sources and
+ // needs special handling.
+ jobject current_parent = nullptr; // the starting parent is the BootClassLoader.
+ for (size_t i = class_loader_chain_.size() - 1; i > 0; i--) {
+ std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(
+ class_loader_chain_[i].opened_dex_files);
+ current_parent = class_linker->CreateWellKnownClassLoader(
+ self,
+ class_path_files,
+ GetClassLoaderClass(class_loader_chain_[i].type),
+ current_parent);
+ }
- ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
- return class_linker->CreatePathClassLoader(self, class_path_files);
+ // We set up all the parents. Move on to create the first class loader.
+ // Its classpath comes first, followed by compilation sources. This ensures that whenever
+ // we need to resolve classes from it the classpath elements come first.
+
+ std::vector<const DexFile*> first_class_loader_classpath = MakeNonOwningPointerVector(
+ class_loader_chain_[0].opened_dex_files);
+ first_class_loader_classpath.insert(first_class_loader_classpath.end(),
+ compilation_sources.begin(),
+ compilation_sources.end());
+
+ return class_linker->CreateWellKnownClassLoader(
+ self,
+ first_class_loader_classpath,
+ GetClassLoaderClass(class_loader_chain_[0].type),
+ current_parent);
}
std::vector<const DexFile*> ClassLoaderContext::FlattenOpenedDexFiles() const {
@@ -623,5 +640,15 @@
return true;
}
+jclass ClassLoaderContext::GetClassLoaderClass(ClassLoaderType type) {
+ switch (type) {
+ case kPathClassLoader: return WellKnownClasses::dalvik_system_PathClassLoader;
+ case kDelegateLastClassLoader: return WellKnownClasses::dalvik_system_DelegateLastClassLoader;
+ case kInvalidClassLoader: break; // will fail after the switch.
+ }
+ LOG(FATAL) << "Invalid class loader type " << type;
+ UNREACHABLE();
+}
+
} // namespace art
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index 8c65e2e..37dd02b 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -60,11 +60,22 @@
bool RemoveLocationsFromClassPaths(const dchecked_vector<std::string>& compilation_sources);
// Creates the entire class loader hierarchy according to the current context.
- // The compilation sources are appended to the classpath of the top class loader
- // (i.e the class loader whose parent is the BootClassLoader).
- // Should only be called if OpenDexFiles() returned true.
+ // Returns the first class loader from the chain.
+ //
+ // For example: if the context was built from the spec
+ // "ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]..."
+ // the method returns the class loader correponding to ClassLoader1. The parent chain will be
+ // ClassLoader1 --> ClassLoader2 --> ... --> BootClassLoader.
+ //
+ // The compilation sources are appended to the classpath of the first class loader (in the above
+ // example ClassLoader1).
+ //
// If the context is empty, this method only creates a single PathClassLoader with the
// given compilation_sources.
+ //
+ // Notes:
+ // 1) the objects are not completely set up. Do not use this outside of tests and the compiler.
+ // 2) should only be called before the first call to OpenDexFiles().
jobject CreateClassLoader(const std::vector<const DexFile*>& compilation_sources) const;
// Encodes the context as a string suitable to be added in oat files.
@@ -89,6 +100,11 @@
// The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...
// ClassLoaderType is either "PCL" (PathClassLoader) or "DLC" (DelegateLastClassLoader).
// ClasspathElem is the path of dex/jar/apk file.
+ //
+ // The spec represents a class loader chain with the natural interpretation:
+ // ClassLoader1 has ClassLoader2 as parent which has ClassLoader3 as a parent and so on.
+ // The last class loader is assumed to have the BootClassLoader as a parent.
+ //
// Note that we allowed class loaders with an empty class path in order to support a custom
// class loader for the source dex files.
static std::unique_ptr<ClassLoaderContext> Create(const std::string& spec);
@@ -168,6 +184,9 @@
// The returned format can be used when parsing a context spec.
static const char* GetClassLoaderTypeName(ClassLoaderType type);
+ // Returns the WellKnownClass for the given class loader type.
+ static jclass GetClassLoaderClass(ClassLoaderType type);
+
// The class loader chain represented as a vector.
// The parent of class_loader_chain_[i] is class_loader_chain_[i++].
// The parent of the last element is assumed to be the boot class loader.
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index 659db4b..1b46f67 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -117,6 +117,24 @@
ASSERT_FALSE(context->special_shared_library_);
}
+ void VerifyClassLoaderDexFiles(ScopedObjectAccess& soa,
+ Handle<mirror::ClassLoader> class_loader,
+ jclass type,
+ std::vector<const DexFile*>& expected_dex_files)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ASSERT_TRUE(class_loader->GetClass() == soa.Decode<mirror::Class>(type));
+
+ std::vector<const DexFile*> class_loader_dex_files = GetDexFiles(soa, class_loader);
+ ASSERT_EQ(expected_dex_files.size(), class_loader_dex_files.size());
+
+ for (size_t i = 0; i < expected_dex_files.size(); i++) {
+ ASSERT_EQ(expected_dex_files[i]->GetLocation(),
+ class_loader_dex_files[i]->GetLocation());
+ ASSERT_EQ(expected_dex_files[i]->GetLocationChecksum(),
+ class_loader_dex_files[i]->GetLocationChecksum());
+ }
+ }
+
private:
void VerifyClassLoaderInfo(ClassLoaderContext* context,
size_t index,
@@ -246,7 +264,7 @@
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<2> hs(soa.Self());
+ StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader = hs.NewHandle(
soa.Decode<mirror::ClassLoader>(jclass_loader));
@@ -255,25 +273,17 @@
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());
+ // For the first class loader the class path dex files must come first and then the
+ // compilation sources.
+ std::vector<const DexFile*> expected_classpath = MakeNonOwningPointerVector(classpath_dex);
+ for (auto& dex : compilation_sources_raw) {
+ expected_classpath.push_back(dex);
}
- // 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());
- }
+ VerifyClassLoaderDexFiles(soa,
+ class_loader,
+ WellKnownClasses::dalvik_system_PathClassLoader,
+ expected_classpath);
}
TEST_F(ClassLoaderContextTest, CreateClassLoaderWithEmptyContext) {
@@ -290,28 +300,90 @@
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<2> hs(soa.Self());
+ StackHandleScope<1> 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));
+ // An empty context should create a single PathClassLoader with only the compilation sources.
+ VerifyClassLoaderDexFiles(soa,
+ class_loader,
+ WellKnownClasses::dalvik_system_PathClassLoader,
+ compilation_sources_raw);
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);
-
- // The compilation sources should be the only files present in the class loader
- ASSERT_EQ(compilation_sources.size(), class_loader_dex_files.size());
- for (size_t i = 0; i < compilation_sources.size(); i++) {
- ASSERT_EQ(compilation_sources[i]->GetLocation(),
- class_loader_dex_files[i]->GetLocation());
- ASSERT_EQ(compilation_sources[i]->GetLocationChecksum(),
- class_loader_dex_files[i]->GetLocationChecksum());
- }
}
+TEST_F(ClassLoaderContextTest, CreateClassLoaderWithComplexChain) {
+ // Setup the context.
+ std::vector<std::unique_ptr<const DexFile>> classpath_dex_a = OpenTestDexFiles("ForClassLoaderA");
+ std::vector<std::unique_ptr<const DexFile>> classpath_dex_b = OpenTestDexFiles("ForClassLoaderB");
+ std::vector<std::unique_ptr<const DexFile>> classpath_dex_c = OpenTestDexFiles("ForClassLoaderC");
+ std::vector<std::unique_ptr<const DexFile>> classpath_dex_d = OpenTestDexFiles("ForClassLoaderD");
+
+ std::string context_spec =
+ "PCL[" + CreateClassPath(classpath_dex_a) + ":" + CreateClassPath(classpath_dex_b) + "];" +
+ "DLC[" + CreateClassPath(classpath_dex_c) + "];" +
+ "PCL[" + CreateClassPath(classpath_dex_d) + "]";
+
+ std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_spec);
+ ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, ""));
+
+ // Setup the compilation sources.
+ std::vector<std::unique_ptr<const DexFile>> compilation_sources = OpenTestDexFiles("MultiDex");
+ std::vector<const DexFile*> compilation_sources_raw =
+ MakeNonOwningPointerVector(compilation_sources);
+
+ // Create the class loader.
+ jobject jclass_loader = context->CreateClassLoader(compilation_sources_raw);
+ ASSERT_TRUE(jclass_loader != nullptr);
+
+ // Verify the class loader.
+ ScopedObjectAccess soa(Thread::Current());
+
+ StackHandleScope<3> hs(soa.Self());
+ Handle<mirror::ClassLoader> class_loader_1 = hs.NewHandle(
+ soa.Decode<mirror::ClassLoader>(jclass_loader));
+
+ // Verify the first class loader
+
+ // For the first class loader the class path dex files must come first and then the
+ // compilation sources.
+ std::vector<const DexFile*> class_loader_1_dex_files =
+ MakeNonOwningPointerVector(classpath_dex_a);
+ for (auto& dex : classpath_dex_b) {
+ class_loader_1_dex_files.push_back(dex.get());
+ }
+ for (auto& dex : compilation_sources_raw) {
+ class_loader_1_dex_files.push_back(dex);
+ }
+ VerifyClassLoaderDexFiles(soa,
+ class_loader_1,
+ WellKnownClasses::dalvik_system_PathClassLoader,
+ class_loader_1_dex_files);
+
+ // Verify the second class loader
+ Handle<mirror::ClassLoader> class_loader_2 = hs.NewHandle(class_loader_1->GetParent());
+ std::vector<const DexFile*> class_loader_2_dex_files =
+ MakeNonOwningPointerVector(classpath_dex_c);
+ VerifyClassLoaderDexFiles(soa,
+ class_loader_2,
+ WellKnownClasses::dalvik_system_DelegateLastClassLoader,
+ class_loader_2_dex_files);
+
+ // Verify the third class loader
+ Handle<mirror::ClassLoader> class_loader_3 = hs.NewHandle(class_loader_2->GetParent());
+ std::vector<const DexFile*> class_loader_3_dex_files =
+ MakeNonOwningPointerVector(classpath_dex_d);
+ VerifyClassLoaderDexFiles(soa,
+ class_loader_3,
+ WellKnownClasses::dalvik_system_PathClassLoader,
+ class_loader_3_dex_files);
+ // The last class loader should have the BootClassLoader as a parent.
+ ASSERT_TRUE(class_loader_3->GetParent()->GetClass() ==
+ soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+}
+
+
TEST_F(ClassLoaderContextTest, RemoveSourceLocations) {
std::unique_ptr<ClassLoaderContext> context =
ClassLoaderContext::Create("PCL[a.dex]");
@@ -338,9 +410,8 @@
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 = "PCL[" +
- dex1[0]->GetLocation() + "*" + std::to_string(dex1[0]->GetLocationChecksum()) + ":" +
- dex2[0]->GetLocation() + "*" + std::to_string(dex2[0]->GetLocationChecksum()) + "]";
+ std::string expected_encoding = "PCL[" + CreateClassPathWithChecksums(dex1) + ":" +
+ CreateClassPathWithChecksums(dex2) + "]";
ASSERT_EQ(expected_encoding, context->EncodeContextForOatFile(""));
}
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 659c7e4..aae9973 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -589,18 +589,24 @@
}
std::vector<const DexFile*> CommonRuntimeTestImpl::GetDexFiles(jobject jclass_loader) {
- std::vector<const DexFile*> ret;
-
ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<2> hs(soa.Self());
+ StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader = hs.NewHandle(
soa.Decode<mirror::ClassLoader>(jclass_loader));
+ return GetDexFiles(soa, class_loader);
+}
- DCHECK_EQ(class_loader->GetClass(),
- soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader));
- DCHECK_EQ(class_loader->GetParent()->GetClass(),
- soa.Decode<mirror::Class>(WellKnownClasses::java_lang_BootClassLoader));
+std::vector<const DexFile*> CommonRuntimeTestImpl::GetDexFiles(
+ ScopedObjectAccess& soa,
+ Handle<mirror::ClassLoader> class_loader) {
+ std::vector<const DexFile*> ret;
+
+ DCHECK(
+ (class_loader->GetClass() ==
+ soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) ||
+ (class_loader->GetClass() ==
+ soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader)));
// The class loader is a PathClassLoader which inherits from BaseDexClassLoader.
// We need to get the DexPathList and loop through it.
@@ -618,6 +624,7 @@
// Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look
// at the mCookie which is a DexFile vector.
if (dex_elements_obj != nullptr) {
+ StackHandleScope<1> hs(soa.Self());
Handle<mirror::ObjectArray<mirror::Object>> dex_elements =
hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>());
for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
@@ -757,6 +764,28 @@
return location;
}
+std::string CommonRuntimeTestImpl::CreateClassPath(
+ const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
+ CHECK(!dex_files.empty());
+ std::string classpath = dex_files[0]->GetLocation();
+ for (size_t i = 1; i < dex_files.size(); i++) {
+ classpath += ":" + dex_files[i]->GetLocation();
+ }
+ return classpath;
+}
+
+std::string CommonRuntimeTestImpl::CreateClassPathWithChecksums(
+ const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
+ CHECK(!dex_files.empty());
+ std::string classpath = dex_files[0]->GetLocation() + "*" +
+ std::to_string(dex_files[0]->GetLocationChecksum());
+ for (size_t i = 1; i < dex_files.size(); i++) {
+ classpath += ":" + dex_files[i]->GetLocation() + "*" +
+ std::to_string(dex_files[i]->GetLocationChecksum());
+ }
+ return classpath;
+}
+
CheckJniAbortCatcher::CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) {
vm_->SetCheckJniAbortHook(Hook, &actual_);
}
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index 5893573..c494513 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -28,6 +28,7 @@
// TODO: Add inl file and avoid including inl.
#include "obj_ptr-inl.h"
#include "os.h"
+#include "scoped_thread_state_change-inl.h"
namespace art {
@@ -159,9 +160,12 @@
const DexFile* java_lang_dex_file_;
std::vector<const DexFile*> boot_class_path_;
- // Get the dex files from a PathClassLoader. This in order of the dex elements and their dex
- // arrays.
+ // Get the dex files from a PathClassLoader or DelegateLastClassLoader.
+ // This only looks into the current class loader and does not recurse into the parents.
std::vector<const DexFile*> GetDexFiles(jobject jclass_loader);
+ std::vector<const DexFile*> GetDexFiles(ScopedObjectAccess& soa,
+ Handle<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Get the first dex file from a PathClassLoader. Will abort if it is null.
const DexFile* GetFirstDexFile(jobject jclass_loader);
@@ -176,6 +180,15 @@
// initializers, initialize well-known classes, and creates the heap thread pool.
virtual void FinalizeSetup();
+ // Creates the class path string for the given dex files (the list of dex file locations
+ // separated by ':').
+ std::string CreateClassPath(
+ const std::vector<std::unique_ptr<const DexFile>>& dex_files);
+ // Same as CreateClassPath but add the dex file checksum after each location. The separator
+ // is '*'.
+ std::string CreateClassPathWithChecksums(
+ const std::vector<std::unique_ptr<const DexFile>>& dex_files);
+
private:
static std::string GetCoreFileLocation(const char* suffix);