Add a version of `GetOptimizationStatus` that accepts inputs in strings
This function will be used by artd.
Bug: 233383589
Test: atest art_standalone_runtime_tests:OatFileAssistantTest
Change-Id: I0d9e57dd1402261fd17b954005fbf80ba0ad2926
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 81aaad5..1ba0cbb 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -1141,6 +1141,57 @@
&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 6a6b3c0..527f473 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -204,7 +204,7 @@
// - 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 @@
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 30852fe..c0662aa 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 @@
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
- assistant->GetOptimizationStatus(
- &odex_location2,
- &compilation_filter2,
- &compilation_reason2,
- &odex_status2);
+ ASSERT_TRUE(
+ OatFileAssistant::GetOptimizationStatus(file,
+ GetInstructionSetString(kRuntimeISA),
+ context->EncodeContextForDex2oat(/*base_dir=*/""),
+ MaybeCreateRuntimeOptions(),
+ &compilation_filter2,
+ &compilation_reason2,
+ &odex_location2,
+ &error_msg));
- ASSERT_EQ(expected_filter, compilation_filter2);
+ ASSERT_EQ(expected_filter_name, compilation_filter2);
ASSERT_EQ(expected_reason, compilation_reason2);
- ASSERT_EQ(expected_odex_status, odex_status2);
- }
- 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);
+ // Verify the instance methods (called at runtime).
+ OatFileAssistant assistant = CreateOatFileAssistant(file.c_str(), context);
+
+ std::string odex_location3; // ignored
+ std::string compilation_filter3;
+ std::string compilation_reason3;
+ std::string odex_status3;
+
+ 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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, 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