Merge "Remove suspend point from field loading"
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index d9d09bc..7283710 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -67,6 +67,7 @@
 ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives
 ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods
 ART_GTEST_oat_file_assistant_test_DEX_DEPS := Main MainStripped MultiDex Nested
+ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex
 ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY
 ART_GTEST_proxy_test_DEX_DEPS := Interfaces
 ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index eda7ec6..7e32b43 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1237,6 +1237,11 @@
       for (auto& class_path_file : class_path_files_) {
         class_path_files.push_back(class_path_file.get());
       }
+
+      // Store the classpath we have right now.
+      key_value_store_->Put(OatHeader::kClassPathKey,
+                            OatFile::EncodeDexFileDependencies(class_path_files));
+
       // Then the dex files we'll compile. Thus we'll resolve the class-path first.
       class_path_files.insert(class_path_files.end(), dex_files_.begin(), dex_files_.end());
 
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 0d92fc2..4e59217 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1259,94 +1259,124 @@
   return ClassPathEntry(nullptr, nullptr);
 }
 
-mirror::Class* ClassLinker::FindClassInPathClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                                       Thread* self, const char* descriptor,
-                                                       size_t hash,
-                                                       Handle<mirror::ClassLoader> class_loader) {
-  // Can we special case for a well understood PathClassLoader with the BootClassLoader as parent?
-  if (class_loader->GetClass() !=
-      soa.Decode<mirror::Class*>(WellKnownClasses::dalvik_system_PathClassLoader) ||
-      class_loader->GetParent()->GetClass() !=
-          soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_BootClassLoader)) {
-    return nullptr;
-  }
-  ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
-  // Check if this would be found in the parent boot class loader.
-  if (pair.second != nullptr) {
-    mirror::Class* klass = LookupClass(self, descriptor, hash, nullptr);
-    if (klass != nullptr) {
-      // May return null if resolution on another thread fails.
-      klass = EnsureResolved(self, descriptor, klass);
+static bool IsBootClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+                              mirror::ClassLoader* class_loader)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  return class_loader == nullptr ||
+      class_loader->GetClass() ==
+          soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_BootClassLoader);
+}
+
+bool ClassLinker::FindClassInPathClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+                                             Thread* self, const char* descriptor,
+                                             size_t hash,
+                                             Handle<mirror::ClassLoader> class_loader,
+                                             mirror::Class** result) {
+  // Termination case: boot class-loader.
+  if (IsBootClassLoader(soa, class_loader.Get())) {
+    // The boot class loader, search the boot class path.
+    ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
+    if (pair.second != nullptr) {
+      mirror::Class* klass = LookupClass(self, descriptor, hash, nullptr);
+      if (klass != nullptr) {
+        *result = EnsureResolved(self, descriptor, klass);
+      } else {
+        *result = DefineClass(self, descriptor, hash, NullHandle<mirror::ClassLoader>(),
+                              *pair.first, *pair.second);
+      }
+      if (*result == nullptr) {
+        CHECK(self->IsExceptionPending()) << descriptor;
+        self->ClearException();
+      }
     } else {
-      // May OOME.
-      klass = DefineClass(self, descriptor, hash, NullHandle<mirror::ClassLoader>(), *pair.first,
-                          *pair.second);
+      *result = nullptr;
     }
-    if (klass == nullptr) {
-      CHECK(self->IsExceptionPending()) << descriptor;
-      self->ClearException();
-    }
-    return klass;
-  } else {
-    // Handle as if this is the child PathClassLoader.
-    // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension).
-    StackHandleScope<3> hs(self);
-    // The class loader is a PathClassLoader which inherits from BaseDexClassLoader.
-    // We need to get the DexPathList and loop through it.
-    ArtField* const cookie_field = soa.DecodeField(WellKnownClasses::dalvik_system_DexFile_cookie);
-    ArtField* const dex_file_field =
-        soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
-    mirror::Object* dex_path_list =
-        soa.DecodeField(WellKnownClasses::dalvik_system_PathClassLoader_pathList)->
-        GetObject(class_loader.Get());
-    if (dex_path_list != nullptr && dex_file_field != nullptr && cookie_field != nullptr) {
-      // DexPathList has an array dexElements of Elements[] which each contain a dex file.
-      mirror::Object* dex_elements_obj =
-          soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
-          GetObject(dex_path_list);
-      // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look
-      // at the mCookie which is a DexFile vector.
-      if (dex_elements_obj != nullptr) {
-        Handle<mirror::ObjectArray<mirror::Object>> dex_elements =
-            hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>());
-        for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
-          mirror::Object* element = dex_elements->GetWithoutChecks(i);
-          if (element == nullptr) {
-            // Should never happen, fall back to java code to throw a NPE.
+    return true;
+  }
+
+  // Unsupported class-loader?
+  if (class_loader->GetClass() !=
+      soa.Decode<mirror::Class*>(WellKnownClasses::dalvik_system_PathClassLoader)) {
+    *result = nullptr;
+    return false;
+  }
+
+  // Handles as RegisterDexFile may allocate dex caches (and cause thread suspension).
+  StackHandleScope<4> hs(self);
+  Handle<mirror::ClassLoader> h_parent(hs.NewHandle(class_loader->GetParent()));
+  bool recursive_result = FindClassInPathClassLoader(soa, self, descriptor, hash, h_parent, result);
+
+  if (!recursive_result) {
+    // Something wrong up the chain.
+    return false;
+  }
+
+  if (*result != nullptr) {
+    // Found the class up the chain.
+    return true;
+  }
+
+  // Handle this step.
+  // Handle as if this is the child PathClassLoader.
+  // The class loader is a PathClassLoader which inherits from BaseDexClassLoader.
+  // We need to get the DexPathList and loop through it.
+  ArtField* const cookie_field = soa.DecodeField(WellKnownClasses::dalvik_system_DexFile_cookie);
+  ArtField* const dex_file_field =
+      soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+  mirror::Object* dex_path_list =
+      soa.DecodeField(WellKnownClasses::dalvik_system_PathClassLoader_pathList)->
+      GetObject(class_loader.Get());
+  if (dex_path_list != nullptr && dex_file_field != nullptr && cookie_field != nullptr) {
+    // DexPathList has an array dexElements of Elements[] which each contain a dex file.
+    mirror::Object* dex_elements_obj =
+        soa.DecodeField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
+        GetObject(dex_path_list);
+    // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look
+    // at the mCookie which is a DexFile vector.
+    if (dex_elements_obj != nullptr) {
+      Handle<mirror::ObjectArray<mirror::Object>> dex_elements =
+          hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>());
+      for (int32_t i = 0; i < dex_elements->GetLength(); ++i) {
+        mirror::Object* element = dex_elements->GetWithoutChecks(i);
+        if (element == nullptr) {
+          // Should never happen, fall back to java code to throw a NPE.
+          break;
+        }
+        mirror::Object* dex_file = dex_file_field->GetObject(element);
+        if (dex_file != nullptr) {
+          mirror::LongArray* long_array = cookie_field->GetObject(dex_file)->AsLongArray();
+          if (long_array == nullptr) {
+            // This should never happen so log a warning.
+            LOG(WARNING) << "Null DexFile::mCookie for " << descriptor;
             break;
           }
-          mirror::Object* dex_file = dex_file_field->GetObject(element);
-          if (dex_file != nullptr) {
-            mirror::LongArray* long_array = cookie_field->GetObject(dex_file)->AsLongArray();
-            if (long_array == nullptr) {
-              // This should never happen so log a warning.
-              LOG(WARNING) << "Null DexFile::mCookie for " << descriptor;
-              break;
-            }
-            int32_t long_array_size = long_array->GetLength();
-            for (int32_t j = 0; j < long_array_size; ++j) {
-              const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>(
-                  long_array->GetWithoutChecks(j)));
-              const DexFile::ClassDef* dex_class_def = cp_dex_file->FindClassDef(descriptor, hash);
-              if (dex_class_def != nullptr) {
-                RegisterDexFile(*cp_dex_file);
-                mirror::Class* klass = DefineClass(self, descriptor, hash, class_loader,
-                                                   *cp_dex_file, *dex_class_def);
-                if (klass == nullptr) {
-                  CHECK(self->IsExceptionPending()) << descriptor;
-                  self->ClearException();
-                  return nullptr;
-                }
-                return klass;
+          int32_t long_array_size = long_array->GetLength();
+          for (int32_t j = 0; j < long_array_size; ++j) {
+            const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>(
+                long_array->GetWithoutChecks(j)));
+            const DexFile::ClassDef* dex_class_def = cp_dex_file->FindClassDef(descriptor, hash);
+            if (dex_class_def != nullptr) {
+              RegisterDexFile(*cp_dex_file);
+              mirror::Class* klass = DefineClass(self, descriptor, hash, class_loader,
+                                                 *cp_dex_file, *dex_class_def);
+              if (klass == nullptr) {
+                CHECK(self->IsExceptionPending()) << descriptor;
+                self->ClearException();
+                // TODO: Is it really right to break here, and not check the other dex files?
+                return true;
               }
+              *result = klass;
+              return true;
             }
           }
         }
       }
     }
     self->AssertNoPendingException();
-    return nullptr;
   }
+
+  // Result is still null from the parent call, no need to set it again...
+  return true;
 }
 
 mirror::Class* ClassLinker::FindClass(Thread* self, const char* descriptor,
@@ -1384,10 +1414,18 @@
     }
   } else {
     ScopedObjectAccessUnchecked soa(self);
-    mirror::Class* cp_klass = FindClassInPathClassLoader(soa, self, descriptor, hash,
-                                                         class_loader);
-    if (cp_klass != nullptr) {
-      return cp_klass;
+    mirror::Class* cp_klass;
+    if (FindClassInPathClassLoader(soa, self, descriptor, hash, class_loader, &cp_klass)) {
+      // The chain was understood. So the value in cp_klass is either the class we were looking
+      // for, or not found.
+      if (cp_klass != nullptr) {
+        return cp_klass;
+      }
+      // TODO: We handle the boot classpath loader in FindClassInPathClassLoader. Try to unify this
+      //       and the branch above. TODO: throw the right exception here.
+
+      // We'll let the Java-side rediscover all this and throw the exception with the right stack
+      // trace.
     }
 
     if (Runtime::Current()->IsAotCompiler()) {
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 2427462..68624b0 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -117,11 +117,15 @@
                            Handle<mirror::ClassLoader> class_loader)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
-  // Find a class in the path class loader, loading it if necessary without using JNI. Hash
-  // function is supposed to be ComputeModifiedUtf8Hash(descriptor).
-  mirror::Class* FindClassInPathClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
-                                            Thread* self, const char* descriptor, size_t hash,
-                                            Handle<mirror::ClassLoader> class_loader)
+  // Finds a class in the path class loader, loading it if necessary without using JNI. Hash
+  // function is supposed to be ComputeModifiedUtf8Hash(descriptor). Returns true if the
+  // class-loader chain could be handled, false otherwise, i.e., a non-supported class-loader
+  // was encountered while walking the parent chain (currently only BootClassLoader and
+  // PathClassLoader are supported).
+  bool FindClassInPathClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
+                                  Thread* self, const char* descriptor, size_t hash,
+                                  Handle<mirror::ClassLoader> class_loader,
+                                  mirror::Class** result)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   // Finds a class by its descriptor using the "system" class loader, ie by searching the
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index a971c1b3..fbb07e8 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -39,6 +39,7 @@
 #include "thread.h"
 #include "transaction.h"
 #include "well_known_classes.h"
+#include "zip_archive.h"
 
 namespace art {
 namespace interpreter {
@@ -641,6 +642,100 @@
   }
 }
 
+// This allows reading security.properties in an unstarted runtime and initialize Security.
+static void UnstartedSecurityGetSecurityPropertiesReader(
+    Thread* self,
+    ShadowFrame* shadow_frame ATTRIBUTE_UNUSED,
+    JValue* result,
+    size_t arg_offset ATTRIBUTE_UNUSED)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  Runtime* runtime = Runtime::Current();
+  const std::vector<const DexFile*>& path = runtime->GetClassLinker()->GetBootClassPath();
+  std::string canonical(DexFile::GetDexCanonicalLocation(path[0]->GetLocation().c_str()));
+  mirror::String* string_data;
+
+  // Use a block to enclose the I/O and MemMap code so buffers are released early.
+  {
+    std::string error_msg;
+    std::unique_ptr<ZipArchive> zip_archive(ZipArchive::Open(canonical.c_str(), &error_msg));
+    if (zip_archive.get() == nullptr) {
+      AbortTransactionOrFail(self, "Could not open zip file %s: %s", canonical.c_str(),
+                             error_msg.c_str());
+      return;
+    }
+    std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find("java/security/security.properties",
+                                                          &error_msg));
+    if (zip_entry.get() == nullptr) {
+      AbortTransactionOrFail(self, "Could not find security.properties file in %s: %s",
+                             canonical.c_str(), error_msg.c_str());
+      return;
+    }
+    std::unique_ptr<MemMap> map(zip_entry->ExtractToMemMap(canonical.c_str(),
+                                                           "java/security/security.properties",
+                                                           &error_msg));
+    if (map.get() == nullptr) {
+      AbortTransactionOrFail(self, "Could not unzip security.properties file in %s: %s",
+                             canonical.c_str(), error_msg.c_str());
+      return;
+    }
+
+    uint32_t length = zip_entry->GetUncompressedLength();
+    std::unique_ptr<char[]> tmp(new char[length + 1]);
+    memcpy(tmp.get(), map->Begin(), length);
+    tmp.get()[length] = 0;  // null terminator
+
+    string_data = mirror::String::AllocFromModifiedUtf8(self, tmp.get());
+  }
+
+  if (string_data == nullptr) {
+    AbortTransactionOrFail(self, "Could not create string from file content of %s",
+                           canonical.c_str());
+    return;
+  }
+
+  // Create a StringReader.
+  StackHandleScope<3> hs(self);
+  Handle<mirror::String> h_string(hs.NewHandle(string_data));
+
+  Handle<mirror::Class> h_class(hs.NewHandle(
+      runtime->GetClassLinker()->FindClass(self,
+                                           "Ljava/io/StringReader;",
+                                           NullHandle<mirror::ClassLoader>())));
+  if (h_class.Get() == nullptr) {
+    AbortTransactionOrFail(self, "Could not find StringReader class");
+    return;
+  }
+
+  if (!runtime->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) {
+    AbortTransactionOrFail(self, "Could not initialize StringReader class");
+    return;
+  }
+
+  Handle<mirror::Object> h_obj(hs.NewHandle(h_class->AllocObject(self)));
+  if (h_obj.Get() == nullptr) {
+    AbortTransactionOrFail(self, "Could not allocate StringReader object");
+    return;
+  }
+
+  mirror::ArtMethod* constructor = h_class->FindDeclaredDirectMethod("<init>",
+                                                                     "(Ljava/lang/String;)V");
+  if (constructor == nullptr) {
+    AbortTransactionOrFail(self, "Could not find StringReader constructor");
+    return;
+  }
+
+  uint32_t args[1];
+  args[0] = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(h_string.Get()));
+  EnterInterpreterFromInvoke(self, constructor, h_obj.Get(), args, nullptr);
+
+  if (self->IsExceptionPending()) {
+    AbortTransactionOrFail(self, "Could not run StringReader constructor");
+    return;
+  }
+
+  result->SetL(h_obj.Get());
+}
+
 static void UnstartedJNIVMRuntimeNewUnpaddedArray(Thread* self,
                                                   mirror::ArtMethod* method ATTRIBUTE_UNUSED,
                                                   mirror::Object* receiver ATTRIBUTE_UNUSED,
@@ -963,6 +1058,8 @@
           &UnstartedMemoryPeekEntry },
       { "void libcore.io.Memory.peekByteArray(long, byte[], int, int)",
           &UnstartedMemoryPeekArrayEntry },
+      { "java.io.Reader java.security.Security.getSecurityPropertiesReader()",
+          &UnstartedSecurityGetSecurityPropertiesReader },
   };
 
   for (auto& def : defs) {
diff --git a/runtime/native/java_lang_VMClassLoader.cc b/runtime/native/java_lang_VMClassLoader.cc
index 35932e0..0c39f2b 100644
--- a/runtime/native/java_lang_VMClassLoader.cc
+++ b/runtime/native/java_lang_VMClassLoader.cc
@@ -44,8 +44,8 @@
   if (loader != nullptr) {
     // Try the common case.
     StackHandleScope<1> hs(soa.Self());
-    c = cl->FindClassInPathClassLoader(soa, soa.Self(), descriptor.c_str(), descriptor_hash,
-                                       hs.NewHandle(loader));
+    cl->FindClassInPathClassLoader(soa, soa.Self(), descriptor.c_str(), descriptor_hash,
+                                   hs.NewHandle(loader), &c);
     if (c != nullptr) {
       return soa.AddLocalReference<jclass>(c);
     }
diff --git a/runtime/oat.h b/runtime/oat.h
index de95fef..a31e09a 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -38,6 +38,7 @@
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDex2OatHostKey = "dex2oat-host";
   static constexpr const char* kPicKey = "pic";
+  static constexpr const char* kClassPathKey = "classpath";
 
   static OatHeader* Create(InstructionSet instruction_set,
                            const InstructionSetFeatures* instruction_set_features,
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 81703b1..d3c4b49 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -20,6 +20,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <cstdlib>
 #include <sstream>
 
 #include "base/bit_vector.h"
@@ -592,4 +593,90 @@
   // TODO: Check against oat_patches. b/18144996
 }
 
+static constexpr char kDexClassPathEncodingSeparator = '*';
+
+std::string OatFile::EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files) {
+  std::ostringstream out;
+
+  for (const DexFile* dex_file : dex_files) {
+    out << dex_file->GetLocation().c_str();
+    out << kDexClassPathEncodingSeparator;
+    out << dex_file->GetLocationChecksum();
+    out << kDexClassPathEncodingSeparator;
+  }
+
+  return out.str();
+}
+
+bool OatFile::CheckStaticDexFileDependencies(const char* dex_dependencies, std::string* msg) {
+  if (dex_dependencies == nullptr || dex_dependencies[0] == 0) {
+    // No dependencies.
+    return true;
+  }
+
+  // Assumption: this is not performance-critical. So it's OK to do this with a std::string and
+  //             Split() instead of manual parsing of the combined char*.
+  std::vector<std::string> split;
+  Split(dex_dependencies, kDexClassPathEncodingSeparator, &split);
+  if (split.size() % 2 != 0) {
+    // Expected pairs of location and checksum.
+    *msg = StringPrintf("Odd number of elements in dependency list %s", dex_dependencies);
+    return false;
+  }
+
+  for (auto it = split.begin(), end = split.end(); it != end; it += 2) {
+    std::string& location = *it;
+    std::string& checksum = *(it + 1);
+    int64_t converted = strtoll(checksum.c_str(), nullptr, 10);
+    if (converted == 0) {
+      // Conversion error.
+      *msg = StringPrintf("Conversion error for %s", checksum.c_str());
+      return false;
+    }
+
+    uint32_t dex_checksum;
+    std::string error_msg;
+    if (DexFile::GetChecksum(DexFile::GetDexCanonicalLocation(location.c_str()).c_str(),
+                             &dex_checksum,
+                             &error_msg)) {
+      if (converted != dex_checksum) {
+        *msg = StringPrintf("Checksums don't match for %s: %" PRId64 " vs %u",
+                            location.c_str(), converted, dex_checksum);
+        return false;
+      }
+    } else {
+      // Problem retrieving checksum.
+      // TODO: odex files?
+      *msg = StringPrintf("Could not retrieve checksum for %s: %s", location.c_str(),
+                          error_msg.c_str());
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool OatFile::GetDexLocationsFromDependencies(const char* dex_dependencies,
+                                              std::vector<std::string>* locations) {
+  DCHECK(locations != nullptr);
+  if (dex_dependencies == nullptr || dex_dependencies[0] == 0) {
+    return true;
+  }
+
+  // Assumption: this is not performance-critical. So it's OK to do this with a std::string and
+  //             Split() instead of manual parsing of the combined char*.
+  std::vector<std::string> split;
+  Split(dex_dependencies, kDexClassPathEncodingSeparator, &split);
+  if (split.size() % 2 != 0) {
+    // Expected pairs of location and checksum.
+    return false;
+  }
+
+  for (auto it = split.begin(), end = split.end(); it != end; it += 2) {
+    locations->push_back(*it);
+  }
+
+  return true;
+}
+
 }  // namespace art
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 73a8c8e..a5d5ae8 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -248,6 +248,18 @@
   static std::string ResolveRelativeEncodedDexLocation(
       const char* abs_dex_location, const std::string& rel_dex_location);
 
+  // Create a dependency list (dex locations and checksums) for the given dex files.
+  static std::string EncodeDexFileDependencies(const std::vector<const DexFile*>& dex_files);
+
+  // Check the given dependency list against their dex files - thus the name "Static," this does
+  // not check the class-loader environment, only whether there have been file updates.
+  static bool CheckStaticDexFileDependencies(const char* dex_dependencies, std::string* msg);
+
+  // Get the dex locations of a dependency list. Note: this is *not* cleaned for synthetic
+  // locations of multidex files.
+  static bool GetDexLocationsFromDependencies(const char* dex_dependencies,
+                                              std::vector<std::string>* locations);
+
  private:
   static void CheckLocation(const std::string& location);
 
diff --git a/runtime/oat_file_test.cc b/runtime/oat_file_test.cc
index f2213e9..a88553c 100644
--- a/runtime/oat_file_test.cc
+++ b/runtime/oat_file_test.cc
@@ -20,9 +20,15 @@
 
 #include <gtest/gtest.h>
 
+#include "common_runtime_test.h"
+#include "scoped_thread_state_change.h"
+
 namespace art {
 
-TEST(OatFileTest, ResolveRelativeEncodedDexLocation) {
+class OatFileTest : public CommonRuntimeTest {
+};
+
+TEST_F(OatFileTest, ResolveRelativeEncodedDexLocation) {
   EXPECT_EQ(std::string("/data/app/foo/base.apk"),
       OatFile::ResolveRelativeEncodedDexLocation(
         nullptr, "/data/app/foo/base.apk"));
@@ -56,4 +62,54 @@
         "/data/app/foo/base.apk", "o/base.apk"));
 }
 
+static std::vector<const DexFile*> ToConstDexFiles(
+    const std::vector<std::unique_ptr<const DexFile>>& in) {
+  std::vector<const DexFile*> ret;
+  for (auto& d : in) {
+    ret.push_back(d.get());
+  }
+  return ret;
+}
+
+TEST_F(OatFileTest, DexFileDependencies) {
+  std::string error_msg;
+
+  // No dependencies.
+  EXPECT_TRUE(OatFile::CheckStaticDexFileDependencies(nullptr, &error_msg)) << error_msg;
+  EXPECT_TRUE(OatFile::CheckStaticDexFileDependencies("", &error_msg)) << error_msg;
+
+  // Ill-formed dependencies.
+  EXPECT_FALSE(OatFile::CheckStaticDexFileDependencies("abc", &error_msg));
+  EXPECT_FALSE(OatFile::CheckStaticDexFileDependencies("abc*123*def", &error_msg));
+  EXPECT_FALSE(OatFile::CheckStaticDexFileDependencies("abc*def*", &error_msg));
+
+  // Unsatisfiable dependency.
+  EXPECT_FALSE(OatFile::CheckStaticDexFileDependencies("abc*123*", &error_msg));
+
+  // Load some dex files to be able to do a real test.
+  ScopedObjectAccess soa(Thread::Current());
+
+  std::vector<std::unique_ptr<const DexFile>> dex_files1 = OpenTestDexFiles("Main");
+  std::vector<const DexFile*> dex_files_const1 = ToConstDexFiles(dex_files1);
+  std::string encoding1 = OatFile::EncodeDexFileDependencies(dex_files_const1);
+  EXPECT_TRUE(OatFile::CheckStaticDexFileDependencies(encoding1.c_str(), &error_msg))
+      << error_msg << " " << encoding1;
+  std::vector<std::string> split1;
+  EXPECT_TRUE(OatFile::GetDexLocationsFromDependencies(encoding1.c_str(), &split1));
+  ASSERT_EQ(split1.size(), 1U);
+  EXPECT_EQ(split1[0], dex_files_const1[0]->GetLocation());
+
+  std::vector<std::unique_ptr<const DexFile>> dex_files2 = OpenTestDexFiles("MultiDex");
+  EXPECT_GT(dex_files2.size(), 1U);
+  std::vector<const DexFile*> dex_files_const2 = ToConstDexFiles(dex_files2);
+  std::string encoding2 = OatFile::EncodeDexFileDependencies(dex_files_const2);
+  EXPECT_TRUE(OatFile::CheckStaticDexFileDependencies(encoding2.c_str(), &error_msg))
+      << error_msg << " " << encoding2;
+  std::vector<std::string> split2;
+  EXPECT_TRUE(OatFile::GetDexLocationsFromDependencies(encoding2.c_str(), &split2));
+  ASSERT_EQ(split2.size(), 2U);
+  EXPECT_EQ(split2[0], dex_files_const2[0]->GetLocation());
+  EXPECT_EQ(split2[1], dex_files_const2[1]->GetLocation());
+}
+
 }  // namespace art