ART: Native support for multidex

Native support for zip files with multiple classesX.dex.

Works by explicitly looking for those files in ascending order. As
these files have no file system representation for themselves,
introduce synthetic dex locations: the name of the originating file
plus a colon plus the name of the dex file, e.g., test.jar:classes2.dex.

Opening a zip dex file will return all dex files in this way. This
keeps the changes to dex2oat minimal.

To hide multidex/synthetic names from the Java layer, let the handle
of dalvik.system.DexFile refer to a vector of DexFile objects. When
opening a location, test possible synthetic names and add them to the
vector. Thus, the original multidex jar in the classpath will be
associated with all embedded dex files.

Change-Id: I0de107e1369cbc94416c544aca3b17525c9eac8b
diff --git a/compiler/image_test.cc b/compiler/image_test.cc
index e8bbaef..d52ec0a 100644
--- a/compiler/image_test.cc
+++ b/compiler/image_test.cc
@@ -129,11 +129,7 @@
   runtime_.reset();
   java_lang_dex_file_ = NULL;
 
-  std::string error_msg;
-  std::unique_ptr<const DexFile> dex(DexFile::Open(GetLibCoreDexFileName().c_str(),
-                                             GetLibCoreDexFileName().c_str(),
-                                             &error_msg));
-  ASSERT_TRUE(dex.get() != nullptr) << error_msg;
+  std::unique_ptr<const DexFile> dex(LoadExpectSingleDexFile(GetLibCoreDexFileName().c_str()));
 
   // Remove the reservation of the memory for use to load the image.
   UnreserveImageSpace();
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 2d55140..de2960c 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -478,11 +478,8 @@
         continue;
       }
       std::string error_msg;
-      const DexFile* dex_file = DexFile::Open(parsed[i].c_str(), parsed[i].c_str(), &error_msg);
-      if (dex_file == nullptr) {
+      if (!DexFile::Open(parsed[i].c_str(), parsed[i].c_str(), &error_msg, &dex_files)) {
         LOG(WARNING) << "Failed to open dex file '" << parsed[i] << "': " << error_msg;
-      } else {
-        dex_files.push_back(dex_file);
       }
     }
   }
@@ -536,12 +533,9 @@
       LOG(WARNING) << "Skipping non-existent dex file '" << dex_filename << "'";
       continue;
     }
-    const DexFile* dex_file = DexFile::Open(dex_filename, dex_location, &error_msg);
-    if (dex_file == nullptr) {
+    if (!DexFile::Open(dex_filename, dex_location, &error_msg, &dex_files)) {
       LOG(WARNING) << "Failed to open .dex from file '" << dex_filename << "': " << error_msg;
       ++failure_count;
-    } else {
-      dex_files.push_back(dex_file);
     }
     ATRACE_END();
   }
@@ -1319,13 +1313,11 @@
             << error_msg;
         return EXIT_FAILURE;
       }
-      const DexFile* dex_file = DexFile::Open(*zip_archive.get(), zip_location, &error_msg);
-      if (dex_file == nullptr) {
+      if (!DexFile::OpenFromZip(*zip_archive.get(), zip_location, &error_msg, &dex_files)) {
         LOG(ERROR) << "Failed to open dex from file descriptor for zip file '" << zip_location
             << "': " << error_msg;
         return EXIT_FAILURE;
       }
-      dex_files.push_back(dex_file);
       ATRACE_END();
     } else {
       size_t failure_count = OpenDexFiles(dex_filenames, dex_locations, dex_files);
diff --git a/runtime/base/scoped_flock.cc b/runtime/base/scoped_flock.cc
index c0bce84..351de3d 100644
--- a/runtime/base/scoped_flock.cc
+++ b/runtime/base/scoped_flock.cc
@@ -63,6 +63,10 @@
   return file_.get();
 }
 
+bool ScopedFlock::HasFile() {
+  return file_.get() != nullptr;
+}
+
 ScopedFlock::ScopedFlock() { }
 
 ScopedFlock::~ScopedFlock() {
diff --git a/runtime/base/scoped_flock.h b/runtime/base/scoped_flock.h
index 26b4eb0..f8ed805 100644
--- a/runtime/base/scoped_flock.h
+++ b/runtime/base/scoped_flock.h
@@ -40,6 +40,10 @@
 
   // Returns the (locked) file associated with this instance.
   File* GetFile();
+
+  // Returns whether a file is held.
+  bool HasFile();
+
   ~ScopedFlock();
  private:
   std::unique_ptr<File> file_;
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 61f94d4..60453c3 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -634,15 +634,22 @@
 const OatFile* ClassLinker::FindOpenedOatFileForDexFile(const DexFile& dex_file) {
   const char* dex_location = dex_file.GetLocation().c_str();
   uint32_t dex_location_checksum = dex_file.GetLocationChecksum();
-  return FindOpenedOatFileFromDexLocation(dex_location, &dex_location_checksum);
+  return FindOpenedOatFile(nullptr, dex_location, &dex_location_checksum);
 }
 
-const OatFile* ClassLinker::FindOpenedOatFileFromDexLocation(
-    const char* dex_location, const uint32_t* const dex_location_checksum) {
+const OatFile* ClassLinker::FindOpenedOatFile(const char* oat_location, const char* dex_location,
+                                              const uint32_t* const dex_location_checksum) {
   ReaderMutexLock mu(Thread::Current(), dex_lock_);
   for (size_t i = 0; i < oat_files_.size(); i++) {
     const OatFile* oat_file = oat_files_[i];
     DCHECK(oat_file != NULL);
+
+    if (oat_location != nullptr) {
+      if (oat_file->GetLocation() != oat_location) {
+        continue;
+      }
+    }
+
     const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location,
                                                                       dex_location_checksum,
                                                                       false);
@@ -653,10 +660,229 @@
   return NULL;
 }
 
-const DexFile* ClassLinker::FindDexFileInOatLocation(const char* dex_location,
-                                                     uint32_t dex_location_checksum,
-                                                     const char* oat_location,
-                                                     std::string* error_msg) {
+static std::string GetMultiDexClassesDexName(size_t number, const char* dex_location) {
+  if (number == 0) {
+    return dex_location;
+  } else {
+    return StringPrintf("%s" kMultiDexSeparatorString "classes%zu.dex", dex_location, number + 1);
+  }
+}
+
+static bool LoadMultiDexFilesFromOatFile(const OatFile* oat_file, const char* dex_location,
+                                         bool generated,
+                                         std::vector<std::string>* error_msgs,
+                                         std::vector<const DexFile*>* dex_files) {
+  if (oat_file == nullptr) {
+    return false;
+  }
+
+  size_t old_size = dex_files->size();  // To rollback on error.
+
+  bool success = true;
+  for (size_t i = 0; success; ++i) {
+    std::string next_name_str = GetMultiDexClassesDexName(i, dex_location);
+    const char* next_name = next_name_str.c_str();
+
+    uint32_t dex_location_checksum;
+    uint32_t* dex_location_checksum_pointer = &dex_location_checksum;
+    std::string error_msg;
+    if (!DexFile::GetChecksum(next_name, dex_location_checksum_pointer, &error_msg)) {
+      DCHECK_EQ(false, i == 0 && generated);
+      dex_location_checksum_pointer = nullptr;
+    }
+
+    const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(next_name, nullptr, false);
+
+    if (oat_dex_file == nullptr) {
+      if (i == 0 && generated) {
+        std::string error_msg;
+        error_msg = StringPrintf("\nFailed to find dex file '%s' (checksum 0x%x) in generated out "
+                                 " file'%s'", dex_location, dex_location_checksum,
+                                 oat_file->GetLocation().c_str());
+        error_msgs->push_back(error_msg);
+      }
+      break;  // Not found, done.
+    }
+
+    // Checksum test. Test must succeed when generated.
+    success = !generated;
+    if (dex_location_checksum_pointer != nullptr) {
+      success = dex_location_checksum == oat_dex_file->GetDexFileLocationChecksum();
+    }
+
+    if (success) {
+      const DexFile* dex_file = oat_dex_file->OpenDexFile(&error_msg);
+      if (dex_file == nullptr) {
+        success = false;
+        error_msgs->push_back(error_msg);
+      } else {
+        dex_files->push_back(dex_file);
+      }
+    }
+
+    // When we generated the file, we expect success, or something is terribly wrong.
+    CHECK_EQ(false, generated && !success)
+        << "dex_location=" << next_name << " oat_location=" << oat_file->GetLocation().c_str()
+        << std::hex << " dex_location_checksum=" << dex_location_checksum
+        << " OatDexFile::GetLocationChecksum()=" << oat_dex_file->GetDexFileLocationChecksum();
+  }
+
+  if (dex_files->size() == old_size) {
+    success = false;  // We did not even find classes.dex
+  }
+
+  if (success) {
+    return true;
+  } else {
+    // Free all the dex files we have loaded.
+    auto it = dex_files->begin() + old_size;
+    auto it_end = dex_files->end();
+    for (; it != it_end; it++) {
+      delete *it;
+    }
+    dex_files->erase(dex_files->begin() + old_size, it_end);
+
+    return false;
+  }
+}
+
+// Multidex files make it possible that some, but not all, dex files can be broken/outdated. This
+// complicates the loading process, as we should not use an iterative loading process, because that
+// would register the oat file and dex files that come before the broken one. Instead, check all
+// multidex ahead of time.
+bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location,
+                                      std::vector<std::string>* error_msgs,
+                                      std::vector<const DexFile*>* dex_files) {
+  // 1) Check whether we have an open oat file.
+  // This requires a dex checksum, use the "primary" one.
+  uint32_t dex_location_checksum;
+  uint32_t* dex_location_checksum_pointer = &dex_location_checksum;
+  bool have_checksum = true;
+  std::string checksum_error_msg;
+  if (!DexFile::GetChecksum(dex_location, dex_location_checksum_pointer, &checksum_error_msg)) {
+    dex_location_checksum_pointer = nullptr;
+    have_checksum = false;
+  }
+
+  bool needs_registering = false;
+
+  std::unique_ptr<const OatFile> open_oat_file(FindOpenedOatFile(oat_location, dex_location,
+                                                                 dex_location_checksum_pointer));
+
+  // 2) If we do not have an open one, maybe there's one on disk already.
+
+  // In case the oat file is not open, we play a locking game here so
+  // that if two different processes race to load and register or generate
+  // (or worse, one tries to open a partial generated file) we will be okay.
+  // This is actually common with apps that use DexClassLoader to work
+  // around the dex method reference limit and that have a background
+  // service running in a separate process.
+  ScopedFlock scoped_flock;
+
+  if (open_oat_file.get() == nullptr) {
+    if (oat_location != nullptr) {
+      // Can only do this if we have a checksum, else error.
+      if (!have_checksum) {
+        error_msgs->push_back(checksum_error_msg);
+        return false;
+      }
+
+      std::string error_msg;
+
+      // We are loading or creating one in the future. Time to set up the file lock.
+      if (!scoped_flock.Init(oat_location, &error_msg)) {
+        error_msgs->push_back(error_msg);
+        return false;
+      }
+
+      open_oat_file.reset(FindOatFileInOatLocationForDexFile(dex_location, dex_location_checksum,
+                                                             oat_location, &error_msg));
+
+      if (open_oat_file.get() == nullptr) {
+        std::string compound_msg = StringPrintf("Failed to find dex file '%s' in oat location '%s': %s",
+                                                dex_location, oat_location, error_msg.c_str());
+        VLOG(class_linker) << compound_msg;
+        error_msgs->push_back(compound_msg);
+      }
+    } else {
+      // TODO: What to lock here?
+      open_oat_file.reset(FindOatFileContainingDexFileFromDexLocation(dex_location,
+                                                                      dex_location_checksum_pointer,
+                                                                      kRuntimeISA, error_msgs));
+    }
+    needs_registering = true;
+  }
+
+  // 3) If we have an oat file, check all contained multidex files for our dex_location.
+  // Note: LoadMultiDexFilesFromOatFile will check for nullptr in the first argument.
+  bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location, false, error_msgs,
+                                              dex_files);
+  if (success) {
+    const OatFile* oat_file = open_oat_file.release();  // Avoid deleting it.
+    if (needs_registering) {
+      // We opened the oat file, so we must register it.
+      RegisterOatFile(oat_file);
+    }
+    return true;
+  } else {
+    if (needs_registering) {
+      // We opened it, delete it.
+      open_oat_file.reset();
+    } else {
+      open_oat_file.release();  // Do not delete open oat files.
+    }
+  }
+
+  // 4) If it's not the case (either no oat file or mismatches), regenerate and load.
+
+  // Need a checksum, fail else.
+  if (!have_checksum) {
+    error_msgs->push_back(checksum_error_msg);
+    return false;
+  }
+
+  // Look in cache location if no oat_location is given.
+  std::string cache_location;
+  if (oat_location == nullptr) {
+    // Use the dalvik cache.
+    const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA)));
+    cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str());
+    oat_location = cache_location.c_str();
+  }
+
+  // Definitely need to lock now.
+  if (!scoped_flock.HasFile()) {
+    std::string error_msg;
+    if (!scoped_flock.Init(oat_location, &error_msg)) {
+      error_msgs->push_back(error_msg);
+      return false;
+    }
+  }
+
+  // Create the oat file.
+  open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),
+                                                  oat_location, error_msgs));
+
+  // Failed, bail.
+  if (open_oat_file.get() == nullptr) {
+    return false;
+  }
+
+  // Try to load again, but stronger checks.
+  success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location, true, error_msgs,
+                                         dex_files);
+  if (success) {
+    RegisterOatFile(open_oat_file.release());
+    return true;
+  } else {
+    return false;
+  }
+}
+
+const OatFile* ClassLinker::FindOatFileInOatLocationForDexFile(const char* dex_location,
+                                                               uint32_t dex_location_checksum,
+                                                               const char* oat_location,
+                                                               std::string* error_msg) {
   std::unique_ptr<OatFile> oat_file(OatFile::Open(oat_location, oat_location, NULL,
                                             !Runtime::Current()->IsCompiler(),
                                             error_msg));
@@ -699,44 +925,21 @@
                               actual_dex_checksum);
     return nullptr;
   }
-  const DexFile* dex_file = oat_dex_file->OpenDexFile(error_msg);
-  if (dex_file != nullptr) {
-    RegisterOatFile(oat_file.release());
-  }
-  return dex_file;
-}
-
-const DexFile* ClassLinker::FindOrCreateOatFileForDexLocation(
-    const char* dex_location,
-    uint32_t dex_location_checksum,
-    const char* oat_location,
-    std::vector<std::string>* error_msgs) {
-  // We play a locking game here so that if two different processes
-  // race to generate (or worse, one tries to open a partial generated
-  // file) we will be okay. This is actually common with apps that use
-  // DexClassLoader to work around the dex method reference limit and
-  // that have a background service running in a separate process.
-  ScopedFlock scoped_flock;
-  std::string error_msg;
-  if (!scoped_flock.Init(oat_location, &error_msg)) {
-    error_msgs->push_back(error_msg);
+  std::unique_ptr<const DexFile> dex_file(oat_dex_file->OpenDexFile(error_msg));
+  if (dex_file.get() != nullptr) {
+    return oat_file.release();
+  } else {
     return nullptr;
   }
+}
 
-  // Check if we already have an up-to-date output file
-  const DexFile* dex_file = FindDexFileInOatLocation(dex_location, dex_location_checksum,
-                                                     oat_location, &error_msg);
-  if (dex_file != nullptr) {
-    return dex_file;
-  }
-  std::string compound_msg = StringPrintf("Failed to find dex file '%s' in oat location '%s': %s",
-                                          dex_location, oat_location, error_msg.c_str());
-  VLOG(class_linker) << compound_msg;
-  error_msgs->push_back(compound_msg);
-
+const OatFile* ClassLinker::CreateOatFileForDexLocation(const char* dex_location,
+                                                        int fd, const char* oat_location,
+                                                        std::vector<std::string>* error_msgs) {
   // Generate the output oat file for the dex file
   VLOG(class_linker) << "Generating oat file " << oat_location << " for " << dex_location;
-  if (!GenerateOatFile(dex_location, scoped_flock.GetFile()->Fd(), oat_location, &error_msg)) {
+  std::string error_msg;
+  if (!GenerateOatFile(dex_location, fd, oat_location, &error_msg)) {
     CHECK(!error_msg.empty());
     error_msgs->push_back(error_msg);
     return nullptr;
@@ -745,27 +948,13 @@
                                             !Runtime::Current()->IsCompiler(),
                                             &error_msg));
   if (oat_file.get() == nullptr) {
-    compound_msg = StringPrintf("\nFailed to open generated oat file '%s': %s",
-                                oat_location, error_msg.c_str());
+    std::string compound_msg = StringPrintf("\nFailed to open generated oat file '%s': %s",
+                                            oat_location, error_msg.c_str());
     error_msgs->push_back(compound_msg);
     return nullptr;
   }
-  const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location,
-                                                                    &dex_location_checksum);
-  if (oat_dex_file == nullptr) {
-    error_msg = StringPrintf("\nFailed to find dex file '%s' (checksum 0x%x) in generated out file "
-                             "'%s'", dex_location, dex_location_checksum, oat_location);
-    error_msgs->push_back(error_msg);
-    return nullptr;
-  }
-  const DexFile* result = oat_dex_file->OpenDexFile(&error_msg);
-  CHECK(result != nullptr) << error_msgs << ", " << error_msg;
-  CHECK_EQ(dex_location_checksum, result->GetLocationChecksum())
-          << "dex_location=" << dex_location << " oat_location=" << oat_location << std::hex
-          << " dex_location_checksum=" << dex_location_checksum
-          << " DexFile::GetLocationChecksum()=" << result->GetLocationChecksum();
-  RegisterOatFile(oat_file.release());
-  return result;
+
+  return oat_file.release();
 }
 
 bool ClassLinker::VerifyOatFileChecksums(const OatFile* oat_file,
@@ -832,17 +1021,17 @@
   return false;
 }
 
-const DexFile* ClassLinker::VerifyAndOpenDexFileFromOatFile(const std::string& oat_file_location,
-                                                            const char* dex_location,
-                                                            std::string* error_msg,
-                                                            bool* open_failed) {
+const OatFile* ClassLinker::LoadOatFileAndVerifyDexFile(const std::string& oat_file_location,
+                                                        const char* dex_location,
+                                                        std::string* error_msg,
+                                                        bool* open_failed) {
   std::unique_ptr<const OatFile> oat_file(FindOatFileFromOatLocation(oat_file_location, error_msg));
   if (oat_file.get() == nullptr) {
     *open_failed = true;
     return nullptr;
   }
   *open_failed = false;
-  const DexFile* dex_file = nullptr;
+  std::unique_ptr<const DexFile> dex_file;
   uint32_t dex_location_checksum;
   if (!DexFile::GetChecksum(dex_location, &dex_location_checksum, error_msg)) {
     // If no classes.dex found in dex_location, it has been stripped or is corrupt, assume oat is
@@ -855,49 +1044,38 @@
                                 error_msg->c_str());
       return nullptr;
     }
-    dex_file = oat_dex_file->OpenDexFile(error_msg);
+    dex_file.reset(oat_dex_file->OpenDexFile(error_msg));
   } else {
     bool verified = VerifyOatFileChecksums(oat_file.get(), dex_location, dex_location_checksum,
                                            kRuntimeISA, error_msg);
     if (!verified) {
       return nullptr;
     }
-    dex_file = oat_file->GetOatDexFile(dex_location,
-                                       &dex_location_checksum)->OpenDexFile(error_msg);
+    dex_file.reset(oat_file->GetOatDexFile(dex_location,
+                                           &dex_location_checksum)->OpenDexFile(error_msg));
   }
-  if (dex_file != nullptr) {
-    RegisterOatFile(oat_file.release());
+
+  if (dex_file.get() != nullptr) {
+    return oat_file.release();
+  } else {
+    return nullptr;
   }
-  return dex_file;
 }
 
-const DexFile* ClassLinker::FindDexFileInOatFileFromDexLocation(
+const OatFile* ClassLinker::FindOatFileContainingDexFileFromDexLocation(
     const char* dex_location,
     const uint32_t* const dex_location_checksum,
     InstructionSet isa,
     std::vector<std::string>* error_msgs) {
-  const OatFile* open_oat_file = FindOpenedOatFileFromDexLocation(dex_location,
-                                                                  dex_location_checksum);
-  if (open_oat_file != nullptr) {
-    const OatFile::OatDexFile* oat_dex_file = open_oat_file->GetOatDexFile(dex_location,
-                                                                           dex_location_checksum);
-    std::string error_msg;
-    const DexFile* ret = oat_dex_file->OpenDexFile(&error_msg);
-    if (ret == nullptr) {
-      error_msgs->push_back(error_msg);
-    }
-    return ret;
-  }
-
   // Look for an existing file next to dex. for example, for
   // /foo/bar/baz.jar, look for /foo/bar/<isa>/baz.odex.
   std::string odex_filename(DexFilenameToOdexFilename(dex_location, isa));
   bool open_failed;
   std::string error_msg;
-  const DexFile* dex_file = VerifyAndOpenDexFileFromOatFile(odex_filename, dex_location,
-                                                            &error_msg, &open_failed);
-  if (dex_file != nullptr) {
-    return dex_file;
+  const OatFile* oat_file = LoadOatFileAndVerifyDexFile(odex_filename, dex_location, &error_msg,
+                                                        &open_failed);
+  if (oat_file != nullptr) {
+    return oat_file;
   }
   if (dex_location_checksum == nullptr) {
     error_msgs->push_back(StringPrintf("Failed to open oat file from %s and no classes.dex found in"
@@ -910,10 +1088,10 @@
   const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA)));
   std::string cache_location(GetDalvikCacheFilenameOrDie(dex_location,
                                                          dalvik_cache.c_str()));
-  dex_file = VerifyAndOpenDexFileFromOatFile(cache_location, dex_location, &cache_error_msg,
-                                             &open_failed);
-  if (dex_file != nullptr) {
-    return dex_file;
+  oat_file = LoadOatFileAndVerifyDexFile(cache_location, dex_location, &cache_error_msg,
+                                         &open_failed);
+  if (oat_file != nullptr) {
+    return oat_file;
   }
   if (!open_failed && TEMP_FAILURE_RETRY(unlink(cache_location.c_str())) != 0) {
     PLOG(FATAL) << "Failed to remove obsolete oat file from " << cache_location;
@@ -924,9 +1102,7 @@
   VLOG(class_linker) << compound_msg;
   error_msgs->push_back(compound_msg);
 
-  // Try to generate oat file if it wasn't found or was obsolete.
-  return FindOrCreateOatFileForDexLocation(dex_location, *dex_location_checksum,
-                                           cache_location.c_str(), error_msgs);
+  return nullptr;
 }
 
 const OatFile* ClassLinker::FindOpenedOatFileFromOatLocation(const std::string& oat_location) {
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 7d7bf15..60dad7b 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -273,23 +273,12 @@
                                             std::string* error_msg)
       LOCKS_EXCLUDED(dex_lock_);
 
-  // Finds the oat file for a dex location, generating the oat file if
-  // it is missing or out of date. Returns the DexFile from within the
-  // created oat file.
-  const DexFile* FindOrCreateOatFileForDexLocation(const char* dex_location,
-                                                   uint32_t dex_location_checksum,
-                                                   const char* oat_location,
-                                                   std::vector<std::string>* error_msgs)
+  // Find or create the oat file holding dex_location. Then load all corresponding dex files
+  // (if multidex) into the given vector.
+  bool OpenDexFilesFromOat(const char* dex_location, const char* oat_location,
+                           std::vector<std::string>* error_msgs,
+                           std::vector<const DexFile*>* dex_files)
       LOCKS_EXCLUDED(dex_lock_, Locks::mutator_lock_);
-  // Find a DexFile within an OatFile given a DexFile location. Note
-  // that this returns null if the location checksum of the DexFile
-  // does not match the OatFile.
-  const DexFile* FindDexFileInOatFileFromDexLocation(const char* location,
-                                                     const uint32_t* const location_checksum,
-                                                     InstructionSet isa,
-                                                     std::vector<std::string>* error_msgs)
-      LOCKS_EXCLUDED(dex_lock_, Locks::mutator_lock_);
-
 
   // Returns true if oat file contains the dex file with the given location and checksum.
   static bool VerifyOatFileChecksums(const OatFile* oat_file,
@@ -545,21 +534,47 @@
   const OatFile* FindOpenedOatFileForDexFile(const DexFile& dex_file)
       LOCKS_EXCLUDED(dex_lock_)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  const OatFile* FindOpenedOatFileFromDexLocation(const char* dex_location,
-                                                  const uint32_t* const dex_location_checksum)
+
+  // Find an opened oat file that contains dex_location. If oat_location is not nullptr, the file
+  // must have that location, else any oat location is accepted.
+  const OatFile* FindOpenedOatFile(const char* oat_location, const char* dex_location,
+                                   const uint32_t* const dex_location_checksum)
       LOCKS_EXCLUDED(dex_lock_);
   const OatFile* FindOpenedOatFileFromOatLocation(const std::string& oat_location)
       LOCKS_EXCLUDED(dex_lock_);
-  const DexFile* FindDexFileInOatLocation(const char* dex_location,
-                                          uint32_t dex_location_checksum,
-                                          const char* oat_location,
-                                          std::string* error_msg)
+
+  // Note: will not register the oat file.
+  const OatFile* FindOatFileInOatLocationForDexFile(const char* dex_location,
+                                                    uint32_t dex_location_checksum,
+                                                    const char* oat_location,
+                                                    std::string* error_msg)
       LOCKS_EXCLUDED(dex_lock_);
 
-  const DexFile* VerifyAndOpenDexFileFromOatFile(const std::string& oat_file_location,
-                                                 const char* dex_location,
-                                                 std::string* error_msg,
-                                                 bool* open_failed)
+  // Creates the oat file from the dex_location to the oat_location. Needs a file descriptor for
+  // the file to be written, which is assumed to be under a lock.
+  const OatFile* CreateOatFileForDexLocation(const char* dex_location,
+                                             int fd, const char* oat_location,
+                                             std::vector<std::string>* error_msgs)
+      LOCKS_EXCLUDED(dex_lock_, Locks::mutator_lock_);
+
+  // Finds an OatFile that contains a DexFile for the given a DexFile location.
+  //
+  // Note 1: this will not check open oat files, which are assumed to be stale when this is run.
+  // Note 2: Does not register the oat file. It is the caller's job to register if the file is to
+  //         be kept.
+  const OatFile* FindOatFileContainingDexFileFromDexLocation(const char* location,
+                                                             const uint32_t* const location_checksum,
+                                                             InstructionSet isa,
+                                                             std::vector<std::string>* error_msgs)
+      LOCKS_EXCLUDED(dex_lock_, Locks::mutator_lock_);
+
+  // Find a verify an oat file with the given dex file. Will return nullptr when the oat file
+  // was not found or the dex file could not be verified.
+  // Note: Does not register the oat file.
+  const OatFile* LoadOatFileAndVerifyDexFile(const std::string& oat_file_location,
+                                             const char* dex_location,
+                                             std::string* error_msg,
+                                             bool* open_failed)
       LOCKS_EXCLUDED(dex_lock_);
 
   mirror::ArtMethod* CreateProxyConstructor(Thread* self, Handle<mirror::Class> klass,
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index 044d08b..1df4c05 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -156,6 +156,18 @@
     return !kIsTargetBuild;
   }
 
+  const DexFile* LoadExpectSingleDexFile(const char* location) {
+    std::vector<const DexFile*> dex_files;
+    std::string error_msg;
+    if (!DexFile::Open(location, location, &error_msg, &dex_files)) {
+      LOG(FATAL) << "Could not open .dex file '" << location << "': " << error_msg << "\n";
+      return nullptr;
+    } else {
+      CHECK_EQ(1U, dex_files.size()) << "Expected only one dex file in " << location;
+      return dex_files[0];
+    }
+  }
+
   virtual void SetUp() {
     SetEnvironmentVariables(android_data_);
     dalvik_cache_.append(android_data_.c_str());
@@ -164,12 +176,7 @@
     ASSERT_EQ(mkdir_result, 0);
 
     std::string error_msg;
-    java_lang_dex_file_ = DexFile::Open(GetLibCoreDexFileName().c_str(),
-                                        GetLibCoreDexFileName().c_str(), &error_msg);
-    if (java_lang_dex_file_ == nullptr) {
-      LOG(FATAL) << "Could not open .dex file '" << GetLibCoreDexFileName() << "': "
-          << error_msg << "\n";
-    }
+    java_lang_dex_file_ = LoadExpectSingleDexFile(GetLibCoreDexFileName().c_str());
     boot_class_path_.push_back(java_lang_dex_file_);
 
     std::string min_heap_string(StringPrintf("-Xms%zdm", gc::Heap::kDefaultInitialSize / MB));
@@ -233,7 +240,7 @@
     // There's a function to clear the array, but it's not public...
     typedef void (*IcuCleanupFn)();
     void* sym = dlsym(RTLD_DEFAULT, "u_cleanup_" U_ICU_VERSION_SHORT);
-    CHECK(sym != nullptr);
+    CHECK(sym != nullptr) << dlerror();
     IcuCleanupFn icu_cleanup_fn = reinterpret_cast<IcuCleanupFn>(sym);
     (*icu_cleanup_fn)();
 
@@ -264,7 +271,8 @@
     return GetAndroidRoot();
   }
 
-  const DexFile* OpenTestDexFile(const char* name) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  std::vector<const DexFile*> OpenTestDexFiles(const char* name)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     CHECK(name != nullptr);
     std::string filename;
     if (IsHost()) {
@@ -277,26 +285,36 @@
     filename += name;
     filename += ".jar";
     std::string error_msg;
-    const DexFile* dex_file = DexFile::Open(filename.c_str(), filename.c_str(), &error_msg);
-    CHECK(dex_file != nullptr) << "Failed to open '" << filename << "': " << error_msg;
-    CHECK_EQ(PROT_READ, dex_file->GetPermissions());
-    CHECK(dex_file->IsReadOnly());
-    opened_dex_files_.push_back(dex_file);
-    return dex_file;
+    std::vector<const DexFile*> dex_files;
+    bool success = DexFile::Open(filename.c_str(), filename.c_str(), &error_msg, &dex_files);
+    CHECK(success) << "Failed to open '" << filename << "': " << error_msg;
+    for (const DexFile* dex_file : dex_files) {
+      CHECK_EQ(PROT_READ, dex_file->GetPermissions());
+      CHECK(dex_file->IsReadOnly());
+    }
+    opened_dex_files_.insert(opened_dex_files_.end(), dex_files.begin(), dex_files.end());
+    return dex_files;
+  }
+
+  const DexFile* OpenTestDexFile(const char* name)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    std::vector<const DexFile*> vector = OpenTestDexFiles(name);
+    EXPECT_EQ(1U, vector.size());
+    return vector[0];
   }
 
   jobject LoadDex(const char* dex_name) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    const DexFile* dex_file = OpenTestDexFile(dex_name);
-    CHECK(dex_file != nullptr);
-    class_linker_->RegisterDexFile(*dex_file);
-    std::vector<const DexFile*> class_path;
-    class_path.push_back(dex_file);
+    std::vector<const DexFile*> dex_files = OpenTestDexFiles(dex_name);
+    CHECK_NE(0U, dex_files.size());
+    for (const DexFile* dex_file : dex_files) {
+      class_linker_->RegisterDexFile(*dex_file);
+    }
     ScopedObjectAccessUnchecked soa(Thread::Current());
     ScopedLocalRef<jobject> class_loader_local(soa.Env(),
         soa.Env()->AllocObject(WellKnownClasses::dalvik_system_PathClassLoader));
     jobject class_loader = soa.Env()->NewGlobalRef(class_loader_local.get());
     soa.Self()->SetClassLoaderOverride(soa.Decode<mirror::ClassLoader*>(class_loader_local.get()));
-    Runtime::Current()->SetCompileTimeClassPath(class_loader, class_path);
+    Runtime::Current()->SetCompileTimeClassPath(class_loader, dex_files);
     return class_loader;
   }
 
diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc
index 10f34d9..d368e41 100644
--- a/runtime/dex_file.cc
+++ b/runtime/dex_file.cc
@@ -87,7 +87,21 @@
 bool DexFile::GetChecksum(const char* filename, uint32_t* checksum, std::string* error_msg) {
   CHECK(checksum != NULL);
   uint32_t magic;
-  ScopedFd fd(OpenAndReadMagic(filename, &magic, error_msg));
+
+  // Strip ":...", which is the location
+  const char* zip_entry_name = kClassesDex;
+  const char* file_part = filename;
+  std::unique_ptr<const char> file_part_ptr;
+
+
+  if (IsMultiDexLocation(filename)) {
+    std::pair<const char*, const char*> pair = SplitMultiDexLocation(filename);
+    file_part_ptr.reset(pair.first);
+    file_part = pair.first;
+    zip_entry_name = pair.second;
+  }
+
+  ScopedFd fd(OpenAndReadMagic(file_part, &magic, error_msg));
   if (fd.get() == -1) {
     DCHECK(!error_msg->empty());
     return false;
@@ -95,13 +109,13 @@
   if (IsZipMagic(magic)) {
     std::unique_ptr<ZipArchive> zip_archive(ZipArchive::OpenFromFd(fd.release(), filename, error_msg));
     if (zip_archive.get() == NULL) {
-      *error_msg = StringPrintf("Failed to open zip archive '%s'", filename);
+      *error_msg = StringPrintf("Failed to open zip archive '%s'", file_part);
       return false;
     }
-    std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(kClassesDex, error_msg));
+    std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(zip_entry_name, error_msg));
     if (zip_entry.get() == NULL) {
-      *error_msg = StringPrintf("Zip archive '%s' doesn't contain %s (error msg: %s)", filename,
-                                kClassesDex, error_msg->c_str());
+      *error_msg = StringPrintf("Zip archive '%s' doesn't contain %s (error msg: %s)", file_part,
+                                zip_entry_name, error_msg->c_str());
       return false;
     }
     *checksum = zip_entry->GetCrc32();
@@ -119,20 +133,26 @@
   return false;
 }
 
-const DexFile* DexFile::Open(const char* filename,
-                             const char* location,
-                             std::string* error_msg) {
+bool DexFile::Open(const char* filename, const char* location, std::string* error_msg,
+                   std::vector<const DexFile*>* dex_files) {
   uint32_t magic;
   ScopedFd fd(OpenAndReadMagic(filename, &magic, error_msg));
   if (fd.get() == -1) {
     DCHECK(!error_msg->empty());
-    return NULL;
+    return false;
   }
   if (IsZipMagic(magic)) {
-    return DexFile::OpenZip(fd.release(), location, error_msg);
+    return DexFile::OpenZip(fd.release(), location, error_msg, dex_files);
   }
   if (IsDexMagic(magic)) {
-    return DexFile::OpenFile(fd.release(), location, true, error_msg);
+    std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.release(), location, true,
+                                                              error_msg));
+    if (dex_file.get() != nullptr) {
+      dex_files->push_back(dex_file.release());
+      return true;
+    } else {
+      return false;
+    }
   }
   *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename);
   return nullptr;
@@ -217,13 +237,14 @@
 
 const char* DexFile::kClassesDex = "classes.dex";
 
-const DexFile* DexFile::OpenZip(int fd, const std::string& location, std::string* error_msg) {
+bool DexFile::OpenZip(int fd, const std::string& location, std::string* error_msg,
+                      std::vector<const  DexFile*>* dex_files) {
   std::unique_ptr<ZipArchive> zip_archive(ZipArchive::OpenFromFd(fd, location.c_str(), error_msg));
   if (zip_archive.get() == nullptr) {
     DCHECK(!error_msg->empty());
-    return nullptr;
+    return false;
   }
-  return DexFile::Open(*zip_archive, location, error_msg);
+  return DexFile::OpenFromZip(*zip_archive, location, error_msg, dex_files);
 }
 
 const DexFile* DexFile::OpenMemory(const std::string& location,
@@ -238,17 +259,20 @@
                     error_msg);
 }
 
-const DexFile* DexFile::Open(const ZipArchive& zip_archive, const std::string& location,
-                             std::string* error_msg) {
+const DexFile* DexFile::Open(const ZipArchive& zip_archive, const char* entry_name,
+                             const std::string& location, std::string* error_msg,
+                             ZipOpenErrorCode* error_code) {
   CHECK(!location.empty());
-  std::unique_ptr<ZipEntry> zip_entry(zip_archive.Find(kClassesDex, error_msg));
+  std::unique_ptr<ZipEntry> zip_entry(zip_archive.Find(entry_name, error_msg));
   if (zip_entry.get() == NULL) {
+    *error_code = ZipOpenErrorCode::kEntryNotFound;
     return nullptr;
   }
-  std::unique_ptr<MemMap> map(zip_entry->ExtractToMemMap(location.c_str(), kClassesDex, error_msg));
+  std::unique_ptr<MemMap> map(zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg));
   if (map.get() == NULL) {
-    *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", kClassesDex, location.c_str(),
+    *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", entry_name, location.c_str(),
                               error_msg->c_str());
+    *error_code = ZipOpenErrorCode::kExtractToMemoryError;
     return nullptr;
   }
   std::unique_ptr<const DexFile> dex_file(OpenMemory(location, zip_entry->GetCrc32(), map.release(),
@@ -256,20 +280,63 @@
   if (dex_file.get() == nullptr) {
     *error_msg = StringPrintf("Failed to open dex file '%s' from memory: %s", location.c_str(),
                               error_msg->c_str());
+    *error_code = ZipOpenErrorCode::kDexFileError;
     return nullptr;
   }
   if (!dex_file->DisableWrite()) {
     *error_msg = StringPrintf("Failed to make dex file '%s' read only", location.c_str());
+    *error_code = ZipOpenErrorCode::kMakeReadOnlyError;
     return nullptr;
   }
   CHECK(dex_file->IsReadOnly()) << location;
   if (!DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(),
                                location.c_str(), error_msg)) {
+    *error_code = ZipOpenErrorCode::kVerifyError;
     return nullptr;
   }
+  *error_code = ZipOpenErrorCode::kNoError;
   return dex_file.release();
 }
 
+bool DexFile::OpenFromZip(const ZipArchive& zip_archive, const std::string& location,
+                          std::string* error_msg, std::vector<const DexFile*>* dex_files) {
+  ZipOpenErrorCode error_code;
+  std::unique_ptr<const DexFile> dex_file(Open(zip_archive, kClassesDex, location, error_msg,
+                                               &error_code));
+  if (dex_file.get() == nullptr) {
+    return false;
+  } else {
+    // Had at least classes.dex.
+    dex_files->push_back(dex_file.release());
+
+    // Now try some more.
+    size_t i = 2;
+
+    // We could try to avoid std::string allocations by working on a char array directly. As we
+    // do not expect a lot of iterations, this seems too involved and brittle.
+
+    while (i < 100) {
+      std::string name = StringPrintf("classes%zu.dex", i);
+      std::string fake_location = location + ":" + name;
+      std::unique_ptr<const DexFile> next_dex_file(Open(zip_archive, name.c_str(), fake_location,
+                                                        error_msg, &error_code));
+      if (next_dex_file.get() == nullptr) {
+        if (error_code != ZipOpenErrorCode::kEntryNotFound) {
+          LOG(WARNING) << error_msg;
+        }
+        break;
+      } else {
+        dex_files->push_back(next_dex_file.release());
+      }
+
+      i++;
+    }
+
+    return true;
+  }
+}
+
+
 const DexFile* DexFile::OpenMemory(const byte* base,
                                    size_t size,
                                    const std::string& location,
@@ -865,6 +932,25 @@
   }
 }
 
+bool DexFile::IsMultiDexLocation(const char* location) {
+  return strrchr(location, kMultiDexSeparator) != nullptr;
+}
+
+std::pair<const char*, const char*> DexFile::SplitMultiDexLocation(
+    const char* location) {
+  const char* colon_ptr = strrchr(location, kMultiDexSeparator);
+
+  // Check it's synthetic.
+  CHECK_NE(colon_ptr, static_cast<const char*>(nullptr));
+
+  size_t colon_index = colon_ptr - location;
+  char* tmp = new char[colon_index + 1];
+  strncpy(tmp, location, colon_index);
+  tmp[colon_index] = 0;
+
+  return std::make_pair(tmp, colon_ptr + 1);
+}
+
 std::ostream& operator<<(std::ostream& os, const DexFile& dex_file) {
   os << StringPrintf("[DexFile: %s dex-checksum=%08x location-checksum=%08x %p-%p]",
                      dex_file.GetLocation().c_str(),
diff --git a/runtime/dex_file.h b/runtime/dex_file.h
index 8270a2b..04f1cc1 100644
--- a/runtime/dex_file.h
+++ b/runtime/dex_file.h
@@ -63,6 +63,13 @@
   // The value of an invalid index.
   static const uint16_t kDexNoIndex16 = 0xFFFF;
 
+  // The separator charactor in MultiDex locations.
+  static constexpr char kMultiDexSeparator = ':';
+
+  // A string version of the previous. This is a define so that we can merge string literals in the
+  // preprocessor.
+  #define kMultiDexSeparatorString ":"
+
   // Raw header_item.
   struct Header {
     uint8_t magic_[8];
@@ -352,8 +359,9 @@
   // Return true if the checksum could be found, false otherwise.
   static bool GetChecksum(const char* filename, uint32_t* checksum, std::string* error_msg);
 
-  // Opens .dex file, guessing the container format based on file extension
-  static const DexFile* Open(const char* filename, const char* location, std::string* error_msg);
+  // Opens .dex files found in the container, guessing the container format based on file extension.
+  static bool Open(const char* filename, const char* location, std::string* error_msg,
+                   std::vector<const DexFile*>* dex_files);
 
   // Opens .dex file, backed by existing memory
   static const DexFile* Open(const uint8_t* base, size_t size,
@@ -363,9 +371,9 @@
     return OpenMemory(base, size, location, location_checksum, NULL, error_msg);
   }
 
-  // Opens .dex file from the classes.dex in a zip archive
-  static const DexFile* Open(const ZipArchive& zip_archive, const std::string& location,
-                             std::string* error_msg);
+  // Open all classesXXX.dex files from a zip archive.
+  static bool OpenFromZip(const ZipArchive& zip_archive, const std::string& location,
+                          std::string* error_msg, std::vector<const DexFile*>* dex_files);
 
   // Closes a .dex file.
   virtual ~DexFile();
@@ -823,8 +831,24 @@
   // Opens a .dex file
   static const DexFile* OpenFile(int fd, const char* location, bool verify, std::string* error_msg);
 
-  // Opens a dex file from within a .jar, .zip, or .apk file
-  static const DexFile* OpenZip(int fd, const std::string& location, std::string* error_msg);
+  // Opens dex files from within a .jar, .zip, or .apk file
+  static bool OpenZip(int fd, const std::string& location, std::string* error_msg,
+                      std::vector<const DexFile*>* dex_files);
+
+  enum class ZipOpenErrorCode {  // private
+    kNoError,
+    kEntryNotFound,
+    kExtractToMemoryError,
+    kDexFileError,
+    kMakeReadOnlyError,
+    kVerifyError
+  };
+
+  // Opens .dex file from the entry_name in a zip archive. error_code is undefined when non-nullptr
+  // return.
+  static const DexFile* Open(const ZipArchive& zip_archive, const char* entry_name,
+                             const std::string& location, std::string* error_msg,
+                             ZipOpenErrorCode* error_code);
 
   // Opens a .dex file at the given address backed by a MemMap
   static const DexFile* OpenMemory(const std::string& location,
@@ -855,6 +879,18 @@
       DexDebugNewPositionCb position_cb, DexDebugNewLocalCb local_cb,
       void* context, const byte* stream, LocalInfo* local_in_reg) const;
 
+  // Check whether a location denotes a multidex dex file. This is a very simple check: returns
+  // whether the string contains the separator character.
+  static bool IsMultiDexLocation(const char* location);
+
+  // Splits a multidex location at the last separator character. The second component is a pointer
+  // to the character after the separator. The first is a copy of the substring up to the separator.
+  //
+  // Note: It's the caller's job to free the first component of the returned pair.
+  // Bug 15313523: gcc/libc++ don't allow a unique_ptr for the first component
+  static std::pair<const char*, const char*> SplitMultiDexLocation(const char* location);
+
+
   // The base address of the memory mapping.
   const byte* const begin_;
 
diff --git a/runtime/dex_file_test.cc b/runtime/dex_file_test.cc
index a814c34..c1e00fc 100644
--- a/runtime/dex_file_test.cc
+++ b/runtime/dex_file_test.cc
@@ -146,8 +146,11 @@
   // read dex file
   ScopedObjectAccess soa(Thread::Current());
   std::string error_msg;
-  const DexFile* dex_file = DexFile::Open(location, location, &error_msg);
-  CHECK(dex_file != nullptr) << error_msg;
+  std::vector<const DexFile*> tmp;
+  bool success = DexFile::Open(location, location, &error_msg, &tmp);
+  CHECK(success) << error_msg;
+  EXPECT_EQ(1U, tmp.size());
+  const DexFile* dex_file = tmp[0];
   EXPECT_EQ(PROT_READ, dex_file->GetPermissions());
   EXPECT_TRUE(dex_file->IsReadOnly());
   return dex_file;
diff --git a/runtime/dex_file_verifier_test.cc b/runtime/dex_file_verifier_test.cc
index d0ce00f..93faeae 100644
--- a/runtime/dex_file_verifier_test.cc
+++ b/runtime/dex_file_verifier_test.cc
@@ -115,7 +115,14 @@
 
   // read dex file
   ScopedObjectAccess soa(Thread::Current());
-  return DexFile::Open(location, location, error_msg);
+  std::vector<const DexFile*> tmp;
+  bool success = DexFile::Open(location, location, error_msg, &tmp);
+  CHECK(success) << error_msg;
+  EXPECT_EQ(1U, tmp.size());
+  const DexFile* dex_file = tmp[0];
+  EXPECT_EQ(PROT_READ, dex_file->GetPermissions());
+  EXPECT_TRUE(dex_file->IsReadOnly());
+  return dex_file;
 }
 
 
@@ -170,7 +177,15 @@
 
   // read dex file
   ScopedObjectAccess soa(Thread::Current());
-  return DexFile::Open(location, location, error_msg);
+  std::vector<const DexFile*> tmp;
+  if (!DexFile::Open(location, location, error_msg, &tmp)) {
+    return nullptr;
+  }
+  EXPECT_EQ(1U, tmp.size());
+  const DexFile* dex_file = tmp[0];
+  EXPECT_EQ(PROT_READ, dex_file->GetPermissions());
+  EXPECT_TRUE(dex_file->IsReadOnly());
+  return dex_file;
 }
 
 static bool ModifyAndLoad(const char* location, size_t offset, uint8_t new_val,
diff --git a/runtime/dex_method_iterator_test.cc b/runtime/dex_method_iterator_test.cc
index 5e2d89e..0d00cc3 100644
--- a/runtime/dex_method_iterator_test.cc
+++ b/runtime/dex_method_iterator_test.cc
@@ -21,26 +21,15 @@
 namespace art {
 
 class DexMethodIteratorTest : public CommonRuntimeTest {
- public:
-  const DexFile* OpenDexFile(const std::string& partial_filename) {
-    std::string dfn = GetDexFileName(partial_filename);
-    std::string error_msg;
-    const DexFile* dexfile = DexFile::Open(dfn.c_str(), dfn.c_str(), &error_msg);
-    if (dexfile == nullptr) {
-      LG << "Failed to open '" << dfn << "': " << error_msg;
-    }
-    return dexfile;
-  }
 };
 
 TEST_F(DexMethodIteratorTest, Basic) {
   ScopedObjectAccess soa(Thread::Current());
   std::vector<const DexFile*> dex_files;
-  dex_files.push_back(OpenDexFile("core-libart"));
-  dex_files.push_back(OpenDexFile("conscrypt"));
-  dex_files.push_back(OpenDexFile("okhttp"));
-  dex_files.push_back(OpenDexFile("core-junit"));
-  dex_files.push_back(OpenDexFile("bouncycastle"));
+  const char* jars[] = { "core-libart", "conscrypt", "okhttp", "core-junit", "bouncycastle" };
+  for (size_t i = 0; i < 5; ++i) {
+    dex_files.push_back(LoadExpectSingleDexFile(GetDexFileName(jars[i]).c_str()));
+  }
   DexMethodIterator it(dex_files);
   while (it.HasNext()) {
     const DexFile& dex_file = it.GetDexFile();
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 9512a5a..440d3d0 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -26,6 +26,7 @@
 #include <unistd.h>
 
 #include "base/logging.h"
+#include "base/stl_util.h"
 #include "class_linker.h"
 #include "common_throws.h"
 #include "dex_file-inl.h"
@@ -106,34 +107,19 @@
     return 0;
   }
 
-  uint32_t dex_location_checksum;
-  uint32_t* dex_location_checksum_pointer = &dex_location_checksum;
-  std::vector<std::string> error_msgs;
-  std::string error_msg;
-  if (!DexFile::GetChecksum(sourceName.c_str(), dex_location_checksum_pointer, &error_msg)) {
-    dex_location_checksum_pointer = NULL;
-  }
-
   ClassLinker* linker = Runtime::Current()->GetClassLinker();
-  const DexFile* dex_file;
-  if (outputName.c_str() == nullptr) {
-    // FindOrCreateOatFileForDexLocation can tolerate a missing dex_location_checksum
-    dex_file = linker->FindDexFileInOatFileFromDexLocation(sourceName.c_str(),
-                                                           dex_location_checksum_pointer,
-                                                           kRuntimeISA,
-                                                           &error_msgs);
+  std::unique_ptr<std::vector<const DexFile*>> dex_files(new std::vector<const DexFile*>());
+  std::vector<std::string> error_msgs;
+
+  bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs,
+                                             dex_files.get());
+
+  if (success) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(dex_files.release()));
   } else {
-    // FindOrCreateOatFileForDexLocation requires the dex_location_checksum
-    if (dex_location_checksum_pointer == NULL) {
-      ScopedObjectAccess soa(env);
-      DCHECK(!error_msg.empty());
-      ThrowIOException("%s", error_msg.c_str());
-      return 0;
-    }
-    dex_file = linker->FindOrCreateOatFileForDexLocation(sourceName.c_str(), dex_location_checksum,
-                                                         outputName.c_str(), &error_msgs);
-  }
-  if (dex_file == nullptr) {
+    // The vector should be empty after a failed loading attempt.
+    DCHECK_EQ(0U, dex_files->size());
+
     ScopedObjectAccess soa(env);
     CHECK(!error_msgs.empty());
     // The most important message is at the end. So set up nesting by going forward, which will
@@ -146,35 +132,41 @@
 
     return 0;
   }
-  return static_cast<jlong>(reinterpret_cast<uintptr_t>(dex_file));
 }
 
-static const DexFile* toDexFile(jlong dex_file_address, JNIEnv* env) {
-  const DexFile* dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>(dex_file_address));
-  if (UNLIKELY(dex_file == nullptr)) {
+static std::vector<const DexFile*>* toDexFiles(jlong dex_file_address, JNIEnv* env) {
+  std::vector<const DexFile*>* dex_files = reinterpret_cast<std::vector<const DexFile*>*>(
+      static_cast<uintptr_t>(dex_file_address));
+  if (UNLIKELY(dex_files == nullptr)) {
     ScopedObjectAccess soa(env);
     ThrowNullPointerException(NULL, "dex_file == null");
   }
-  return dex_file;
+  return dex_files;
 }
 
 static void DexFile_closeDexFile(JNIEnv* env, jclass, jlong cookie) {
-  const DexFile* dex_file;
-  dex_file = toDexFile(cookie, env);
-  if (dex_file == nullptr) {
+  std::unique_ptr<std::vector<const DexFile*>> dex_files(toDexFiles(cookie, env));
+  if (dex_files.get() == nullptr) {
     return;
   }
   ScopedObjectAccess soa(env);
-  if (Runtime::Current()->GetClassLinker()->IsDexFileRegistered(*dex_file)) {
-    return;
+
+  size_t index = 0;
+  for (const DexFile* dex_file : *dex_files) {
+    if (Runtime::Current()->GetClassLinker()->IsDexFileRegistered(*dex_file)) {
+      (*dex_files)[index] = nullptr;
+    }
+    index++;
   }
-  delete dex_file;
+
+  STLDeleteElements(dex_files.get());
+  // Unique_ptr will delete the vector itself.
 }
 
 static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,
                                         jlong cookie) {
-  const DexFile* dex_file = toDexFile(cookie, env);
-  if (dex_file == NULL) {
+  std::vector<const DexFile*>* dex_files = toDexFiles(cookie, env);
+  if (dex_files == NULL) {
     VLOG(class_linker) << "Failed to find dex_file";
     return NULL;
   }
@@ -184,33 +176,60 @@
     return NULL;
   }
   const std::string descriptor(DotToDescriptor(class_name.c_str()));
-  const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str());
-  if (dex_class_def == NULL) {
-    VLOG(class_linker) << "Failed to find dex_class_def";
-    return NULL;
+
+  for (const DexFile* dex_file : *dex_files) {
+    const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str());
+    if (dex_class_def != nullptr) {
+      ScopedObjectAccess soa(env);
+      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+      class_linker->RegisterDexFile(*dex_file);
+      StackHandleScope<1> hs(soa.Self());
+      Handle<mirror::ClassLoader> class_loader(
+          hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
+      mirror::Class* result = class_linker->DefineClass(descriptor.c_str(), class_loader, *dex_file,
+                                                        *dex_class_def);
+      if (result != nullptr) {
+        VLOG(class_linker) << "DexFile_defineClassNative returning " << result;
+        return soa.AddLocalReference<jclass>(result);
+      }
+    }
   }
-  ScopedObjectAccess soa(env);
-  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  class_linker->RegisterDexFile(*dex_file);
-  StackHandleScope<1> hs(soa.Self());
-  Handle<mirror::ClassLoader> class_loader(
-      hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
-  mirror::Class* result = class_linker->DefineClass(descriptor.c_str(), class_loader, *dex_file,
-                                                    *dex_class_def);
-  VLOG(class_linker) << "DexFile_defineClassNative returning " << result;
-  return soa.AddLocalReference<jclass>(result);
+  VLOG(class_linker) << "Failed to find dex_class_def";
+  return nullptr;
 }
 
+// Needed as a compare functor for sets of const char
+struct CharPointerComparator {
+  bool operator()(const char *str1, const char *str2) const {
+    return strcmp(str1, str2) < 0;
+  }
+};
+
+// Note: this can be an expensive call, as we sort out duplicates in MultiDex files.
 static jobjectArray DexFile_getClassNameList(JNIEnv* env, jclass, jlong cookie) {
   jobjectArray result = nullptr;
-  const DexFile* dex_file = toDexFile(cookie, env);
-  if (dex_file != nullptr) {
-    result = env->NewObjectArray(dex_file->NumClassDefs(), WellKnownClasses::java_lang_String,
-                                 nullptr);
-    if (result != nullptr) {
+  std::vector<const DexFile*>* dex_files = toDexFiles(cookie, env);
+
+  if (dex_files != nullptr) {
+    // Push all class descriptors into a set. Use set instead of unordered_set as we want to
+    // retrieve all in the end.
+    std::set<const char*, CharPointerComparator> descriptors;
+    for (const DexFile* dex_file : *dex_files) {
       for (size_t i = 0; i < dex_file->NumClassDefs(); ++i) {
         const DexFile::ClassDef& class_def = dex_file->GetClassDef(i);
-        std::string descriptor(DescriptorToDot(dex_file->GetClassDescriptor(class_def)));
+        const char* descriptor = dex_file->GetClassDescriptor(class_def);
+        descriptors.insert(descriptor);
+      }
+    }
+
+    // Now create output array and copy the set into it.
+    result = env->NewObjectArray(descriptors.size(), WellKnownClasses::java_lang_String, nullptr);
+    if (result != nullptr) {
+      auto it = descriptors.begin();
+      auto it_end = descriptors.end();
+      jsize i = 0;
+      for (; it != it_end; it++, ++i) {
+        std::string descriptor(DescriptorToDot(*it));
         ScopedLocalRef<jstring> jdescriptor(env, env->NewStringUTF(descriptor.c_str()));
         if (jdescriptor.get() == nullptr) {
           return nullptr;
diff --git a/runtime/utils.cc b/runtime/utils.cc
index e5b8b22..c52549e 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -1232,6 +1232,7 @@
 std::string DexFilenameToOdexFilename(const std::string& location, const InstructionSet isa) {
   // location = /foo/bar/baz.jar
   // odex_location = /foo/bar/<isa>/baz.odex
+
   CHECK_GE(location.size(), 4U) << location;  // must be at least .123
   std::string odex_location(location);
   InsertIsaDirectory(isa, &odex_location);
diff --git a/runtime/utils.h b/runtime/utils.h
index a61d30f..68ea475 100644
--- a/runtime/utils.h
+++ b/runtime/utils.h
@@ -401,6 +401,7 @@
 
 // Returns an .odex file name next adjacent to the dex location.
 // For example, for "/foo/bar/baz.jar", return "/foo/bar/<isa>/baz.odex".
+// Note: does not support multidex location strings.
 std::string DexFilenameToOdexFilename(const std::string& location, InstructionSet isa);
 
 // Check whether the given magic matches a known file type.
diff --git a/test/113-multidex/build b/test/113-multidex/build
new file mode 100644
index 0000000..ec8706e
--- /dev/null
+++ b/test/113-multidex/build
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 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.
+
+# Stop if something fails.
+set -e
+
+mkdir classes
+
+# All except Main
+${JAVAC} -d classes `find src -name '*.java'`
+rm classes/Main.class
+${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex classes
+
+# Only Main
+${JAVAC} -d classes `find src -name '*.java'`
+rm classes/Second.class classes/FillerA.class classes/FillerB.class classes/Inf*.class
+${DX} -JXmx256m --debug --dex --dump-to=classes2.lst --output=classes2.dex classes
+
+zip $TEST_NAME.jar classes.dex classes2.dex
diff --git a/test/113-multidex/expected.txt b/test/113-multidex/expected.txt
new file mode 100644
index 0000000..603e911
--- /dev/null
+++ b/test/113-multidex/expected.txt
@@ -0,0 +1,12 @@
+FillerA
+Second
+Second::zcall
+Second::zcall1
+Second::zcall2
+Second::zcall3
+Second::zcall4
+Second::zcall5
+Second::zcall6
+Second::zcall7
+Second::zcall8
+Second::zcall9
diff --git a/test/113-multidex/info.txt b/test/113-multidex/info.txt
new file mode 100644
index 0000000..d0a4ac1
--- /dev/null
+++ b/test/113-multidex/info.txt
@@ -0,0 +1,2 @@
+Test whether we can run code from an application split into multiple dex files (similar to
+MultiDex).
diff --git a/test/113-multidex/src/FillerA.java b/test/113-multidex/src/FillerA.java
new file mode 100644
index 0000000..d169018
--- /dev/null
+++ b/test/113-multidex/src/FillerA.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public class FillerA {
+  public void methodA() {
+  }
+
+  public void methodB() {
+  }
+}
diff --git a/test/113-multidex/src/FillerB.java b/test/113-multidex/src/FillerB.java
new file mode 100644
index 0000000..ec3ac9d
--- /dev/null
+++ b/test/113-multidex/src/FillerB.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public class FillerB {
+  public void methodC() {
+  }
+
+  public void methodD() {
+  }
+}
diff --git a/test/113-multidex/src/Inf1.java b/test/113-multidex/src/Inf1.java
new file mode 100644
index 0000000..3deb6b4
--- /dev/null
+++ b/test/113-multidex/src/Inf1.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public interface Inf1 {
+  public void zcall();
+  public void zcall1();
+  public void zcall2();
+  public void zcall3();
+  public void zcall4();
+  public void zcall5();
+  public void zcall6();
+  public void zcall7();
+  public void zcall8();
+  public void zcall9();
+}
\ No newline at end of file
diff --git a/test/113-multidex/src/Inf2.java b/test/113-multidex/src/Inf2.java
new file mode 100644
index 0000000..ac09509
--- /dev/null
+++ b/test/113-multidex/src/Inf2.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public interface Inf2 {
+  public void zcall();
+  public void zcall1();
+  public void zcall2();
+  public void zcall3();
+  public void zcall4();
+  public void zcall5();
+  public void zcall6();
+  public void zcall7();
+  public void zcall8();
+  public void zcall9();
+}
\ No newline at end of file
diff --git a/test/113-multidex/src/Inf3.java b/test/113-multidex/src/Inf3.java
new file mode 100644
index 0000000..d6c377b
--- /dev/null
+++ b/test/113-multidex/src/Inf3.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public interface Inf3 {
+  public void zcall();
+  public void zcall1();
+  public void zcall2();
+  public void zcall3();
+  public void zcall4();
+  public void zcall5();
+  public void zcall6();
+  public void zcall7();
+  public void zcall8();
+  public void zcall9();
+}
\ No newline at end of file
diff --git a/test/113-multidex/src/Inf4.java b/test/113-multidex/src/Inf4.java
new file mode 100644
index 0000000..a1801b9
--- /dev/null
+++ b/test/113-multidex/src/Inf4.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public interface Inf4 {
+  public void zcall();
+  public void zcall1();
+  public void zcall2();
+  public void zcall3();
+  public void zcall4();
+  public void zcall5();
+  public void zcall6();
+  public void zcall7();
+  public void zcall8();
+  public void zcall9();
+}
\ No newline at end of file
diff --git a/test/113-multidex/src/Inf5.java b/test/113-multidex/src/Inf5.java
new file mode 100644
index 0000000..e8115ce
--- /dev/null
+++ b/test/113-multidex/src/Inf5.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public interface Inf5 {
+  public void zcall();
+  public void zcall1();
+  public void zcall2();
+  public void zcall3();
+  public void zcall4();
+  public void zcall5();
+  public void zcall6();
+  public void zcall7();
+  public void zcall8();
+  public void zcall9();
+}
\ No newline at end of file
diff --git a/test/113-multidex/src/Inf6.java b/test/113-multidex/src/Inf6.java
new file mode 100644
index 0000000..554bdb8
--- /dev/null
+++ b/test/113-multidex/src/Inf6.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public interface Inf6 {
+  public void zcall();
+  public void zcall1();
+  public void zcall2();
+  public void zcall3();
+  public void zcall4();
+  public void zcall5();
+  public void zcall6();
+  public void zcall7();
+  public void zcall8();
+  public void zcall9();
+}
\ No newline at end of file
diff --git a/test/113-multidex/src/Inf7.java b/test/113-multidex/src/Inf7.java
new file mode 100644
index 0000000..1982775
--- /dev/null
+++ b/test/113-multidex/src/Inf7.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public interface Inf7 {
+  public void zcall();
+  public void zcall1();
+  public void zcall2();
+  public void zcall3();
+  public void zcall4();
+  public void zcall5();
+  public void zcall6();
+  public void zcall7();
+  public void zcall8();
+  public void zcall9();
+}
\ No newline at end of file
diff --git a/test/113-multidex/src/Inf8.java b/test/113-multidex/src/Inf8.java
new file mode 100644
index 0000000..87296db
--- /dev/null
+++ b/test/113-multidex/src/Inf8.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public interface Inf8 {
+  public void zcall();
+  public void zcall1();
+  public void zcall2();
+  public void zcall3();
+  public void zcall4();
+  public void zcall5();
+  public void zcall6();
+  public void zcall7();
+  public void zcall8();
+  public void zcall9();
+}
\ No newline at end of file
diff --git a/test/113-multidex/src/Main.java b/test/113-multidex/src/Main.java
new file mode 100644
index 0000000..1c74220
--- /dev/null
+++ b/test/113-multidex/src/Main.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public class Main {
+    static public void main(String[] args) throws Exception {
+      System.out.println(new FillerA().getClass().getName());
+
+      Inf1 second = new Second();
+      System.out.println(second.getClass().getName());
+      second.zcall();
+      second.zcall1();
+      second.zcall2();
+      second.zcall3();
+      second.zcall4();
+      second.zcall5();
+      second.zcall6();
+      second.zcall7();
+      second.zcall8();
+      second.zcall9();
+    }
+
+}
diff --git a/test/113-multidex/src/Second.java b/test/113-multidex/src/Second.java
new file mode 100644
index 0000000..d0c2535
--- /dev/null
+++ b/test/113-multidex/src/Second.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+public class Second implements Inf1, Inf2, Inf3, Inf4, Inf5, Inf6, Inf7, Inf8 {
+  public void zcall() {
+    System.out.println("Second::zcall");
+  }
+
+  public void zcall1() {
+    System.out.println("Second::zcall1");
+  }
+
+  public void zcall2() {
+    System.out.println("Second::zcall2");
+  }
+
+  public void zcall3() {
+    System.out.println("Second::zcall3");
+  }
+
+  public void zcall4() {
+    System.out.println("Second::zcall4");
+  }
+
+  public void zcall5() {
+    System.out.println("Second::zcall5");
+  }
+
+  public void zcall6() {
+    System.out.println("Second::zcall6");
+  }
+
+  public void zcall7() {
+    System.out.println("Second::zcall7");
+  }
+
+  public void zcall8() {
+    System.out.println("Second::zcall8");
+  }
+
+  public void zcall9() {
+    System.out.println("Second::zcall9");
+  }
+}
diff --git a/tools/generate-operator-out.py b/tools/generate-operator-out.py
index 56b8674..6baa6e3 100755
--- a/tools/generate-operator-out.py
+++ b/tools/generate-operator-out.py
@@ -23,11 +23,12 @@
 import sys
 
 
-_ENUM_START_RE = re.compile(r'\benum\b\s+(\S+)\s+:?.*\{')
+_ENUM_START_RE = re.compile(r'\benum\b\s+(class\s+)?(\S+)\s+:?.*\{(\s+// private)?')
 _ENUM_VALUE_RE = re.compile(r'([A-Za-z0-9_]+)(.*)')
 _ENUM_END_RE = re.compile(r'^\s*\};$')
 _ENUMS = {}
 _NAMESPACES = {}
+_ENUM_CLASSES = {}
 
 def Confused(filename, line_number, line):
   sys.stderr.write('%s:%d: confused by:\n%s\n' % (filename, line_number, line))
@@ -38,7 +39,9 @@
 def ProcessFile(filename):
   lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n')
   in_enum = False
+  is_enum_class = False
   line_number = 0
+  
 
   namespaces = []
   enclosing_classes = []
@@ -51,11 +54,18 @@
       m = _ENUM_START_RE.search(raw_line)
       if m:
         # Yes, so add an empty entry to _ENUMS for this enum.
-        enum_name = m.group(1)
+        
+        # Except when it's private
+        if m.group(3) is not None:
+          continue
+        
+        is_enum_class = m.group(1) is not None
+        enum_name = m.group(2)
         if len(enclosing_classes) > 0:
           enum_name = '::'.join(enclosing_classes) + '::' + enum_name
         _ENUMS[enum_name] = []
         _NAMESPACES[enum_name] = '::'.join(namespaces)
+        _ENUM_CLASSES[enum_name] = is_enum_class
         in_enum = True
         continue
 
@@ -139,7 +149,10 @@
       Confused(filename, line_number, raw_line)
 
     if len(enclosing_classes) > 0:
-      enum_value = '::'.join(enclosing_classes) + '::' + enum_value
+      if is_enum_class:
+        enum_value = enum_name + '::' + enum_value
+      else:
+        enum_value = '::'.join(enclosing_classes) + '::' + enum_value
 
     _ENUMS[enum_name].append((enum_value, enum_text))
 
@@ -170,7 +183,8 @@
     print '  switch (rhs) {'
     for (enum_value, enum_text) in _ENUMS[enum_name]:
       print '    case %s: os << "%s"; break;' % (enum_value, enum_text)
-    print '    default: os << "%s[" << static_cast<int>(rhs) << "]"; break;' % enum_name
+    if not _ENUM_CLASSES[enum_name]:
+      print '    default: os << "%s[" << static_cast<int>(rhs) << "]"; break;' % enum_name
     print '  }'
     print '  return os;'
     print '}'