diff options
author | 2025-03-21 20:05:44 -0700 | |
---|---|---|
committer | 2025-03-21 20:05:44 -0700 | |
commit | b61923778ebb23c5b282ff67b509493a96593419 (patch) | |
tree | d59971c5123aae888aa7de90665c9a3d3fc9a4d8 | |
parent | b6f024a322376684cb357b48767353f18fb39dfc (diff) | |
parent | b73b25fdd947faef7856ba9277581553a96f8675 (diff) |
Snap for 13256841 from b73b25fdd947faef7856ba9277581553a96f8675 to 25Q2-release
Change-Id: Ib9d9918eb8a5bbf74e53ba61aaf7a2121c45a357
99 files changed, 2331 insertions, 617 deletions
diff --git a/artd/artd.cc b/artd/artd.cc index d781750306..144b783a29 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -84,6 +84,7 @@ #include "fstab/fstab.h" #include "oat/oat_file_assistant.h" #include "oat/oat_file_assistant_context.h" +#include "oat/sdc_file.h" #include "odrefresh/odrefresh.h" #include "path_utils.h" #include "profman/profman_result.h" @@ -115,9 +116,11 @@ using ::aidl::com::android::server::art::IArtdNotification; using ::aidl::com::android::server::art::MergeProfileOptions; using ::aidl::com::android::server::art::OutputArtifacts; using ::aidl::com::android::server::art::OutputProfile; +using ::aidl::com::android::server::art::OutputSecureDexMetadataCompanion; using ::aidl::com::android::server::art::PriorityClass; using ::aidl::com::android::server::art::ProfilePath; using ::aidl::com::android::server::art::RuntimeArtifactsPath; +using ::aidl::com::android::server::art::SecureDexMetadataWithCompanionPaths; using ::aidl::com::android::server::art::VdexPath; using ::android::base::Basename; using ::android::base::Dirname; @@ -235,6 +238,10 @@ ArtifactsLocation ArtifactsLocationToAidl(OatFileAssistant::Location location) { return ArtifactsLocation::NEXT_TO_DEX; case OatFileAssistant::Location::kLocationDm: return ArtifactsLocation::DM; + case OatFileAssistant::Location::kLocationSdmOat: + return ArtifactsLocation::SDM_DALVIK_CACHE; + case OatFileAssistant::Location::kLocationSdmOdex: + return ArtifactsLocation::SDM_NEXT_TO_DEX; // No default. All cases should be explicitly handled, or the compilation will fail. } // This should never happen. Just in case we get a non-enumerator value. @@ -269,20 +276,18 @@ Result<void> PrepareArtifactsDir(const std::string& path, const FsPermission& fs return {}; } -Result<void> PrepareArtifactsDirs(const OutputArtifacts& output_artifacts, +Result<void> PrepareArtifactsDirs(const std::string& dex_path, + const std::string& isa_str, + const FsPermission& dir_fs_permission, /*out*/ std::string* oat_dir_path) { - if (output_artifacts.artifactsPath.isInDalvikCache) { - return {}; - } - std::filesystem::path oat_path( - OR_RETURN(BuildArtifactsPath(output_artifacts.artifactsPath)).oat_path); + OR_RETURN(BuildOatPath(dex_path, isa_str, /*is_in_dalvik_cache=*/false))); std::filesystem::path isa_dir = oat_path.parent_path(); std::filesystem::path oat_dir = isa_dir.parent_path(); DCHECK_EQ(oat_dir.filename(), "oat"); - OR_RETURN(PrepareArtifactsDir(oat_dir, output_artifacts.permissionSettings.dirFsPermission)); - OR_RETURN(PrepareArtifactsDir(isa_dir, output_artifacts.permissionSettings.dirFsPermission)); + OR_RETURN(PrepareArtifactsDir(oat_dir, dir_fs_permission)); + OR_RETURN(PrepareArtifactsDir(isa_dir, dir_fs_permission)); *oat_dir_path = oat_dir; return {}; } @@ -992,6 +997,64 @@ ndk::ScopedAStatus Artd::getDexoptNeeded(const std::string& in_dexFile, return ScopedAStatus::ok(); } +ndk::ScopedAStatus Artd::maybeCreateSdc(const OutputSecureDexMetadataCompanion& in_outputSdc) { + RETURN_FATAL_IF_PRE_REBOOT(options_); + + if (in_outputSdc.permissionSettings.seContext.has_value()) { + // SDM files are for primary dex files. + return Fatal("'seContext' must be null"); + } + + std::string sdm_path = OR_RETURN_FATAL(BuildSdmPath(in_outputSdc.sdcPath)); + std::string sdc_path = OR_RETURN_FATAL(BuildSdcPath(in_outputSdc.sdcPath)); + + Result<std::unique_ptr<File>> sdm_file = OpenFileForReading(sdm_path); + if (!sdm_file.ok()) { + if (sdm_file.error().code() == ENOENT) { + // No SDM file found. That's typical. + return ScopedAStatus::ok(); + } + return NonFatal(sdm_file.error().message()); + } + struct stat sdm_st = OR_RETURN_NON_FATAL(Fstat(*sdm_file.value())); + + std::string error_msg; + std::unique_ptr<SdcReader> sdc_reader = SdcReader::Load(sdc_path, &error_msg); + if (sdc_reader != nullptr && sdc_reader->GetSdmTimestampNs() == TimeSpecToNs(sdm_st.st_mtim)) { + // Already has an SDC file for the SDM file. + return ScopedAStatus::ok(); + } + + std::string oat_dir_path; // For restorecon, can be empty if the artifacts are in dalvik-cache. + if (!in_outputSdc.sdcPath.isInDalvikCache) { + OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputSdc.sdcPath.dexPath, + in_outputSdc.sdcPath.isa, + in_outputSdc.permissionSettings.dirFsPermission, + &oat_dir_path)); + + // Unlike the two `restorecon_` calls in `dexopt`, we only need one restorecon here because SDM + // files are for primary dex files, whose oat directory doesn't have an MLS label. + OR_RETURN_NON_FATAL(restorecon_(oat_dir_path, /*se_context=*/std::nullopt, /*recurse=*/true)); + } + + OatFileAssistantContext* ofa_context = OR_RETURN_NON_FATAL(GetOatFileAssistantContext()); + + std::unique_ptr<NewFile> sdc_file = OR_RETURN_NON_FATAL( + NewFile::Create(sdc_path, in_outputSdc.permissionSettings.fileFsPermission)); + SdcWriter writer(File(DupCloexec(sdc_file->Fd()), sdc_file->TempPath(), /*check_usage=*/true)); + + writer.SetSdmTimestampNs(TimeSpecToNs(sdm_st.st_mtim)); + writer.SetApexVersions(ofa_context->GetApexVersions()); + + if (!writer.Save(&error_msg)) { + return NonFatal(error_msg); + } + + OR_RETURN_NON_FATAL(sdc_file->CommitOrAbandon()); + + return ScopedAStatus::ok(); +} + ndk::ScopedAStatus Artd::dexopt( const OutputArtifacts& in_outputArtifacts, const std::string& in_dexFile, @@ -1029,12 +1092,15 @@ ndk::ScopedAStatus Artd::dexopt( } std::string oat_dir_path; // For restorecon, can be empty if the artifacts are in dalvik-cache. - OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputArtifacts, &oat_dir_path)); - - // First-round restorecon. artd doesn't have the permission to create files with the - // `apk_data_file` label, so we need to restorecon the "oat" directory first so that files will - // inherit `dalvikcache_data_file` rather than `apk_data_file`. if (!in_outputArtifacts.artifactsPath.isInDalvikCache) { + OR_RETURN_NON_FATAL(PrepareArtifactsDirs(in_outputArtifacts.artifactsPath.dexPath, + in_outputArtifacts.artifactsPath.isa, + in_outputArtifacts.permissionSettings.dirFsPermission, + &oat_dir_path)); + + // First-round restorecon. artd doesn't have the permission to create files with the + // `apk_data_file` label, so we need to restorecon the "oat" directory first so that files will + // inherit `dalvikcache_data_file` rather than `apk_data_file`. OR_RETURN_NON_FATAL(restorecon_( oat_dir_path, in_outputArtifacts.permissionSettings.seContext, /*recurse=*/true)); } @@ -1274,12 +1340,14 @@ ScopedAStatus Artd::createCancellationSignal( return ScopedAStatus::ok(); } -ScopedAStatus Artd::cleanup(const std::vector<ProfilePath>& in_profilesToKeep, - const std::vector<ArtifactsPath>& in_artifactsToKeep, - const std::vector<VdexPath>& in_vdexFilesToKeep, - const std::vector<RuntimeArtifactsPath>& in_runtimeArtifactsToKeep, - bool in_keepPreRebootStagedFiles, - int64_t* _aidl_return) { +ScopedAStatus Artd::cleanup( + const std::vector<ProfilePath>& in_profilesToKeep, + const std::vector<ArtifactsPath>& in_artifactsToKeep, + const std::vector<VdexPath>& in_vdexFilesToKeep, + const std::vector<SecureDexMetadataWithCompanionPaths>& in_SdmSdcFilesToKeep, + const std::vector<RuntimeArtifactsPath>& in_runtimeArtifactsToKeep, + bool in_keepPreRebootStagedFiles, + int64_t* _aidl_return) { RETURN_FATAL_IF_PRE_REBOOT(options_); std::unordered_set<std::string> files_to_keep; for (const ProfilePath& profile : in_profilesToKeep) { @@ -1297,6 +1365,10 @@ ScopedAStatus Artd::cleanup(const std::vector<ProfilePath>& in_profilesToKeep, RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(vdex, "vdexFilesToKeep"); files_to_keep.insert(OR_RETURN_FATAL(BuildVdexPath(vdex))); } + for (const SecureDexMetadataWithCompanionPaths& sdm_sdc : in_SdmSdcFilesToKeep) { + files_to_keep.insert(OR_RETURN_FATAL(BuildSdmPath(sdm_sdc))); + files_to_keep.insert(OR_RETURN_FATAL(BuildSdcPath(sdm_sdc))); + } std::string android_data = OR_RETURN_NON_FATAL(GetAndroidDataOrError()); std::string android_expand = OR_RETURN_NON_FATAL(GetAndroidExpandOrError()); for (const RuntimeArtifactsPath& runtime_image_path : in_runtimeArtifactsToKeep) { @@ -1360,6 +1432,17 @@ ScopedAStatus Artd::isInDalvikCache(const std::string& in_dexFile, bool* _aidl_r return NonFatal(ART_FORMAT("Fstab entries not found for '{}'", in_dexFile)); } +ScopedAStatus Artd::deleteSdmSdcFiles(const SecureDexMetadataWithCompanionPaths& in_SdmSdcPaths, + int64_t* _aidl_return) { + RETURN_FATAL_IF_PRE_REBOOT(options_); + + std::string sdm_path = OR_RETURN_FATAL(BuildSdmPath(in_SdmSdcPaths)); + std::string sdc_path = OR_RETURN_FATAL(BuildSdcPath(in_SdmSdcPaths)); + + *_aidl_return = GetSizeAndDeleteFile(sdm_path) + GetSizeAndDeleteFile(sdc_path); + return ScopedAStatus::ok(); +} + ScopedAStatus Artd::deleteRuntimeArtifacts(const RuntimeArtifactsPath& in_runtimeArtifactsPath, int64_t* _aidl_return) { RETURN_FATAL_IF_PRE_REBOOT(options_); @@ -1393,6 +1476,14 @@ ScopedAStatus Artd::getVdexFileSize(const VdexPath& in_vdexPath, int64_t* _aidl_ return ScopedAStatus::ok(); } +ndk::ScopedAStatus Artd::getSdmFileSize(const SecureDexMetadataWithCompanionPaths& in_sdmPath, + int64_t* _aidl_return) { + RETURN_FATAL_IF_PRE_REBOOT(options_); + std::string sdm_path = OR_RETURN_FATAL(BuildSdmPath(in_sdmPath)); + *_aidl_return = GetSize(sdm_path).value_or(0); + return ScopedAStatus::ok(); +} + ScopedAStatus Artd::getRuntimeArtifactsSize(const RuntimeArtifactsPath& in_runtimeArtifactsPath, int64_t* _aidl_return) { RETURN_FATAL_IF_PRE_REBOOT(options_); diff --git a/artd/artd.h b/artd/artd.h index 7426901688..d48a209b0d 100644 --- a/artd/artd.h +++ b/artd/artd.h @@ -38,6 +38,7 @@ #include "aidl/com/android/server/art/BnArtd.h" #include "aidl/com/android/server/art/BnArtdCancellationSignal.h" #include "aidl/com/android/server/art/BnArtdNotification.h" +#include "aidl/com/android/server/art/SecureDexMetadataWithCompanionPaths.h" #include "android-base/result.h" #include "android-base/thread_annotations.h" #include "android-base/unique_fd.h" @@ -217,6 +218,10 @@ class Artd : public aidl::com::android::server::art::BnArtd { int32_t in_dexoptTrigger, aidl::com::android::server::art::GetDexoptNeededResult* _aidl_return) override; + ndk::ScopedAStatus maybeCreateSdc( + const aidl::com::android::server::art::OutputSecureDexMetadataCompanion& in_outputSdc) + override; + ndk::ScopedAStatus dexopt( const aidl::com::android::server::art::OutputArtifacts& in_outputArtifacts, const std::string& in_dexFile, @@ -240,6 +245,8 @@ class Artd : public aidl::com::android::server::art::BnArtd { const std::vector<aidl::com::android::server::art::ProfilePath>& in_profilesToKeep, const std::vector<aidl::com::android::server::art::ArtifactsPath>& in_artifactsToKeep, const std::vector<aidl::com::android::server::art::VdexPath>& in_vdexFilesToKeep, + const std::vector<aidl::com::android::server::art::SecureDexMetadataWithCompanionPaths>& + in_SdmSdcFilesToKeep, const std::vector<aidl::com::android::server::art::RuntimeArtifactsPath>& in_runtimeArtifactsToKeep, bool in_keepPreRebootStagedFiles, @@ -249,6 +256,10 @@ class Artd : public aidl::com::android::server::art::BnArtd { ndk::ScopedAStatus isInDalvikCache(const std::string& in_dexFile, bool* _aidl_return) override; + ndk::ScopedAStatus deleteSdmSdcFiles( + const aidl::com::android::server::art::SecureDexMetadataWithCompanionPaths& in_sdmSdcPaths, + int64_t* _aidl_return) override; + ndk::ScopedAStatus deleteRuntimeArtifacts( const aidl::com::android::server::art::RuntimeArtifactsPath& in_runtimeArtifactsPath, int64_t* _aidl_return) override; @@ -260,6 +271,10 @@ class Artd : public aidl::com::android::server::art::BnArtd { ndk::ScopedAStatus getVdexFileSize(const aidl::com::android::server::art::VdexPath& in_vdexPath, int64_t* _aidl_return) override; + ndk::ScopedAStatus getSdmFileSize( + const aidl::com::android::server::art::SecureDexMetadataWithCompanionPaths& in_sdmPath, + int64_t* _aidl_return) override; + ndk::ScopedAStatus getRuntimeArtifactsSize( const aidl::com::android::server::art::RuntimeArtifactsPath& in_runtimeArtifactsPath, int64_t* _aidl_return) override; diff --git a/artd/artd_test.cc b/artd/artd_test.cc index 76167c58fb..f6eeda7e57 100644 --- a/artd/artd_test.cc +++ b/artd/artd_test.cc @@ -43,6 +43,7 @@ #include "aidl/com/android/server/art/ArtConstants.h" #include "aidl/com/android/server/art/BnArtd.h" +#include "aidl/com/android/server/art/OutputArtifacts.h" #include "android-base/collections.h" #include "android-base/errors.h" #include "android-base/file.h" @@ -58,6 +59,7 @@ #include "base/common_art_test.h" #include "base/macros.h" #include "base/pidfd.h" +#include "base/time_utils.h" #include "exec_utils.h" #include "file_utils.h" #include "gmock/gmock.h" @@ -94,6 +96,7 @@ using ::aidl::com::android::server::art::OutputProfile; using ::aidl::com::android::server::art::PriorityClass; using ::aidl::com::android::server::art::ProfilePath; using ::aidl::com::android::server::art::RuntimeArtifactsPath; +using ::aidl::com::android::server::art::SecureDexMetadataWithCompanionPaths; using ::aidl::com::android::server::art::VdexPath; using ::android::base::Append; using ::android::base::Dirname; @@ -130,10 +133,12 @@ using ::testing::Property; using ::testing::ResultOf; using ::testing::Return; using ::testing::SetArgPointee; +using ::testing::StartsWith; using ::testing::StrEq; using ::testing::UnorderedElementsAreArray; using ::testing::WithArg; +using PermissionSettings = OutputArtifacts::PermissionSettings; using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath; using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath; using TmpProfilePath = ProfilePath::TmpProfilePath; @@ -153,10 +158,10 @@ ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& l }); } -void CheckContent(const std::string& path, const std::string& expected_content) { +void CheckContent(const std::string& path, const Matcher<std::string>& expected_content_matcher) { std::string actual_content; ASSERT_TRUE(ReadFileToString(path, &actual_content)); - EXPECT_EQ(actual_content, expected_content); + EXPECT_THAT(actual_content, expected_content_matcher); } void CheckOtherReadable(const std::string& path, bool expected_value) { @@ -384,24 +389,24 @@ class ArtdTest : public CommonArtTest { }; struct stat st; ASSERT_EQ(stat(scratch_path_.c_str(), &st), 0); + permission_settings_ = { + .dirFsPermission = + FsPermission{ + .uid = static_cast<int32_t>(st.st_uid), + .gid = static_cast<int32_t>(st.st_gid), + .isOtherReadable = true, + .isOtherExecutable = true, + }, + .fileFsPermission = + FsPermission{ + .uid = static_cast<int32_t>(st.st_uid), + .gid = static_cast<int32_t>(st.st_gid), + .isOtherReadable = true, + }, + }; output_artifacts_ = OutputArtifacts{ .artifactsPath = artifacts_path_, - .permissionSettings = - OutputArtifacts::PermissionSettings{ - .dirFsPermission = - FsPermission{ - .uid = static_cast<int32_t>(st.st_uid), - .gid = static_cast<int32_t>(st.st_gid), - .isOtherReadable = true, - .isOtherExecutable = true, - }, - .fileFsPermission = - FsPermission{ - .uid = static_cast<int32_t>(st.st_uid), - .gid = static_cast<int32_t>(st.st_gid), - .isOtherReadable = true, - }, - }, + .permissionSettings = permission_settings_, }; clc_1_ = GetTestDexFileName("Main"); clc_2_ = GetTestDexFileName("Nested"); @@ -417,6 +422,12 @@ class ArtdTest : public CommonArtTest { dm_path_ = DexMetadataPath{.dexPath = dex_file_}; std::filesystem::create_directories( std::filesystem::path(OR_FATAL(BuildFinalProfilePath(tmp_profile_path_))).parent_path()); + + sdm_sdc_paths_ = { + .dexPath = dex_file_, + .isa = isa_, + .isInDalvikCache = false, + }; } void TearDown() override { @@ -547,6 +558,7 @@ class ArtdTest : public CommonArtTest { std::string dex_file_; std::string isa_; ArtifactsPath artifacts_path_; + PermissionSettings permission_settings_; OutputArtifacts output_artifacts_; std::string clc_1_; std::string clc_2_; @@ -561,6 +573,8 @@ class ArtdTest : public CommonArtTest { bool dex_file_other_readable_ = true; bool profile_other_readable_ = true; + SecureDexMetadataWithCompanionPaths sdm_sdc_paths_; + private: void InitFilesBeforeDexopt() { // Required files. @@ -701,6 +715,79 @@ TEST_F(ArtdTest, deleteArtifactsFileIsDir) { EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art")); } +TEST_F(ArtdTest, maybeCreateSdc) { + // Unable to create OatFileAssistantContext on host to get APEX versions. + TEST_DISABLED_FOR_HOST(); + + std::string sdm_file = OR_FAIL(BuildSdmPath(sdm_sdc_paths_)); + std::string sdc_file = OR_FAIL(BuildSdcPath(sdm_sdc_paths_)); + CreateFile(sdm_file); + + ASSERT_STATUS_OK(artd_->maybeCreateSdc( + {.sdcPath = sdm_sdc_paths_, .permissionSettings = permission_settings_})); + + CheckContent(sdc_file, StartsWith("sdm-timestamp-ns=")); +} + +TEST_F(ArtdTest, maybeCreateSdcAlreadyCreated) { + // Unable to create OatFileAssistantContext on host to get APEX versions. + TEST_DISABLED_FOR_HOST(); + + std::string sdm_file = OR_FAIL(BuildSdmPath(sdm_sdc_paths_)); + std::string sdc_file = OR_FAIL(BuildSdcPath(sdm_sdc_paths_)); + CreateFile(sdm_file); + + ASSERT_STATUS_OK(artd_->maybeCreateSdc( + {.sdcPath = sdm_sdc_paths_, .permissionSettings = permission_settings_})); + + struct stat sdc_st; + ASSERT_EQ(stat(sdc_file.c_str(), &sdc_st), 0); + + ASSERT_STATUS_OK(artd_->maybeCreateSdc( + {.sdcPath = sdm_sdc_paths_, .permissionSettings = permission_settings_})); + + struct stat new_sdc_st; + ASSERT_EQ(stat(sdc_file.c_str(), &new_sdc_st), 0); + + EXPECT_EQ(TimeSpecToNs(sdc_st.st_mtim), TimeSpecToNs(new_sdc_st.st_mtim)); +} + +TEST_F(ArtdTest, maybeCreateSdcOutdatedTimestamp) { + // Unable to create OatFileAssistantContext on host to get APEX versions. + TEST_DISABLED_FOR_HOST(); + + std::string sdm_file = OR_FAIL(BuildSdmPath(sdm_sdc_paths_)); + std::string sdc_file = OR_FAIL(BuildSdcPath(sdm_sdc_paths_)); + CreateFile(sdm_file); + + ASSERT_STATUS_OK(artd_->maybeCreateSdc( + {.sdcPath = sdm_sdc_paths_, .permissionSettings = permission_settings_})); + + struct stat sdc_st; + ASSERT_EQ(stat(sdc_file.c_str(), &sdc_st), 0); + + // Simulate that the SDM file is updated. + CreateFile(sdm_file); + + ASSERT_STATUS_OK(artd_->maybeCreateSdc( + {.sdcPath = sdm_sdc_paths_, .permissionSettings = permission_settings_})); + + struct stat new_sdc_st; + ASSERT_EQ(stat(sdc_file.c_str(), &new_sdc_st), 0); + + // The SDC file should be updated. + EXPECT_LT(TimeSpecToNs(sdc_st.st_mtim), TimeSpecToNs(new_sdc_st.st_mtim)); +} + +TEST_F(ArtdTest, maybeCreateSdcNoSdm) { + std::string sdc_file = OR_FAIL(BuildSdcPath(sdm_sdc_paths_)); + + ASSERT_STATUS_OK(artd_->maybeCreateSdc( + {.sdcPath = sdm_sdc_paths_, .permissionSettings = permission_settings_})); + + EXPECT_FALSE(std::filesystem::exists(sdc_file)); +} + TEST_F(ArtdTest, dexopt) { dexopt_options_.generateAppImage = true; @@ -2143,15 +2230,23 @@ TEST_F(ArtdTest, mergeProfilesWithOptionsDumpClassesAndMethods) { CheckContent(output_profile.profilePath.tmpPath, "dump"); } +static std::string EncodeLocationForDalvikCache(const std::string& location) { + std::string encoded = location.substr(/*pos=*/1); // Remove the leading '/'; + std::replace(encoded.begin(), encoded.end(), '/', '@'); + return encoded; +} + class ArtdCleanupTest : public ArtdTest { protected: void SetUpForCleanup() { // Unmanaged files. CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/1.odex"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/1.arm64.sdm"); CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.odex"); CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.txt"); CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.txt"); CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.tmp"); + CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.sdc"); // Files to keep. CreateGcKeptFile(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof"); @@ -2179,6 +2274,15 @@ class ArtdCleanupTest : public ArtdTest { "/123456-7890/user/1/com.android.foo/cache/oat_primary/arm64/base.art"); CreateGcKeptFile(android_data_ + "/user/0/com.android.foo/cache/not_oat_dir/oat_primary/arm64/base.art"); + CreateGcKeptFile(android_data_ + + "/app/~~fadsfgadg==/com.android.baz-fadsfgadg==/base.arm64.sdm"); + CreateGcKeptFile(android_data_ + + "/app/~~fadsfgadg==/com.android.baz-fadsfgadg==/oat/arm64/base.sdc"); + CreateGcKeptFile(android_data_ + + "/app/~~jhrwafasr==/com.android.qux-bredcweff==/base.arm64.sdm"); + CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/" + + EncodeLocationForDalvikCache(android_data_) + + "@app@~~jhrwafasr==@com.android.qux-bredcweff==@base.apk@classes.sdc"); // Files to remove. CreateGcRemovedFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof"); @@ -2220,6 +2324,26 @@ class ArtdCleanupTest : public ArtdTest { "/user/0/com.android.foo/cache/oat_primary/arm64/different_dex.art"); CreateGcRemovedFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/different_isa/base.art"); + CreateGcRemovedFile(android_data_ + + "/app/~~fadsfgadg==/com.android.baz-fadsfgadg==/different_dex.arm64.sdm"); + CreateGcRemovedFile( + android_data_ + + "/app/~~fadsfgadg==/com.android.baz-fadsfgadg==/oat/arm64/different_dex.sdc"); + CreateGcRemovedFile(android_data_ + + "/app/~~fadsfgadg==/com.android.baz-fadsfgadg==/base.different_isa.sdm"); + CreateGcRemovedFile( + android_data_ + + "/app/~~fadsfgadg==/com.android.baz-fadsfgadg==/oat/different_isa/base.sdc"); + CreateGcRemovedFile(android_data_ + + "/app/~~jhrwafasr==/com.android.qux-bredcweff==/different_dex.arm64.sdm"); + CreateGcRemovedFile( + android_data_ + "/dalvik-cache/arm64/" + EncodeLocationForDalvikCache(android_data_) + + "@app@~~jhrwafasr==@com.android.qux-bredcweff==@different_dex.apk@classes.sdc"); + CreateGcRemovedFile(android_data_ + + "/app/~~jhrwafasr==/com.android.qux-bredcweff==/base.different_isa.sdm"); + CreateGcRemovedFile(android_data_ + "/dalvik-cache/different_isa/" + + EncodeLocationForDalvikCache(android_data_) + + "@app@~~jhrwafasr==@com.android.qux-bredcweff==@base.apk@classes.sdc"); } void CreateGcRemovedFile(const std::string& path) { @@ -2260,6 +2384,18 @@ class ArtdCleanupTest : public ArtdTest { .isInDalvikCache = false}}, }, { + SecureDexMetadataWithCompanionPaths{ + .dexPath = + android_data_ + "/app/~~fadsfgadg==/com.android.baz-fadsfgadg==/base.apk", + .isa = "arm64", + .isInDalvikCache = false}, + SecureDexMetadataWithCompanionPaths{ + .dexPath = + android_data_ + "/app/~~jhrwafasr==/com.android.qux-bredcweff==/base.apk", + .isa = "arm64", + .isInDalvikCache = true}, + }, + { RuntimeArtifactsPath{ .packageName = "com.android.foo", .dexPath = "/a/b/base.apk", .isa = "arm64"}, }, @@ -2348,6 +2484,19 @@ TEST_F(ArtdTest, isInDalvikCache) { EXPECT_THAT(is_in_dalvik_cache("/foo"), HasValue(true)); } +TEST_F(ArtdTest, deleteSdmSdcFiles) { + CreateFile(scratch_path_ + "/a/b.arm64.sdm", "**"); // 2 bytes. + CreateFile(scratch_path_ + "/a/oat/arm64/b.sdc", "*"); // 1 byte. + + int64_t result = -1; + ASSERT_STATUS_OK(artd_->deleteSdmSdcFiles( + {.dexPath = scratch_path_ + "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}, &result)); + EXPECT_EQ(result, 2 + 1); + + EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/b.arm64.sdm")); + EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.sdc")); +} + TEST_F(ArtdTest, deleteRuntimeArtifacts) { std::vector<std::string> removed_files; std::vector<std::string> kept_files; @@ -2413,6 +2562,8 @@ TEST_F(ArtdTest, deleteRuntimeArtifactsAndroidDataNotExist) { EXPECT_EQ(aidl_return, 0); } +// Verifies that `deleteRuntimeArtifacts` doesn't treat "*" as a wildcard. It should either treat it +// as a normal character in the path or reject it. The caller is never supposed to use a wildcard. TEST_F(ArtdTest, deleteRuntimeArtifactsSpecialChars) { std::vector<std::string> removed_files; std::vector<std::string> kept_files; @@ -2430,25 +2581,18 @@ TEST_F(ArtdTest, deleteRuntimeArtifactsSpecialChars) { CreateKeptFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/arm64/base.art"); CreateRemovedFile(android_data_ + "/user/0/*/cache/oat_primary/arm64/base.art"); - CreateRemovedFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/*/base.art"); CreateRemovedFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/arm64/*.art"); int64_t aidl_return; - ASSERT_TRUE( - artd_ - ->deleteRuntimeArtifacts({.packageName = "*", .dexPath = "/a/b/base.apk", .isa = "arm64"}, - &aidl_return) - .isOk()); - ASSERT_TRUE(artd_ - ->deleteRuntimeArtifacts( - {.packageName = "com.android.foo", .dexPath = "/a/b/*.apk", .isa = "arm64"}, - &aidl_return) - .isOk()); - ASSERT_TRUE(artd_ - ->deleteRuntimeArtifacts( - {.packageName = "com.android.foo", .dexPath = "/a/b/base.apk", .isa = "*"}, - &aidl_return) - .isOk()); + ASSERT_STATUS_OK(artd_->deleteRuntimeArtifacts( + {.packageName = "*", .dexPath = "/a/b/base.apk", .isa = "arm64"}, &aidl_return)); + ASSERT_STATUS_OK(artd_->deleteRuntimeArtifacts( + {.packageName = "com.android.foo", .dexPath = "/a/b/*.apk", .isa = "arm64"}, &aidl_return)); + ASSERT_FALSE(artd_ + ->deleteRuntimeArtifacts( + {.packageName = "com.android.foo", .dexPath = "/a/b/base.apk", .isa = "*"}, + &aidl_return) + .isOk()); for (const std::string& path : removed_files) { EXPECT_FALSE(std::filesystem::exists(path)) << ART_FORMAT("'{}' should be removed", path); @@ -2497,6 +2641,19 @@ TEST_F(ArtdTest, getVdexFileSize) { EXPECT_EQ(aidl_return, 1); } +TEST_F(ArtdTest, getSdmFileSize) { + CreateFile(scratch_path_ + "/a/b.arm64.sdm", std::string(1, '*')); + + int64_t aidl_return = -1; + ASSERT_TRUE( + artd_ + ->getSdmFileSize( + {.dexPath = scratch_path_ + "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}, + &aidl_return) + .isOk()); + EXPECT_EQ(aidl_return, 1); +} + TEST_F(ArtdTest, getRuntimeArtifactsSize) { CreateFile(android_data_ + "/user_de/0/com.android.foo/cache/oat_primary/arm64/base.art", std::string(1, '*')); diff --git a/artd/binder/com/android/server/art/ArtifactsLocation.aidl b/artd/binder/com/android/server/art/ArtifactsLocation.aidl index 1084456dec..bb1d505c29 100644 --- a/artd/binder/com/android/server/art/ArtifactsLocation.aidl +++ b/artd/binder/com/android/server/art/ArtifactsLocation.aidl @@ -27,4 +27,15 @@ enum ArtifactsLocation { NEXT_TO_DEX = 2, /** In the dex metadata file. This means the only usable artifact is the VDEX file. */ DM = 3, + /** + * The OAT and ART files are in the SDM file next to the dex file. The VDEX file is in the DM + * file next to the dex file. The SDC file is in the global "dalvik-cache" folder. (This happens + * typically when the app is in incremental-fs.) + */ + SDM_DALVIK_CACHE = 4, + /** + * The OAT and ART files are in the SDM file next to the dex file. The VDEX file is in the DM + * file next to the dex file. The SDC file is next to the dex file. + */ + SDM_NEXT_TO_DEX = 5, } diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl index 7e9cf9da58..705076d0a6 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -153,6 +153,15 @@ interface IArtd { int dexoptTrigger); /** + * Creates a secure dex metadata companion (SDC) file for the secure dex metadata (SDM) file, if + * the SDM file exists while the SDC file doesn't exist (meaning the SDM file is seen the first + * time). + * + * Throws fatal and non-fatal errors. + */ + void maybeCreateSdc(in com.android.server.art.OutputSecureDexMetadataCompanion outputSdc); + + /** * Dexopts a dex file for the given instruction set. * * Throws fatal and non-fatal errors. When dexopt fails, the non-fatal status includes an error @@ -200,6 +209,7 @@ interface IArtd { long cleanup(in List<com.android.server.art.ProfilePath> profilesToKeep, in List<com.android.server.art.ArtifactsPath> artifactsToKeep, in List<com.android.server.art.VdexPath> vdexFilesToKeep, + in List<com.android.server.art.SecureDexMetadataWithCompanionPaths> SdmSdcFilesToKeep, in List<com.android.server.art.RuntimeArtifactsPath> runtimeArtifactsToKeep, boolean keepPreRebootStagedFiles); @@ -221,6 +231,16 @@ interface IArtd { boolean isInDalvikCache(@utf8InCpp String dexFile); /** + * Deletes the SDM and SDC files and returns the released space, in bytes. + * + * Not supported in Pre-reboot Dexopt mode. + * + * Throws fatal errors. Logs and ignores non-fatal errors. + */ + long deleteSdmSdcFiles( + in com.android.server.art.SecureDexMetadataWithCompanionPaths sdmSdcPaths); + + /** * Deletes runtime artifacts and returns the released space, in bytes. * * Not supported in Pre-reboot Dexopt mode. @@ -251,6 +271,16 @@ interface IArtd { long getVdexFileSize(in com.android.server.art.VdexPath vdexPath); /** + * Returns the size of the SDM file, in bytes, or 0 if it doesn't exist or a non-fatal error + * occurred. + * + * Not supported in Pre-reboot Dexopt mode. + * + * Throws fatal errors. Logs and ignores non-fatal errors. + */ + long getSdmFileSize(in com.android.server.art.SecureDexMetadataWithCompanionPaths sdmPath); + + /** * Returns the size of the runtime artifacts, in bytes, or 0 if they don't exist or a non-fatal * error occurred. * diff --git a/artd/binder/com/android/server/art/OutputSecureDexMetadataCompanion.aidl b/artd/binder/com/android/server/art/OutputSecureDexMetadataCompanion.aidl new file mode 100644 index 0000000000..df5c84df7f --- /dev/null +++ b/artd/binder/com/android/server/art/OutputSecureDexMetadataCompanion.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2025 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. + */ + +package com.android.server.art; + +/** + * Represents output secure dex metadata companion (SDC) file. + * + * @hide + */ +parcelable OutputSecureDexMetadataCompanion { + /** The path to the output. */ + com.android.server.art.SecureDexMetadataWithCompanionPaths sdcPath; + + /** The permissions settings of the output. */ + com.android.server.art.OutputArtifacts.PermissionSettings permissionSettings; +} diff --git a/artd/binder/com/android/server/art/SecureDexMetadataWithCompanionPaths.aidl b/artd/binder/com/android/server/art/SecureDexMetadataWithCompanionPaths.aidl new file mode 100644 index 0000000000..db0571100e --- /dev/null +++ b/artd/binder/com/android/server/art/SecureDexMetadataWithCompanionPaths.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2025 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. + */ + +package com.android.server.art; + +/** + * Represents the paths to a secure dex metadata (SDM) file and its companion (SDC) file. + * + * @hide + */ +parcelable SecureDexMetadataWithCompanionPaths { + /** + * The absolute path starting with '/' to the dex file that the SDM file is next to. + */ + @utf8InCpp String dexPath; + /** The instruction set of the dexopt artifacts. */ + @utf8InCpp String isa; + /** + * Whether the SDC file in the dalvik-cache folder. This is true typically when the app is in + * incremental-fs. + * + * Only applicable to the SDC file, because the SDM file is installed with the app and therefore + * always to the dex file regardlessly. + */ + boolean isInDalvikCache; +} diff --git a/artd/path_utils.cc b/artd/path_utils.cc index 52bae7097e..0f269e2dbb 100644 --- a/artd/path_utils.cc +++ b/artd/path_utils.cc @@ -45,6 +45,7 @@ using ::aidl::com::android::server::art::OutputArtifacts; using ::aidl::com::android::server::art::OutputProfile; using ::aidl::com::android::server::art::ProfilePath; using ::aidl::com::android::server::art::RuntimeArtifactsPath; +using ::aidl::com::android::server::art::SecureDexMetadataWithCompanionPaths; using ::aidl::com::android::server::art::VdexPath; using ::android::base::Error; using ::android::base::Result; @@ -107,6 +108,7 @@ std::vector<std::string> ListManagedFiles(const std::string& android_data, for (const std::string& data_root : {android_data, android_expand + "/*"}) { // Artifacts for primary dex files. patterns.push_back(data_root + "/app/*/*/oat/**"); + patterns.push_back(data_root + "/app/*/*/*.sdm"); for (const char* user_dir : {"/user", "/user_de"}) { std::string data_dir = data_root + user_dir + "/*/*"; @@ -148,9 +150,17 @@ std::vector<std::string> ListRuntimeArtifactsFiles( return tools::Glob(patterns, gListRootDir); } +static Result<InstructionSet> ValidateAndGetIsa(const std::string& isa_str) { + InstructionSet isa = GetInstructionSetFromString(isa_str.c_str()); + if (isa == InstructionSet::kNone) { + return Errorf("Instruction set '{}' is invalid", isa_str); + } + return isa; +} + Result<void> ValidateRuntimeArtifactsPath(const RuntimeArtifactsPath& runtime_artifacts_path) { OR_RETURN(ValidatePathElement(runtime_artifacts_path.packageName, "packageName")); - OR_RETURN(ValidatePathElement(runtime_artifacts_path.isa, "isa")); + OR_RETURN(ValidateAndGetIsa(runtime_artifacts_path.isa)); OR_RETURN(ValidateDexPath(runtime_artifacts_path.dexPath)); return {}; } @@ -159,32 +169,36 @@ Result<std::string> BuildArtBinPath(const std::string& binary_name) { return ART_FORMAT("{}/bin/{}", OR_RETURN(GetArtRootOrError()), binary_name); } -Result<RawArtifactsPath> BuildArtifactsPath(const ArtifactsPath& artifacts_path) { - OR_RETURN(ValidateDexPath(artifacts_path.dexPath)); - - InstructionSet isa = GetInstructionSetFromString(artifacts_path.isa.c_str()); - if (isa == InstructionSet::kNone) { - return Errorf("Instruction set '{}' is invalid", artifacts_path.isa); - } +Result<std::string> BuildOatPath(const std::string& dex_path, + const std::string& isa_str, + bool is_in_dalvik_cache) { + OR_RETURN(ValidateDexPath(dex_path)); + InstructionSet isa = OR_RETURN(ValidateAndGetIsa(isa_str)); + std::string oat_path; std::string error_msg; - RawArtifactsPath path; - if (artifacts_path.isInDalvikCache) { + if (is_in_dalvik_cache) { // Apps' OAT files are never in ART APEX data. - if (!OatFileAssistant::DexLocationToOatFilename(artifacts_path.dexPath, + if (!OatFileAssistant::DexLocationToOatFilename(dex_path, isa, /*deny_art_apex_data_files=*/true, - &path.oat_path, + &oat_path, &error_msg)) { - return Error() << error_msg; + return Errorf("{}", error_msg); } } else { - if (!OatFileAssistant::DexLocationToOdexFilename( - artifacts_path.dexPath, isa, &path.oat_path, &error_msg)) { - return Error() << error_msg; + if (!OatFileAssistant::DexLocationToOdexFilename(dex_path, isa, &oat_path, &error_msg)) { + return Errorf("{}", error_msg); } } + return oat_path; +} + +Result<RawArtifactsPath> BuildArtifactsPath(const ArtifactsPath& artifacts_path) { + RawArtifactsPath path; + path.oat_path = OR_RETURN( + BuildOatPath(artifacts_path.dexPath, artifacts_path.isa, artifacts_path.isInDalvikCache)); path.vdex_path = ReplaceFileExtension(path.oat_path, kVdexExtension); path.art_path = ReplaceFileExtension(path.oat_path, kArtExtension); @@ -303,6 +317,19 @@ Result<std::string> BuildVdexPath(const VdexPath& vdex_path) { return OR_RETURN(BuildArtifactsPath(vdex_path.get<VdexPath::artifactsPath>())).vdex_path; } +Result<std::string> BuildSdmPath(const SecureDexMetadataWithCompanionPaths& sdm_path) { + // `sdm_path.isInDalvikCache` is intentionally ignored because it's only applicable to SDC files. + OR_RETURN(ValidateDexPath(sdm_path.dexPath)); + OR_RETURN(ValidateAndGetIsa(sdm_path.isa)); + return ReplaceFileExtension(sdm_path.dexPath, ART_FORMAT(".{}{}", sdm_path.isa, kSdmExtension)); +} + +Result<std::string> BuildSdcPath(const SecureDexMetadataWithCompanionPaths& sdc_path) { + std::string oat_path = + OR_RETURN(BuildOatPath(sdc_path.dexPath, sdc_path.isa, sdc_path.isInDalvikCache)); + return ReplaceFileExtension(oat_path, ".sdc"); +} + bool PreRebootFlag(const ProfilePath& profile_path) { switch (profile_path.getTag()) { case ProfilePath::primaryRefProfilePath: diff --git a/artd/path_utils.h b/artd/path_utils.h index 1528d0610b..e31115683b 100644 --- a/artd/path_utils.h +++ b/artd/path_utils.h @@ -55,6 +55,10 @@ android::base::Result<void> ValidateRuntimeArtifactsPath( android::base::Result<std::string> BuildArtBinPath(const std::string& binary_name); +android::base::Result<std::string> BuildOatPath(const std::string& dex_path, + const std::string& isa_str, + bool is_in_dalvik_cache); + // Returns the absolute paths to files built from the `ArtifactsPath`. android::base::Result<RawArtifactsPath> BuildArtifactsPath( const aidl::com::android::server::art::ArtifactsPath& artifacts_path); @@ -96,6 +100,12 @@ android::base::Result<std::string> BuildProfileOrDmPath( android::base::Result<std::string> BuildVdexPath( const aidl::com::android::server::art::VdexPath& vdex_path); +android::base::Result<std::string> BuildSdmPath( + const aidl::com::android::server::art::SecureDexMetadataWithCompanionPaths& sdm_path); + +android::base::Result<std::string> BuildSdcPath( + const aidl::com::android::server::art::SecureDexMetadataWithCompanionPaths& sdc_path); + // Takes an argument of type `WritableProfilePath`. Returns the pre-reboot flag by value if the // argument is const, or by reference otherwise. template <typename T, diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc index 116177a7a7..8b50ca3549 100644 --- a/artd/path_utils_test.cc +++ b/artd/path_utils_test.cc @@ -255,6 +255,21 @@ TEST_F(PathUtilsTest, BuildVdexPath) { HasValue("/a/oat/arm64/b.vdex")); } +TEST_F(PathUtilsTest, BuildSdmPath) { + EXPECT_THAT(BuildSdmPath({.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}), + HasValue("/a/b.arm64.sdm")); +} + +TEST_F(PathUtilsTest, BuildSdcPath) { + EXPECT_THAT(BuildSdcPath({.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = false}), + HasValue("/a/oat/arm64/b.sdc")); +} + +TEST_F(PathUtilsTest, BuildSdcPathDalvikCache) { + EXPECT_THAT(BuildSdcPath({.dexPath = "/a/b.apk", .isa = "arm64", .isInDalvikCache = true}), + HasValue(android_data_ + "/dalvik-cache/arm64/a@b.apk@classes.sdc")); +} + } // namespace } // namespace artd } // namespace art diff --git a/compiler/optimizing/instruction_simplifier_test.cc b/compiler/optimizing/instruction_simplifier_test.cc index a2e3882c19..448dc32006 100644 --- a/compiler/optimizing/instruction_simplifier_test.cc +++ b/compiler/optimizing/instruction_simplifier_test.cc @@ -234,9 +234,9 @@ TEST_P(InstanceOfInstructionSimplifierTestGroup, ExactClassCheckCastOther) { INSTANTIATE_TEST_SUITE_P(InstructionSimplifierTest, InstanceOfInstructionSimplifierTestGroup, - testing::Values(InstanceOfKind::kSelf, - InstanceOfKind::kUnrelatedLoaded, - InstanceOfKind::kUnrelatedUnloaded, - InstanceOfKind::kSupertype)); + ::testing::Values(InstanceOfKind::kSelf, + InstanceOfKind::kUnrelatedLoaded, + InstanceOfKind::kUnrelatedUnloaded, + InstanceOfKind::kSupertype)); } // namespace art diff --git a/compiler/optimizing/load_store_elimination_test.cc b/compiler/optimizing/load_store_elimination_test.cc index bb73688b89..af039229af 100644 --- a/compiler/optimizing/load_store_elimination_test.cc +++ b/compiler/optimizing/load_store_elimination_test.cc @@ -1779,17 +1779,17 @@ TEST_P(TwoTypesConversionsTestGroup, MergingConvertedValueStorePhiDeduplication) } auto Int32AndSmallerTypesGenerator() { - return testing::Values(DataType::Type::kInt32, - DataType::Type::kInt16, - DataType::Type::kInt8, - DataType::Type::kUint16, - DataType::Type::kUint8); + return ::testing::Values(DataType::Type::kInt32, + DataType::Type::kInt16, + DataType::Type::kInt8, + DataType::Type::kUint16, + DataType::Type::kUint8); } -INSTANTIATE_TEST_SUITE_P( - LoadStoreEliminationTest, - TwoTypesConversionsTestGroup, - testing::Combine(Int32AndSmallerTypesGenerator(), Int32AndSmallerTypesGenerator())); +INSTANTIATE_TEST_SUITE_P(LoadStoreEliminationTest, + TwoTypesConversionsTestGroup, + ::testing::Combine(Int32AndSmallerTypesGenerator(), + Int32AndSmallerTypesGenerator())); // // ENTRY // obj = new Obj(); diff --git a/compiler/optimizing/reference_type_propagation_test.cc b/compiler/optimizing/reference_type_propagation_test.cc index 0e0acd11c2..f720b8d911 100644 --- a/compiler/optimizing/reference_type_propagation_test.cc +++ b/compiler/optimizing/reference_type_propagation_test.cc @@ -496,16 +496,16 @@ TEST_P(LoopReferenceTypePropagationTestGroup, RunVisitTest) { INSTANTIATE_TEST_SUITE_P(ReferenceTypePropagationTest, LoopReferenceTypePropagationTestGroup, - testing::Combine(testing::Values(ShuffleOrder::kAlmostTopological, - ShuffleOrder::kReverseTopological, - ShuffleOrder::kTopological, - ShuffleOrder::kRandom), - testing::Values(-1, 10, 40), - testing::Values(0, 1), - testing::Values(InitialNullState::kAllNonNull, - InitialNullState::kAllNull, - InitialNullState::kHalfNull, - InitialNullState::kRandom))); + ::testing::Combine(::testing::Values(ShuffleOrder::kAlmostTopological, + ShuffleOrder::kReverseTopological, + ShuffleOrder::kTopological, + ShuffleOrder::kRandom), + ::testing::Values(-1, 10, 40), + ::testing::Values(0, 1), + ::testing::Values(InitialNullState::kAllNonNull, + InitialNullState::kAllNull, + InitialNullState::kHalfNull, + InitialNullState::kRandom))); TEST_P(NonLoopReferenceTypePropagationTestGroup, RunVisitTest) { RunVisitListTest([&](std::vector<HInstruction*>& lst) { MutateList(lst, GetParam()); }); @@ -513,9 +513,9 @@ TEST_P(NonLoopReferenceTypePropagationTestGroup, RunVisitTest) { INSTANTIATE_TEST_SUITE_P(ReferenceTypePropagationTest, NonLoopReferenceTypePropagationTestGroup, - testing::Values(ShuffleOrder::kAlmostTopological, - ShuffleOrder::kReverseTopological, - ShuffleOrder::kTopological, - ShuffleOrder::kRandom)); + ::testing::Values(ShuffleOrder::kAlmostTopological, + ShuffleOrder::kReverseTopological, + ShuffleOrder::kTopological, + ShuffleOrder::kRandom)); } // namespace art diff --git a/compiler/utils/assembler_test_base.h b/compiler/utils/assembler_test_base.h index 0a89ad1299..515361af00 100644 --- a/compiler/utils/assembler_test_base.h +++ b/compiler/utils/assembler_test_base.h @@ -42,7 +42,7 @@ namespace art HIDDEN { static constexpr bool kKeepDisassembledFiles = false; // We put this into a class as gtests are self-contained, so this helper needs to be in an h-file. -class AssemblerTestBase : public testing::Test { +class AssemblerTestBase : public ::testing::Test { public: AssemblerTestBase() {} diff --git a/dex2oat/linker/elf_writer_test.cc b/dex2oat/linker/elf_writer_test.cc index 38eec45611..4f7c3b4452 100644 --- a/dex2oat/linker/elf_writer_test.cc +++ b/dex2oat/linker/elf_writer_test.cc @@ -219,13 +219,12 @@ TEST_F(ElfWriterTest, CheckDynamicSection) { size_t bss_methods_offset, size_t bss_roots_offset, size_t dex_section_size, - /*out*/ size_t *number_of_dynamic_symbols) { - SCOPED_TRACE(testing::Message() << "rodata_size: " << rodata_size - << ", text_size: " << text_size + /*out*/ size_t* number_of_dynamic_symbols) { + SCOPED_TRACE(::testing::Message() + << "rodata_size: " << rodata_size << ", text_size: " << text_size << ", data_img_rel_ro_size: " << data_img_rel_ro_size << ", data_img_rel_ro_app_image_offset: " << data_img_rel_ro_app_image_offset - << ", bss_size: " << bss_size - << ", bss_methods_offset: " << bss_methods_offset + << ", bss_size: " << bss_size << ", bss_methods_offset: " << bss_methods_offset << ", bss_roots_offset: " << bss_roots_offset << ", dex_section_size: " << dex_section_size); diff --git a/dex2oat/linker/multi_oat_relative_patcher_test.cc b/dex2oat/linker/multi_oat_relative_patcher_test.cc index b2aa619337..a610bde6e9 100644 --- a/dex2oat/linker/multi_oat_relative_patcher_test.cc +++ b/dex2oat/linker/multi_oat_relative_patcher_test.cc @@ -27,7 +27,7 @@ namespace linker { static const MethodReference kNullMethodRef = MethodReference(nullptr, 0u); -class MultiOatRelativePatcherTest : public testing::Test { +class MultiOatRelativePatcherTest : public ::testing::Test { protected: class MockPatcher : public RelativePatcher { public: diff --git a/dex2oat/linker/relative_patcher_test.h b/dex2oat/linker/relative_patcher_test.h index 3e7ba796e0..16f263ec21 100644 --- a/dex2oat/linker/relative_patcher_test.h +++ b/dex2oat/linker/relative_patcher_test.h @@ -36,7 +36,7 @@ namespace art { namespace linker { // Base class providing infrastructure for architecture-specific tests. -class RelativePatcherTest : public testing::Test { +class RelativePatcherTest : public ::testing::Test { protected: RelativePatcherTest(InstructionSet instruction_set, const std::string& variant) : storage_(/*swap_fd=*/ -1), diff --git a/libartbase/Android.bp b/libartbase/Android.bp index 2d8194c46e..24603c1220 100644 --- a/libartbase/Android.bp +++ b/libartbase/Android.bp @@ -323,10 +323,8 @@ art_cc_library_static { srcs: [ "base/testing.cc", ], - header_libs: [ - "libbase_headers", - "art_libartbase_headers", - ], + header_libs: ["art_libartbase_headers"], + export_header_lib_headers: ["art_libartbase_headers"], } art_cc_defaults { diff --git a/libartbase/base/arena_allocator_test.cc b/libartbase/base/arena_allocator_test.cc index 6323a2b97c..d956b4ab5e 100644 --- a/libartbase/base/arena_allocator_test.cc +++ b/libartbase/base/arena_allocator_test.cc @@ -23,7 +23,7 @@ namespace art { -class ArenaAllocatorTest : public testing::Test { +class ArenaAllocatorTest : public ::testing::Test { protected: size_t NumberOfArenas(ArenaAllocator* allocator) { size_t result = 0u; diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc index 2fcf41c14e..41ed98d651 100644 --- a/libartbase/base/common_art_test.cc +++ b/libartbase/base/common_art_test.cc @@ -34,7 +34,6 @@ #include "android-base/process.h" #include "android-base/scopeguard.h" #include "android-base/stringprintf.h" -#include "android-base/strings.h" #include "android-base/unique_fd.h" #include "art_field-inl.h" #include "base/file_utils.h" @@ -163,107 +162,6 @@ android::base::ScopeGuard<std::function<void()>> ScopedInaccessible(const std::s return android::base::make_scope_guard([=]() { std::filesystem::permissions(path, old_perms); }); } -std::string CommonArtTestImpl::GetAndroidBuildTop() { - CHECK(IsHost()); - std::string android_build_top; - - // Look at how we were invoked to find the expected directory. - std::string argv; - if (android::base::ReadFileToString("/proc/self/cmdline", &argv)) { - // /proc/self/cmdline is the programs 'argv' with elements delimited by '\0'. - std::filesystem::path path(argv.substr(0, argv.find('\0'))); - path = std::filesystem::absolute(path); - // Walk up until we find the one of the well-known directories. - for (; path.parent_path() != path; path = path.parent_path()) { - // We are running tests from out/host/linux-x86 on developer machine. - if (path.filename() == std::filesystem::path("linux-x86")) { - android_build_top = path.parent_path().parent_path().parent_path(); - break; - } - // We are running tests from testcases (extracted from zip) on tradefed. - // The first path is for remote runs and the second path for local runs. - if (path.filename() == std::filesystem::path("testcases") || - path.filename().string().starts_with("host_testcases")) { - android_build_top = path.append("art_common"); - break; - } - } - } - CHECK(!android_build_top.empty()); - - // Check that the expected directory matches the environment variable. - const char* android_build_top_from_env = getenv("ANDROID_BUILD_TOP"); - android_build_top = std::filesystem::path(android_build_top).string(); - CHECK(!android_build_top.empty()); - if (android_build_top_from_env != nullptr) { - if (std::filesystem::weakly_canonical(android_build_top).string() != - std::filesystem::weakly_canonical(android_build_top_from_env).string()) { - android_build_top = android_build_top_from_env; - } - } else { - setenv("ANDROID_BUILD_TOP", android_build_top.c_str(), /*overwrite=*/0); - } - if (android_build_top.back() != '/') { - android_build_top += '/'; - } - return android_build_top; -} - -std::string CommonArtTestImpl::GetAndroidHostOut() { - CHECK(IsHost()); - - // Check that the expected directory matches the environment variable. - // ANDROID_HOST_OUT is set by envsetup or unset and is the full path to host binaries/libs - const char* android_host_out_from_env = getenv("ANDROID_HOST_OUT"); - // OUT_DIR is a user-settable ENV_VAR that controls where soong puts build artifacts. It can - // either be relative to ANDROID_BUILD_TOP or a concrete path. - const char* android_out_dir = getenv("OUT_DIR"); - // Take account of OUT_DIR setting. - if (android_out_dir == nullptr) { - android_out_dir = "out"; - } - std::string android_host_out; - if (android_out_dir[0] == '/') { - android_host_out = (std::filesystem::path(android_out_dir) / "host" / "linux-x86").string(); - } else { - android_host_out = - (std::filesystem::path(GetAndroidBuildTop()) / android_out_dir / "host" / "linux-x86") - .string(); - } - std::filesystem::path expected(android_host_out); - if (android_host_out_from_env != nullptr) { - std::filesystem::path from_env(std::filesystem::weakly_canonical(android_host_out_from_env)); - if (std::filesystem::weakly_canonical(expected).string() != from_env.string()) { - LOG(WARNING) << "Execution path (" << expected << ") not below ANDROID_HOST_OUT (" - << from_env << ")! Using env-var."; - expected = from_env; - } - } else { - setenv("ANDROID_HOST_OUT", android_host_out.c_str(), /*overwrite=*/0); - } - return expected.string(); -} - -std::string CommonArtTestImpl::GetHostBootClasspathInstallRoot() { - CHECK(IsHost()); - std::string build_install_root = GetAndroidHostOut() + "/testcases/art_common/out/host/linux-x86"; - // Look for the `apex` subdirectory as a discriminator to check the location. - if (OS::DirectoryExists((build_install_root + "/apex").c_str())) { - // This is the path where "m art-host-tests" installs support files for host - // tests, so use it when the tests are run in a build tree (which is the - // case when testing locally). - return build_install_root; - } - if (OS::DirectoryExists((GetAndroidRoot() + "/apex").c_str())) { - // This is the location for host tests in CI when the files are unzipped - // from art-host-tests.zip. - return GetAndroidRoot(); - } - LOG(ERROR) << "Neither location has a boot classpath (forgot \"m art-host-tests\"?): " - << build_install_root << " or " << GetAndroidRoot(); - return "<no boot classpath found>"; -} - void CommonArtTestImpl::SetUpAndroidRootEnvVars() { if (IsHost()) { std::string android_host_out = GetAndroidHostOut(); @@ -463,41 +361,6 @@ std::vector<std::string> CommonArtTestImpl::GetLibCoreModuleNames() const { return art::testing::GetLibCoreModuleNames(); } -std::vector<std::string> CommonArtTestImpl::GetLibCoreDexFileNames( - const std::vector<std::string>& modules) const { - return art::testing::GetLibCoreDexFileNames( - kIsTargetBuild ? "" : GetHostBootClasspathInstallRoot(), modules); -} - -std::vector<std::string> CommonArtTestImpl::GetLibCoreDexFileNames() const { - std::vector<std::string> modules = GetLibCoreModuleNames(); - return art::testing::GetLibCoreDexFileNames( - kIsTargetBuild ? "" : GetHostBootClasspathInstallRoot(), modules); -} - -std::vector<std::string> CommonArtTestImpl::GetLibCoreDexLocations( - const std::vector<std::string>& modules) const { - std::string prefix = ""; - if (IsHost()) { - std::string android_root = GetAndroidRoot(); - std::string build_top = GetAndroidBuildTop(); - CHECK(android_root.starts_with(build_top)) - << " android_root=" << android_root << " build_top=" << build_top; - prefix = android_root.substr(build_top.size()); - } - return art::testing::GetLibCoreDexFileNames(prefix, modules); -} - -std::vector<std::string> CommonArtTestImpl::GetLibCoreDexLocations() const { - std::vector<std::string> modules = GetLibCoreModuleNames(); - return GetLibCoreDexLocations(modules); -} - -std::string CommonArtTestImpl::GetClassPathOption(const char* option, - const std::vector<std::string>& class_path) { - return option + android::base::Join(class_path, ':'); -} - // Check that for target builds we have ART_TARGET_NATIVETEST_DIR set. #ifdef ART_TARGET #ifndef ART_TARGET_NATIVETEST_DIR diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h index 5da6ac9920..b27199c386 100644 --- a/libartbase/base/common_art_test.h +++ b/libartbase/base/common_art_test.h @@ -32,6 +32,7 @@ #include "base/memory_tool.h" #include "base/mutex.h" #include "base/os.h" +#include "base/testing.h" #include "base/unix_file/fd_file.h" #include "dex/art_dex_file_loader.h" #include "dex/compact_dex_file.h" @@ -152,19 +153,29 @@ class CommonArtTestImpl { virtual std::vector<std::string> GetLibCoreModuleNames() const; // Gets the paths of the libcore dex files for given modules. - std::vector<std::string> GetLibCoreDexFileNames(const std::vector<std::string>& modules) const; + std::vector<std::string> GetLibCoreDexFileNames(const std::vector<std::string>& modules) const { + return art::testing::GetLibCoreDexFileNames(modules); + } // Gets the paths of the libcore dex files. - std::vector<std::string> GetLibCoreDexFileNames() const; + std::vector<std::string> GetLibCoreDexFileNames() const { + return GetLibCoreDexFileNames(GetLibCoreModuleNames()); + } // Gets the on-host or on-device locations of the libcore dex files for given modules. - std::vector<std::string> GetLibCoreDexLocations(const std::vector<std::string>& modules) const; + std::vector<std::string> GetLibCoreDexLocations(const std::vector<std::string>& modules) const { + return art::testing::GetLibCoreDexLocations(modules); + } // Gets the on-host or on-device locations of the libcore dex files. - std::vector<std::string> GetLibCoreDexLocations() const; + std::vector<std::string> GetLibCoreDexLocations() const { + return GetLibCoreDexLocations(GetLibCoreModuleNames()); + } static std::string GetClassPathOption(const char* option, - const std::vector<std::string>& class_path); + const std::vector<std::string>& class_path) { + return art::testing::GetClassPathOption(option, class_path); + } // Retuerns the filename for a test dex (i.e. XandY or ManyMethods). std::string GetTestDexFileName(const char* name) const; @@ -227,21 +238,15 @@ class CommonArtTestImpl { static std::string GetAndroidTool(const char* name, InstructionSet isa = InstructionSet::kX86_64); protected: - static bool IsHost() { - return !art::kIsTargetBuild; - } + static bool IsHost() { return art::testing::IsHost(); } - // Returns ${ANDROID_BUILD_TOP}. Ensure it has tailing /. - static std::string GetAndroidBuildTop(); + static std::string GetAndroidBuildTop() { return art::testing::GetAndroidBuildTop(); } - // Returns ${ANDROID_HOST_OUT}. - static std::string GetAndroidHostOut(); + static std::string GetAndroidHostOut() { return art::testing::GetAndroidHostOut(); } - // Returns the path where boot classpath and boot image files are installed - // for host tests (by the art_common mk module, typically built through "m - // art-host-tests"). Different in CI where they are unpacked from the - // art-host-tests.zip file. - static std::string GetHostBootClasspathInstallRoot(); + static std::string GetHostBootClasspathInstallRoot() { + return art::testing::GetHostBootClasspathInstallRoot(); + } // File location to boot.art, e.g. /apex/com.android.art/javalib/boot.art static std::string GetCoreArtLocation(); @@ -300,10 +305,10 @@ class CommonArtTestBase : public TestType, public CommonArtTestImpl { } }; -using CommonArtTest = CommonArtTestBase<testing::Test>; +using CommonArtTest = CommonArtTestBase<::testing::Test>; template <typename Param> -using CommonArtTestWithParam = CommonArtTestBase<testing::TestWithParam<Param>>; +using CommonArtTestWithParam = CommonArtTestBase<::testing::TestWithParam<Param>>; // Returns a list of PIDs of the processes whose process name (the first commandline argument) fully // matches the given name. diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc index 2acebb9b4f..4253fa1ce7 100644 --- a/libartbase/base/file_utils.cc +++ b/libartbase/base/file_utils.cc @@ -715,6 +715,15 @@ std::string GetDmFilename(const std::string& dex_location) { return ReplaceFileExtension(dex_location, kDmExtension); } +std::string GetSdmFilename(const std::string& dex_location, InstructionSet isa) { + return ReplaceFileExtension(dex_location, + StringPrintf("%s%s", GetInstructionSetString(isa), kSdmExtension)); +} + +std::string GetSdcFilename(const std::string& oat_location) { + return ReplaceFileExtension(oat_location, kSdcExtension); +} + // check for the file in /system, followed by /system_ext std::string GetSystemOdexFilenameForApex(std::string_view location, InstructionSet isa) { DCHECK(LocationIsOnApex(location)); diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h index e8aa5f663a..89f2420aa4 100644 --- a/libartbase/base/file_utils.h +++ b/libartbase/base/file_utils.h @@ -39,6 +39,7 @@ static constexpr const char* kVdexExtension = ".vdex"; static constexpr const char* kArtExtension = ".art"; static constexpr const char* kDmExtension = ".dm"; static constexpr const char* kSdmExtension = ".sdm"; +static constexpr const char* kSdcExtension = ".sdc"; // These methods return the Android Root, which is the historical location of // the Android "system" directory, containing the built Android artifacts. On @@ -174,6 +175,12 @@ std::string GetVdexFilename(const std::string& oat_filename); // Returns the dm filename for the given dex location. std::string GetDmFilename(const std::string& dex_location); +// Returns the sdm filename for the given dex location. +std::string GetSdmFilename(const std::string& dex_location, InstructionSet isa); + +// Returns the sdc filename for the given oat filename. +std::string GetSdcFilename(const std::string& oat_filename); + // Returns the odex location on /system for a DEX file on /apex. The caller must make sure that // `location` is on /apex. std::string GetSystemOdexFilenameForApex(std::string_view location, InstructionSet isa); diff --git a/libartbase/base/hash_set_test.cc b/libartbase/base/hash_set_test.cc index b7b289cf65..959ab5ebf5 100644 --- a/libartbase/base/hash_set_test.cc +++ b/libartbase/base/hash_set_test.cc @@ -39,7 +39,7 @@ struct IsEmptyFnString { } }; -class HashSetTest : public testing::Test { +class HashSetTest : public ::testing::Test { public: HashSetTest() : seed_(97421), unique_number_(0) { } diff --git a/libartbase/base/intrusive_forward_list_test.cc b/libartbase/base/intrusive_forward_list_test.cc index f96fc9d516..095a94ceeb 100644 --- a/libartbase/base/intrusive_forward_list_test.cc +++ b/libartbase/base/intrusive_forward_list_test.cc @@ -67,7 +67,7 @@ bool operator<(const IFLTestValue2& lhs, const IFLTestValue2& rhs) { ASSERT_TRUE(std::equal((expected).begin(), (expected).end(), (value).begin())); \ } while (false) -class IntrusiveForwardListTest : public testing::Test { +class IntrusiveForwardListTest : public ::testing::Test { public: template <typename ListType> void IteratorToConstIterator(); diff --git a/libartbase/base/logging_test.cc b/libartbase/base/logging_test.cc index 1fa3209f7f..985a8eafae 100644 --- a/libartbase/base/logging_test.cc +++ b/libartbase/base/logging_test.cc @@ -32,7 +32,7 @@ static void SimpleAborter(const char* msg) { _exit(1); } -class LoggingTest : public testing::Test { +class LoggingTest : public ::testing::Test { protected: LoggingTest() { // In our abort tests we really don't want the runtime to create a real dump. diff --git a/libartbase/base/mem_map_test.cc b/libartbase/base/mem_map_test.cc index b3da8fbf52..76a50a59e4 100644 --- a/libartbase/base/mem_map_test.cc +++ b/libartbase/base/mem_map_test.cc @@ -899,17 +899,17 @@ TEST_F(MemMapTest, Reservation) { namespace { -class DumpMapsOnFailListener : public testing::EmptyTestEventListener { - void OnTestPartResult(const testing::TestPartResult& result) override { +class DumpMapsOnFailListener : public ::testing::EmptyTestEventListener { + void OnTestPartResult(const ::testing::TestPartResult& result) override { switch (result.type()) { - case testing::TestPartResult::kFatalFailure: + case ::testing::TestPartResult::kFatalFailure: art::PrintFileToLog("/proc/self/maps", android::base::LogSeverity::ERROR); break; // TODO: Could consider logging on EXPECT failures. - case testing::TestPartResult::kNonFatalFailure: - case testing::TestPartResult::kSkip: - case testing::TestPartResult::kSuccess: + case ::testing::TestPartResult::kNonFatalFailure: + case ::testing::TestPartResult::kSkip: + case ::testing::TestPartResult::kSuccess: break; } } @@ -921,5 +921,5 @@ class DumpMapsOnFailListener : public testing::EmptyTestEventListener { extern "C" __attribute__((visibility("default"))) __attribute__((used)) void ArtTestGlobalInit() { - testing::UnitTest::GetInstance()->listeners().Append(new DumpMapsOnFailListener()); + ::testing::UnitTest::GetInstance()->listeners().Append(new DumpMapsOnFailListener()); } diff --git a/libartbase/base/metrics/metrics_test.cc b/libartbase/base/metrics/metrics_test.cc index bcc4da4e06..3581615514 100644 --- a/libartbase/base/metrics/metrics_test.cc +++ b/libartbase/base/metrics/metrics_test.cc @@ -31,7 +31,7 @@ using test::CounterValue; using test::GetBuckets; using test::TestBackendBase; -class MetricsTest : public testing::Test {}; +class MetricsTest : public ::testing::Test {}; TEST_F(MetricsTest, SimpleCounter) { MetricsCounter<DatumId::kClassVerificationTotalTime> test_counter; diff --git a/libartbase/base/testing.cc b/libartbase/base/testing.cc index 3cd2836876..6ec207eded 100644 --- a/libartbase/base/testing.cc +++ b/libartbase/base/testing.cc @@ -14,19 +14,123 @@ * limitations under the License. */ +#include "testing.h" + #include <string> #include <vector> +#include "android-base/file.h" #include "android-base/stringprintf.h" +#include "android-base/strings.h" #include "base/file_utils.h" #include "base/globals.h" +#include "base/os.h" namespace art { namespace testing { -namespace { +std::string GetAndroidBuildTop() { + CHECK(IsHost()); + std::string android_build_top; + + // Look at how we were invoked to find the expected directory. + std::string argv; + if (android::base::ReadFileToString("/proc/self/cmdline", &argv)) { + // /proc/self/cmdline is the programs 'argv' with elements delimited by '\0'. + std::filesystem::path path(argv.substr(0, argv.find('\0'))); + path = std::filesystem::absolute(path); + // Walk up until we find the one of the well-known directories. + for (; path.parent_path() != path; path = path.parent_path()) { + // We are running tests from out/host/linux-x86 on developer machine. + if (path.filename() == std::filesystem::path("linux-x86")) { + android_build_top = path.parent_path().parent_path().parent_path(); + break; + } + // We are running tests from testcases (extracted from zip) on tradefed. + // The first path is for remote runs and the second path for local runs. + if (path.filename() == std::filesystem::path("testcases") || + path.filename().string().starts_with("host_testcases")) { + android_build_top = path.append("art_common"); + break; + } + } + } + CHECK(!android_build_top.empty()); + + // Check that the expected directory matches the environment variable. + const char* android_build_top_from_env = getenv("ANDROID_BUILD_TOP"); + android_build_top = std::filesystem::path(android_build_top).string(); + CHECK(!android_build_top.empty()); + if (android_build_top_from_env != nullptr) { + if (std::filesystem::weakly_canonical(android_build_top).string() != + std::filesystem::weakly_canonical(android_build_top_from_env).string()) { + android_build_top = android_build_top_from_env; + } + } else { + setenv("ANDROID_BUILD_TOP", android_build_top.c_str(), /*overwrite=*/0); + } + if (android_build_top.back() != '/') { + android_build_top += '/'; + } + return android_build_top; +} + +std::string GetAndroidHostOut() { + CHECK(IsHost()); + + // Check that the expected directory matches the environment variable. + // ANDROID_HOST_OUT is set by envsetup or unset and is the full path to host binaries/libs + const char* android_host_out_from_env = getenv("ANDROID_HOST_OUT"); + // OUT_DIR is a user-settable ENV_VAR that controls where soong puts build artifacts. It can + // either be relative to ANDROID_BUILD_TOP or a concrete path. + const char* android_out_dir = getenv("OUT_DIR"); + // Take account of OUT_DIR setting. + if (android_out_dir == nullptr) { + android_out_dir = "out"; + } + std::string android_host_out; + if (android_out_dir[0] == '/') { + android_host_out = (std::filesystem::path(android_out_dir) / "host" / "linux-x86").string(); + } else { + android_host_out = + (std::filesystem::path(GetAndroidBuildTop()) / android_out_dir / "host" / "linux-x86") + .string(); + } + std::filesystem::path expected(android_host_out); + if (android_host_out_from_env != nullptr) { + std::filesystem::path from_env(std::filesystem::weakly_canonical(android_host_out_from_env)); + if (std::filesystem::weakly_canonical(expected).string() != from_env.string()) { + LOG(WARNING) << "Execution path (" << expected << ") not below ANDROID_HOST_OUT (" << from_env + << ")! Using env-var."; + expected = from_env; + } + } else { + setenv("ANDROID_HOST_OUT", android_host_out.c_str(), /*overwrite=*/0); + } + return expected.string(); +} + +std::string GetHostBootClasspathInstallRoot() { + CHECK(IsHost()); + std::string build_install_root = GetAndroidHostOut() + "/testcases/art_common/out/host/linux-x86"; + // Look for the `apex` subdirectory as a discriminator to check the location. + if (OS::DirectoryExists((build_install_root + "/apex").c_str())) { + // This is the path where "m art-host-tests" installs support files for host + // tests, so use it when the tests are run in a build tree (which is the + // case when testing locally). + return build_install_root; + } + if (OS::DirectoryExists((GetAndroidRoot() + "/apex").c_str())) { + // This is the location for host tests in CI when the files are unzipped + // from art-host-tests.zip. + return GetAndroidRoot(); + } + LOG(ERROR) << "Neither location has a boot classpath (forgot \"m art-host-tests\"?): " + << build_install_root << " or " << GetAndroidRoot(); + return "<no boot classpath found>"; +} -std::string GetDexFileName(const std::string& jar_prefix, const std::string& prefix) { +static std::string GetDexFileName(const std::string& jar_prefix, const std::string& prefix) { const char* apexPath = (jar_prefix == "conscrypt") ? kAndroidConscryptApexDefaultPath : @@ -35,7 +139,15 @@ std::string GetDexFileName(const std::string& jar_prefix, const std::string& pre "%s%s/javalib/%s.jar", prefix.c_str(), apexPath, jar_prefix.c_str()); } -} // namespace +static std::vector<std::string> GetPrefixedDexFileNames(const std::string& prefix, + const std::vector<std::string>& modules) { + std::vector<std::string> result; + result.reserve(modules.size()); + for (const std::string& module : modules) { + result.push_back(GetDexFileName(module, prefix)); + } + return result; +} std::vector<std::string> GetLibCoreModuleNames(bool core_only) { // Note: This must start with the CORE_IMG_JARS in Android.common_path.mk because that's what we @@ -59,23 +171,25 @@ std::vector<std::string> GetLibCoreModuleNames(bool core_only) { return modules; } -std::vector<std::string> GetLibCoreDexFileNames(const std::string& prefix, - const std::vector<std::string>& modules) { - std::vector<std::string> result; - result.reserve(modules.size()); - for (const std::string& module : modules) { - result.push_back(GetDexFileName(module, prefix)); - } - return result; +std::vector<std::string> GetLibCoreDexFileNames(const std::vector<std::string>& modules) { + return GetPrefixedDexFileNames(kIsTargetBuild ? "" : GetHostBootClasspathInstallRoot(), modules); } std::vector<std::string> GetLibCoreDexFileNames(const std::string& prefix, bool core_only) { std::vector<std::string> modules = GetLibCoreModuleNames(core_only); - return GetLibCoreDexFileNames(prefix, modules); + return GetPrefixedDexFileNames(prefix, modules); } std::vector<std::string> GetLibCoreDexLocations(const std::vector<std::string>& modules) { - return GetLibCoreDexFileNames(/*prefix=*/"", modules); + std::string prefix = ""; + if (IsHost()) { + std::string android_root = GetAndroidRoot(); + std::string build_top = GetAndroidBuildTop(); + CHECK(android_root.starts_with(build_top)) + << " android_root=" << android_root << " build_top=" << build_top; + prefix = android_root.substr(build_top.size()); + } + return GetPrefixedDexFileNames(prefix, modules); } std::vector<std::string> GetLibCoreDexLocations(bool core_only) { @@ -83,5 +197,9 @@ std::vector<std::string> GetLibCoreDexLocations(bool core_only) { return GetLibCoreDexLocations(modules); } +std::string GetClassPathOption(const char* option, const std::vector<std::string>& class_path) { + return option + android::base::Join(class_path, ':'); +} + } // namespace testing } // namespace art diff --git a/libartbase/base/testing.h b/libartbase/base/testing.h index 88d0aee6e0..55d7428436 100644 --- a/libartbase/base/testing.h +++ b/libartbase/base/testing.h @@ -22,20 +22,41 @@ #include <string> #include <vector> +#include "base/globals.h" + namespace art { namespace testing { +inline bool IsHost() { return !art::kIsTargetBuild; } + +// Returns ${ANDROID_BUILD_TOP}. Ensure it has tailing /. +std::string GetAndroidBuildTop(); + +// Returns ${ANDROID_HOST_OUT}. +std::string GetAndroidHostOut(); + +// Returns the path where boot classpath and boot image files are installed +// for host tests (by the art_common mk module, typically built through "m +// art-host-tests"). Different in CI where they are unpacked from the +// art-host-tests.zip file. +std::string GetHostBootClasspathInstallRoot(); + // Note: "libcore" here means art + conscrypt + icu. // Gets the names of the libcore modules. // If `core_only` is true, only returns the names of CORE_IMG_JARS in Android.common_path.mk. std::vector<std::string> GetLibCoreModuleNames(bool core_only = false); -// Gets the paths of the libcore dex files for given modules. -std::vector<std::string> GetLibCoreDexFileNames(const std::string& prefix, - const std::vector<std::string>& modules); +// Gets the paths of the libcore dex files for given modules, prefixed appropriately for host or +// target tests. +std::vector<std::string> GetLibCoreDexFileNames(const std::vector<std::string>& modules); + +// Gets the paths of the libcore module dex files, prefixed appropriately for host or target tests. +inline std::vector<std::string> GetLibCoreDexFileNames() { + return GetLibCoreDexFileNames(GetLibCoreModuleNames()); +} -// Gets the paths of the libcore dex files. +// Gets the paths of the libcore dex files, prefixed by the given string. // If `core_only` is true, only returns the filenames of CORE_IMG_JARS in Android.common_path.mk. std::vector<std::string> GetLibCoreDexFileNames(const std::string& prefix, bool core_only = false); @@ -46,6 +67,8 @@ std::vector<std::string> GetLibCoreDexLocations(const std::vector<std::string>& // If `core_only` is true, only returns the filenames of CORE_IMG_JARS in Android.common_path.mk. std::vector<std::string> GetLibCoreDexLocations(bool core_only = false); +std::string GetClassPathOption(const char* option, const std::vector<std::string>& class_path); + } // namespace testing } // namespace art diff --git a/libartbase/base/time_utils.h b/libartbase/base/time_utils.h index dd73b1c951..ddabb1289f 100644 --- a/libartbase/base/time_utils.h +++ b/libartbase/base/time_utils.h @@ -26,6 +26,8 @@ #include <cstdint> #include <string> +#include "android-base/logging.h" + namespace art { enum TimeUnit { @@ -123,6 +125,13 @@ void NanoSleep(uint64_t ns); // time corresponding to the indicated clock value plus the supplied offset. void InitTimeSpec(bool absolute, int clock, int64_t ms, int32_t ns, timespec* ts); +// Converts `timespec` to nanoseconds. The return value can be negative, which should be interpreted +// as a time before the epoch. +static constexpr int64_t TimeSpecToNs(timespec ts) { + DCHECK_GE(ts.tv_nsec, 0); // According to POSIX. + return static_cast<int64_t>(ts.tv_sec) * INT64_C(1000000000) + ts.tv_nsec; +} + } // namespace art #endif // ART_LIBARTBASE_BASE_TIME_UTILS_H_ diff --git a/libartbase/base/unix_file/fd_file_test.cc b/libartbase/base/unix_file/fd_file_test.cc index 374edc96e7..3de3bd91a2 100644 --- a/libartbase/base/unix_file/fd_file_test.cc +++ b/libartbase/base/unix_file/fd_file_test.cc @@ -357,7 +357,7 @@ TEST_F(FdFileTest, CopySparseFullCopy) { TEST_DISABLED_FOR_HOST(); auto verify_fullcopy = [&](size_t empty_prefix, size_t empty_suffix) { - SCOPED_TRACE(testing::Message() << "prefix:" << empty_prefix << ", suffix:" << empty_suffix); + SCOPED_TRACE(::testing::Message() << "prefix:" << empty_prefix << ", suffix:" << empty_suffix); std::unique_ptr<art::ScratchFile> src; ASSERT_NO_FATAL_FAILURE(CreateSparseSourceFile(empty_prefix, empty_suffix, src)); @@ -435,9 +435,9 @@ TEST_F(FdFileTest, CopySparsePartialCopy) { size_t copy_end_offset) { // The copy starts <copy_start_offset> from the start of the source file. // The copy ends <copy_end_offset> from the end of the source file. - SCOPED_TRACE(testing::Message() << "prefix:" << empty_prefix << ", suffix:" << empty_suffix - << ", copy_start_offset:" << copy_start_offset << ", copy_end_offset:" - << copy_end_offset); + SCOPED_TRACE(::testing::Message() << "prefix:" << empty_prefix << ", suffix:" << empty_suffix + << ", copy_start_offset:" << copy_start_offset + << ", copy_end_offset:" << copy_end_offset); std::unique_ptr<art::ScratchFile> src; ASSERT_NO_FATAL_FAILURE(CreateSparseSourceFile(empty_prefix, empty_suffix, src)); diff --git a/libartbase/base/unix_file/random_access_file_test.h b/libartbase/base/unix_file/random_access_file_test.h index 0592256291..abcc161aab 100644 --- a/libartbase/base/unix_file/random_access_file_test.h +++ b/libartbase/base/unix_file/random_access_file_test.h @@ -25,7 +25,7 @@ namespace unix_file { -class RandomAccessFileTest : public testing::Test { +class RandomAccessFileTest : public ::testing::Test { protected: virtual ~RandomAccessFileTest() { } diff --git a/libartbase/base/utils_test.cc b/libartbase/base/utils_test.cc index a422fc5369..88d8f07741 100644 --- a/libartbase/base/utils_test.cc +++ b/libartbase/base/utils_test.cc @@ -22,7 +22,7 @@ namespace art { -class UtilsTest : public testing::Test {}; +class UtilsTest : public ::testing::Test {}; TEST_F(UtilsTest, PrettySize) { EXPECT_EQ("1024MB", PrettySize(1 * GB)); @@ -114,10 +114,10 @@ TEST_F(UtilsTest, Split) { } TEST_F(UtilsTest, GetProcessStatus) { - EXPECT_THAT(GetProcessStatus("Name"), - testing::AnyOf( - "art_libartbase_", // Test binary name: `art_libartbase_test`. - "art_standalone_")); // Test binary name: `art_standalone_libartbase_test`. + EXPECT_THAT( + GetProcessStatus("Name"), + ::testing::AnyOf("art_libartbase_", // Test binary name: `art_libartbase_test`. + "art_standalone_")); // Test binary name: `art_standalone_libartbase_test`. EXPECT_EQ("R (running)", GetProcessStatus("State")); EXPECT_EQ("<unknown>", GetProcessStatus("tate")); EXPECT_EQ("<unknown>", GetProcessStatus("e")); diff --git a/libartpalette/Android.bp b/libartpalette/Android.bp index 89607219cc..29ce22bcc4 100644 --- a/libartpalette/Android.bp +++ b/libartpalette/Android.bp @@ -141,8 +141,7 @@ art_cc_defaults { }, } -// Version of ART gtest `art_libartpalette_tests` for host. -// TODO(b/192274705): Remove this module when the migration to standalone ART gtests is complete. +// Version of API coverage test for host. art_cc_test { name: "art_libartpalette_tests", defaults: [ @@ -153,17 +152,41 @@ art_cc_test { device_supported: false, } -// Standalone version of ART gtest `art_libartpalette_tests`, not bundled with the ART APEX on -// target. +// MCTS test for API coverage. This test starts a VM to check the JNI +// notification callbacks, so it should not use art_standalone_gtest_defaults, +// which statically links a runtime via libart-gtest. art_cc_test { name: "art_standalone_libartpalette_tests", defaults: [ - "art_standalone_gtest_defaults", + "art_standalone_test_defaults", "art_libartpalette_tests_defaults", ], + static_libs: [ + "libartbase-testing", + "libartpalette", + ], + shared_libs: [ + "liblog", + // Bypass stubs to get access to the platform-only JniInvocation APIs. + // They're not NDK APIs, but have the same stability requirements. + "libnativehelper#impl", + ], test_config_template: ":art-gtests-target-standalone-cts-template", test_suites: [ "cts", + "general-tests", "mcts-art", + "mts-art", ], + + // Duplicated from art_standalone_gtest_defaults + compile_multilib: "both", + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, } diff --git a/libartpalette/apex/palette_test.cc b/libartpalette/apex/palette_test.cc index 744d6a8d30..a72e523365 100644 --- a/libartpalette/apex/palette_test.cc +++ b/libartpalette/apex/palette_test.cc @@ -23,12 +23,13 @@ #include <cstring> -#include "base/common_art_test.h" +#include "base/testing.h" #include "gtest/gtest.h" #ifdef ART_TARGET_ANDROID #include "android-modules-utils/sdk_level.h" #include "android/api-level.h" +#include "nativehelper/JniInvocation.h" #endif namespace { @@ -58,7 +59,7 @@ bool PaletteDebugStoreIsSupported() { } // namespace -class PaletteClientTest : public testing::Test {}; +class PaletteClientTest : public ::testing::Test {}; TEST_F(PaletteClientTest, SchedPriority) { int32_t tid = GetTid(); @@ -92,16 +93,25 @@ TEST_F(PaletteClientTest, Ashmem) { #endif } -class PaletteClientJniTest : public art::CommonArtTest {}; - -TEST_F(PaletteClientJniTest, JniInvocation) { +TEST_F(PaletteClientTest, JniInvocation) { +#ifndef ART_TARGET_ANDROID + // On host we need to use the runtime linked into the test to start a VM (e.g. + // by inheriting CommonArtTest), while on device it needs to launch the + // runtime through libnativehelper. Let's not bother on host since this test + // is only for native API coverage on device. + GTEST_SKIP() << "Will only spin up a VM on Android"; +#else bool enabled; EXPECT_EQ(PALETTE_STATUS_OK, PaletteShouldReportJniInvocations(&enabled)); + // Load the default JNI_CreateJavaVM implementation, i.e., libart.so. + JniInvocation jni_invocation; + ASSERT_TRUE(jni_invocation.Init(/*library=*/ nullptr)); + std::string boot_class_path_string = - GetClassPathOption("-Xbootclasspath:", GetLibCoreDexFileNames()); - std::string boot_class_path_locations_string = - GetClassPathOption("-Xbootclasspath-locations:", GetLibCoreDexLocations()); + art::testing::GetClassPathOption("-Xbootclasspath:", art::testing::GetLibCoreDexFileNames()); + std::string boot_class_path_locations_string = art::testing::GetClassPathOption( + "-Xbootclasspath-locations:", art::testing::GetLibCoreDexLocations()); JavaVMOption options[] = { {.optionString = boot_class_path_string.c_str(), .extraInfo = nullptr}, @@ -123,6 +133,7 @@ TEST_F(PaletteClientJniTest, JniInvocation) { PaletteNotifyEndJniInvocation(env); EXPECT_EQ(JNI_OK, jvm->DestroyJavaVM()); +#endif } TEST_F(PaletteClientTest, SetTaskProfiles) { diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java index ad73d6d4c3..0932087289 100644 --- a/libartservice/service/java/com/android/server/art/AidlUtils.java +++ b/libartservice/service/java/com/android/server/art/AidlUtils.java @@ -223,6 +223,26 @@ public final class AidlUtils { } @NonNull + public static SecureDexMetadataWithCompanionPaths buildSecureDexMetadataWithCompanionPaths( + @NonNull String dexPath, @NonNull String isa, boolean isInDalvikCache) { + var paths = new SecureDexMetadataWithCompanionPaths(); + paths.dexPath = dexPath; + paths.isa = isa; + paths.isInDalvikCache = isInDalvikCache; + return paths; + } + + @NonNull + public static OutputSecureDexMetadataCompanion buildOutputSecureDexMetadataCompanion( + @NonNull String dexPath, @NonNull String isa, boolean isInDalvikCache, + @NonNull PermissionSettings permissionSettings) { + var outputSdc = new OutputSecureDexMetadataCompanion(); + outputSdc.sdcPath = buildSecureDexMetadataWithCompanionPaths(dexPath, isa, isInDalvikCache); + outputSdc.permissionSettings = permissionSettings; + return outputSdc; + } + + @NonNull public static WritableProfilePath toWritableProfilePath(@NonNull ProfilePath profile) { switch (profile.getTag()) { case ProfilePath.primaryRefProfilePath: @@ -288,4 +308,11 @@ public final class AidlUtils { + "got " + profile.getTag()); } } + + @NonNull + public static String toString(@NonNull SecureDexMetadataWithCompanionPaths paths) { + return String.format( + "SecureDexMetadataWithCompanionPaths[dexPath = %s, isa = %s, isInDalvikCache = %b]", + paths.dexPath, paths.isa, paths.isInDalvikCache); + } } diff --git a/libartservice/service/java/com/android/server/art/ArtFileManager.java b/libartservice/service/java/com/android/server/art/ArtFileManager.java index 754b9ec1dd..534952a304 100644 --- a/libartservice/service/java/com/android/server/art/ArtFileManager.java +++ b/libartservice/service/java/com/android/server/art/ArtFileManager.java @@ -45,7 +45,6 @@ import dalvik.system.DexFile; import com.google.auto.value.AutoValue; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; @@ -102,6 +101,7 @@ public class ArtFileManager { public WritableArtifactLists getWritableArtifacts(@NonNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull Options options) throws RemoteException { List<ArtifactsPath> artifacts = new ArrayList<>(); + List<SecureDexMetadataWithCompanionPaths> sdmFiles = new ArrayList<>(); List<RuntimeArtifactsPath> runtimeArtifacts = new ArrayList<>(); if (options.forPrimaryDex()) { @@ -110,6 +110,9 @@ public class ArtFileManager { for (Abi abi : Utils.getAllAbis(pkgState)) { artifacts.add(AidlUtils.buildArtifactsPathAsInput( dexInfo.dexPath(), abi.isa(), isInDalvikCache)); + // SDM files are only for primary dex files. + sdmFiles.add(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + dexInfo.dexPath(), abi.isa(), isInDalvikCache)); // Runtime images are only generated for primary dex files. runtimeArtifacts.add(AidlUtils.buildRuntimeArtifactsPath( pkgState.getPackageName(), dexInfo.dexPath(), abi.isa())); @@ -126,7 +129,7 @@ public class ArtFileManager { } } - return WritableArtifactLists.create(artifacts, runtimeArtifacts); + return new WritableArtifactLists(artifacts, sdmFiles, runtimeArtifacts); } /** Returns artifacts that are usable, regardless of whether they are writable. */ @@ -135,6 +138,7 @@ public class ArtFileManager { @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) throws RemoteException { List<ArtifactsPath> artifacts = new ArrayList<>(); List<VdexPath> vdexFiles = new ArrayList<>(); + List<SecureDexMetadataWithCompanionPaths> sdmFiles = new ArrayList<>(); List<RuntimeArtifactsPath> runtimeArtifacts = new ArrayList<>(); var options = ArtFileManager.Options.builder() @@ -159,9 +163,30 @@ public class ArtFileManager { } else { artifacts.add(thisArtifacts); } + } else if (result.artifactsLocation == ArtifactsLocation.SDM_DALVIK_CACHE + || result.artifactsLocation == ArtifactsLocation.SDM_NEXT_TO_DEX) { + sdmFiles.add(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + dexInfo.dexPath(), abi.isa(), + result.artifactsLocation == ArtifactsLocation.SDM_DALVIK_CACHE)); + } + + if (result.artifactsLocation != ArtifactsLocation.NONE_OR_ERROR) { // Runtime images are only generated for primary dex files. if (dexInfo instanceof DetailedPrimaryDexInfo && !DexFile.isOptimizedCompilerFilter(result.compilerFilter)) { + // Those not added to the list are definitely unusable, but those added to + // the list are not necessarily usable. For example, runtime artifacts can + // be outdated when the corresponding dex file is updated, but they may + // still show up in this list. + // + // However, this is not a severe problem. For `ArtManagerLocal.cleanup`, the + // worst result is only that we are keeping more runtime artifacts than + // needed. For `ArtManagerLocal.getArtManagedFileStats`, this is an edge + // case because the API call is transitively initiated by the app itself, + // and the runtime refreshes unusable runtime artifacts as soon as the app + // starts. + // + // TODO(jiakaiz): Improve this. runtimeArtifacts.add(AidlUtils.buildRuntimeArtifactsPath( pkgState.getPackageName(), dexInfo.dexPath(), abi.isa())); } @@ -176,7 +201,7 @@ public class ArtFileManager { } } - return UsableArtifactLists.create(artifacts, vdexFiles, runtimeArtifacts); + return new UsableArtifactLists(artifacts, vdexFiles, sdmFiles, runtimeArtifacts); } @NonNull @@ -209,7 +234,7 @@ public class ArtFileManager { } } - return ProfileLists.create(refProfiles, curProfiles); + return new ProfileLists(refProfiles, curProfiles); } @NonNull @@ -221,71 +246,17 @@ public class ArtFileManager { : mInjector.getDexUseManager().getSecondaryDexInfo(pkgState.getPackageName()); } - @Immutable - @AutoValue - @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava. - public abstract static class WritableArtifactLists { - protected WritableArtifactLists() {} - - public static @NonNull WritableArtifactLists create(@NonNull List<ArtifactsPath> artifacts, - @NonNull List<RuntimeArtifactsPath> runtimeArtifacts) { - return new AutoValue_ArtFileManager_WritableArtifactLists( - Collections.unmodifiableList(artifacts), - Collections.unmodifiableList(runtimeArtifacts)); - } - - public abstract @NonNull List<ArtifactsPath> artifacts(); - public abstract @NonNull List<RuntimeArtifactsPath> runtimeArtifacts(); - } - - @Immutable - @AutoValue - @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava. - public abstract static class UsableArtifactLists { - protected UsableArtifactLists() {} - - public static @NonNull UsableArtifactLists create(@NonNull List<ArtifactsPath> artifacts, - @NonNull List<VdexPath> vdexFiles, - @NonNull List<RuntimeArtifactsPath> runtimeArtifacts) { - return new AutoValue_ArtFileManager_UsableArtifactLists( - Collections.unmodifiableList(artifacts), - Collections.unmodifiableList(vdexFiles), - Collections.unmodifiableList(runtimeArtifacts)); - } - - public abstract @NonNull List<ArtifactsPath> artifacts(); - public abstract @NonNull List<VdexPath> vdexFiles(); - - // Those not added to the list are definitely unusable, but those added to the list are not - // necessarily usable. For example, runtime artifacts can be outdated when the corresponding - // dex file is updated, but they may still show up in this list. - // - // However, this is not a severe problem. For `ArtManagerLocal.cleanup`, the worst result is - // only that we are keeping more runtime artifacts than needed. For - // `ArtManagerLocal.getArtManagedFileStats`, this is an edge case because the API call is - // transitively initiated by the app itself, and the runtime refreshes unusable runtime - // artifacts as soon as the app starts. - // - // TODO(jiakaiz): Improve this. - public abstract @NonNull List<RuntimeArtifactsPath> runtimeArtifacts(); - } - - @Immutable - @AutoValue - @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava. - public abstract static class ProfileLists { - protected ProfileLists() {} - - public static @NonNull ProfileLists create( - @NonNull List<ProfilePath> refProfiles, @NonNull List<ProfilePath> curProfiles) { - return new AutoValue_ArtFileManager_ProfileLists( - Collections.unmodifiableList(refProfiles), - Collections.unmodifiableList(curProfiles)); - } + public record WritableArtifactLists(@NonNull List<ArtifactsPath> artifacts, + @NonNull List<SecureDexMetadataWithCompanionPaths> sdmFiles, + @NonNull List<RuntimeArtifactsPath> runtimeArtifacts) {} - public abstract @NonNull List<ProfilePath> refProfiles(); - public abstract @NonNull List<ProfilePath> curProfiles(); + public record UsableArtifactLists(@NonNull List<ArtifactsPath> artifacts, + @NonNull List<VdexPath> vdexFiles, + @NonNull List<SecureDexMetadataWithCompanionPaths> sdmFiles, + @NonNull List<RuntimeArtifactsPath> runtimeArtifacts) {} + public record ProfileLists( + @NonNull List<ProfilePath> refProfiles, @NonNull List<ProfilePath> curProfiles) { public @NonNull List<ProfilePath> allProfiles() { List<ProfilePath> profiles = new ArrayList<>(); profiles.addAll(refProfiles()); diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java index 735da25fbb..993cb2c558 100644 --- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java @@ -202,8 +202,8 @@ public final class ArtManagerLocal { } /** - * Deletes dexopt artifacts of a package, including the artifacts for primary dex files and the - * ones for secondary dex files. This includes VDEX, ODEX, and ART files. + * Deletes dexopt artifacts (including cloud dexopt artifacts) of a package, for primary dex + * files and for secondary dex files. This includes VDEX, ODEX, ART, SDM, and SDC files. * * Also deletes runtime artifacts of the package, though they are not dexopt artifacts. * @@ -232,6 +232,9 @@ public final class ArtManagerLocal { for (RuntimeArtifactsPath runtimeArtifacts : list.runtimeArtifacts()) { freedBytes += mInjector.getArtd().deleteRuntimeArtifacts(runtimeArtifacts); } + for (SecureDexMetadataWithCompanionPaths sdmSdcFiles : list.sdmFiles()) { + freedBytes += mInjector.getArtd().deleteSdmSdcFiles(sdmSdcFiles); + } return DeleteResult.create(freedBytes); } catch (RemoteException e) { Utils.logArtdException(e); @@ -390,13 +393,17 @@ public final class ArtManagerLocal { } /** - * Resets the dexopt state of the package as if the package is newly installed. + * Resets the dexopt state of the package as if the package is newly installed without cloud + * dexopt artifacts (SDM files). * - * More specifically, it clears reference profiles, current profiles, any code compiled from - * those local profiles, and runtime artifacts. If there is an external profile (e.g., a cloud - * profile), the code compiled from that profile will be kept. + * More specifically, + * - It clears current profiles, reference profiles, and all dexopt artifacts (including cloud + * dexopt artifacts). + * - If there is an external profile (e.g., a cloud profile), the reference profile will be + * re-created from that profile, and dexopt artifacts will be regenerated for that profile. * - * For secondary dex files, it also clears all dexopt artifacts. + * For secondary dex files, it clears all profiles and dexopt artifacts without regeneration + * because secondary dex files are supposed to be unknown at install time. * * @hide */ @@ -1056,6 +1063,10 @@ public final class ArtManagerLocal { for (RuntimeArtifactsPath runtimeArtifacts : artifactLists.runtimeArtifacts()) { artifactsSize += artd.getRuntimeArtifactsSize(runtimeArtifacts); } + for (SecureDexMetadataWithCompanionPaths sdmFile : artifactLists.sdmFiles()) { + // We don't count SDC files because they are presumed to be tiny. + artifactsSize += artd.getSdmFileSize(sdmFile); + } ProfileLists profileLists = mInjector.getArtFileManager().getProfiles(pkgState, pkg, ArtFileManager.Options.builder() @@ -1119,6 +1130,7 @@ public final class ArtManagerLocal { // - The dexopt artifacts, if they are up-to-date and the app is not hibernating. // - Only the VDEX part of the dexopt artifacts, if the dexopt artifacts are outdated // but the VDEX part is still usable and the app is not hibernating. + // - The SDM and SDC files, if they are up-to-date and the app is not hibernating. // - The runtime artifacts, if dexopt artifacts are fully or partially usable and the // usable parts don't contain AOT-compiled code. (This logic must be aligned with the // one that determines when runtime images can be loaded in @@ -1126,6 +1138,7 @@ public final class ArtManagerLocal { List<ProfilePath> profilesToKeep = new ArrayList<>(); List<ArtifactsPath> artifactsToKeep = new ArrayList<>(); List<VdexPath> vdexFilesToKeep = new ArrayList<>(); + List<SecureDexMetadataWithCompanionPaths> sdmSdcFilesToKeep = new ArrayList<>(); List<RuntimeArtifactsPath> runtimeArtifactsToKeep = new ArrayList<>(); for (PackageState pkgState : snapshot.getPackageStates().values()) { @@ -1146,11 +1159,12 @@ public final class ArtManagerLocal { mInjector.getArtFileManager().getUsableArtifacts(pkgState, pkg); artifactsToKeep.addAll(artifactLists.artifacts()); vdexFilesToKeep.addAll(artifactLists.vdexFiles()); + sdmSdcFilesToKeep.addAll(artifactLists.sdmFiles()); runtimeArtifactsToKeep.addAll(artifactLists.runtimeArtifacts()); } } return mInjector.getArtd().cleanup(profilesToKeep, artifactsToKeep, vdexFilesToKeep, - runtimeArtifactsToKeep, + sdmSdcFilesToKeep, runtimeArtifactsToKeep, SdkLevel.isAtLeastV() && mInjector.getPreRebootDexoptJob().hasStarted()); } catch (RemoteException e) { Utils.logArtdException(e); diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java index f83f5cf083..a3b2fba42a 100644 --- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java +++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java @@ -967,13 +967,16 @@ public final class ArtShellCommand extends BasicShellCommandHandler { pw.println(" -f Force dexopt, also when the compiler filter being applied is not"); pw.println(" better than that of the current dexopt artifacts for a package."); pw.println(" --reset Reset the dexopt state of the package as if the package is newly"); - pw.println(" installed."); - pw.println(" More specifically, it clears current profiles, reference profiles"); - pw.println(" from local profiles, and any code compiled from those local profiles."); - pw.println(" If there is an external profile (e.g., a cloud profile), the reference"); - pw.println(" profile from that profile and the code compiled from that profile will"); - pw.println(" be kept."); - pw.println(" For secondary dex files, it also clears all dexopt artifacts."); + pw.println(" installed without cloud dexopt artifacts (SDM files)."); + pw.println(" More specifically,"); + pw.println(" - It clears current profiles, reference profiles, and all dexopt"); + pw.println(" artifacts (including cloud dexopt artifacts)."); + pw.println(" - If there is an external profile (e.g., a cloud profile), the"); + pw.println(" reference profile will be re-created from that profile, and dexopt"); + pw.println(" artifacts will be regenerated for that profile."); + pw.println(" For secondary dex files, it clears all profiles and dexopt artifacts"); + pw.println(" without regeneration because secondary dex files are supposed to be"); + pw.println(" unknown at install time."); pw.println(" When this flag is set, all the other flags are ignored."); pw.println(" -v Verbose mode. This mode prints detailed results."); pw.println(" --force-merge-profile Force merge profiles even if the difference between"); @@ -1000,7 +1003,7 @@ public final class ArtShellCommand extends BasicShellCommandHandler { pw.println(); pw.println("delete-dexopt PACKAGE_NAME"); pw.println(" Delete the dexopt artifacts of both primary dex files and secondary dex"); - pw.println(" files of a package."); + pw.println(" files of a package, including cloud dexopt artifacts (SDM files)."); pw.println(); pw.println("bg-dexopt-job [--cancel | --disable | --enable]"); pw.println(" Control the background dexopt job."); diff --git a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java index 9c7f45de24..92a0dab04d 100644 --- a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java @@ -67,8 +67,6 @@ import java.io.OutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collections; @@ -668,23 +666,6 @@ public class DexUseManagerLocal { @NonNull String loadingPackageName, boolean isolatedProcess, @NonNull String classLoaderContext, @NonNull String abiName, long lastUsedAtMs) { DexLoader loader = DexLoader.create(loadingPackageName, isolatedProcess); - // This is to avoid a loading package from using up the SecondaryDexUse entries for another - // package (up to the MAX_SECONDARY_DEX_FILES_PER_OWNER limit). - // Note that we are using system_server's permission to check the existence. This is fine - // with the assumption that the file must be world readable to be used by other apps. - // We could use artd's permission to check the existence, and then there wouldn't be any - // permission issue, but that requires bringing up the artd service, which may be too - // expensive. - // TODO(jiakaiz): Check if the assumption is true. - // This doesn't apply to secondary dex files that aren't used by other apps, but we - // don't care about the loading package messing up its own SecondaryDexUse - // entries. - // Also note that the check doesn't follow symlinks because GMSCore creates symlinks to - // its secondary dex files, while system_server doesn't have the permission to follow them. - if (isLoaderOtherApp(loader, owningPackageName) && !mInjector.pathExists(dexPath)) { - AsLog.w("Not recording non-existent secondary dex file '" + dexPath + "'"); - return; - } synchronized (mLock) { PackageDexUse packageDexUse = mDexUse.mPackageDexUseByOwningPackageName.computeIfAbsent( owningPackageName, k -> new PackageDexUse()); @@ -1402,10 +1383,6 @@ public class DexUseManagerLocal { return System.currentTimeMillis(); } - public boolean pathExists(String path) { - return Files.exists(Paths.get(path), LinkOption.NOFOLLOW_LINKS); - } - @NonNull public String getFilename() { return FILENAME; diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java index cc1b66b9ba..1c6abe6c41 100644 --- a/libartservice/service/java/com/android/server/art/Dexopter.java +++ b/libartservice/service/java/com/android/server/art/Dexopter.java @@ -113,6 +113,8 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { continue; } + onDexoptStart(dexInfo); + String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo); DexMetadataInfo dmInfo = mInjector.getDexMetadataHelper().getDexMetadataInfo(buildDmPath(dexInfo)); @@ -199,14 +201,15 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { long sizeBeforeBytes = 0; Dex2OatResult dex2OatResult = Dex2OatResult.notRun(); @DexoptResult.DexoptResultExtendedStatusFlags int extendedStatusFlags = 0; + DexoptTarget<DexInfoType> target = null; try { - var target = DexoptTarget.<DexInfoType>builder() - .setDexInfo(dexInfo) - .setIsa(abi.isa()) - .setIsInDalvikCache(isInDalvikCache) - .setCompilerFilter(compilerFilter) - .setDmPath(dmInfo.dmPath()) - .build(); + target = DexoptTarget.<DexInfoType>builder() + .setDexInfo(dexInfo) + .setIsa(abi.isa()) + .setIsInDalvikCache(isInDalvikCache) + .setCompilerFilter(compilerFilter) + .setDmPath(dmInfo.dmPath()) + .build(); var options = GetDexoptNeededOptions.builder() .setProfileMerged(profileMerged) .setFlags(mParams.getFlags()) @@ -316,6 +319,9 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { AsLog.i(String.format("Dexopt result: [packageName = %s] %s", mPkgState.getPackageName(), result)); results.add(result); + + onDexoptTargetResult(target, status); + if (status != DexoptResult.DEXOPT_SKIPPED && status != DexoptResult.DEXOPT_PERFORMED) { succeeded = false; @@ -626,7 +632,10 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { return VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput( dexPath, isa, false /* isInDalvikCache */)); case ArtifactsLocation.DM: - // The DM file is passed to dex2oat as a separate flag whenever it exists. + case ArtifactsLocation.SDM_DALVIK_CACHE: + case ArtifactsLocation.SDM_NEXT_TO_DEX: + // In these cases, the VDEX file is in the DM file. The whole DM file is passed to + // dex2oat as a separate flag whenever it exists. return null; default: // This should never happen as the value is got from artd. @@ -734,6 +743,18 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { */ @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo); + /** + * Called at an early stage during dexopt of every dex file, even before dexopt is skipped by + * the noop compiler filter. + */ + protected void onDexoptStart(@NonNull DexInfoType dexInfo) throws RemoteException {} + + /** + * Called once for every dex file and every ABI when dexopt has a result. + */ + protected void onDexoptTargetResult(@NonNull DexoptTarget<DexInfoType> target, + @DexoptResult.DexoptResultStatus int status) throws RemoteException {} + @AutoValue abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> { abstract @NonNull DexInfoType dexInfo(); diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexopter.java b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java index eb200592aa..94ab94e213 100644 --- a/libartservice/service/java/com/android/server/art/PrimaryDexopter.java +++ b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java @@ -38,6 +38,7 @@ import com.android.modules.utils.pm.PackageStateModulesUtils; import com.android.server.art.model.ArtFlags; import com.android.server.art.model.Config; import com.android.server.art.model.DexoptParams; +import com.android.server.art.model.DexoptResult; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; @@ -178,6 +179,44 @@ public class PrimaryDexopter extends Dexopter<DetailedPrimaryDexInfo> { return AidlUtils.buildDexMetadataPath(dexInfo.dexPath()); } + @Override + protected void onDexoptStart(@NonNull DetailedPrimaryDexInfo dexInfo) throws RemoteException { + if (!mInjector.isPreReboot() && android.content.pm.Flags.cloudCompilationPm()) { + boolean isInDalvikCache = isInDalvikCache(); + for (Abi abi : getAllAbis(dexInfo)) { + maybeCreateSdc(dexInfo, abi.isa(), isInDalvikCache); + } + } + } + + private void maybeCreateSdc(@NonNull DetailedPrimaryDexInfo dexInfo, @NonNull String isa, + boolean isInDalvikCache) throws RemoteException { + // SDC file doesn't contain sensitive data, so it can always to public. + PermissionSettings permissionSettings = + getPermissionSettings(dexInfo, true /* canBePublic */); + OutputSecureDexMetadataCompanion outputSdc = + AidlUtils.buildOutputSecureDexMetadataCompanion( + dexInfo.dexPath(), isa, isInDalvikCache, permissionSettings); + + try { + mInjector.getArtd().maybeCreateSdc(outputSdc); + } catch (ServiceSpecificException e) { + AsLog.e("Failed to create sdc for " + AidlUtils.toString(outputSdc.sdcPath), e); + } + } + + @Override + protected void onDexoptTargetResult(@NonNull DexoptTarget<DetailedPrimaryDexInfo> target, + @DexoptResult.DexoptResultStatus int status) throws RemoteException { + // An optimization to release disk space as soon as possible. The SDM and SDC files would be + // deleted by the file GC anyway if not deleted here. + if (status == DexoptResult.DEXOPT_PERFORMED && !mInjector.isPreReboot()) { + mInjector.getArtd().deleteSdmSdcFiles( + AidlUtils.buildSecureDexMetadataWithCompanionPaths( + target.dexInfo().dexPath(), target.isa(), target.isInDalvikCache())); + } + } + private boolean isSharedLibrary() { return PackageStateModulesUtils.isLoadableInOtherProcesses(mPkgState, true /* codeOnly */); } diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java index a791211705..754208a05f 100644 --- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java +++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java @@ -333,6 +333,15 @@ public class ArtManagerLocalTest { verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput( "/data/user/0/foo/not_found.apk", "arm64", false /* isInDalvikCache */))); + verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/base.apk", "arm64", mIsInDalvikCache))); + verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/base.apk", "arm", mIsInDalvikCache))); + verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/split_0.apk", "arm64", mIsInDalvikCache))); + verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache))); + verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath( PKG_NAME_1, "/somewhere/app/foo/base.apk", "arm64"))); verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath( @@ -344,6 +353,7 @@ public class ArtManagerLocalTest { // Verify that there are no more calls than the ones above. verify(mArtd, times(6)).deleteArtifacts(any()); + verify(mArtd, times(4)).deleteSdmSdcFiles(any()); verify(mArtd, times(4)).deleteRuntimeArtifacts(any()); } @@ -572,6 +582,15 @@ public class ArtManagerLocalTest { verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPathAsInput( "/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache))); + verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/base.apk", "arm64", mIsInDalvikCache))); + verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/base.apk", "arm", mIsInDalvikCache))); + verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/split_0.apk", "arm64", mIsInDalvikCache))); + verify(mArtd).deleteSdmSdcFiles(deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache))); + verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath( PKG_NAME_1, "/somewhere/app/foo/base.apk", "arm64"))); verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath( @@ -1268,6 +1287,7 @@ public class ArtManagerLocalTest { "arm64", true /* isInDalvikCache */)), inAnyOrderDeepEquals(VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput( "/somewhere/app/foo/split_0.apk", "arm", false /* isInDalvikCache */))), + inAnyOrderDeepEquals() /* sdmSdcFilesToKeep */, inAnyOrderDeepEquals(AidlUtils.buildRuntimeArtifactsPath( PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm64"), AidlUtils.buildRuntimeArtifactsPath( @@ -1276,6 +1296,60 @@ public class ArtManagerLocalTest { } @Test + public void testCleanupDmAndSdm() throws Exception { + when(mPreRebootDexoptJob.hasStarted()).thenReturn(false); + + // It should keep the SDM file, but not runtime images. + doReturn(createGetDexoptStatusResult( + "speed-profile", "cloud", "location", ArtifactsLocation.SDM_NEXT_TO_DEX)) + .when(mArtd) + .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm64"), any()); + + // It should keep the SDM file, but not runtime images. + doReturn(createGetDexoptStatusResult( + "speed-profile", "cloud", "location", ArtifactsLocation.SDM_DALVIK_CACHE)) + .when(mArtd) + .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm"), any()); + + // It should keep the SDM file and runtime images. + doReturn(createGetDexoptStatusResult( + "verify", "cloud", "location", ArtifactsLocation.SDM_NEXT_TO_DEX)) + .when(mArtd) + .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm64"), any()); + + // It should only keep runtime images. + doReturn(createGetDexoptStatusResult("verify", "vdex", "location", ArtifactsLocation.DM)) + .when(mArtd) + .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm"), any()); + + // This file is uninteresting in this test. + doReturn(createGetDexoptStatusResult( + "run-from-apk", "unknown", "unknown", ArtifactsLocation.NONE_OR_ERROR)) + .when(mArtd) + .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any()); + + when(mSnapshot.getPackageStates()).thenReturn(Map.of(PKG_NAME_1, mPkgState1)); + mArtManagerLocal.cleanup(mSnapshot); + + verify(mArtd).cleanup(any() /* profilesToKeep */, + inAnyOrderDeepEquals() /* artifactsToKeep */, + inAnyOrderDeepEquals() /* vdexFilesToKeep */, + inAnyOrderDeepEquals(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/base.apk", "arm64", + false /* isInDalvikCache */), + AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/base.apk", "arm", true /* isInDalvikCache */), + AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/split_0.apk", "arm64", + false /* isInDalvikCache */)), + inAnyOrderDeepEquals(AidlUtils.buildRuntimeArtifactsPath( + PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm64"), + AidlUtils.buildRuntimeArtifactsPath( + PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm")), + eq(false) /* keepPreRebootStagedFiles */); + } + + @Test public void testGetArtManagedFileStatsSystem() throws Exception { testGetArtManagedFileStats(true /* isSystemOrRootOrShell */); } @@ -1426,6 +1500,64 @@ public class ArtManagerLocalTest { } @Test + public void testGetArtManagedFileStatsDmAndSdm() throws Exception { + // It should count the SDM file, but not runtime images. + doReturn(createGetDexoptStatusResult( + "speed-profile", "cloud", "location", ArtifactsLocation.SDM_NEXT_TO_DEX)) + .when(mArtd) + .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm64"), any()); + + // It should count the SDM file, but not runtime images. + doReturn(createGetDexoptStatusResult( + "speed-profile", "cloud", "location", ArtifactsLocation.SDM_DALVIK_CACHE)) + .when(mArtd) + .getDexoptStatus(eq("/somewhere/app/foo/base.apk"), eq("arm"), any()); + + // It should count the SDM file and runtime images. + doReturn(createGetDexoptStatusResult( + "verify", "cloud", "location", ArtifactsLocation.SDM_NEXT_TO_DEX)) + .when(mArtd) + .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm64"), any()); + + // It should only count runtime images. + doReturn(createGetDexoptStatusResult("verify", "vdex", "location", ArtifactsLocation.DM)) + .when(mArtd) + .getDexoptStatus(eq("/somewhere/app/foo/split_0.apk"), eq("arm"), any()); + + // This file is uninteresting in this test. + doReturn(createGetDexoptStatusResult( + "run-from-apk", "unknown", "unknown", ArtifactsLocation.NONE_OR_ERROR)) + .when(mArtd) + .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any()); + + // These are counted as TYPE_DEXOPT_ARTIFACT. + doReturn(1l << 0).when(mArtd).getSdmFileSize( + deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/base.apk", "arm64", false /* isInDalvikCache */))); + doReturn(1l << 1).when(mArtd).getSdmFileSize( + deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/base.apk", "arm", true /* isInDalvikCache */))); + doReturn(1l << 2).when(mArtd).getSdmFileSize( + deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/split_0.apk", "arm64", false /* isInDalvikCache */))); + doReturn(1l << 3).when(mArtd).getRuntimeArtifactsSize( + deepEq(AidlUtils.buildRuntimeArtifactsPath( + PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm64"))); + doReturn(1l << 4).when(mArtd).getRuntimeArtifactsSize( + deepEq(AidlUtils.buildRuntimeArtifactsPath( + PKG_NAME_1, "/somewhere/app/foo/split_0.apk", "arm"))); + + ArtManagedFileStats stats = mArtManagerLocal.getArtManagedFileStats(mSnapshot, PKG_NAME_1); + assertThat(stats.getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT)) + .isEqualTo((1l << 0) + (1l << 1) + (1l << 2) + (1l << 3) + (1l << 4)); + + verify(mArtd, never()).getArtifactsSize(any()); + verify(mArtd, never()).getVdexFileSize(any()); + verify(mArtd, times(3)).getSdmFileSize(any()); + verify(mArtd, times(2)).getRuntimeArtifactsSize(any()); + } + + @Test public void testCommitPreRebootStagedFiles() throws Exception { when(mSnapshot.getPackageStates()).thenReturn(Map.of(PKG_NAME_1, mPkgState1)); diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java index f0002b80d7..1627c5eeef 100644 --- a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java +++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java @@ -177,7 +177,6 @@ public class DexUseManagerTest { lenient().when(mInjector.getArtd()).thenReturn(mArtd); lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(0l); - lenient().when(mInjector.pathExists(any())).thenReturn(true); lenient().when(mInjector.getFilename()).thenReturn(mTempFile.getPath()); lenient() .when(mInjector.createScheduledExecutor()) @@ -909,12 +908,11 @@ public class DexUseManagerTest { } @Test - public void testExistingExternalSecondaryDexPath() throws Exception { + public void testSecondaryDexPath() throws Exception { mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save. long oldFileSize = mTempFile.length(); String existingDexPath = mCeDir + "/foo.apk"; - when(mInjector.pathExists(existingDexPath)).thenReturn(true); mDexUseManager.notifyDexContainersLoaded( mSnapshot, LOADING_PKG_NAME, Map.of(existingDexPath, "PCL[]")); @@ -923,35 +921,6 @@ public class DexUseManagerTest { } @Test - public void testNonexistingExternalSecondaryDexPath() throws Exception { - mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save. - long oldFileSize = mTempFile.length(); - - String nonexistingDexPath = mCeDir + "/foo.apk"; - when(mInjector.pathExists(nonexistingDexPath)).thenReturn(false); - mDexUseManager.notifyDexContainersLoaded( - mSnapshot, LOADING_PKG_NAME, Map.of(nonexistingDexPath, "PCL[]")); - - mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save. - assertThat(mTempFile.length()).isEqualTo(oldFileSize); - } - - @Test - public void testInternalSecondaryDexPath() throws Exception { - mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save. - long oldFileSize = mTempFile.length(); - - String nonexistingDexPath = mCeDir + "/foo.apk"; - lenient().when(mInjector.pathExists(nonexistingDexPath)).thenReturn(false); - mDexUseManager.notifyDexContainersLoaded( - mSnapshot, OWNING_PKG_NAME, Map.of(nonexistingDexPath, "PCL[]")); - verify(mArtd, never()).getDexFileVisibility(nonexistingDexPath); - - mMockClock.advanceTime(DexUseManagerLocal.INTERVAL_MS); // Save. - assertThat(mTempFile.length()).isGreaterThan(oldFileSize); - } - - @Test public void testLimitSecondaryDexFiles() throws Exception { for (int n = 0; n < MAX_SECONDARY_DEX_FILES_PER_OWNER_FOR_TESTING - 1; ++n) { mDexUseManager.notifyDexContainersLoaded(mSnapshot, LOADING_PKG_NAME, diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java index 10f9f59e91..9c3f87e92e 100644 --- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java @@ -202,6 +202,7 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase { params.mIsPreReboot = true; params.mExpectedOutputIsPreReboot = true; params.mExpectedDeletesRuntimeArtifacts = false; + params.mExpectedDeletesSdmSdcFiles = false; list.add(params); params = new Params(); @@ -385,6 +386,17 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase { PKG_NAME, "/somewhere/app/foo/split_0.apk", "arm"))); } + if (mParams.mExpectedDeletesSdmSdcFiles) { + // Only delete SDM and SDC files for successful dexopt operations, namely the first one + // and the fourth one. + doReturn(1l).when(mArtd).deleteSdmSdcFiles( + deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/base.apk", "arm64", mParams.mIsInDalvikCache))); + doReturn(1l).when(mArtd).deleteSdmSdcFiles( + deepEq(AidlUtils.buildSecureDexMetadataWithCompanionPaths( + "/somewhere/app/foo/split_0.apk", "arm", mParams.mIsInDalvikCache))); + } + assertThat(mPrimaryDexopter.dexopt()) .comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality()) .containsExactly( @@ -416,6 +428,10 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase { if (!mParams.mExpectedDeletesRuntimeArtifacts) { verify(mArtd, times(0)).deleteRuntimeArtifacts(any()); } + + if (!mParams.mExpectedDeletesSdmSdcFiles) { + verify(mArtd, times(0)).deleteSdmSdcFiles(any()); + } } private static class Params { @@ -451,6 +467,7 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase { public boolean mExpectedIsHiddenApiPolicyEnabled = true; public boolean mExpectedOutputIsPreReboot = false; public boolean mExpectedDeletesRuntimeArtifacts = true; + public boolean mExpectedDeletesSdmSdcFiles = true; public String toString() { return String.format("isInDalvikCache=%b," @@ -477,7 +494,8 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase { + "expectedIsDebuggable=%b," + "expectedIsHiddenApiPolicyEnabled=%b," + "expectedOutputIsPreReboot=%b," - + "expectedDeleteRuntimeArtifacts=%b", + + "expectedDeletesRuntimeArtifacts=%b," + + "expectedDeletesSdmSdcFiles=%b", mIsInDalvikCache, mHiddenApiEnforcementPolicy, mIsVmSafeMode, mIsDebuggable, mIsSystemUi, mIsLauncher, mIsUseEmbeddedDex, mIsSanboxSdkLib, mRequestedCompilerFilter, mCallbackReturnedCompilerFilter, mForce, @@ -485,7 +503,7 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase { mForceCompilerFilter, mAlwaysDebuggable, mExpectedCallbackInputCompilerFilter, mExpectedCompilerFilter, mExpectedDexoptTrigger, mExpectedIsDebuggable, mExpectedIsHiddenApiPolicyEnabled, mExpectedOutputIsPreReboot, - mExpectedDeletesRuntimeArtifacts); + mExpectedDeletesRuntimeArtifacts, mExpectedDeletesSdmSdcFiles); } } } diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java index 02ae2fe218..b032538bbb 100644 --- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java @@ -16,6 +16,7 @@ package com.android.server.art; +import static com.android.server.art.OutputArtifacts.PermissionSettings; import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; import static com.android.server.art.testing.TestingUtils.deepEq; @@ -62,6 +63,7 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.zip.ZipFile; @SmallTest @@ -156,50 +158,52 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase { mUsedEmbeddedProfiles = new ArrayList<>(); } - @Test - public void testDexoptInputVdex() throws Exception { - // null. - doReturn(dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR)) + private void checkDexoptInputVdex( + @ArtifactsLocation int location, Supplier<VdexPath> inputVdexMatcher) throws Exception { + doReturn(dexoptIsNeeded(location)) .when(mArtd) .getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), any(), anyInt()); - doReturn(mArtdDexoptResult) - .when(mArtd) - .dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), any(), - anyInt(), any(), any()); - // ArtifactsPath, isInDalvikCache=true. - doReturn(dexoptIsNeeded(ArtifactsLocation.DALVIK_CACHE)) - .when(mArtd) - .getDexoptNeeded(eq(mDexPath), eq("arm"), any(), any(), anyInt()); - doReturn(mArtdDexoptResult) - .when(mArtd) - .dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(), - deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput( - mDexPath, "arm", true /* isInDalvikCache */))), - any(), anyInt(), any(), any()); + List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt(); + verifyStatusAllOk(results); + verify(mArtd).dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), + inputVdexMatcher.get(), any(), anyInt(), any(), any()); + } - // ArtifactsPath, isInDalvikCache=false. - doReturn(dexoptIsNeeded(ArtifactsLocation.NEXT_TO_DEX)) - .when(mArtd) - .getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), any(), anyInt()); - doReturn(mArtdDexoptResult) - .when(mArtd) - .dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(), any(), - deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput( - mSplit0DexPath, "arm64", false /* isInDalvikCache */))), - any(), anyInt(), any(), any()); + @Test + public void testDexoptInputVdexNoneOrError() throws Exception { + checkDexoptInputVdex(ArtifactsLocation.NONE_OR_ERROR, () -> isNull()); + } - // DexMetadataPath. - doReturn(dexoptIsNeeded(ArtifactsLocation.DM)) - .when(mArtd) - .getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), any(), anyInt()); - doReturn(mArtdDexoptResult) - .when(mArtd) - .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(), isNull(), any(), - anyInt(), any(), any()); + @Test + public void testDexoptInputVdexDalvikCache() throws Exception { + checkDexoptInputVdex(ArtifactsLocation.DALVIK_CACHE, () -> { + return deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput( + mDexPath, "arm64", true /* isInDalvikCache */))); + }); + } - List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt(); - verifyStatusAllOk(results); + @Test + public void testDexoptInputVdexNextToDex() throws Exception { + checkDexoptInputVdex(ArtifactsLocation.NEXT_TO_DEX, () -> { + return deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput( + mDexPath, "arm64", false /* isInDalvikCache */))); + }); + } + + @Test + public void testDexoptInputVdexDm() throws Exception { + checkDexoptInputVdex(ArtifactsLocation.DM, () -> isNull()); + } + + @Test + public void testDexoptInputVdexSdmDalvikCache() throws Exception { + checkDexoptInputVdex(ArtifactsLocation.SDM_DALVIK_CACHE, () -> isNull()); + } + + @Test + public void testDexoptInputVdexSdmNextToDex() throws Exception { + checkDexoptInputVdex(ArtifactsLocation.SDM_NEXT_TO_DEX, () -> isNull()); } @Test @@ -945,6 +949,52 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase { } } + @Test + public void testMaybeCreateSdc() throws Exception { + mDexoptParams = new DexoptParams.Builder("install") + .setCompilerFilter("speed-profile") + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX) + .build(); + mPrimaryDexopter = + new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal); + + mPrimaryDexopter.dexopt(); + + FsPermission dirFsPermission = AidlUtils.buildFsPermission(Process.SYSTEM_UID /* uid */, + Process.SYSTEM_UID /* gid */, false /* isOtherReadable */, + true /* isOtherExecutable */); + FsPermission fileFsPermission = AidlUtils.buildFsPermission( + Process.SYSTEM_UID /* uid */, SHARED_GID /* gid */, true /* isOtherReadable */); + PermissionSettings permissionSettings = AidlUtils.buildPermissionSettings( + dirFsPermission, fileFsPermission, null /* seContext */); + + verify(mArtd).maybeCreateSdc(deepEq(AidlUtils.buildOutputSecureDexMetadataCompanion( + mDexPath, "arm64", false /* isInDalvikCache */, permissionSettings))); + verify(mArtd).maybeCreateSdc(deepEq(AidlUtils.buildOutputSecureDexMetadataCompanion( + mDexPath, "arm", false /* isInDalvikCache */, permissionSettings))); + verify(mArtd).maybeCreateSdc(deepEq(AidlUtils.buildOutputSecureDexMetadataCompanion( + mSplit0DexPath, "arm64", false /* isInDalvikCache */, permissionSettings))); + verify(mArtd).maybeCreateSdc(deepEq(AidlUtils.buildOutputSecureDexMetadataCompanion( + mSplit0DexPath, "arm", false /* isInDalvikCache */, permissionSettings))); + } + + @Test + public void testMaybeCreateSdcCompilerFilterSkip() throws Exception { + mDexoptParams = new DexoptParams.Builder("install") + .setCompilerFilter(DexoptParams.COMPILER_FILTER_NOOP) + .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX) + .build(); + mPrimaryDexopter = + new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal); + + mPrimaryDexopter.dexopt(); + + verify(mArtd, times(4)).maybeCreateSdc(any()); + verify(mArtd, never()) + .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(), + any()); + } + private void checkDexoptWithProfile(IArtd artd, String dexPath, String isa, ProfilePath profile, boolean isOtherReadable) throws Exception { artd.dexopt(argThat(artifacts diff --git a/libartservice/service/native/service_test.cc b/libartservice/service/native/service_test.cc index d1d429edd7..b0864b7769 100644 --- a/libartservice/service/native/service_test.cc +++ b/libartservice/service/native/service_test.cc @@ -30,7 +30,7 @@ using ::android::base::testing::WithMessage; using std::literals::operator""s; // NOLINT -class ArtServiceTest : public testing::Test {}; +class ArtServiceTest : public ::testing::Test {}; TEST_F(ArtServiceTest, ValidatePathElementOk) { EXPECT_THAT(ValidatePathElement("com.android.foo", "packageName"), Ok()); @@ -110,7 +110,7 @@ TEST_F(ArtServiceTest, ValidateDexPathNul) { class ArtServiceGcTest : public CommonRuntimeTest {}; TEST_F(ArtServiceGcTest, GetGarbageCollector) { - EXPECT_THAT(GetGarbageCollector(), testing::HasSubstr("CollectorType")); + EXPECT_THAT(GetGarbageCollector(), ::testing::HasSubstr("CollectorType")); } } // namespace diff --git a/libarttools/art_exec_test.cc b/libarttools/art_exec_test.cc index d6e0ba4802..c729b5dc0f 100644 --- a/libarttools/art_exec_test.cc +++ b/libarttools/art_exec_test.cc @@ -69,10 +69,10 @@ bool GetCap(pid_t pid, cap_flag_t flag, cap_value_t value) { return flag_value == CAP_SET; } -class ArtExecTest : public testing::Test { +class ArtExecTest : public ::testing::Test { protected: void SetUp() override { - testing::Test::SetUp(); + ::testing::Test::SetUp(); if (!kIsTargetAndroid) { GTEST_SKIP() << "art_exec is for device only"; } diff --git a/libarttools/cmdline_builder_test.cc b/libarttools/cmdline_builder_test.cc index 1acf2e3f0b..04b746fa8b 100644 --- a/libarttools/cmdline_builder_test.cc +++ b/libarttools/cmdline_builder_test.cc @@ -28,7 +28,7 @@ namespace { using ::testing::ElementsAre; using ::testing::IsEmpty; -class CmdlineBuilderTest : public testing::Test { +class CmdlineBuilderTest : public ::testing::Test { protected: CmdlineBuilder args_; }; diff --git a/libarttools/system_properties_test.cc b/libarttools/system_properties_test.cc index acffd9727c..6d5db58573 100644 --- a/libarttools/system_properties_test.cc +++ b/libarttools/system_properties_test.cc @@ -30,7 +30,7 @@ class MockSystemProperties : public SystemProperties { MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override)); }; -class SystemPropertiesTest : public testing::Test { +class SystemPropertiesTest : public ::testing::Test { protected: MockSystemProperties system_properties_; }; diff --git a/libdexfile/dex/code_item_accessors_test.cc b/libdexfile/dex/code_item_accessors_test.cc index 9f20fc2495..bd1ddc958d 100644 --- a/libdexfile/dex/code_item_accessors_test.cc +++ b/libdexfile/dex/code_item_accessors_test.cc @@ -26,7 +26,7 @@ namespace art { -class CodeItemAccessorsTest : public testing::Test {}; +class CodeItemAccessorsTest : public ::testing::Test {}; std::unique_ptr<const DexFile> CreateFakeDex(bool compact_dex, std::vector<uint8_t>* data) { data->resize(MemMap::GetPageSize()); diff --git a/libdexfile/dex/descriptors_names_test.cc b/libdexfile/dex/descriptors_names_test.cc index f3ec3ed0f3..1b0654a770 100644 --- a/libdexfile/dex/descriptors_names_test.cc +++ b/libdexfile/dex/descriptors_names_test.cc @@ -20,7 +20,7 @@ namespace art { -class DescriptorsNamesTest : public testing::Test {}; +class DescriptorsNamesTest : public ::testing::Test {}; TEST_F(DescriptorsNamesTest, PrettyDescriptor_ArrayReferences) { EXPECT_EQ("java.lang.Class[]", PrettyDescriptor("[Ljava/lang/Class;")); diff --git a/libdexfile/dex/dex_file_loader_test.cc b/libdexfile/dex/dex_file_loader_test.cc index 3de1103256..6041ea2077 100644 --- a/libdexfile/dex/dex_file_loader_test.cc +++ b/libdexfile/dex/dex_file_loader_test.cc @@ -27,7 +27,7 @@ namespace art { -class DexFileLoaderTest : public testing::Test {}; +class DexFileLoaderTest : public ::testing::Test {}; static constexpr char kLocationString[] = "/a/dex/file/location"; diff --git a/libdexfile/dex/dex_file_verifier_test.cc b/libdexfile/dex/dex_file_verifier_test.cc index d67d9a938d..8320b94c9e 100644 --- a/libdexfile/dex/dex_file_verifier_test.cc +++ b/libdexfile/dex/dex_file_verifier_test.cc @@ -56,7 +56,7 @@ static void FixUpChecksum(uint8_t* dex_file) { header->checksum_ = adler_checksum; } -class DexFileVerifierTest : public testing::Test { +class DexFileVerifierTest : public ::testing::Test { protected: DexFile* GetDexFile(const uint8_t* dex_bytes, size_t length) { auto container = std::make_shared<MemoryDexFileContainer>(dex_bytes, length); diff --git a/libdexfile/dex/type_lookup_table_test.cc b/libdexfile/dex/type_lookup_table_test.cc index 4316be0bd6..27da71cf25 100644 --- a/libdexfile/dex/type_lookup_table_test.cc +++ b/libdexfile/dex/type_lookup_table_test.cc @@ -49,14 +49,14 @@ TEST_P(TypeLookupTableTest, Find) { INSTANTIATE_TEST_CASE_P(FindNonExistingClassWithoutCollisions, TypeLookupTableTest, - testing::Values(DescriptorClassDefIdxPair("LAB;", 1U))); + ::testing::Values(DescriptorClassDefIdxPair("LAB;", 1U))); INSTANTIATE_TEST_CASE_P(FindNonExistingClassWithCollisions, TypeLookupTableTest, - testing::Values(DescriptorClassDefIdxPair("LDA;", dex::kDexNoIndex))); + ::testing::Values(DescriptorClassDefIdxPair("LDA;", dex::kDexNoIndex))); INSTANTIATE_TEST_CASE_P(FindClassNoCollisions, TypeLookupTableTest, - testing::Values(DescriptorClassDefIdxPair("LC;", 2U))); + ::testing::Values(DescriptorClassDefIdxPair("LC;", 2U))); INSTANTIATE_TEST_CASE_P(FindClassWithCollisions, TypeLookupTableTest, - testing::Values(DescriptorClassDefIdxPair("LAB;", 1U))); + ::testing::Values(DescriptorClassDefIdxPair("LAB;", 1U))); } // namespace art diff --git a/libdexfile/dex/utf_test.cc b/libdexfile/dex/utf_test.cc index 85c74d285c..e8c3313286 100644 --- a/libdexfile/dex/utf_test.cc +++ b/libdexfile/dex/utf_test.cc @@ -26,7 +26,7 @@ namespace art { -class UtfTest : public testing::Test {}; +class UtfTest : public ::testing::Test {}; TEST_F(UtfTest, GetLeadingUtf16Char) { EXPECT_EQ(0xffff, GetLeadingUtf16Char(0xeeeeffff)); diff --git a/libnativebridge/tests/NativeBridgeTest.h b/libnativebridge/tests/NativeBridgeTest.h index 88ea0e33c8..1b9415db58 100644 --- a/libnativebridge/tests/NativeBridgeTest.h +++ b/libnativebridge/tests/NativeBridgeTest.h @@ -34,7 +34,7 @@ constexpr const char* kNativeBridgeLibrary8 = "libnativebridge8-test-case.so"; namespace android { -class NativeBridgeTest : public testing::Test { +class NativeBridgeTest : public ::testing::Test { protected: NativeBridgeTest() : temp_dir_() { app_data_dir_ = std::string(temp_dir_.path); diff --git a/libnativeloader/native_loader_api_test.cpp b/libnativeloader/native_loader_api_test.cpp index aeda1cc2a7..6ef7d36134 100644 --- a/libnativeloader/native_loader_api_test.cpp +++ b/libnativeloader/native_loader_api_test.cpp @@ -38,7 +38,7 @@ using ::testing::StrEq; class NativeLoaderLazyTest : public ::testing::Test { protected: void SetUp() override { - jni_mock = std::make_unique<testing::NiceMock<MockJni>>(); + jni_mock = std::make_unique<::testing::NiceMock<MockJni>>(); env = std::make_unique<JNIEnv>(); env->functions = CreateJNINativeInterface(); } diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp index a07551032c..90810647c3 100644 --- a/libnativeloader/native_loader_test.cpp +++ b/libnativeloader/native_loader_test.cpp @@ -131,7 +131,7 @@ class MockPlatform : public Platform { ON_CALL(*this, NativeBridgeIsSupported(_)).WillByDefault(Return(is_bridged_)); ON_CALL(*this, NativeBridgeIsPathSupported(_)).WillByDefault(Return(is_bridged_)); ON_CALL(*this, mock_get_exported_namespace(_, _)) - .WillByDefault(testing::Invoke([](bool, const char* name) -> mock_namespace_handle { + .WillByDefault(::testing::Invoke([](bool, const char* name) -> mock_namespace_handle { if (namespaces.find(name) != namespaces.end()) { return namespaces[name]; } @@ -274,8 +274,8 @@ class NativeLoaderTest : public ::testing::TestWithParam<bool> { bool IsBridged() { return GetParam(); } void SetUp() override { - mock = std::make_unique<testing::NiceMock<MockPlatform>>(IsBridged()); - jni_mock = std::make_unique<testing::NiceMock<MockJni>>(); + mock = std::make_unique<::testing::NiceMock<MockPlatform>>(IsBridged()); + jni_mock = std::make_unique<::testing::NiceMock<MockJni>>(); env = std::make_unique<JNIEnv>(); env->functions = CreateJNINativeInterface(); @@ -379,7 +379,7 @@ TEST_P(NativeLoaderTest, OpenNativeLibraryWithoutClassloaderAndCallerLocation) { EXPECT_EQ(errmsg, nullptr); } -INSTANTIATE_TEST_SUITE_P(NativeLoaderTests, NativeLoaderTest, testing::Bool()); +INSTANTIATE_TEST_SUITE_P(NativeLoaderTests, NativeLoaderTest, ::testing::Bool()); ///////////////////////////////////////////////////////////////// @@ -439,8 +439,8 @@ class NativeLoaderTest_Create : public NativeLoaderTest { ON_CALL(*jni_mock, JniObject_getParent(StrEq(class_loader))).WillByDefault(Return(nullptr)); - EXPECT_CALL(*mock, NativeBridgeIsPathSupported(_)).Times(testing::AnyNumber()); - EXPECT_CALL(*mock, NativeBridgeInitialized()).Times(testing::AnyNumber()); + EXPECT_CALL(*mock, NativeBridgeIsPathSupported(_)).Times(::testing::AnyNumber()); + EXPECT_CALL(*mock, NativeBridgeInitialized()).Times(::testing::AnyNumber()); EXPECT_CALL(*mock, mock_create_namespace( Eq(IsBridged()), StartsWith(expected_namespace_prefix + "-"), nullptr, @@ -684,7 +684,7 @@ TEST_P(NativeLoaderTest_Create, TwoApks) { } } -INSTANTIATE_TEST_SUITE_P(NativeLoaderTests_Create, NativeLoaderTest_Create, testing::Bool()); +INSTANTIATE_TEST_SUITE_P(NativeLoaderTests_Create, NativeLoaderTest_Create, ::testing::Bool()); const std::function<Result<bool>(const struct ConfigEntry&)> always_true = [](const struct ConfigEntry&) -> Result<bool> { return true; }; diff --git a/oatdump/oatdump_test.cc b/oatdump/oatdump_test.cc index fa46202327..bc25896c46 100644 --- a/oatdump/oatdump_test.cc +++ b/oatdump/oatdump_test.cc @@ -22,7 +22,7 @@ namespace art { INSTANTIATE_TEST_SUITE_P(DynamicOrStatic, OatDumpTest, - testing::Values(Flavor::kDynamic, Flavor::kStatic)); + ::testing::Values(Flavor::kDynamic, Flavor::kStatic)); // Disable tests on arm and arm64 as they are taking too long to run. b/27824283. #define TEST_DISABLED_FOR_ARM_AND_ARM64() \ diff --git a/oatdump/oatdump_test.h b/oatdump/oatdump_test.h index 9af33578db..eb713357d9 100644 --- a/oatdump/oatdump_test.h +++ b/oatdump/oatdump_test.h @@ -41,7 +41,7 @@ enum class Flavor { kStatic, // oatdump(d)s, dex2oat(d)s }; -class OatDumpTest : public CommonRuntimeTest, public testing::WithParamInterface<Flavor> { +class OatDumpTest : public CommonRuntimeTest, public ::testing::WithParamInterface<Flavor> { protected: virtual void SetUp() { CommonRuntimeTest::SetUp(); diff --git a/odrefresh/odr_metrics_record_test.cc b/odrefresh/odr_metrics_record_test.cc index 8c24156aa4..1208e188e2 100644 --- a/odrefresh/odr_metrics_record_test.cc +++ b/odrefresh/odr_metrics_record_test.cc @@ -132,7 +132,7 @@ TEST_F(OdrMetricsRecordTest, HappyPath) { TEST_F(OdrMetricsRecordTest, EmptyInput) { OdrMetricsRecord record{}; - ASSERT_THAT(record.ReadFromFile(file_path_), testing::Not(Ok())); + ASSERT_THAT(record.ReadFromFile(file_path_), ::testing::Not(Ok())); } TEST_F(OdrMetricsRecordTest, UnexpectedInput) { diff --git a/runtime/Android.bp b/runtime/Android.bp index 892f1b0a19..c0a65f27a7 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -397,6 +397,7 @@ cc_defaults { "oat/oat_file_assistant_context.cc", "oat/oat_file_manager.cc", "oat/oat_quick_method_header.cc", + "oat/sdc_file.cc", "oat/stack_map.cc", "object_lock.cc", "offsets.cc", @@ -1129,6 +1130,7 @@ art_cc_defaults { "native_stack_dump_test.cc", "oat/oat_file_assistant_test.cc", "oat/oat_file_test.cc", + "oat/sdc_file_test.cc", "parsed_options_test.cc", "prebuilt_tools_test.cc", "proxy_test.cc", diff --git a/runtime/arch/memcmp16_test.cc b/runtime/arch/memcmp16_test.cc index 37aad21a40..21d3f13ab7 100644 --- a/runtime/arch/memcmp16_test.cc +++ b/runtime/arch/memcmp16_test.cc @@ -30,8 +30,7 @@ class RandGen { uint32_t val_; }; -class MemCmp16Test : public testing::Test { -}; +class MemCmp16Test : public ::testing::Test {}; // A simple implementation to compare against. // Note: this version is equivalent to the generic one used when no optimized version is available. diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 009d705caf..b14eaae51b 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -2240,8 +2240,16 @@ bool ClassLinker::AddImageSpace(gc::space::ImageSpace* space, return false; } + const char* oat_apex_versions = + oat_header->GetStoreValueByKeyUnsafe(OatHeader::kApexVersionsKey); + if (oat_apex_versions == nullptr) { + *error_msg = StringPrintf("Missing apex versions in special root in runtime image '%s'", + space->GetImageLocation().c_str()); + return false; + } + // Validate the apex versions. - if (!gc::space::ImageSpace::ValidateApexVersions(*oat_header, + if (!gc::space::ImageSpace::ValidateApexVersions(oat_apex_versions, runtime->GetApexVersions(), space->GetImageLocation(), error_msg)) { diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h index 7c20de30fd..1866583058 100644 --- a/runtime/common_runtime_test.h +++ b/runtime/common_runtime_test.h @@ -244,10 +244,10 @@ class CommonRuntimeTestBase : public TestType, public CommonRuntimeTestImpl { } }; -using CommonRuntimeTest = CommonRuntimeTestBase<testing::Test>; +using CommonRuntimeTest = CommonRuntimeTestBase<::testing::Test>; template <typename Param> -using CommonRuntimeTestWithParam = CommonRuntimeTestBase<testing::TestWithParam<Param>>; +using CommonRuntimeTestWithParam = CommonRuntimeTestBase<::testing::TestWithParam<Param>>; // Sets a CheckJni abort hook to catch failures. Note that this will cause CheckJNI to carry on // rather than aborting, so be careful! diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h index 7e8378aa67..53598bcfa0 100644 --- a/runtime/dex2oat_environment_test.h +++ b/runtime/dex2oat_environment_test.h @@ -20,13 +20,16 @@ #include <sys/wait.h> #include <fstream> +#include <memory> #include <optional> #include <string> #include <vector> +#include "android-base/file.h" #include "android-base/result.h" #include "android-base/strings.h" #include "base/file_utils.h" +#include "base/globals.h" #include "base/macros.h" #include "base/os.h" #include "base/stl_util.h" @@ -40,6 +43,7 @@ #include "gc/space/image_space.h" #include "gtest/gtest.h" #include "oat/oat_file_assistant.h" +#include "oat/sdc_file.h" #include "runtime.h" #include "ziparchive/zip_writer.h" @@ -256,7 +260,9 @@ class Dex2oatEnvironmentTest : public Dex2oatScratchDirs, public CommonRuntimeTe return WEXITSTATUS(res.status_code); } - void CreateDexMetadata(const std::string& vdex, const std::string& out_dm) { + void CreateDexMetadata(const std::string& vdex, + const std::string& out_dm, + bool page_aligned = false) { // Read the vdex bytes. std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex.c_str())); std::vector<uint8_t> data(vdex_file->GetLength()); @@ -265,13 +271,55 @@ class Dex2oatEnvironmentTest : public Dex2oatScratchDirs, public CommonRuntimeTe // Zip the content. FILE* file = fopen(out_dm.c_str(), "wbe"); ZipWriter writer(file); - writer.StartEntry("primary.vdex", ZipWriter::kAlign32); + writer.StartAlignedEntry( + "primary.vdex", /*flags=*/0, /*alignment=*/page_aligned ? kMaxPageSize : 4); writer.WriteBytes(data.data(), data.size()); writer.FinishEntry(); writer.Finish(); fflush(file); fclose(file); } + + void CreateSecureDexMetadata(const std::string& odex, + const std::string& art, + const std::string& out_sdm) { + // Zip the content. + std::unique_ptr<File> sdm_file(OS::CreateEmptyFileWriteOnly(out_sdm.c_str())); + ASSERT_NE(sdm_file, nullptr); + ZipWriter writer(fdopen(sdm_file->Fd(), "wb")); + + std::string odex_data; + ASSERT_TRUE(android::base::ReadFileToString(odex, &odex_data)); + writer.StartAlignedEntry("primary.odex", /*flags=*/0, /*alignment=*/kMaxPageSize); + writer.WriteBytes(odex_data.data(), odex_data.size()); + writer.FinishEntry(); + + if (!art.empty()) { + std::string art_data; + ASSERT_TRUE(android::base::ReadFileToString(art, &art_data)); + writer.StartAlignedEntry("primary.art", /*flags=*/0, /*alignment=*/kMaxPageSize); + writer.WriteBytes(art_data.data(), art_data.size()); + writer.FinishEntry(); + } + + writer.Finish(); + ASSERT_EQ(sdm_file->FlushClose(), 0); + } + + void CreateSecureDexMetadataCompanion(const std::string& sdm, + const std::string& apex_versions, + const std::string& out_sdc) { + struct stat sdm_st; + ASSERT_EQ(stat(sdm.c_str(), &sdm_st), 0); + + std::unique_ptr<File> sdc_file(OS::CreateEmptyFileWriteOnly(out_sdc.c_str())); + ASSERT_NE(sdc_file, nullptr); + SdcWriter sdc_writer(std::move(*sdc_file)); + sdc_writer.SetSdmTimestampNs(TimeSpecToNs(sdm_st.st_mtim)); + sdc_writer.SetApexVersions(apex_versions); + std::string error_msg; + ASSERT_TRUE(sdc_writer.Save(&error_msg)) << error_msg; + } }; } // namespace art diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc index 40e10142d7..6239f7de87 100644 --- a/runtime/dexopt_test.cc +++ b/runtime/dexopt_test.cc @@ -202,6 +202,38 @@ void DexoptTest::GenerateOatForTest(const char* dex_location, CompilerFilter::Fi GenerateOatForTest(dex_location, filter, /*with_alternate_image=*/false); } +void DexoptTest::GenerateSdmDmForTest(const std::string& dex_location, + const std::string& sdm_location, + const std::string& dm_location, + CompilerFilter::Filter filter, + bool include_app_image, + const char* compilation_reason, + const std::vector<std::string>& extra_args) { + std::string tmp_dir = GetScratchDir() + "/sdm_tmp"; + ASSERT_EQ(0, mkdir(tmp_dir.c_str(), 0700)); + + std::string odex_location = tmp_dir + "/TestDex.odex"; + std::string vdex_location = tmp_dir + "/TestDex.vdex"; + std::string art_location; + + std::vector<std::string> extra_args_with_app_image = extra_args; + if (include_app_image) { + art_location = tmp_dir + "/TestDex.art"; + extra_args_with_app_image.push_back("--app-image-file=" + art_location); + } + + // Generate temporary ODEX, VDEX, and ART files in order to create the SDM and DM files from. + ASSERT_NO_FATAL_FAILURE(GenerateOdexForTest( + dex_location, odex_location, filter, compilation_reason, extra_args_with_app_image)); + + // Create the SDM and DM files. + ASSERT_NO_FATAL_FAILURE(CreateSecureDexMetadata(odex_location, art_location, sdm_location)); + ASSERT_NO_FATAL_FAILURE(CreateDexMetadata(vdex_location, dm_location, /*page_aligned=*/true)); + + // Cleanup the temporary files. + ASSERT_NO_FATAL_FAILURE(ClearDirectory(tmp_dir.c_str())); +} + void DexoptTest::ReserveImageSpace() { MemMap::Init(); diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h index cf32785c0b..fa18fdd94b 100644 --- a/runtime/dexopt_test.h +++ b/runtime/dexopt_test.h @@ -64,6 +64,16 @@ class DexoptTest : public Dex2oatEnvironmentTest { // Generate a standard oat file in the oat location. void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter); + // Generate sdm and dm files for the purposes of test. + // If `include_app_image` is true, generates an app image and includes it in the sdm file. + void GenerateSdmDmForTest(const std::string& dex_location, + const std::string& sdm_location, + const std::string& dm_location, + CompilerFilter::Filter filter, + bool include_app_image, + const char* compilation_reason = nullptr, + const std::vector<std::string>& extra_args = {}); + bool Dex2Oat(const std::vector<std::string>& args, std::string* error_msg); private: diff --git a/runtime/exec_utils_test.cc b/runtime/exec_utils_test.cc index eb21652c19..636383d474 100644 --- a/runtime/exec_utils_test.cc +++ b/runtime/exec_utils_test.cc @@ -102,7 +102,7 @@ class NeverFallbackExecUtils : public TestingExecUtils { } }; -class ExecUtilsTest : public CommonRuntimeTest, public testing::WithParamInterface<bool> { +class ExecUtilsTest : public CommonRuntimeTest, public ::testing::WithParamInterface<bool> { protected: void SetUp() override { CommonRuntimeTest::SetUp(); @@ -390,6 +390,6 @@ TEST_P(ExecUtilsTest, ExecNewProcessGroupFalse) { &error_msg); } -INSTANTIATE_TEST_SUITE_P(AlwaysOrNeverFallback, ExecUtilsTest, testing::Values(true, false)); +INSTANTIATE_TEST_SUITE_P(AlwaysOrNeverFallback, ExecUtilsTest, ::testing::Values(true, false)); } // namespace art diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 97512bc79c..f557f3c8a9 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -26,6 +26,7 @@ #include <optional> #include <random> #include <string> +#include <string_view> #include <vector> #include "android-base/logging.h" @@ -3415,31 +3416,39 @@ void ImageSpace::Dump(std::ostream& os) const { << ",name=\"" << GetName() << "\"]"; } -bool ImageSpace::ValidateApexVersions(const OatHeader& oat_header, - const std::string& apex_versions, - const std::string& file_location, +bool ImageSpace::ValidateApexVersions(const OatFile& oat_file, + std::string_view runtime_apex_versions, std::string* error_msg) { // For a boot image, the key value store only exists in the first OAT file. Skip other OAT files. - if (oat_header.GetKeyValueStoreSize() == 0) { + if (oat_file.GetOatHeader().GetKeyValueStoreSize() == 0) { return true; } - const char* oat_apex_versions = oat_header.GetStoreValueByKey(OatHeader::kApexVersionsKey); - if (oat_apex_versions == nullptr) { + std::optional<std::string_view> oat_apex_versions = oat_file.GetApexVersions(); + if (!oat_apex_versions.has_value()) { *error_msg = StringPrintf("ValidateApexVersions failed to get APEX versions from oat file '%s'", - file_location.c_str()); + oat_file.GetLocation().c_str()); return false; } + + return ValidateApexVersions( + *oat_apex_versions, runtime_apex_versions, oat_file.GetLocation(), error_msg); +} + +bool ImageSpace::ValidateApexVersions(std::string_view oat_apex_versions, + std::string_view runtime_apex_versions, + const std::string& file_location, + std::string* error_msg) { // For a boot image, it can be generated from a subset of the bootclasspath. // For an app image, some dex files get compiled with a subset of the bootclasspath. // For such cases, the OAT APEX versions will be a prefix of the runtime APEX versions. - if (!apex_versions.starts_with(oat_apex_versions)) { - *error_msg = StringPrintf( - "ValidateApexVersions found APEX versions mismatch between oat file '%s' and the runtime " - "(Oat file: '%s', Runtime: '%s')", - file_location.c_str(), + if (!runtime_apex_versions.starts_with(oat_apex_versions)) { + *error_msg = ART_FORMAT( + "ValidateApexVersions found APEX versions mismatch between oat file '{}' and the runtime " + "(Oat file: '{}', Runtime: '{}')", + file_location, oat_apex_versions, - apex_versions.c_str()); + runtime_apex_versions); return false; } return true; @@ -3455,10 +3464,7 @@ bool ImageSpace::ValidateOatFile(const OatFile& oat_file, ArrayRef<const std::string> dex_filenames, ArrayRef<File> dex_files, const std::string& apex_versions) { - if (!ValidateApexVersions(oat_file.GetOatHeader(), - apex_versions, - oat_file.GetLocation(), - error_msg)) { + if (!ValidateApexVersions(oat_file, apex_versions, error_msg)) { return false; } diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h index 266b1d5925..4d0ce8181e 100644 --- a/runtime/gc/space/image_space.h +++ b/runtime/gc/space/image_space.h @@ -260,9 +260,13 @@ class ImageSpace : public MemMapSpace { const std::string& image_location, bool boot_image_extension = false); - // Returns true if the APEX versions in the OAT header match the given APEX versions. - static bool ValidateApexVersions(const OatHeader& oat_header, - const std::string& apex_versions, + // Returns true if the APEX versions of the OAT file match the given APEX versions. + static bool ValidateApexVersions(const OatFile& oat_file, + std::string_view runtime_apex_versions, + std::string* error_msg); + + static bool ValidateApexVersions(std::string_view oat_apex_versions, + std::string_view runtime_apex_versions, const std::string& file_location, std::string* error_msg); diff --git a/runtime/gc/space/space_create_test.cc b/runtime/gc/space/space_create_test.cc index 83568351b3..759c5de4ef 100644 --- a/runtime/gc/space/space_create_test.cc +++ b/runtime/gc/space/space_create_test.cc @@ -351,10 +351,10 @@ TEST_P(SpaceCreateTest, AllocAndFreeListTestBody) { INSTANTIATE_TEST_CASE_P(CreateRosAllocSpace, SpaceCreateTest, - testing::Values(kMallocSpaceRosAlloc)); + ::testing::Values(kMallocSpaceRosAlloc)); INSTANTIATE_TEST_CASE_P(CreateDlMallocSpace, SpaceCreateTest, - testing::Values(kMallocSpaceDlMalloc)); + ::testing::Values(kMallocSpaceDlMalloc)); } // namespace space } // namespace gc diff --git a/runtime/jit/jit_memory_region_test.cc b/runtime/jit/jit_memory_region_test.cc index 449255a7f7..b17e01fff9 100644 --- a/runtime/jit/jit_memory_region_test.cc +++ b/runtime/jit/jit_memory_region_test.cc @@ -52,7 +52,7 @@ static void registerSignalHandler() { sigaction(SIGSEGV, &sa, nullptr); } -class TestZygoteMemory : public testing::Test { +class TestZygoteMemory : public ::testing::Test { public: void BasicTest() { // Zygote JIT memory only works on kernels that don't segfault on flush. diff --git a/runtime/metrics/reporter_test.cc b/runtime/metrics/reporter_test.cc index 039e43ca48..b3a0c025ca 100644 --- a/runtime/metrics/reporter_test.cc +++ b/runtime/metrics/reporter_test.cc @@ -399,7 +399,7 @@ TEST_F(MetricsReporterTest, CompilerFilter) { } // Test class for period spec parsing -class ReportingPeriodSpecTest : public testing::Test { +class ReportingPeriodSpecTest : public ::testing::Test { public: void VerifyFalse(const std::string& spec_str) { Verify(spec_str, false, false, false, {}); diff --git a/runtime/oat/oat.cc b/runtime/oat/oat.cc index 840825cea8..9882b0fa51 100644 --- a/runtime/oat/oat.cc +++ b/runtime/oat/oat.cc @@ -373,7 +373,7 @@ const uint8_t* OatHeader::GetKeyValueStore() const { return key_value_store_; } -const char* OatHeader::GetStoreValueByKey(const char* key) const { +const char* OatHeader::GetStoreValueByKeyUnsafe(const char* key) const { std::string_view key_view(key); uint32_t offset = 0; diff --git a/runtime/oat/oat.h b/runtime/oat/oat.h index 0061c90da9..2069b569e3 100644 --- a/runtime/oat/oat.h +++ b/runtime/oat/oat.h @@ -164,7 +164,14 @@ class EXPORT PACKED(4) OatHeader { uint32_t GetKeyValueStoreSize() const; const uint8_t* GetKeyValueStore() const; - const char* GetStoreValueByKey(const char* key) const; + const char* GetStoreValueByKeyUnsafe(const char* key) const; + + const char* GetStoreValueByKey(const char* key) const { + // Do not get apex versions from the oat header directly. Use `OatFile::GetApexVersions` + // instead. + DCHECK_NE(std::string_view(key), kApexVersionsKey); + return GetStoreValueByKeyUnsafe(key); + } // Returns the next key-value pair, at the given offset. On success, updates `offset`. // The expected use case is to start the iteration with an offset initialized to zero and diff --git a/runtime/oat/oat_file.cc b/runtime/oat/oat_file.cc index 1ecad4abf9..ec845cd0c1 100644 --- a/runtime/oat/oat_file.cc +++ b/runtime/oat/oat_file.cc @@ -20,25 +20,34 @@ #include <sys/stat.h> #include <unistd.h> +#include <cstddef> #include <cstdint> +#include <cstdio> #include <cstdlib> #include <cstring> +#include <memory> #include <optional> #include <sstream> +#include <string> #include <type_traits> +#include "android-base/file.h" #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "arch/instruction_set_features.h" #include "art_method.h" +#include "base/array_ref.h" #include "base/bit_vector.h" #include "base/file_utils.h" +#include "base/globals.h" #include "base/logging.h" // For VLOG_IS_ON. +#include "base/macros.h" #include "base/mem_map.h" #include "base/os.h" #include "base/pointer_size.h" #include "base/stl_util.h" #include "base/systrace.h" +#include "base/time_utils.h" #include "base/unix_file/fd_file.h" #include "base/utils.h" #include "base/zip_archive.h" @@ -59,6 +68,7 @@ #include "mirror/class.h" #include "mirror/object-inl.h" #include "oat.h" +#include "oat/sdc_file.h" #include "oat_file-inl.h" #include "oat_file_manager.h" #include "runtime-inl.h" @@ -174,6 +184,14 @@ class OatFileBase : public OatFile { /*inout*/ MemMap* reservation, // Where to load if not null. /*out*/ std::string* error_msg); + template <typename kOatFileBaseSubType> + static OatFileBase* OpenOatFileFromSdm(const std::string& sdm_filename, + const std::string& sdc_filename, + const std::string& dm_filename, + const std::string& dex_filename, + bool executable, + /*out*/ std::string* error_msg); + protected: OatFileBase(const std::string& filename, bool executable) : OatFile(filename, executable) {} @@ -316,6 +334,61 @@ OatFileBase* OatFileBase::OpenOatFile(int zip_fd, return ret.release(); } +template <typename kOatFileBaseSubType> +OatFileBase* OatFileBase::OpenOatFileFromSdm(const std::string& sdm_filename, + const std::string& sdc_filename, + const std::string& dm_filename, + const std::string& dex_filename, + bool executable, + /*out*/ std::string* error_msg) { + std::string elf_filename = sdm_filename + kZipSeparator + "primary.odex"; + std::unique_ptr<OatFileBase> ret(new kOatFileBaseSubType(elf_filename, executable)); + + struct stat sdm_st; + if (stat(sdm_filename.c_str(), &sdm_st) != 0) { + *error_msg = ART_FORMAT("Failed to stat sdm file '{}': {}", sdm_filename, strerror(errno)); + return nullptr; + } + + std::unique_ptr<SdcReader> sdc_reader = SdcReader::Load(sdc_filename, error_msg); + if (sdc_reader == nullptr) { + return nullptr; + } + if (sdc_reader->GetSdmTimestampNs() != TimeSpecToNs(sdm_st.st_mtim)) { + // The sdm file had been replaced after the sdc file was created. + *error_msg = ART_FORMAT("Obsolete sdc file '{}'", sdc_filename); + return nullptr; + } + // The apex-versions value in the sdc file, written by ART Service, is the value of + // `Runtime::GetApexVersions` at the time where the sdm file was first seen on device. We use it + // to override the APEX versions in the oat header. This is for detecting samegrade placebos. + ret->override_apex_versions_ = sdc_reader->GetApexVersions(); + + if (!ret->Load(elf_filename, executable, /*low_4gb=*/false, /*reservation=*/nullptr, error_msg)) { + return nullptr; + } + + if (!ret->ComputeFields(elf_filename, error_msg)) { + return nullptr; + } + + ret->PreSetup(elf_filename); + + ret->vdex_ = VdexFile::OpenFromDm(dm_filename, ret->vdex_begin_, ret->vdex_end_, error_msg); + if (ret->vdex_ == nullptr) { + return nullptr; + } + + if (!ret->Setup(/*zip_fd=*/-1, + ArrayRef<const std::string>(&dex_filename, /*size=*/1u), + /*dex_files=*/{}, + error_msg)) { + return nullptr; + } + + return ret.release(); +} + bool OatFileBase::LoadVdex(const std::string& vdex_filename, bool low_4gb, std::string* error_msg) { vdex_ = VdexFile::OpenAtAddress(vdex_begin_, vdex_end_ - vdex_begin_, @@ -1330,11 +1403,11 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, return false; #else { - UniqueCPtr<char> absolute_path(realpath(elf_filename.c_str(), nullptr)); - if (absolute_path == nullptr) { - *error_msg = StringPrintf("Failed to find absolute path for '%s'", elf_filename.c_str()); - return false; - } + // `elf_filename` is in the format of `/path/to/oat` or `/path/to/zip!/primary.odex`. We can + // reuse `GetDexCanonicalLocation` to resolve the real path of the part before "!" even though + // `elf_filename` does not refer to a dex file. + static_assert(std::string_view(kZipSeparator).starts_with(DexFileLoader::kMultiDexSeparator)); + std::string absolute_path = DexFileLoader::GetDexCanonicalLocation(elf_filename.c_str()); #ifdef ART_TARGET_ANDROID android_dlextinfo extinfo = {}; extinfo.flags = ANDROID_DLEXT_FORCE_LOAD; // Force-load, don't reuse handle @@ -1350,9 +1423,9 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, } if (strncmp(kAndroidArtApexDefaultPath, - absolute_path.get(), + absolute_path.c_str(), sizeof(kAndroidArtApexDefaultPath) - 1) != 0 || - absolute_path.get()[sizeof(kAndroidArtApexDefaultPath) - 1] != '/') { + absolute_path.c_str()[sizeof(kAndroidArtApexDefaultPath) - 1] != '/') { // Use the system namespace for OAT files outside the ART APEX. Search // paths and links don't matter here, but permitted paths do, and the // system namespace is configured to allow loading from all appropriate @@ -1361,7 +1434,7 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, extinfo.library_namespace = GetSystemLinkerNamespace(); } - dlopen_handle_ = android_dlopen_ext(absolute_path.get(), RTLD_NOW, &extinfo); + dlopen_handle_ = android_dlopen_ext(absolute_path.c_str(), RTLD_NOW, &extinfo); if (reservation != nullptr && dlopen_handle_ != nullptr) { // Find used pages from the reservation. struct dl_iterate_context { @@ -1435,7 +1508,7 @@ bool DlOpenOatFile::Dlopen(const std::string& elf_filename, return false; } MutexLock mu(Thread::Current(), *Locks::host_dlopen_handles_lock_); - dlopen_handle_ = dlopen(absolute_path.get(), RTLD_NOW); + dlopen_handle_ = dlopen(absolute_path.c_str(), RTLD_NOW); if (dlopen_handle_ != nullptr) { if (!host_dlopen_handles_.insert(dlopen_handle_).second) { dlclose(dlopen_handle_); @@ -2034,6 +2107,38 @@ OatFile* OatFile::OpenFromVdex(int zip_fd, return OatFileBackedByVdex::Open(zip_fd, std::move(vdex_file), location, context, error_msg); } +OatFile* OatFile::OpenFromSdm(const std::string& sdm_filename, + const std::string& sdc_filename, + const std::string& dm_filename, + const std::string& dex_filename, + bool executable, + /*out*/ std::string* error_msg) { + ScopedTrace trace("Open sdm file " + sdm_filename); + CHECK(!sdm_filename.empty()); + CHECK(!sdc_filename.empty()); + CHECK(!dm_filename.empty()); + CHECK(!dex_filename.empty()); + + // Check if the dm file exists, to fail fast. The dm file contains the vdex that is essential for + // using the odex in the sdm file. + if (!OS::FileExists(dm_filename.c_str())) { + *error_msg = + ART_FORMAT("Not loading sdm file because dm file '{}' does not exist", dm_filename); + return nullptr; + } + + // Try dlopen first, as it is required for native debuggability. This will fail fast if dlopen is + // disabled. + OatFile* with_dlopen = OatFileBase::OpenOatFileFromSdm<DlOpenOatFile>( + sdm_filename, sdc_filename, dm_filename, dex_filename, executable, error_msg); + if (with_dlopen != nullptr) { + return with_dlopen; + } + + return OatFileBase::OpenOatFileFromSdm<ElfOatFile>( + sdm_filename, sdc_filename, dm_filename, dex_filename, executable, error_msg); +} + OatFile::OatFile(const std::string& location, bool is_executable) : location_(location), vdex_(nullptr), @@ -2586,4 +2691,13 @@ bool OatFile::IsBackedByVdexOnly() const { return oat_dex_files_storage_.size() >= 1 && oat_dex_files_storage_[0]->IsBackedByVdexOnly(); } +std::optional<std::string_view> OatFile::GetApexVersions() const { + if (override_apex_versions_.has_value()) { + return override_apex_versions_; + } + const char* oat_apex_versions = + GetOatHeader().GetStoreValueByKeyUnsafe(OatHeader::kApexVersionsKey); + return oat_apex_versions != nullptr ? std::make_optional(oat_apex_versions) : std::nullopt; +} + } // namespace art diff --git a/runtime/oat/oat_file.h b/runtime/oat/oat_file.h index 9e66e1d4a7..33645fa4ba 100644 --- a/runtime/oat/oat_file.h +++ b/runtime/oat/oat_file.h @@ -19,6 +19,7 @@ #include <list> #include <memory> +#include <optional> #include <string> #include <string_view> #include <vector> @@ -186,6 +187,13 @@ class OatFile { ClassLoaderContext* context, std::string* error_msg); + static OatFile* OpenFromSdm(const std::string& sdm_filename, + const std::string& sdc_filename, + const std::string& dm_filename, + const std::string& dex_filename, + bool executable, + /*out*/ std::string* error_msg); + // Set the start of the app image. // Needed for initializing app image relocations in the .data.img.rel.ro section. void SetAppImageBegin(uint8_t* app_image_begin) const { @@ -426,6 +434,8 @@ class OatFile { // Returns the mapping info of `dex_file` if found in the BcpBssInfo, or nullptr otherwise. const BssMappingInfo* FindBcpMappingInfo(const DexFile* dex_file) const; + std::optional<std::string_view> GetApexVersions() const; + protected: OatFile(const std::string& filename, bool executable); @@ -518,6 +528,9 @@ class OatFile { // by the `dex_filenames` parameter, in case the OatFile does not embed the dex code. std::vector<std::unique_ptr<const DexFile>> external_dex_files_; + // If set, overrides the APEX versions in the header. + std::optional<std::string> override_apex_versions_ = std::nullopt; + friend class gc::collector::FakeOatFile; // For modifying begin_ and end_. friend class OatClass; friend class art::OatDexFile; diff --git a/runtime/oat/oat_file_assistant.cc b/runtime/oat/oat_file_assistant.cc index f5d6832cff..53f412908d 100644 --- a/runtime/oat/oat_file_assistant.cc +++ b/runtime/oat/oat_file_assistant.cc @@ -191,6 +191,12 @@ OatFileAssistant::OatFileAssistant(const char* dex_location, oat_file_name, /*is_oat_location=*/true, /*use_fd=*/false)); + info_list_.push_back( + std::make_unique<OatFileInfoBackedBySdm>(this, + GetSdmFilename(dex_location_, isa), + /*is_oat_location=*/true, + GetDmFilename(dex_location_), + GetSdcFilename(oat_file_name))); } if (!odex_file_name.empty()) { @@ -202,6 +208,12 @@ OatFileAssistant::OatFileAssistant(const char* dex_location, zip_fd, vdex_fd, oat_fd)); + info_list_.push_back( + std::make_unique<OatFileInfoBackedBySdm>(this, + GetSdmFilename(dex_location_, isa), + /*is_oat_location=*/false, + GetDmFilename(dex_location_), + GetSdcFilename(odex_file_name))); } // When there is no odex/oat available (e.g., they are both out of date), we look for a useable @@ -324,7 +336,8 @@ int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target_compiler_fil OatFileInfo& info = GetBestInfo(); DexOptNeeded dexopt_needed = info.GetDexOptNeeded( target_compiler_filter, GetDexOptTrigger(target_compiler_filter, profile_changed, downgrade)); - if (dexopt_needed != kNoDexOptNeeded && info.GetType() == OatFileType::kDm) { + if (dexopt_needed != kNoDexOptNeeded && + (info.GetType() == OatFileType::kDm || info.GetType() == OatFileType::kSdm)) { // The usable vdex file is in the DM file. This information cannot be encoded in the integer. // Return kDex2OatFromScratch so that neither the vdex in the "oat" location nor the vdex in the // "odex" location will be picked by installd. @@ -491,10 +504,7 @@ OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& return kOatBootImageOutOfDate; } if (!gc::space::ImageSpace::ValidateApexVersions( - file.GetOatHeader(), - GetOatFileAssistantContext()->GetApexVersions(), - file.GetLocation(), - error_msg)) { + file, GetOatFileAssistantContext()->GetApexVersions(), error_msg)) { return kOatBootImageOutOfDate; } } @@ -959,6 +969,10 @@ bool OatFileAssistant::OatFileInfoBackedByOat::FileExists() const { return use_fd_ || OatFileInfo::FileExists(); } +bool OatFileAssistant::OatFileInfoBackedBySdm::FileExists() const { + return OatFileInfo::FileExists() && OS::FileExists(sdc_filename_.c_str()); +} + bool OatFileAssistant::OatFileInfoBackedByVdex::FileExists() const { return use_fd_ || OatFileInfo::FileExists(); } @@ -1018,6 +1032,21 @@ std::unique_ptr<OatFile> OatFileAssistant::OatFileInfoBackedByOat::LoadFile( } } +std::unique_ptr<OatFile> OatFileAssistant::OatFileInfoBackedBySdm::LoadFile( + std::string* error_msg) const { + bool executable = oat_file_assistant_->load_executable_; + if (executable && oat_file_assistant_->only_load_trusted_executable_) { + executable = LocationIsTrusted(filename_, /*trust_art_apex_data_files=*/true); + } + + return std::unique_ptr<OatFile>(OatFile::OpenFromSdm(filename_, + sdc_filename_, + dm_filename_, + oat_file_assistant_->dex_location_, + executable, + error_msg)); +} + std::unique_ptr<OatFile> OatFileAssistant::OatFileInfoBackedByVdex::LoadFile( std::string* error_msg) const { // Check to see if there is a vdex file we can make use of. @@ -1292,7 +1321,13 @@ bool OatFileAssistant::ZipFileOnlyContainsUncompressedDex() { OatFileAssistant::Location OatFileAssistant::GetLocation(OatFileInfo& info) { if (info.IsUseable()) { - if (info.GetType() == OatFileType::kDm) { + if (info.GetType() == OatFileType::kSdm) { + if (info.IsOatLocation()) { + return kLocationSdmOat; + } else { + return kLocationSdmOdex; + } + } else if (info.GetType() == OatFileType::kDm) { return kLocationDm; } else if (info.IsOatLocation()) { return kLocationOat; diff --git a/runtime/oat/oat_file_assistant.h b/runtime/oat/oat_file_assistant.h index bcb0c8e9db..80eabba34b 100644 --- a/runtime/oat/oat_file_assistant.h +++ b/runtime/oat/oat_file_assistant.h @@ -123,8 +123,14 @@ class OatFileAssistant { kLocationOat = 1, // In the "oat" folder next to the dex file. kLocationOdex = 2, - // In the DM file. This means the only usable file is the vdex file. + // In the dm file. This means the only usable file is the vdex file. kLocationDm = 3, + // The oat and art files are in the sdm file next to the dex file. The vdex file is in the dm + // file next to the dex file. The sdc file is in the global "dalvik-cache" folder. + kLocationSdmOat = 4, + // The oat and art files are in the sdm file next to the dex file. The vdex file is in the dm + // file next to the dex file. The sdc file is next to the dex file. + kLocationSdmOdex = 5, }; // Represents the status of the current oat file and/or vdex file. @@ -381,6 +387,7 @@ class OatFileAssistant { enum class OatFileType { kNone, kOat, + kSdm, kVdex, kDm, }; @@ -529,6 +536,33 @@ class OatFileAssistant { const int oat_fd_; }; + class OatFileInfoBackedBySdm : public OatFileInfo { + public: + OatFileInfoBackedBySdm(OatFileAssistant* oat_file_assistant, + const std::string& sdm_filename, + bool is_oat_location, + const std::string& dm_filename, + const std::string& sdc_filename) + : OatFileInfo(oat_file_assistant, sdm_filename, is_oat_location), + dm_filename_(dm_filename), + sdc_filename_(sdc_filename) {} + + OatFileType GetType() override { return OatFileType::kSdm; } + + const char* GetLocationDebugString() override { + return IsOatLocation() ? "sdm with sdc in dalvik-cache" : "sdm with sdc next to the dex file"; + } + + bool FileExists() const override; + + protected: + std::unique_ptr<OatFile> LoadFile(std::string* error_msg) const override; + + private: + const std::string dm_filename_; + const std::string sdc_filename_; + }; + class OatFileInfoBackedByVdex : public OatFileInfo { public: OatFileInfoBackedByVdex(OatFileAssistant* oat_file_assistant, diff --git a/runtime/oat/oat_file_assistant_context.h b/runtime/oat/oat_file_assistant_context.h index 82b79edef0..28d5b49dc2 100644 --- a/runtime/oat/oat_file_assistant_context.h +++ b/runtime/oat/oat_file_assistant_context.h @@ -76,7 +76,7 @@ class OatFileAssistantContext { const std::vector<std::string>* GetBcpChecksums(size_t bcp_index, std::string* error_msg); // Returns a string that represents the apex versions of boot classpath jars. See // `Runtime::apex_versions_` for the encoding format. - const std::string& GetApexVersions(); + EXPORT const std::string& GetApexVersions(); private: std::unique_ptr<RuntimeOptions> runtime_options_; diff --git a/runtime/oat/oat_file_assistant_test.cc b/runtime/oat/oat_file_assistant_test.cc index b1f196a23a..c3e2f63e24 100644 --- a/runtime/oat/oat_file_assistant_test.cc +++ b/runtime/oat/oat_file_assistant_test.cc @@ -33,6 +33,7 @@ #include "android-base/strings.h" #include "arch/instruction_set.h" #include "art_field-inl.h" +#include "base/file_utils.h" #include "base/os.h" #include "base/utils.h" #include "class_linker.h" @@ -52,7 +53,7 @@ namespace art HIDDEN { class OatFileAssistantBaseTest : public DexoptTest {}; class OatFileAssistantTest : public OatFileAssistantBaseTest, - public testing::WithParamInterface<bool> { + public ::testing::WithParamInterface<bool> { public: void SetUp() override { DexoptTest::SetUp(); @@ -2183,6 +2184,310 @@ TEST_P(OatFileAssistantTest, ShouldRecompileForImageFromSpeedProfile) { oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify)); } +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, and the SDC file is in odex +// location. +// Expect: The best artifact location should be kLocationSdmOdex. Dexopt should be performed only if +// the compiler filter is better than "speed-profile". +// +// The legacy version should return kDex2OatFromScratch if the target compiler filter is better than +// "verify". +TEST_P(OatFileAssistantTest, SdmUpToDate) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOdex); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kSpeed, + default_trigger_, + /*expected_dexopt_needed=*/true, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kSpeedProfile, + default_trigger_, + /*expected_dexopt_needed=*/false, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile)); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kVerify, + default_trigger_, + /*expected_dexopt_needed=*/false, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify)); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, and the SDC file is in oat +// location. +// Expect: The best artifact location should be kLocationSdmOat. +TEST_P(OatFileAssistantTest, SdmUpToDateSdcInOatLocation) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + std::string oat_location; + std::string error_msg; + ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename( + dex_location, kRuntimeISA, &oat_location, &error_msg)) + << error_msg; + std::string sdc_location = GetSdcFilename(oat_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOat); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, and the SDM file contains no +// ART file. +// Expect: The best artifact location should be kLocationSdmOdex. +TEST_P(OatFileAssistantTest, SdmUpToDateNoArt) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/false, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOdex); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file. Meanwhile, we have an ODEX +// file that is also up to date. +// Expect: The ODEX file is preferred over the SDM file. The best artifact location should be +// kLocationOdex. +TEST_P(OatFileAssistantTest, SdmAndOdexUpToDate) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + std::string odex_location = GetOdexDir() + "/TestDex.odex"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + ASSERT_NO_FATAL_FAILURE(GenerateOdexForTest(dex_location, + odex_location, + CompilerFilter::kSpeedProfile, + /*compilation_reason=*/"bg-dexopt")); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "bg-dexopt", + "up-to-date", + OatFileAssistant::kLocationOdex); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file. Meanwhile, we have a VDEX +// file that is also up to date. +// Expect: The SDM file is preferred over the VDEX file. The best artifact location should be +// kLocationSdmOdex. +TEST_P(OatFileAssistantTest, SdmAndVdexUpToDate) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + std::string odex_location = GetOdexDir() + "/TestDex.odex"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + ASSERT_NO_FATAL_FAILURE(GenerateOdexForTest(dex_location, + odex_location, + CompilerFilter::kSpeedProfile, + /*compilation_reason=*/"bg-dexopt")); + ASSERT_EQ(0, unlink(odex_location.c_str())); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOdex); +} + +// Case: We have SDM, DM, and SDC files for a compressed DEX file. +// Expect: The SDM file is still picked. Dexopt should be performed if the compiler filter is +// "speed-profile" or above. +TEST_P(OatFileAssistantTest, SdmUpToDateCompressedDex) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + Copy(GetMultiDexSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud", + /*extra_args=*/{"--copy-dex-files=false"})); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance(&oat_file_assistant, + "speed-profile", + "cloud", + "up-to-date", + OatFileAssistant::kLocationSdmOdex); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kSpeedProfile, + default_trigger_, + /*expected_dexopt_needed=*/true, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile)); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kVerify, + default_trigger_, + /*expected_dexopt_needed=*/false, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationSdmOdex); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify)); +} + +// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, but the SDC file contains the +// wrong APEX versions. +// Expect: The SDM file is rejected, while the DM file is still picked. Dexopt should be performed +// if the compiler filter is better than "verify". +TEST_P(OatFileAssistantTest, SdmApexVersionMismatch) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string sdm_location = + GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA)); + std::string dm_location = GetScratchDir() + "/TestDex.dm"; + std::string sdc_location = GetOdexDir() + "/TestDex.sdc"; + Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location); + + ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location, + sdm_location, + dm_location, + CompilerFilter::kSpeedProfile, + /*include_app_image=*/true, + /*compilation_reason=*/"cloud")); + ASSERT_NO_FATAL_FAILURE( + CreateSecureDexMetadataCompanion(sdm_location, "wrong-apex-version", sdc_location)); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str()); + + VerifyOptimizationStatusWithInstance( + &oat_file_assistant, "verify", "vdex", "up-to-date", OatFileAssistant::kLocationDm); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kSpaceProfile, + default_trigger_, + /*expected_dexopt_needed=*/true, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationDm); + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpaceProfile)); + + VerifyGetDexOptNeeded(&oat_file_assistant, + CompilerFilter::kVerify, + default_trigger_, + /*expected_dexopt_needed=*/false, + /*expected_is_vdex_usable=*/true, + /*expected_location=*/OatFileAssistant::kLocationDm); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify)); +} + class CollectDexCacheVisitor : public DexCacheVisitor { public: explicit CollectDexCacheVisitor( @@ -2593,6 +2898,8 @@ TEST_P(OatFileAssistantTest, ValidateBootClassPathChecksums) { // - Oat file corrupted after status check, before reload unexecutable // because it's unrelocated and no dex2oat -INSTANTIATE_TEST_SUITE_P(WithOrWithoutRuntime, OatFileAssistantTest, testing::Values(true, false)); +INSTANTIATE_TEST_SUITE_P(WithOrWithoutRuntime, + OatFileAssistantTest, + ::testing::Values(true, false)); } // namespace art diff --git a/runtime/oat/sdc_file.cc b/runtime/oat/sdc_file.cc new file mode 100644 index 0000000000..e141aea85e --- /dev/null +++ b/runtime/oat/sdc_file.cc @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sdc_file.h" + +#include <cstdint> +#include <memory> +#include <regex> +#include <string> +#include <string_view> +#include <unordered_map> +#include <vector> + +#include "android-base/file.h" +#include "android-base/parseint.h" +#include "android-base/scopeguard.h" +#include "base/macros.h" +#include "base/utils.h" + +namespace art HIDDEN { + +using ::android::base::ParseInt; +using ::android::base::ReadFileToString; +using ::android::base::WriteStringToFd; + +std::unique_ptr<SdcReader> SdcReader::Load(const std::string& filename, std::string* error_msg) { + std::unique_ptr<SdcReader> reader(new SdcReader()); + + // The sdc file is supposed to be small, so read fully into memory for simplicity. + if (!ReadFileToString(filename, &reader->content_)) { + *error_msg = ART_FORMAT("Failed to load sdc file '{}': {}", filename, strerror(errno)); + return nullptr; + } + + std::vector<std::string_view> lines; + Split(reader->content_, '\n', &lines); + std::unordered_map<std::string_view, std::string_view> map; + for (std::string_view line : lines) { + size_t pos = line.find('='); + if (pos == std::string_view::npos || pos == 0) { + *error_msg = ART_FORMAT("Malformed line '{}' in sdc file '{}'", line, filename); + return nullptr; + } + if (!map.try_emplace(line.substr(0, pos), line.substr(pos + 1)).second) { + *error_msg = ART_FORMAT("Duplicate key '{}' in sdc file '{}'", line.substr(0, pos), filename); + return nullptr; + } + } + + decltype(map)::iterator it; + if ((it = map.find("sdm-timestamp-ns")) == map.end()) { + *error_msg = ART_FORMAT("Missing key 'sdm-timestamp-ns' in sdc file '{}'", filename); + return nullptr; + } + if (!ParseInt(std::string(it->second), &reader->sdm_timestamp_ns_, /*min=*/INT64_C(1))) { + *error_msg = ART_FORMAT("Invalid 'sdm-timestamp-ns' {}", it->second); + return nullptr; + } + + if ((it = map.find("apex-versions")) == map.end()) { + *error_msg = ART_FORMAT("Missing key 'apex-versions' in sdc file '{}'", filename); + return nullptr; + } + if (!std::regex_match(it->second.begin(), it->second.end(), std::regex("[0-9/]*"))) { + *error_msg = ART_FORMAT("Invalid 'apex-versions' {}", it->second); + return nullptr; + } + reader->apex_versions_ = it->second; + + if (map.size() > 2) { + *error_msg = ART_FORMAT("Malformed sdc file '{}'. Unrecognized keys", filename); + return nullptr; + } + + return reader; +} + +bool SdcWriter::Save(std::string* error_msg) { + auto cleanup = android::base::make_scope_guard([this] { (void)file_.FlushClose(); }); + if (sdm_timestamp_ns_ <= 0) { + *error_msg = ART_FORMAT("Invalid 'sdm-timestamp-ns' {}", sdm_timestamp_ns_); + return false; + } + DCHECK_EQ(file_.GetLength(), 0); + std::string content = + ART_FORMAT("sdm-timestamp-ns={}\napex-versions={}\n", sdm_timestamp_ns_, apex_versions_); + if (!WriteStringToFd(content, file_.Fd())) { + *error_msg = ART_FORMAT("Failed to write sdc file '{}': {}", file_.GetPath(), strerror(errno)); + return false; + } + int res = file_.FlushClose(); + if (res != 0) { + *error_msg = + ART_FORMAT("Failed to flush close sdc file '{}': {}", file_.GetPath(), strerror(-res)); + return false; + } + cleanup.Disable(); + return true; +} + +} // namespace art diff --git a/runtime/oat/sdc_file.h b/runtime/oat/sdc_file.h new file mode 100644 index 0000000000..f4d7a5d116 --- /dev/null +++ b/runtime/oat/sdc_file.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_OAT_SDC_FILE_H_ +#define ART_RUNTIME_OAT_SDC_FILE_H_ + +#include <memory> +#include <string> +#include <string_view> +#include <utility> + +#include "base/macros.h" +#include "base/os.h" + +namespace art HIDDEN { + +// A helper class to read a secure dex metadata companion (SDC) file. +// +// Secure dex metadata companion (SDC) file is a file type that augments a secure dex metadata (SDM) +// file with additional metadata. +// +// 1. There may be exactly one SDC file accompanying each SDM file. An SDC file without a +// corresponding SDM file, or with a mismatching SDM timestamp, is garbage. +// 2. They are always local on device. +// 3. They are only read and written by the ART module. +// 4. A later version of the ART module must be able to understand the contents. +// +// It is a text file in the format of: +// key1=value1\n +// key2=value2\n +// ... +// Repeated keys are not allowed. This is an extensible format, so versioning is not needed. +// +// In principle, ART Service generates an SDC file for an SDM file during installation. +// Specifically, during dexopt, which typically takes place during installation, if there is an SDM +// file while the corresponding SDC file is missing (meaning the SDM file is newly installed) or +// stale (meaning the SDM file is newly replaced), ART Service will generate a new SDC file. This +// means an SDM file without a corresponding SDC file is a transient state and is valid from ART +// Service's perspective. +// +// From the runtime's perspective, an SDM file without a corresponding SDC file is incomplete. That +// means: +// - At app execution time, the runtime ignores an SDM file without a corresponding SDC. +// - ART Service's file GC, which uses the runtime's judgement, considers an SDM file without a +// corresponding SDC invalid and may clean it up. This may race with a package installation before +// the SDC is created, but it's rare and the effect is recoverable, so it's considered acceptable. +class EXPORT SdcReader { + public: + static std::unique_ptr<SdcReader> Load(const std::string& filename, std::string* error_msg); + + // The mtime of the SDM file on device, in nanoseconds. + // This is for detecting obsolete SDC files. + int64_t GetSdmTimestampNs() const { return sdm_timestamp_ns_; } + + // The value of `Runtime::GetApexVersions` at the time where the SDM file was first seen on + // device. This is for detecting samegrade placebos. + std::string_view GetApexVersions() const { return apex_versions_; } + + private: + SdcReader() = default; + + std::string content_; + int64_t sdm_timestamp_ns_; + std::string_view apex_versions_; +}; + +// A helper class to write a secure dex metadata companion (SDC) file. +class EXPORT SdcWriter { + public: + // Takes ownership of the file. + explicit SdcWriter(File&& file) : file_(std::move(file)) {} + + // See `SdcReader::GetSdmTimestampNs`. + void SetSdmTimestampNs(int64_t value) { sdm_timestamp_ns_ = value; } + + // See `SdcReader::GetApexVersions`. + void SetApexVersions(std::string_view value) { apex_versions_ = value; } + + bool Save(std::string* error_msg); + + private: + File file_; + int64_t sdm_timestamp_ns_ = 0; + std::string apex_versions_; +}; + +} // namespace art + +#endif // ART_RUNTIME_OAT_SDC_FILE_H_ diff --git a/runtime/oat/sdc_file_test.cc b/runtime/oat/sdc_file_test.cc new file mode 100644 index 0000000000..eb1d3d6cb2 --- /dev/null +++ b/runtime/oat/sdc_file_test.cc @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sdc_file.h" + +#include <cstdint> +#include <memory> +#include <string> + +#include "android-base/file.h" +#include "base/common_art_test.h" +#include "base/macros.h" +#include "base/os.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace art HIDDEN { + +using ::android::base::ReadFileToString; +using ::android::base::WriteStringToFile; +using ::testing::HasSubstr; +using ::testing::StartsWith; + +class SdcFileTestBase : public CommonArtTest { + protected: + void SetUp() override { + CommonArtTest::SetUp(); + + scratch_dir_ = std::make_unique<ScratchDir>(); + test_file_ = scratch_dir_->GetPath() + "test.sdc"; + } + + void TearDown() override { + scratch_dir_.reset(); + CommonArtTest::TearDown(); + } + + std::unique_ptr<ScratchDir> scratch_dir_; + std::string test_file_; +}; + +class SdcReaderTest : public SdcFileTestBase {}; + +TEST_F(SdcReaderTest, Success) { + ASSERT_TRUE(WriteStringToFile( + "sdm-timestamp-ns=987654321000000003\napex-versions=/12345678/12345679\n", test_file_)); + + std::string error_msg; + std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg); + ASSERT_NE(reader, nullptr) << error_msg; + + EXPECT_EQ(reader->GetApexVersions(), "/12345678/12345679"); + EXPECT_EQ(reader->GetSdmTimestampNs(), INT64_C(987654321000000003)); +} + +TEST_F(SdcReaderTest, NotFound) { + std::string error_msg; + std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg); + ASSERT_EQ(reader, nullptr); + + EXPECT_THAT(error_msg, StartsWith("Failed to load sdc file")); +} + +TEST_F(SdcReaderTest, MissingApexVersions) { + ASSERT_TRUE(WriteStringToFile("sdm-timestamp-ns=987654321\n", test_file_)); + + std::string error_msg; + std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg); + ASSERT_EQ(reader, nullptr); + + EXPECT_THAT(error_msg, StartsWith("Missing key 'apex-versions' in sdc file")); +} + +TEST_F(SdcReaderTest, InvalidSdmTimestamp) { + ASSERT_TRUE( + WriteStringToFile("sdm-timestamp-ns=0\napex-versions=/12345678/12345679\n", test_file_)); + + std::string error_msg; + std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg); + ASSERT_EQ(reader, nullptr); + + EXPECT_THAT(error_msg, HasSubstr("Invalid 'sdm-timestamp-ns'")); +} + +TEST_F(SdcReaderTest, InvalidApexVersions) { + ASSERT_TRUE(WriteStringToFile("sdm-timestamp-ns=987654321\napex-versions=abc\n", test_file_)); + + std::string error_msg; + std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg); + ASSERT_EQ(reader, nullptr); + + EXPECT_THAT(error_msg, HasSubstr("Invalid 'apex-versions'")); +} + +TEST_F(SdcReaderTest, UnrecognizedKey) { + ASSERT_TRUE(WriteStringToFile( + "sdm-timestamp-ns=987654321\napex-versions=/12345678/12345679\nwrong-key=12345678\n", + test_file_)); + + std::string error_msg; + std::unique_ptr<SdcReader> reader = SdcReader::Load(test_file_, &error_msg); + ASSERT_EQ(reader, nullptr); + + EXPECT_THAT(error_msg, HasSubstr("Unrecognized keys")); +} + +class SdcWriterTest : public SdcFileTestBase {}; + +TEST_F(SdcWriterTest, Success) { + std::unique_ptr<File> file(OS::CreateEmptyFileWriteOnly(test_file_.c_str())); + ASSERT_NE(file, nullptr); + SdcWriter writer(std::move(*file)); + + writer.SetApexVersions("/12345678/12345679"); + writer.SetSdmTimestampNs(987654321l); + + std::string error_msg; + ASSERT_TRUE(writer.Save(&error_msg)) << error_msg; + + std::string content; + ASSERT_TRUE(ReadFileToString(test_file_, &content)); + + EXPECT_EQ(content, "sdm-timestamp-ns=987654321\napex-versions=/12345678/12345679\n"); +} + +TEST_F(SdcWriterTest, SaveFailed) { + ASSERT_TRUE(WriteStringToFile("", test_file_)); + + std::unique_ptr<File> file(OS::OpenFileForReading(test_file_.c_str())); + ASSERT_NE(file, nullptr); + SdcWriter writer( + File(file->Release(), file->GetPath(), /*check_usage=*/false, /*read_only_mode=*/false)); + + writer.SetApexVersions("/12345678/12345679"); + writer.SetSdmTimestampNs(987654321l); + + std::string error_msg; + EXPECT_FALSE(writer.Save(&error_msg)); + + EXPECT_THAT(error_msg, StartsWith("Failed to write sdc file")); +} + +TEST_F(SdcWriterTest, InvalidSdmTimestamp) { + std::unique_ptr<File> file(OS::CreateEmptyFileWriteOnly(test_file_.c_str())); + ASSERT_NE(file, nullptr); + SdcWriter writer(std::move(*file)); + + writer.SetApexVersions("/12345678/12345679"); + + std::string error_msg; + EXPECT_FALSE(writer.Save(&error_msg)); + + EXPECT_THAT(error_msg, StartsWith("Invalid 'sdm-timestamp-ns'")); +} + +} // namespace art diff --git a/sigchainlib/sigchain_test.cc b/sigchainlib/sigchain_test.cc index 3a68381117..238aea3152 100644 --- a/sigchainlib/sigchain_test.cc +++ b/sigchainlib/sigchain_test.cc @@ -104,7 +104,8 @@ static void TestSignalBlocking(const std::function<void()>& fn) { fn(); - if (testing::Test::HasFatalFailure()) return; + if (::testing::Test::HasFatalFailure()) + return; ASSERT_EQ(0, RealSigprocmask(SIG_SETMASK, nullptr, &mask)); ASSERT_FALSE(sigismember64(&mask, SIGSEGV)); } @@ -266,7 +267,7 @@ DISABLE_HWASAN void fault_address_tag_impl() { auto* tagged_null = reinterpret_cast<int*>(0x2bULL << 56); EXPECT_EXIT( - { [[maybe_unused]] volatile int load = *tagged_null; }, testing::ExitedWithCode(0), ""); + { [[maybe_unused]] volatile int load = *tagged_null; }, ::testing::ExitedWithCode(0), ""); // Our sigaction implementation always implements the "clear unknown bits" // semantics for oldact.sa_flags regardless of kernel version so we rely on it @@ -275,9 +276,10 @@ DISABLE_HWASAN void fault_address_tag_impl() { ASSERT_EQ(0, sigaction(SIGSEGV, &action, nullptr)); ASSERT_EQ(0, sigaction(SIGSEGV, nullptr, &action)); if (action.sa_flags & SA_EXPOSE_TAGBITS) { - EXPECT_EXIT({ [[maybe_unused]] volatile int load = *tagged_null; }, - testing::ExitedWithCode(0x2b), - ""); + EXPECT_EXIT( + { [[maybe_unused]] volatile int load = *tagged_null; }, + ::testing::ExitedWithCode(0x2b), + ""); } } #endif diff --git a/test/Android.bp b/test/Android.bp index d3084fe7d2..76559a92c4 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -363,6 +363,10 @@ art_cc_defaults { }, }, static_libs: [ + // This dependency links the whole runtime statically into the test. Note that the boot + // classpath is not (normally) bundled with the test, so if the runtime is used to actually + // start a VM it may load the boot classpath from the device. Depending on the test + // configuration, that may not be in sync with the statically linked runtime. "libart-gtest", ], version_script: ":art-standalone-gtest-version", diff --git a/test/default_run.py b/test/default_run.py index 85cf4a97ad..21673f79a3 100755 --- a/test/default_run.py +++ b/test/default_run.py @@ -920,19 +920,11 @@ def default_run(ctx, args, **kwargs): if SIMPLEPERF: dalvikvm_cmdline = f"simpleperf record {dalvikvm_cmdline} && simpleperf report" - def sanitize_dex2oat_cmdline(cmdline: str) -> str: - args = [] - for arg in cmdline.split(" "): - if arg == "--class-loader-context=&": - arg = r"--class-loader-context=\&" - args.append(arg) - return " ".join(args) - # Remove whitespace. - dex2oat_cmdline = sanitize_dex2oat_cmdline(dex2oat_cmdline) + dex2oat_cmdline = re.sub(" +", " ", dex2oat_cmdline) dalvikvm_cmdline = re.sub(" +", " ", dalvikvm_cmdline) dm_cmdline = re.sub(" +", " ", dm_cmdline) - vdex_cmdline = sanitize_dex2oat_cmdline(vdex_cmdline) + vdex_cmdline = re.sub(" +", " ", vdex_cmdline) profman_cmdline = re.sub(" +", " ", profman_cmdline) # Use an empty ASAN_OPTIONS to enable defaults. diff --git a/test/generate-boot-image/generate-boot-image.cc b/test/generate-boot-image/generate-boot-image.cc index 454b63185a..539670fa85 100644 --- a/test/generate-boot-image/generate-boot-image.cc +++ b/test/generate-boot-image/generate-boot-image.cc @@ -46,7 +46,6 @@ using ::android::base::ParseBool; using ::android::base::ParseBoolResult; using ::android::base::StringPrintf; using ::art::testing::GetLibCoreDexFileNames; -using ::art::testing::GetLibCoreDexLocations; constexpr const char* kUsage = R"( A commandline tool to generate a primary boot image for testing. @@ -141,7 +140,7 @@ int GenerateBootImage(const Options& options) { if (options.android_root_for_location) { dex_locations = dex_files; } else { - dex_locations = GetLibCoreDexLocations(options.core_only); + dex_locations = GetLibCoreDexFileNames(/*prefix=*/"", options.core_only); } args.push_back("--runtime-arg"); args.push_back("-Xbootclasspath:" + Join(dex_files, ":")); diff --git a/test/standalone_test_lib_check.cc b/test/standalone_test_lib_check.cc index 426a302f54..72c835a658 100644 --- a/test/standalone_test_lib_check.cc +++ b/test/standalone_test_lib_check.cc @@ -180,6 +180,6 @@ TEST(StandaloneTestAllowedLibDeps, test) { disallowed_libs.push_back(dyn_lib_dep); } - EXPECT_THAT(disallowed_libs, testing::IsEmpty()) + EXPECT_THAT(disallowed_libs, ::testing::IsEmpty()) << path_to_self.value() << " has disallowed shared library dependencies."; } diff --git a/tools/libcore_gcstress_debug_failures.txt b/tools/libcore_gcstress_debug_failures.txt index 6d987487f5..6fee7e881a 100644 --- a/tools/libcore_gcstress_debug_failures.txt +++ b/tools/libcore_gcstress_debug_failures.txt @@ -29,7 +29,8 @@ description: "Timeouts on host with gcstress and debug.", result: EXEC_FAILED, modes: [host], - names: ["jsr166.ForkJoinPoolTest#testIsQuiescent", + names: ["jsr166.ConcurrentSkipListSetTest#testRecursiveSubSets", + "jsr166.ForkJoinPoolTest#testIsQuiescent", "jsr166.ScheduledExecutorSubclassTest#testTimedInvokeAll4", "jsr166.StampedLockTest#testWriteAfterReadLock", "jsr166.StampedLockTest#testReadTryLock_Interruptible", @@ -37,6 +38,7 @@ "jsr166.StampedLockTest#testReadLockInterruptibly", "jsr166.StampedLockTest#testWriteLockInterruptibly", "jsr166.TimeUnitTest#testConvert", + "jsr166.TreeMapTest#testRecursiveSubMaps", "jsr166.TreeSetTest#testRecursiveSubSets", "libcore.java.lang.OldThreadTest#test_getState", "libcore.java.lang.StringTest#testFastPathString_wellFormedUtf8Sequence", @@ -53,7 +55,8 @@ "org.apache.harmony.tests.java.lang.ref.ReferenceQueueTest#test_removeJ", "org.apache.harmony.tests.java.lang.ProcessManagerTest#testSleep", "org.apache.harmony.tests.java.util.TimerTest#testOverdueTaskExecutesImmediately", - "org.apache.harmony.tests.java.util.WeakHashMapTest#test_keySet_hasNext" + "org.apache.harmony.tests.java.util.WeakHashMapTest#test_keySet_hasNext", + "test.java.util.Collections.RacingCollections#main" ] }, { diff --git a/tools/luci/config/generated/cr-buildbucket.cfg b/tools/luci/config/generated/cr-buildbucket.cfg index fd7bfdaf48..cef584cf10 100644 --- a/tools/luci/config/generated/cr-buildbucket.cfg +++ b/tools/luci/config/generated/cr-buildbucket.cfg @@ -11,7 +11,7 @@ buckets { group: "project-art-admins" } acls { - group: "all" + group: "googlers" } swarming { builders { @@ -898,7 +898,7 @@ buckets { group: "project-art-admins" } acls { - group: "all" + group: "googlers" } constraints { pools: "luci.art.ci" diff --git a/tools/luci/config/generated/luci-logdog.cfg b/tools/luci/config/generated/luci-logdog.cfg index 01a391261d..9d817a1e14 100644 --- a/tools/luci/config/generated/luci-logdog.cfg +++ b/tools/luci/config/generated/luci-logdog.cfg @@ -4,6 +4,6 @@ # For the schema of this file, see ProjectConfig message: # https://config.luci.app/schemas/projects:luci-logdog.cfg -reader_auth_groups: "all" +reader_auth_groups: "googlers" writer_auth_groups: "luci-logdog-chromium-writers" archive_gs_bucket: "chromium-luci-logdog" diff --git a/tools/luci/config/generated/luci-scheduler.cfg b/tools/luci/config/generated/luci-scheduler.cfg index 4888f69517..a2b426ee19 100644 --- a/tools/luci/config/generated/luci-scheduler.cfg +++ b/tools/luci/config/generated/luci-scheduler.cfg @@ -439,6 +439,6 @@ acl_sets { granted_to: "group:project-art-admins" } acls { - granted_to: "group:all" + granted_to: "group:googlers" } } diff --git a/tools/luci/config/generated/project.cfg b/tools/luci/config/generated/project.cfg index 3a46212fd6..5619b9ab83 100644 --- a/tools/luci/config/generated/project.cfg +++ b/tools/luci/config/generated/project.cfg @@ -5,9 +5,9 @@ # https://config.luci.app/schemas/projects:project.cfg name: "art" -access: "group:all" +access: "group:googlers" lucicfg { - version: "1.43.16" + version: "1.44.1" package_dir: ".." config_dir: "generated" entry_point: "main.star" diff --git a/tools/luci/config/generated/realms.cfg b/tools/luci/config/generated/realms.cfg index d439ea168c..ade0991042 100644 --- a/tools/luci/config/generated/realms.cfg +++ b/tools/luci/config/generated/realms.cfg @@ -12,15 +12,15 @@ realms { } bindings { role: "role/buildbucket.reader" - principals: "group:all" + principals: "group:googlers" } bindings { role: "role/configs.reader" - principals: "group:all" + principals: "group:googlers" } bindings { role: "role/logdog.reader" - principals: "group:all" + principals: "group:googlers" } bindings { role: "role/logdog.writer" @@ -32,7 +32,7 @@ realms { } bindings { role: "role/scheduler.reader" - principals: "group:all" + principals: "group:googlers" } bindings { role: "role/swarming.poolOwner" @@ -44,7 +44,7 @@ realms { } bindings { role: "role/swarming.poolViewer" - principals: "group:all" + principals: "group:googlers" } bindings { role: "role/swarming.taskTriggerer" diff --git a/tools/luci/config/main.star b/tools/luci/config/main.star index 2c530ba229..42ee495bbc 100755 --- a/tools/luci/config/main.star +++ b/tools/luci/config/main.star @@ -47,7 +47,6 @@ luci.project( scheduler = "luci-scheduler.appspot.com", swarming = "chromium-swarm.appspot.com", acls = [ - # Publicly readable. acl.entry( roles = [ acl.BUILDBUCKET_READER, @@ -55,7 +54,7 @@ luci.project( acl.PROJECT_CONFIGS_READER, acl.SCHEDULER_READER, ], - groups = "all", + groups = "googlers", ), acl.entry( roles = [ @@ -76,7 +75,7 @@ luci.project( ), luci.binding( roles = "role/swarming.poolViewer", - groups = "all", + groups = "googlers", ), ], ) |