diff options
| -rw-r--r-- | compiler/optimizing/register_allocator_linear_scan.cc | 2 | ||||
| -rw-r--r-- | compiler/optimizing/ssa_liveness_analysis.h | 17 | ||||
| -rw-r--r-- | dexlayout/dexlayout_main.cc | 6 | ||||
| -rw-r--r-- | runtime/dexopt_test.cc | 45 | ||||
| -rw-r--r-- | runtime/dexopt_test.h | 2 | ||||
| -rw-r--r-- | runtime/gc/space/image_space_test.cc | 2 | ||||
| -rw-r--r-- | runtime/oat_file_assistant.cc | 334 | ||||
| -rw-r--r-- | runtime/oat_file_assistant.h | 83 | ||||
| -rw-r--r-- | runtime/oat_file_assistant_test.cc | 445 | ||||
| -rw-r--r-- | runtime/oat_file_manager.cc | 46 | ||||
| -rwxr-xr-x | test/138-duplicate-classes-check2/run | 19 | ||||
| -rw-r--r-- | test/677-fsi/expected.txt | 1 | ||||
| -rw-r--r-- | test/718-zipfile-finalizer/expected.txt | 0 | ||||
| -rw-r--r-- | test/718-zipfile-finalizer/info.txt | 2 | ||||
| -rw-r--r-- | test/718-zipfile-finalizer/src/Main.java | 40 | ||||
| -rw-r--r-- | tools/dexanalyze/dexanalyze_bytecode.cc | 24 | ||||
| -rw-r--r-- | tools/dexanalyze/dexanalyze_strings.cc | 312 | ||||
| -rw-r--r-- | tools/dexanalyze/dexanalyze_strings.h | 6 | ||||
| -rw-r--r-- | tools/jfuzz/Android.bp | 29 | ||||
| -rw-r--r-- | tools/jfuzz/Android.mk | 25 |
20 files changed, 539 insertions, 901 deletions
diff --git a/compiler/optimizing/register_allocator_linear_scan.cc b/compiler/optimizing/register_allocator_linear_scan.cc index 216fb57a96..1e00003701 100644 --- a/compiler/optimizing/register_allocator_linear_scan.cc +++ b/compiler/optimizing/register_allocator_linear_scan.cc @@ -312,7 +312,7 @@ void RegisterAllocatorLinearScan::ProcessInstruction(HInstruction* instruction) for (size_t safepoint_index = safepoints_.size(); safepoint_index > 0; --safepoint_index) { HInstruction* safepoint = safepoints_[safepoint_index - 1u]; - size_t safepoint_position = safepoint->GetLifetimePosition(); + size_t safepoint_position = SafepointPosition::ComputePosition(safepoint); // Test that safepoints are ordered in the optimal way. DCHECK(safepoint_index == safepoints_.size() || diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h index 97c00c9c1d..92d0b08301 100644 --- a/compiler/optimizing/ssa_liveness_analysis.h +++ b/compiler/optimizing/ssa_liveness_analysis.h @@ -230,12 +230,25 @@ class SafepointPosition : public ArenaObject<kArenaAllocSsaLiveness> { : instruction_(instruction), next_(nullptr) {} + static size_t ComputePosition(HInstruction* instruction) { + // We special case instructions emitted at use site, as their + // safepoint position needs to be at their use. + if (instruction->IsEmittedAtUseSite()) { + // Currently only applies to implicit null checks, which are emitted + // at the next instruction. + DCHECK(instruction->IsNullCheck()) << instruction->DebugName(); + return instruction->GetLifetimePosition() + 2; + } else { + return instruction->GetLifetimePosition(); + } + } + void SetNext(SafepointPosition* next) { next_ = next; } size_t GetPosition() const { - return instruction_->GetLifetimePosition(); + return ComputePosition(instruction_); } SafepointPosition* GetNext() const { @@ -922,7 +935,7 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { if (first_safepoint_ == nullptr) { first_safepoint_ = last_safepoint_ = safepoint; } else { - DCHECK_LT(last_safepoint_->GetPosition(), safepoint->GetPosition()); + DCHECK_LE(last_safepoint_->GetPosition(), safepoint->GetPosition()); last_safepoint_->SetNext(safepoint); last_safepoint_ = safepoint; } diff --git a/dexlayout/dexlayout_main.cc b/dexlayout/dexlayout_main.cc index 71e56d19ea..9f73347354 100644 --- a/dexlayout/dexlayout_main.cc +++ b/dexlayout/dexlayout_main.cc @@ -60,6 +60,7 @@ static void Usage(void) { LOG(ERROR) << " -p : profile file name (defaults to no profile)"; LOG(ERROR) << " -s : visualize reference pattern"; LOG(ERROR) << " -t : display file section sizes"; + LOG(ERROR) << " -u : update dex checksums"; LOG(ERROR) << " -v : verify output file is canonical to input (IR level comparison)"; LOG(ERROR) << " -w : output dex directory"; LOG(ERROR) << " -x : compact dex generation level, either 'none' or 'fast'"; @@ -85,7 +86,7 @@ int DexlayoutDriver(int argc, char** argv) { // Parse all arguments. while (1) { - const int ic = getopt(argc, argv, "abcdefghil:o:p:stvw:x:"); + const int ic = getopt(argc, argv, "abcdefghil:o:p:stuvw:x:"); if (ic < 0) { break; // done } @@ -138,6 +139,9 @@ int DexlayoutDriver(int argc, char** argv) { options.show_section_statistics_ = true; options.verbose_ = false; break; + case 'u': // update checksum + options.update_checksum_ = true; + break; case 'v': // verify output options.verify_output_ = true; break; diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc index 9e3159d5e7..127e14e239 100644 --- a/runtime/dexopt_test.cc +++ b/runtime/dexopt_test.cc @@ -20,6 +20,8 @@ #include <backtrace/BacktraceMap.h> #include <gtest/gtest.h> +#include "android-base/stringprintf.h" +#include "android-base/strings.h" #include "base/file_utils.h" #include "base/mem_map.h" #include "common_runtime_test.h" @@ -27,6 +29,7 @@ #include "dex2oat_environment_test.h" #include "dexopt_test.h" #include "gc/space/image_space.h" +#include "hidden_api.h" namespace art { void DexoptTest::SetUp() { @@ -45,6 +48,46 @@ void DexoptTest::PostRuntimeCreate() { ReserveImageSpace(); } +static std::string ImageLocation() { + Runtime* runtime = Runtime::Current(); + const std::vector<gc::space::ImageSpace*>& image_spaces = + runtime->GetHeap()->GetBootImageSpaces(); + if (image_spaces.empty()) { + return ""; + } + return image_spaces[0]->GetImageLocation(); +} + +bool DexoptTest::Dex2Oat(const std::vector<std::string>& args, std::string* error_msg) { + Runtime* runtime = Runtime::Current(); + + std::vector<std::string> argv; + argv.push_back(runtime->GetCompilerExecutable()); + if (runtime->IsJavaDebuggable()) { + argv.push_back("--debuggable"); + } + runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); + + if (runtime->GetHiddenApiEnforcementPolicy() != hiddenapi::EnforcementPolicy::kNoChecks) { + argv.push_back("--runtime-arg"); + argv.push_back("-Xhidden-api-checks"); + } + + if (!kIsTargetBuild) { + argv.push_back("--host"); + } + + argv.push_back("--boot-image=" + ImageLocation()); + + std::vector<std::string> compiler_options = runtime->GetCompilerOptions(); + argv.insert(argv.end(), compiler_options.begin(), compiler_options.end()); + + argv.insert(argv.end(), args.begin(), args.end()); + + std::string command_line(android::base::Join(argv, ' ')); + return Exec(argv, error_msg); +} + void DexoptTest::GenerateOatForTest(const std::string& dex_location, const std::string& oat_location_in, CompilerFilter::Filter filter, @@ -96,7 +139,7 @@ void DexoptTest::GenerateOatForTest(const std::string& dex_location, } std::string error_msg; - ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; if (!relocate) { // Restore the dalvik cache if needed. diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h index df7181a1e7..5dff379a32 100644 --- a/runtime/dexopt_test.h +++ b/runtime/dexopt_test.h @@ -71,6 +71,8 @@ class DexoptTest : public Dex2oatEnvironmentTest { // Generate a standard oat file in the oat location. void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter); + static bool Dex2Oat(const std::vector<std::string>& args, std::string* error_msg); + private: // Pre-Relocate the image to a known non-zero offset so we don't have to // deal with the runtime randomly relocating the image by 0 and messing up diff --git a/runtime/gc/space/image_space_test.cc b/runtime/gc/space/image_space_test.cc index d93385de3a..347af4e1fd 100644 --- a/runtime/gc/space/image_space_test.cc +++ b/runtime/gc/space/image_space_test.cc @@ -41,7 +41,7 @@ TEST_F(DexoptTest, ValidateOatFile) { args.push_back("--dex-file=" + multidex1); args.push_back("--dex-file=" + dex2); args.push_back("--oat-file=" + oat_location); - ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; std::unique_ptr<OatFile> oat(OatFile::Open(/* zip_fd */ -1, oat_location.c_str(), diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index f7c74cc23b..e262ff7150 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -36,7 +36,6 @@ #include "exec_utils.h" #include "gc/heap.h" #include "gc/space/image_space.h" -#include "hidden_api.h" #include "image.h" #include "oat.h" #include "runtime.h" @@ -182,30 +181,6 @@ bool OatFileAssistant::IsInBootClassPath() { return false; } -bool OatFileAssistant::Lock(std::string* error_msg) { - CHECK(error_msg != nullptr); - CHECK(flock_.get() == nullptr) << "OatFileAssistant::Lock already acquired"; - - // Note the lock will only succeed for secondary dex files and in test - // environment. - // - // The lock *will fail* for all primary apks in a production environment. - // The app does not have permissions to create locks next to its dex location - // (be it system, data or vendor parition). We also cannot use the odex or - // oat location for the same reasoning. - // - // This is best effort and if it fails it's unlikely that we will be able - // to generate oat files anyway. - std::string lock_file_name = dex_location_ + "." + GetInstructionSetString(isa_) + ".flock"; - - flock_ = LockedFile::Open(lock_file_name.c_str(), error_msg); - if (flock_.get() == nullptr) { - unlink(lock_file_name.c_str()); - return false; - } - return true; -} - int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target, bool profile_changed, bool downgrade, @@ -221,72 +196,10 @@ int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target, return -dexopt_needed; } -// Figure out the currently specified compile filter option in the runtime. -// Returns true on success, false if the compiler filter is invalid, in which -// case error_msg describes the problem. -static bool GetRuntimeCompilerFilterOption(CompilerFilter::Filter* filter, - std::string* error_msg) { - CHECK(filter != nullptr); - CHECK(error_msg != nullptr); - - *filter = OatFileAssistant::kDefaultCompilerFilterForDexLoading; - for (StringPiece option : Runtime::Current()->GetCompilerOptions()) { - if (option.starts_with("--compiler-filter=")) { - const char* compiler_filter_string = option.substr(strlen("--compiler-filter=")).data(); - if (!CompilerFilter::ParseCompilerFilter(compiler_filter_string, filter)) { - *error_msg = std::string("Unknown --compiler-filter value: ") - + std::string(compiler_filter_string); - return false; - } - } - } - return true; -} - bool OatFileAssistant::IsUpToDate() { return GetBestInfo().Status() == kOatUpToDate; } -OatFileAssistant::ResultOfAttemptToUpdate -OatFileAssistant::MakeUpToDate(bool profile_changed, - ClassLoaderContext* class_loader_context, - std::string* error_msg) { - // The method doesn't use zip_fd_ and directly opens dex files at dex_locations_. - CHECK_EQ(-1, zip_fd_) << "MakeUpToDate should not be called with zip_fd"; - - CompilerFilter::Filter target; - if (!GetRuntimeCompilerFilterOption(&target, error_msg)) { - return kUpdateNotAttempted; - } - - OatFileInfo& info = GetBestInfo(); - // TODO(calin, jeffhao): the context should really be passed to GetDexOptNeeded: b/62269291. - // This is actually not trivial in the current logic as it will interact with the collision - // check: - // - currently, if the context does not match but we have no collisions we still accept the - // oat file. - // - if GetDexOptNeeded would return kDex2OatFromScratch for a context mismatch and we make - // the oat code up to date the collision check becomes useless. - // - however, MakeUpToDate will not always succeed (e.g. for primary apks, or for dex files - // loaded in other processes). So it boils down to how far do we want to complicate - // the logic in order to enable the use of oat files. Maybe its time to try simplify it. - switch (info.GetDexOptNeeded( - target, profile_changed, /*downgrade*/ false, class_loader_context)) { - case kNoDexOptNeeded: - return kUpdateSucceeded; - - // TODO: For now, don't bother with all the different ways we can call - // dex2oat to generate the oat file. Always generate the oat file as if it - // were kDex2OatFromScratch. - case kDex2OatFromScratch: - case kDex2OatForBootImage: - case kDex2OatForRelocation: - case kDex2OatForFilter: - return GenerateOatFileNoChecks(info, target, class_loader_context, error_msg); - } - UNREACHABLE(); -} - std::unique_ptr<OatFile> OatFileAssistant::GetBestOatFile() { return GetBestInfo().ReleaseFileForUse(); } @@ -615,243 +528,6 @@ static bool DexLocationToOdexNames(const std::string& location, return true; } -// Prepare a subcomponent of the odex directory. -// (i.e. create and set the expected permissions on the path `dir`). -static bool PrepareDirectory(const std::string& dir, std::string* error_msg) { - struct stat dir_stat; - if (TEMP_FAILURE_RETRY(stat(dir.c_str(), &dir_stat)) == 0) { - // The directory exists. Check if it is indeed a directory. - if (!S_ISDIR(dir_stat.st_mode)) { - *error_msg = dir + " is not a dir"; - return false; - } else { - // The dir is already on disk. - return true; - } - } - - // Failed to stat. We need to create the directory. - if (errno != ENOENT) { - *error_msg = "Could not stat isa dir " + dir + ":" + strerror(errno); - return false; - } - - mode_t mode = S_IRWXU | S_IXGRP | S_IXOTH; - if (mkdir(dir.c_str(), mode) != 0) { - *error_msg = "Could not create dir " + dir + ":" + strerror(errno); - return false; - } - if (chmod(dir.c_str(), mode) != 0) { - *error_msg = "Could not create the oat dir " + dir + ":" + strerror(errno); - return false; - } - return true; -} - -// Prepares the odex directory for the given dex location. -static bool PrepareOdexDirectories(const std::string& dex_location, - const std::string& expected_odex_location, - InstructionSet isa, - std::string* error_msg) { - std::string actual_odex_location; - std::string oat_dir; - std::string isa_dir; - if (!DexLocationToOdexNames( - dex_location, isa, &actual_odex_location, &oat_dir, &isa_dir, error_msg)) { - return false; - } - DCHECK_EQ(expected_odex_location, actual_odex_location); - - if (!PrepareDirectory(oat_dir, error_msg)) { - return false; - } - if (!PrepareDirectory(isa_dir, error_msg)) { - return false; - } - return true; -} - -class Dex2oatFileWrapper { - public: - explicit Dex2oatFileWrapper(File* file) - : file_(file), - unlink_file_at_destruction_(true) { - } - - ~Dex2oatFileWrapper() { - if (unlink_file_at_destruction_ && (file_ != nullptr)) { - file_->Erase(/*unlink*/ true); - } - } - - File* GetFile() { return file_.get(); } - - void DisableUnlinkAtDestruction() { - unlink_file_at_destruction_ = false; - } - - private: - std::unique_ptr<File> file_; - bool unlink_file_at_destruction_; -}; - -OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChecks( - OatFileAssistant::OatFileInfo& info, - CompilerFilter::Filter filter, - const ClassLoaderContext* class_loader_context, - std::string* error_msg) { - CHECK(error_msg != nullptr); - - Runtime* runtime = Runtime::Current(); - if (!runtime->IsDex2OatEnabled()) { - *error_msg = "Generation of oat file for dex location " + dex_location_ - + " not attempted because dex2oat is disabled."; - return kUpdateNotAttempted; - } - - if (info.Filename() == nullptr) { - *error_msg = "Generation of oat file for dex location " + dex_location_ - + " not attempted because the oat file name could not be determined."; - return kUpdateNotAttempted; - } - const std::string& oat_file_name = *info.Filename(); - const std::string& vdex_file_name = GetVdexFilename(oat_file_name); - - // dex2oat ignores missing dex files and doesn't report an error. - // Check explicitly here so we can detect the error properly. - // TODO: Why does dex2oat behave that way? - struct stat dex_path_stat; - if (TEMP_FAILURE_RETRY(stat(dex_location_.c_str(), &dex_path_stat)) != 0) { - *error_msg = "Could not access dex location " + dex_location_ + ":" + strerror(errno); - return kUpdateNotAttempted; - } - - // If this is the odex location, we need to create the odex file layout (../oat/isa/..) - if (!info.IsOatLocation()) { - if (!PrepareOdexDirectories(dex_location_, oat_file_name, isa_, error_msg)) { - return kUpdateNotAttempted; - } - } - - // Set the permissions for the oat and the vdex files. - // The user always gets read and write while the group and others propagate - // the reading access of the original dex file. - mode_t file_mode = S_IRUSR | S_IWUSR | - (dex_path_stat.st_mode & S_IRGRP) | - (dex_path_stat.st_mode & S_IROTH); - - Dex2oatFileWrapper vdex_file_wrapper(OS::CreateEmptyFile(vdex_file_name.c_str())); - File* vdex_file = vdex_file_wrapper.GetFile(); - if (vdex_file == nullptr) { - *error_msg = "Generation of oat file " + oat_file_name - + " not attempted because the vdex file " + vdex_file_name - + " could not be opened."; - return kUpdateNotAttempted; - } - - if (fchmod(vdex_file->Fd(), file_mode) != 0) { - *error_msg = "Generation of oat file " + oat_file_name - + " not attempted because the vdex file " + vdex_file_name - + " could not be made world readable."; - return kUpdateNotAttempted; - } - - Dex2oatFileWrapper oat_file_wrapper(OS::CreateEmptyFile(oat_file_name.c_str())); - File* oat_file = oat_file_wrapper.GetFile(); - if (oat_file == nullptr) { - *error_msg = "Generation of oat file " + oat_file_name - + " not attempted because the oat file could not be created."; - return kUpdateNotAttempted; - } - - if (fchmod(oat_file->Fd(), file_mode) != 0) { - *error_msg = "Generation of oat file " + oat_file_name - + " not attempted because the oat file could not be made world readable."; - return kUpdateNotAttempted; - } - - std::vector<std::string> args; - args.push_back("--dex-file=" + dex_location_); - args.push_back("--output-vdex-fd=" + std::to_string(vdex_file->Fd())); - args.push_back("--oat-fd=" + std::to_string(oat_file->Fd())); - args.push_back("--oat-location=" + oat_file_name); - args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter)); - const std::string dex2oat_context = class_loader_context == nullptr - ? OatFile::kSpecialSharedLibrary - : class_loader_context->EncodeContextForDex2oat(/*base_dir*/ ""); - args.push_back("--class-loader-context=" + dex2oat_context); - - if (!Dex2Oat(args, error_msg)) { - return kUpdateFailed; - } - - if (vdex_file->FlushCloseOrErase() != 0) { - *error_msg = "Unable to close vdex file " + vdex_file_name; - return kUpdateFailed; - } - - if (oat_file->FlushCloseOrErase() != 0) { - *error_msg = "Unable to close oat file " + oat_file_name; - return kUpdateFailed; - } - - // Mark that the odex file has changed and we should try to reload. - info.Reset(); - // We have compiled successfully. Disable the auto-unlink. - vdex_file_wrapper.DisableUnlinkAtDestruction(); - oat_file_wrapper.DisableUnlinkAtDestruction(); - - return kUpdateSucceeded; -} - -bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args, - std::string* error_msg) { - Runtime* runtime = Runtime::Current(); - std::string image_location = ImageLocation(); - if (image_location.empty()) { - *error_msg = "No image location found for Dex2Oat."; - return false; - } - - std::vector<std::string> argv; - argv.push_back(runtime->GetCompilerExecutable()); - if (runtime->IsJavaDebuggable()) { - argv.push_back("--debuggable"); - } - runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); - - if (!runtime->IsVerificationEnabled()) { - argv.push_back("--compiler-filter=verify-none"); - } - - if (runtime->GetHiddenApiEnforcementPolicy() != hiddenapi::EnforcementPolicy::kNoChecks) { - argv.push_back("--runtime-arg"); - argv.push_back("-Xhidden-api-checks"); - } - - if (runtime->MustRelocateIfPossible()) { - argv.push_back("--runtime-arg"); - argv.push_back("-Xrelocate"); - } else { - argv.push_back("--runtime-arg"); - argv.push_back("-Xnorelocate"); - } - - if (!kIsTargetBuild) { - argv.push_back("--host"); - } - - argv.push_back("--boot-image=" + image_location); - - std::vector<std::string> compiler_options = runtime->GetCompilerOptions(); - argv.insert(argv.end(), compiler_options.begin(), compiler_options.end()); - - argv.insert(argv.end(), args.begin(), args.end()); - - std::string command_line(android::base::Join(argv, ' ')); - return Exec(argv, error_msg); -} - bool OatFileAssistant::DexLocationToOdexFilename(const std::string& location, InstructionSet isa, std::string* odex_filename, @@ -885,16 +561,6 @@ bool OatFileAssistant::DexLocationToOatFilename(const std::string& location, return GetDalvikCacheFilename(location.c_str(), cache_dir.c_str(), oat_filename, error_msg); } -std::string OatFileAssistant::ImageLocation() { - Runtime* runtime = Runtime::Current(); - const std::vector<gc::space::ImageSpace*>& image_spaces = - runtime->GetHeap()->GetBootImageSpaces(); - if (image_spaces.empty()) { - return ""; - } - return image_spaces[0]->GetImageLocation(); -} - const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums() { if (!required_dex_checksums_attempted_) { required_dex_checksums_attempted_ = true; diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index a6d0961835..dbfbdf9fbc 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -48,11 +48,6 @@ class ImageSpace; // dex location is in the boot class path. class OatFileAssistant { public: - // The default compile filter to use when optimizing dex file at load time if they - // are out of date. - static const CompilerFilter::Filter kDefaultCompilerFilterForDexLoading = - CompilerFilter::kQuicken; - enum DexOptNeeded { // No dexopt should (or can) be done to update the apk/jar. // Matches Java: dalvik.system.DexFile.NO_DEXOPT_NEEDED = 0 @@ -144,24 +139,6 @@ class OatFileAssistant { // path. bool IsInBootClassPath(); - // Obtains a lock on the target oat file. - // Only one OatFileAssistant object can hold the lock for a target oat file - // at a time. The Lock is released automatically when the OatFileAssistant - // object goes out of scope. The Lock() method must not be called if the - // lock has already been acquired. - // - // Returns true on success. - // Returns false on error, in which case error_msg will contain more - // information on the error. - // - // The 'error_msg' argument must not be null. - // - // This is intended to be used to avoid race conditions when multiple - // processes generate oat files, such as when a foreground Activity and - // a background Service both use DexClassLoaders pointing to the same dex - // file. - bool Lock(std::string* error_msg); - // Return what action needs to be taken to produce up-to-date code for this // dex location. If "downgrade" is set to false, it verifies if the current // compiler filter is at least as good as an oat file generated with the @@ -187,33 +164,6 @@ class OatFileAssistant { // irrespective of the compiler filter of the up-to-date code. bool IsUpToDate(); - // Return code used when attempting to generate updated code. - enum ResultOfAttemptToUpdate { - kUpdateFailed, // We tried making the code up to date, but - // encountered an unexpected failure. - kUpdateNotAttempted, // We wanted to update the code, but determined we - // should not make the attempt. - kUpdateSucceeded // We successfully made the code up to date - // (possibly by doing nothing). - }; - - // Attempts to generate or relocate the oat file as needed to make it up to - // date based on the current runtime and compiler options. - // profile_changed should be true to indicate the profile has recently - // changed for this dex location. - // - // If the dex files need to be made up to date, class_loader_context will be - // passed to dex2oat. - // - // Returns the result of attempting to update the code. - // - // If the result is not kUpdateSucceeded, the value of error_msg will be set - // to a string describing why there was a failure or the update was not - // attempted. error_msg must not be null. - ResultOfAttemptToUpdate MakeUpToDate(bool profile_changed, - ClassLoaderContext* class_loader_context, - std::string* error_msg); - // Returns an oat file that can be used for loading dex files. // Returns null if no suitable oat file was found. // @@ -284,18 +234,6 @@ class OatFileAssistant { // Returns the status of the oat file for the dex location. OatStatus OatFileStatus(); - // Executes dex2oat using the current runtime configuration overridden with - // the given arguments. This does not check to see if dex2oat is enabled in - // the runtime configuration. - // Returns true on success. - // - // If there is a failure, the value of error_msg will be set to a string - // describing why there was failure. error_msg must not be null. - // - // TODO: The OatFileAssistant probably isn't the right place to have this - // function. - static bool Dex2Oat(const std::vector<std::string>& args, std::string* error_msg); - // Constructs the odex file name for the given dex location. // Returns true on success, in which case odex_filename is set to the odex // file name. @@ -436,20 +374,6 @@ class OatFileAssistant { bool file_released_ = false; }; - // Generate the oat file for the given info from the dex file using the - // current runtime compiler options, the specified filter and class loader - // context. - // This does not check the current status before attempting to generate the - // oat file. - // - // If the result is not kUpdateSucceeded, the value of error_msg will be set - // to a string describing why there was a failure or the update was not - // attempted. error_msg must not be null. - ResultOfAttemptToUpdate GenerateOatFileNoChecks(OatFileInfo& info, - CompilerFilter::Filter target, - const ClassLoaderContext* class_loader_context, - std::string* error_msg); - // Return info for the best oat file. OatFileInfo& GetBestInfo(); @@ -473,13 +397,6 @@ class OatFileAssistant { // location. OatStatus GivenOatFileStatus(const OatFile& file); - // Returns the current image location. - // Returns an empty string if the image location could not be retrieved. - // - // TODO: This method should belong with an image file manager, not - // the oat file assistant. - static std::string ImageLocation(); - // Gets the dex checksums required for an up-to-date oat file. // Returns cached_required_dex_checksums if the required checksums were // located. Returns null if the required checksums were not found. The diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index 0b3c61d474..5889f8cc3e 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -41,11 +41,6 @@ namespace art { -static const std::string kSpecialSharedLibrary = "&"; // NOLINT [runtime/string] [4] -static ClassLoaderContext* kSpecialSharedLibraryContext = nullptr; - -static constexpr char kDex2oatCmdLineHiddenApiArg[] = " --runtime-arg -Xhidden-api-checks"; - class OatFileAssistantTest : public DexoptTest { public: void VerifyOptimizationStatus(const std::string& file, @@ -109,6 +104,97 @@ static bool IsExecutedAsRoot() { return geteuid() == 0; } +// Case: We have a MultiDEX file and up-to-date ODEX file for it with relative +// encoded dex locations. +// Expect: The oat file status is kNoDexOptNeeded. +TEST_F(OatFileAssistantTest, RelativeEncodedDexLocation) { + std::string dex_location = GetScratchDir() + "/RelativeEncodedDexLocation.jar"; + std::string odex_location = GetOdexDir() + "/RelativeEncodedDexLocation.odex"; + + // Create the dex file + Copy(GetMultiDexSrc1(), dex_location); + + // Create the oat file with relative encoded dex location. + std::vector<std::string> args = { + "--dex-file=" + dex_location, + "--dex-location=" + std::string("RelativeEncodedDexLocation.jar"), + "--oat-file=" + odex_location, + "--compiler-filter=speed" + }; + + std::string error_msg; + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; + + // Verify we can load both dex files. + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); + + std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); + ASSERT_TRUE(oat_file.get() != nullptr); + EXPECT_TRUE(oat_file->IsExecutable()); + std::vector<std::unique_ptr<const DexFile>> dex_files; + dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str()); + EXPECT_EQ(2u, dex_files.size()); +} + +TEST_F(OatFileAssistantTest, MakeUpToDateWithContext) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string odex_location = GetOdexDir() + "/TestDex.odex"; + std::string context_location = GetScratchDir() + "/ContextDex.jar"; + Copy(GetDexSrc1(), dex_location); + Copy(GetDexSrc2(), context_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + std::string context_str = "PCL[" + context_location + "]"; + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); + ASSERT_TRUE(context != nullptr); + ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); + + std::string error_msg; + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location); + args.push_back("--oat-file=" + odex_location); + args.push_back("--class-loader-context=" + context_str); + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; + + std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); + EXPECT_NE(nullptr, oat_file.get()); + EXPECT_EQ(context->EncodeContextForOatFile(""), + oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey)); +} + +TEST_F(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string odex_location = GetOdexDir() + "/TestDex.odex"; + std::string context_location = GetScratchDir() + "/ContextDex.jar"; + Copy(GetDexSrc1(), dex_location); + Copy(GetDexSrc2(), context_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); + + std::string context_str = "PCL[" + context_location + "]"; + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); + ASSERT_TRUE(context != nullptr); + ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); + + std::string error_msg; + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location); + args.push_back("--oat-file=" + odex_location); + args.push_back("--class-loader-context=" + context_str); + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; + + // A relative context simulates a dependent split context. + std::unique_ptr<ClassLoaderContext> relative_context = + ClassLoaderContext::Create("PCL[ContextDex.jar]"); + EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded( + CompilerFilter::kDefaultCompilerFilter, + /* downgrade */ false, + /* profile_changed */ false, + relative_context.get())); +} + // Case: We have a DEX file, but no OAT file for it. // Expect: The status is kDex2OatNeeded. TEST_F(OatFileAssistantTest, DexNoOat) { @@ -145,11 +231,6 @@ TEST_F(OatFileAssistantTest, NoDexNoOat) { oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); EXPECT_FALSE(oat_file_assistant.HasOriginalDexFiles()); - // Trying to make the oat file up to date should not fail or crash. - std::string error_msg; - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); - // Trying to get the best oat file should fail, but not crash. std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); EXPECT_EQ(nullptr, oat_file.get()); @@ -584,37 +665,6 @@ TEST_F(OatFileAssistantTest, StrippedMultiDexNonMainOutOfDate) { EXPECT_EQ(OatFileAssistant::kOatDexOutOfDate, oat_file_assistant.OatFileStatus()); } -// Case: We have a MultiDEX file and up-to-date ODEX file for it with relative -// encoded dex locations. -// Expect: The oat file status is kNoDexOptNeeded. -TEST_F(OatFileAssistantTest, RelativeEncodedDexLocation) { - std::string dex_location = GetScratchDir() + "/RelativeEncodedDexLocation.jar"; - std::string odex_location = GetOdexDir() + "/RelativeEncodedDexLocation.odex"; - - // Create the dex file - Copy(GetMultiDexSrc1(), dex_location); - - // Create the oat file with relative encoded dex location. - std::vector<std::string> args; - args.push_back("--dex-file=" + dex_location); - args.push_back("--dex-location=" + std::string("RelativeEncodedDexLocation.jar")); - args.push_back("--oat-file=" + odex_location); - args.push_back("--compiler-filter=speed"); - - std::string error_msg; - ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; - - // Verify we can load both dex files. - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); - - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - ASSERT_TRUE(oat_file.get() != nullptr); - EXPECT_TRUE(oat_file->IsExecutable()); - std::vector<std::unique_ptr<const DexFile>> dex_files; - dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str()); - EXPECT_EQ(2u, dex_files.size()); -} - // Case: We have a DEX file and an OAT file out of date with respect to the // dex checksum. TEST_F(OatFileAssistantTest, OatDexOutOfDate) { @@ -872,13 +922,6 @@ TEST_F(OatFileAssistantTest, ResourceOnlyDex) { EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); EXPECT_FALSE(oat_file_assistant.HasOriginalDexFiles()); - // Make the oat file up to date. This should have no effect. - std::string error_msg; - Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << - error_msg; - EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); @@ -1037,35 +1080,6 @@ TEST_F(OatFileAssistantTest, LoadNoExecOatUpToDate) { EXPECT_EQ(1u, dex_files.size()); } -// Case: We don't have a DEX file and can't write the oat file. -// Expect: We should fail to generate the oat file without crashing. -TEST_F(OatFileAssistantTest, GenNoDex) { - if (IsExecutedAsRoot()) { - // We cannot simulate non writable locations when executed as root: b/38000545. - LOG(ERROR) << "Test skipped because it's running as root"; - return; - } - - std::string dex_location = GetScratchDir() + "/GenNoDex.jar"; - - ScopedNonWritable scoped_non_writable(dex_location); - ASSERT_TRUE(scoped_non_writable.IsSuccessful()); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, true); - std::string error_msg; - Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); - // We should get kUpdateSucceeded from MakeUpToDate since there's nothing - // that can be done in this situation. - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); - - // Verify it didn't create an oat in the default location (dalvik-cache). - OatFileAssistant ofm(dex_location.c_str(), kRuntimeISA, false); - EXPECT_EQ(OatFileAssistant::kOatCannotOpen, ofm.OatFileStatus()); - // Verify it didn't create the odex file in the default location (../oat/isa/...odex) - EXPECT_EQ(OatFileAssistant::kOatCannotOpen, ofm.OdexFileStatus()); -} - // Turn an absolute path into a path relative to the current working // directory. static std::string MakePathRelative(const std::string& target) { @@ -1131,13 +1145,6 @@ TEST_F(OatFileAssistantTest, ShortDexLocation) { EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus()); EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); EXPECT_FALSE(oat_file_assistant.HasOriginalDexFiles()); - - // Trying to make it up to date should have no effect. - std::string error_msg; - Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); - EXPECT_TRUE(error_msg.empty()); } // Case: Non-standard extension for dex file. @@ -1156,11 +1163,12 @@ TEST_F(OatFileAssistantTest, LongDexExtension) { EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); } + // A task to generate a dex location. Used by the RaceToGenerate test. class RaceGenerateTask : public Task { public: - explicit RaceGenerateTask(const std::string& dex_location, const std::string& oat_location) - : dex_location_(dex_location), oat_location_(oat_location), loaded_oat_file_(nullptr) + RaceGenerateTask(const std::string& dex_location, const std::string& oat_location) + : dex_location_(dex_location), oat_location_(oat_location), loaded_oat_file_(nullptr) {} void Run(Thread* self ATTRIBUTE_UNUSED) { @@ -1169,6 +1177,15 @@ class RaceGenerateTask : public Task { std::vector<std::unique_ptr<const DexFile>> dex_files; std::vector<std::string> error_msgs; const OatFile* oat_file = nullptr; + { + // Create the oat file. + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location_); + args.push_back("--oat-file=" + oat_location_); + std::string error_msg; + ASSERT_TRUE(DexoptTest::Dex2Oat(args, &error_msg)) << error_msg; + } + dex_files = Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat( dex_location_.c_str(), Runtime::Current()->GetSystemClassLoader(), @@ -1176,8 +1193,9 @@ class RaceGenerateTask : public Task { &oat_file, &error_msgs); CHECK(!dex_files.empty()) << android::base::Join(error_msgs, '\n'); - CHECK(dex_files[0]->GetOatDexFile() != nullptr) << dex_files[0]->GetLocation(); - loaded_oat_file_ = dex_files[0]->GetOatDexFile()->GetOatFile(); + if (dex_files[0]->GetOatDexFile() != nullptr) { + loaded_oat_file_ = dex_files[0]->GetOatDexFile()->GetOatFile(); + } CHECK_EQ(loaded_oat_file_, oat_file); } @@ -1191,12 +1209,8 @@ class RaceGenerateTask : public Task { const OatFile* loaded_oat_file_; }; -// Test the case where multiple processes race to generate an oat file. -// This simulates multiple processes using multiple threads. -// -// We want unique Oat files to be loaded even when there is a race to load. -// TODO: The test case no longer tests locking the way it was intended since we now get multiple -// copies of the same Oat files mapped at different locations. +// Test the case where dex2oat invocations race with multiple processes trying to +// load the oat file. TEST_F(OatFileAssistantTest, RaceToGenerate) { std::string dex_location = GetScratchDir() + "/RaceToGenerate.jar"; std::string oat_location = GetOdexDir() + "/RaceToGenerate.oat"; @@ -1209,24 +1223,26 @@ TEST_F(OatFileAssistantTest, RaceToGenerate) { // take a while to generate. Copy(GetLibCoreDexFileNames()[0], dex_location); - const int kNumThreads = 32; + const size_t kNumThreads = 32; Thread* self = Thread::Current(); ThreadPool thread_pool("Oat file assistant test thread pool", kNumThreads); std::vector<std::unique_ptr<RaceGenerateTask>> tasks; - for (int i = 0; i < kNumThreads; i++) { + for (size_t i = 0; i < kNumThreads; i++) { std::unique_ptr<RaceGenerateTask> task(new RaceGenerateTask(dex_location, oat_location)); thread_pool.AddTask(self, task.get()); tasks.push_back(std::move(task)); } thread_pool.StartWorkers(self); - thread_pool.Wait(self, true, false); + thread_pool.Wait(self, /* do_work */ true, /* may_hold_locks */ false); - // Verify every task got a unique oat file. + // Verify that tasks which got an oat file got a unique one. std::set<const OatFile*> oat_files; for (auto& task : tasks) { const OatFile* oat_file = task->GetLoadedOatFile(); - EXPECT_TRUE(oat_files.find(oat_file) == oat_files.end()); - oat_files.insert(oat_file); + if (oat_file != nullptr) { + EXPECT_TRUE(oat_files.find(oat_file) == oat_files.end()); + oat_files.insert(oat_file); + } } } @@ -1274,36 +1290,6 @@ TEST_F(OatFileAssistantNoDex2OatTest, LoadMultiDexOdexNoOat) { EXPECT_EQ(2u, dex_files.size()); } -TEST_F(OatFileAssistantTest, RuntimeCompilerFilterOptionUsed) { - std::string dex_location = GetScratchDir() + "/RuntimeCompilerFilterOptionUsed.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - std::string error_msg; - Runtime::Current()->AddCompilerOption("--compiler-filter=quicken"); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << - error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken)); - EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter, - oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); - - Runtime::Current()->AddCompilerOption("--compiler-filter=speed"); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) - << error_msg; - EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken)); - EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); - - Runtime::Current()->AddCompilerOption("--compiler-filter=bogus"); - EXPECT_EQ(OatFileAssistant::kUpdateNotAttempted, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)); -} - TEST(OatFileAssistantUtilsTest, DexLocationToOdexFilename) { std::string error_msg; std::string odex_file; @@ -1350,112 +1336,6 @@ TEST_F(OatFileAssistantTest, DexOptStatusValues) { } } -// Verify that when no compiler filter is passed the default one from OatFileAssistant is used. -TEST_F(OatFileAssistantTest, DefaultMakeUpToDateFilter) { - std::string dex_location = GetScratchDir() + "/TestDex.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; - std::string error_msg; - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, - oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) << - error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(default_filter)); - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - EXPECT_EQ(default_filter, oat_file->GetCompilerFilter()); -} - -TEST_F(OatFileAssistantTest, MakeUpToDateWithSpecialSharedLibrary) { - std::string dex_location = GetScratchDir() + "/TestDex.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; - std::string error_msg; - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(default_filter)); - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - EXPECT_EQ(kSpecialSharedLibrary, - oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey)); -} - -TEST_F(OatFileAssistantTest, MakeUpToDateWithContext) { - std::string dex_location = GetScratchDir() + "/TestDex.jar"; - std::string context_location = GetScratchDir() + "/ContextDex.jar"; - Copy(GetDexSrc1(), dex_location); - Copy(GetDexSrc2(), context_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; - std::string error_msg; - std::string context_str = "PCL[" + context_location + "]"; - std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); - ASSERT_TRUE(context != nullptr); - ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); - - int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get())); - - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - EXPECT_EQ(context->EncodeContextForOatFile(""), - oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey)); -} - -TEST_F(OatFileAssistantTest, MakeUpToDateWithHiddenApiDisabled) { - hiddenapi::ScopedHiddenApiEnforcementPolicySetting hiddenapi_exemption( - hiddenapi::EnforcementPolicy::kNoChecks); - - std::string dex_location = GetScratchDir() + "/TestDexHiddenApiDisabled.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - std::string error_msg; - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - - const char* cmd_line = oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kDex2OatCmdLineKey); - EXPECT_NE(nullptr, cmd_line); - EXPECT_EQ(nullptr, strstr(cmd_line, kDex2oatCmdLineHiddenApiArg)); -} - -TEST_F(OatFileAssistantTest, MakeUpToDateWithHiddenApiEnabled) { - hiddenapi::ScopedHiddenApiEnforcementPolicySetting hiddenapi_exemption( - hiddenapi::EnforcementPolicy::kBlacklistOnly); - - std::string dex_location = GetScratchDir() + "/TestDexHiddenApiEnabled.jar"; - Copy(GetDexSrc1(), dex_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - std::string error_msg; - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - - std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); - EXPECT_NE(nullptr, oat_file.get()); - - const char* cmd_line = oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kDex2OatCmdLineKey); - EXPECT_NE(nullptr, cmd_line); - EXPECT_NE(nullptr, strstr(cmd_line, kDex2oatCmdLineHiddenApiArg)); -} - TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) { std::string dex_location = GetScratchDir() + "/TestDex.jar"; std::string context_location = GetScratchDir() + "/ContextDex.jar"; @@ -1464,19 +1344,12 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) { OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; std::string error_msg; std::string context_str = "PCL[" + context_location + "]"; std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); ASSERT_TRUE(context != nullptr); ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); - int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get())); - // Update the context by overriding the jar file. Copy(GetMultiDexSrc2(), context_location); std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str); @@ -1484,88 +1357,10 @@ TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) { // DexOptNeeded should advise compilation from scratch. EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, oat_file_assistant.GetDexOptNeeded( - default_filter, false, false, updated_context.get())); -} - -TEST_F(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) { - std::string dex_location = GetScratchDir() + "/TestDex.jar"; - std::string context_location = GetScratchDir() + "/ContextDex.jar"; - Copy(GetDexSrc1(), dex_location); - Copy(GetDexSrc2(), context_location); - - OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false); - - const CompilerFilter::Filter default_filter = - OatFileAssistant::kDefaultCompilerFilterForDexLoading; - std::string error_msg; - std::string context_str = "PCL[" + context_location + "]"; - std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str); - ASSERT_TRUE(context != nullptr); - ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, "")); - - int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg); - EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - - // A relative context simulates a dependent split context. - std::unique_ptr<ClassLoaderContext> relative_context = - ClassLoaderContext::Create("PCL[ContextDex.jar]"); - EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded, - oat_file_assistant.GetDexOptNeeded( - default_filter, false, false, relative_context.get())); -} - -TEST_F(OatFileAssistantTest, SystemOdex) { - std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; - std::string odex_location = GetScratchDir() + "/OatUpToDate.odex"; - std::string system_location = GetAndroidRoot() + "/OatUpToDate.jar"; - - std::string error_msg; - - Copy(GetDexSrc1(), dex_location); - EXPECT_FALSE(LocationIsOnSystem(dex_location.c_str())); - - { - OatFileAssistant oat_file_assistant(dex_location.c_str(), - kRuntimeISA, - true, - false); - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); - } - - { - OatFileAssistant oat_file_assistant(dex_location.c_str(), - kRuntimeISA, - true, - true); - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_FALSE(oat_file_assistant.GetBestOatFile()->IsExecutable()); - } - - Copy(GetDexSrc1(), system_location); - EXPECT_TRUE(LocationIsOnSystem(system_location.c_str())); - - { - OatFileAssistant oat_file_assistant(system_location.c_str(), - kRuntimeISA, - true, - false); - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); - } - - { - OatFileAssistant oat_file_assistant(system_location.c_str(), - kRuntimeISA, - true, - true); - int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg); - ASSERT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg; - EXPECT_TRUE(oat_file_assistant.GetBestOatFile()->IsExecutable()); - } + CompilerFilter::kDefaultCompilerFilter, + /* downgrade */ false, + /* profile_changed */ false, + updated_context.get())); } // TODO: More Tests: diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 59a1045ba2..bcad4a3428 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -465,57 +465,15 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( !runtime->IsAotCompiler(), only_use_system_oat_files_); - // Lock the target oat location to avoid races generating and loading the - // oat file. - std::string error_msg; - if (!oat_file_assistant.Lock(/*out*/&error_msg)) { - // Don't worry too much if this fails. If it does fail, it's unlikely we - // can generate an oat file anyway. - VLOG(class_linker) << "OatFileAssistant::Lock: " << error_msg; - } - - const OatFile* source_oat_file = nullptr; - - if (!oat_file_assistant.IsUpToDate()) { - // Update the oat file on disk if we can, based on the --compiler-filter - // option derived from the current runtime options. - // This may fail, but that's okay. Best effort is all that matters here. - // TODO(calin): b/64530081 b/66984396. Pass a null context to verify and compile - // secondary dex files in isolation (and avoid to extract/verify the main apk - // if it's in the class path). Note this trades correctness for performance - // since the resulting slow down is unacceptable in some cases until b/64530081 - // is fixed. - // We still pass the class loader context when the classpath string of the runtime - // is not empty, which is the situation when ART is invoked standalone. - ClassLoaderContext* actual_context = Runtime::Current()->GetClassPathString().empty() - ? nullptr - : context.get(); - switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/ false, - actual_context, - /*out*/ &error_msg)) { - case OatFileAssistant::kUpdateFailed: - LOG(WARNING) << error_msg; - break; - - case OatFileAssistant::kUpdateNotAttempted: - // Avoid spamming the logs if we decided not to attempt making the oat - // file up to date. - VLOG(oat) << error_msg; - break; - - case OatFileAssistant::kUpdateSucceeded: - // Nothing to do. - break; - } - } - // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release()); VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()=" << reinterpret_cast<uintptr_t>(oat_file.get()) << " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")"; + const OatFile* source_oat_file = nullptr; CheckCollisionResult check_collision_result = CheckCollisionResult::kPerformedHasCollisions; + std::string error_msg; if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { // Prevent oat files from being loaded if no class_loader or dex_elements are provided. // This can happen when the deprecated DexFile.<init>(String) is called directly, and it diff --git a/test/138-duplicate-classes-check2/run b/test/138-duplicate-classes-check2/run deleted file mode 100755 index 8494ad9aad..0000000000 --- a/test/138-duplicate-classes-check2/run +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2015 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. - -# We want to run as no-dex-file-fallback to confirm that even though the -ex file has a symbolic -# reference to A, there's no class-def, so we don't detect a collision. -exec ${RUN} --runtime-option -Xno-dex-file-fallback "${@}" diff --git a/test/677-fsi/expected.txt b/test/677-fsi/expected.txt index c7fb8fed77..2b073430b6 100644 --- a/test/677-fsi/expected.txt +++ b/test/677-fsi/expected.txt @@ -1,3 +1,2 @@ oat file has dex code, but APK has uncompressed dex code -oat file has dex code, but APK has uncompressed dex code Hello World diff --git a/test/718-zipfile-finalizer/expected.txt b/test/718-zipfile-finalizer/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/718-zipfile-finalizer/expected.txt diff --git a/test/718-zipfile-finalizer/info.txt b/test/718-zipfile-finalizer/info.txt new file mode 100644 index 0000000000..c8b827e63c --- /dev/null +++ b/test/718-zipfile-finalizer/info.txt @@ -0,0 +1,2 @@ +Test that ZipFile.finalize doesn't throw exceptions +in the presence of a not fully constructed instance. diff --git a/test/718-zipfile-finalizer/src/Main.java b/test/718-zipfile-finalizer/src/Main.java new file mode 100644 index 0000000000..3eb439b7a6 --- /dev/null +++ b/test/718-zipfile-finalizer/src/Main.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 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. + */ + +import java.util.zip.ZipFile; + +public class Main { + public static void main(String[] args) throws Exception { + // By throwing an exception when setting up arguments of + // the constructor, we end up with a not fully constructed + // ZipFile. + try { + new ZipFile(null, throwException(), null); + throw new Error("Expected Exception"); + } catch (Exception e) { + // expected + } + + // Run finalizers. The golden file of this test checks + // that no exception is thrown from finalizers. + System.gc(); + System.runFinalization(); + } + + public static int throwException() throws Exception { + throw new Exception(); + } +} diff --git a/tools/dexanalyze/dexanalyze_bytecode.cc b/tools/dexanalyze/dexanalyze_bytecode.cc index e6e58c0ecc..659a940e8b 100644 --- a/tools/dexanalyze/dexanalyze_bytecode.cc +++ b/tools/dexanalyze/dexanalyze_bytecode.cc @@ -264,18 +264,18 @@ void NewRegisterInstructions::ProcessCodeItem(const DexFile& dex_file, uint32_t receiver = inst->VRegB_22c(); uint32_t first_arg_reg = code_item.RegistersSize() - code_item.InsSize(); uint32_t out_reg = inst->VRegA_22c(); - if (Enabled(kExperimentInstanceFieldSelf) && - first_arg_reg == receiver && - holder_type == current_class_type) { - if (count_types) { - ++current_type.fields_.FindOrAdd(dex_field_idx)->second; - } else { - uint32_t field_idx = types[holder_type.index_].fields_.Get(dex_field_idx); - ExtendPrefix(&out_reg, &field_idx); - CHECK(InstNibbles(new_opcode, {out_reg, field_idx})); - continue; - } - } else if (Enabled(kExperimentInstanceField)) { + if (Enabled(kExperimentInstanceFieldSelf) && + first_arg_reg == receiver && + holder_type == current_class_type) { + if (count_types) { + ++current_type.fields_.FindOrAdd(dex_field_idx)->second; + } else { + uint32_t field_idx = types[holder_type.index_].fields_.Get(dex_field_idx); + ExtendPrefix(&out_reg, &field_idx); + CHECK(InstNibbles(new_opcode, {out_reg, field_idx})); + continue; + } + } else if (Enabled(kExperimentInstanceField)) { if (count_types) { ++current_type.types_.FindOrAdd(holder_type.index_)->second; ++types[holder_type.index_].fields_.FindOrAdd(dex_field_idx)->second; diff --git a/tools/dexanalyze/dexanalyze_strings.cc b/tools/dexanalyze/dexanalyze_strings.cc index 9f67ff431a..863e4ee4b3 100644 --- a/tools/dexanalyze/dexanalyze_strings.cc +++ b/tools/dexanalyze/dexanalyze_strings.cc @@ -19,6 +19,7 @@ #include <algorithm> #include <iomanip> #include <iostream> +#include <queue> #include "dex/class_accessor-inl.h" #include "dex/code_item_accessors-inl.h" @@ -27,8 +28,171 @@ namespace art { namespace dexanalyze { +// Tunable parameters. +static const size_t kMinPrefixLen = 1; +static const size_t kMaxPrefixLen = 255; +static const size_t kPrefixConstantCost = 4; +static const size_t kPrefixIndexCost = 2; + +// Node value = (distance from root) * (occurrences - 1). +class MatchTrie { + public: + void Add(const std::string& str) { + MatchTrie* node = this; + size_t depth = 0u; + for (uint8_t c : str) { + ++depth; + if (node->nodes_[c] == nullptr) { + MatchTrie* new_node = new MatchTrie(); + node->nodes_[c].reset(new_node); + new_node->parent_ = node; + new_node->depth_ = depth; + new_node->incoming_ = c; + node = new_node; + } else { + node = node->nodes_[c].get(); + } + ++node->count_; + } + node->is_end_ = true; + } + + // Returns the length of the longest prefix and if it's a leaf node. + std::pair<size_t, bool> LongestPrefix(const std::string& str) const { + const MatchTrie* node = this; + const MatchTrie* best_node = this; + size_t depth = 0u; + size_t best_depth = 0u; + for (uint8_t c : str) { + if (node->nodes_[c] == nullptr) { + break; + } + node = node->nodes_[c].get(); + ++depth; + if (node->is_end_) { + best_depth = depth; + best_node = node; + } + } + bool is_leaf = true; + for (const std::unique_ptr<MatchTrie>& cur_node : best_node->nodes_) { + if (cur_node != nullptr) { + is_leaf = false; + } + } + return {best_depth, is_leaf}; + } + + int32_t Savings() const { + int32_t cost = kPrefixConstantCost; + int32_t first_used = 0u; + if (chosen_suffix_count_ == 0u) { + cost += depth_; + } + uint32_t extra_savings = 0u; + for (MatchTrie* cur = parent_; cur != nullptr; cur = cur->parent_) { + if (cur->chosen_) { + first_used = cur->depth_; + if (cur->chosen_suffix_count_ == 0u) { + // First suffix for the chosen parent, remove the cost of the dictionary entry. + extra_savings += first_used; + } + break; + } + } + return count_ * (depth_ - first_used) - cost + extra_savings; + } + + template <typename T, typename... Args, template <typename...> class Queue> + T PopRealTop(Queue<T, Args...>& queue) { + auto pair = queue.top(); + queue.pop(); + // Keep updating values until one sticks. + while (pair.second->Savings() != pair.first) { + pair.first = pair.second->Savings(); + queue.push(pair); + pair = queue.top(); + queue.pop(); + } + return pair; + } + + std::vector<std::string> ExtractPrefixes(size_t max) { + std::vector<std::string> ret; + // Make priority queue and adaptively update it. Each node value is the savings from picking + // it. Insert all of the interesting nodes in the queue (children != 1). + std::priority_queue<std::pair<int32_t, MatchTrie*>> queue; + // Add all of the nodes to the queue. + std::vector<MatchTrie*> work(1, this); + while (!work.empty()) { + MatchTrie* elem = work.back(); + work.pop_back(); + size_t num_childs = 0u; + for (const std::unique_ptr<MatchTrie>& child : elem->nodes_) { + if (child != nullptr) { + work.push_back(child.get()); + ++num_childs; + } + } + if (num_childs > 1u || elem->is_end_) { + queue.emplace(elem->Savings(), elem); + } + } + std::priority_queue<std::pair<int32_t, MatchTrie*>> prefixes; + // The savings can only ever go down for a given node, never up. + while (max != 0u && !queue.empty()) { + std::pair<int32_t, MatchTrie*> pair = PopRealTop(queue); + if (pair.second != this && pair.first > 0) { + // Pick this node. + uint32_t count = pair.second->count_; + pair.second->chosen_ = true; + for (MatchTrie* cur = pair.second->parent_; cur != this; cur = cur->parent_) { + if (cur->chosen_) { + break; + } + cur->count_ -= count; + } + for (MatchTrie* cur = pair.second->parent_; cur != this; cur = cur->parent_) { + ++cur->chosen_suffix_count_; + } + prefixes.emplace(pair.first, pair.second); + --max; + } else { + // Negative or no EV, just delete the node. + } + } + while (!prefixes.empty()) { + std::pair<int32_t, MatchTrie*> pair = PopRealTop(prefixes); + if (pair.first <= 0) { + continue; + } + std::vector<uint8_t> chars; + for (MatchTrie* cur = pair.second; cur != this; cur = cur->parent_) { + chars.push_back(cur->incoming_); + } + ret.push_back(std::string(chars.rbegin(), chars.rend())); + // LOG(INFO) << pair.second->Savings() << " : " << ret.back(); + } + return ret; + } + + std::unique_ptr<MatchTrie> nodes_[256]; + MatchTrie* parent_ = nullptr; + uint32_t count_ = 0u; + int32_t depth_ = 0u; + int32_t savings_ = 0u; + uint8_t incoming_ = 0u; + // If the current node is the end of a possible prefix. + bool is_end_ = false; + // If the current node is chosen to be a used prefix. + bool chosen_ = false; + // If the current node is a prefix of a longer chosen prefix. + uint32_t chosen_suffix_count_ = 0u; +}; + void AnalyzeStrings::ProcessDexFiles(const std::vector<std::unique_ptr<const DexFile>>& dex_files) { std::set<std::string> unique_strings; + // Accumulate the strings. for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { for (size_t i = 0; i < dex_file->NumStringIds(); ++i) { uint32_t length = 0; @@ -49,18 +213,17 @@ void AnalyzeStrings::ProcessDexFiles(const std::vector<std::unique_ptr<const Dex } } // Unique strings only since we want to exclude savings from multidex duplication. - std::vector<std::string> strings(unique_strings.begin(), unique_strings.end()); - unique_strings.clear(); - - // Tunable parameters. - static const size_t kMinPrefixLen = 1; - static const size_t kMaxPrefixLen = 255; - static const size_t kPrefixConstantCost = 4; - static const size_t kPrefixIndexCost = 2; + ProcessStrings(std::vector<std::string>(unique_strings.begin(), unique_strings.end()), 1); +} +void AnalyzeStrings::ProcessStrings(const std::vector<std::string>& strings, size_t iterations) { + if (iterations == 0u) { + return; + } // Calculate total shared prefix. std::vector<size_t> shared_len; prefixes_.clear(); + std::unique_ptr<MatchTrie> prefix_construct(new MatchTrie()); for (size_t i = 0; i < strings.size(); ++i) { size_t best_len = 0; if (i > 0) { @@ -73,62 +236,104 @@ void AnalyzeStrings::ProcessDexFiles(const std::vector<std::unique_ptr<const Dex std::string prefix; if (best_len >= kMinPrefixLen) { prefix = strings[i].substr(0, best_len); + prefix_construct->Add(prefix); ++prefixes_[prefix]; + total_shared_prefix_bytes_ += best_len; } total_prefix_index_cost_ += kPrefixIndexCost; } - // Optimize the result by moving long prefixes to shorter ones if it causes savings. - while (true) { - bool have_savings = false; - auto it = prefixes_.begin(); - std::vector<std::string> longest; - for (const auto& pair : prefixes_) { - longest.push_back(pair.first); - } - std::sort(longest.begin(), longest.end(), [](const std::string& a, const std::string& b) { - return a.length() > b.length(); - }); - // Do longest first since this provides the best results. - for (const std::string& s : longest) { - it = prefixes_.find(s); - CHECK(it != prefixes_.end()); - const std::string& prefix = it->first; - int64_t best_savings = 0u; - int64_t best_len = -1; - for (int64_t len = prefix.length() - 1; len >= 0; --len) { - auto found = prefixes_.find(prefix.substr(0, len)); - if (len != 0 && found == prefixes_.end()) { - continue; + + static constexpr size_t kPrefixBits = 15; + static constexpr size_t kShortLen = (1u << (15 - kPrefixBits)) - 1; + std::unique_ptr<MatchTrie> prefix_trie(new MatchTrie()); + static constexpr bool kUseGreedyTrie = true; + if (kUseGreedyTrie) { + std::vector<std::string> prefixes(prefix_construct->ExtractPrefixes(1 << kPrefixBits)); + for (auto&& str : prefixes) { + prefix_trie->Add(str); + } + } else { + // Optimize the result by moving long prefixes to shorter ones if it causes additional savings. + while (true) { + bool have_savings = false; + auto it = prefixes_.begin(); + std::vector<std::string> longest; + for (const auto& pair : prefixes_) { + longest.push_back(pair.first); + } + std::sort(longest.begin(), longest.end(), [](const std::string& a, const std::string& b) { + return a.length() > b.length(); + }); + // Do longest first since this provides the best results. + for (const std::string& s : longest) { + it = prefixes_.find(s); + CHECK(it != prefixes_.end()); + const std::string& prefix = it->first; + int64_t best_savings = 0u; + int64_t best_len = -1; + for (int64_t len = prefix.length() - 1; len >= 0; --len) { + auto found = prefixes_.find(prefix.substr(0, len)); + if (len != 0 && found == prefixes_.end()) { + continue; + } + // Calculate savings from downgrading the prefix. + int64_t savings = kPrefixConstantCost + prefix.length() - + (prefix.length() - len) * it->second; + if (savings > best_savings) { + best_savings = savings; + best_len = len; + break; + } } - // Calculate savings from downgrading the prefix. - int64_t savings = kPrefixConstantCost + prefix.length() - - (prefix.length() - len) * it->second; - if (savings > best_savings) { - best_savings = savings; - best_len = len; - break; + if (best_len != -1) { + prefixes_[prefix.substr(0, best_len)] += it->second; + it = prefixes_.erase(it); + optimization_savings_ += best_savings; + have_savings = true; + } else { + ++it; } } - if (best_len != -1) { - prefixes_[prefix.substr(0, best_len)] += it->second; - it = prefixes_.erase(it); - optimization_savings_ += best_savings; - have_savings = true; - } else { - ++it; + if (!have_savings) { + break; } } - if (!have_savings) { - break; + for (auto&& pair : prefixes_) { + prefix_trie->Add(pair.first); + } + } + + // Count longest prefixes. + std::set<std::string> used_prefixes; + std::vector<std::string> suffix; + for (const std::string& str : strings) { + auto pair = prefix_trie->LongestPrefix(str); + const size_t len = pair.first; + if (len >= kMinPrefixLen) { + ++strings_used_prefixed_; + total_prefix_savings_ += len; + used_prefixes.insert(str.substr(0, len)); + } + suffix.push_back(str.substr(len)); + if (suffix.back().size() < kShortLen) { + ++short_strings_; + } else { + ++long_strings_; } } - total_num_prefixes_ += prefixes_.size(); - for (const auto& pair : prefixes_) { + std::sort(suffix.begin(), suffix.end()); + for (const std::string& prefix : used_prefixes) { // 4 bytes for an offset, one for length. - total_prefix_dict_ += pair.first.length(); + auto pair = prefix_trie->LongestPrefix(prefix); + CHECK_EQ(pair.first, prefix.length()); + if (pair.second) { + // Only need to add to dictionary if it's a leaf, otherwise we can reuse string data of the + // other prefix. + total_prefix_dict_ += prefix.size(); + } total_prefix_table_ += kPrefixConstantCost; - total_prefix_savings_ += pair.first.length() * pair.second; } + ProcessStrings(suffix, iterations - 1); } void AnalyzeStrings::Dump(std::ostream& os, uint64_t total_size) const { @@ -137,17 +342,20 @@ void AnalyzeStrings::Dump(std::ostream& os, uint64_t total_size) const { os << "ASCII string data bytes " << Percent(ascii_string_bytes_, total_size) << "\n"; // Prefix based strings. - os << "Total shared prefix bytes " << Percent(total_prefix_savings_, total_size) << "\n"; + os << "Total shared prefix bytes " << Percent(total_shared_prefix_bytes_, total_size) << "\n"; os << "Prefix dictionary cost " << Percent(total_prefix_dict_, total_size) << "\n"; os << "Prefix table cost " << Percent(total_prefix_table_, total_size) << "\n"; os << "Prefix index cost " << Percent(total_prefix_index_cost_, total_size) << "\n"; - int64_t net_savings = total_prefix_savings_; + int64_t net_savings = total_prefix_savings_ + short_strings_; net_savings -= total_prefix_dict_; net_savings -= total_prefix_table_; net_savings -= total_prefix_index_cost_; os << "Prefix dictionary elements " << total_num_prefixes_ << "\n"; os << "Optimization savings " << Percent(optimization_savings_, total_size) << "\n"; os << "Prefix net savings " << Percent(net_savings, total_size) << "\n"; + os << "Strings using prefix " + << Percent(strings_used_prefixed_, total_prefix_index_cost_ / kPrefixIndexCost) << "\n"; + os << "Short strings " << Percent(short_strings_, short_strings_ + long_strings_) << "\n"; if (verbose_level_ >= VerboseLevel::kEverything) { std::vector<std::pair<std::string, size_t>> pairs(prefixes_.begin(), prefixes_.end()); // Sort lexicographically. diff --git a/tools/dexanalyze/dexanalyze_strings.h b/tools/dexanalyze/dexanalyze_strings.h index 3559afaff7..32702a60cb 100644 --- a/tools/dexanalyze/dexanalyze_strings.h +++ b/tools/dexanalyze/dexanalyze_strings.h @@ -36,15 +36,21 @@ class AnalyzeStrings : public Experiment { void Dump(std::ostream& os, uint64_t total_size) const override; private: + void ProcessStrings(const std::vector<std::string>& strings, size_t iterations); + int64_t wide_string_bytes_ = 0u; int64_t ascii_string_bytes_ = 0u; int64_t string_data_bytes_ = 0u; + int64_t total_shared_prefix_bytes_ = 0u; int64_t total_prefix_savings_ = 0u; int64_t total_prefix_dict_ = 0u; int64_t total_prefix_table_ = 0u; int64_t total_prefix_index_cost_ = 0u; int64_t total_num_prefixes_ = 0u; int64_t optimization_savings_ = 0u; + int64_t strings_used_prefixed_ = 0u; + int64_t short_strings_ = 0u; + int64_t long_strings_ = 0u; std::unordered_map<std::string, size_t> prefixes_; }; diff --git a/tools/jfuzz/Android.bp b/tools/jfuzz/Android.bp new file mode 100644 index 0000000000..f0d8b3779d --- /dev/null +++ b/tools/jfuzz/Android.bp @@ -0,0 +1,29 @@ +// Copyright (C) 2016 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. + +// Fuzzer tool. +cc_binary_host { + name: "jfuzz", + srcs: ["jfuzz.cc"], + cflags: [ + "-O0", + "-g", + "-Wall", + ], + target: { + windows: { + enabled: true, + }, + }, +} diff --git a/tools/jfuzz/Android.mk b/tools/jfuzz/Android.mk deleted file mode 100644 index c7002d67ec..0000000000 --- a/tools/jfuzz/Android.mk +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (C) 2016 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. - -# Fuzzer tool. - -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_CPP_EXTENSION := cc -LOCAL_SRC_FILES := jfuzz.cc -LOCAL_CFLAGS += -O0 -g -Wall -LOCAL_MODULE_HOST_OS := darwin linux windows -LOCAL_MODULE := jfuzz -include $(BUILD_HOST_EXECUTABLE) |