diff options
| author | 2021-06-28 17:52:05 -0700 | |
|---|---|---|
| committer | 2021-08-27 17:58:41 -0700 | |
| commit | 65578d6afabcd47d7bcd19d92b5fb330affacf0b (patch) | |
| tree | d0538231d8d799c11f1b66f3276fc8e455151f48 | |
| parent | 0bd3275a71e2e5a5dd6b08a16be92e2e1e775664 (diff) | |
add IInstalld.controlDexOptBlocking call
- This allows cancelling pending dexoptimization by killng the currently
running dex2oat process and block future dexopt calls.
- As the only client is system server and installd restarts, it relies on
the client to restore the state to unblcoked state properly.
Bug: 179094324
Bug: 156537504
Test: Run added test with existing tests
atest installd_dexopt_test installd_service_test
Change-Id: I73bff6e4cd4473b57d0f06b062dd035dbda6b496
| -rw-r--r-- | cmds/installd/InstalldNativeService.cpp | 15 | ||||
| -rw-r--r-- | cmds/installd/InstalldNativeService.h | 5 | ||||
| -rw-r--r-- | cmds/installd/binder/android/os/IInstalld.aidl | 6 | ||||
| -rw-r--r-- | cmds/installd/dexopt.cpp | 177 | ||||
| -rw-r--r-- | cmds/installd/dexopt.h | 9 | ||||
| -rw-r--r-- | cmds/installd/tests/installd_dexopt_test.cpp | 78 |
6 files changed, 259 insertions, 31 deletions
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp index a176df9789..d55a927e8d 100644 --- a/cmds/installd/InstalldNativeService.cpp +++ b/cmds/installd/InstalldNativeService.cpp @@ -307,6 +307,8 @@ status_t InstalldNativeService::dump(int fd, const Vector<String16> & /* args */ } } + out << "is_dexopt_blocked:" << android::installd::is_dexopt_blocked() << endl; + out << endl; out.flush(); @@ -2399,7 +2401,8 @@ binder::Status InstalldNativeService::dexopt(const std::string& apkPath, int32_t const std::optional<std::string>& seInfo, bool downgrade, int32_t targetSdkVersion, const std::optional<std::string>& profileName, const std::optional<std::string>& dexMetadataPath, - const std::optional<std::string>& compilationReason) { + const std::optional<std::string>& compilationReason, + bool* aidl_return) { ENFORCE_UID(AID_SYSTEM); CHECK_ARGUMENT_UUID(uuid); CHECK_ARGUMENT_PATH(apkPath); @@ -2427,12 +2430,20 @@ binder::Status InstalldNativeService::dexopt(const std::string& apkPath, int32_t const char* dm_path = getCStr(dexMetadataPath); const char* compilation_reason = getCStr(compilationReason); std::string error_msg; + bool completed = false; // not necessary but for compiler int res = android::installd::dexopt(apk_path, uid, pkgname, instruction_set, dexoptNeeded, oat_dir, dexFlags, compiler_filter, volume_uuid, class_loader_context, se_info, - downgrade, targetSdkVersion, profile_name, dm_path, compilation_reason, &error_msg); + downgrade, targetSdkVersion, profile_name, dm_path, compilation_reason, &error_msg, + &completed); + *aidl_return = completed; return res ? error(res, error_msg) : ok(); } +binder::Status InstalldNativeService::controlDexOptBlocking(bool block) { + android::installd::control_dexopt_blocking(block); + return ok(); +} + binder::Status InstalldNativeService::compileLayouts(const std::string& apkPath, const std::string& packageName, const std ::string& outDexFile, int uid, diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h index 3127be6fd5..480e41b67f 100644 --- a/cmds/installd/InstalldNativeService.h +++ b/cmds/installd/InstalldNativeService.h @@ -114,7 +114,10 @@ public: const std::optional<std::string>& seInfo, bool downgrade, int32_t targetSdkVersion, const std::optional<std::string>& profileName, const std::optional<std::string>& dexMetadataPath, - const std::optional<std::string>& compilationReason); + const std::optional<std::string>& compilationReason, + bool* aidl_return); + + binder::Status controlDexOptBlocking(bool block); binder::Status compileLayouts(const std::string& apkPath, const std::string& packageName, const std::string& outDexFile, int uid, bool* _aidl_return); diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl index 816e50826d..b5d95c3271 100644 --- a/cmds/installd/binder/android/os/IInstalld.aidl +++ b/cmds/installd/binder/android/os/IInstalld.aidl @@ -57,7 +57,8 @@ interface IInstalld { @utf8InCpp String packageName, int appId, @utf8InCpp String seInfo, int targetSdkVersion, @utf8InCpp String fromCodePath); - void dexopt(@utf8InCpp String apkPath, int uid, @nullable @utf8InCpp String packageName, + // Returns false if it is cancelled. Returns true if it is completed or have other errors. + boolean dexopt(@utf8InCpp String apkPath, int uid, @nullable @utf8InCpp String packageName, @utf8InCpp String instructionSet, int dexoptNeeded, @nullable @utf8InCpp String outputPath, int dexFlags, @utf8InCpp String compilerFilter, @nullable @utf8InCpp String uuid, @@ -66,6 +67,9 @@ interface IInstalld { @nullable @utf8InCpp String profileName, @nullable @utf8InCpp String dexMetadataPath, @nullable @utf8InCpp String compilationReason); + // Blocks (when block is true) or unblock (when block is false) dexopt. + // Blocking also invloves cancelling the currently running dexopt. + void controlDexOptBlocking(boolean block); boolean compileLayouts(@utf8InCpp String apkPath, @utf8InCpp String packageName, @utf8InCpp String outDexFile, int uid); diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp index 210f977585..7bb9bef658 100644 --- a/cmds/installd/dexopt.cpp +++ b/cmds/installd/dexopt.cpp @@ -15,8 +15,8 @@ */ #define LOG_TAG "installd" -#include <array> #include <fcntl.h> +#include <signal.h> #include <stdlib.h> #include <string.h> #include <sys/capability.h> @@ -28,10 +28,14 @@ #include <sys/wait.h> #include <unistd.h> +#include <array> #include <iomanip> +#include <mutex> +#include <unordered_set> #include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/no_destructor.h> #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> @@ -47,6 +51,7 @@ #include <selinux/android.h> #include <server_configurable_flags/get_flags.h> #include <system/thread_defs.h> +#include <utils/Mutex.h> #include "dexopt.h" #include "dexopt_return_codes.h" @@ -69,6 +74,76 @@ using android::base::StringPrintf; using android::base::WriteFully; using android::base::unique_fd; +namespace { + +class DexOptStatus { + public: + // Check if dexopt is cancelled and fork if it is not cancelled. + // cancelled is set to true if cancelled. Otherwise it will be set to false. + // If it is not cancelled, it will return the return value of fork() call. + // If cancelled, fork will not happen and it will return -1. + pid_t check_cancellation_and_fork(/* out */ bool *cancelled) { + std::lock_guard<std::mutex> lock(dexopt_lock_); + if (dexopt_blocked_) { + *cancelled = true; + return -1; + } + pid_t pid = fork(); + *cancelled = false; + if (pid > 0) { // parent + dexopt_pids_.insert(pid); + } + return pid; + } + + // Returns true if pid was killed (is in killed list). It could have finished if killing + // happened after the process is finished. + bool check_if_killed_and_remove_dexopt_pid(pid_t pid) { + std::lock_guard<std::mutex> lock(dexopt_lock_); + dexopt_pids_.erase(pid); + if (dexopt_killed_pids_.erase(pid) == 1) { + return true; + } + return false; + } + + // Tells whether dexopt is blocked or not. + bool is_dexopt_blocked() { + std::lock_guard<std::mutex> lock(dexopt_lock_); + return dexopt_blocked_; + } + + // Enable or disable dexopt blocking. + void control_dexopt_blocking(bool block) { + std::lock_guard<std::mutex> lock(dexopt_lock_); + dexopt_blocked_ = block; + if (!block) { + return; + } + // Blocked, also kill currently running tasks + for (auto pid : dexopt_pids_) { + LOG(INFO) << "control_dexopt_blocking kill pid:" << pid; + kill(pid, SIGKILL); + dexopt_killed_pids_.insert(pid); + } + dexopt_pids_.clear(); + } + + private: + std::mutex dexopt_lock_; + // when true, dexopt is blocked and will not run. + bool dexopt_blocked_ GUARDED_BY(dexopt_lock_) = false; + // PIDs of child process while runinng dexopt. + // If the child process is finished, it should be removed. + std::unordered_set<pid_t> dexopt_pids_ GUARDED_BY(dexopt_lock_); + // PIDs of child processes killed by cancellation. + std::unordered_set<pid_t> dexopt_killed_pids_ GUARDED_BY(dexopt_lock_); +}; + +android::base::NoDestructor<DexOptStatus> dexopt_status_; + +} // namespace + namespace android { namespace installd { @@ -1525,23 +1600,46 @@ static std::string join_fds(const std::vector<unique_fd>& fds) { return ss.str(); } +void control_dexopt_blocking(bool block) { + dexopt_status_->control_dexopt_blocking(block); +} + +bool is_dexopt_blocked() { + return dexopt_status_->is_dexopt_blocked(); +} + +enum SecondaryDexOptProcessResult { + kSecondaryDexOptProcessOk = 0, + kSecondaryDexOptProcessCancelled = 1, + kSecondaryDexOptProcessError = 2 +}; + // Processes the dex_path as a secondary dex files and return true if the path dex file should -// be compiled. Returns false for errors (logged) or true if the secondary dex path was process -// successfully. -// When returning true, the output parameters will be: +// be compiled. +// Returns: kSecondaryDexOptProcessError for errors (logged). +// kSecondaryDexOptProcessOk if the secondary dex path was process successfully. +// kSecondaryDexOptProcessCancelled if the processing was cancelled. +// +// When returning kSecondaryDexOptProcessOk, the output parameters will be: // - is_public_out: whether or not the oat file should not be made public // - dexopt_needed_out: valid OatFileAsssitant::DexOptNeeded // - oat_dir_out: the oat dir path where the oat file should be stored -static bool process_secondary_dex_dexopt(const std::string& dex_path, const char* pkgname, - int dexopt_flags, const char* volume_uuid, int uid, const char* instruction_set, - const char* compiler_filter, bool* is_public_out, int* dexopt_needed_out, - std::string* oat_dir_out, bool downgrade, const char* class_loader_context, - const std::vector<std::string>& context_dex_paths, /* out */ std::string* error_msg) { +static SecondaryDexOptProcessResult process_secondary_dex_dexopt(const std::string& dex_path, + const char* pkgname, int dexopt_flags, const char* volume_uuid, int uid, + const char* instruction_set, const char* compiler_filter, bool* is_public_out, + int* dexopt_needed_out, std::string* oat_dir_out, bool downgrade, + const char* class_loader_context, const std::vector<std::string>& context_dex_paths, + /* out */ std::string* error_msg) { LOG(DEBUG) << "Processing secondary dex path " << dex_path; + + if (dexopt_status_->is_dexopt_blocked()) { + return kSecondaryDexOptProcessCancelled; + } + int storage_flag; if (!validate_dexopt_storage_flags(dexopt_flags, &storage_flag, error_msg)) { LOG(ERROR) << *error_msg; - return false; + return kSecondaryDexOptProcessError; } // Compute the oat dir as it's not easy to extract it from the child computation. char oat_path[PKG_PATH_MAX]; @@ -1550,11 +1648,15 @@ static bool process_secondary_dex_dexopt(const std::string& dex_path, const char if (!create_secondary_dex_oat_layout( dex_path, instruction_set, oat_dir, oat_isa_dir, oat_path, error_msg)) { LOG(ERROR) << "Could not create secondary odex layout: " << *error_msg; - return false; + return kSecondaryDexOptProcessError; } oat_dir_out->assign(oat_dir); - pid_t pid = fork(); + bool cancelled = false; + pid_t pid = dexopt_status_->check_cancellation_and_fork(&cancelled); + if (cancelled) { + return kSecondaryDexOptProcessCancelled; + } if (pid == 0) { // child -- drop privileges before continuing. drop_capabilities(uid); @@ -1623,12 +1725,17 @@ static bool process_secondary_dex_dexopt(const std::string& dex_path, const char /* parent */ int result = wait_child(pid); + cancelled = dexopt_status_->check_if_killed_and_remove_dexopt_pid(pid); if (!WIFEXITED(result)) { + if ((WTERMSIG(result) == SIGKILL) && cancelled) { + LOG(INFO) << "dexoptanalyzer cancelled for path:" << dex_path; + return kSecondaryDexOptProcessCancelled; + } *error_msg = StringPrintf("dexoptanalyzer failed for path %s: 0x%04x", dex_path.c_str(), result); LOG(ERROR) << *error_msg; - return false; + return kSecondaryDexOptProcessError; } result = WEXITSTATUS(result); // Check that we successfully executed dexoptanalyzer. @@ -1656,7 +1763,7 @@ static bool process_secondary_dex_dexopt(const std::string& dex_path, const char // It is ok to check this flag outside in the parent process. *is_public_out = ((dexopt_flags & DEXOPT_PUBLIC) != 0) && is_file_public(dex_path); - return success; + return success ? kSecondaryDexOptProcessOk : kSecondaryDexOptProcessError; } static std::string format_dexopt_error(int status, const char* dex_path) { @@ -1670,17 +1777,29 @@ static std::string format_dexopt_error(int status, const char* dex_path) { return StringPrintf("Dex2oat invocation for %s failed with 0x%04x", dex_path, status); } + int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* instruction_set, int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter, const char* volume_uuid, const char* class_loader_context, const char* se_info, bool downgrade, int target_sdk_version, const char* profile_name, - const char* dex_metadata_path, const char* compilation_reason, std::string* error_msg) { + const char* dex_metadata_path, const char* compilation_reason, std::string* error_msg, + /* out */ bool* completed) { CHECK(pkgname != nullptr); CHECK(pkgname[0] != 0); CHECK(error_msg != nullptr); CHECK_EQ(dexopt_flags & ~DEXOPT_MASK, 0) << "dexopt flags contains unknown fields: " << dexopt_flags; + bool local_completed; // local placeholder for nullptr case + if (completed == nullptr) { + completed = &local_completed; + } + *completed = true; + if (dexopt_status_->is_dexopt_blocked()) { + *completed = false; + return 0; + } + if (!validate_dex_path_size(dex_path)) { *error_msg = StringPrintf("Failed to validate %s", dex_path); return -1; @@ -1712,14 +1831,19 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins *error_msg = "Failed acquiring context dex paths"; return -1; // We had an error, logged in the process method. } - - if (process_secondary_dex_dexopt(dex_path, pkgname, dexopt_flags, volume_uuid, uid, - instruction_set, compiler_filter, &is_public, &dexopt_needed, &oat_dir_str, - downgrade, class_loader_context, context_dex_paths, error_msg)) { + SecondaryDexOptProcessResult sec_dex_result = process_secondary_dex_dexopt(dex_path, + pkgname, dexopt_flags, volume_uuid, uid,instruction_set, compiler_filter, + &is_public, &dexopt_needed, &oat_dir_str, downgrade, class_loader_context, + context_dex_paths, error_msg); + if (sec_dex_result == kSecondaryDexOptProcessOk) { oat_dir = oat_dir_str.c_str(); if (dexopt_needed == NO_DEXOPT_NEEDED) { return 0; // Nothing to do, report success. } + } else if (sec_dex_result == kSecondaryDexOptProcessCancelled) { + // cancelled, not an error. + *completed = false; + return 0; } else { if (error_msg->empty()) { // TODO: Make this a CHECK. *error_msg = "Failed processing secondary."; @@ -1849,7 +1973,11 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins use_jitzygote_image, compilation_reason); - pid_t pid = fork(); + bool cancelled = false; + pid_t pid = dexopt_status_->check_cancellation_and_fork(&cancelled); + if (cancelled) { + return 0; + } if (pid == 0) { // Need to set schedpolicy before dropping privileges // for cgroup migration. See details at b/175178520. @@ -1867,9 +1995,16 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins runner.Exec(DexoptReturnCodes::kDex2oatExec); } else { int res = wait_child(pid); + bool cancelled = dexopt_status_->check_if_killed_and_remove_dexopt_pid(pid); if (res == 0) { LOG(VERBOSE) << "DexInv: --- END '" << dex_path << "' (success) ---"; } else { + if ((WTERMSIG(res) == SIGKILL) && cancelled) { + LOG(VERBOSE) << "DexInv: --- END '" << dex_path << "' --- cancelled"; + // cancelled, not an error + *completed = false; + return 0; + } LOG(VERBOSE) << "DexInv: --- END '" << dex_path << "' --- status=0x" << std::hex << std::setw(4) << res << ", process failed"; *error_msg = format_dexopt_error(res, dex_path); @@ -1877,12 +2012,14 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins } } + // TODO(b/156537504) Implement SWAP of completed files // We've been successful, don't delete output. out_oat.DisableCleanup(); out_vdex.DisableCleanup(); out_image.DisableCleanup(); reference_profile.DisableCleanup(); + *completed = true; return 0; } diff --git a/cmds/installd/dexopt.h b/cmds/installd/dexopt.h index 5a637b1ce7..12579b04fc 100644 --- a/cmds/installd/dexopt.h +++ b/cmds/installd/dexopt.h @@ -121,11 +121,18 @@ bool hash_secondary_dex_file(const std::string& dex_path, const std::string& pkgname, int uid, const std::optional<std::string>& volume_uuid, int storage_flag, std::vector<uint8_t>* out_secondary_dex_hash); +// completed pass false if it is canceled. Otherwise it will be true even if there is other +// error. int dexopt(const char *apk_path, uid_t uid, const char *pkgName, const char *instruction_set, int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter, const char* volume_uuid, const char* class_loader_context, const char* se_info, bool downgrade, int target_sdk_version, const char* profile_name, - const char* dexMetadataPath, const char* compilation_reason, std::string* error_msg); + const char* dexMetadataPath, const char* compilation_reason, std::string* error_msg, + /* out */ bool* completed = nullptr); + +bool is_dexopt_blocked(); + +void control_dexopt_blocking(bool block); bool calculate_oat_file_path_default(char path[PKG_PATH_MAX], const char *oat_dir, const char *apk_path, const char *instruction_set); diff --git a/cmds/installd/tests/installd_dexopt_test.cpp b/cmds/installd/tests/installd_dexopt_test.cpp index 7e7e513568..ea26955af5 100644 --- a/cmds/installd/tests/installd_dexopt_test.cpp +++ b/cmds/installd/tests/installd_dexopt_test.cpp @@ -232,6 +232,7 @@ protected: virtual void TearDown() { if (!kDebug) { + service_->controlDexOptBlocking(false); service_->destroyAppData( volume_uuid_, package_name_, kTestUserId, kAppDataFlags, ce_data_inode_); run_cmd("rm -rf " + app_apk_dir_); @@ -347,7 +348,7 @@ protected: void CompileSecondaryDex(const std::string& path, int32_t dex_storage_flag, bool should_binder_call_succeed, bool should_dex_be_compiled = true, /*out */ binder::Status* binder_result = nullptr, int32_t uid = -1, - const char* class_loader_context = nullptr) { + const char* class_loader_context = nullptr, bool expect_completed = true) { if (uid == -1) { uid = kTestAppUid; } @@ -364,6 +365,7 @@ protected: std::optional<std::string> dm_path; std::optional<std::string> compilation_reason; + bool completed = false; binder::Status result = service_->dexopt(path, uid, package_name_, @@ -379,8 +381,10 @@ protected: target_sdk_version, profile_name, dm_path, - compilation_reason); + compilation_reason, + &completed); ASSERT_EQ(should_binder_call_succeed, result.isOk()) << result.toString8().c_str(); + ASSERT_EQ(expect_completed, completed); int expected_access = should_dex_be_compiled ? 0 : -1; std::string odex = GetSecondaryDexArtifact(path, "odex"); std::string vdex = GetSecondaryDexArtifact(path, "vdex"); @@ -431,6 +435,11 @@ protected: ASSERT_EQ(mode, st.st_mode); } + void AssertNoFile(const std::string& file) { + struct stat st; + ASSERT_EQ(-1, stat(file.c_str(), &st)); + } + void CompilePrimaryDexOk(std::string compiler_filter, int32_t dex_flags, const char* oat_dir, @@ -447,6 +456,7 @@ protected: dm_path, downgrade, true, + true, binder_result); } @@ -466,6 +476,27 @@ protected: dm_path, downgrade, false, + true, + binder_result); + } + + void CompilePrimaryDexCancelled(std::string compiler_filter, + int32_t dex_flags, + const char* oat_dir, + int32_t uid, + int32_t dexopt_needed, + binder::Status* binder_result = nullptr, + const char* dm_path = nullptr, + bool downgrade = false) { + CompilePrimaryDex(compiler_filter, + dex_flags, + oat_dir, + uid, + dexopt_needed, + dm_path, + downgrade, + true, // should_binder_call_succeed + false, // expect_completed binder_result); } @@ -477,6 +508,7 @@ protected: const char* dm_path, bool downgrade, bool should_binder_call_succeed, + bool expect_completed, /*out */ binder::Status* binder_result) { std::optional<std::string> out_path = oat_dir ? std::make_optional<std::string>(oat_dir) : std::nullopt; std::string class_loader_context = "PCL[]"; @@ -491,6 +523,7 @@ protected: dm_path_opt, &prof_result)); ASSERT_TRUE(prof_result); + bool completed = false; binder::Status result = service_->dexopt(apk_path_, uid, package_name_, @@ -506,8 +539,10 @@ protected: target_sdk_version, profile_name, dm_path_opt, - compilation_reason); + compilation_reason, + &completed); ASSERT_EQ(should_binder_call_succeed, result.isOk()) << result.toString8().c_str(); + ASSERT_EQ(expect_completed, completed); if (!should_binder_call_succeed) { if (binder_result != nullptr) { @@ -525,11 +560,20 @@ protected: bool is_public = (dex_flags & DEXOPT_PUBLIC) != 0; mode_t mode = S_IFREG | (is_public ? 0644 : 0640); - CheckFileAccess(odex, kSystemUid, uid, mode); - CheckFileAccess(vdex, kSystemUid, uid, mode); + if (expect_completed) { + CheckFileAccess(odex, kSystemUid, uid, mode); + CheckFileAccess(vdex, kSystemUid, uid, mode); + } else { + AssertNoFile(odex); + AssertNoFile(vdex); + } if (compiler_filter == "speed-profile") { - CheckFileAccess(art, kSystemUid, uid, mode); + if (expect_completed) { + CheckFileAccess(art, kSystemUid, uid, mode); + } else { + AssertNoFile(art); + } } if (binder_result != nullptr) { *binder_result = result; @@ -750,6 +794,28 @@ TEST_F(DexoptTest, DexoptPrimaryBackgroundOk) { empty_dm_file_.c_str()); } +TEST_F(DexoptTest, DexoptBlockPrimary) { + LOG(INFO) << "DexoptPrimaryPublic"; + service_->controlDexOptBlocking(true); + CompilePrimaryDexCancelled("verify", + DEXOPT_BOOTCOMPLETE | DEXOPT_PUBLIC, + app_oat_dir_.c_str(), + kTestAppGid, + DEX2OAT_FROM_SCRATCH, nullptr, nullptr); + service_->controlDexOptBlocking(false); +} + +TEST_F(DexoptTest, DexoptUnblockPrimary) { + LOG(INFO) << "DexoptPrimaryPublic"; + service_->controlDexOptBlocking(true); + service_->controlDexOptBlocking(false); + CompilePrimaryDexOk("verify", + DEXOPT_BOOTCOMPLETE | DEXOPT_PUBLIC, + app_oat_dir_.c_str(), + kTestAppGid, + DEX2OAT_FROM_SCRATCH, nullptr, nullptr); +} + TEST_F(DexoptTest, DeleteDexoptArtifactsData) { LOG(INFO) << "DeleteDexoptArtifactsData"; TestDeleteOdex(/*in_dalvik_cache=*/ false); |