Merge changes I1ab9ec9f,I0fe01760

* changes:
  Revert^2 "OatFileAssistant: look at vdex file for IsDexOptNeeded"
  Check vdex file is valid in VdexFile::Open.
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 19f0f1c..196d8d4 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1282,13 +1282,10 @@
         DCHECK_EQ(input_vdex_fd_, -1);
         if (!input_vdex_.empty()) {
           std::string error_msg;
-          input_vdex_file_.reset(VdexFile::Open(input_vdex_,
-                                                /* writable */ false,
-                                                /* low_4gb */ false,
-                                                &error_msg));
-          if (input_vdex_file_ != nullptr && !input_vdex_file_->IsValid()) {
-            input_vdex_file_.reset(nullptr);
-          }
+          input_vdex_file_ = VdexFile::Open(input_vdex_,
+                                            /* writable */ false,
+                                            /* low_4gb */ false,
+                                            &error_msg);
         }
 
         DCHECK_EQ(output_vdex_fd_, -1);
@@ -1330,19 +1327,16 @@
           PLOG(WARNING) << "Failed getting length of vdex file";
         } else {
           std::string error_msg;
-          input_vdex_file_.reset(VdexFile::Open(input_vdex_fd_,
-                                                s.st_size,
-                                                "vdex",
-                                                /* writable */ false,
-                                                /* low_4gb */ false,
-                                                &error_msg));
+          input_vdex_file_ = VdexFile::Open(input_vdex_fd_,
+                                            s.st_size,
+                                            "vdex",
+                                            /* writable */ false,
+                                            /* low_4gb */ false,
+                                            &error_msg);
           // If there's any problem with the passed vdex, just warn and proceed
           // without it.
           if (input_vdex_file_ == nullptr) {
-            PLOG(WARNING) << "Failed opening vdex file " << error_msg;
-          } else if (!input_vdex_file_->IsValid()) {
-            PLOG(WARNING) << "Existing vdex file is invalid";
-            input_vdex_file_.reset(nullptr);
+            PLOG(WARNING) << "Failed opening vdex file: " << error_msg;
           }
         }
       }
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 540df5a..5581810 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -576,6 +576,7 @@
         "type_lookup_table_test.cc",
         "utf_test.cc",
         "utils_test.cc",
+        "vdex_file_test.cc",
         "verifier/method_verifier_test.cc",
         "verifier/reg_type_test.cc",
         "zip_archive_test.cc",
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index d47f1b5..31eb1cc 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -193,7 +193,7 @@
                            bool writable,
                            bool low_4gb,
                            std::string* error_msg) {
-  vdex_.reset(VdexFile::Open(vdex_filename, writable, low_4gb, error_msg));
+  vdex_ = VdexFile::Open(vdex_filename, writable, low_4gb, error_msg);
   if (vdex_.get() == nullptr) {
     *error_msg = StringPrintf("Failed to load vdex file '%s' %s",
                               vdex_filename.c_str(),
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index b19ace5..07d7b5a 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,60 @@
   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) {
+  if (file.GetHeader().GetNumberOfDexFiles() <= 0) {
+    VLOG(oat) << "Vdex does not contain any dex files";
+    return false;
   }
 
-  // Verify the dex checksum.
+  // 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 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 +382,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 +390,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 +399,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 +853,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 5772008..102285a 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -111,27 +111,84 @@
   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 empty VDEX and ODEX files.
+TEST_F(OatFileAssistantTest, EmptyVdexOdex) {
+  std::string dex_location = GetScratchDir() + "/EmptyVdexOdex.jar";
+  std::string odex_location = GetOdexDir() + "/EmptyVdexOdex.oat";
+  std::string vdex_location = GetOdexDir() + "/EmptyVdexOdex.vdex";
+
+  Copy(GetDexSrc1(), dex_location);
+  OS::CreateEmptyFile(vdex_location.c_str());
+  OS::CreateEmptyFile(odex_location.c_str());
+
+  OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
+  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+      oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
+}
+
+// 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.
@@ -254,6 +311,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) {
@@ -945,6 +1052,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
diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc
index dabf8c8..2481c8b 100644
--- a/runtime/vdex_file.cc
+++ b/runtime/vdex_file.cc
@@ -49,10 +49,10 @@
   DCHECK(IsVersionValid());
 }
 
-VdexFile* VdexFile::Open(const std::string& vdex_filename,
-                         bool writable,
-                         bool low_4gb,
-                         std::string* error_msg) {
+std::unique_ptr<VdexFile> VdexFile::Open(const std::string& vdex_filename,
+                                         bool writable,
+                                         bool low_4gb,
+                                         std::string* error_msg) {
   if (!OS::FileExists(vdex_filename.c_str())) {
     *error_msg = "File " + vdex_filename + " does not exist.";
     return nullptr;
@@ -79,12 +79,12 @@
   return Open(vdex_file->Fd(), vdex_length, vdex_filename, writable, low_4gb, error_msg);
 }
 
-VdexFile* VdexFile::Open(int file_fd,
-                         size_t vdex_length,
-                         const std::string& vdex_filename,
-                         bool writable,
-                         bool low_4gb,
-                         std::string* error_msg) {
+std::unique_ptr<VdexFile> VdexFile::Open(int file_fd,
+                                         size_t vdex_length,
+                                         const std::string& vdex_filename,
+                                         bool writable,
+                                         bool low_4gb,
+                                         std::string* error_msg) {
   std::unique_ptr<MemMap> mmap(MemMap::MapFile(vdex_length,
                                                writable ? PROT_READ | PROT_WRITE : PROT_READ,
                                                MAP_SHARED,
@@ -98,8 +98,14 @@
     return nullptr;
   }
 
+  std::unique_ptr<VdexFile> vdex(new VdexFile(mmap.release()));
+  if (!vdex->IsValid()) {
+    *error_msg = "Vdex file is not valid";
+    return nullptr;
+  }
+
   *error_msg = "Success";
-  return new VdexFile(mmap.release());
+  return vdex;
 }
 
 const uint8_t* VdexFile::GetNextDexFileData(const uint8_t* cursor) const {
diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h
index 330b955..7daf2f8 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -73,17 +73,19 @@
 
   typedef uint32_t VdexChecksum;
 
-  static VdexFile* Open(const std::string& vdex_filename,
-                        bool writable,
-                        bool low_4gb,
-                        std::string* error_msg);
+  // Returns nullptr if the vdex file cannot be opened or is not valid.
+  static std::unique_ptr<VdexFile> Open(const std::string& vdex_filename,
+                                        bool writable,
+                                        bool low_4gb,
+                                        std::string* error_msg);
 
-  static VdexFile* Open(int file_fd,
-                        size_t vdex_length,
-                        const std::string& vdex_filename,
-                        bool writable,
-                        bool low_4gb,
-                        std::string* error_msg);
+  // Returns nullptr if the vdex file cannot be opened or is not valid.
+  static std::unique_ptr<VdexFile> Open(int file_fd,
+                                        size_t vdex_length,
+                                        const std::string& vdex_filename,
+                                        bool writable,
+                                        bool low_4gb,
+                                        std::string* error_msg);
 
   const uint8_t* Begin() const { return mmap_->Begin(); }
   const uint8_t* End() const { return mmap_->End(); }
diff --git a/runtime/vdex_file_test.cc b/runtime/vdex_file_test.cc
new file mode 100644
index 0000000..909e117
--- /dev/null
+++ b/runtime/vdex_file_test.cc
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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 "vdex_file.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "common_runtime_test.h"
+
+namespace art {
+
+class VdexFileTest : public CommonRuntimeTest {
+};
+
+TEST_F(VdexFileTest, OpenEmptyVdex) {
+  // Verify we fail to open an empty vdex file.
+  ScratchFile tmp;
+  std::string error_msg;
+  std::unique_ptr<VdexFile> vdex = VdexFile::Open(tmp.GetFd(),
+                                                  0,
+                                                  tmp.GetFilename(),
+                                                  /*writable*/false,
+                                                  /*low_4gb*/false,
+                                                  &error_msg);
+  EXPECT_TRUE(vdex == nullptr);
+
+  vdex = VdexFile::Open(tmp.GetFilename(), /*writable*/false, /*low_4gb*/false, &error_msg);
+  EXPECT_TRUE(vdex == nullptr);
+}
+
+}  // namespace art