diff options
| -rw-r--r-- | build/Android.gtest.mk | 1 | ||||
| -rw-r--r-- | runtime/Android.bp | 2 | ||||
| -rw-r--r-- | runtime/class_loader_context.cc | 285 | ||||
| -rw-r--r-- | runtime/class_loader_context.h | 150 | ||||
| -rw-r--r-- | runtime/class_loader_context_test.cc | 265 | ||||
| -rw-r--r-- | runtime/oat_file.cc | 2 | ||||
| -rw-r--r-- | runtime/oat_file.h | 2 | ||||
| -rw-r--r-- | runtime/oat_file_assistant.cc | 30 | ||||
| -rw-r--r-- | runtime/oat_file_assistant.h | 7 |
9 files changed, 732 insertions, 12 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index b6ffcc54f7..ff4f9016c0 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -104,6 +104,7 @@ ART_GTEST_dex2oat_environment_tests_DEX_DEPS := Main MainStripped MultiDex Multi ART_GTEST_atomic_method_ref_map_test_DEX_DEPS := Interfaces ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit ForClassLoaderA ForClassLoaderB ForClassLoaderC ForClassLoaderD Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode +ART_GTEST_class_loader_context_test_DEX_DEPS := Main MultiDex MyClass ART_GTEST_class_table_test_DEX_DEPS := XandY ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes diff --git a/runtime/Android.bp b/runtime/Android.bp index 0dfc60d88a..8d15c349dd 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -48,6 +48,7 @@ cc_defaults { "cha.cc", "check_jni.cc", "class_linker.cc", + "class_loader_context.cc", "class_table.cc", "code_simulator_container.cc", "common_throws.cc", @@ -542,6 +543,7 @@ art_cc_test { "base/unix_file/fd_file_test.cc", "cha_test.cc", "class_linker_test.cc", + "class_loader_context_test.cc", "class_table_test.cc", "compiler_filter_test.cc", "dex_file_test.cc", diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc new file mode 100644 index 0000000000..5cbcd8f1ff --- /dev/null +++ b/runtime/class_loader_context.cc @@ -0,0 +1,285 @@ +/* + * 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_context.h" + +#include "base/dchecked_vector.h" +#include "base/stl_util.h" +#include "class_linker.h" +#include "dex_file.h" +#include "oat_file_assistant.h" +#include "runtime.h" +#include "scoped_thread_state_change-inl.h" +#include "thread.h" + +namespace art { + +static constexpr char kPathClassLoaderString[] = "PCL"; +static constexpr char kDelegateLastClassLoaderString[] = "DLC"; +static constexpr char kClassLoaderOpeningMark = '['; +static constexpr char kClassLoaderClosingMark = ']'; +static constexpr char kClassLoaderSep = ';'; +static constexpr char kClasspathSep = ':'; + +ClassLoaderContext::ClassLoaderContext() + : special_shared_library_(false), + dex_files_open_attempted_(false), + dex_files_open_result_(false) {} + +std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Create(const std::string& spec) { + std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext()); + if (result->Parse(spec)) { + return result; + } else { + return nullptr; + } +} + +// The expected format is: "ClassLoaderType1[ClasspathElem1:ClasspathElem2...]". +bool ClassLoaderContext::ParseClassLoaderSpec(const std::string& class_loader_spec, + ClassLoaderType class_loader_type) { + const char* class_loader_type_str = GetClassLoaderTypeName(class_loader_type); + size_t type_str_size = strlen(class_loader_type_str); + + CHECK_EQ(0, class_loader_spec.compare(0, type_str_size, class_loader_type_str)); + + // Check the opening and closing markers. + if (class_loader_spec[type_str_size] != kClassLoaderOpeningMark) { + return false; + } + if (class_loader_spec[class_loader_spec.length() - 1] != kClassLoaderClosingMark) { + return false; + } + + // At this point we know the format is ok; continue and extract the classpath. + // Note that class loaders with an empty class path are allowed. + std::string classpath = class_loader_spec.substr(type_str_size + 1, + class_loader_spec.length() - type_str_size - 2); + + class_loader_chain_.push_back(ClassLoaderInfo(class_loader_type)); + Split(classpath, kClasspathSep, &class_loader_chain_.back().classpath); + + return true; +} + +// Extracts the class loader type from the given spec. +// Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not +// recognized. +ClassLoaderContext::ClassLoaderType +ClassLoaderContext::ExtractClassLoaderType(const std::string& class_loader_spec) { + const ClassLoaderType kValidTypes[] = {kPathClassLoader, kDelegateLastClassLoader}; + for (const ClassLoaderType& type : kValidTypes) { + const char* type_str = GetClassLoaderTypeName(type); + if (class_loader_spec.compare(0, strlen(type_str), type_str) == 0) { + return type; + } + } + return kInvalidClassLoader; +} + +// The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]... +// ClassLoaderType is either "PCL" (PathClassLoader) or "DLC" (DelegateLastClassLoader). +// ClasspathElem is the path of dex/jar/apk file. +bool ClassLoaderContext::Parse(const std::string& spec) { + if (spec.empty()) { + LOG(ERROR) << "Empty string passed to Parse"; + return false; + } + // Stop early if we detect the special shared library, which may be passed as the classpath + // for dex2oat when we want to skip the shared libraries check. + if (spec == OatFile::kSpecialSharedLibrary) { + LOG(INFO) << "The ClassLoaderContext is a special shared library."; + special_shared_library_ = true; + return true; + } + + std::vector<std::string> class_loaders; + Split(spec, kClassLoaderSep, &class_loaders); + + for (const std::string& class_loader : class_loaders) { + ClassLoaderType type = ExtractClassLoaderType(class_loader); + if (type == kInvalidClassLoader) { + LOG(ERROR) << "Invalid class loader type: " << class_loader; + return false; + } + if (!ParseClassLoaderSpec(class_loader, type)) { + LOG(ERROR) << "Invalid class loader spec: " << class_loader; + return false; + } + } + return true; +} + +// Opens requested class path files and appends them to opened_dex_files. If the dex files have +// been stripped, this opens them from their oat files (which get added to opened_oat_files). +bool ClassLoaderContext::OpenDexFiles(InstructionSet isa, const std::string& classpath_dir) { + CHECK(!dex_files_open_attempted_) << "OpenDexFiles should not be called twice"; + + dex_files_open_attempted_ = true; + // Assume we can open all dex files. If not, we will set this to false as we go. + dex_files_open_result_ = true; + + if (special_shared_library_) { + // Nothing to open if the context is a special shared library. + return true; + } + + // Note that we try to open all dex files even if some fail. + // We may get resource-only apks which we cannot load. + // TODO(calin): Refine the dex opening interface to be able to tell if an archive contains + // no dex files. So that we can distinguish the real failures... + for (ClassLoaderInfo& info : class_loader_chain_) { + for (const std::string& cp_elem : info.classpath) { + // If path is relative, append it to the provided base directory. + std::string location = cp_elem; + if (location[0] != '/') { + location = classpath_dir + '/' + location; + } + std::string error_msg; + // When opening the dex files from the context we expect their checksum to match their + // contents. So pass true to verify_checksum. + if (!DexFile::Open(location.c_str(), + location.c_str(), + /*verify_checksum*/ true, + &error_msg, + &info.opened_dex_files)) { + // If we fail to open the dex file because it's been stripped, try to open the dex file + // from its corresponding oat file. + // This could happen when we need to recompile a pre-build whose dex code has been stripped. + // (for example, if the pre-build is only quicken and we want to re-compile it + // speed-profile). + // TODO(calin): Use the vdex directly instead of going through the oat file. + OatFileAssistant oat_file_assistant(location.c_str(), isa, false); + std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile()); + std::vector<std::unique_ptr<const DexFile>> oat_dex_files; + if (oat_file != nullptr && + OatFileAssistant::LoadDexFiles(*oat_file, location, &oat_dex_files)) { + info.opened_oat_files.push_back(std::move(oat_file)); + info.opened_dex_files.insert(info.opened_dex_files.end(), + std::make_move_iterator(oat_dex_files.begin()), + std::make_move_iterator(oat_dex_files.end())); + } else { + LOG(WARNING) << "Could not open dex files from location: " << location; + dex_files_open_result_ = false; + } + } + } + } + + return dex_files_open_result_; +} + +bool ClassLoaderContext::RemoveLocationsFromClassPaths( + const dchecked_vector<std::string>& locations) { + CHECK(!dex_files_open_attempted_) + << "RemoveLocationsFromClasspaths cannot be call after OpenDexFiles"; + + std::set<std::string> canonical_locations; + for (const std::string& location : locations) { + canonical_locations.insert(DexFile::GetDexCanonicalLocation(location.c_str())); + } + bool removed_locations = false; + for (ClassLoaderInfo& info : class_loader_chain_) { + size_t initial_size = info.classpath.size(); + auto kept_it = std::remove_if( + info.classpath.begin(), + info.classpath.end(), + [canonical_locations](const std::string& location) { + return ContainsElement(canonical_locations, + DexFile::GetDexCanonicalLocation(location.c_str())); + }); + info.classpath.erase(kept_it, info.classpath.end()); + if (initial_size != info.classpath.size()) { + removed_locations = true; + } + } + return removed_locations; +} + +std::string ClassLoaderContext::EncodeContextForOatFile(const std::string& base_dir) const { + CheckDexFilesOpened("EncodeContextForOatFile"); + if (special_shared_library_) { + return OatFile::kSpecialSharedLibrary; + } + + if (class_loader_chain_.empty()) { + return ""; + } + + // TODO(calin): Transition period: assume we only have a classloader until + // the oat file assistant implements the full class loader check. + CHECK_EQ(1u, class_loader_chain_.size()); + + return OatFile::EncodeDexFileDependencies(MakeNonOwningPointerVector( + class_loader_chain_[0].opened_dex_files), base_dir); +} + +jobject ClassLoaderContext::CreateClassLoader( + const std::vector<const DexFile*>& compilation_sources) const { + CheckDexFilesOpened("CreateClassLoader"); + + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + std::vector<const DexFile*> class_path_files; + + // 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); + } + + // 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()); + + ClassLinker* const class_linker = Runtime::Current()->GetClassLinker(); + return class_linker->CreatePathClassLoader(self, class_path_files); +} + +std::vector<const DexFile*> ClassLoaderContext::FlattenOpenedDexFiles() const { + CheckDexFilesOpened("FlattenOpenedDexFiles"); + + std::vector<const DexFile*> result; + for (const ClassLoaderInfo& info : class_loader_chain_) { + for (const std::unique_ptr<const DexFile>& dex_file : info.opened_dex_files) { + result.push_back(dex_file.get()); + } + } + return result; +} + +const char* ClassLoaderContext::GetClassLoaderTypeName(ClassLoaderType type) { + switch (type) { + case kPathClassLoader: return kPathClassLoaderString; + case kDelegateLastClassLoader: return kDelegateLastClassLoaderString; + default: + LOG(FATAL) << "Invalid class loader type " << type; + UNREACHABLE(); + } +} + +void ClassLoaderContext::CheckDexFilesOpened(const std::string& calling_method) const { + CHECK(dex_files_open_attempted_) + << "Dex files were not successfully opened before the call to " << calling_method + << "attempt=" << dex_files_open_attempted_ << ", result=" << dex_files_open_result_; +} +} // namespace art + diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h new file mode 100644 index 0000000000..4af5017ef2 --- /dev/null +++ b/runtime/class_loader_context.h @@ -0,0 +1,150 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_CLASS_LOADER_CONTEXT_H_ +#define ART_RUNTIME_CLASS_LOADER_CONTEXT_H_ + +#include <string> +#include <vector> + +#include "arch/instruction_set.h" +#include "base/dchecked_vector.h" +#include "jni.h" + +namespace art { + +class DexFile; +class OatFile; + +// Utility class which holds the class loader context used during compilation/verification. +class ClassLoaderContext { + public: + // Creates an empty context (with no class loaders). + ClassLoaderContext(); + + // Opens requested class path files and appends them to ClassLoaderInfo::opened_dex_files. + // If the dex files have been stripped, the method opens them from their oat files which are added + // to ClassLoaderInfo::opened_oat_files. The 'classpath_dir' argument specifies the directory to + // use for the relative class paths. + // Returns true if all dex files where successfully opened. + // It may be called only once per ClassLoaderContext. The second call will abort. + // + // Note that a "false" return could mean that either an apk/jar contained no dex files or + // that we hit a I/O or checksum mismatch error. + // TODO(calin): Currently there's no easy way to tell the difference. + // + // TODO(calin): we're forced to complicate the flow in this class with a different + // OpenDexFiles step because the current dex2oat flow requires the dex files be opened before + // the class loader is created. Consider reworking the dex2oat part. + bool OpenDexFiles(InstructionSet isa, const std::string& classpath_dir); + + // Remove the specified compilation sources from all classpaths present in this context. + // Should only be called before the first call to OpenDexFiles(). + 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. + jobject CreateClassLoader(const std::vector<const DexFile*>& compilation_sources) const; + + // Encodes the context as a string suitable to be added in oat files. + // (so that it can be read and verified at runtime against the actual class + // loader hierarchy). + // Should only be called if OpenDexFiles() returned true. + // E.g. if the context is PCL[a.dex:b.dex] this will return "a.dex*a_checksum*b.dex*a_checksum". + std::string EncodeContextForOatFile(const std::string& base_dir) const; + + // Flattens the opened dex files into the given vector. + // Should only be called if OpenDexFiles() returned true. + std::vector<const DexFile*> FlattenOpenedDexFiles() const; + + // Creates the class loader context from the given string. + // The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]... + // ClassLoaderType is either "PCL" (PathClassLoader) or "DLC" (DelegateLastClassLoader). + // ClasspathElem is the path of dex/jar/apk file. + // 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); + + private: + enum ClassLoaderType { + kInvalidClassLoader = 0, + kPathClassLoader = 1, + kDelegateLastClassLoader = 2 + }; + + struct ClassLoaderInfo { + // The type of this class loader. + ClassLoaderType type; + // The list of class path elements that this loader loads. + // Note that this list may contain relative paths. + std::vector<std::string> classpath; + // After OpenDexFiles is called this holds the opened dex files. + std::vector<std::unique_ptr<const DexFile>> opened_dex_files; + // After OpenDexFiles, in case some of the dex files were opened from their oat files + // this holds the list of opened oat files. + std::vector<std::unique_ptr<OatFile>> opened_oat_files; + + explicit ClassLoaderInfo(ClassLoaderType cl_type) : type(cl_type) {} + }; + + // Reads the class loader spec in place and returns true if the spec is valid and the + // compilation context was constructed. + bool Parse(const std::string& spec); + + // Attempts to parse a single class loader spec for the given class_loader_type. + // If successful the class loader spec will be added to the chain. + // Returns whether or not the operation was successful. + bool ParseClassLoaderSpec(const std::string& class_loader_spec, + ClassLoaderType class_loader_type); + + // Extracts the class loader type from the given spec. + // Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not + // recognized. + static ClassLoaderType ExtractClassLoaderType(const std::string& class_loader_spec); + + // Returns the string representation of the class loader type. + // The returned format can be used when parsing a context spec. + static const char* GetClassLoaderTypeName(ClassLoaderType type); + + // CHECKs that the dex files were opened (OpenDexFiles was called). Aborts if not. + void CheckDexFilesOpened(const std::string& calling_method) const; + + // 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. + std::vector<ClassLoaderInfo> class_loader_chain_; + + // Whether or not the class loader context should be ignored at runtime when loading the oat + // files. When true, dex2oat will use OatFile::kSpecialSharedLibrary as the classpath key in + // the oat file. + // TODO(calin): Can we get rid of this and cover all relevant use cases? + // (e.g. packages using prebuild system packages as shared libraries b/36480683) + bool special_shared_library_; + + // Whether or not OpenDexFiles() was called. + bool dex_files_open_attempted_; + // The result of the last OpenDexFiles() operation. + bool dex_files_open_result_; + + friend class ClassLoaderContextTest; + + DISALLOW_COPY_AND_ASSIGN(ClassLoaderContext); +}; + +} // namespace art +#endif // ART_RUNTIME_CLASS_LOADER_CONTEXT_H_ diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc new file mode 100644 index 0000000000..4643e78226 --- /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 diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 888de457dc..2ed30df372 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -1577,7 +1577,7 @@ CompilerFilter::Filter OatFile::GetCompilerFilter() const { static constexpr char kDexClassPathEncodingSeparator = '*'; std::string OatFile::EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files, - std::string& base_dir) { + const std::string& base_dir) { std::ostringstream out; for (const DexFile* dex_file : dex_files) { diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 66ed44f1b9..6393e091db 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -299,7 +299,7 @@ class OatFile { // Create a dependency list (dex locations and checksums) for the given dex files. // Removes dex file paths prefixed with base_dir to convert them back to relative paths. static std::string EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files, - std::string& base_dir); + const std::string& base_dir); // Finds the associated oat class for a dex_file and descriptor. Returns an invalid OatClass on // error and sets found to false. diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 4820feb56c..c8766578c4 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -298,28 +298,38 @@ std::string OatFileAssistant::GetStatusDump() { } std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles( - const OatFile& oat_file, const char* dex_location) { + const OatFile &oat_file, const char *dex_location) { std::vector<std::unique_ptr<const DexFile>> dex_files; + if (LoadDexFiles(oat_file, dex_location, &dex_files)) { + return dex_files; + } else { + return std::vector<std::unique_ptr<const DexFile>>(); + } +} +bool OatFileAssistant::LoadDexFiles( + const OatFile &oat_file, + const std::string& dex_location, + std::vector<std::unique_ptr<const DexFile>>* out_dex_files) { // Load the main dex file. std::string error_msg; const OatFile::OatDexFile* oat_dex_file = oat_file.GetOatDexFile( - dex_location, nullptr, &error_msg); + dex_location.c_str(), nullptr, &error_msg); if (oat_dex_file == nullptr) { LOG(WARNING) << error_msg; - return std::vector<std::unique_ptr<const DexFile>>(); + return false; } std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg); if (dex_file.get() == nullptr) { LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg; - return std::vector<std::unique_ptr<const DexFile>>(); + return false; } - dex_files.push_back(std::move(dex_file)); + out_dex_files->push_back(std::move(dex_file)); // Load the rest of the multidex entries - for (size_t i = 1; ; i++) { - std::string multidex_dex_location = DexFile::GetMultiDexLocation(i, dex_location); + for (size_t i = 1;; i++) { + std::string multidex_dex_location = DexFile::GetMultiDexLocation(i, dex_location.c_str()); oat_dex_file = oat_file.GetOatDexFile(multidex_dex_location.c_str(), nullptr); if (oat_dex_file == nullptr) { // There are no more multidex entries to load. @@ -329,11 +339,11 @@ std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles( dex_file = oat_dex_file->OpenDexFile(&error_msg); if (dex_file.get() == nullptr) { LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg; - return std::vector<std::unique_ptr<const DexFile>>(); + return false; } - dex_files.push_back(std::move(dex_file)); + out_dex_files->push_back(std::move(dex_file)); } - return dex_files; + return true; } bool OatFileAssistant::HasOriginalDexFiles() { diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index 03d9ca38a8..92d87eaeae 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -207,6 +207,13 @@ class OatFileAssistant { static std::vector<std::unique_ptr<const DexFile>> LoadDexFiles( const OatFile& oat_file, const char* dex_location); + // Same as `std::vector<std::unique_ptr<const DexFile>> LoadDexFiles(...)` with the difference: + // - puts the dex files in the given vector + // - returns whether or not all dex files were successfully opened + static bool LoadDexFiles(const OatFile& oat_file, + const std::string& dex_location, + std::vector<std::unique_ptr<const DexFile>>* out_dex_files); + // Returns true if there are dex files in the original dex location that can // be compiled with dex2oat for this dex location. // Returns false if there is no original dex file, or if the original dex |