diff options
-rw-r--r-- | runtime/oat_file_assistant.cc | 51 | ||||
-rw-r--r-- | runtime/oat_file_assistant.h | 14 | ||||
-rw-r--r-- | runtime/oat_file_assistant_test.cc | 242 |
3 files changed, 236 insertions, 71 deletions
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 81aaad580a..1ba0cbb640 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -1141,6 +1141,57 @@ void OatFileAssistant::GetOptimizationStatus(const std::string& filename, &out_odex_status); } +bool OatFileAssistant::GetOptimizationStatus(const std::string& filename, + const std::string& isa_str, + const std::string& context_str, + std::unique_ptr<RuntimeOptions> runtime_options, + /*out*/ std::string* compiler_filter, + /*out*/ std::string* compilation_reason, + /*out*/ std::string* odex_location, + /*out*/ std::string* error_msg) { + InstructionSet isa = GetInstructionSetFromString(isa_str.c_str()); + if (isa == InstructionSet::kNone) { + *error_msg = StringPrintf("Instruction set '%s' is invalid", isa_str.c_str()); + return false; + } + + std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str.c_str()); + if (context == nullptr) { + *error_msg = StringPrintf("Class loader context '%s' is invalid", context_str.c_str()); + return false; + } + + std::vector<int> context_fds; + if (!context->OpenDexFiles(android::base::Dirname(filename.c_str()), + context_fds, + /*only_read_checksums=*/true)) { + *error_msg = + StringPrintf("Failed to load class loader context files for '%s' with context '%s'", + filename.c_str(), + context_str.c_str()); + return false; + } + + OatFileAssistant oat_file_assistant(filename.c_str(), + isa, + context.get(), + /*load_executable=*/false, + /*only_load_trusted_executable=*/true, + std::move(runtime_options)); + + // We ignore the odex_status because it is not meaningful. It can never be + // "boot-image-more-recent" or "context-mismatch". In the case where the boot image has changed or + // there is a context mismatch, the value is "up-to-date" because the vdex file is still usable. + // I.e., it can only be either "up-to-date" or "apk-more-recent", which means it doesn't give us + // information in addition to what we can learn from compiler_filter. + std::string ignored_odex_status; + + oat_file_assistant.GetOptimizationStatus( + odex_location, compiler_filter, compilation_reason, &ignored_odex_status); + + return true; +} + void OatFileAssistant::GetOptimizationStatus( std::string* out_odex_location, std::string* out_compilation_filter, diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index 6a6b3c08f1..527f473fa6 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -204,7 +204,7 @@ class OatFileAssistant { // - out_compilation_reason: the optimization reason. The reason might // be "unknown" if the compiler artifacts were not annotated during optimizations. // - out_odex_status: a human readable refined status of the validity of the odex file. - // E.g. up-to-date, boot-image-more-recent, apk-more-recent. + // E.g. up-to-date, apk-more-recent. // // This method will try to mimic the runtime effect of loading the dex file. // For example, if there is no usable oat file, the compiler filter will be set @@ -220,6 +220,18 @@ class OatFileAssistant { std::string* out_compilation_reason, std::unique_ptr<RuntimeOptions> runtime_options = nullptr); + // A convenient version of `GetOptimizationStatus` that accepts ISA and class loader context in + // strings. Returns true on success, or returns false and outputs an error message if it fails to + // parse the input strings. + static bool GetOptimizationStatus(const std::string& filename, + const std::string& isa_str, + const std::string& context_str, + std::unique_ptr<RuntimeOptions> runtime_options, + /*out*/ std::string* compiler_filter, + /*out*/ std::string* compilation_reason, + /*out*/ std::string* odex_location, + /*out*/ std::string* error_msg); + // Open and returns an image space associated with the oat file. static std::unique_ptr<gc::space::ImageSpace> OpenImageSpace(const OatFile* oat_file); diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index 30852fe8da..c0662aa5c7 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -23,6 +23,7 @@ #include <functional> #include <memory> #include <string> +#include <type_traits> #include <vector> #include "android-base/scopeguard.h" @@ -53,49 +54,73 @@ class OatFileAssistantTest : public OatFileAssistantBaseTest, with_runtime_ = GetParam(); } - void VerifyOptimizationStatus(OatFileAssistant* assistant, - const std::string& file, - const std::string& expected_filter, + // Verifies all variants of `GetOptimizationStatus`. + // + // `expected_filter` can be either a value of `CompilerFilter::Filter` or a string. + // If `check_context` is true, only verifies the variants that checks class loader context. + template <typename T> + void VerifyOptimizationStatus(const std::string& file, + ClassLoaderContext* context, + const T& expected_filter, const std::string& expected_reason, - const std::string& expected_odex_status) { - // Verify the static methods (called from PM for dexOptNeeded). - std::string compilation_filter1; - std::string compilation_reason1; + const std::string& expected_odex_status, + bool check_context = false) { + std::string expected_filter_name; + if constexpr (std::is_same_v<T, CompilerFilter::Filter>) { + expected_filter_name = CompilerFilter::NameOfFilter(expected_filter); + } else { + expected_filter_name = expected_filter; + } - OatFileAssistant::GetOptimizationStatus( - file, kRuntimeISA, &compilation_filter1, &compilation_reason1, MaybeCreateRuntimeOptions()); + // Verify the static method (called from PM for dumpsys). + // This variant does not check class loader context. + if (!check_context) { + std::string compilation_filter1; + std::string compilation_reason1; - ASSERT_EQ(expected_filter, compilation_filter1); - ASSERT_EQ(expected_reason, compilation_reason1); + OatFileAssistant::GetOptimizationStatus(file, + kRuntimeISA, + &compilation_filter1, + &compilation_reason1, + MaybeCreateRuntimeOptions()); - // Verify the instance methods (called at runtime for systrace). - std::string odex_location2; // ignored + ASSERT_EQ(expected_filter_name, compilation_filter1); + ASSERT_EQ(expected_reason, compilation_reason1); + } + + // Verify the static method (called from artd). std::string compilation_filter2; std::string compilation_reason2; - std::string odex_status2; + std::string odex_location2; // ignored + std::string error_msg; // ignored + + ASSERT_TRUE( + OatFileAssistant::GetOptimizationStatus(file, + GetInstructionSetString(kRuntimeISA), + context->EncodeContextForDex2oat(/*base_dir=*/""), + MaybeCreateRuntimeOptions(), + &compilation_filter2, + &compilation_reason2, + &odex_location2, + &error_msg)); + + ASSERT_EQ(expected_filter_name, compilation_filter2); + ASSERT_EQ(expected_reason, compilation_reason2); - assistant->GetOptimizationStatus( - &odex_location2, - &compilation_filter2, - &compilation_reason2, - &odex_status2); + // Verify the instance methods (called at runtime). + OatFileAssistant assistant = CreateOatFileAssistant(file.c_str(), context); - ASSERT_EQ(expected_filter, compilation_filter2); - ASSERT_EQ(expected_reason, compilation_reason2); - ASSERT_EQ(expected_odex_status, odex_status2); - } + std::string odex_location3; // ignored + std::string compilation_filter3; + std::string compilation_reason3; + std::string odex_status3; - void VerifyOptimizationStatus(OatFileAssistant* assistant, - const std::string& file, - CompilerFilter::Filter expected_filter, - const std::string& expected_reason, - const std::string& expected_odex_status) { - VerifyOptimizationStatus( - assistant, - file, - CompilerFilter::NameOfFilter(expected_filter), - expected_reason, - expected_odex_status); + assistant.GetOptimizationStatus( + &odex_location3, &compilation_filter3, &compilation_reason3, &odex_status3); + + ASSERT_EQ(expected_filter_name, compilation_filter3); + ASSERT_EQ(expected_reason, compilation_reason3); + ASSERT_EQ(expected_odex_status, odex_status3); } void InsertNewBootClasspathEntry() { @@ -329,11 +354,7 @@ TEST_P(OatFileAssistantTest, DexNoOat) { EXPECT_TRUE(oat_file_assistant.HasDexFiles()); VerifyOptimizationStatus( - &oat_file_assistant, - dex_location, - "run-from-apk", - "unknown", - "io-error-no-oat"); + dex_location, default_context_.get(), "run-from-apk", "unknown", "io-error-no-oat"); } // Case: We have no DEX file and no OAT file. @@ -382,11 +403,7 @@ TEST_P(OatFileAssistantTest, OdexUpToDate) { EXPECT_TRUE(oat_file_assistant.HasDexFiles()); VerifyOptimizationStatus( - &oat_file_assistant, - dex_location, - CompilerFilter::kSpeed, - "install", - "up-to-date"); + dex_location, default_context_.get(), CompilerFilter::kSpeed, "install", "up-to-date"); } // Case: We have an ODEX file compiled against partial boot image. @@ -420,11 +437,7 @@ TEST_P(OatFileAssistantTest, OdexUpToDatePartialBootImage) { EXPECT_TRUE(oat_file_assistant.HasDexFiles()); VerifyOptimizationStatus( - &oat_file_assistant, - dex_location, - CompilerFilter::kSpeed, - "install", - "up-to-date"); + dex_location, default_context_.get(), CompilerFilter::kSpeed, "install", "up-to-date"); } // Case: We have a DEX file and a PIC ODEX file, but no OAT file. We load the dex @@ -498,11 +511,7 @@ TEST_P(OatFileAssistantTest, OatUpToDate) { EXPECT_TRUE(oat_file_assistant.HasDexFiles()); VerifyOptimizationStatus( - &oat_file_assistant, - dex_location, - CompilerFilter::kSpeed, - "unknown", - "up-to-date"); + dex_location, default_context_.get(), CompilerFilter::kSpeed, "unknown", "up-to-date"); } // Case: Passing valid file descriptors of updated odex/vdex files along with the dex file. @@ -663,12 +672,7 @@ TEST_P(OatFileAssistantTest, VdexUpToDateNoOdex) { // care what the actual dumped value is. oat_file_assistant.GetStatusDump(); - VerifyOptimizationStatus( - &oat_file_assistant, - dex_location, - "verify", - "vdex", - "up-to-date"); + VerifyOptimizationStatus(dex_location, default_context_.get(), "verify", "vdex", "up-to-date"); } // Case: We have a DEX file and empty VDEX and ODEX files. @@ -855,11 +859,7 @@ TEST_P(OatFileAssistantTest, OatDexOutOfDate) { EXPECT_TRUE(oat_file_assistant.HasDexFiles()); VerifyOptimizationStatus( - &oat_file_assistant, - dex_location, - "run-from-apk-fallback", - "unknown", - "apk-more-recent"); + dex_location, default_context_.get(), "run-from-apk-fallback", "unknown", "apk-more-recent"); } // Case: We have a DEX file and an (ODEX) VDEX file out of date with respect @@ -934,12 +934,36 @@ TEST_P(OatFileAssistantTest, OatImageOutOfDate) { EXPECT_EQ(OatFileAssistant::kOatBootImageOutOfDate, oat_file_assistant.OatFileStatus()); EXPECT_TRUE(oat_file_assistant.HasDexFiles()); + VerifyOptimizationStatus(dex_location, default_context_.get(), "verify", "vdex", "up-to-date"); +} + +TEST_P(OatFileAssistantTest, OatContextOutOfDate) { + std::string dex_location = GetScratchDir() + "/TestDex.jar"; + std::string odex_location = GetOdexDir() + "/TestDex.odex"; + + std::string context_location = GetScratchDir() + "/ContextDex.jar"; + Copy(GetDexSrc1(), dex_location); + Copy(GetDexSrc2(), context_location); + + std::string error_msg; + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location); + args.push_back("--oat-file=" + odex_location); + args.push_back("--class-loader-context=PCL[" + context_location + "]"); + ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg; + + // Update the context by overriding the jar file. + Copy(GetMultiDexSrc2(), context_location); + + std::unique_ptr<ClassLoaderContext> context = + ClassLoaderContext::Create("PCL[" + context_location + "]"); + ASSERT_TRUE(context != nullptr); + ASSERT_TRUE(context->OpenDexFiles()); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + VerifyOptimizationStatus( - &oat_file_assistant, - dex_location, - "verify", - "vdex", - "up-to-date"); + dex_location.c_str(), context.get(), "verify", "vdex", "up-to-date", /*check_context=*/true); } // Case: We have a DEX file and a verify-at-runtime OAT file out of date with @@ -1772,6 +1796,84 @@ TEST_P(OatFileAssistantTest, GetDexOptNeededWithApexVersions) { } } +TEST_P(OatFileAssistantTest, ErrorOnInvalidIsaString) { + std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar"; + std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex"; + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install"); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + std::string ignored_compilation_filter; + std::string ignored_compilation_reason; + std::string ignored_odex_location; + std::string error_msg; + EXPECT_FALSE(OatFileAssistant::GetOptimizationStatus( + dex_location, + /*isa_str=*/"foo", + default_context_->EncodeContextForDex2oat(/*base_dir=*/""), + MaybeCreateRuntimeOptions(), + &ignored_compilation_filter, + &ignored_compilation_reason, + &ignored_odex_location, + &error_msg)); + EXPECT_EQ(error_msg, "Instruction set 'foo' is invalid"); +} + +TEST_P(OatFileAssistantTest, ErrorOnInvalidContextString) { + std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar"; + std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex"; + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install"); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + std::string ignored_compilation_filter; + std::string ignored_compilation_reason; + std::string ignored_odex_location; + std::string error_msg; + EXPECT_FALSE(OatFileAssistant::GetOptimizationStatus(dex_location, + GetInstructionSetString(kRuntimeISA), + /*context_str=*/"foo", + MaybeCreateRuntimeOptions(), + &ignored_compilation_filter, + &ignored_compilation_reason, + &ignored_odex_location, + &error_msg)); + EXPECT_EQ(error_msg, "Class loader context 'foo' is invalid"); +} + +TEST_P(OatFileAssistantTest, ErrorOnInvalidContextFile) { + std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar"; + std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex"; + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install"); + + // Create a broken context file. + std::string context_location = GetScratchDir() + "/BrokenContext.jar"; + std::ofstream output(context_location); + output.close(); + + auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime(); + + std::string ignored_compilation_filter; + std::string ignored_compilation_reason; + std::string ignored_odex_location; + std::string error_msg; + EXPECT_FALSE( + OatFileAssistant::GetOptimizationStatus(dex_location, + GetInstructionSetString(kRuntimeISA), + /*context_str=*/"PCL[" + context_location + "]", + MaybeCreateRuntimeOptions(), + &ignored_compilation_filter, + &ignored_compilation_reason, + &ignored_odex_location, + &error_msg)); + EXPECT_EQ(error_msg, + "Failed to load class loader context files for '" + dex_location + + "' with context 'PCL[" + context_location + "]'"); +} + // TODO: More Tests: // * Test class linker falls back to unquickened dex for DexNoOat // * Test class linker falls back to unquickened dex for MultiDexNoOat |