Add support for shared libaries in class loader context.
For example:
PCL[a.dex:b.dex]{PCL[s1.dex]#PCL[s2.dex:s3.dex]};DLC[c.dex:d.dex]{DLC[s4.dex]}
Rewrite the class_loader_chain_ to be a chain instead of a vector,
to simplify processing and encoding of contexts.
bug: 111174995
Test: class_loader_context_test, test.py
Change-Id: I7c9f71b67a91b43ae534721b43dc4fdb8e0b6ec4
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index dd10f3c..de9fe22 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -42,6 +42,9 @@
static constexpr char kDelegateLastClassLoaderString[] = "DLC";
static constexpr char kClassLoaderOpeningMark = '[';
static constexpr char kClassLoaderClosingMark = ']';
+static constexpr char kClassLoaderSharedLibraryOpeningMark = '{';
+static constexpr char kClassLoaderSharedLibraryClosingMark = '}';
+static constexpr char kClassLoaderSharedLibrarySeparator = '#';
static constexpr char kClassLoaderSeparator = ';';
static constexpr char kClasspathSeparator = ':';
static constexpr char kDexFileChecksumSeparator = '*';
@@ -58,17 +61,35 @@
dex_files_open_result_(true),
owns_the_dex_files_(owns_the_dex_files) {}
+// Utility method to add parent and shared libraries of `info` into
+// the `work_list`.
+static void AddToWorkList(
+ ClassLoaderContext::ClassLoaderInfo* info,
+ std::vector<ClassLoaderContext::ClassLoaderInfo*>& work_list) {
+ if (info->parent != nullptr) {
+ work_list.push_back(info->parent.get());
+ }
+ for (size_t i = 0; i < info->shared_libraries.size(); ++i) {
+ work_list.push_back(info->shared_libraries[i].get());
+ }
+}
+
ClassLoaderContext::~ClassLoaderContext() {
- if (!owns_the_dex_files_) {
+ if (!owns_the_dex_files_ && class_loader_chain_ != nullptr) {
// 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) {
+ std::vector<ClassLoaderInfo*> work_list;
+ work_list.push_back(class_loader_chain_.get());
+ while (!work_list.empty()) {
+ ClassLoaderInfo* info = work_list.back();
+ work_list.pop_back();
+ for (std::unique_ptr<OatFile>& oat_file : info->opened_oat_files) {
oat_file.release(); // NOLINT b/117926937
}
- for (std::unique_ptr<const DexFile>& dex_file : info.opened_dex_files) {
+ for (std::unique_ptr<const DexFile>& dex_file : info->opened_dex_files) {
dex_file.release(); // NOLINT b/117926937
}
+ AddToWorkList(info, work_list);
}
}
}
@@ -86,11 +107,16 @@
}
}
-// The expected format is: "ClassLoaderType1[ClasspathElem1*Checksum1:ClasspathElem2*Checksum2...]".
+// The expected format is:
+// "ClassLoaderType1[ClasspathElem1*Checksum1:ClasspathElem2*Checksum2...]{ClassLoaderType2[...]}".
// The checksum part of the format is expected only if parse_cheksums is true.
-bool ClassLoaderContext::ParseClassLoaderSpec(const std::string& class_loader_spec,
- ClassLoaderType class_loader_type,
- bool parse_checksums) {
+std::unique_ptr<ClassLoaderContext::ClassLoaderInfo> ClassLoaderContext::ParseClassLoaderSpec(
+ const std::string& class_loader_spec,
+ bool parse_checksums) {
+ ClassLoaderType class_loader_type = ExtractClassLoaderType(class_loader_spec);
+ if (class_loader_type == kInvalidClassLoader) {
+ return nullptr;
+ }
const char* class_loader_type_str = GetClassLoaderTypeName(class_loader_type);
size_t type_str_size = strlen(class_loader_type_str);
@@ -98,21 +124,24 @@
// Check the opening and closing markers.
if (class_loader_spec[type_str_size] != kClassLoaderOpeningMark) {
- return false;
+ return nullptr;
}
- if (class_loader_spec[class_loader_spec.length() - 1] != kClassLoaderClosingMark) {
- return false;
+ if ((class_loader_spec[class_loader_spec.length() - 1] != kClassLoaderClosingMark) &&
+ (class_loader_spec[class_loader_spec.length() - 1] != kClassLoaderSharedLibraryClosingMark)) {
+ return nullptr;
}
+ size_t closing_index = class_loader_spec.find_first_of(kClassLoaderClosingMark);
+
// At this point we know the format is ok; continue and extract the classpath.
// Note that class loaders with an empty class path are allowed.
std::string classpath = class_loader_spec.substr(type_str_size + 1,
- class_loader_spec.length() - type_str_size - 2);
+ closing_index - type_str_size - 1);
- class_loader_chain_.push_back(ClassLoaderInfo(class_loader_type));
+ std::unique_ptr<ClassLoaderInfo> info(new ClassLoaderInfo(class_loader_type));
if (!parse_checksums) {
- Split(classpath, kClasspathSeparator, &class_loader_chain_.back().classpath);
+ Split(classpath, kClasspathSeparator, &info->classpath);
} else {
std::vector<std::string> classpath_elements;
Split(classpath, kClasspathSeparator, &classpath_elements);
@@ -120,18 +149,37 @@
std::vector<std::string> dex_file_with_checksum;
Split(element, kDexFileChecksumSeparator, &dex_file_with_checksum);
if (dex_file_with_checksum.size() != 2) {
- return false;
+ return nullptr;
}
uint32_t checksum = 0;
if (!android::base::ParseUint(dex_file_with_checksum[1].c_str(), &checksum)) {
- return false;
+ return nullptr;
}
- class_loader_chain_.back().classpath.push_back(dex_file_with_checksum[0]);
- class_loader_chain_.back().checksums.push_back(checksum);
+ info->classpath.push_back(dex_file_with_checksum[0]);
+ info->checksums.push_back(checksum);
}
}
- return true;
+ if (class_loader_spec[class_loader_spec.length() - 1] == kClassLoaderSharedLibraryClosingMark) {
+ size_t start_index = class_loader_spec.find_first_of(kClassLoaderSharedLibraryOpeningMark);
+ if (start_index == std::string::npos) {
+ return nullptr;
+ }
+ std::string shared_libraries_spec =
+ class_loader_spec.substr(start_index + 1, class_loader_spec.length() - start_index - 2);
+ std::vector<std::string> shared_libraries;
+ Split(shared_libraries_spec, kClassLoaderSharedLibrarySeparator, &shared_libraries);
+ for (const std::string& shared_library_spec : shared_libraries) {
+ std::unique_ptr<ClassLoaderInfo> shared_library(
+ ParseInternal(shared_library_spec, parse_checksums));
+ if (shared_library == nullptr) {
+ return nullptr;
+ }
+ info->shared_libraries.push_back(std::move(shared_library));
+ }
+ }
+
+ return info;
}
// Extracts the class loader type from the given spec.
@@ -157,7 +205,7 @@
// By default we load the dex files in a PathClassLoader.
// So an empty spec is equivalent to an empty PathClassLoader (this happens when running
// tests)
- class_loader_chain_.push_back(ClassLoaderInfo(kPathClassLoader));
+ class_loader_chain_.reset(new ClassLoaderInfo(kPathClassLoader));
return true;
}
@@ -169,21 +217,102 @@
return true;
}
- std::vector<std::string> class_loaders;
- Split(spec, kClassLoaderSeparator, &class_loaders);
+ CHECK(class_loader_chain_ == nullptr);
+ class_loader_chain_.reset(ParseInternal(spec, parse_checksums));
+ return class_loader_chain_ != nullptr;
+}
- for (const std::string& class_loader : class_loaders) {
- ClassLoaderType type = ExtractClassLoaderType(class_loader);
- if (type == kInvalidClassLoader) {
- LOG(ERROR) << "Invalid class loader type: " << class_loader;
- return false;
+ClassLoaderContext::ClassLoaderInfo* ClassLoaderContext::ParseInternal(
+ const std::string& spec, bool parse_checksums) {
+ CHECK(!spec.empty());
+ CHECK_NE(spec, OatFile::kSpecialSharedLibrary);
+ std::string remaining = spec;
+ std::unique_ptr<ClassLoaderInfo> first(nullptr);
+ ClassLoaderInfo* previous_iteration = nullptr;
+ while (!remaining.empty()) {
+ std::string class_loader_spec;
+ size_t first_class_loader_separator = remaining.find_first_of(kClassLoaderSeparator);
+ size_t first_shared_library_open =
+ remaining.find_first_of(kClassLoaderSharedLibraryOpeningMark);
+ if (first_class_loader_separator == std::string::npos) {
+ // Only one class loader, for example:
+ // PCL[...]
+ class_loader_spec = remaining;
+ remaining = "";
+ } else if ((first_shared_library_open == std::string::npos) ||
+ (first_shared_library_open > first_class_loader_separator)) {
+ // We found a class loader spec without shared libraries, for example:
+ // PCL[...];PCL[...]{...}
+ class_loader_spec = remaining.substr(0, first_class_loader_separator);
+ remaining = remaining.substr(first_class_loader_separator + 1,
+ remaining.size() - first_class_loader_separator - 1);
+ } else {
+ // The class loader spec contains shared libraries. Find the matching closing
+ // shared library marker for it.
+
+ // Counter of opened shared library marker we've encountered so far.
+ uint32_t counter = 1;
+ // The index at which we're operating in the loop.
+ uint32_t string_index = first_shared_library_open + 1;
+ while (counter != 0) {
+ size_t shared_library_close =
+ remaining.find_first_of(kClassLoaderSharedLibraryClosingMark, string_index);
+ size_t shared_library_open =
+ remaining.find_first_of(kClassLoaderSharedLibraryOpeningMark, string_index);
+ if (shared_library_close == std::string::npos) {
+ // No matching closing market. Return an error.
+ LOG(ERROR) << "Invalid class loader spec: " << class_loader_spec;
+ return nullptr;
+ }
+
+ if ((shared_library_open == std::string::npos) ||
+ (shared_library_close < shared_library_open)) {
+ // We have seen a closing marker. Decrement the counter.
+ --counter;
+ if (counter == 0) {
+ // Found the matching closing marker.
+ class_loader_spec = remaining.substr(0, shared_library_close + 1);
+
+ // Compute the remaining string to analyze.
+ if (remaining.size() == shared_library_close + 1) {
+ remaining = "";
+ } else if ((remaining.size() == shared_library_close + 2) ||
+ (remaining.at(shared_library_close + 1) != kClassLoaderSeparator)) {
+ LOG(ERROR) << "Invalid class loader spec: " << class_loader_spec;
+ return nullptr;
+ } else {
+ remaining = remaining.substr(shared_library_close + 2,
+ remaining.size() - shared_library_close - 2);
+ }
+ } else {
+ // Move the search index forward.
+ string_index = shared_library_close + 1;
+ }
+ } else {
+ // New nested opening marker. Increment the counter and move the search
+ // index after the marker.
+ ++counter;
+ string_index = shared_library_open + 1;
+ }
+ }
}
- if (!ParseClassLoaderSpec(class_loader, type, parse_checksums)) {
- LOG(ERROR) << "Invalid class loader spec: " << class_loader;
- return false;
+
+ std::unique_ptr<ClassLoaderInfo> info =
+ ParseClassLoaderSpec(class_loader_spec, parse_checksums);
+ if (info == nullptr) {
+ LOG(ERROR) << "Invalid class loader spec: " << class_loader_spec;
+ return nullptr;
+ }
+ if (first == nullptr) {
+ first.reset(info.release());
+ previous_iteration = first.get();
+ } else {
+ CHECK(previous_iteration != nullptr);
+ previous_iteration->parent.reset(info.release());
+ previous_iteration = previous_iteration->parent.get();
}
}
- return true;
+ return first.release();
}
// Opens requested class path files and appends them to opened_dex_files. If the dex files have
@@ -208,9 +337,13 @@
// TODO(calin): Refine the dex opening interface to be able to tell if an archive contains
// no dex files. So that we can distinguish the real failures...
const ArtDexFileLoader dex_file_loader;
- for (ClassLoaderInfo& info : class_loader_chain_) {
- size_t opened_dex_files_index = info.opened_dex_files.size();
- for (const std::string& cp_elem : info.classpath) {
+ std::vector<ClassLoaderInfo*> work_list;
+ work_list.push_back(class_loader_chain_.get());
+ while (!work_list.empty()) {
+ ClassLoaderInfo* info = work_list.back();
+ work_list.pop_back();
+ size_t opened_dex_files_index = info->opened_dex_files.size();
+ for (const std::string& cp_elem : info->classpath) {
// If path is relative, append it to the provided base directory.
std::string location = cp_elem;
if (location[0] != '/' && !classpath_dir.empty()) {
@@ -225,7 +358,7 @@
Runtime::Current()->IsVerificationEnabled(),
/*verify_checksum=*/ true,
&error_msg,
- &info.opened_dex_files)) {
+ &info->opened_dex_files)) {
// If we fail to open the dex file because it's been stripped, try to open the dex file
// from its corresponding oat file.
// This could happen when we need to recompile a pre-build whose dex code has been stripped.
@@ -237,10 +370,10 @@
std::vector<std::unique_ptr<const DexFile>> oat_dex_files;
if (oat_file != nullptr &&
OatFileAssistant::LoadDexFiles(*oat_file, location, &oat_dex_files)) {
- info.opened_oat_files.push_back(std::move(oat_file));
- info.opened_dex_files.insert(info.opened_dex_files.end(),
- std::make_move_iterator(oat_dex_files.begin()),
- std::make_move_iterator(oat_dex_files.end()));
+ info->opened_oat_files.push_back(std::move(oat_file));
+ info->opened_dex_files.insert(info->opened_dex_files.end(),
+ std::make_move_iterator(oat_dex_files.begin()),
+ std::make_move_iterator(oat_dex_files.end()));
} else {
LOG(WARNING) << "Could not open dex files from location: " << location;
dex_files_open_result_ = false;
@@ -257,14 +390,15 @@
// This will allow the context to VerifyClassLoaderContextMatch which expects or multidex
// location in the class paths.
// Note that this will also remove the paths that could not be opened.
- info.original_classpath = std::move(info.classpath);
- info.classpath.clear();
- info.checksums.clear();
- for (size_t k = opened_dex_files_index; k < info.opened_dex_files.size(); k++) {
- std::unique_ptr<const DexFile>& dex = info.opened_dex_files[k];
- info.classpath.push_back(dex->GetLocation());
- info.checksums.push_back(dex->GetLocationChecksum());
+ info->original_classpath = std::move(info->classpath);
+ info->classpath.clear();
+ info->checksums.clear();
+ for (size_t k = opened_dex_files_index; k < info->opened_dex_files.size(); k++) {
+ std::unique_ptr<const DexFile>& dex = info->opened_dex_files[k];
+ info->classpath.push_back(dex->GetLocation());
+ info->checksums.push_back(dex->GetLocationChecksum());
}
+ AddToWorkList(info, work_list);
}
return dex_files_open_result_;
@@ -275,24 +409,33 @@
CHECK(!dex_files_open_attempted_)
<< "RemoveLocationsFromClasspaths cannot be call after OpenDexFiles";
+ if (class_loader_chain_ == nullptr) {
+ return false;
+ }
+
std::set<std::string> canonical_locations;
for (const std::string& location : locations) {
canonical_locations.insert(DexFileLoader::GetDexCanonicalLocation(location.c_str()));
}
bool removed_locations = false;
- for (ClassLoaderInfo& info : class_loader_chain_) {
- size_t initial_size = info.classpath.size();
+ std::vector<ClassLoaderInfo*> work_list;
+ work_list.push_back(class_loader_chain_.get());
+ while (!work_list.empty()) {
+ ClassLoaderInfo* info = work_list.back();
+ work_list.pop_back();
+ size_t initial_size = info->classpath.size();
auto kept_it = std::remove_if(
- info.classpath.begin(),
- info.classpath.end(),
+ info->classpath.begin(),
+ info->classpath.end(),
[canonical_locations](const std::string& location) {
return ContainsElement(canonical_locations,
DexFileLoader::GetDexCanonicalLocation(location.c_str()));
});
- info.classpath.erase(kept_it, info.classpath.end());
- if (initial_size != info.classpath.size()) {
+ info->classpath.erase(kept_it, info->classpath.end());
+ if (initial_size != info->classpath.size()) {
removed_locations = true;
}
+ AddToWorkList(info, work_list);
}
return removed_locations;
}
@@ -315,11 +458,11 @@
}
if (stored_context != nullptr) {
- DCHECK_EQ(class_loader_chain_.size(), stored_context->class_loader_chain_.size());
+ DCHECK_EQ(GetParentChainSize(), stored_context->GetParentChainSize());
}
std::ostringstream out;
- if (class_loader_chain_.empty()) {
+ if (class_loader_chain_ == nullptr) {
// We can get in this situation if the context was created with a class path containing the
// source dex files which were later removed (happens during run-tests).
out << GetClassLoaderTypeName(kPathClassLoader)
@@ -328,62 +471,122 @@
return out.str();
}
- for (size_t i = 0; i < class_loader_chain_.size(); i++) {
- const ClassLoaderInfo& info = class_loader_chain_[i];
- if (i > 0) {
- out << kClassLoaderSeparator;
- }
- out << GetClassLoaderTypeName(info.type);
- out << kClassLoaderOpeningMark;
- std::set<std::string> seen_locations;
- SafeMap<std::string, std::string> remap;
- if (stored_context != nullptr) {
- DCHECK_EQ(info.original_classpath.size(),
- stored_context->class_loader_chain_[i].classpath.size());
- for (size_t k = 0; k < info.original_classpath.size(); ++k) {
- // Note that we don't care if the same name appears twice.
- remap.Put(info.original_classpath[k], stored_context->class_loader_chain_[i].classpath[k]);
- }
- }
- for (size_t k = 0; k < info.opened_dex_files.size(); k++) {
- const std::unique_ptr<const DexFile>& dex_file = info.opened_dex_files[k];
- if (for_dex2oat) {
- // dex2oat only needs the base location. It cannot accept multidex locations.
- // So ensure we only add each file once.
- bool new_insert = seen_locations.insert(
- DexFileLoader::GetBaseLocation(dex_file->GetLocation())).second;
- if (!new_insert) {
- continue;
- }
- }
- std::string location = dex_file->GetLocation();
- // If there is a stored class loader remap, fix up the multidex strings.
- if (!remap.empty()) {
- std::string base_dex_location = DexFileLoader::GetBaseLocation(location);
- auto it = remap.find(base_dex_location);
- CHECK(it != remap.end()) << base_dex_location;
- location = it->second + DexFileLoader::GetMultiDexSuffix(location);
- }
- if (k > 0) {
- out << kClasspathSeparator;
- }
- // Find paths that were relative and convert them back from absolute.
- if (!base_dir.empty() && location.substr(0, base_dir.length()) == base_dir) {
- out << location.substr(base_dir.length() + 1).c_str();
- } else {
- out << location.c_str();
- }
- // dex2oat does not need the checksums.
- if (!for_dex2oat) {
- out << kDexFileChecksumSeparator;
- out << dex_file->GetLocationChecksum();
- }
- }
- out << kClassLoaderClosingMark;
- }
+ EncodeContextInternal(
+ *class_loader_chain_,
+ base_dir,
+ for_dex2oat,
+ (stored_context == nullptr ? nullptr : stored_context->class_loader_chain_.get()),
+ out);
return out.str();
}
+void ClassLoaderContext::EncodeContextInternal(const ClassLoaderInfo& info,
+ const std::string& base_dir,
+ bool for_dex2oat,
+ ClassLoaderInfo* stored_info,
+ std::ostringstream& out) const {
+ out << GetClassLoaderTypeName(info.type);
+ out << kClassLoaderOpeningMark;
+ std::set<std::string> seen_locations;
+ SafeMap<std::string, std::string> remap;
+ if (stored_info != nullptr) {
+ for (size_t k = 0; k < info.original_classpath.size(); ++k) {
+ // Note that we don't care if the same name appears twice.
+ remap.Put(info.original_classpath[k], stored_info->classpath[k]);
+ }
+ }
+ for (size_t k = 0; k < info.opened_dex_files.size(); k++) {
+ const std::unique_ptr<const DexFile>& dex_file = info.opened_dex_files[k];
+ if (for_dex2oat) {
+ // dex2oat only needs the base location. It cannot accept multidex locations.
+ // So ensure we only add each file once.
+ bool new_insert = seen_locations.insert(
+ DexFileLoader::GetBaseLocation(dex_file->GetLocation())).second;
+ if (!new_insert) {
+ continue;
+ }
+ }
+ std::string location = dex_file->GetLocation();
+ // If there is a stored class loader remap, fix up the multidex strings.
+ if (!remap.empty()) {
+ std::string base_dex_location = DexFileLoader::GetBaseLocation(location);
+ auto it = remap.find(base_dex_location);
+ CHECK(it != remap.end()) << base_dex_location;
+ location = it->second + DexFileLoader::GetMultiDexSuffix(location);
+ }
+ if (k > 0) {
+ out << kClasspathSeparator;
+ }
+ // Find paths that were relative and convert them back from absolute.
+ if (!base_dir.empty() && location.substr(0, base_dir.length()) == base_dir) {
+ out << location.substr(base_dir.length() + 1).c_str();
+ } else {
+ out << location.c_str();
+ }
+ // dex2oat does not need the checksums.
+ if (!for_dex2oat) {
+ out << kDexFileChecksumSeparator;
+ out << dex_file->GetLocationChecksum();
+ }
+ }
+ out << kClassLoaderClosingMark;
+
+ if (!info.shared_libraries.empty()) {
+ out << kClassLoaderSharedLibraryOpeningMark;
+ for (uint32_t i = 0; i < info.shared_libraries.size(); ++i) {
+ if (i > 0) {
+ out << kClassLoaderSharedLibrarySeparator;
+ }
+ EncodeContextInternal(
+ *info.shared_libraries[i].get(),
+ base_dir,
+ for_dex2oat,
+ (stored_info == nullptr ? nullptr : stored_info->shared_libraries[i].get()),
+ out);
+ }
+ out << kClassLoaderSharedLibraryClosingMark;
+ }
+ if (info.parent != nullptr) {
+ out << kClassLoaderSeparator;
+ EncodeContextInternal(
+ *info.parent.get(),
+ base_dir,
+ for_dex2oat,
+ (stored_info == nullptr ? nullptr : stored_info->parent.get()),
+ out);
+ }
+}
+
+// Returns the WellKnownClass for the given class loader type.
+static jclass GetClassLoaderClass(ClassLoaderContext::ClassLoaderType type) {
+ switch (type) {
+ case ClassLoaderContext::kPathClassLoader:
+ return WellKnownClasses::dalvik_system_PathClassLoader;
+ case ClassLoaderContext::kDelegateLastClassLoader:
+ return WellKnownClasses::dalvik_system_DelegateLastClassLoader;
+ case ClassLoaderContext::kInvalidClassLoader: break; // will fail after the switch.
+ }
+ LOG(FATAL) << "Invalid class loader type " << type;
+ UNREACHABLE();
+}
+
+static jobject CreateClassLoaderInternal(Thread* self,
+ const ClassLoaderContext::ClassLoaderInfo& info)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ CHECK(info.shared_libraries.empty()) << "Class loader shared library not implemented yet";
+ jobject parent = nullptr;
+ if (info.parent != nullptr) {
+ parent = CreateClassLoaderInternal(self, *info.parent.get());
+ }
+ std::vector<const DexFile*> class_path_files = MakeNonOwningPointerVector(
+ info.opened_dex_files);
+ return Runtime::Current()->GetClassLinker()->CreateWellKnownClassLoader(
+ self,
+ class_path_files,
+ GetClassLoaderClass(info.type),
+ parent);
+}
+
jobject ClassLoaderContext::CreateClassLoader(
const std::vector<const DexFile*>& compilation_sources) const {
CheckDexFilesOpened("CreateClassLoader");
@@ -393,22 +596,14 @@
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
- if (class_loader_chain_.empty()) {
+ if (class_loader_chain_ == nullptr) {
return class_linker->CreatePathClassLoader(self, compilation_sources);
}
- // 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);
+ // Create the class loader of the parent.
+ jobject parent = nullptr;
+ if (class_loader_chain_->parent != nullptr) {
+ parent = CreateClassLoaderInternal(self, *class_loader_chain_->parent.get());
}
// We set up all the parents. Move on to create the first class loader.
@@ -416,26 +611,34 @@
// 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);
+ class_loader_chain_->opened_dex_files);
first_class_loader_classpath.insert(first_class_loader_classpath.end(),
- compilation_sources.begin(),
- compilation_sources.end());
+ compilation_sources.begin(),
+ compilation_sources.end());
return class_linker->CreateWellKnownClassLoader(
self,
first_class_loader_classpath,
- GetClassLoaderClass(class_loader_chain_[0].type),
- current_parent);
+ GetClassLoaderClass(class_loader_chain_->type),
+ parent);
}
std::vector<const DexFile*> ClassLoaderContext::FlattenOpenedDexFiles() const {
CheckDexFilesOpened("FlattenOpenedDexFiles");
std::vector<const DexFile*> result;
- for (const ClassLoaderInfo& info : class_loader_chain_) {
- for (const std::unique_ptr<const DexFile>& dex_file : info.opened_dex_files) {
+ if (class_loader_chain_ == nullptr) {
+ return result;
+ }
+ std::vector<ClassLoaderInfo*> work_list;
+ work_list.push_back(class_loader_chain_.get());
+ while (!work_list.empty()) {
+ ClassLoaderInfo* info = work_list.back();
+ work_list.pop_back();
+ for (const std::unique_ptr<const DexFile>& dex_file : info->opened_dex_files) {
result.push_back(dex_file.get());
}
+ AddToWorkList(info, work_list);
}
return result;
}
@@ -632,12 +835,21 @@
GetDexFilesFromDexElementsArray(soa, dex_elements, &dex_files_loaded);
}
- class_loader_chain_.push_back(ClassLoaderContext::ClassLoaderInfo(type));
- ClassLoaderInfo& info = class_loader_chain_.back();
+ ClassLoaderInfo* info = new ClassLoaderContext::ClassLoaderInfo(type);
+ if (class_loader_chain_ == nullptr) {
+ class_loader_chain_.reset(info);
+ } else {
+ ClassLoaderInfo* child = class_loader_chain_.get();
+ while (child->parent != nullptr) {
+ child = child->parent.get();
+ }
+ child->parent.reset(info);
+ }
+
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);
+ 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.
@@ -696,7 +908,7 @@
// collision check.
if (expected_context.special_shared_library_) {
// Special case where we are the only entry in the class path.
- if (class_loader_chain_.size() == 1 && class_loader_chain_[0].classpath.size() == 0) {
+ if (class_loader_chain_->parent == nullptr && class_loader_chain_->classpath.size() == 0) {
return VerificationResult::kVerifies;
}
return VerificationResult::kForcedToSkipChecks;
@@ -704,41 +916,43 @@
return VerificationResult::kForcedToSkipChecks;
}
- 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()
- << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+ ClassLoaderInfo* info = class_loader_chain_.get();
+ ClassLoaderInfo* expected = expected_context.class_loader_chain_.get();
+ CHECK(info != nullptr);
+ CHECK(expected != nullptr);
+ if (!ClassLoaderInfoMatch(*info, *expected, context_spec, verify_names, verify_checksums)) {
return VerificationResult::kMismatch;
}
+ return VerificationResult::kVerifies;
+}
- 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)
+bool ClassLoaderContext::ClassLoaderInfoMatch(
+ const ClassLoaderInfo& info,
+ const ClassLoaderInfo& expected_info,
+ const std::string& context_spec,
+ bool verify_names,
+ bool verify_checksums) const {
+ if (info.type != expected_info.type) {
+ LOG(WARNING) << "ClassLoaderContext type mismatch"
+ << ". expected=" << GetClassLoaderTypeName(expected_info.type)
+ << ", found=" << GetClassLoaderTypeName(info.type)
+ << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+ return false;
+ }
+ if (info.classpath.size() != expected_info.classpath.size()) {
+ LOG(WARNING) << "ClassLoaderContext classpath size mismatch"
+ << ". expected=" << expected_info.classpath.size()
+ << ", found=" << info.classpath.size()
<< " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
- return VerificationResult::kMismatch;
- }
- 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()
- << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
- return VerificationResult::kMismatch;
- }
+ return false;
+ }
- if (verify_checksums) {
- DCHECK_EQ(info.classpath.size(), info.checksums.size());
- DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size());
- }
+ if (verify_checksums) {
+ DCHECK_EQ(info.classpath.size(), info.checksums.size());
+ DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size());
+ }
- if (!verify_names) {
- continue;
- }
-
+ if (verify_names) {
for (size_t k = 0; k < info.classpath.size(); k++) {
// Compute the dex location that must be compared.
// We shouldn't do a naive comparison `info.classpath[k] == expected_info.classpath[k]`
@@ -778,34 +992,58 @@
// Compare the locations.
if (dex_name != expected_dex_name) {
- LOG(WARNING) << "ClassLoaderContext classpath element mismatch for position " << i
+ LOG(WARNING) << "ClassLoaderContext classpath element mismatch"
<< ". expected=" << expected_info.classpath[k]
<< ", found=" << info.classpath[k]
<< " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
- return VerificationResult::kMismatch;
+ return false;
}
// Compare the checksums.
if (info.checksums[k] != expected_info.checksums[k]) {
- LOG(WARNING) << "ClassLoaderContext classpath element checksum mismatch for position " << i
+ LOG(WARNING) << "ClassLoaderContext classpath element checksum mismatch"
<< ". expected=" << expected_info.checksums[k]
<< ", found=" << info.checksums[k]
<< " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
- return VerificationResult::kMismatch;
+ return false;
}
}
}
- return VerificationResult::kVerifies;
-}
-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.
+ if (info.shared_libraries.size() != expected_info.shared_libraries.size()) {
+ LOG(WARNING) << "ClassLoaderContext shared library size mismatch. "
+ << "Expected=" << expected_info.classpath.size()
+ << ", found=" << info.classpath.size()
+ << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+ return false;
}
- LOG(FATAL) << "Invalid class loader type " << type;
- UNREACHABLE();
+ for (size_t i = 0; i < info.shared_libraries.size(); ++i) {
+ if (!ClassLoaderInfoMatch(*info.shared_libraries[i].get(),
+ *expected_info.shared_libraries[i].get(),
+ context_spec,
+ verify_names,
+ verify_checksums)) {
+ return false;
+ }
+ }
+ if (info.parent.get() == nullptr) {
+ if (expected_info.parent.get() != nullptr) {
+ LOG(WARNING) << "ClassLoaderContext parent mismatch. "
+ << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+ return false;
+ }
+ return true;
+ } else if (expected_info.parent.get() == nullptr) {
+ LOG(WARNING) << "ClassLoaderContext parent mismatch. "
+ << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+ return false;
+ } else {
+ return ClassLoaderInfoMatch(*info.parent.get(),
+ *expected_info.parent.get(),
+ context_spec,
+ verify_names,
+ verify_checksums);
+ }
}
} // namespace art
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index a4268aa..37cef81 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -154,10 +154,11 @@
// This will return a context with a single and empty PathClassLoader.
static std::unique_ptr<ClassLoaderContext> Default();
- private:
struct ClassLoaderInfo {
// The type of this class loader.
ClassLoaderType type;
+ // Shared libraries this context has.
+ std::vector<std::unique_ptr<ClassLoaderInfo>> shared_libraries;
// The list of class path elements that this loader loads.
// Note that this list may contain relative paths.
std::vector<std::string> classpath;
@@ -171,13 +172,35 @@
// After OpenDexFiles, in case some of the dex files were opened from their oat files
// this holds the list of opened oat files.
std::vector<std::unique_ptr<OatFile>> opened_oat_files;
+ // The parent class loader.
+ std::unique_ptr<ClassLoaderInfo> parent;
explicit ClassLoaderInfo(ClassLoaderType cl_type) : type(cl_type) {}
};
+ private:
// Creates an empty context (with no class loaders).
ClassLoaderContext();
+ // Get the parent of the class loader chain at depth `index`.
+ ClassLoaderInfo* GetParent(size_t index) const {
+ ClassLoaderInfo* result = class_loader_chain_.get();
+ while ((result != nullptr) && (index-- != 0)) {
+ result = result->parent.get();
+ }
+ return result;
+ }
+
+ size_t GetParentChainSize() const {
+ size_t result = 0;
+ ClassLoaderInfo* info = class_loader_chain_.get();
+ while (info != nullptr) {
+ ++result;
+ info = info->parent.get();
+ }
+ return result;
+ }
+
// 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
@@ -188,13 +211,13 @@
// 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);
+ ClassLoaderInfo* ParseInternal(const std::string& spec, bool parse_checksums);
- // Attempts to parse a single class loader spec for the given class_loader_type.
- // If successful the class loader spec will be added to the chain.
- // Returns whether or not the operation was successful.
- bool ParseClassLoaderSpec(const std::string& class_loader_spec,
- ClassLoaderType class_loader_type,
- bool parse_checksums = false);
+ // Attempts to parse a single class loader spec.
+ // Returns the ClassLoaderInfo abstraction for this spec, or null if it cannot be parsed.
+ std::unique_ptr<ClassLoaderInfo> ParseClassLoaderSpec(
+ const std::string& class_loader_spec,
+ 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
@@ -219,6 +242,20 @@
bool for_dex2oat,
ClassLoaderContext* stored_context) const;
+ // Internal version of `EncodeContext`, which will be called recursively
+ // on the parent and shared libraries.
+ void EncodeContextInternal(const ClassLoaderInfo& info,
+ const std::string& base_dir,
+ bool for_dex2oat,
+ ClassLoaderInfo* stored_info,
+ std::ostringstream& out) const;
+
+ bool ClassLoaderInfoMatch(const ClassLoaderInfo& info,
+ const ClassLoaderInfo& expected_info,
+ const std::string& context_spec,
+ bool verify_names,
+ bool verify_checksums) const;
+
// Extracts the class loader type from the given spec.
// Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not
// recognized.
@@ -228,13 +265,8 @@
// The returned format can be used when parsing a context spec.
static const char* GetClassLoaderTypeName(ClassLoaderType type);
- // Returns the WellKnownClass for the given class loader type.
- static jclass GetClassLoaderClass(ClassLoaderType type);
-
- // The class loader chain represented as a vector.
- // The parent of class_loader_chain_[i] is class_loader_chain_[i++].
- // The parent of the last element is assumed to be the boot class loader.
- std::vector<ClassLoaderInfo> class_loader_chain_;
+ // The class loader chain.
+ std::unique_ptr<ClassLoaderInfo> class_loader_chain_;
// Whether or not the class loader context should be ignored at runtime when loading the oat
// files. When true, dex2oat will use OatFile::kSpecialSharedLibrary as the classpath key in
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index ea624f1..cb3dc65 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -40,7 +40,7 @@
public:
void VerifyContextSize(ClassLoaderContext* context, size_t expected_size) {
ASSERT_TRUE(context != nullptr);
- ASSERT_EQ(expected_size, context->class_loader_chain_.size());
+ ASSERT_EQ(expected_size, context->GetParentChainSize());
}
void VerifyClassLoaderPCL(ClassLoaderContext* context,
@@ -57,6 +57,33 @@
context, index, ClassLoaderContext::kDelegateLastClassLoader, classpath);
}
+ void VerifyClassLoaderSharedLibraryPCL(ClassLoaderContext* context,
+ size_t loader_index,
+ size_t shared_library_index,
+ const std::string& classpath) {
+ VerifyClassLoaderInfoSL(
+ context, loader_index, shared_library_index, ClassLoaderContext::kPathClassLoader,
+ classpath);
+ }
+
+ void VerifySharedLibrariesSize(ClassLoaderContext* context,
+ size_t loader_index,
+ size_t expected_size) {
+ ASSERT_TRUE(context != nullptr);
+ ASSERT_GT(context->GetParentChainSize(), loader_index);
+ const ClassLoaderContext::ClassLoaderInfo& info = *context->GetParent(loader_index);
+ ASSERT_EQ(info.shared_libraries.size(), expected_size);
+ }
+
+ void VerifyClassLoaderSharedLibraryDLC(ClassLoaderContext* context,
+ size_t loader_index,
+ size_t shared_library_index,
+ const std::string& classpath) {
+ VerifyClassLoaderInfoSL(
+ context, loader_index, shared_library_index, ClassLoaderContext::kDelegateLastClassLoader,
+ classpath);
+ }
+
void VerifyClassLoaderPCLFromTestDex(ClassLoaderContext* context,
size_t index,
const std::string& test_name) {
@@ -91,7 +118,7 @@
ASSERT_TRUE(context != nullptr);
ASSERT_TRUE(context->dex_files_open_attempted_);
ASSERT_TRUE(context->dex_files_open_result_);
- ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index];
+ ClassLoaderContext::ClassLoaderInfo& info = *context->GetParent(index);
ASSERT_EQ(all_dex_files->size(), info.classpath.size());
ASSERT_EQ(all_dex_files->size(), info.opened_dex_files.size());
size_t cur_open_dex_index = 0;
@@ -168,14 +195,31 @@
ClassLoaderContext::ClassLoaderType type,
const std::string& classpath) {
ASSERT_TRUE(context != nullptr);
- ASSERT_GT(context->class_loader_chain_.size(), index);
- ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index];
+ ASSERT_GT(context->GetParentChainSize(), index);
+ ClassLoaderContext::ClassLoaderInfo& info = *context->GetParent(index);
ASSERT_EQ(type, info.type);
std::vector<std::string> expected_classpath;
Split(classpath, ':', &expected_classpath);
ASSERT_EQ(expected_classpath, info.classpath);
}
+ void VerifyClassLoaderInfoSL(ClassLoaderContext* context,
+ size_t loader_index,
+ size_t shared_library_index,
+ ClassLoaderContext::ClassLoaderType type,
+ const std::string& classpath) {
+ ASSERT_TRUE(context != nullptr);
+ ASSERT_GT(context->GetParentChainSize(), loader_index);
+ const ClassLoaderContext::ClassLoaderInfo& info = *context->GetParent(loader_index);
+ ASSERT_GT(info.shared_libraries.size(), shared_library_index);
+ const ClassLoaderContext::ClassLoaderInfo& sl =
+ *info.shared_libraries[shared_library_index].get();
+ ASSERT_EQ(type, info.type);
+ std::vector<std::string> expected_classpath;
+ Split(classpath, ':', &expected_classpath);
+ ASSERT_EQ(expected_classpath, sl.classpath);
+ }
+
void VerifyClassLoaderFromTestDex(ClassLoaderContext* context,
size_t index,
ClassLoaderContext::ClassLoaderType type,
@@ -223,6 +267,23 @@
VerifyClassLoaderPCL(context.get(), 2, "e.dex");
}
+TEST_F(ClassLoaderContextTest, ParseSharedLibraries) {
+ std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(
+ "PCL[a.dex:b.dex]{PCL[s1.dex]#PCL[s2.dex:s3.dex]};DLC[c.dex:d.dex]{DLC[s4.dex]}");
+ VerifyContextSize(context.get(), 2);
+ VerifyClassLoaderSharedLibraryPCL(context.get(), 0, 0, "s1.dex");
+ VerifyClassLoaderSharedLibraryPCL(context.get(), 0, 1, "s2.dex:s3.dex");
+ VerifyClassLoaderDLC(context.get(), 1, "c.dex:d.dex");
+ VerifyClassLoaderSharedLibraryDLC(context.get(), 1, 0, "s4.dex");
+}
+
+TEST_F(ClassLoaderContextTest, ParseEnclosingSharedLibraries) {
+ std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(
+ "PCL[a.dex:b.dex]{PCL[s1.dex]{PCL[s2.dex:s3.dex];PCL[s4.dex]}}");
+ VerifyContextSize(context.get(), 1);
+ VerifyClassLoaderSharedLibraryPCL(context.get(), 0, 0, "s1.dex");
+}
+
TEST_F(ClassLoaderContextTest, ParseValidEmptyContextDLC) {
std::unique_ptr<ClassLoaderContext> context =
ClassLoaderContext::Create("DLC[]");
@@ -230,6 +291,13 @@
VerifyClassLoaderDLC(context.get(), 0, "");
}
+TEST_F(ClassLoaderContextTest, ParseValidEmptyContextSharedLibrary) {
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("DLC[]{}");
+ VerifyContextSize(context.get(), 1);
+ VerifySharedLibrariesSize(context.get(), 0, 0);
+}
+
TEST_F(ClassLoaderContextTest, ParseValidContextSpecialSymbol) {
std::unique_ptr<ClassLoaderContext> context =
ClassLoaderContext::Create(OatFile::kSpecialSharedLibrary);
@@ -243,6 +311,11 @@
ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCLa.dex]"));
ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL{a.dex}"));
ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL[a.dex];DLC[b.dex"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL[a.dex]{ABC};DLC[b.dex"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("PCL[a.dex]{};DLC[b.dex"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("DLC[s4.dex]}"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("DLC[s4.dex]{"));
+ ASSERT_TRUE(nullptr == ClassLoaderContext::Create("DLC{DLC[s4.dex]}"));
}
TEST_F(ClassLoaderContextTest, OpenInvalidDexFiles) {
@@ -662,6 +735,62 @@
ClassLoaderContext::VerificationResult::kMismatch);
}
+TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchWithSL) {
+ std::string context_spec =
+ "PCL[a.dex*123:b.dex*456]{PCL[d.dex*321];PCL[e.dex*654]#PCL[f.dex*098:g.dex*999]}"
+ ";DLC[c.dex*890]";
+ std::unique_ptr<ClassLoaderContext> context = ParseContextWithChecksums(context_spec);
+ // Pretend that we successfully open the dex files to pass the DCHECKS.
+ // (as it's much easier to test all the corner cases without relying on actual dex files).
+ PretendContextOpenedDexFiles(context.get());
+
+ VerifyContextSize(context.get(), 2);
+ VerifyClassLoaderPCL(context.get(), 0, "a.dex:b.dex");
+ VerifyClassLoaderDLC(context.get(), 1, "c.dex");
+ VerifyClassLoaderSharedLibraryPCL(context.get(), 0, 0, "d.dex");
+ VerifyClassLoaderSharedLibraryPCL(context.get(), 0, 1, "f.dex:g.dex");
+
+ ASSERT_EQ(context->VerifyClassLoaderContextMatch(context_spec),
+ ClassLoaderContext::VerificationResult::kVerifies);
+
+ std::string wrong_class_loader_type =
+ "PCL[a.dex*123:b.dex*456]{DLC[d.dex*321];PCL[e.dex*654]#PCL[f.dex*098:g.dex*999]}"
+ ";DLC[c.dex*890]";
+ ASSERT_EQ(context->VerifyClassLoaderContextMatch(wrong_class_loader_type),
+ ClassLoaderContext::VerificationResult::kMismatch);
+
+ std::string wrong_class_loader_order =
+ "PCL[a.dex*123:b.dex*456]{PCL[f.dex#098:g.dex#999}#PCL[d.dex*321];PCL[e.dex*654]}"
+ ";DLC[c.dex*890]";
+ ASSERT_EQ(context->VerifyClassLoaderContextMatch(wrong_class_loader_order),
+ ClassLoaderContext::VerificationResult::kMismatch);
+
+ std::string wrong_classpath_order =
+ "PCL[a.dex*123:b.dex*456]{PCL[d.dex*321];PCL[e.dex*654]#PCL[g.dex*999:f.dex*098]}"
+ ";DLC[c.dex*890]";
+ ASSERT_EQ(context->VerifyClassLoaderContextMatch(wrong_classpath_order),
+ ClassLoaderContext::VerificationResult::kMismatch);
+
+ std::string wrong_checksum =
+ "PCL[a.dex*123:b.dex*456]{PCL[d.dex*333];PCL[e.dex*654]#PCL[g.dex*999:f.dex*098]}"
+ ";DLC[c.dex*890]";
+ ASSERT_EQ(context->VerifyClassLoaderContextMatch(wrong_checksum),
+ ClassLoaderContext::VerificationResult::kMismatch);
+
+ std::string wrong_extra_class_loader =
+ "PCL[a.dex*123:b.dex*456]"
+ "{PCL[d.dex*321];PCL[e.dex*654]#PCL[f.dex*098:g.dex*999];PCL[i.dex#444]}"
+ ";DLC[c.dex*890]";
+ ASSERT_EQ(context->VerifyClassLoaderContextMatch(wrong_extra_class_loader),
+ ClassLoaderContext::VerificationResult::kMismatch);
+
+ std::string wrong_extra_classpath =
+ "PCL[a.dex*123:b.dex*456]{PCL[d.dex*321:i.dex#444];PCL[e.dex*654]#PCL[f.dex*098:g.dex*999]}"
+ ";DLC[c.dex*890]";
+ ASSERT_EQ(context->VerifyClassLoaderContextMatch(wrong_extra_classpath),
+ ClassLoaderContext::VerificationResult::kMismatch);
+}
+
TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncoding) {
jobject class_loader_a = LoadDexInPathClassLoader("ForClassLoaderA", nullptr);
jobject class_loader_b = LoadDexInDelegateLastClassLoader("ForClassLoaderB", class_loader_a);