Merge "art: use proper nativehelper headers"
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index cf6d1ec..571c91a 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -104,7 +104,7 @@
ART_GTEST_atomic_dex_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_loader_context_test_DEX_DEPS := Main MultiDex MyClass ForClassLoaderA ForClassLoaderB ForClassLoaderC ForClassLoaderD
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/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index b3de3c9..b5d3736 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -414,16 +414,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 ed1aee6..68ec0b5 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -1102,4 +1102,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_linker.cc b/runtime/class_linker.cc
index 1abff59..a9237ef3 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -45,6 +45,7 @@
#include "base/value_object.h"
#include "cha.h"
#include "class_linker-inl.h"
+#include "class_loader_utils.h"
#include "class_table-inl.h"
#include "compiler_callbacks.h"
#include "debugger.h"
@@ -2481,27 +2482,6 @@
return ClassPathEntry(nullptr, nullptr);
}
-// Returns true if the given class loader is either a PathClassLoader or a DexClassLoader.
-// (they both have the same behaviour with respect to class lockup order)
-static bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
- Handle<mirror::ClassLoader> class_loader)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- mirror::Class* class_loader_class = class_loader->GetClass();
- return
- (class_loader_class ==
- soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) ||
- (class_loader_class ==
- soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader));
-}
-
-static bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
- Handle<mirror::ClassLoader> class_loader)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- mirror::Class* class_loader_class = class_loader->GetClass();
- return class_loader_class ==
- soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader);
-}
-
bool ClassLinker::FindClassInBaseDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
Thread* self,
const char* descriptor,
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 2bed1d5..eab3b86 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -16,14 +16,20 @@
#include "class_loader_context.h"
+#include "art_field-inl.h"
#include "base/dchecked_vector.h"
#include "base/stl_util.h"
#include "class_linker.h"
+#include "class_loader_utils.h"
#include "dex_file.h"
+#include "handle_scope-inl.h"
+#include "jni_internal.h"
#include "oat_file_assistant.h"
+#include "obj_ptr-inl.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
#include "thread.h"
+#include "well_known_classes.h"
namespace art {
@@ -38,7 +44,29 @@
ClassLoaderContext::ClassLoaderContext()
: special_shared_library_(false),
dex_files_open_attempted_(false),
- dex_files_open_result_(false) {}
+ dex_files_open_result_(false),
+ owns_the_dex_files_(true) {}
+
+ClassLoaderContext::ClassLoaderContext(bool owns_the_dex_files)
+ : special_shared_library_(false),
+ dex_files_open_attempted_(true),
+ dex_files_open_result_(true),
+ owns_the_dex_files_(owns_the_dex_files) {}
+
+ClassLoaderContext::~ClassLoaderContext() {
+ if (!owns_the_dex_files_) {
+ // If the context does not own the dex/oat files release the unique pointers to
+ // make sure we do not de-allocate them.
+ for (ClassLoaderInfo& info : class_loader_chain_) {
+ for (std::unique_ptr<OatFile>& oat_file : info.opened_oat_files) {
+ oat_file.release();
+ }
+ for (std::unique_ptr<const DexFile>& dex_file : info.opened_dex_files) {
+ dex_file.release();
+ }
+ }
+ }
+}
std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Create(const std::string& spec) {
std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext());
@@ -277,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 {
@@ -325,36 +370,285 @@
<< "attempt=" << dex_files_open_attempted_ << ", result=" << dex_files_open_result_;
}
-bool ClassLoaderContext::DecodePathClassLoaderContextFromOatFileKey(
- const std::string& context_spec,
- std::vector<std::string>* out_classpath,
- std::vector<uint32_t>* out_checksums,
- bool* out_is_special_shared_library) {
- ClassLoaderContext context;
- if (!context.Parse(context_spec, /*parse_checksums*/ true)) {
- LOG(ERROR) << "Invalid class loader context: " << context_spec;
+// Collects the dex files from the give Java dex_file object. Only the dex files with
+// at least 1 class are collected. If a null java_dex_file is passed this method does nothing.
+static bool CollectDexFilesFromJavaDexFile(ObjPtr<mirror::Object> java_dex_file,
+ ArtField* const cookie_field,
+ std::vector<const DexFile*>* out_dex_files)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (java_dex_file == nullptr) {
+ return true;
+ }
+ // On the Java side, the dex files are stored in the cookie field.
+ mirror::LongArray* long_array = cookie_field->GetObject(java_dex_file)->AsLongArray();
+ if (long_array == nullptr) {
+ // This should never happen so log a warning.
+ LOG(ERROR) << "Unexpected null cookie";
+ return false;
+ }
+ int32_t long_array_size = long_array->GetLength();
+ // Index 0 from the long array stores the oat file. The dex files start at index 1.
+ for (int32_t j = 1; j < long_array_size; ++j) {
+ const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>(
+ long_array->GetWithoutChecks(j)));
+ if (cp_dex_file != nullptr && cp_dex_file->NumClassDefs() > 0) {
+ // TODO(calin): It's unclear why the dex files with no classes are skipped here and when
+ // cp_dex_file can be null.
+ out_dex_files->push_back(cp_dex_file);
+ }
+ }
+ return true;
+}
+
+// Collects all the dex files loaded by the given class loader.
+// Returns true for success or false if an unexpected state is discovered (e.g. a null dex cookie,
+// a null list of dex elements or a null dex element).
+static bool CollectDexFilesFromSupportedClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+ Handle<mirror::ClassLoader> class_loader,
+ std::vector<const DexFile*>* out_dex_files)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ CHECK(IsPathOrDexClassLoader(soa, class_loader) || IsDelegateLastClassLoader(soa, class_loader));
+
+ // All supported class loaders inherit from BaseDexClassLoader.
+ // We need to get the DexPathList and loop through it.
+ ArtField* const cookie_field =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
+ ArtField* const dex_file_field =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+ ObjPtr<mirror::Object> dex_path_list =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)->
+ GetObject(class_loader.Get());
+ CHECK(cookie_field != nullptr);
+ CHECK(dex_file_field != nullptr);
+ if (dex_path_list == nullptr) {
+ // This may be null if the current class loader is under construction and it does not
+ // have its fields setup yet.
+ return true;
+ }
+ // DexPathList has an array dexElements of Elements[] which each contain a dex file.
+ ObjPtr<mirror::Object> dex_elements_obj =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
+ GetObject(dex_path_list);
+ // 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) {
+ // TODO(calin): It's unclear if we should just assert here. For now be prepared for the worse
+ // and assume we have no elements.
+ return true;
+ } else {
+ 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) {
+ mirror::Object* element = dex_elements->GetWithoutChecks(i);
+ if (element == nullptr) {
+ // Should never happen, log an error and break.
+ // TODO(calin): It's unclear if we should just assert here.
+ // This code was propagated to oat_file_manager from the class linker where it would
+ // throw a NPE. For now, return false which will mark this class loader as unsupported.
+ LOG(ERROR) << "Unexpected null in the dex element list";
+ return false;
+ }
+ ObjPtr<mirror::Object> dex_file = dex_file_field->GetObject(element);
+ if (!CollectDexFilesFromJavaDexFile(dex_file, cookie_field, out_dex_files)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool GetDexFilesFromDexElementsArray(
+ ScopedObjectAccessAlreadyRunnable& soa,
+ Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
+ std::vector<const DexFile*>* out_dex_files) REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(dex_elements != nullptr);
+
+ ArtField* const cookie_field =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
+ ArtField* const dex_file_field =
+ jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+ ObjPtr<mirror::Class> const element_class = soa.Decode<mirror::Class>(
+ WellKnownClasses::dalvik_system_DexPathList__Element);
+ ObjPtr<mirror::Class> const dexfile_class = soa.Decode<mirror::Class>(
+ WellKnownClasses::dalvik_system_DexFile);
+
+ for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
+ mirror::Object* element = dex_elements->GetWithoutChecks(i);
+ // We can hit a null element here because this is invoked with a partially filled dex_elements
+ // array from DexPathList. DexPathList will open each dex sequentially, each time passing the
+ // list of dex files which were opened before.
+ if (element == nullptr) {
+ continue;
+ }
+
+ // We support this being dalvik.system.DexPathList$Element and dalvik.system.DexFile.
+ // TODO(calin): Code caried over oat_file_manager: supporting both classes seem to be
+ // a historical glitch. All the java code opens dex files using an array of Elements.
+ ObjPtr<mirror::Object> dex_file;
+ if (element_class == element->GetClass()) {
+ dex_file = dex_file_field->GetObject(element);
+ } else if (dexfile_class == element->GetClass()) {
+ dex_file = element;
+ } else {
+ LOG(ERROR) << "Unsupported element in dex_elements: "
+ << mirror::Class::PrettyClass(element->GetClass());
+ return false;
+ }
+
+ if (!CollectDexFilesFromJavaDexFile(dex_file, cookie_field, out_dex_files)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Adds the `class_loader` info to the `context`.
+// The dex file present in `dex_elements` array (if not null) will be added at the end of
+// the classpath.
+// This method is recursive (w.r.t. the class loader parent) and will stop once it reaches the
+// BootClassLoader. Note that the class loader chain is expected to be short.
+bool ClassLoaderContext::AddInfoToContextFromClassLoader(
+ ScopedObjectAccessAlreadyRunnable& soa,
+ Handle<mirror::ClassLoader> class_loader,
+ Handle<mirror::ObjectArray<mirror::Object>> dex_elements)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (ClassLinker::IsBootClassLoader(soa, class_loader.Get())) {
+ // Nothing to do for the boot class loader as we don't add its dex files to the context.
+ return true;
+ }
+
+ ClassLoaderContext::ClassLoaderType type;
+ if (IsPathOrDexClassLoader(soa, class_loader)) {
+ type = kPathClassLoader;
+ } else if (IsDelegateLastClassLoader(soa, class_loader)) {
+ type = kDelegateLastClassLoader;
+ } else {
+ LOG(WARNING) << "Unsupported class loader";
return false;
}
- *out_is_special_shared_library = context.special_shared_library_;
- if (context.special_shared_library_) {
+ // Inspect the class loader for its dex files.
+ std::vector<const DexFile*> dex_files_loaded;
+ CollectDexFilesFromSupportedClassLoader(soa, class_loader, &dex_files_loaded);
+
+ // If we have a dex_elements array extract its dex elements now.
+ // This is used in two situations:
+ // 1) when a new ClassLoader is created DexPathList will open each dex file sequentially
+ // passing the list of already open dex files each time. This ensures that we see the
+ // correct context even if the ClassLoader under construction is not fully build.
+ // 2) when apk splits are loaded on the fly, the framework will load their dex files by
+ // appending them to the current class loader. When the new code paths are loaded in
+ // BaseDexClassLoader, the paths already present in the class loader will be passed
+ // in the dex_elements array.
+ if (dex_elements != nullptr) {
+ GetDexFilesFromDexElementsArray(soa, dex_elements, &dex_files_loaded);
+ }
+
+ class_loader_chain_.push_back(ClassLoaderContext::ClassLoaderInfo(type));
+ ClassLoaderInfo& info = class_loader_chain_.back();
+ for (const DexFile* dex_file : dex_files_loaded) {
+ info.classpath.push_back(dex_file->GetLocation());
+ info.checksums.push_back(dex_file->GetLocationChecksum());
+ info.opened_dex_files.emplace_back(dex_file);
+ }
+
+ // We created the ClassLoaderInfo for the current loader. Move on to its parent.
+
+ StackHandleScope<1> hs(Thread::Current());
+ Handle<mirror::ClassLoader> parent = hs.NewHandle(class_loader->GetParent());
+
+ // Note that dex_elements array is null here. The elements are considered to be part of the
+ // current class loader and are not passed to the parents.
+ ScopedNullHandle<mirror::ObjectArray<mirror::Object>> null_dex_elements;
+ return AddInfoToContextFromClassLoader(soa, parent, null_dex_elements);
+}
+
+std::unique_ptr<ClassLoaderContext> ClassLoaderContext::CreateContextForClassLoader(
+ jobject class_loader,
+ jobjectArray dex_elements) {
+ CHECK(class_loader != nullptr);
+
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScope<2> hs(soa.Self());
+ Handle<mirror::ClassLoader> h_class_loader =
+ hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
+ Handle<mirror::ObjectArray<mirror::Object>> h_dex_elements =
+ hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(dex_elements));
+
+ std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext(/*owns_the_dex_files*/ false));
+ if (result->AddInfoToContextFromClassLoader(soa, h_class_loader, h_dex_elements)) {
+ return result;
+ } else {
+ return nullptr;
+ }
+}
+
+bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) {
+ ClassLoaderContext expected_context;
+ if (!expected_context.Parse(context_spec, /*parse_checksums*/ true)) {
+ LOG(WARNING) << "Invalid class loader context: " << context_spec;
+ return false;
+ }
+
+ if (expected_context.special_shared_library_) {
return true;
}
- if (context.class_loader_chain_.empty()) {
- return true;
+ if (expected_context.class_loader_chain_.size() != class_loader_chain_.size()) {
+ LOG(WARNING) << "ClassLoaderContext size mismatch. expected="
+ << expected_context.class_loader_chain_.size()
+ << ", actual=" << class_loader_chain_.size();
+ return false;
}
- // TODO(calin): assert that we only have a PathClassLoader until the logic for
- // checking the context covers all case.
- CHECK_EQ(1u, context.class_loader_chain_.size());
- const ClassLoaderInfo& info = context.class_loader_chain_[0];
- CHECK_EQ(kPathClassLoader, info.type);
- DCHECK_EQ(info.classpath.size(), info.checksums.size());
+ for (size_t i = 0; i < class_loader_chain_.size(); i++) {
+ const ClassLoaderInfo& info = class_loader_chain_[i];
+ const ClassLoaderInfo& expected_info = expected_context.class_loader_chain_[i];
+ if (info.type != expected_info.type) {
+ LOG(WARNING) << "ClassLoaderContext type mismatch for position " << i
+ << ". expected=" << GetClassLoaderTypeName(expected_info.type)
+ << ", found=" << GetClassLoaderTypeName(info.type);
+ return false;
+ }
+ if (info.classpath.size() != expected_info.classpath.size()) {
+ LOG(WARNING) << "ClassLoaderContext classpath size mismatch for position " << i
+ << ". expected=" << expected_info.classpath.size()
+ << ", found=" << info.classpath.size();
+ return false;
+ }
- *out_classpath = info.classpath;
- *out_checksums = info.checksums;
+ DCHECK_EQ(info.classpath.size(), info.checksums.size());
+ DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size());
+
+ for (size_t k = 0; k < info.classpath.size(); k++) {
+ if (info.classpath[k] != expected_info.classpath[k]) {
+ LOG(WARNING) << "ClassLoaderContext classpath element mismatch for position " << i
+ << ". expected=" << expected_info.classpath[k]
+ << ", found=" << info.classpath[k];
+ return false;
+ }
+ if (info.checksums[k] != expected_info.checksums[k]) {
+ LOG(WARNING) << "ClassLoaderContext classpath element checksum mismatch for position " << i
+ << ". expected=" << expected_info.checksums[k]
+ << ", found=" << info.checksums[k];
+ return false;
+ }
+ }
+ }
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 9727a3b..37dd02b 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -22,7 +22,9 @@
#include "arch/instruction_set.h"
#include "base/dchecked_vector.h"
-#include "jni.h"
+#include "handle_scope.h"
+#include "mirror/class_loader.h"
+#include "scoped_thread_state_change.h"
namespace art {
@@ -35,6 +37,8 @@
// Creates an empty context (with no class loaders).
ClassLoaderContext();
+ ~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
@@ -56,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.
@@ -74,24 +89,35 @@
// Should only be called if OpenDexFiles() returned true.
std::vector<const DexFile*> FlattenOpenedDexFiles() const;
+ // Verifies that the current context is identical to the context encoded as `context_spec`.
+ // Identical means:
+ // - the number and type of the class loaders from the chain matches
+ // - the class loader from the same position have the same classpath
+ // (the order and checksum of the dex files matches)
+ bool VerifyClassLoaderContextMatch(const std::string& context_spec);
+
// 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.
+ //
+ // 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);
- // Decodes the class loader context stored in the oat file with EncodeContextForOatFile.
- // Returns true if the format matches, or false otherwise. If the return is true, the out
- // arguments will contain the classpath dex files, their checksums and whether or not the
- // context is a special shared library.
- // The method asserts that the context is made out of only one PathClassLoader.
- static bool DecodePathClassLoaderContextFromOatFileKey(
- const std::string& context_spec,
- std::vector<std::string>* out_classpath,
- std::vector<uint32_t>* out_checksums,
- bool* out_is_special_shared_library);
+ // Creates a context for the given class_loader and dex_elements.
+ // The method will walk the parent chain starting from `class_loader` and add their dex files
+ // to the current class loaders chain. The `dex_elements` will be added at the end of the
+ // classpath belonging to the `class_loader` argument.
+ // The ownership of the opened dex files will be retained by the given `class_loader`.
+ // If there are errors in processing the class loader chain (e.g. unsupported elements) the
+ // method returns null.
+ static std::unique_ptr<ClassLoaderContext> CreateContextForClassLoader(jobject class_loader,
+ jobjectArray dex_elements);
private:
enum ClassLoaderType {
@@ -118,6 +144,13 @@
explicit ClassLoaderInfo(ClassLoaderType cl_type) : type(cl_type) {}
};
+ // Constructs an empty context.
+ // `owns_the_dex_files` specifies whether or not the context will own the opened dex files
+ // present in the class loader chain. If `owns_the_dex_files` is true then OpenDexFiles cannot
+ // be called on this context (dex_files_open_attempted_ and dex_files_open_result_ will be set
+ // to true as well)
+ explicit ClassLoaderContext(bool owns_the_dex_files);
+
// 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, bool parse_checksums = false);
@@ -129,6 +162,19 @@
ClassLoaderType class_loader_type,
bool parse_checksums = false);
+ // CHECKs that the dex files were opened (OpenDexFiles was called and set dex_files_open_result_
+ // to true). Aborts if not. The `calling_method` is used in the log message to identify the source
+ // of the call.
+ void CheckDexFilesOpened(const std::string& calling_method) const;
+
+ // Adds the `class_loader` info to the context.
+ // The dex file present in `dex_elements` array (if not null) will be added at the end of
+ // the classpath.
+ bool AddInfoToContextFromClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+ Handle<mirror::ClassLoader> class_loader,
+ Handle<mirror::ObjectArray<mirror::Object>> dex_elements)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Extracts the class loader type from the given spec.
// Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not
// recognized.
@@ -138,8 +184,8 @@
// 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;
+ // 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++].
@@ -158,6 +204,13 @@
// The result of the last OpenDexFiles() operation.
bool dex_files_open_result_;
+ // Whether or not the context owns the opened dex and oat files.
+ // If true, the opened dex files will be de-allocated when the context is destructed.
+ // If false, the objects will continue to be alive.
+ // Note that for convenience the the opened dex/oat files are stored as unique pointers
+ // which will release their ownership in the destructor based on this flag.
+ const bool owns_the_dex_files_;
+
friend class ClassLoaderContextTest;
DISALLOW_COPY_AND_ASSIGN(ClassLoaderContext);
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index 03eb0e4..1b46f67 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -45,18 +45,32 @@
void VerifyClassLoaderPCL(ClassLoaderContext* context,
size_t index,
- std::string classpath) {
+ const std::string& classpath) {
VerifyClassLoaderInfo(
context, index, ClassLoaderContext::kPathClassLoader, classpath);
}
void VerifyClassLoaderDLC(ClassLoaderContext* context,
size_t index,
- std::string classpath) {
+ const std::string& classpath) {
VerifyClassLoaderInfo(
context, index, ClassLoaderContext::kDelegateLastClassLoader, classpath);
}
+ void VerifyClassLoaderPCLFromTestDex(ClassLoaderContext* context,
+ size_t index,
+ const std::string& test_name) {
+ VerifyClassLoaderFromTestDex(
+ context, index, ClassLoaderContext::kPathClassLoader, test_name);
+ }
+
+ void VerifyClassLoaderDLCFromTestDex(ClassLoaderContext* context,
+ size_t index,
+ const std::string& test_name) {
+ VerifyClassLoaderFromTestDex(
+ context, index, ClassLoaderContext::kDelegateLastClassLoader, test_name);
+ }
+
void VerifyOpenDexFiles(
ClassLoaderContext* context,
size_t index,
@@ -83,11 +97,49 @@
}
}
+ std::unique_ptr<ClassLoaderContext> CreateContextForClassLoader(jobject class_loader) {
+ return ClassLoaderContext::CreateContextForClassLoader(class_loader, nullptr);
+ }
+
+ std::unique_ptr<ClassLoaderContext> ParseContextWithChecksums(const std::string& context_spec) {
+ std::unique_ptr<ClassLoaderContext> context(new ClassLoaderContext());
+ if (!context->Parse(context_spec, /*parse_checksums*/ true)) {
+ return nullptr;
+ }
+ return context;
+ }
+
+ void VerifyContextForClassLoader(ClassLoaderContext* context) {
+ ASSERT_TRUE(context != nullptr);
+ ASSERT_TRUE(context->dex_files_open_attempted_);
+ ASSERT_TRUE(context->dex_files_open_result_);
+ ASSERT_FALSE(context->owns_the_dex_files_);
+ 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,
ClassLoaderContext::ClassLoaderType type,
- std::string classpath) {
+ const std::string& classpath) {
ASSERT_TRUE(context != nullptr);
ASSERT_GT(context->class_loader_chain_.size(), index);
ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index];
@@ -96,6 +148,18 @@
Split(classpath, ':', &expected_classpath);
ASSERT_EQ(expected_classpath, info.classpath);
}
+
+ void VerifyClassLoaderFromTestDex(ClassLoaderContext* context,
+ size_t index,
+ ClassLoaderContext::ClassLoaderType type,
+ const std::string& test_name) {
+ std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles(test_name.c_str());
+ std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files;
+ all_dex_files.push_back(&dex_files);
+
+ VerifyClassLoaderInfo(context, index, type, GetTestDexFileName(test_name.c_str()));
+ VerifyOpenDexFiles(context, index, all_dex_files);
+ }
};
TEST_F(ClassLoaderContextTest, ParseValidContextPCL) {
@@ -200,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));
@@ -209,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) {
@@ -244,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]");
@@ -292,46 +410,82 @@
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(""));
}
-TEST_F(ClassLoaderContextTest, DecodeOatFileKey) {
- std::string oat_file_encoding = "PCL[a.dex*123:b.dex*456]";
- std::vector<std::string> classpath;
- std::vector<uint32_t> checksums;
- bool is_special_shared_library;
- bool result = ClassLoaderContext::DecodePathClassLoaderContextFromOatFileKey(
- oat_file_encoding,
- &classpath,
- &checksums,
- &is_special_shared_library);
- ASSERT_TRUE(result);
- ASSERT_FALSE(is_special_shared_library);
- ASSERT_EQ(2u, classpath.size());
- ASSERT_EQ(2u, checksums.size());
- ASSERT_EQ("a.dex", classpath[0]);
- ASSERT_EQ(123u, checksums[0]);
- ASSERT_EQ("b.dex", classpath[1]);
- ASSERT_EQ(456u, checksums[1]);
+// TODO(calin) add a test which creates the context for a class loader together with dex_elements.
+TEST_F(ClassLoaderContextTest, CreateContextForClassLoader) {
+ // The chain is
+ // ClassLoaderA (PathClassLoader)
+ // ^
+ // |
+ // ClassLoaderB (DelegateLastClassLoader)
+ // ^
+ // |
+ // ClassLoaderC (PathClassLoader)
+ // ^
+ // |
+ // ClassLoaderD (DelegateLastClassLoader)
+
+ jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
+ jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a);
+ jobject class_loader_c = LoadDexInPathClassLoader("ForClassLoaderC", class_loader_b);
+ jobject class_loader_d = LoadDexInDelegateLastClassLoader("ForClassLoaderD", class_loader_c);
+
+ std::unique_ptr<ClassLoaderContext> context = CreateContextForClassLoader(class_loader_d);
+
+ VerifyContextForClassLoader(context.get());
+ VerifyContextSize(context.get(), 4);
+
+ VerifyClassLoaderDLCFromTestDex(context.get(), 0, "ForClassLoaderD");
+ VerifyClassLoaderPCLFromTestDex(context.get(), 1, "ForClassLoaderC");
+ VerifyClassLoaderDLCFromTestDex(context.get(), 2, "ForClassLoaderB");
+ VerifyClassLoaderPCLFromTestDex(context.get(), 3, "ForClassLoaderA");
}
-TEST_F(ClassLoaderContextTest, DecodeOatFileKeySpecialLibrary) {
- std::string oat_file_encoding = "&";
- std::vector<std::string> classpath;
- std::vector<uint32_t> checksums;
- bool is_special_shared_library;
- bool result = ClassLoaderContext::DecodePathClassLoaderContextFromOatFileKey(
- oat_file_encoding,
- &classpath,
- &checksums,
- &is_special_shared_library);
- ASSERT_TRUE(result);
- ASSERT_TRUE(is_special_shared_library);
- ASSERT_TRUE(classpath.empty());
- ASSERT_TRUE(checksums.empty());
+TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatch) {
+ std::string context_spec = "PCL[a.dex*123:b.dex*456];DLC[c.dex*890]";
+ std::unique_ptr<ClassLoaderContext> context = ParseContextWithChecksums(context_spec);
+
+ VerifyContextSize(context.get(), 2);
+ VerifyClassLoaderPCL(context.get(), 0, "a.dex:b.dex");
+ VerifyClassLoaderDLC(context.get(), 1, "c.dex");
+
+ ASSERT_TRUE(context->VerifyClassLoaderContextMatch(context_spec));
+
+ std::string wrong_class_loader_type = "PCL[a.dex*123:b.dex*456];PCL[c.dex*890]";
+ ASSERT_FALSE(context->VerifyClassLoaderContextMatch(wrong_class_loader_type));
+
+ std::string wrong_class_loader_order = "DLC[c.dex*890];PCL[a.dex*123:b.dex*456]";
+ ASSERT_FALSE(context->VerifyClassLoaderContextMatch(wrong_class_loader_order));
+
+ std::string wrong_classpath_order = "PCL[b.dex*456:a.dex*123];DLC[c.dex*890]";
+ ASSERT_FALSE(context->VerifyClassLoaderContextMatch(wrong_classpath_order));
+
+ std::string wrong_checksum = "PCL[a.dex*999:b.dex*456];DLC[c.dex*890]";
+ ASSERT_FALSE(context->VerifyClassLoaderContextMatch(wrong_checksum));
+
+ std::string wrong_extra_class_loader = "PCL[a.dex*123:b.dex*456];DLC[c.dex*890];PCL[d.dex*321]";
+ ASSERT_FALSE(context->VerifyClassLoaderContextMatch(wrong_extra_class_loader));
+
+ std::string wrong_extra_classpath = "PCL[a.dex*123:b.dex*456];DLC[c.dex*890:d.dex*321]";
+ ASSERT_FALSE(context->VerifyClassLoaderContextMatch(wrong_extra_classpath));
+
+ std::string wrong_spec = "PCL[a.dex*999:b.dex*456];DLC[";
+ ASSERT_FALSE(context->VerifyClassLoaderContextMatch(wrong_spec));
+}
+
+TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncoding) {
+ jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
+ jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a);
+ jobject class_loader_c = LoadDexInPathClassLoader("ForClassLoaderC", class_loader_b);
+ jobject class_loader_d = LoadDexInDelegateLastClassLoader("ForClassLoaderD", class_loader_c);
+
+ std::unique_ptr<ClassLoaderContext> context = CreateContextForClassLoader(class_loader_d);
+
+ ASSERT_TRUE(context->VerifyClassLoaderContextMatch(context->EncodeContextForOatFile("")));
}
} // namespace art
diff --git a/runtime/class_loader_utils.h b/runtime/class_loader_utils.h
new file mode 100644
index 0000000..d160a51
--- /dev/null
+++ b/runtime/class_loader_utils.h
@@ -0,0 +1,50 @@
+/*
+ * 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_UTILS_H_
+#define ART_RUNTIME_CLASS_LOADER_UTILS_H_
+
+#include "handle_scope.h"
+#include "mirror/class_loader.h"
+#include "scoped_thread_state_change-inl.h"
+#include "well_known_classes.h"
+
+namespace art {
+
+// Returns true if the given class loader is either a PathClassLoader or a DexClassLoader.
+// (they both have the same behaviour with respect to class lockup order)
+static bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+ Handle<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ mirror::Class* class_loader_class = class_loader->GetClass();
+ return
+ (class_loader_class ==
+ soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) ||
+ (class_loader_class ==
+ soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader));
+}
+
+static bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+ Handle<mirror::ClassLoader> class_loader)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ mirror::Class* class_loader_class = class_loader->GetClass();
+ return class_loader_class ==
+ soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader);
+}
+
+} // namespace art
+
+#endif // ART_RUNTIME_CLASS_LOADER_UTILS_H_
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index a4b9053..a425224 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 fcf3a31..daf9ac3 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);
diff --git a/runtime/gc/space/malloc_space.cc b/runtime/gc/space/malloc_space.cc
index a186f4c..1154620 100644
--- a/runtime/gc/space/malloc_space.cc
+++ b/runtime/gc/space/malloc_space.cc
@@ -191,10 +191,14 @@
VLOG(heap) << "Size " << GetMemMap()->Size();
VLOG(heap) << "GrowthLimit " << PrettySize(growth_limit);
VLOG(heap) << "Capacity " << PrettySize(capacity);
- // Remap the tail.
+ // Remap the tail. Pass MAP_PRIVATE since we don't want to share the same ashmem as the zygote
+ // space.
std::string error_msg;
- std::unique_ptr<MemMap> mem_map(GetMemMap()->RemapAtEnd(End(), alloc_space_name,
- PROT_READ | PROT_WRITE, &error_msg));
+ std::unique_ptr<MemMap> mem_map(GetMemMap()->RemapAtEnd(End(),
+ alloc_space_name,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
+ &error_msg));
CHECK(mem_map.get() != nullptr) << error_msg;
void* allocator = CreateAllocator(End(), starting_size_, initial_size_, capacity,
low_memory_mode);
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 1c36bde..8295f46 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -47,9 +47,13 @@
static constexpr int kProtAll = PROT_READ | PROT_WRITE | PROT_EXEC;
static constexpr int kProtData = PROT_READ | PROT_WRITE;
static constexpr int kProtCode = PROT_READ | PROT_EXEC;
+static constexpr int kProtReadOnly = PROT_READ;
+static constexpr int kProtNone = PROT_NONE;
static constexpr size_t kCodeSizeLogThreshold = 50 * KB;
static constexpr size_t kStackMapSizeLogThreshold = 50 * KB;
+static constexpr size_t kMinMapSpacingPages = 1;
+static constexpr size_t kMaxMapSpacingPages = 128;
#define CHECKED_MPROTECT(memory, size, prot) \
do { \
@@ -60,19 +64,52 @@
} \
} while (false) \
+static MemMap* SplitMemMap(MemMap* existing_map,
+ const char* name,
+ size_t split_offset,
+ int split_prot,
+ std::string* error_msg,
+ bool use_ashmem,
+ unique_fd* shmem_fd = nullptr) {
+ std::string error_str;
+ uint8_t* divider = existing_map->Begin() + split_offset;
+ MemMap* new_map = existing_map->RemapAtEnd(divider,
+ name,
+ split_prot,
+ MAP_SHARED,
+ &error_str,
+ use_ashmem,
+ shmem_fd);
+ if (new_map == nullptr) {
+ std::ostringstream oss;
+ oss << "Failed to create spacing for " << name << ": "
+ << error_str << " offset=" << split_offset;
+ *error_msg = oss.str();
+ return nullptr;
+ }
+ return new_map;
+}
+
+
JitCodeCache* JitCodeCache::Create(size_t initial_capacity,
size_t max_capacity,
bool generate_debug_info,
std::string* error_msg) {
ScopedTrace trace(__PRETTY_FUNCTION__);
- CHECK_GE(max_capacity, initial_capacity);
+ CHECK_GT(max_capacity, initial_capacity);
+ CHECK_GE(max_capacity - kMaxMapSpacingPages * kPageSize, initial_capacity);
- // Generating debug information is mostly for using the 'perf' tool, which does
- // not work with ashmem.
+ // Generating debug information is for using the Linux perf tool on
+ // host which does not work with ashmem.
bool use_ashmem = !generate_debug_info;
+
// With 'perf', we want a 1-1 mapping between an address and a method.
bool garbage_collect_code = !generate_debug_info;
+ // We only use two mappings (separating rw from rx) if we are able to use ashmem.
+ // See the above comment for debug information and not using ashmem.
+ bool use_two_mappings = !generate_debug_info;
+
// We need to have 32 bit offsets from method headers in code cache which point to things
// in the data cache. If the maps are more than 4G apart, having multiple maps wouldn't work.
// Ensure we're below 1 GB to be safe.
@@ -109,30 +146,114 @@
initial_capacity = RoundDown(initial_capacity, 2 * kPageSize);
max_capacity = RoundDown(max_capacity, 2 * kPageSize);
- // Data cache is 1 / 2 of the map.
- // TODO: Make this variable?
- size_t data_size = max_capacity / 2;
- size_t code_size = max_capacity - data_size;
- DCHECK_EQ(code_size + data_size, max_capacity);
- uint8_t* divider = data_map->Begin() + data_size;
+ // Create a region for JIT data and executable code. This will be
+ // laid out as:
+ //
+ // +----------------+ --------------------
+ // : : ^ ^
+ // : post_code_map : | post_code_size |
+ // : [padding] : v |
+ // +----------------+ - |
+ // | | ^ |
+ // | code_map | | code_size |
+ // | [JIT Code] | v |
+ // +----------------+ - | total_mapping_size
+ // : : ^ |
+ // : pre_code_map : | pre_code_size |
+ // : [padding] : v |
+ // +----------------+ - |
+ // | | ^ |
+ // | data_map | | data_size |
+ // | [Jit Data] | v v
+ // +----------------+ --------------------
+ //
+ // The padding regions - pre_code_map and post_code_map - exist to
+ // put some random distance between the writable JIT code mapping
+ // and the executable mapping. The padding is discarded at the end
+ // of this function.
+ size_t total_mapping_size = kMaxMapSpacingPages * kPageSize;
+ size_t data_size = RoundUp((max_capacity - total_mapping_size) / 2, kPageSize);
+ size_t pre_code_size =
+ GetRandomNumber(kMinMapSpacingPages, kMaxMapSpacingPages) * kPageSize;
+ size_t code_size = max_capacity - total_mapping_size - data_size;
+ size_t post_code_size = total_mapping_size - pre_code_size;
+ DCHECK_EQ(code_size + data_size + total_mapping_size, max_capacity);
- MemMap* code_map =
- data_map->RemapAtEnd(divider, "jit-code-cache", kProtAll, &error_str, use_ashmem);
- if (code_map == nullptr) {
- std::ostringstream oss;
- oss << "Failed to create read write execute cache: " << error_str << " size=" << max_capacity;
- *error_msg = oss.str();
+ // Create pre-code padding region after data region, discarded after
+ // code and data regions are set-up.
+ std::unique_ptr<MemMap> pre_code_map(SplitMemMap(data_map.get(),
+ "jit-code-cache-padding",
+ data_size,
+ kProtNone,
+ error_msg,
+ use_ashmem));
+ if (pre_code_map == nullptr) {
return nullptr;
}
- DCHECK_EQ(code_map->Begin(), divider);
+ DCHECK_EQ(data_map->Size(), data_size);
+ DCHECK_EQ(pre_code_map->Size(), pre_code_size + code_size + post_code_size);
+
+ // Create code region.
+ unique_fd writable_code_fd;
+ std::unique_ptr<MemMap> code_map(SplitMemMap(pre_code_map.get(),
+ "jit-code-cache",
+ pre_code_size,
+ use_two_mappings ? kProtCode : kProtAll,
+ error_msg,
+ use_ashmem,
+ &writable_code_fd));
+ if (code_map == nullptr) {
+ return nullptr;
+ }
+ DCHECK_EQ(pre_code_map->Size(), pre_code_size);
+ DCHECK_EQ(code_map->Size(), code_size + post_code_size);
+
+ // Padding after code region, discarded after code and data regions
+ // are set-up.
+ std::unique_ptr<MemMap> post_code_map(SplitMemMap(code_map.get(),
+ "jit-code-cache-padding",
+ code_size,
+ kProtNone,
+ error_msg,
+ use_ashmem));
+ if (post_code_map == nullptr) {
+ return nullptr;
+ }
+ DCHECK_EQ(code_map->Size(), code_size);
+ DCHECK_EQ(post_code_map->Size(), post_code_size);
+
+ std::unique_ptr<MemMap> writable_code_map;
+ if (use_two_mappings) {
+ // Allocate the R/W view.
+ writable_code_map.reset(MemMap::MapFile(code_size,
+ kProtData,
+ MAP_SHARED,
+ writable_code_fd.get(),
+ /* start */ 0,
+ /* low_4gb */ true,
+ "jit-writable-code",
+ &error_str));
+ if (writable_code_map == nullptr) {
+ std::ostringstream oss;
+ oss << "Failed to create writable code cache: " << error_str << " size=" << code_size;
+ *error_msg = oss.str();
+ return nullptr;
+ }
+ }
data_size = initial_capacity / 2;
code_size = initial_capacity - data_size;
DCHECK_EQ(code_size + data_size, initial_capacity);
- return new JitCodeCache(
- code_map, data_map.release(), code_size, data_size, max_capacity, garbage_collect_code);
+ return new JitCodeCache(writable_code_map.release(),
+ code_map.release(),
+ data_map.release(),
+ code_size,
+ data_size,
+ max_capacity,
+ garbage_collect_code);
}
-JitCodeCache::JitCodeCache(MemMap* code_map,
+JitCodeCache::JitCodeCache(MemMap* writable_code_map,
+ MemMap* executable_code_map,
MemMap* data_map,
size_t initial_code_capacity,
size_t initial_data_capacity,
@@ -141,8 +262,9 @@
: lock_("Jit code cache", kJitCodeCacheLock),
lock_cond_("Jit code cache condition variable", lock_),
collection_in_progress_(false),
- code_map_(code_map),
data_map_(data_map),
+ executable_code_map_(executable_code_map),
+ writable_code_map_(writable_code_map),
max_capacity_(max_capacity),
current_capacity_(initial_code_capacity + initial_data_capacity),
code_end_(initial_code_capacity),
@@ -162,7 +284,8 @@
inline_cache_cond_("Jit inline cache condition variable", lock_) {
DCHECK_GE(max_capacity, initial_code_capacity + initial_data_capacity);
- code_mspace_ = create_mspace_with_base(code_map_->Begin(), code_end_, false /*locked*/);
+ MemMap* writable_map = GetWritableMemMap();
+ code_mspace_ = create_mspace_with_base(writable_map->Begin(), code_end_, false /*locked*/);
data_mspace_ = create_mspace_with_base(data_map_->Begin(), data_end_, false /*locked*/);
if (code_mspace_ == nullptr || data_mspace_ == nullptr) {
@@ -171,7 +294,10 @@
SetFootprintLimit(current_capacity_);
- CHECKED_MPROTECT(code_map_->Begin(), code_map_->Size(), kProtCode);
+ if (writable_code_map_ != nullptr) {
+ CHECKED_MPROTECT(writable_code_map_->Begin(), writable_code_map_->Size(), kProtReadOnly);
+ }
+ CHECKED_MPROTECT(executable_code_map_->Begin(), executable_code_map_->Size(), kProtCode);
CHECKED_MPROTECT(data_map_->Begin(), data_map_->Size(), kProtData);
VLOG(jit) << "Created jit code cache: initial data size="
@@ -181,7 +307,7 @@
}
bool JitCodeCache::ContainsPc(const void* ptr) const {
- return code_map_->Begin() <= ptr && ptr < code_map_->End();
+ return executable_code_map_->Begin() <= ptr && ptr < executable_code_map_->End();
}
bool JitCodeCache::ContainsMethod(ArtMethod* method) {
@@ -194,27 +320,96 @@
return false;
}
+/* This method is only for CHECK/DCHECK that pointers are within to a region. */
+static bool IsAddressInMap(const void* addr,
+ const MemMap* mem_map,
+ const char* check_name) {
+ if (addr == nullptr || mem_map->HasAddress(addr)) {
+ return true;
+ }
+ LOG(ERROR) << "Is" << check_name << "Address " << addr
+ << " not in [" << reinterpret_cast<void*>(mem_map->Begin())
+ << ", " << reinterpret_cast<void*>(mem_map->Begin() + mem_map->Size()) << ")";
+ return false;
+}
+
+bool JitCodeCache::IsDataAddress(const void* raw_addr) const {
+ return IsAddressInMap(raw_addr, data_map_.get(), "Data");
+}
+
+bool JitCodeCache::IsExecutableAddress(const void* raw_addr) const {
+ return IsAddressInMap(raw_addr, executable_code_map_.get(), "Executable");
+}
+
+bool JitCodeCache::IsWritableAddress(const void* raw_addr) const {
+ return IsAddressInMap(raw_addr, GetWritableMemMap(), "Writable");
+}
+
+// Convert one address within the source map to the same offset within the destination map.
+static void* ConvertAddress(const void* source_address,
+ const MemMap* source_map,
+ const MemMap* destination_map) {
+ DCHECK(source_map->HasAddress(source_address)) << source_address;
+ ptrdiff_t offset = reinterpret_cast<const uint8_t*>(source_address) - source_map->Begin();
+ uintptr_t address = reinterpret_cast<uintptr_t>(destination_map->Begin()) + offset;
+ return reinterpret_cast<void*>(address);
+}
+
+template <typename T>
+T* JitCodeCache::ToExecutableAddress(T* writable_address) const {
+ CHECK(IsWritableAddress(writable_address));
+ if (writable_address == nullptr) {
+ return nullptr;
+ }
+ void* executable_address = ConvertAddress(writable_address,
+ GetWritableMemMap(),
+ executable_code_map_.get());
+ CHECK(IsExecutableAddress(executable_address));
+ return reinterpret_cast<T*>(executable_address);
+}
+
+void* JitCodeCache::ToWritableAddress(const void* executable_address) const {
+ CHECK(IsExecutableAddress(executable_address));
+ if (executable_address == nullptr) {
+ return nullptr;
+ }
+ void* writable_address = ConvertAddress(executable_address,
+ executable_code_map_.get(),
+ GetWritableMemMap());
+ CHECK(IsWritableAddress(writable_address));
+ return writable_address;
+}
+
class ScopedCodeCacheWrite : ScopedTrace {
public:
- explicit ScopedCodeCacheWrite(MemMap* code_map, bool only_for_tlb_shootdown = false)
- : ScopedTrace("ScopedCodeCacheWrite"),
- code_map_(code_map),
- only_for_tlb_shootdown_(only_for_tlb_shootdown) {
+ explicit ScopedCodeCacheWrite(JitCodeCache* code_cache, bool only_for_tlb_shootdown = false)
+ : ScopedTrace("ScopedCodeCacheWrite") {
ScopedTrace trace("mprotect all");
- CHECKED_MPROTECT(
- code_map_->Begin(), only_for_tlb_shootdown_ ? kPageSize : code_map_->Size(), kProtAll);
+ int prot_to_start_writing = kProtAll;
+ if (code_cache->writable_code_map_ == nullptr) {
+ // If there is only one mapping, use the executable mapping and toggle between rwx and rx.
+ prot_to_start_writing = kProtAll;
+ prot_to_stop_writing_ = kProtCode;
+ } else {
+ // If there are two mappings, use the writable mapping and toggle between rw and r.
+ prot_to_start_writing = kProtData;
+ prot_to_stop_writing_ = kProtReadOnly;
+ }
+ writable_map_ = code_cache->GetWritableMemMap();
+ // If we're using ScopedCacheWrite only for TLB shootdown, we limit the scope of mprotect to
+ // one page.
+ size_ = only_for_tlb_shootdown ? kPageSize : writable_map_->Size();
+ CHECKED_MPROTECT(writable_map_->Begin(), size_, prot_to_start_writing);
}
~ScopedCodeCacheWrite() {
ScopedTrace trace("mprotect code");
- CHECKED_MPROTECT(
- code_map_->Begin(), only_for_tlb_shootdown_ ? kPageSize : code_map_->Size(), kProtCode);
+ CHECKED_MPROTECT(writable_map_->Begin(), size_, prot_to_stop_writing_);
}
- private:
- MemMap* const code_map_;
- // If we're using ScopedCacheWrite only for TLB shootdown, we limit the scope of mprotect to
- // one page.
- const bool only_for_tlb_shootdown_;
+ private:
+ int prot_to_stop_writing_;
+ MemMap* writable_map_;
+ size_t size_;
DISALLOW_COPY_AND_ASSIGN(ScopedCodeCacheWrite);
};
@@ -324,8 +519,10 @@
}
}
-static uint8_t* GetRootTable(const void* code_ptr, uint32_t* number_of_roots = nullptr) {
+uint8_t* JitCodeCache::GetRootTable(const void* code_ptr, uint32_t* number_of_roots) {
+ CHECK(IsExecutableAddress(code_ptr));
OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr);
+ // GetOptimizedCodeInfoPtr uses offsets relative to the EXECUTABLE address.
uint8_t* data = method_header->GetOptimizedCodeInfoPtr();
uint32_t roots = GetNumberOfRoots(data);
if (number_of_roots != nullptr) {
@@ -370,6 +567,8 @@
void JitCodeCache::SweepRootTables(IsMarkedVisitor* visitor) {
MutexLock mu(Thread::Current(), lock_);
for (const auto& entry : method_code_map_) {
+ // GetRootTable takes an EXECUTABLE address.
+ CHECK(IsExecutableAddress(entry.first));
uint32_t number_of_roots = 0;
uint8_t* roots_data = GetRootTable(entry.first, &number_of_roots);
GcRoot<mirror::Object>* roots = reinterpret_cast<GcRoot<mirror::Object>*>(roots_data);
@@ -407,17 +606,19 @@
}
}
-void JitCodeCache::FreeCode(const void* code_ptr) {
- uintptr_t allocation = FromCodeToAllocation(code_ptr);
+void JitCodeCache::FreeCodeAndData(const void* code_ptr) {
+ CHECK(IsExecutableAddress(code_ptr));
// Notify native debugger that we are about to remove the code.
// It does nothing if we are not using native debugger.
DeleteJITCodeEntryForAddress(reinterpret_cast<uintptr_t>(code_ptr));
+ // GetRootTable takes an EXECUTABLE address.
FreeData(GetRootTable(code_ptr));
- FreeCode(reinterpret_cast<uint8_t*>(allocation));
+ FreeRawCode(reinterpret_cast<uint8_t*>(FromCodeToAllocation(code_ptr)));
}
void JitCodeCache::FreeAllMethodHeaders(
const std::unordered_set<OatQuickMethodHeader*>& method_headers) {
+ // method_headers are expected to be in the executable region.
{
MutexLock mu(Thread::Current(), *Locks::cha_lock_);
Runtime::Current()->GetClassHierarchyAnalysis()
@@ -429,9 +630,9 @@
// so it's possible for the same method_header to start representing
// different compile code.
MutexLock mu(Thread::Current(), lock_);
- ScopedCodeCacheWrite scc(code_map_.get());
+ ScopedCodeCacheWrite scc(this);
for (const OatQuickMethodHeader* method_header : method_headers) {
- FreeCode(method_header->GetCode());
+ FreeCodeAndData(method_header->GetCode());
}
}
@@ -448,9 +649,10 @@
// with the classlinker_classes_lock_ held, and suspending ourselves could
// lead to a deadlock.
{
- ScopedCodeCacheWrite scc(code_map_.get());
+ ScopedCodeCacheWrite scc(this);
for (auto it = method_code_map_.begin(); it != method_code_map_.end();) {
if (alloc.ContainsUnsafe(it->second)) {
+ CHECK(IsExecutableAddress(OatQuickMethodHeader::FromCodePointer(it->first)));
method_headers.insert(OatQuickMethodHeader::FromCodePointer(it->first));
it = method_code_map_.erase(it);
} else {
@@ -572,16 +774,21 @@
MutexLock mu(self, lock_);
WaitForPotentialCollectionToComplete(self);
{
- ScopedCodeCacheWrite scc(code_map_.get());
+ ScopedCodeCacheWrite scc(this);
memory = AllocateCode(total_size);
if (memory == nullptr) {
return nullptr;
}
- code_ptr = memory + header_size;
+ uint8_t* writable_ptr = memory + header_size;
+ code_ptr = ToExecutableAddress(writable_ptr);
- std::copy(code, code + code_size, code_ptr);
- method_header = OatQuickMethodHeader::FromCodePointer(code_ptr);
- new (method_header) OatQuickMethodHeader(
+ std::copy(code, code + code_size, writable_ptr);
+ OatQuickMethodHeader* writable_method_header =
+ OatQuickMethodHeader::FromCodePointer(writable_ptr);
+ // We need to be able to write the OatQuickMethodHeader, so we use writable_method_header.
+ // Otherwise, the offsets encoded in OatQuickMethodHeader are used relative to an executable
+ // address, so we use code_ptr.
+ new (writable_method_header) OatQuickMethodHeader(
code_ptr - stack_map,
code_ptr - method_info,
frame_size_in_bytes,
@@ -597,10 +804,16 @@
// https://android.googlesource.com/kernel/msm/+/3fbe6bc28a6b9939d0650f2f17eb5216c719950c
FlushInstructionCache(reinterpret_cast<char*>(code_ptr),
reinterpret_cast<char*>(code_ptr + code_size));
+ if (writable_ptr != code_ptr) {
+ FlushDataCache(reinterpret_cast<char*>(writable_ptr),
+ reinterpret_cast<char*>(writable_ptr + code_size));
+ }
DCHECK(!Runtime::Current()->IsAotCompiler());
if (has_should_deoptimize_flag) {
- method_header->SetHasShouldDeoptimizeFlag();
+ writable_method_header->SetHasShouldDeoptimizeFlag();
}
+ // All the pointers exported from the cache are executable addresses.
+ method_header = ToExecutableAddress(writable_method_header);
}
number_of_compilations_++;
@@ -639,13 +852,14 @@
// but below we still make the compiled code valid for the method.
MutexLock mu(self, lock_);
// Fill the root table before updating the entry point.
+ CHECK(IsDataAddress(roots_data));
DCHECK_EQ(FromStackMapToRoots(stack_map), roots_data);
DCHECK_LE(roots_data, stack_map);
FillRootTable(roots_data, roots);
{
// Flush data cache, as compiled code references literals in it.
// We also need a TLB shootdown to act as memory barrier across cores.
- ScopedCodeCacheWrite ccw(code_map_.get(), /* only_for_tlb_shootdown */ true);
+ ScopedCodeCacheWrite ccw(this, /* only_for_tlb_shootdown */ true);
FlushDataCache(reinterpret_cast<char*>(roots_data),
reinterpret_cast<char*>(roots_data + data_size));
}
@@ -696,11 +910,11 @@
bool in_cache = false;
{
- ScopedCodeCacheWrite ccw(code_map_.get());
+ ScopedCodeCacheWrite ccw(this);
for (auto code_iter = method_code_map_.begin(); code_iter != method_code_map_.end();) {
if (code_iter->second == method) {
if (release_memory) {
- FreeCode(code_iter->first);
+ FreeCodeAndData(code_iter->first);
}
code_iter = method_code_map_.erase(code_iter);
in_cache = true;
@@ -754,10 +968,10 @@
profiling_infos_.erase(profile);
}
method->SetProfilingInfo(nullptr);
- ScopedCodeCacheWrite ccw(code_map_.get());
+ ScopedCodeCacheWrite ccw(this);
for (auto code_iter = method_code_map_.begin(); code_iter != method_code_map_.end();) {
if (code_iter->second == method) {
- FreeCode(code_iter->first);
+ FreeCodeAndData(code_iter->first);
code_iter = method_code_map_.erase(code_iter);
continue;
}
@@ -823,6 +1037,7 @@
uint8_t* stack_map_data,
uint8_t* roots_data) {
DCHECK_EQ(FromStackMapToRoots(stack_map_data), roots_data);
+ CHECK(IsDataAddress(roots_data));
MutexLock mu(self, lock_);
FreeData(reinterpret_cast<uint8_t*>(roots_data));
}
@@ -944,11 +1159,11 @@
void JitCodeCache::SetFootprintLimit(size_t new_footprint) {
size_t per_space_footprint = new_footprint / 2;
- DCHECK(IsAlignedParam(per_space_footprint, kPageSize));
+ CHECK(IsAlignedParam(per_space_footprint, kPageSize));
DCHECK_EQ(per_space_footprint * 2, new_footprint);
mspace_set_footprint_limit(data_mspace_, per_space_footprint);
{
- ScopedCodeCacheWrite scc(code_map_.get());
+ ScopedCodeCacheWrite scc(this);
mspace_set_footprint_limit(code_mspace_, per_space_footprint);
}
}
@@ -1026,8 +1241,8 @@
number_of_collections_++;
live_bitmap_.reset(CodeCacheBitmap::Create(
"code-cache-bitmap",
- reinterpret_cast<uintptr_t>(code_map_->Begin()),
- reinterpret_cast<uintptr_t>(code_map_->Begin() + current_capacity_ / 2)));
+ reinterpret_cast<uintptr_t>(executable_code_map_->Begin()),
+ reinterpret_cast<uintptr_t>(executable_code_map_->Begin() + current_capacity_ / 2)));
collection_in_progress_ = true;
}
}
@@ -1103,14 +1318,16 @@
std::unordered_set<OatQuickMethodHeader*> method_headers;
{
MutexLock mu(self, lock_);
- ScopedCodeCacheWrite scc(code_map_.get());
+ ScopedCodeCacheWrite scc(this);
// Iterate over all compiled code and remove entries that are not marked.
for (auto it = method_code_map_.begin(); it != method_code_map_.end();) {
const void* code_ptr = it->first;
+ CHECK(IsExecutableAddress(code_ptr));
uintptr_t allocation = FromCodeToAllocation(code_ptr);
if (GetLiveBitmap()->Test(allocation)) {
++it;
} else {
+ CHECK(IsExecutableAddress(it->first));
method_headers.insert(OatQuickMethodHeader::FromCodePointer(it->first));
it = method_code_map_.erase(it);
}
@@ -1153,6 +1370,7 @@
for (const auto& it : method_code_map_) {
ArtMethod* method = it.second;
const void* code_ptr = it.first;
+ CHECK(IsExecutableAddress(code_ptr));
const OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr);
if (method_header->GetEntryPoint() == method->GetEntryPointFromQuickCompiledCode()) {
GetLiveBitmap()->AtomicTestAndSet(FromCodeToAllocation(code_ptr));
@@ -1178,6 +1396,7 @@
// Free all profiling infos of methods not compiled nor being compiled.
auto profiling_kept_end = std::remove_if(profiling_infos_.begin(), profiling_infos_.end(),
[this] (ProfilingInfo* info) NO_THREAD_SAFETY_ANALYSIS {
+ CHECK(IsDataAddress(info));
const void* ptr = info->GetMethod()->GetEntryPointFromQuickCompiledCode();
// We have previously cleared the ProfilingInfo pointer in the ArtMethod in the hope
// that the compiled code would not get revived. As mutator threads run concurrently,
@@ -1238,6 +1457,7 @@
--it;
const void* code_ptr = it->first;
+ CHECK(IsExecutableAddress(code_ptr));
OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr);
if (!method_header->Contains(pc)) {
return nullptr;
@@ -1320,6 +1540,7 @@
// store in the ArtMethod's ProfilingInfo pointer.
QuasiAtomic::ThreadFenceRelease();
+ CHECK(IsDataAddress(info));
method->SetProfilingInfo(info);
profiling_infos_.push_back(info);
histogram_profiling_info_memory_use_.AddValue(profile_info_size);
@@ -1332,7 +1553,8 @@
if (code_mspace_ == mspace) {
size_t result = code_end_;
code_end_ += increment;
- return reinterpret_cast<void*>(result + code_map_->Begin());
+ MemMap* writable_map = GetWritableMemMap();
+ return reinterpret_cast<void*>(result + writable_map->Begin());
} else {
DCHECK_EQ(data_mspace_, mspace);
size_t result = data_end_;
@@ -1484,6 +1706,7 @@
size_t JitCodeCache::GetMemorySizeOfCodePointer(const void* ptr) {
MutexLock mu(Thread::Current(), lock_);
+ CHECK(IsExecutableAddress(ptr));
return mspace_usable_size(reinterpret_cast<const void*>(FromCodeToAllocation(ptr)));
}
@@ -1519,22 +1742,27 @@
size_t header_size = RoundUp(sizeof(OatQuickMethodHeader), alignment);
// Ensure the header ends up at expected instruction alignment.
DCHECK_ALIGNED_PARAM(reinterpret_cast<uintptr_t>(result + header_size), alignment);
+ CHECK(IsWritableAddress(result));
used_memory_for_code_ += mspace_usable_size(result);
return result;
}
-void JitCodeCache::FreeCode(uint8_t* code) {
- used_memory_for_code_ -= mspace_usable_size(code);
- mspace_free(code_mspace_, code);
+void JitCodeCache::FreeRawCode(void* code) {
+ CHECK(IsExecutableAddress(code));
+ void* writable_code = ToWritableAddress(code);
+ used_memory_for_code_ -= mspace_usable_size(writable_code);
+ mspace_free(code_mspace_, writable_code);
}
uint8_t* JitCodeCache::AllocateData(size_t data_size) {
void* result = mspace_malloc(data_mspace_, data_size);
+ CHECK(IsDataAddress(reinterpret_cast<uint8_t*>(result)));
used_memory_for_data_ += mspace_usable_size(result);
return reinterpret_cast<uint8_t*>(result);
}
void JitCodeCache::FreeData(uint8_t* data) {
+ CHECK(IsDataAddress(data));
used_memory_for_data_ -= mspace_usable_size(data);
mspace_free(data_mspace_, data);
}
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index daa1d61..a062ce4 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -229,6 +229,8 @@
REQUIRES(!lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+ uint8_t* GetRootTable(const void* code_ptr, uint32_t* number_of_roots = nullptr);
+
// The GC needs to disallow the reading of inline caches when it processes them,
// to avoid having a class being used while it is being deleted.
void AllowInlineCacheAccess() REQUIRES(!lock_);
@@ -247,9 +249,12 @@
}
private:
+ friend class ScopedCodeCacheWrite;
+
// Take ownership of maps.
JitCodeCache(MemMap* code_map,
MemMap* data_map,
+ MemMap* writable_code_map,
size_t initial_code_capacity,
size_t initial_data_capacity,
size_t max_capacity,
@@ -292,7 +297,7 @@
REQUIRES(!Locks::cha_lock_);
// Free in the mspace allocations for `code_ptr`.
- void FreeCode(const void* code_ptr) REQUIRES(lock_);
+ void FreeCodeAndData(const void* code_ptr) REQUIRES(lock_);
// Number of bytes allocated in the code cache.
size_t CodeCacheSizeLocked() REQUIRES(lock_);
@@ -325,7 +330,7 @@
bool CheckLiveCompiledCodeHasProfilingInfo()
REQUIRES(lock_);
- void FreeCode(uint8_t* code) REQUIRES(lock_);
+ void FreeRawCode(void* code) REQUIRES(lock_);
uint8_t* AllocateCode(size_t code_size) REQUIRES(lock_);
void FreeData(uint8_t* data) REQUIRES(lock_);
uint8_t* AllocateData(size_t data_size) REQUIRES(lock_);
@@ -335,25 +340,58 @@
REQUIRES(!lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+ MemMap* GetWritableMemMap() const {
+ if (writable_code_map_ == nullptr) {
+ // The system required us to map the JIT Code Cache RWX (see
+ // JitCodeCache::Create()).
+ return executable_code_map_.get();
+ } else {
+ // Executable code is mapped RX, and writable code is mapped RW
+ // to the underlying same memory, but at a different address.
+ return writable_code_map_.get();
+ }
+ }
+
+ bool IsDataAddress(const void* raw_addr) const;
+
+ bool IsExecutableAddress(const void* raw_addr) const;
+
+ bool IsWritableAddress(const void* raw_addr) const;
+
+ template <typename T>
+ T* ToExecutableAddress(T* writable_address) const;
+
+ void* ToWritableAddress(const void* executable_address) const;
+
// Lock for guarding allocations, collections, and the method_code_map_.
Mutex lock_;
// Condition to wait on during collection.
ConditionVariable lock_cond_ GUARDED_BY(lock_);
// Whether there is a code cache collection in progress.
bool collection_in_progress_ GUARDED_BY(lock_);
- // Mem map which holds code.
- std::unique_ptr<MemMap> code_map_;
+ // JITting methods obviously requires both write and execute permissions on a region of memory.
+ // In tye typical (non-debugging) case, we separate the memory mapped view that can write the code
+ // from a view that the runtime uses to execute the code. Having these two views eliminates any
+ // single address region having rwx permissions. An attacker could still write the writable
+ // address and then execute the executable address. We allocate the mappings with a random
+ // address relationship to each other which makes the attacker need two addresses rather than
+ // just one. In the debugging case there is no file descriptor to back the
+ // shared memory, and hence we have to use a single mapping.
// Mem map which holds data (stack maps and profiling info).
std::unique_ptr<MemMap> data_map_;
+ // Mem map which holds a non-writable view of code for JIT.
+ std::unique_ptr<MemMap> executable_code_map_;
+ // Mem map which holds a non-executable view of code for JIT.
+ std::unique_ptr<MemMap> writable_code_map_;
// The opaque mspace for allocating code.
void* code_mspace_ GUARDED_BY(lock_);
// The opaque mspace for allocating data.
void* data_mspace_ GUARDED_BY(lock_);
// Bitmap for collecting code and data.
std::unique_ptr<CodeCacheBitmap> live_bitmap_;
- // Holds compiled code associated to the ArtMethod.
+ // Holds non-writable compiled code associated to the ArtMethod.
SafeMap<const void*, ArtMethod*> method_code_map_ GUARDED_BY(lock_);
- // Holds osr compiled code associated to the ArtMethod.
+ // Holds non-writable osr compiled code associated to the ArtMethod.
SafeMap<ArtMethod*, const void*> osr_code_map_ GUARDED_BY(lock_);
// ProfilingInfo objects we have allocated.
std::vector<ProfilingInfo*> profiling_infos_ GUARDED_BY(lock_);
diff --git a/runtime/mem_map.cc b/runtime/mem_map.cc
index 7b41608..17035dd 100644
--- a/runtime/mem_map.cc
+++ b/runtime/mem_map.cc
@@ -536,8 +536,13 @@
}
}
-MemMap* MemMap::RemapAtEnd(uint8_t* new_end, const char* tail_name, int tail_prot,
- std::string* error_msg, bool use_ashmem) {
+MemMap* MemMap::RemapAtEnd(uint8_t* new_end,
+ const char* tail_name,
+ int tail_prot,
+ int sharing_flags,
+ std::string* error_msg,
+ bool use_ashmem,
+ unique_fd* shmem_fd) {
use_ashmem = use_ashmem && !kIsTargetLinux;
DCHECK_GE(new_end, Begin());
DCHECK_LE(new_end, End());
@@ -563,14 +568,14 @@
DCHECK_ALIGNED(tail_base_size, kPageSize);
unique_fd fd;
- int flags = MAP_PRIVATE | MAP_ANONYMOUS;
+ int flags = MAP_ANONYMOUS | sharing_flags;
if (use_ashmem) {
// android_os_Debug.cpp read_mapinfo assumes all ashmem regions associated with the VM are
// prefixed "dalvik-".
std::string debug_friendly_name("dalvik-");
debug_friendly_name += tail_name;
fd.reset(ashmem_create_region(debug_friendly_name.c_str(), tail_base_size));
- flags = MAP_PRIVATE | MAP_FIXED;
+ flags = MAP_FIXED | sharing_flags;
if (fd.get() == -1) {
*error_msg = StringPrintf("ashmem_create_region failed for '%s': %s",
tail_name, strerror(errno));
@@ -604,6 +609,9 @@
fd.get());
return nullptr;
}
+ if (shmem_fd != nullptr) {
+ shmem_fd->reset(fd.release());
+ }
return new MemMap(tail_name, actual, tail_size, actual, tail_base_size, tail_prot, false);
}
diff --git a/runtime/mem_map.h b/runtime/mem_map.h
index 5603963..d8908ad 100644
--- a/runtime/mem_map.h
+++ b/runtime/mem_map.h
@@ -25,6 +25,7 @@
#include <string>
#include "android-base/thread_annotations.h"
+#include "android-base/unique_fd.h"
namespace art {
@@ -37,6 +38,8 @@
#define USE_ART_LOW_4G_ALLOCATOR 0
#endif
+using android::base::unique_fd;
+
#ifdef __linux__
static constexpr bool kMadviseZeroes = true;
#else
@@ -168,11 +171,14 @@
}
// Unmap the pages at end and remap them to create another memory map.
+ // sharing_flags should be either MAP_PRIVATE or MAP_SHARED.
MemMap* RemapAtEnd(uint8_t* new_end,
const char* tail_name,
int tail_prot,
+ int sharing_flags,
std::string* error_msg,
- bool use_ashmem = true);
+ bool use_ashmem = true,
+ unique_fd* shmem_fd = nullptr);
static bool CheckNoGaps(MemMap* begin_map, MemMap* end_map)
REQUIRES(!MemMap::mem_maps_lock_);
diff --git a/runtime/mem_map_test.cc b/runtime/mem_map_test.cc
index 5f027b1..8d6bb38 100644
--- a/runtime/mem_map_test.cc
+++ b/runtime/mem_map_test.cc
@@ -74,6 +74,7 @@
MemMap* m1 = m0->RemapAtEnd(base0 + page_size,
"MemMapTest_RemapAtEndTest_map1",
PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
&error_msg);
// Check the states of the two maps.
EXPECT_EQ(m0->Begin(), base0) << error_msg;
@@ -456,6 +457,7 @@
std::unique_ptr<MemMap> m1(m0->RemapAtEnd(base0 + 3 * page_size,
"MemMapTest_AlignByTest_map1",
PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
&error_msg));
uint8_t* base1 = m1->Begin();
ASSERT_TRUE(base1 != nullptr) << error_msg;
@@ -465,6 +467,7 @@
std::unique_ptr<MemMap> m2(m1->RemapAtEnd(base1 + 4 * page_size,
"MemMapTest_AlignByTest_map2",
PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
&error_msg));
uint8_t* base2 = m2->Begin();
ASSERT_TRUE(base2 != nullptr) << error_msg;
@@ -474,6 +477,7 @@
std::unique_ptr<MemMap> m3(m2->RemapAtEnd(base2 + 3 * page_size,
"MemMapTest_AlignByTest_map1",
PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
&error_msg));
uint8_t* base3 = m3->Begin();
ASSERT_TRUE(base3 != nullptr) << error_msg;
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index dc542d4..e950fca 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -264,212 +264,6 @@
}
}
-template <typename T>
-static void IterateOverJavaDexFile(ObjPtr<mirror::Object> dex_file,
- ArtField* const cookie_field,
- const T& fn)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (dex_file != nullptr) {
- mirror::LongArray* long_array = cookie_field->GetObject(dex_file)->AsLongArray();
- if (long_array == nullptr) {
- // This should never happen so log a warning.
- LOG(WARNING) << "Null DexFile::mCookie";
- return;
- }
- int32_t long_array_size = long_array->GetLength();
- // Start from 1 to skip the oat file.
- for (int32_t j = 1; j < long_array_size; ++j) {
- const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>(
- long_array->GetWithoutChecks(j)));
- if (!fn(cp_dex_file)) {
- return;
- }
- }
- }
-}
-
-template <typename T>
-static void IterateOverPathClassLoader(
- Handle<mirror::ClassLoader> class_loader,
- MutableHandle<mirror::ObjectArray<mirror::Object>> dex_elements,
- const T& fn) REQUIRES_SHARED(Locks::mutator_lock_) {
- // Handle this step.
- // Handle as if this is the child PathClassLoader.
- // The class loader is a PathClassLoader which inherits from BaseDexClassLoader.
- // We need to get the DexPathList and loop through it.
- ArtField* const cookie_field =
- jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
- ArtField* const dex_file_field =
- jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
- ObjPtr<mirror::Object> dex_path_list =
- jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)->
- GetObject(class_loader.Get());
- if (dex_path_list != nullptr && dex_file_field != nullptr && cookie_field != nullptr) {
- // DexPathList has an array dexElements of Elements[] which each contain a dex file.
- ObjPtr<mirror::Object> dex_elements_obj =
- jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
- GetObject(dex_path_list);
- // 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) {
- dex_elements.Assign(dex_elements_obj->AsObjectArray<mirror::Object>());
- for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
- mirror::Object* element = dex_elements->GetWithoutChecks(i);
- if (element == nullptr) {
- // Should never happen, fall back to java code to throw a NPE.
- break;
- }
- ObjPtr<mirror::Object> dex_file = dex_file_field->GetObject(element);
- IterateOverJavaDexFile(dex_file, cookie_field, fn);
- }
- }
- }
-}
-
-static bool GetDexFilesFromClassLoader(
- ScopedObjectAccessAlreadyRunnable& soa,
- mirror::ClassLoader* class_loader,
- std::vector<const DexFile*>* dex_files)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (ClassLinker::IsBootClassLoader(soa, class_loader)) {
- // The boot class loader. We don't load any of these files, as we know we compiled against
- // them correctly.
- return true;
- }
-
- // Unsupported class-loader?
- if (soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader) !=
- class_loader->GetClass()) {
- VLOG(class_linker) << "Unsupported class-loader "
- << mirror::Class::PrettyClass(class_loader->GetClass());
- return false;
- }
-
- bool recursive_result = GetDexFilesFromClassLoader(soa, class_loader->GetParent(), dex_files);
- if (!recursive_result) {
- // Something wrong up the chain.
- return false;
- }
-
- // Collect all the dex files.
- auto GetDexFilesFn = [&] (const DexFile* cp_dex_file)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (cp_dex_file->NumClassDefs() > 0) {
- dex_files->push_back(cp_dex_file);
- }
- return true; // Continue looking.
- };
-
- // Handle for dex-cache-element.
- StackHandleScope<3> hs(soa.Self());
- MutableHandle<mirror::ObjectArray<mirror::Object>> dex_elements(
- hs.NewHandle<mirror::ObjectArray<mirror::Object>>(nullptr));
- Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(class_loader));
-
- IterateOverPathClassLoader(h_class_loader, dex_elements, GetDexFilesFn);
-
- return true;
-}
-
-static void GetDexFilesFromDexElementsArray(
- ScopedObjectAccessAlreadyRunnable& soa,
- Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
- std::vector<const DexFile*>* dex_files)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (dex_elements == nullptr) {
- // Nothing to do.
- return;
- }
-
- ArtField* const cookie_field =
- jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
- ArtField* const dex_file_field =
- jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
- ObjPtr<mirror::Class> const element_class = soa.Decode<mirror::Class>(
- WellKnownClasses::dalvik_system_DexPathList__Element);
- ObjPtr<mirror::Class> const dexfile_class = soa.Decode<mirror::Class>(
- WellKnownClasses::dalvik_system_DexFile);
-
- // Collect all the dex files.
- auto GetDexFilesFn = [&] (const DexFile* cp_dex_file)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (cp_dex_file != nullptr && cp_dex_file->NumClassDefs() > 0) {
- dex_files->push_back(cp_dex_file);
- }
- return true; // Continue looking.
- };
-
- for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
- mirror::Object* element = dex_elements->GetWithoutChecks(i);
- if (element == nullptr) {
- continue;
- }
-
- // We support this being dalvik.system.DexPathList$Element and dalvik.system.DexFile.
-
- ObjPtr<mirror::Object> dex_file;
- if (element_class == element->GetClass()) {
- dex_file = dex_file_field->GetObject(element);
- } else if (dexfile_class == element->GetClass()) {
- dex_file = element;
- } else {
- LOG(WARNING) << "Unsupported element in dex_elements: "
- << mirror::Class::PrettyClass(element->GetClass());
- continue;
- }
-
- IterateOverJavaDexFile(dex_file, cookie_field, GetDexFilesFn);
- }
-}
-
-static bool AreSharedLibrariesOk(const std::string& context_spec,
- std::vector<const DexFile*>& dex_files,
- std::string* error_msg) {
- std::vector<std::string> classpath;
- std::vector<uint32_t> checksums;
- bool is_special_shared_library;
- if (!ClassLoaderContext::DecodePathClassLoaderContextFromOatFileKey(
- context_spec, &classpath, &checksums, &is_special_shared_library)) {
- *error_msg = "Could not decode the class loader context from the oat file key.";
- return false;
- }
-
- DCHECK_EQ(classpath.size(), checksums.size());
-
- // The classpath size should match the number of dex files.
- if (classpath.size() != dex_files.size()) {
- *error_msg = "The number of loaded dex files does not match the number of files "
- "specified in the context. Expected=" + std::to_string(classpath.size()) +
- ", found=" + std::to_string(dex_files.size());
- return false;
- }
-
- // If we find the special shared library, skip the shared libraries check.
- if (is_special_shared_library) {
- return true;
- }
-
- // Check that the loaded dex files have the same order and checksums as the shared libraries.
- for (size_t i = 0; i < dex_files.size(); ++i) {
- const std::string& dex_location = dex_files[i]->GetLocation();
- uint32_t dex_location_checksum = dex_files[i]->GetLocationChecksum();
- std::string absolute_library_path =
- OatFile::ResolveRelativeEncodedDexLocation(dex_location.c_str(), classpath[i]);
- if (dex_location != absolute_library_path) {
- *error_msg = "SharedLibraryCheck: expected=" + absolute_library_path + ", found=" +
- dex_location;
- return false;
- }
- if (dex_location_checksum != checksums[i]) {
- *error_msg = "SharedLibraryCheck: checksum mismatch for " + dex_location + ". Expected=" +
- std::to_string(checksums[i]) + ", found=" + std::to_string(dex_location_checksum);
- return false;
- }
- }
-
- return true;
-}
-
static bool CollisionCheck(std::vector<const DexFile*>& dex_files_loaded,
std::vector<const DexFile*>& dex_files_unloaded,
std::string* error_msg /*out*/) {
@@ -554,52 +348,38 @@
DCHECK(oat_file != nullptr);
DCHECK(error_msg != nullptr);
- std::vector<const DexFile*> dex_files_loaded;
-
- // Try to get dex files from the given class loader. If the class loader is null, or we do
- // not support one of the class loaders in the chain, we do nothing and assume the collision
- // check has succeeded.
- bool class_loader_ok = false;
- {
- ScopedObjectAccess soa(Thread::Current());
- StackHandleScope<2> hs(Thread::Current());
- Handle<mirror::ClassLoader> h_class_loader =
- hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
- Handle<mirror::ObjectArray<mirror::Object>> h_dex_elements =
- hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(dex_elements));
- if (h_class_loader != nullptr &&
- GetDexFilesFromClassLoader(soa, h_class_loader.Get(), &dex_files_loaded)) {
- class_loader_ok = true;
-
- // In this case, also take into account the dex_elements array, if given. We don't need to
- // read it otherwise, as we'll compare against all open oat files anyways.
- GetDexFilesFromDexElementsArray(soa, h_dex_elements, &dex_files_loaded);
- } else if (h_class_loader != nullptr) {
- VLOG(class_linker) << "Something unsupported with "
- << mirror::Class::PrettyClass(h_class_loader->GetClass());
-
- // This is a class loader we don't recognize. Our earlier strategy would
- // be to perform a global duplicate class check (with all loaded oat files)
- // but that seems overly conservative - we have no way of knowing that
- // those files are present in the same loader hierarchy. Among other
- // things, it hurt GMS core and its filtering class loader.
- }
+ // If the class_loader is null there's not much we can do. This happens if a dex files is loaded
+ // directly with DexFile APIs instead of using class loaders.
+ if (class_loader == nullptr) {
+ LOG(WARNING) << "Opening an oat file without a class loader. "
+ << "Are you using the deprecated DexFile APIs?";
+ return false;
}
- // Exit if we find a class loader we don't recognize. Proceed to check shared
- // libraries and do a full class loader check otherwise.
- if (!class_loader_ok) {
- LOG(WARNING) << "Skipping duplicate class check due to unrecognized classloader";
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);
+
+ // The context might be null if there are unrecognized class loaders in the chain or they
+ // don't meet sensible sanity conditions. In this case we assume that the app knows what it's
+ // doing and accept the oat file.
+ // Note that this has correctness implications as we cannot guarantee that the class resolution
+ // used during compilation is OK (b/37777332).
+ if (context == nullptr) {
+ LOG(WARNING) << "Skipping duplicate class check due to unsupported classloader";
return false;
}
- // Exit if shared libraries are ok. Do a full duplicate classes check otherwise.
- const std::string
- shared_libraries(oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
- if (AreSharedLibrariesOk(shared_libraries, dex_files_loaded, error_msg)) {
+ // If the pat file loading context matches the context used during compilation then we accept
+ // the oat file without addition checks
+ if (context->VerifyClassLoaderContextMatch(
+ oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey))) {
return false;
}
+ // The class loader context does not match. Perform a full duplicate classes check.
+
+ std::vector<const DexFile*> dex_files_loaded = context->FlattenOpenedDexFiles();
+
// Vector that holds the newly opened dex files live, this is done to prevent leaks.
std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 68e1856..2c64b56 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -50,6 +50,7 @@
import json
import multiprocessing
import os
+import operator
import re
import subprocess
import sys
@@ -75,9 +76,11 @@
OPTIMIZING_COMPILER_TYPES = set()
JVMTI_TYPES = set()
ADDRESS_SIZES_TARGET = {'host': set(), 'target': set()}
+TIME_STATS = {}
# timeout for individual tests.
# TODO: make it adjustable per tests and for buildbots
timeout = 3000 # 50 minutes
+global_timeout = 14100 # 235 minutes (The go/ab timeout is 14500)
# DISABLED_TEST_CONTAINER holds information about the disabled tests. It is a map
# that has key as the test name (like 001-HelloWorld), and value as set of
@@ -355,7 +358,7 @@
# stops creating any any thread and wait for all the exising threads
# to end.
while threading.active_count() > 2:
- time.sleep(0.1)
+ time.sleep(1)
return
test_name = 'test-art-'
test_name += target + '-run-test-'
@@ -506,11 +509,13 @@
test_skipped = True
else:
test_skipped = False
+ start_recording_time(test_name)
if gdb:
proc = subprocess.Popen(command.split(), stderr=subprocess.STDOUT, universal_newlines=True)
else:
proc = subprocess.Popen(command.split(), stderr=subprocess.STDOUT, stdout = subprocess.PIPE,
universal_newlines=True)
+ stop_recording_time(test_name)
script_output = proc.communicate(timeout=timeout)[0]
test_passed = not proc.wait()
@@ -729,6 +734,7 @@
sys.stdout.flush()
def print_analysis():
+ print_mutex.acquire()
if not verbose:
# Without --verbose, the testrunner erases passing test info. It
# does that by overriding the printed text with white spaces all across
@@ -762,6 +768,7 @@
print_text(COLOR_ERROR + '----------' + COLOR_NORMAL + '\n')
for failed_test in sorted([test_info[0] for test_info in failed_tests]):
print_text(('%s\n' % (failed_test)))
+ print_mutex.release()
def parse_test_name(test_name):
@@ -990,7 +997,33 @@
return test
+def start_recording_time(key):
+ """To begin recording time for the event associated with the key.
+ """
+ TIME_STATS[key] = -(time.time())
+
+def stop_recording_time(key):
+ """To stop timer for the event associated with the key.
+ """
+ TIME_STATS[key] = time.time() + TIME_STATS[key]
+
+def print_time_info():
+ """Print time information for different invocation.
+ """
+ print_mutex.acquire()
+ print_text('\nTIME INFO\n')
+ for key in TIME_STATS:
+ # Handle unfinised jobs.
+ if TIME_STATS[key] < 0:
+ TIME_STATS[key] = time.time() + TIME_STATS[key]
+
+ info_list = sorted(TIME_STATS.items(), key=operator.itemgetter(1), reverse=True)
+ for time_info_tuple in info_list:
+ print_text('%s : %.2f sec\n' % (time_info_tuple[0], time_info_tuple[1]))
+ print_mutex.release()
+
def main():
+ start_time = time.time()
gather_test_info()
user_requested_test = parse_option()
setup_test_env()
@@ -1006,8 +1039,10 @@
build_command += ' ' + build_targets
# Add 'dist' to avoid Jack issues b/36169180.
build_command += ' dist'
+ start_recording_time(build_command)
if subprocess.call(build_command.split()):
sys.exit(1)
+ stop_recording_time(build_command)
if user_requested_test:
test_runner_thread = threading.Thread(target=run_tests, args=(user_requested_test,))
else:
@@ -1016,6 +1051,13 @@
try:
test_runner_thread.start()
while threading.active_count() > 1:
+ if (time.time() - start_time > global_timeout):
+ # to ensure that the run ends before the go/ab bots
+ # time out the invocation.
+ print_text("FAILED: timeout reached")
+ print_time_info()
+ print_analysis()
+ sys.exit(1)
time.sleep(0.1)
print_analysis()
except Exception as e:
diff --git a/tools/dexfuzz/src/dexfuzz/program/mutators/InvokeChanger.java b/tools/dexfuzz/src/dexfuzz/program/mutators/InvokeChanger.java
index 3488503..8750fc6 100644
--- a/tools/dexfuzz/src/dexfuzz/program/mutators/InvokeChanger.java
+++ b/tools/dexfuzz/src/dexfuzz/program/mutators/InvokeChanger.java
@@ -136,7 +136,7 @@
String oldInsnString = invokeInsn.toString();
- Opcode newOpcode = isInvokeCalIInst(invokeInsn);
+ Opcode newOpcode = getDifferentInvokeCallOpcode(invokeInsn);
invokeInsn.insn.info = Instruction.getOpcodeInfo(newOpcode);
@@ -148,7 +148,7 @@
invokeCallInsns = null;
}
- private Opcode isInvokeCalIInst(MInsn mInsn) {
+ private Opcode getDifferentInvokeCallOpcode(MInsn mInsn) {
Opcode opcode = mInsn.insn.info.opcode;
if (isSimpleInvokeInst(opcode)) {
int index = opcode.ordinal() - Opcode.INVOKE_VIRTUAL.ordinal();
@@ -159,7 +159,7 @@
int length = INVOKE_RANGE_LIST.length;
return INVOKE_RANGE_LIST[(index + 1 + rng.nextInt(length - 1)) % length];
}
- return opcode;
+ return opcode;
}
private boolean isSimpleInvokeInst(Opcode opcode){