OatFileAssistant: look at vdex file for IsDexOptNeeded
Even if the oat file cannot be opened, there may be a vdex file that
contains valid dex code. The oat file assistant should take this into
account when determining the dexopt status.
Bug: 34339100
Test: oat_file_assistant_test, with new tests added.
Test: install an app, dumpsys package to show app status,
delete app's oat file, dumpsys package to show status as vdex only,
cmd package compile -r install app, verify in log that input vdex fd is
provided to dex2oat.
Change-Id: Iab43e617a899773b318d61adeb117ed2cec085da
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 8554fa2..8a23457 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -33,6 +33,7 @@
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
#include "utils.h"
+#include "vdex_file.h"
namespace art {
@@ -216,28 +217,38 @@
bool oat_file_exists = false;
bool odex_file_exists = false;
if (oat_.Status() != kOatCannotOpen) {
- // If we can open the file, neither Filename nor GetFile should return null.
+ // If we can open the file, Filename should not return null.
CHECK(oat_.Filename() != nullptr);
- CHECK(oat_.GetFile() != nullptr);
oat_file_exists = true;
- status << *oat_.Filename() << " [compilation_filter=";
- status << CompilerFilter::NameOfFilter(oat_.GetFile()->GetCompilerFilter());
- status << ", status=" << oat_.Status();
+ status << *oat_.Filename() << "[status=" << oat_.Status() << ", ";
+ const OatFile* file = oat_.GetFile();
+ if (file == nullptr) {
+ // If the file is null even though the status is not kOatCannotOpen, it
+ // means we must have a vdex file with no corresponding oat file. In
+ // this case we cannot determine the compilation filter. Indicate that
+ // we have only the vdex file instead.
+ status << "vdex-only";
+ } else {
+ status << "compilation_filter=" << CompilerFilter::NameOfFilter(file->GetCompilerFilter());
+ }
}
if (odex_.Status() != kOatCannotOpen) {
- // If we can open the file, neither Filename nor GetFile should return null.
+ // If we can open the file, Filename should not return null.
CHECK(odex_.Filename() != nullptr);
- CHECK(odex_.GetFile() != nullptr);
odex_file_exists = true;
if (oat_file_exists) {
status << "] ";
}
- status << *odex_.Filename() << " [compilation_filter=";
- status << CompilerFilter::NameOfFilter(odex_.GetFile()->GetCompilerFilter());
- status << ", status=" << odex_.Status();
+ status << *odex_.Filename() << "[status=" << odex_.Status() << ", ";
+ const OatFile* file = odex_.GetFile();
+ if (file == nullptr) {
+ status << "vdex-only";
+ } else {
+ status << "compilation_filter=" << CompilerFilter::NameOfFilter(file->GetCompilerFilter());
+ }
}
if (!oat_file_exists && !odex_file_exists) {
@@ -303,24 +314,55 @@
return oat_.Status();
}
-OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& file) {
- // Verify the ART_USE_READ_BARRIER state.
- const bool is_cc = file.GetOatHeader().IsConcurrentCopying();
- constexpr bool kRuntimeIsCC = kUseReadBarrier;
- if (is_cc != kRuntimeIsCC) {
- return kOatCannotOpen;
+bool OatFileAssistant::DexChecksumUpToDate(const VdexFile& file, std::string* error_msg) {
+ // TODO: Use GetRequiredDexChecksum to get secondary checksums as well, not
+ // just the primary. Because otherwise we may fail to see a secondary
+ // checksum failure in the case when the original (multidex) files are
+ // stripped but we have a newer odex file.
+ const uint32_t* dex_checksum_pointer = GetRequiredDexChecksum();
+ if (dex_checksum_pointer != nullptr) {
+ uint32_t actual_checksum = file.GetLocationChecksum(0);
+ if (*dex_checksum_pointer != actual_checksum) {
+ VLOG(oat) << "Dex checksum does not match for primary dex: " << dex_location_
+ << ". Expected: " << *dex_checksum_pointer
+ << ", Actual: " << actual_checksum;
+ return false;
+ }
}
- // Verify the dex checksum.
+ // Verify the dex checksums for any secondary multidex files
+ for (uint32_t i = 1; i < file.GetHeader().GetNumberOfDexFiles(); i++) {
+ std::string secondary_dex_location = DexFile::GetMultiDexLocation(i, dex_location_.c_str());
+ uint32_t expected_secondary_checksum = 0;
+ if (DexFile::GetChecksum(secondary_dex_location.c_str(),
+ &expected_secondary_checksum,
+ error_msg)) {
+ uint32_t actual_secondary_checksum = file.GetLocationChecksum(i);
+ if (expected_secondary_checksum != actual_secondary_checksum) {
+ VLOG(oat) << "Dex checksum does not match for secondary dex: "
+ << secondary_dex_location
+ << ". Expected: " << expected_secondary_checksum
+ << ", Actual: " << actual_secondary_checksum;
+ return false;
+ }
+ } else {
+ // If we can't get the checksum for the secondary location, we assume
+ // the dex checksum is up to date for this and all other secondary dex
+ // files.
+ break;
+ }
+ }
+ return true;
+}
+
+bool OatFileAssistant::DexChecksumUpToDate(const OatFile& file, std::string* error_msg) {
// Note: GetOatDexFile will return null if the dex checksum doesn't match
// what we provide, which verifies the primary dex checksum for us.
- std::string error_msg;
const uint32_t* dex_checksum_pointer = GetRequiredDexChecksum();
const OatFile::OatDexFile* oat_dex_file = file.GetOatDexFile(
- dex_location_.c_str(), dex_checksum_pointer, &error_msg);
+ dex_location_.c_str(), dex_checksum_pointer, error_msg);
if (oat_dex_file == nullptr) {
- LOG(ERROR) << error_msg;
- return kOatDexOutOfDate;
+ return false;
}
// Verify the dex checksums for any secondary multidex files
@@ -335,7 +377,7 @@
uint32_t expected_secondary_checksum = 0;
if (DexFile::GetChecksum(secondary_dex_location.c_str(),
- &expected_secondary_checksum, &error_msg)) {
+ &expected_secondary_checksum, error_msg)) {
uint32_t actual_secondary_checksum
= secondary_oat_dex_file->GetDexFileLocationChecksum();
if (expected_secondary_checksum != actual_secondary_checksum) {
@@ -343,7 +385,7 @@
<< secondary_dex_location
<< ". Expected: " << expected_secondary_checksum
<< ", Actual: " << actual_secondary_checksum;
- return kOatDexOutOfDate;
+ return false;
}
} else {
// If we can't get the checksum for the secondary location, we assume
@@ -352,6 +394,35 @@
break;
}
}
+ return true;
+}
+
+OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& file) {
+ // Verify the ART_USE_READ_BARRIER state.
+ // TODO: Don't fully reject files due to read barrier state. If they contain
+ // compiled code and are otherwise okay, we should return something like
+ // kOatRelocationOutOfDate. If they don't contain compiled code, the read
+ // barrier state doesn't matter.
+ const bool is_cc = file.GetOatHeader().IsConcurrentCopying();
+ constexpr bool kRuntimeIsCC = kUseReadBarrier;
+ if (is_cc != kRuntimeIsCC) {
+ return kOatCannotOpen;
+ }
+
+ // Verify the dex checksum.
+ std::string error_msg;
+ if (kIsVdexEnabled) {
+ VdexFile* vdex = file.GetVdexFile();
+ if (!DexChecksumUpToDate(*vdex, &error_msg)) {
+ LOG(ERROR) << error_msg;
+ return kOatDexOutOfDate;
+ }
+ } else {
+ if (!DexChecksumUpToDate(file, &error_msg)) {
+ LOG(ERROR) << error_msg;
+ return kOatDexOutOfDate;
+ }
+ }
CompilerFilter::Filter current_compiler_filter = file.GetCompilerFilter();
@@ -777,7 +848,27 @@
status_attempted_ = true;
const OatFile* file = GetFile();
if (file == nullptr) {
- status_ = kOatCannotOpen;
+ // Check to see if there is a vdex file we can make use of.
+ std::string error_msg;
+ std::string vdex_filename = ReplaceFileExtension(filename_, "vdex");
+ std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_filename,
+ /*writeable*/false,
+ /*low_4gb*/false,
+ &error_msg));
+ if (vdex == nullptr) {
+ status_ = kOatCannotOpen;
+ VLOG(oat) << "unable to open vdex file " << vdex_filename << ": " << error_msg;
+ } else {
+ if (oat_file_assistant_->DexChecksumUpToDate(*vdex, &error_msg)) {
+ // The vdex file does not contain enough information to determine
+ // whether it is up to date with respect to the boot image, so we
+ // assume it is out of date.
+ VLOG(oat) << error_msg;
+ status_ = kOatBootImageOutOfDate;
+ } else {
+ status_ = kOatDexOutOfDate;
+ }
+ }
} else {
status_ = oat_file_assistant_->GivenOatFileStatus(*file);
VLOG(oat) << file->GetLocation() << " is " << status_
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 588a698..6d47ad2 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -379,6 +379,16 @@
// Return info for the best oat file.
OatFileInfo& GetBestInfo();
+ // Returns true if the dex checksums in the given vdex file are up to date
+ // with respect to the dex location. If the dex checksums are not up to
+ // date, error_msg is updated with a message describing the problem.
+ bool DexChecksumUpToDate(const VdexFile& file, std::string* error_msg);
+
+ // Returns true if the dex checksums in the given oat file are up to date
+ // with respect to the dex location. If the dex checksums are not up to
+ // date, error_msg is updated with a message describing the problem.
+ bool DexChecksumUpToDate(const OatFile& file, std::string* error_msg);
+
// Return the status for a given opened oat file with respect to the dex
// location.
OatStatus GivenOatFileStatus(const OatFile& file);
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index afa804c..e8e9295 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -341,27 +341,69 @@
EXPECT_TRUE(oat_file_assistant.HasOriginalDexFiles());
}
-// Case: We have a DEX file and ODEX file for a different dex location.
-// Expect: The status is kDex2OatNeeded.
-TEST_F(OatFileAssistantTest, OatForDifferentDex) {
- // Generate an odex file for OatForDifferentDex_A.jar
- std::string dex_location_a = GetScratchDir() + "/OatForDifferentDex_A.jar";
- std::string odex_location = GetOdexDir() + "/OatForDifferentDex.odex";
- Copy(GetDexSrc1(), dex_location_a);
- GenerateOdexForTest(dex_location_a, odex_location, CompilerFilter::kSpeed);
+// Case: We have a DEX file and up-to-date (ODEX) VDEX file for it, but no
+// ODEX file.
+TEST_F(OatFileAssistantTest, VdexUpToDateNoOdex) {
+ // This test case is only meaningful if vdex is enabled.
+ if (!kIsVdexEnabled) {
+ return;
+ }
- // Try to use that odex file for OatForDifferentDex.jar
- std::string dex_location = GetScratchDir() + "/OatForDifferentDex.jar";
+ std::string dex_location = GetScratchDir() + "/VdexUpToDateNoOdex.jar";
+ std::string oat_location = GetOdexDir() + "/VdexUpToDateNoOdex.oat";
+
Copy(GetDexSrc1(), dex_location);
+ // Generating and deleting the oat file should have the side effect of
+ // creating an up-to-date vdex file.
+ GenerateOdexForTest(dex_location, oat_location, CompilerFilter::kSpeed);
+ ASSERT_EQ(0, unlink(oat_location.c_str()));
+
+ OatFileAssistant oat_file_assistant(dex_location.c_str(),
+ oat_location.c_str(),
+ kRuntimeISA,
+ false);
+
+ // Even though the vdex file is up to date, because we don't have the oat
+ // file, we can't know that the vdex depends on the boot image and is up to
+ // date with respect to the boot image. Instead we must assume the vdex file
+ // depends on the boot image and is out of date with respect to the boot
+ // image.
+ EXPECT_EQ(-OatFileAssistant::kDex2OatForBootImage,
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
+
+ // Make sure we don't crash in this case when we dump the status. We don't
+ // care what the actual dumped value is.
+ oat_file_assistant.GetStatusDump();
+}
+
+// Case: We have a DEX file and up-to-date (OAT) VDEX file for it, but no OAT
+// file.
+TEST_F(OatFileAssistantTest, VdexUpToDateNoOat) {
+ // This test case is only meaningful if vdex is enabled.
+ if (!kIsVdexEnabled) {
+ return;
+ }
+
+ std::string dex_location = GetScratchDir() + "/VdexUpToDateNoOat.jar";
+ std::string oat_location;
+ std::string error_msg;
+ ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
+ dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
+
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+ ASSERT_EQ(0, unlink(oat_location.c_str()));
+
OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+ // Even though the vdex file is up to date, because we don't have the oat
+ // file, we can't know that the vdex depends on the boot image and is up to
+ // date with respect to the boot image. Instead we must assume the vdex file
+ // depends on the boot image and is out of date with respect to the boot
+ // image.
+ EXPECT_EQ(OatFileAssistant::kDex2OatForBootImage,
oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
-
- EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
- EXPECT_EQ(OatFileAssistant::kOatDexOutOfDate, oat_file_assistant.OdexFileStatus());
- EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
}
// Case: We have a DEX file and speed-profile OAT file for it.
@@ -484,6 +526,56 @@
EXPECT_TRUE(oat_file_assistant.HasOriginalDexFiles());
}
+// Case: We have a DEX file and an (ODEX) VDEX file out of date with respect
+// to the dex checksum, but no ODEX file.
+TEST_F(OatFileAssistantTest, VdexDexOutOfDate) {
+ // This test case is only meaningful if vdex is enabled.
+ if (!kIsVdexEnabled) {
+ return;
+ }
+
+ std::string dex_location = GetScratchDir() + "/VdexDexOutOfDate.jar";
+ std::string oat_location = GetOdexDir() + "/VdexDexOutOfDate.oat";
+
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, oat_location, CompilerFilter::kSpeed);
+ ASSERT_EQ(0, unlink(oat_location.c_str()));
+ Copy(GetDexSrc2(), dex_location);
+
+ OatFileAssistant oat_file_assistant(dex_location.c_str(),
+ oat_location.c_str(),
+ kRuntimeISA,
+ false);
+
+ EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
+}
+
+// Case: We have a MultiDEX (ODEX) VDEX file where the secondary dex file is
+// out of date and there is no corresponding ODEX file.
+TEST_F(OatFileAssistantTest, VdexMultiDexSecondaryOutOfDate) {
+ // This test case is only meaningful if vdex is enabled.
+ if (!kIsVdexEnabled) {
+ return;
+ }
+
+ std::string dex_location = GetScratchDir() + "/VdexMultiDexSecondaryOutOfDate.jar";
+ std::string oat_location = GetOdexDir() + "/VdexMultiDexSecondaryOutOfDate.oat";
+
+ Copy(GetMultiDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, oat_location, CompilerFilter::kSpeed);
+ ASSERT_EQ(0, unlink(oat_location.c_str()));
+ Copy(GetMultiDexSrc2(), dex_location);
+
+ OatFileAssistant oat_file_assistant(dex_location.c_str(),
+ oat_location.c_str(),
+ kRuntimeISA,
+ false);
+
+ EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
+}
+
// Case: We have a DEX file and an OAT file out of date with respect to the
// boot image.
TEST_F(OatFileAssistantTest, OatImageOutOfDate) {
@@ -1175,6 +1267,4 @@
// - Dex is stripped, don't have odex.
// - Oat file corrupted after status check, before reload unexecutable
// because it's unrelocated and no dex2oat
-// * Test unrelocated specific target compilation type can be relocated to
-// make it up to date.
} // namespace art