Collect verifier dependencies

MethodVerifier tests whether a DEX method is valid w.r.t. the classes
in class path. Since the APK does not change across OTA updates, it
is not necessary to analyze the bytecode again with MethodVerifier,
as long as its dependencies on the class path (which may have changed)
are satisfied.

This patch introduces VerifierDeps, a class path dependency collector,
and adds hooks into MethodVerifier where classes/methods/fields are
resolved and where assignability of types is tested.

Test: m test-art-host-gtest-verifier_deps_test
Bug: 30937355
Change-Id: Iee0b321d772a5c7d1cb471aaa6e13918310b7e2f
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index b93c1af..db18661 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -65,6 +65,12 @@
 	cp $< $@
 	$(call dexpreopt-remove-classes.dex,$@)
 
+ART_TEST_GTEST_VerifierDeps_SRC := $(abspath $(wildcard $(LOCAL_PATH)/VerifierDeps/*.smali))
+ART_TEST_HOST_GTEST_VerifierDeps_DEX := $(dir $(ART_TEST_HOST_GTEST_Main_DEX))$(subst Main,VerifierDeps,$(basename $(notdir $(ART_TEST_HOST_GTEST_Main_DEX))))$(suffix $(ART_TEST_HOST_GTEST_Main_DEX))
+
+$(ART_TEST_HOST_GTEST_VerifierDeps_DEX): $(ART_TEST_GTEST_VerifierDeps_SRC) $(HOST_OUT_EXECUTABLES)/smali
+	 $(HOST_OUT_EXECUTABLES)/smali --output=$@ $(filter %.smali,$^)
+
 # Dex file dependencies for each gtest.
 ART_GTEST_dex2oat_environment_tests_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested
 
@@ -88,6 +94,7 @@
 ART_GTEST_stub_test_DEX_DEPS := AllFields
 ART_GTEST_transaction_test_DEX_DEPS := Transaction
 ART_GTEST_type_lookup_table_test_DEX_DEPS := Lookup
+ART_GTEST_verifier_deps_test_DEX_DEPS := VerifierDeps
 
 # The elf writer test has dependencies on core.oat.
 ART_GTEST_elf_writer_test_HOST_DEPS := $(HOST_CORE_IMAGE_optimizing_no-pic_64) $(HOST_CORE_IMAGE_optimizing_no-pic_32)
@@ -562,11 +569,14 @@
 ART_GTEST_stub_test_DEX_DEPS :=
 ART_GTEST_transaction_test_DEX_DEPS :=
 ART_GTEST_dex2oat_environment_tests_DEX_DEPS :=
+ART_GTEST_verifier_deps_test_DEX_DEPS :=
 ART_VALGRIND_DEPENDENCIES :=
 ART_VALGRIND_TARGET_DEPENDENCIES :=
 $(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval ART_TEST_TARGET_GTEST_$(dir)_DEX :=))
 $(foreach dir,$(GTEST_DEX_DIRECTORIES), $(eval ART_TEST_HOST_GTEST_$(dir)_DEX :=))
 ART_TEST_HOST_GTEST_MainStripped_DEX :=
 ART_TEST_TARGET_GTEST_MainStripped_DEX :=
+ART_TEST_GTEST_VerifierDeps_SRC :=
+ART_TEST_HOST_GTEST_VerifierDeps_DEX :=
 GTEST_DEX_DIRECTORIES :=
 LOCAL_PATH :=
diff --git a/compiler/dex/quick_compiler_callbacks.h b/compiler/dex/quick_compiler_callbacks.h
index 1f69686..824194c 100644
--- a/compiler/dex/quick_compiler_callbacks.h
+++ b/compiler/dex/quick_compiler_callbacks.h
@@ -29,8 +29,10 @@
     QuickCompilerCallbacks(VerificationResults* verification_results,
                            DexFileToMethodInlinerMap* method_inliner_map,
                            CompilerCallbacks::CallbackMode mode)
-        : CompilerCallbacks(mode), verification_results_(verification_results),
-          method_inliner_map_(method_inliner_map) {
+        : CompilerCallbacks(mode),
+          verification_results_(verification_results),
+          method_inliner_map_(method_inliner_map),
+          verifier_deps_(nullptr) {
       CHECK(verification_results != nullptr);
       CHECK(method_inliner_map != nullptr);
     }
@@ -47,9 +49,18 @@
       return true;
     }
 
+    verifier::VerifierDeps* GetVerifierDeps() const OVERRIDE {
+      return verifier_deps_;
+    }
+
+    void SetVerifierDeps(verifier::VerifierDeps* deps) {
+      verifier_deps_ = deps;
+    }
+
   private:
     VerificationResults* const verification_results_;
     DexFileToMethodInlinerMap* const method_inliner_map_;
+    verifier::VerifierDeps* verifier_deps_;
 };
 
 }  // namespace art
diff --git a/compiler/dex/verified_method.cc b/compiler/dex/verified_method.cc
index 4bcd59a..e19fb7b 100644
--- a/compiler/dex/verified_method.cc
+++ b/compiler/dex/verified_method.cc
@@ -231,7 +231,7 @@
                                                                 inst->VRegA_21c()));
         const verifier::RegType& cast_type =
             method_verifier->ResolveCheckedClass(inst->VRegB_21c());
-        is_safe_cast = cast_type.IsStrictlyAssignableFrom(reg_type);
+        is_safe_cast = cast_type.IsStrictlyAssignableFrom(reg_type, method_verifier);
       } else {
         const verifier::RegType& array_type(line->GetRegisterType(method_verifier,
                                                                   inst->VRegB_23x()));
@@ -243,7 +243,7 @@
                                                                     inst->VRegA_23x()));
           const verifier::RegType& component_type = method_verifier->GetRegTypeCache()
               ->GetComponentType(array_type, method_verifier->GetClassLoader());
-          is_safe_cast = component_type.IsStrictlyAssignableFrom(value_type);
+          is_safe_cast = component_type.IsStrictlyAssignableFrom(value_type, method_verifier);
         }
       }
       if (is_safe_cast) {
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 1296bcf..63f5e0c 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -77,6 +77,7 @@
 #include "ScopedLocalRef.h"
 #include "scoped_thread_state_change.h"
 #include "utils.h"
+#include "verifier/verifier_deps.h"
 #include "well_known_classes.h"
 #include "zip_archive.h"
 
@@ -1472,6 +1473,12 @@
 
     dex_files_ = MakeNonOwningPointerVector(opened_dex_files_);
 
+    if (!IsBootImage()) {
+      // Collect verification dependencies when compiling an app.
+      verifier_deps_.reset(new verifier::VerifierDeps(dex_files_));
+      callbacks_->SetVerifierDeps(verifier_deps_.get());
+    }
+
     // We had to postpone the swap decision till now, as this is the point when we actually
     // know about the dex files we're going to use.
 
@@ -2626,6 +2633,9 @@
   std::vector<std::vector<const DexFile*>> dex_files_per_oat_file_;
   std::unordered_map<const DexFile*, size_t> dex_file_oat_index_map_;
 
+  // Collector of verifier dependencies.
+  std::unique_ptr<verifier::VerifierDeps> verifier_deps_;
+
   // Backing storage.
   std::vector<std::string> char_backing_storage_;
 
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 6acc1d8..c00689b 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -198,6 +198,7 @@
         "verifier/reg_type.cc",
         "verifier/reg_type_cache.cc",
         "verifier/register_line.cc",
+        "verifier/verifier_deps.cc",
         "well_known_classes.cc",
         "zip_archive.cc",
 
@@ -558,6 +559,7 @@
         "utils_test.cc",
         "verifier/method_verifier_test.cc",
         "verifier/reg_type_test.cc",
+        "verifier/verifier_deps_test.cc",
         "zip_archive_test.cc",
     ],
     shared_libs: [
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index fec918b..43c38c4 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -48,6 +48,7 @@
 Mutex* Locks::modify_ldt_lock_ = nullptr;
 MutatorMutex* Locks::mutator_lock_ = nullptr;
 Mutex* Locks::profiler_lock_ = nullptr;
+Mutex* Locks::verifier_deps_lock_ = nullptr;
 ReaderWriterMutex* Locks::oat_file_manager_lock_ = nullptr;
 Mutex* Locks::host_dlopen_handles_lock_ = nullptr;
 Mutex* Locks::reference_processor_lock_ = nullptr;
@@ -947,6 +948,7 @@
     DCHECK(deoptimization_lock_ != nullptr);
     DCHECK(heap_bitmap_lock_ != nullptr);
     DCHECK(oat_file_manager_lock_ != nullptr);
+    DCHECK(verifier_deps_lock_ != nullptr);
     DCHECK(host_dlopen_handles_lock_ != nullptr);
     DCHECK(intern_table_lock_ != nullptr);
     DCHECK(jni_libraries_lock_ != nullptr);
@@ -1035,6 +1037,10 @@
     DCHECK(oat_file_manager_lock_ == nullptr);
     oat_file_manager_lock_ = new ReaderWriterMutex("OatFile manager lock", current_lock_level);
 
+    UPDATE_CURRENT_LOCK_LEVEL(kVerifierDepsLock);
+    DCHECK(verifier_deps_lock_ == nullptr);
+    verifier_deps_lock_ = new Mutex("verifier deps lock", current_lock_level);
+
     UPDATE_CURRENT_LOCK_LEVEL(kHostDlOpenHandlesLock);
     DCHECK(host_dlopen_handles_lock_ == nullptr);
     host_dlopen_handles_lock_ = new Mutex("host dlopen handles lock", current_lock_level);
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index d0dc886..8af9fa5 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -83,6 +83,7 @@
   kInternTableLock,
   kOatFileSecondaryLookupLock,
   kHostDlOpenHandlesLock,
+  kVerifierDepsLock,
   kOatFileManagerLock,
   kTracingUniqueMethodsLock,
   kTracingStreamingLock,
@@ -650,8 +651,11 @@
   // Guards opened oat files in OatFileManager.
   static ReaderWriterMutex* oat_file_manager_lock_ ACQUIRED_AFTER(modify_ldt_lock_);
 
+  // Guards verifier dependency collection in VerifierDeps.
+  static Mutex* verifier_deps_lock_ ACQUIRED_AFTER(oat_file_manager_lock_);
+
   // Guards dlopen_handles_ in DlOpenOatFile.
-  static Mutex* host_dlopen_handles_lock_ ACQUIRED_AFTER(oat_file_manager_lock_);
+  static Mutex* host_dlopen_handles_lock_ ACQUIRED_AFTER(verifier_deps_lock_);
 
   // Guards intern table.
   static Mutex* intern_table_lock_ ACQUIRED_AFTER(host_dlopen_handles_lock_);
diff --git a/runtime/compiler_callbacks.h b/runtime/compiler_callbacks.h
index ee797e0..00dedef 100644
--- a/runtime/compiler_callbacks.h
+++ b/runtime/compiler_callbacks.h
@@ -25,6 +25,7 @@
 namespace verifier {
 
 class MethodVerifier;
+class VerifierDeps;
 
 }  // namespace verifier
 
@@ -45,6 +46,8 @@
   // done so. Return false if relocating in this way would be problematic.
   virtual bool IsRelocationPossible() = 0;
 
+  virtual verifier::VerifierDeps* GetVerifierDeps() const = 0;
+
   bool IsBootImage() {
     return mode_ == CallbackMode::kCompileBootImage;
   }
diff --git a/runtime/noop_compiler_callbacks.h b/runtime/noop_compiler_callbacks.h
index 02081cb..9c777cc 100644
--- a/runtime/noop_compiler_callbacks.h
+++ b/runtime/noop_compiler_callbacks.h
@@ -36,6 +36,8 @@
   // to disable the relocation since both deal with writing out the images directly.
   bool IsRelocationPossible() OVERRIDE { return false; }
 
+  verifier::VerifierDeps* GetVerifierDeps() const OVERRIDE { return nullptr; }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(NoopCompilerCallbacks);
 };
diff --git a/runtime/utils.cc b/runtime/utils.cc
index d48edcf..6f10aaa 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -442,6 +442,12 @@
   if ((access_flags & kAccStatic) != 0) {
     result += "static ";
   }
+  if ((access_flags & kAccAbstract) != 0) {
+    result += "abstract ";
+  }
+  if ((access_flags & kAccInterface) != 0) {
+    result += "interface ";
+  }
   if ((access_flags & kAccTransient) != 0) {
     result += "transient ";
   }
diff --git a/runtime/verifier/method_resolution_kind.h b/runtime/verifier/method_resolution_kind.h
new file mode 100644
index 0000000..f72eb7a
--- /dev/null
+++ b/runtime/verifier/method_resolution_kind.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ART_RUNTIME_VERIFIER_METHOD_RESOLUTION_KIND_H_
+#define ART_RUNTIME_VERIFIER_METHOD_RESOLUTION_KIND_H_
+
+namespace art {
+namespace verifier {
+
+// Values corresponding to the method resolution algorithms defined in mirror::Class.
+enum MethodResolutionKind {
+  kDirectMethodResolution,
+  kVirtualMethodResolution,
+  kInterfaceMethodResolution,
+};
+
+}  // namespace verifier
+}  // namespace art
+
+#endif  // ART_RUNTIME_VERIFIER_METHOD_RESOLUTION_KIND_H_
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 6b1170b..f1d3189 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -37,6 +37,7 @@
 #include "indenter.h"
 #include "intern_table.h"
 #include "leb128.h"
+#include "method_resolution_kind.h"
 #include "mirror/class.h"
 #include "mirror/class-inl.h"
 #include "mirror/dex_cache-inl.h"
@@ -47,6 +48,7 @@
 #include "runtime.h"
 #include "scoped_thread_state_change.h"
 #include "utils.h"
+#include "verifier_deps.h"
 #include "handle_scope-inl.h"
 
 namespace art {
@@ -2189,7 +2191,7 @@
             // We really do expect a reference here.
             Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-object returns a non-reference type "
                                               << reg_type;
-          } else if (!return_type.IsAssignableFrom(reg_type)) {
+          } else if (!return_type.IsAssignableFrom(reg_type, this)) {
             if (reg_type.IsUnresolvedTypes() || return_type.IsUnresolvedTypes()) {
               Fail(VERIFY_ERROR_NO_CLASS) << " can't resolve returned type '" << return_type
                   << "' or '" << reg_type << "'";
@@ -2198,7 +2200,7 @@
               // Check whether arrays are involved. They will show a valid class status, even
               // if their components are erroneous.
               if (reg_type.IsArrayTypes() && return_type.IsArrayTypes()) {
-                return_type.CanAssignArray(reg_type, reg_types_, class_loader_, &soft_error);
+                return_type.CanAssignArray(reg_type, reg_types_, class_loader_, this, &soft_error);
                 if (soft_error) {
                   Fail(VERIFY_ERROR_BAD_CLASS_SOFT) << "array with erroneous component type: "
                         << reg_type << " vs " << return_type;
@@ -2486,7 +2488,7 @@
       break;
     case Instruction::THROW: {
       const RegType& res_type = work_line_->GetRegisterType(this, inst->VRegA_11x());
-      if (!reg_types_.JavaLangThrowable(false).IsAssignableFrom(res_type)) {
+      if (!reg_types_.JavaLangThrowable(false).IsAssignableFrom(res_type, this)) {
         if (res_type.IsUninitializedTypes()) {
           Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "thrown exception not initialized";
         } else if (!res_type.IsReferenceTypes()) {
@@ -2639,7 +2641,8 @@
             cast_type.HasClass() &&             // Could be conflict type, make sure it has a class.
             !cast_type.GetClass()->IsInterface() &&
             (orig_type.IsZero() ||
-                orig_type.IsStrictlyAssignableFrom(cast_type.Merge(orig_type, &reg_types_)))) {
+                orig_type.IsStrictlyAssignableFrom(
+                    cast_type.Merge(orig_type, &reg_types_, this), this))) {
           RegisterLine* update_line = RegisterLine::Create(code_item_->registers_size_, this);
           if (inst->Opcode() == Instruction::IF_EQZ) {
             fallthrough_line.reset(update_line);
@@ -3636,8 +3639,13 @@
     return *result;
   }
   if (klass == nullptr && !result->IsUnresolvedTypes()) {
-    dex_cache_->SetResolvedType(class_idx, result->GetClass());
+    klass = result->GetClass();
+    dex_cache_->SetResolvedType(class_idx, klass);
   }
+
+  // Record result of class resolution attempt.
+  VerifierDeps::MaybeRecordClassResolution(*dex_file_, class_idx, klass);
+
   // Check if access is allowed. Unresolved types use xxxWithAccessCheck to
   // check at runtime if access is allowed and so pass here. If result is
   // primitive, skip the access check.
@@ -3664,7 +3672,7 @@
             common_super = &reg_types_.JavaLangThrowable(false);
           } else {
             const RegType& exception = ResolveClassAndCheckAccess(iterator.GetHandlerTypeIndex());
-            if (!reg_types_.JavaLangThrowable(false).IsAssignableFrom(exception)) {
+            if (!reg_types_.JavaLangThrowable(false).IsAssignableFrom(exception, this)) {
               DCHECK(!exception.IsUninitializedTypes());  // Comes from dex, shouldn't be uninit.
               if (exception.IsUnresolvedTypes()) {
                 // We don't know enough about the type. Fail here and let runtime handle it.
@@ -3679,9 +3687,10 @@
             } else if (common_super->Equals(exception)) {
               // odd case, but nothing to do
             } else {
-              common_super = &common_super->Merge(exception, &reg_types_);
+              common_super = &common_super->Merge(exception, &reg_types_, this);
               if (FailOrAbort(this,
-                              reg_types_.JavaLangThrowable(false).IsAssignableFrom(*common_super),
+                              reg_types_.JavaLangThrowable(false).IsAssignableFrom(
+                                  *common_super, this),
                               "java.lang.Throwable is not assignable-from common_super at ",
                               work_insn_idx_)) {
                 break;
@@ -3701,6 +3710,20 @@
   return *common_super;
 }
 
+inline static MethodResolutionKind GetMethodResolutionKind(
+    MethodType method_type, bool is_interface) {
+  if (method_type == METHOD_DIRECT || method_type == METHOD_STATIC) {
+    return kDirectMethodResolution;
+  } else if (method_type == METHOD_INTERFACE) {
+    return kInterfaceMethodResolution;
+  } else if (method_type == METHOD_SUPER && is_interface) {
+    return kInterfaceMethodResolution;
+  } else {
+    DCHECK(method_type == METHOD_VIRTUAL || method_type == METHOD_SUPER);
+    return kVirtualMethodResolution;
+  }
+}
+
 ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess(
     uint32_t dex_method_idx, MethodType method_type) {
   const DexFile::MethodId& method_id = dex_file_->GetMethodId(dex_method_idx);
@@ -3718,6 +3741,7 @@
   const RegType& referrer = GetDeclaringClass();
   auto* cl = Runtime::Current()->GetClassLinker();
   auto pointer_size = cl->GetImagePointerSize();
+  MethodResolutionKind res_kind = GetMethodResolutionKind(method_type, klass->IsInterface());
 
   ArtMethod* res_method = dex_cache_->GetResolvedMethod(dex_method_idx, pointer_size);
   bool stash_method = false;
@@ -3725,35 +3749,44 @@
     const char* name = dex_file_->GetMethodName(method_id);
     const Signature signature = dex_file_->GetMethodSignature(method_id);
 
-    if (method_type == METHOD_DIRECT || method_type == METHOD_STATIC) {
+    if (res_kind == kDirectMethodResolution) {
       res_method = klass->FindDirectMethod(name, signature, pointer_size);
-    } else if (method_type == METHOD_INTERFACE) {
-      res_method = klass->FindInterfaceMethod(name, signature, pointer_size);
-    } else if (method_type == METHOD_SUPER && klass->IsInterface()) {
-      res_method = klass->FindInterfaceMethod(name, signature, pointer_size);
-    } else {
-      DCHECK(method_type == METHOD_VIRTUAL || method_type == METHOD_SUPER);
+    } else if (res_kind == kVirtualMethodResolution) {
       res_method = klass->FindVirtualMethod(name, signature, pointer_size);
+    } else {
+      DCHECK_EQ(res_kind, kInterfaceMethodResolution);
+      res_method = klass->FindInterfaceMethod(name, signature, pointer_size);
     }
+
     if (res_method != nullptr) {
       stash_method = true;
     } else {
       // If a virtual or interface method wasn't found with the expected type, look in
       // the direct methods. This can happen when the wrong invoke type is used or when
       // a class has changed, and will be flagged as an error in later checks.
-      if (method_type == METHOD_INTERFACE ||
-          method_type == METHOD_VIRTUAL ||
-          method_type == METHOD_SUPER) {
+      // Note that in this case, we do not put the resolved method in the Dex cache
+      // because it was not discovered using the expected type of method resolution.
+      if (res_kind != kDirectMethodResolution) {
+        // Record result of the initial resolution attempt.
+        VerifierDeps::MaybeRecordMethodResolution(*dex_file_, dex_method_idx, res_kind, nullptr);
+        // Change resolution type to 'direct' and try to resolve again.
+        res_kind = kDirectMethodResolution;
         res_method = klass->FindDirectMethod(name, signature, pointer_size);
       }
-      if (res_method == nullptr) {
-        Fail(VERIFY_ERROR_NO_METHOD) << "couldn't find method "
-                                     << PrettyDescriptor(klass) << "." << name
-                                     << " " << signature;
-        return nullptr;
-      }
     }
   }
+
+  // Record result of method resolution attempt.
+  VerifierDeps::MaybeRecordMethodResolution(*dex_file_, dex_method_idx, res_kind, res_method);
+
+  if (res_method == nullptr) {
+    Fail(VERIFY_ERROR_NO_METHOD) << "couldn't find method "
+                                 << PrettyDescriptor(klass) << "."
+                                 << dex_file_->GetMethodName(method_id) << " "
+                                 << dex_file_->GetMethodSignature(method_id);
+    return nullptr;
+  }
+
   // Make sure calls to constructors are "direct". There are additional restrictions but we don't
   // enforce them here.
   if (res_method->IsConstructor() && method_type != METHOD_DIRECT) {
@@ -3897,7 +3930,7 @@
             dex_file_->StringByTypeIdx(class_idx),
             false);
       }
-      if (!res_method_class->IsAssignableFrom(adjusted_type)) {
+      if (!res_method_class->IsAssignableFrom(adjusted_type, this)) {
         Fail(adjusted_type.IsUnresolvedTypes()
                  ? VERIFY_ERROR_NO_CLASS
                  : VERIFY_ERROR_BAD_CLASS_SOFT)
@@ -4029,12 +4062,15 @@
   // has a vtable entry for the target method. Or the target is on a interface.
   if (method_type == METHOD_SUPER) {
     uint16_t class_idx = dex_file_->GetMethodId(method_idx).class_idx_;
-    mirror::Class* reference_class = dex_cache_->GetResolvedType(class_idx);
-    if (reference_class == nullptr) {
+    const RegType& reference_type = reg_types_.FromDescriptor(
+        GetClassLoader(),
+        dex_file_->StringByTypeIdx(class_idx),
+        false);
+    if (reference_type.IsUnresolvedTypes()) {
       Fail(VERIFY_ERROR_BAD_CLASS_SOFT) << "Unable to find referenced class from invoke-super";
       return nullptr;
     }
-    if (reference_class->IsInterface()) {
+    if (reference_type.GetClass()->IsInterface()) {
       // TODO Can we verify anything else.
       if (class_idx == class_def_.class_idx_) {
         Fail(VERIFY_ERROR_CLASS_CHANGE) << "Cannot invoke-super on self as interface";
@@ -4046,12 +4082,12 @@
         Fail(VERIFY_ERROR_NO_CLASS) << "Unable to resolve the full class of 'this' used in an"
                                     << "interface invoke-super";
         return nullptr;
-      } else if (!reference_class->IsAssignableFrom(GetDeclaringClass().GetClass())) {
+      } else if (!reference_type.IsStrictlyAssignableFrom(GetDeclaringClass(), this)) {
         Fail(VERIFY_ERROR_CLASS_CHANGE)
             << "invoke-super in " << PrettyClass(GetDeclaringClass().GetClass()) << " in method "
             << PrettyMethod(dex_method_idx_, *dex_file_) << " to method "
             << PrettyMethod(method_idx, *dex_file_) << " references "
-            << "non-super-interface type " << PrettyClass(reference_class);
+            << "non-super-interface type " << PrettyClass(reference_type.GetClass());
         return nullptr;
       }
     } else {
@@ -4062,7 +4098,7 @@
                                     << " to super " << PrettyMethod(res_method);
         return nullptr;
       }
-      if (!reference_class->IsAssignableFrom(GetDeclaringClass().GetClass()) ||
+      if (!reference_type.IsStrictlyAssignableFrom(GetDeclaringClass(), this) ||
           (res_method->GetMethodIndex() >= super.GetClass()->GetVTableLength())) {
         Fail(VERIFY_ERROR_NO_METHOD) << "invalid invoke-super from "
                                     << PrettyMethod(dex_method_idx_, *dex_file_)
@@ -4177,7 +4213,7 @@
     std::string temp;
     const RegType& res_method_class =
         FromClass(klass->GetDescriptor(&temp), klass, klass->CannotBeAssignedFromOtherTypes());
-    if (!res_method_class.IsAssignableFrom(actual_arg_type)) {
+    if (!res_method_class.IsAssignableFrom(actual_arg_type, this)) {
       Fail(actual_arg_type.IsUninitializedTypes()    // Just overcautious - should have never
                ? VERIFY_ERROR_BAD_CLASS_HARD         // quickened this.
                : actual_arg_type.IsUnresolvedTypes()
@@ -4466,8 +4502,11 @@
     return nullptr;  // Can't resolve Class so no more to do here, will do checking at runtime.
   }
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  ArtField* field = class_linker->ResolveFieldJLS(*dex_file_, field_idx, dex_cache_,
-                                                  class_loader_);
+  ArtField* field = class_linker->ResolveFieldJLS(*dex_file_, field_idx, dex_cache_, class_loader_);
+
+  // Record result of the field resolution attempt.
+  VerifierDeps::MaybeRecordFieldResolution(*dex_file_, field_idx, field);
+
   if (field == nullptr) {
     VLOG(verifier) << "Unable to resolve static field " << field_idx << " ("
               << dex_file_->GetFieldName(field_id) << ") in "
@@ -4501,8 +4540,11 @@
     return nullptr;  // Can't resolve Class so no more to do here
   }
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  ArtField* field = class_linker->ResolveFieldJLS(*dex_file_, field_idx, dex_cache_,
-                                                  class_loader_);
+  ArtField* field = class_linker->ResolveFieldJLS(*dex_file_, field_idx, dex_cache_, class_loader_);
+
+  // Record result of the field resolution attempt.
+  VerifierDeps::MaybeRecordFieldResolution(*dex_file_, field_idx, field);
+
   if (field == nullptr) {
     VLOG(verifier) << "Unable to resolve instance field " << field_idx << " ("
               << dex_file_->GetFieldName(field_id) << ") in "
@@ -4536,7 +4578,7 @@
                                           << " of " << PrettyMethod(dex_method_idx_, *dex_file_);
         return nullptr;
       }
-    } else if (!field_klass.IsAssignableFrom(obj_type)) {
+    } else if (!field_klass.IsAssignableFrom(obj_type, this)) {
       // Trying to access C1.field1 using reference of type C2, which is neither C1 or a sub-class
       // of C1. For resolution to occur the declared class of the field must be compatible with
       // obj_type, we've discovered this wasn't so, so report the field didn't exist.
@@ -4643,7 +4685,7 @@
     if (is_primitive) {
       VerifyPrimitivePut(*field_type, insn_type, vregA);
     } else {
-      if (!insn_type.IsAssignableFrom(*field_type)) {
+      if (!insn_type.IsAssignableFrom(*field_type, this)) {
         // If the field type is not a reference, this is a global failure rather than
         // a class change failure as the instructions and the descriptors for the type
         // should have been consistent within the same file at compile time.
@@ -4675,7 +4717,7 @@
         return;
       }
     } else {
-      if (!insn_type.IsAssignableFrom(*field_type)) {
+      if (!insn_type.IsAssignableFrom(*field_type, this)) {
         // If the field type is not a reference, this is a global failure rather than
         // a class change failure as the instructions and the descriptors for the type
         // should have been consistent within the same file at compile time.
@@ -4806,7 +4848,7 @@
         return;
       }
     } else {
-      if (!insn_type.IsAssignableFrom(*field_type)) {
+      if (!insn_type.IsAssignableFrom(*field_type, this)) {
         Fail(VERIFY_ERROR_BAD_CLASS_SOFT) << "expected field " << PrettyField(field)
                                           << " to be compatible with type '" << insn_type
                                           << "' but found type '" << *field_type
@@ -4832,7 +4874,7 @@
         return;
       }
     } else {
-      if (!insn_type.IsAssignableFrom(*field_type)) {
+      if (!insn_type.IsAssignableFrom(*field_type, this)) {
         Fail(VERIFY_ERROR_BAD_CLASS_SOFT) << "expected field " << PrettyField(field)
                                           << " to be compatible with type '" << insn_type
                                           << "' but found type '" << *field_type
diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h
index c4b1c6e..eb8b7a6 100644
--- a/runtime/verifier/method_verifier.h
+++ b/runtime/verifier/method_verifier.h
@@ -180,6 +180,11 @@
 
   uint8_t EncodePcToReferenceMapData() const;
 
+  const DexFile& GetDexFile() const {
+    DCHECK(dex_file_ != nullptr);
+    return *dex_file_;
+  }
+
   uint32_t DexFileVersion() const {
     return dex_file_->GetVersion();
   }
@@ -353,7 +358,8 @@
    *  (3) Iterate through the method, checking type safety and looking
    *      for code flow problems.
    */
-  static FailureData VerifyMethod(Thread* self, uint32_t method_idx,
+  static FailureData VerifyMethod(Thread* self,
+                                  uint32_t method_idx,
                                   const DexFile* dex_file,
                                   Handle<mirror::DexCache> dex_cache,
                                   Handle<mirror::ClassLoader> class_loader,
@@ -842,6 +848,7 @@
   MethodVerifier* link_;
 
   friend class art::Thread;
+  friend class VerifierDepsTest;
 
   DISALLOW_COPY_AND_ASSIGN(MethodVerifier);
 };
diff --git a/runtime/verifier/reg_type-inl.h b/runtime/verifier/reg_type-inl.h
index 861db3c..d93aaa1 100644
--- a/runtime/verifier/reg_type-inl.h
+++ b/runtime/verifier/reg_type-inl.h
@@ -22,6 +22,8 @@
 #include "base/casts.h"
 #include "base/scoped_arena_allocator.h"
 #include "mirror/class.h"
+#include "method_verifier.h"
+#include "verifier_deps.h"
 
 namespace art {
 namespace verifier {
@@ -62,7 +64,10 @@
   }
 }
 
-inline bool RegType::AssignableFrom(const RegType& lhs, const RegType& rhs, bool strict) {
+inline bool RegType::AssignableFrom(const RegType& lhs,
+                                    const RegType& rhs,
+                                    bool strict,
+                                    MethodVerifier* verifier) {
   if (lhs.Equals(rhs)) {
     return true;
   } else {
@@ -104,10 +109,15 @@
         return true;
       } else if (lhs.IsJavaLangObjectArray()) {
         return rhs.IsObjectArrayTypes();  // All reference arrays may be assigned to Object[]
-      } else if (lhs.HasClass() && rhs.HasClass() &&
-                 lhs.GetClass()->IsAssignableFrom(rhs.GetClass())) {
-        // We're assignable from the Class point-of-view.
-        return true;
+      } else if (lhs.HasClass() && rhs.HasClass()) {
+        // Test assignability from the Class point-of-view.
+        bool result = lhs.GetClass()->IsAssignableFrom(rhs.GetClass());
+        // Record assignability dependency. The `verifier` is null during unit tests.
+        if (verifier != nullptr) {
+          VerifierDeps::MaybeRecordAssignability(
+              verifier->GetDexFile(), lhs.GetClass(), rhs.GetClass(), strict, result);
+        }
+        return result;
       } else {
         // Unresolved types are only assignable for null and equality.
         return false;
@@ -116,12 +126,12 @@
   }
 }
 
-inline bool RegType::IsAssignableFrom(const RegType& src) const {
-  return AssignableFrom(*this, src, false);
+inline bool RegType::IsAssignableFrom(const RegType& src, MethodVerifier* verifier) const {
+  return AssignableFrom(*this, src, false, verifier);
 }
 
-inline bool RegType::IsStrictlyAssignableFrom(const RegType& src) const {
-  return AssignableFrom(*this, src, true);
+inline bool RegType::IsStrictlyAssignableFrom(const RegType& src, MethodVerifier* verifier) const {
+  return AssignableFrom(*this, src, true, verifier);
 }
 
 inline const DoubleHiType* DoubleHiType::GetInstance() {
diff --git a/runtime/verifier/reg_type.cc b/runtime/verifier/reg_type.cc
index 5c19969..3bc2acc 100644
--- a/runtime/verifier/reg_type.cc
+++ b/runtime/verifier/reg_type.cc
@@ -21,6 +21,7 @@
 #include "base/casts.h"
 #include "class_linker-inl.h"
 #include "dex_file-inl.h"
+#include "method_verifier.h"
 #include "mirror/class.h"
 #include "mirror/class-inl.h"
 #include "mirror/object-inl.h"
@@ -575,7 +576,9 @@
   return a.IsConstantTypes() ? b : a;
 }
 
-const RegType& RegType::Merge(const RegType& incoming_type, RegTypeCache* reg_types) const {
+const RegType& RegType::Merge(const RegType& incoming_type,
+                              RegTypeCache* reg_types,
+                              MethodVerifier* verifier) const {
   DCHECK(!Equals(incoming_type));  // Trivial equality handled by caller
   // Perform pointer equality tests for undefined and conflict to avoid virtual method dispatch.
   const UndefinedType& undefined = reg_types->Undefined();
@@ -696,13 +699,21 @@
       // have two sub-classes and don't know how to merge. Create a new string-based unresolved
       // type that reflects our lack of knowledge and that allows the rest of the unresolved
       // mechanics to continue.
-      return reg_types->FromUnresolvedMerge(*this, incoming_type);
+      return reg_types->FromUnresolvedMerge(*this, incoming_type, verifier);
     } else {  // Two reference types, compute Join
       mirror::Class* c1 = GetClass();
       mirror::Class* c2 = incoming_type.GetClass();
       DCHECK(c1 != nullptr && !c1->IsPrimitive());
       DCHECK(c2 != nullptr && !c2->IsPrimitive());
       mirror::Class* join_class = ClassJoin(c1, c2);
+      // Record the dependency that both `c1` and `c2` are assignable to `join_class`.
+      // The `verifier` is null during unit tests.
+      if (verifier != nullptr) {
+        VerifierDeps::MaybeRecordAssignability(
+            verifier->GetDexFile(), join_class, c1, true /* strict */, true /* is_assignable */);
+        VerifierDeps::MaybeRecordAssignability(
+            verifier->GetDexFile(), join_class, c2, true /* strict */, true /* is_assignable */);
+      }
       if (c1 == join_class && !IsPreciseReference()) {
         return *this;
       } else if (c2 == join_class && !incoming_type.IsPreciseReference()) {
@@ -873,8 +884,11 @@
   return os;
 }
 
-bool RegType::CanAssignArray(const RegType& src, RegTypeCache& reg_types,
-                             Handle<mirror::ClassLoader> class_loader, bool* soft_error) const {
+bool RegType::CanAssignArray(const RegType& src,
+                             RegTypeCache& reg_types,
+                             Handle<mirror::ClassLoader> class_loader,
+                             MethodVerifier* verifier,
+                             bool* soft_error) const {
   if (!IsArrayTypes() || !src.IsArrayTypes()) {
     *soft_error = false;
     return false;
@@ -891,7 +905,7 @@
   const RegType& cmp1 = reg_types.GetComponentType(*this, class_loader.Get());
   const RegType& cmp2 = reg_types.GetComponentType(src, class_loader.Get());
 
-  if (cmp1.IsAssignableFrom(cmp2)) {
+  if (cmp1.IsAssignableFrom(cmp2, verifier)) {
     return true;
   }
   if (cmp1.IsUnresolvedTypes()) {
@@ -914,7 +928,7 @@
     *soft_error = false;
     return false;
   }
-  return cmp1.CanAssignArray(cmp2, reg_types, class_loader, soft_error);
+  return cmp1.CanAssignArray(cmp2, reg_types, class_loader, verifier, soft_error);
 }
 
 
diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h
index c3ed77a..9170bb1 100644
--- a/runtime/verifier/reg_type.h
+++ b/runtime/verifier/reg_type.h
@@ -43,7 +43,9 @@
 
 namespace verifier {
 
+class MethodVerifier;
 class RegTypeCache;
+
 /*
  * RegType holds information about the "type" of data held in a register.
  */
@@ -210,7 +212,7 @@
   // Note: Object and interface types may always be assigned to one another, see
   // comment on
   // ClassJoin.
-  bool IsAssignableFrom(const RegType& src) const
+  bool IsAssignableFrom(const RegType& src, MethodVerifier* verifier) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Can this array type potentially be assigned by src.
@@ -220,14 +222,17 @@
   // will be set to true iff the assignment test failure should be treated as a soft-error, i.e.,
   // when both array types have the same 'depth' and the 'final' component types may be assignable
   // (both are reference types).
-  bool CanAssignArray(const RegType& src, RegTypeCache& reg_types,
-                      Handle<mirror::ClassLoader> class_loader, bool* soft_error) const
+  bool CanAssignArray(const RegType& src,
+                      RegTypeCache& reg_types,
+                      Handle<mirror::ClassLoader> class_loader,
+                      MethodVerifier* verifier,
+                      bool* soft_error) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Can this type be assigned by src? Variant of IsAssignableFrom that doesn't
   // allow assignment to
   // an interface from an Object.
-  bool IsStrictlyAssignableFrom(const RegType& src) const
+  bool IsStrictlyAssignableFrom(const RegType& src, MethodVerifier* verifier) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Are these RegTypes the same?
@@ -235,36 +240,21 @@
 
   // Compute the merge of this register from one edge (path) with incoming_type
   // from another.
-  const RegType& Merge(const RegType& incoming_type, RegTypeCache* reg_types) const
+  const RegType& Merge(const RegType& incoming_type,
+                       RegTypeCache* reg_types,
+                       MethodVerifier* verifier) const
       REQUIRES_SHARED(Locks::mutator_lock_);
   // Same as above, but also handles the case where incoming_type == this.
-  const RegType& SafeMerge(const RegType& incoming_type, RegTypeCache* reg_types) const
+  const RegType& SafeMerge(const RegType& incoming_type,
+                           RegTypeCache* reg_types,
+                           MethodVerifier* verifier) const
       REQUIRES_SHARED(Locks::mutator_lock_) {
     if (Equals(incoming_type)) {
       return *this;
     }
-    return Merge(incoming_type, reg_types);
+    return Merge(incoming_type, reg_types, verifier);
   }
 
-  /*
-   * A basic Join operation on classes. For a pair of types S and T the Join, written S v T = J, is
-   * S <: J, T <: J and for-all U such that S <: U, T <: U then J <: U. That is J is the parent of
-   * S and T such that there isn't a parent of both S and T that isn't also the parent of J (ie J
-   * is the deepest (lowest upper bound) parent of S and T).
-   *
-   * This operation applies for regular classes and arrays, however, for interface types there
-   * needn't be a partial ordering on the types. We could solve the problem of a lack of a partial
-   * order by introducing sets of types, however, the only operation permissible on an interface is
-   * invoke-interface. In the tradition of Java verifiers [1] we defer the verification of interface
-   * types until an invoke-interface call on the interface typed reference at runtime and allow
-   * the perversion of Object being assignable to an interface type (note, however, that we don't
-   * allow assignment of Object or Interface to any concrete class and are therefore type safe).
-   *
-   * [1] Java bytecode verification: algorithms and formalizations, Xavier Leroy
-   */
-  static mirror::Class* ClassJoin(mirror::Class* s, mirror::Class* t)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   virtual ~RegType() {}
 
   void VisitRoots(RootVisitor* visitor, const RootInfo& root_info) const
@@ -298,7 +288,29 @@
   friend class RegTypeCache;
 
  private:
-  static bool AssignableFrom(const RegType& lhs, const RegType& rhs, bool strict)
+  /*
+   * A basic Join operation on classes. For a pair of types S and T the Join, written S v T = J, is
+   * S <: J, T <: J and for-all U such that S <: U, T <: U then J <: U. That is J is the parent of
+   * S and T such that there isn't a parent of both S and T that isn't also the parent of J (ie J
+   * is the deepest (lowest upper bound) parent of S and T).
+   *
+   * This operation applies for regular classes and arrays, however, for interface types there
+   * needn't be a partial ordering on the types. We could solve the problem of a lack of a partial
+   * order by introducing sets of types, however, the only operation permissible on an interface is
+   * invoke-interface. In the tradition of Java verifiers [1] we defer the verification of interface
+   * types until an invoke-interface call on the interface typed reference at runtime and allow
+   * the perversion of Object being assignable to an interface type (note, however, that we don't
+   * allow assignment of Object or Interface to any concrete class and are therefore type safe).
+   *
+   * [1] Java bytecode verification: algorithms and formalizations, Xavier Leroy
+   */
+  static mirror::Class* ClassJoin(mirror::Class* s, mirror::Class* t)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  static bool AssignableFrom(const RegType& lhs,
+                             const RegType& rhs,
+                             bool strict,
+                             MethodVerifier* verifier)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   DISALLOW_COPY_AND_ASSIGN(RegType);
diff --git a/runtime/verifier/reg_type_cache.cc b/runtime/verifier/reg_type_cache.cc
index 4d4886e..d0493e5 100644
--- a/runtime/verifier/reg_type_cache.cc
+++ b/runtime/verifier/reg_type_cache.cc
@@ -342,7 +342,9 @@
   }
 }
 
-const RegType& RegTypeCache::FromUnresolvedMerge(const RegType& left, const RegType& right) {
+const RegType& RegTypeCache::FromUnresolvedMerge(const RegType& left,
+                                                 const RegType& right,
+                                                 MethodVerifier* verifier) {
   ArenaBitVector types(&arena_,
                        kDefaultArenaBitVectorBytes * kBitsPerByte,  // Allocate at least 8 bytes.
                        true);                                       // Is expandable.
@@ -383,7 +385,7 @@
   }
 
   // Merge the resolved parts. Left and right might be equal, so use SafeMerge.
-  const RegType& resolved_parts_merged = left_resolved->SafeMerge(*right_resolved, this);
+  const RegType& resolved_parts_merged = left_resolved->SafeMerge(*right_resolved, this, verifier);
   // If we get a conflict here, the merge result is a conflict, not an unresolved merge type.
   if (resolved_parts_merged.IsConflict()) {
     return Conflict();
diff --git a/runtime/verifier/reg_type_cache.h b/runtime/verifier/reg_type_cache.h
index 14d9509..df0fe3d 100644
--- a/runtime/verifier/reg_type_cache.h
+++ b/runtime/verifier/reg_type_cache.h
@@ -75,7 +75,9 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   const RegType& FromDescriptor(mirror::ClassLoader* loader, const char* descriptor, bool precise)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  const RegType& FromUnresolvedMerge(const RegType& left, const RegType& right)
+  const RegType& FromUnresolvedMerge(const RegType& left,
+                                     const RegType& right,
+                                     MethodVerifier* verifier)
       REQUIRES_SHARED(Locks::mutator_lock_);
   const RegType& FromUnresolvedSuperClass(const RegType& child)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/verifier/reg_type_test.cc b/runtime/verifier/reg_type_test.cc
index 42a74f8..f2411b5 100644
--- a/runtime/verifier/reg_type_test.cc
+++ b/runtime/verifier/reg_type_test.cc
@@ -79,8 +79,8 @@
   EXPECT_FALSE(precise_lo.CheckWidePair(precise_const));
   EXPECT_TRUE(precise_lo.CheckWidePair(precise_hi));
   // Test Merging.
-  EXPECT_TRUE((long_lo.Merge(precise_lo, &cache)).IsLongTypes());
-  EXPECT_TRUE((long_hi.Merge(precise_hi, &cache)).IsLongHighTypes());
+  EXPECT_TRUE((long_lo.Merge(precise_lo, &cache, /* verifier */ nullptr)).IsLongTypes());
+  EXPECT_TRUE((long_hi.Merge(precise_hi, &cache, /* verifier */ nullptr)).IsLongHighTypes());
 }
 
 TEST_F(RegTypeTest, Primitives) {
@@ -427,7 +427,8 @@
   const RegType& resolved_ref = cache.JavaLangString();
   const RegType& resolved_unintialiesd = cache.Uninitialized(resolved_ref, 10);
   const RegType& unresolved_unintialized = cache.Uninitialized(unresolved_ref, 12);
-  const RegType& unresolved_merged = cache.FromUnresolvedMerge(unresolved_ref, unresolved_ref_another);
+  const RegType& unresolved_merged = cache.FromUnresolvedMerge(
+      unresolved_ref, unresolved_ref_another, /* verifier */ nullptr);
 
   std::string expected = "Unresolved Reference: java.lang.DoesNotExist";
   EXPECT_EQ(expected, unresolved_ref.Dump());
@@ -488,14 +489,14 @@
   RegTypeCache cache_new(true, allocator);
   const RegType& string = cache_new.JavaLangString();
   const RegType& Object = cache_new.JavaLangObject(true);
-  EXPECT_TRUE(string.Merge(Object, &cache_new).IsJavaLangObject());
+  EXPECT_TRUE(string.Merge(Object, &cache_new, /* verifier */ nullptr).IsJavaLangObject());
   // Merge two unresolved types.
   const RegType& ref_type_0 = cache_new.FromDescriptor(nullptr, "Ljava/lang/DoesNotExist;", true);
   EXPECT_TRUE(ref_type_0.IsUnresolvedReference());
   const RegType& ref_type_1 = cache_new.FromDescriptor(nullptr, "Ljava/lang/DoesNotExistToo;", true);
   EXPECT_FALSE(ref_type_0.Equals(ref_type_1));
 
-  const RegType& merged = ref_type_1.Merge(ref_type_0, &cache_new);
+  const RegType& merged = ref_type_1.Merge(ref_type_0, &cache_new, /* verifier */ nullptr);
   EXPECT_TRUE(merged.IsUnresolvedMergedReference());
   RegType& merged_nonconst = const_cast<RegType&>(merged);
 
@@ -518,22 +519,22 @@
   const RegType& imprecise_cst = cache_new.FromCat1Const(kTestConstantValue, false);
   {
     // float MERGE precise cst => float.
-    const RegType& merged = float_type.Merge(precise_cst, &cache_new);
+    const RegType& merged = float_type.Merge(precise_cst, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsFloat());
   }
   {
     // precise cst MERGE float => float.
-    const RegType& merged = precise_cst.Merge(float_type, &cache_new);
+    const RegType& merged = precise_cst.Merge(float_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsFloat());
   }
   {
     // float MERGE imprecise cst => float.
-    const RegType& merged = float_type.Merge(imprecise_cst, &cache_new);
+    const RegType& merged = float_type.Merge(imprecise_cst, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsFloat());
   }
   {
     // imprecise cst MERGE float => float.
-    const RegType& merged = imprecise_cst.Merge(float_type, &cache_new);
+    const RegType& merged = imprecise_cst.Merge(float_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsFloat());
   }
 }
@@ -554,42 +555,46 @@
   const RegType& imprecise_cst_hi = cache_new.FromCat2ConstHi(kTestConstantValue, false);
   {
     // lo MERGE precise cst lo => lo.
-    const RegType& merged = long_lo_type.Merge(precise_cst_lo, &cache_new);
+    const RegType& merged = long_lo_type.Merge(precise_cst_lo, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsLongLo());
   }
   {
     // precise cst lo MERGE lo => lo.
-    const RegType& merged = precise_cst_lo.Merge(long_lo_type, &cache_new);
+    const RegType& merged = precise_cst_lo.Merge(long_lo_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsLongLo());
   }
   {
     // lo MERGE imprecise cst lo => lo.
-    const RegType& merged = long_lo_type.Merge(imprecise_cst_lo, &cache_new);
+    const RegType& merged = long_lo_type.Merge(
+        imprecise_cst_lo, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsLongLo());
   }
   {
     // imprecise cst lo MERGE lo => lo.
-    const RegType& merged = imprecise_cst_lo.Merge(long_lo_type, &cache_new);
+    const RegType& merged = imprecise_cst_lo.Merge(
+        long_lo_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsLongLo());
   }
   {
     // hi MERGE precise cst hi => hi.
-    const RegType& merged = long_hi_type.Merge(precise_cst_hi, &cache_new);
+    const RegType& merged = long_hi_type.Merge(precise_cst_hi, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsLongHi());
   }
   {
     // precise cst hi MERGE hi => hi.
-    const RegType& merged = precise_cst_hi.Merge(long_hi_type, &cache_new);
+    const RegType& merged = precise_cst_hi.Merge(long_hi_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsLongHi());
   }
   {
     // hi MERGE imprecise cst hi => hi.
-    const RegType& merged = long_hi_type.Merge(imprecise_cst_hi, &cache_new);
+    const RegType& merged = long_hi_type.Merge(
+        imprecise_cst_hi, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsLongHi());
   }
   {
     // imprecise cst hi MERGE hi => hi.
-    const RegType& merged = imprecise_cst_hi.Merge(long_hi_type, &cache_new);
+    const RegType& merged = imprecise_cst_hi.Merge(
+        long_hi_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsLongHi());
   }
 }
@@ -610,42 +615,50 @@
   const RegType& imprecise_cst_hi = cache_new.FromCat2ConstHi(kTestConstantValue, false);
   {
     // lo MERGE precise cst lo => lo.
-    const RegType& merged = double_lo_type.Merge(precise_cst_lo, &cache_new);
+    const RegType& merged = double_lo_type.Merge(
+        precise_cst_lo, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsDoubleLo());
   }
   {
     // precise cst lo MERGE lo => lo.
-    const RegType& merged = precise_cst_lo.Merge(double_lo_type, &cache_new);
+    const RegType& merged = precise_cst_lo.Merge(
+        double_lo_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsDoubleLo());
   }
   {
     // lo MERGE imprecise cst lo => lo.
-    const RegType& merged = double_lo_type.Merge(imprecise_cst_lo, &cache_new);
+    const RegType& merged = double_lo_type.Merge(
+        imprecise_cst_lo, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsDoubleLo());
   }
   {
     // imprecise cst lo MERGE lo => lo.
-    const RegType& merged = imprecise_cst_lo.Merge(double_lo_type, &cache_new);
+    const RegType& merged = imprecise_cst_lo.Merge(
+        double_lo_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsDoubleLo());
   }
   {
     // hi MERGE precise cst hi => hi.
-    const RegType& merged = double_hi_type.Merge(precise_cst_hi, &cache_new);
+    const RegType& merged = double_hi_type.Merge(
+        precise_cst_hi, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsDoubleHi());
   }
   {
     // precise cst hi MERGE hi => hi.
-    const RegType& merged = precise_cst_hi.Merge(double_hi_type, &cache_new);
+    const RegType& merged = precise_cst_hi.Merge(
+        double_hi_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsDoubleHi());
   }
   {
     // hi MERGE imprecise cst hi => hi.
-    const RegType& merged = double_hi_type.Merge(imprecise_cst_hi, &cache_new);
+    const RegType& merged = double_hi_type.Merge(
+        imprecise_cst_hi, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsDoubleHi());
   }
   {
     // imprecise cst hi MERGE hi => hi.
-    const RegType& merged = imprecise_cst_hi.Merge(double_hi_type, &cache_new);
+    const RegType& merged = imprecise_cst_hi.Merge(
+        double_hi_type, &cache_new, /* verifier */ nullptr);
     EXPECT_TRUE(merged.IsDoubleHi());
   }
 }
diff --git a/runtime/verifier/register_line-inl.h b/runtime/verifier/register_line-inl.h
index d2f3485..3823143 100644
--- a/runtime/verifier/register_line-inl.h
+++ b/runtime/verifier/register_line-inl.h
@@ -131,7 +131,7 @@
                                              const RegType& check_type) {
   // Verify the src register type against the check type refining the type of the register
   const RegType& src_type = GetRegisterType(verifier, vsrc);
-  if (UNLIKELY(!check_type.IsAssignableFrom(src_type))) {
+  if (UNLIKELY(!check_type.IsAssignableFrom(src_type, verifier))) {
     enum VerifyError fail_type;
     if (!check_type.IsNonZeroReferenceTypes() || !src_type.IsNonZeroReferenceTypes()) {
       // Hard fail if one of the types is primitive, since they are concretely known.
diff --git a/runtime/verifier/register_line.cc b/runtime/verifier/register_line.cc
index 71aa94e..823336c 100644
--- a/runtime/verifier/register_line.cc
+++ b/runtime/verifier/register_line.cc
@@ -73,7 +73,7 @@
   DCHECK(check_type1.CheckWidePair(check_type2));
   // Verify the src register type against the check type refining the type of the register
   const RegType& src_type = GetRegisterType(verifier, vsrc);
-  if (!check_type1.IsAssignableFrom(src_type)) {
+  if (!check_type1.IsAssignableFrom(src_type, verifier)) {
     verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "register v" << vsrc << " has type " << src_type
                                << " but expected " << check_type1;
     return false;
@@ -433,7 +433,8 @@
     if (line_[idx] != incoming_line->line_[idx]) {
       const RegType& incoming_reg_type = incoming_line->GetRegisterType(verifier, idx);
       const RegType& cur_type = GetRegisterType(verifier, idx);
-      const RegType& new_type = cur_type.Merge(incoming_reg_type, verifier->GetRegTypeCache());
+      const RegType& new_type = cur_type.Merge(
+          incoming_reg_type, verifier->GetRegTypeCache(), verifier);
       changed = changed || !cur_type.Equals(new_type);
       line_[idx] = new_type.GetId();
     }
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
new file mode 100644
index 0000000..4953483
--- /dev/null
+++ b/runtime/verifier/verifier_deps.cc
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "verifier_deps.h"
+
+#include "compiler_callbacks.h"
+#include "mirror/class-inl.h"
+#include "runtime.h"
+
+namespace art {
+namespace verifier {
+
+VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files) {
+  MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+  for (const DexFile* dex_file : dex_files) {
+    DCHECK(GetDexFileDeps(*dex_file) == nullptr);
+    std::unique_ptr<DexFileDeps> deps(new DexFileDeps());
+    dex_deps_.emplace(dex_file, std::move(deps));
+  }
+}
+
+VerifierDeps::DexFileDeps* VerifierDeps::GetDexFileDeps(const DexFile& dex_file) {
+  auto it = dex_deps_.find(&dex_file);
+  return (it == dex_deps_.end()) ? nullptr : it->second.get();
+}
+
+template <typename T>
+uint16_t VerifierDeps::GetAccessFlags(T* element) {
+  static_assert(kAccJavaFlagsMask == 0xFFFF, "Unexpected value of a constant");
+  if (element == nullptr) {
+    return VerifierDeps::kUnresolvedMarker;
+  } else {
+    uint16_t access_flags = Low16Bits(element->GetAccessFlags());
+    CHECK_NE(access_flags, VerifierDeps::kUnresolvedMarker);
+    return access_flags;
+  }
+}
+
+template <typename T>
+uint32_t VerifierDeps::GetDeclaringClassStringId(const DexFile& dex_file, T* element) {
+  static_assert(kAccJavaFlagsMask == 0xFFFF, "Unexpected value of a constant");
+  if (element == nullptr) {
+    return VerifierDeps::kUnresolvedMarker;
+  } else {
+    std::string temp;
+    uint32_t string_id = GetIdFromString(
+        dex_file, element->GetDeclaringClass()->GetDescriptor(&temp));
+    return string_id;
+  }
+}
+
+uint32_t VerifierDeps::GetIdFromString(const DexFile& dex_file, const std::string& str) {
+  const DexFile::StringId* string_id = dex_file.FindStringId(str.c_str());
+  if (string_id != nullptr) {
+    // String is in the DEX file. Return its ID.
+    return dex_file.GetIndexForStringId(*string_id);
+  }
+
+  // String is not in the DEX file. Assign a new ID to it which is higher than
+  // the number of strings in the DEX file.
+
+  DexFileDeps* deps = GetDexFileDeps(dex_file);
+  DCHECK(deps != nullptr);
+
+  uint32_t num_ids_in_dex = dex_file.NumStringIds();
+  uint32_t num_extra_ids = deps->strings_.size();
+
+  for (size_t i = 0; i < num_extra_ids; ++i) {
+    if (deps->strings_[i] == str) {
+      return num_ids_in_dex + i;
+    }
+  }
+
+  deps->strings_.push_back(str);
+
+  uint32_t new_id = num_ids_in_dex + num_extra_ids;
+  CHECK_GE(new_id, num_ids_in_dex);  // check for overflows
+  DCHECK_EQ(str, GetStringFromId(dex_file, new_id));
+
+  return new_id;
+}
+
+std::string VerifierDeps::GetStringFromId(const DexFile& dex_file, uint32_t string_id) {
+  uint32_t num_ids_in_dex = dex_file.NumStringIds();
+  if (string_id < num_ids_in_dex) {
+    return std::string(dex_file.StringDataByIdx(string_id));
+  } else {
+    DexFileDeps* deps = GetDexFileDeps(dex_file);
+    DCHECK(deps != nullptr);
+    string_id -= num_ids_in_dex;
+    CHECK_LT(string_id, deps->strings_.size());
+    return deps->strings_[string_id];
+  }
+}
+
+bool VerifierDeps::IsInClassPath(mirror::Class* klass) {
+  DCHECK(klass != nullptr);
+
+  mirror::DexCache* dex_cache = klass->GetDexCache();
+  if (dex_cache == nullptr) {
+    // This is a synthesized class, in this case always an array. They are not
+    // defined in the compiled DEX files and therefore are part of the classpath.
+    // We could avoid recording dependencies on arrays with component types in
+    // the compiled DEX files but we choose to record them anyway so as to
+    // record the access flags VM sets for array classes.
+    DCHECK(klass->IsArrayClass()) << PrettyDescriptor(klass);
+    return true;
+  }
+
+  const DexFile* dex_file = dex_cache->GetDexFile();
+  DCHECK(dex_file != nullptr);
+
+  // Test if the `dex_deps_` contains an entry for `dex_file`. If not, the dex
+  // file was not registered as being compiled and we assume `klass` is in the
+  // classpath.
+  return (GetDexFileDeps(*dex_file) == nullptr);
+}
+
+void VerifierDeps::AddClassResolution(const DexFile& dex_file,
+                                      uint16_t type_idx,
+                                      mirror::Class* klass) {
+  DexFileDeps* dex_deps = GetDexFileDeps(dex_file);
+  if (dex_deps == nullptr) {
+    // This invocation is from verification of a dex file which is not being compiled.
+    return;
+  }
+
+  if (klass != nullptr && !IsInClassPath(klass)) {
+    // Class resolved into one of the DEX files which are being compiled.
+    // This is not a classpath dependency.
+    return;
+  }
+
+  MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+  dex_deps->classes_.emplace(ClassResolution(type_idx, GetAccessFlags(klass)));
+}
+
+void VerifierDeps::AddFieldResolution(const DexFile& dex_file,
+                                      uint32_t field_idx,
+                                      ArtField* field) {
+  DexFileDeps* dex_deps = GetDexFileDeps(dex_file);
+  if (dex_deps == nullptr) {
+    // This invocation is from verification of a dex file which is not being compiled.
+    return;
+  }
+
+  if (field != nullptr && !IsInClassPath(field->GetDeclaringClass())) {
+    // Field resolved into one of the DEX files which are being compiled.
+    // This is not a classpath dependency.
+    return;
+  }
+
+  MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+  dex_deps->fields_.emplace(FieldResolution(
+      field_idx, GetAccessFlags(field), GetDeclaringClassStringId(dex_file, field)));
+}
+
+void VerifierDeps::AddMethodResolution(const DexFile& dex_file,
+                                       uint32_t method_idx,
+                                       MethodResolutionKind resolution_kind,
+                                       ArtMethod* method) {
+  DexFileDeps* dex_deps = GetDexFileDeps(dex_file);
+  if (dex_deps == nullptr) {
+    // This invocation is from verification of a dex file which is not being compiled.
+    return;
+  }
+
+  if (method != nullptr && !IsInClassPath(method->GetDeclaringClass())) {
+    // Method resolved into one of the DEX files which are being compiled.
+    // This is not a classpath dependency.
+    return;
+  }
+
+  MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+  MethodResolution method_tuple(method_idx,
+                                GetAccessFlags(method),
+                                GetDeclaringClassStringId(dex_file, method));
+  if (resolution_kind == kDirectMethodResolution) {
+    dex_deps->direct_methods_.emplace(method_tuple);
+  } else if (resolution_kind == kVirtualMethodResolution) {
+    dex_deps->virtual_methods_.emplace(method_tuple);
+  } else {
+    DCHECK_EQ(resolution_kind, kInterfaceMethodResolution);
+    dex_deps->interface_methods_.emplace(method_tuple);
+  }
+}
+
+void VerifierDeps::AddAssignability(const DexFile& dex_file,
+                                    mirror::Class* destination,
+                                    mirror::Class* source,
+                                    bool is_strict,
+                                    bool is_assignable) {
+  // Test that the method is only called on reference types.
+  // Note that concurrent verification of `destination` and `source` may have
+  // set their status to erroneous. However, the tests performed below rely
+  // merely on no issues with linking (valid access flags, superclass and
+  // implemented interfaces). If the class at any point reached the IsResolved
+  // status, the requirement holds. This is guaranteed by RegTypeCache::ResolveClass.
+  DCHECK(destination != nullptr && !destination->IsPrimitive());
+  DCHECK(source != nullptr && !source->IsPrimitive());
+
+  if (destination == source ||
+      destination->IsObjectClass() ||
+      (!is_strict && destination->IsInterface())) {
+    // Cases when `destination` is trivially assignable from `source`.
+    DCHECK(is_assignable);
+    return;
+  }
+
+  DCHECK_EQ(is_assignable, destination->IsAssignableFrom(source));
+
+  if (destination->IsArrayClass() && source->IsArrayClass()) {
+    // Both types are arrays. Break down to component types and add recursively.
+    // This helps filter out destinations from compiled DEX files (see below)
+    // and deduplicate entries with the same canonical component type.
+    mirror::Class* destination_component = destination->GetComponentType();
+    mirror::Class* source_component = source->GetComponentType();
+
+    // Only perform the optimization if both types are resolved which guarantees
+    // that they linked successfully, as required at the top of this method.
+    if (destination_component->IsResolved() && source_component->IsResolved()) {
+      AddAssignability(dex_file,
+                       destination_component,
+                       source_component,
+                       /* is_strict */ true,
+                       is_assignable);
+      return;
+    }
+  }
+
+  DexFileDeps* dex_deps = GetDexFileDeps(dex_file);
+  if (dex_deps == nullptr) {
+    // This invocation is from verification of a DEX file which is not being compiled.
+    return;
+  }
+
+  if (!IsInClassPath(destination) && !IsInClassPath(source)) {
+    // Both `destination` and `source` are defined in the compiled DEX files.
+    // No need to record a dependency.
+    return;
+  }
+
+  MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+
+  // Get string IDs for both descriptors and store in the appropriate set.
+
+  std::string temp1, temp2;
+  std::string destination_desc(destination->GetDescriptor(&temp1));
+  std::string source_desc(source->GetDescriptor(&temp2));
+  uint32_t destination_id = GetIdFromString(dex_file, destination_desc);
+  uint32_t source_id = GetIdFromString(dex_file, source_desc);
+
+  if (is_assignable) {
+    dex_deps->assignable_types_.emplace(TypeAssignability(destination_id, source_id));
+  } else {
+    dex_deps->unassignable_types_.emplace(TypeAssignability(destination_id, source_id));
+  }
+}
+
+static inline VerifierDeps* GetVerifierDepsSingleton() {
+  CompilerCallbacks* callbacks = Runtime::Current()->GetCompilerCallbacks();
+  if (callbacks == nullptr) {
+    return nullptr;
+  }
+  return callbacks->GetVerifierDeps();
+}
+
+void VerifierDeps::MaybeRecordClassResolution(const DexFile& dex_file,
+                                              uint16_t type_idx,
+                                              mirror::Class* klass) {
+  VerifierDeps* singleton = GetVerifierDepsSingleton();
+  if (singleton != nullptr) {
+    singleton->AddClassResolution(dex_file, type_idx, klass);
+  }
+}
+
+void VerifierDeps::MaybeRecordFieldResolution(const DexFile& dex_file,
+                                              uint32_t field_idx,
+                                              ArtField* field) {
+  VerifierDeps* singleton = GetVerifierDepsSingleton();
+  if (singleton != nullptr) {
+    singleton->AddFieldResolution(dex_file, field_idx, field);
+  }
+}
+
+void VerifierDeps::MaybeRecordMethodResolution(const DexFile& dex_file,
+                                               uint32_t method_idx,
+                                               MethodResolutionKind resolution_kind,
+                                               ArtMethod* method) {
+  VerifierDeps* singleton = GetVerifierDepsSingleton();
+  if (singleton != nullptr) {
+    singleton->AddMethodResolution(dex_file, method_idx, resolution_kind, method);
+  }
+}
+
+void VerifierDeps::MaybeRecordAssignability(const DexFile& dex_file,
+                                            mirror::Class* destination,
+                                            mirror::Class* source,
+                                            bool is_strict,
+                                            bool is_assignable) {
+  VerifierDeps* singleton = GetVerifierDepsSingleton();
+  if (singleton != nullptr) {
+    singleton->AddAssignability(dex_file, destination, source, is_strict, is_assignable);
+  }
+}
+
+}  // namespace verifier
+}  // namespace art
diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h
new file mode 100644
index 0000000..da63d67
--- /dev/null
+++ b/runtime/verifier/verifier_deps.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ART_RUNTIME_VERIFIER_VERIFIER_DEPS_H_
+#define ART_RUNTIME_VERIFIER_VERIFIER_DEPS_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "art_field.h"
+#include "art_method.h"
+#include "base/mutex.h"
+#include "method_resolution_kind.h"
+#include "os.h"
+
+namespace art {
+namespace verifier {
+
+// Verification dependencies collector class used by the MethodVerifier to record
+// resolution outcomes and type assignability tests of classes/methods/fields
+// not present in the set of compiled DEX files, that is classes/methods/fields
+// defined in the classpath.
+// The compilation driver initializes the class and registers all DEX files
+// which are being compiled. Classes defined in DEX files outside of this set
+// (or synthesized classes without associated DEX files) are considered being
+// in the classpath.
+// During code-flow verification, the MethodVerifier informs the VerifierDeps
+// singleton about the outcome of every resolution and assignability test, and
+// the singleton records them if their outcome may change with changes in the
+// classpath.
+class VerifierDeps {
+ public:
+  explicit VerifierDeps(const std::vector<const DexFile*>& dex_files)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
+  // Record the outcome `klass` of resolving type `type_idx` from `dex_file`.
+  // If `klass` is null, the class is assumed unresolved.
+  static void MaybeRecordClassResolution(const DexFile& dex_file,
+                                         uint16_t type_idx,
+                                         mirror::Class* klass)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
+  // Record the outcome `field` of resolving field `field_idx` from `dex_file`.
+  // If `field` is null, the field is assumed unresolved.
+  static void MaybeRecordFieldResolution(const DexFile& dex_file,
+                                         uint32_t field_idx,
+                                         ArtField* field)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
+  // Record the outcome `method` of resolving method `method_idx` from `dex_file`
+  // using `res_kind` kind of method resolution algorithm. If `method` is null,
+  // the method is assumed unresolved.
+  static void MaybeRecordMethodResolution(const DexFile& dex_file,
+                                          uint32_t method_idx,
+                                          MethodResolutionKind res_kind,
+                                          ArtMethod* method)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
+  // Record the outcome `is_assignable` of type assignability test from `source`
+  // to `destination` as defined by RegType::AssignableFrom. `dex_file` is the
+  // owner of the method for which MethodVerifier performed the assignability test.
+  static void MaybeRecordAssignability(const DexFile& dex_file,
+                                       mirror::Class* destination,
+                                       mirror::Class* source,
+                                       bool is_strict,
+                                       bool is_assignable)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
+ private:
+  static constexpr uint16_t kUnresolvedMarker = static_cast<uint16_t>(-1);
+
+  using ClassResolutionBase = std::tuple<uint32_t, uint16_t>;
+  struct ClassResolution : public ClassResolutionBase {
+    ClassResolution(uint32_t type_idx, uint16_t access_flags)
+        : ClassResolutionBase(type_idx, access_flags) {}
+    ClassResolution(const ClassResolution&) = default;
+
+    bool IsResolved() const { return GetAccessFlags() != kUnresolvedMarker; }
+    uint32_t GetDexTypeIndex() const { return std::get<0>(*this); }
+    uint16_t GetAccessFlags() const { return std::get<1>(*this); }
+  };
+
+  using FieldResolutionBase = std::tuple<uint32_t, uint16_t, uint32_t>;
+  struct FieldResolution : public FieldResolutionBase {
+    FieldResolution(uint32_t field_idx, uint16_t access_flags, uint32_t declaring_class_idx)
+        : FieldResolutionBase(field_idx, access_flags, declaring_class_idx) {}
+    FieldResolution(const FieldResolution&) = default;
+
+    bool IsResolved() const { return GetAccessFlags() != kUnresolvedMarker; }
+    uint32_t GetDexFieldIndex() const { return std::get<0>(*this); }
+    uint16_t GetAccessFlags() const { return std::get<1>(*this); }
+    uint32_t GetDeclaringClassIndex() const { return std::get<2>(*this); }
+  };
+
+  using MethodResolutionBase = std::tuple<uint32_t, uint16_t, uint32_t>;
+  struct MethodResolution : public MethodResolutionBase {
+    MethodResolution(uint32_t method_idx, uint16_t access_flags, uint32_t declaring_class_idx)
+        : MethodResolutionBase(method_idx, access_flags, declaring_class_idx) {}
+    MethodResolution(const MethodResolution&) = default;
+
+    bool IsResolved() const { return GetAccessFlags() != kUnresolvedMarker; }
+    uint32_t GetDexMethodIndex() const { return std::get<0>(*this); }
+    uint16_t GetAccessFlags() const { return std::get<1>(*this); }
+    uint32_t GetDeclaringClassIndex() const { return std::get<2>(*this); }
+  };
+
+  using TypeAssignabilityBase = std::tuple<uint32_t, uint32_t>;
+  struct TypeAssignability : public std::tuple<uint32_t, uint32_t> {
+    TypeAssignability(uint32_t destination_idx, uint32_t source_idx)
+        : TypeAssignabilityBase(destination_idx, source_idx) {}
+    TypeAssignability(const TypeAssignability&) = default;
+
+    uint32_t GetDestination() const { return std::get<0>(*this); }
+    uint32_t GetSource() const { return std::get<1>(*this); }
+  };
+
+  // Data structure representing dependencies collected during verification of
+  // methods inside one DexFile.
+  struct DexFileDeps {
+    // Vector of strings which are not present in the corresponding DEX file.
+    // These are referred to with ids starting with `NumStringIds()` of that DexFile.
+    std::vector<std::string> strings_;
+
+    // Set of class pairs recording the outcome of assignability test from one
+    // of the two types to the other.
+    std::set<TypeAssignability> assignable_types_;
+    std::set<TypeAssignability> unassignable_types_;
+
+    // Sets of recorded class/field/method resolutions.
+    std::set<ClassResolution> classes_;
+    std::set<FieldResolution> fields_;
+    std::set<MethodResolution> direct_methods_;
+    std::set<MethodResolution> virtual_methods_;
+    std::set<MethodResolution> interface_methods_;
+  };
+
+  // Finds the DexFileDep instance associated with `dex_file`, or nullptr if
+  // `dex_file` is not reported as being compiled.
+  // We disable thread safety analysis. The method only reads the key set of
+  // `dex_deps_` which stays constant after initialization.
+  DexFileDeps* GetDexFileDeps(const DexFile& dex_file)
+      NO_THREAD_SAFETY_ANALYSIS;
+
+  // Returns true if `klass` is null or not defined in any of dex files which
+  // were reported as being compiled.
+  bool IsInClassPath(mirror::Class* klass)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Returns the index of `str`. If it is defined in `dex_file_`, this is the dex
+  // string ID. If not, an ID is assigned to the string and cached in `strings_`
+  // of the corresponding DexFileDeps structure (either provided or inferred from
+  // `dex_file`).
+  uint32_t GetIdFromString(const DexFile& dex_file, const std::string& str)
+      REQUIRES(Locks::verifier_deps_lock_);
+
+  // Returns the string represented by `id`.
+  std::string GetStringFromId(const DexFile& dex_file, uint32_t string_id)
+      REQUIRES(Locks::verifier_deps_lock_);
+
+  // Returns the bytecode access flags of `element` (bottom 16 bits), or
+  // `kUnresolvedMarker` if `element` is null.
+  template <typename T>
+  uint16_t GetAccessFlags(T* element)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Returns a string ID of the descriptor of the declaring class of `element`,
+  // or `kUnresolvedMarker` if `element` is null.
+  template <typename T>
+  uint32_t GetDeclaringClassStringId(const DexFile& dex_file, T* element)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(Locks::verifier_deps_lock_);
+
+  void AddClassResolution(const DexFile& dex_file,
+                          uint16_t type_idx,
+                          mirror::Class* klass)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
+  void AddFieldResolution(const DexFile& dex_file,
+                          uint32_t field_idx,
+                          ArtField* field)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
+  void AddMethodResolution(const DexFile& dex_file,
+                           uint32_t method_idx,
+                           MethodResolutionKind res_kind,
+                           ArtMethod* method)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
+  void AddAssignability(const DexFile& dex_file,
+                        mirror::Class* destination,
+                        mirror::Class* source,
+                        bool is_strict,
+                        bool is_assignable)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::verifier_deps_lock_);
+
+  // Map from DexFiles into dependencies collected from verification of their methods.
+  std::map<const DexFile*, std::unique_ptr<DexFileDeps>> dex_deps_
+      GUARDED_BY(Locks::verifier_deps_lock_);
+
+  friend class VerifierDepsTest;
+  ART_FRIEND_TEST(VerifierDepsTest, StringToId);
+};
+
+}  // namespace verifier
+}  // namespace art
+
+#endif  // ART_RUNTIME_VERIFIER_VERIFIER_DEPS_H_
diff --git a/runtime/verifier/verifier_deps_test.cc b/runtime/verifier/verifier_deps_test.cc
new file mode 100644
index 0000000..41a9ad3
--- /dev/null
+++ b/runtime/verifier/verifier_deps_test.cc
@@ -0,0 +1,986 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "verifier_deps.h"
+
+#include "class_linker.h"
+#include "common_runtime_test.h"
+#include "compiler_callbacks.h"
+#include "dex_file.h"
+#include "handle_scope-inl.h"
+#include "method_verifier-inl.h"
+#include "mirror/class_loader.h"
+#include "runtime.h"
+#include "thread.h"
+#include "scoped_thread_state_change.h"
+
+namespace art {
+namespace verifier {
+
+class VerifierDepsCompilerCallbacks : public CompilerCallbacks {
+ public:
+  explicit VerifierDepsCompilerCallbacks()
+      : CompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileApp),
+        deps_(nullptr) {}
+
+  void MethodVerified(verifier::MethodVerifier* verifier ATTRIBUTE_UNUSED) OVERRIDE {}
+  void ClassRejected(ClassReference ref ATTRIBUTE_UNUSED) OVERRIDE {}
+  bool IsRelocationPossible() OVERRIDE { return false; }
+
+  verifier::VerifierDeps* GetVerifierDeps() const OVERRIDE { return deps_; }
+  void SetVerifierDeps(verifier::VerifierDeps* deps) { deps_ = deps; }
+
+ private:
+  verifier::VerifierDeps* deps_;
+};
+
+class VerifierDepsTest : public CommonRuntimeTest {
+ public:
+  void SetUpRuntimeOptions(RuntimeOptions* options) {
+    CommonRuntimeTest::SetUpRuntimeOptions(options);
+    callbacks_.reset(new VerifierDepsCompilerCallbacks());
+  }
+
+  mirror::Class* FindClassByName(const std::string& name, ScopedObjectAccess* soa)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    StackHandleScope<1> hs(Thread::Current());
+    Handle<mirror::ClassLoader> class_loader_handle(
+        hs.NewHandle(soa->Decode<mirror::ClassLoader*>(class_loader_)));
+    mirror::Class* result = class_linker_->FindClass(Thread::Current(),
+                                                     name.c_str(),
+                                                     class_loader_handle);
+    DCHECK(result != nullptr) << name;
+    return result;
+  }
+
+  void LoadDexFile(ScopedObjectAccess* soa) REQUIRES_SHARED(Locks::mutator_lock_) {
+    class_loader_ = LoadDex("VerifierDeps");
+    std::vector<const DexFile*> dex_files = GetDexFiles(class_loader_);
+    CHECK_EQ(dex_files.size(), 1u);
+    dex_file_ = dex_files.front();
+
+    mirror::ClassLoader* loader = soa->Decode<mirror::ClassLoader*>(class_loader_);
+    class_linker_->RegisterDexFile(*dex_file_, loader);
+
+    klass_Main_ = FindClassByName("LMain;", soa);
+    CHECK(klass_Main_ != nullptr);
+
+    verifier_deps_.reset(new verifier::VerifierDeps(dex_files));
+    VerifierDepsCompilerCallbacks* callbacks =
+        reinterpret_cast<VerifierDepsCompilerCallbacks*>(callbacks_.get());
+    callbacks->SetVerifierDeps(verifier_deps_.get());
+  }
+
+  bool VerifyMethod(const std::string& method_name) {
+    ScopedObjectAccess soa(Thread::Current());
+    LoadDexFile(&soa);
+
+    StackHandleScope<2> hs(Thread::Current());
+    Handle<mirror::ClassLoader> class_loader_handle(
+        hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader_)));
+    Handle<mirror::DexCache> dex_cache_handle(hs.NewHandle(klass_Main_->GetDexCache()));
+
+    const DexFile::ClassDef* class_def = klass_Main_->GetClassDef();
+    const uint8_t* class_data = dex_file_->GetClassData(*class_def);
+    CHECK(class_data != nullptr);
+
+    ClassDataItemIterator it(*dex_file_, class_data);
+    while (it.HasNextStaticField() || it.HasNextInstanceField()) {
+      it.Next();
+    }
+
+    ArtMethod* method = nullptr;
+    while (it.HasNextDirectMethod()) {
+      ArtMethod* resolved_method = class_linker_->ResolveMethod<ClassLinker::kNoICCECheckForCache>(
+          *dex_file_,
+          it.GetMemberIndex(),
+          dex_cache_handle,
+          class_loader_handle,
+          nullptr,
+          it.GetMethodInvokeType(*class_def));
+      CHECK(resolved_method != nullptr);
+      if (method_name == resolved_method->GetName()) {
+        method = resolved_method;
+        break;
+      }
+      it.Next();
+    }
+    CHECK(method != nullptr);
+
+    MethodVerifier verifier(Thread::Current(),
+                            dex_file_,
+                            dex_cache_handle,
+                            class_loader_handle,
+                            *class_def,
+                            it.GetMethodCodeItem(),
+                            it.GetMemberIndex(),
+                            method,
+                            it.GetMethodAccessFlags(),
+                            true /* can_load_classes */,
+                            true /* allow_soft_failures */,
+                            true /* need_precise_constants */,
+                            false /* verify to dump */,
+                            true /* allow_thread_suspension */);
+    verifier.Verify();
+    return !verifier.HasFailures();
+  }
+
+  bool TestAssignabilityRecording(const std::string& dst,
+                                  const std::string& src,
+                                  bool is_strict,
+                                  bool is_assignable) {
+    ScopedObjectAccess soa(Thread::Current());
+    LoadDexFile(&soa);
+    verifier_deps_->AddAssignability(*dex_file_,
+                                     FindClassByName(dst, &soa),
+                                     FindClassByName(src, &soa),
+                                     is_strict,
+                                     is_assignable);
+    return true;
+  }
+
+  // Iterates over all assignability records and tries to find an entry which
+  // matches the expected destination/source pair.
+  bool HasAssignable(const std::string& expected_destination,
+                     const std::string& expected_source,
+                     bool expected_is_assignable) {
+    MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+    for (auto& dex_dep : verifier_deps_->dex_deps_) {
+      const DexFile& dex_file = *dex_dep.first;
+      auto& storage = expected_is_assignable ? dex_dep.second->assignable_types_
+                                             : dex_dep.second->unassignable_types_;
+      for (auto& entry : storage) {
+        std::string actual_destination =
+            verifier_deps_->GetStringFromId(dex_file, entry.GetDestination());
+        std::string actual_source = verifier_deps_->GetStringFromId(dex_file, entry.GetSource());
+        if ((expected_destination == actual_destination) && (expected_source == actual_source)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  // Iterates over all class resolution records, finds an entry which matches
+  // the given class descriptor and tests its properties.
+  bool HasClass(const std::string& expected_klass,
+                bool expected_resolved,
+                const std::string& expected_access_flags = "") {
+    MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+    for (auto& dex_dep : verifier_deps_->dex_deps_) {
+      for (auto& entry : dex_dep.second->classes_) {
+        if (expected_resolved != entry.IsResolved()) {
+          continue;
+        }
+
+        std::string actual_klass = dex_dep.first->StringByTypeIdx(entry.GetDexTypeIndex());
+        if (expected_klass != actual_klass) {
+          continue;
+        }
+
+        if (expected_resolved) {
+          // Test access flags. Note that PrettyJavaAccessFlags always appends
+          // a space after the modifiers. Add it to the expected access flags.
+          std::string actual_access_flags = PrettyJavaAccessFlags(entry.GetAccessFlags());
+          if (expected_access_flags + " " != actual_access_flags) {
+            continue;
+          }
+        }
+
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Iterates over all field resolution records, finds an entry which matches
+  // the given field class+name+type and tests its properties.
+  bool HasField(const std::string& expected_klass,
+                const std::string& expected_name,
+                const std::string& expected_type,
+                bool expected_resolved,
+                const std::string& expected_access_flags = "",
+                const std::string& expected_decl_klass = "") {
+    MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+    for (auto& dex_dep : verifier_deps_->dex_deps_) {
+      for (auto& entry : dex_dep.second->fields_) {
+        if (expected_resolved != entry.IsResolved()) {
+          continue;
+        }
+
+        const DexFile::FieldId& field_id = dex_dep.first->GetFieldId(entry.GetDexFieldIndex());
+
+        std::string actual_klass = dex_dep.first->StringByTypeIdx(field_id.class_idx_);
+        if (expected_klass != actual_klass) {
+          continue;
+        }
+
+        std::string actual_name = dex_dep.first->StringDataByIdx(field_id.name_idx_);
+        if (expected_name != actual_name) {
+          continue;
+        }
+
+        std::string actual_type = dex_dep.first->StringByTypeIdx(field_id.type_idx_);
+        if (expected_type != actual_type) {
+          continue;
+        }
+
+        if (expected_resolved) {
+          // Test access flags. Note that PrettyJavaAccessFlags always appends
+          // a space after the modifiers. Add it to the expected access flags.
+          std::string actual_access_flags = PrettyJavaAccessFlags(entry.GetAccessFlags());
+          if (expected_access_flags + " " != actual_access_flags) {
+            continue;
+          }
+
+          std::string actual_decl_klass = verifier_deps_->GetStringFromId(
+              *dex_dep.first, entry.GetDeclaringClassIndex());
+          if (expected_decl_klass != actual_decl_klass) {
+            continue;
+          }
+        }
+
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Iterates over all method resolution records, finds an entry which matches
+  // the given field kind+class+name+signature and tests its properties.
+  bool HasMethod(const std::string& expected_kind,
+                 const std::string& expected_klass,
+                 const std::string& expected_name,
+                 const std::string& expected_signature,
+                 bool expected_resolved,
+                 const std::string& expected_access_flags = "",
+                 const std::string& expected_decl_klass = "") {
+    MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+    for (auto& dex_dep : verifier_deps_->dex_deps_) {
+      auto& storage = (expected_kind == "direct") ? dex_dep.second->direct_methods_
+                          : (expected_kind == "virtual") ? dex_dep.second->virtual_methods_
+                              : dex_dep.second->interface_methods_;
+      for (auto& entry : storage) {
+        if (expected_resolved != entry.IsResolved()) {
+          continue;
+        }
+
+        const DexFile::MethodId& method_id = dex_dep.first->GetMethodId(entry.GetDexMethodIndex());
+
+        std::string actual_klass = dex_dep.first->StringByTypeIdx(method_id.class_idx_);
+        if (expected_klass != actual_klass) {
+          continue;
+        }
+
+        std::string actual_name = dex_dep.first->StringDataByIdx(method_id.name_idx_);
+        if (expected_name != actual_name) {
+          continue;
+        }
+
+        std::string actual_signature = dex_dep.first->GetMethodSignature(method_id).ToString();
+        if (expected_signature != actual_signature) {
+          continue;
+        }
+
+        if (expected_resolved) {
+          // Test access flags. Note that PrettyJavaAccessFlags always appends
+          // a space after the modifiers. Add it to the expected access flags.
+          std::string actual_access_flags = PrettyJavaAccessFlags(entry.GetAccessFlags());
+          if (expected_access_flags + " " != actual_access_flags) {
+            continue;
+          }
+
+          std::string actual_decl_klass = verifier_deps_->GetStringFromId(
+              *dex_dep.first, entry.GetDeclaringClassIndex());
+          if (expected_decl_klass != actual_decl_klass) {
+            continue;
+          }
+        }
+
+        return true;
+      }
+    }
+    return false;
+  }
+
+  std::unique_ptr<verifier::VerifierDeps> verifier_deps_;
+  const DexFile* dex_file_;
+  jobject class_loader_;
+  mirror::Class* klass_Main_;
+};
+
+TEST_F(VerifierDepsTest, StringToId) {
+  ScopedObjectAccess soa(Thread::Current());
+  LoadDexFile(&soa);
+
+  MutexLock mu(Thread::Current(), *Locks::verifier_deps_lock_);
+
+  uint32_t id_Main1 = verifier_deps_->GetIdFromString(*dex_file_, "LMain;");
+  ASSERT_LT(id_Main1, dex_file_->NumStringIds());
+  ASSERT_EQ("LMain;", verifier_deps_->GetStringFromId(*dex_file_, id_Main1));
+
+  uint32_t id_Main2 = verifier_deps_->GetIdFromString(*dex_file_, "LMain;");
+  ASSERT_LT(id_Main2, dex_file_->NumStringIds());
+  ASSERT_EQ("LMain;", verifier_deps_->GetStringFromId(*dex_file_, id_Main2));
+
+  uint32_t id_Lorem1 = verifier_deps_->GetIdFromString(*dex_file_, "Lorem ipsum");
+  ASSERT_GE(id_Lorem1, dex_file_->NumStringIds());
+  ASSERT_EQ("Lorem ipsum", verifier_deps_->GetStringFromId(*dex_file_, id_Lorem1));
+
+  uint32_t id_Lorem2 = verifier_deps_->GetIdFromString(*dex_file_, "Lorem ipsum");
+  ASSERT_GE(id_Lorem2, dex_file_->NumStringIds());
+  ASSERT_EQ("Lorem ipsum", verifier_deps_->GetStringFromId(*dex_file_, id_Lorem2));
+
+  ASSERT_EQ(id_Main1, id_Main2);
+  ASSERT_EQ(id_Lorem1, id_Lorem2);
+  ASSERT_NE(id_Main1, id_Lorem1);
+}
+
+TEST_F(VerifierDepsTest, Assignable_BothInBoot) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/util/TimeZone;",
+                                         /* src */ "Ljava/util/SimpleTimeZone;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ true));
+  ASSERT_TRUE(HasAssignable("Ljava/util/TimeZone;", "Ljava/util/SimpleTimeZone;", true));
+}
+
+TEST_F(VerifierDepsTest, Assignable_DestinationInBoot1) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/net/Socket;",
+                                         /* src */ "LMySSLSocket;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ true));
+  ASSERT_TRUE(HasAssignable("Ljava/net/Socket;", "LMySSLSocket;", true));
+}
+
+TEST_F(VerifierDepsTest, Assignable_DestinationInBoot2) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/util/TimeZone;",
+                                         /* src */ "LMySimpleTimeZone;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ true));
+  ASSERT_TRUE(HasAssignable("Ljava/util/TimeZone;", "LMySimpleTimeZone;", true));
+}
+
+TEST_F(VerifierDepsTest, Assignable_DestinationInBoot3) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/util/Collection;",
+                                         /* src */ "LMyThreadSet;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ true));
+  ASSERT_TRUE(HasAssignable("Ljava/util/Collection;", "LMyThreadSet;", true));
+}
+
+TEST_F(VerifierDepsTest, Assignable_BothArrays_Resolved) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "[[Ljava/util/TimeZone;",
+                                         /* src */ "[[Ljava/util/SimpleTimeZone;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ true));
+  // If the component types of both arrays are resolved, we optimize the list of
+  // dependencies by recording a dependency on the component types.
+  ASSERT_FALSE(HasAssignable("[[Ljava/util/TimeZone;", "[[Ljava/util/SimpleTimeZone;", true));
+  ASSERT_FALSE(HasAssignable("[Ljava/util/TimeZone;", "[Ljava/util/SimpleTimeZone;", true));
+  ASSERT_TRUE(HasAssignable("Ljava/util/TimeZone;", "Ljava/util/SimpleTimeZone;", true));
+}
+
+TEST_F(VerifierDepsTest, Assignable_BothArrays_Erroneous) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "[[Ljava/util/TimeZone;",
+                                         /* src */ "[[LMyErroneousTimeZone;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ true));
+  // If the component type of an array is erroneous, we record the dependency on
+  // the array type.
+  ASSERT_FALSE(HasAssignable("[[Ljava/util/TimeZone;", "[[LMyErroneousTimeZone;", true));
+  ASSERT_TRUE(HasAssignable("[Ljava/util/TimeZone;", "[LMyErroneousTimeZone;", true));
+  ASSERT_FALSE(HasAssignable("Ljava/util/TimeZone;", "LMyErroneousTimeZone;", true));
+}
+
+  // We test that VerifierDeps does not try to optimize by storing assignability
+  // of the component types. This is due to the fact that the component type may
+  // be an erroneous class, even though the array type has resolved status.
+
+TEST_F(VerifierDepsTest, Assignable_ArrayToInterface1) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/io/Serializable;",
+                                         /* src */ "[Ljava/util/TimeZone;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ true));
+  ASSERT_TRUE(HasAssignable("Ljava/io/Serializable;", "[Ljava/util/TimeZone;", true));
+}
+
+TEST_F(VerifierDepsTest, Assignable_ArrayToInterface2) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/io/Serializable;",
+                                         /* src */ "[LMyThreadSet;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ true));
+  ASSERT_TRUE(HasAssignable("Ljava/io/Serializable;", "[LMyThreadSet;", true));
+}
+
+TEST_F(VerifierDepsTest, NotAssignable_BothInBoot) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/lang/Exception;",
+                                         /* src */ "Ljava/util/SimpleTimeZone;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ false));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Exception;", "Ljava/util/SimpleTimeZone;", false));
+}
+
+TEST_F(VerifierDepsTest, NotAssignable_DestinationInBoot1) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/lang/Exception;",
+                                         /* src */ "LMySSLSocket;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ false));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Exception;", "LMySSLSocket;", false));
+}
+
+TEST_F(VerifierDepsTest, NotAssignable_DestinationInBoot2) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/lang/Exception;",
+                                         /* src */ "LMySimpleTimeZone;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ false));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Exception;", "LMySimpleTimeZone;", false));
+}
+
+TEST_F(VerifierDepsTest, NotAssignable_BothArrays) {
+  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "[Ljava/lang/Exception;",
+                                         /* src */ "[Ljava/util/SimpleTimeZone;",
+                                         /* is_strict */ true,
+                                         /* is_assignable */ false));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Exception;", "Ljava/util/SimpleTimeZone;", false));
+}
+
+TEST_F(VerifierDepsTest, ArgumentType_ResolvedClass) {
+  ASSERT_TRUE(VerifyMethod("ArgumentType_ResolvedClass"));
+  ASSERT_TRUE(HasClass("Ljava/lang/Thread;", true, "public"));
+}
+
+TEST_F(VerifierDepsTest, ArgumentType_ResolvedReferenceArray) {
+  ASSERT_TRUE(VerifyMethod("ArgumentType_ResolvedReferenceArray"));
+  ASSERT_TRUE(HasClass("[Ljava/lang/Thread;", true, "public final abstract"));
+}
+
+TEST_F(VerifierDepsTest, ArgumentType_ResolvedPrimitiveArray) {
+  ASSERT_TRUE(VerifyMethod("ArgumentType_ResolvedPrimitiveArray"));
+  ASSERT_TRUE(HasClass("[B", true, "public final abstract"));
+}
+
+TEST_F(VerifierDepsTest, ArgumentType_UnresolvedClass) {
+  ASSERT_TRUE(VerifyMethod("ArgumentType_UnresolvedClass"));
+  ASSERT_TRUE(HasClass("LUnresolvedClass;", false));
+}
+
+TEST_F(VerifierDepsTest, ArgumentType_UnresolvedSuper) {
+  ASSERT_TRUE(VerifyMethod("ArgumentType_UnresolvedSuper"));
+  ASSERT_TRUE(HasClass("LMySetWithUnresolvedSuper;", false));
+}
+
+TEST_F(VerifierDepsTest, ReturnType_Reference) {
+  ASSERT_TRUE(VerifyMethod("ReturnType_Reference"));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Throwable;", "Ljava/lang/IllegalStateException;", true));
+}
+
+TEST_F(VerifierDepsTest, ReturnType_Array) {
+  ASSERT_FALSE(VerifyMethod("ReturnType_Array"));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Integer;", "Ljava/lang/IllegalStateException;", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeArgumentType) {
+  ASSERT_TRUE(VerifyMethod("InvokeArgumentType"));
+  ASSERT_TRUE(HasClass("Ljava/text/SimpleDateFormat;", true, "public"));
+  ASSERT_TRUE(HasClass("Ljava/util/SimpleTimeZone;", true, "public"));
+  ASSERT_TRUE(HasMethod("virtual",
+                        "Ljava/text/SimpleDateFormat;",
+                        "setTimeZone",
+                        "(Ljava/util/TimeZone;)V",
+                        true,
+                        "public",
+                        "Ljava/text/DateFormat;"));
+  ASSERT_TRUE(HasAssignable("Ljava/util/TimeZone;", "Ljava/util/SimpleTimeZone;", true));
+}
+
+TEST_F(VerifierDepsTest, MergeTypes_RegisterLines) {
+  ASSERT_TRUE(VerifyMethod("MergeTypes_RegisterLines"));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Exception;", "LMySocketTimeoutException;", true));
+  ASSERT_TRUE(HasAssignable(
+      "Ljava/lang/Exception;", "Ljava/util/concurrent/TimeoutException;", true));
+}
+
+TEST_F(VerifierDepsTest, MergeTypes_IfInstanceOf) {
+  ASSERT_TRUE(VerifyMethod("MergeTypes_IfInstanceOf"));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Exception;", "Ljava/net/SocketTimeoutException;", true));
+  ASSERT_TRUE(HasAssignable(
+      "Ljava/lang/Exception;", "Ljava/util/concurrent/TimeoutException;", true));
+  ASSERT_TRUE(HasAssignable("Ljava/net/SocketTimeoutException;", "Ljava/lang/Exception;", false));
+}
+
+TEST_F(VerifierDepsTest, MergeTypes_Unresolved) {
+  ASSERT_TRUE(VerifyMethod("MergeTypes_Unresolved"));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Exception;", "Ljava/net/SocketTimeoutException;", true));
+  ASSERT_TRUE(HasAssignable(
+      "Ljava/lang/Exception;", "Ljava/util/concurrent/TimeoutException;", true));
+}
+
+TEST_F(VerifierDepsTest, ConstClass_Resolved) {
+  ASSERT_TRUE(VerifyMethod("ConstClass_Resolved"));
+  ASSERT_TRUE(HasClass("Ljava/lang/IllegalStateException;", true, "public"));
+}
+
+TEST_F(VerifierDepsTest, ConstClass_Unresolved) {
+  ASSERT_TRUE(VerifyMethod("ConstClass_Unresolved"));
+  ASSERT_TRUE(HasClass("LUnresolvedClass;", false));
+}
+
+TEST_F(VerifierDepsTest, CheckCast_Resolved) {
+  ASSERT_TRUE(VerifyMethod("CheckCast_Resolved"));
+  ASSERT_TRUE(HasClass("Ljava/lang/IllegalStateException;", true, "public"));
+}
+
+TEST_F(VerifierDepsTest, CheckCast_Unresolved) {
+  ASSERT_TRUE(VerifyMethod("CheckCast_Unresolved"));
+  ASSERT_TRUE(HasClass("LUnresolvedClass;", false));
+}
+
+TEST_F(VerifierDepsTest, InstanceOf_Resolved) {
+  ASSERT_TRUE(VerifyMethod("InstanceOf_Resolved"));
+  ASSERT_TRUE(HasClass("Ljava/lang/IllegalStateException;", true, "public"));
+}
+
+TEST_F(VerifierDepsTest, InstanceOf_Unresolved) {
+  ASSERT_TRUE(VerifyMethod("InstanceOf_Unresolved"));
+  ASSERT_TRUE(HasClass("LUnresolvedClass;", false));
+}
+
+TEST_F(VerifierDepsTest, NewInstance_Resolved) {
+  ASSERT_TRUE(VerifyMethod("NewInstance_Resolved"));
+  ASSERT_TRUE(HasClass("Ljava/lang/IllegalStateException;", true, "public"));
+}
+
+TEST_F(VerifierDepsTest, NewInstance_Unresolved) {
+  ASSERT_TRUE(VerifyMethod("NewInstance_Unresolved"));
+  ASSERT_TRUE(HasClass("LUnresolvedClass;", false));
+}
+
+TEST_F(VerifierDepsTest, NewArray_Resolved) {
+  ASSERT_TRUE(VerifyMethod("NewArray_Resolved"));
+  ASSERT_TRUE(HasClass("[Ljava/lang/IllegalStateException;", true, "public final abstract"));
+}
+
+TEST_F(VerifierDepsTest, NewArray_Unresolved) {
+  ASSERT_TRUE(VerifyMethod("NewArray_Unresolved"));
+  ASSERT_TRUE(HasClass("[LUnresolvedClass;", false));
+}
+
+TEST_F(VerifierDepsTest, Throw) {
+  ASSERT_TRUE(VerifyMethod("Throw"));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Throwable;", "Ljava/lang/IllegalStateException;", true));
+}
+
+TEST_F(VerifierDepsTest, MoveException_Resolved) {
+  ASSERT_TRUE(VerifyMethod("MoveException_Resolved"));
+  ASSERT_TRUE(HasClass("Ljava/io/InterruptedIOException;", true, "public"));
+  ASSERT_TRUE(HasClass("Ljava/net/SocketTimeoutException;", true, "public"));
+  ASSERT_TRUE(HasClass("Ljava/util/zip/ZipException;", true, "public"));
+
+  // Testing that all exception types are assignable to Throwable.
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Throwable;", "Ljava/io/InterruptedIOException;", true));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Throwable;", "Ljava/net/SocketTimeoutException;", true));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Throwable;", "Ljava/util/zip/ZipException;", true));
+
+  // Testing that the merge type is assignable to Throwable.
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Throwable;", "Ljava/io/IOException;", true));
+
+  // Merging of exception types.
+  ASSERT_TRUE(HasAssignable("Ljava/io/IOException;", "Ljava/io/InterruptedIOException;", true));
+  ASSERT_TRUE(HasAssignable("Ljava/io/IOException;", "Ljava/util/zip/ZipException;", true));
+  ASSERT_TRUE(HasAssignable(
+      "Ljava/io/InterruptedIOException;", "Ljava/net/SocketTimeoutException;", true));
+}
+
+TEST_F(VerifierDepsTest, MoveException_Unresolved) {
+  ASSERT_FALSE(VerifyMethod("MoveException_Unresolved"));
+  ASSERT_TRUE(HasClass("LUnresolvedException;", false));
+}
+
+TEST_F(VerifierDepsTest, StaticField_Resolved_DeclaredInReferenced) {
+  ASSERT_TRUE(VerifyMethod("StaticField_Resolved_DeclaredInReferenced"));
+  ASSERT_TRUE(HasClass("Ljava/lang/System;", true, "public final"));
+  ASSERT_TRUE(HasField("Ljava/lang/System;",
+                       "out",
+                       "Ljava/io/PrintStream;",
+                       true,
+                       "public final static",
+                       "Ljava/lang/System;"));
+}
+
+TEST_F(VerifierDepsTest, StaticField_Resolved_DeclaredInSuperclass1) {
+  ASSERT_TRUE(VerifyMethod("StaticField_Resolved_DeclaredInSuperclass1"));
+  ASSERT_TRUE(HasClass("Ljava/util/SimpleTimeZone;", true, "public"));
+  ASSERT_TRUE(HasField(
+      "Ljava/util/SimpleTimeZone;", "LONG", "I", true, "public final static", "Ljava/util/TimeZone;"));
+}
+
+TEST_F(VerifierDepsTest, StaticField_Resolved_DeclaredInSuperclass2) {
+  ASSERT_TRUE(VerifyMethod("StaticField_Resolved_DeclaredInSuperclass2"));
+  ASSERT_TRUE(HasField(
+      "LMySimpleTimeZone;", "SHORT", "I", true, "public final static", "Ljava/util/TimeZone;"));
+}
+
+TEST_F(VerifierDepsTest, StaticField_Resolved_DeclaredInInterface1) {
+  ASSERT_TRUE(VerifyMethod("StaticField_Resolved_DeclaredInInterface1"));
+  ASSERT_TRUE(HasClass("Ljavax/xml/transform/dom/DOMResult;", true, "public"));
+  ASSERT_TRUE(HasField("Ljavax/xml/transform/dom/DOMResult;",
+                       "PI_ENABLE_OUTPUT_ESCAPING",
+                       "Ljava/lang/String;",
+                       true,
+                       "public final static",
+                       "Ljavax/xml/transform/Result;"));
+}
+
+TEST_F(VerifierDepsTest, StaticField_Resolved_DeclaredInInterface2) {
+  ASSERT_TRUE(VerifyMethod("StaticField_Resolved_DeclaredInInterface2"));
+  ASSERT_TRUE(HasField("LMyDOMResult;",
+                       "PI_ENABLE_OUTPUT_ESCAPING",
+                       "Ljava/lang/String;",
+                       true,
+                       "public final static",
+                       "Ljavax/xml/transform/Result;"));
+}
+
+TEST_F(VerifierDepsTest, StaticField_Resolved_DeclaredInInterface3) {
+  ASSERT_TRUE(VerifyMethod("StaticField_Resolved_DeclaredInInterface3"));
+  ASSERT_TRUE(HasField("LMyResult;",
+                       "PI_ENABLE_OUTPUT_ESCAPING",
+                       "Ljava/lang/String;",
+                       true,
+                       "public final static",
+                       "Ljavax/xml/transform/Result;"));
+}
+
+TEST_F(VerifierDepsTest, StaticField_Resolved_DeclaredInInterface4) {
+  ASSERT_TRUE(VerifyMethod("StaticField_Resolved_DeclaredInInterface4"));
+  ASSERT_TRUE(HasField("LMyDocument;",
+                       "ELEMENT_NODE",
+                       "S",
+                       true,
+                       "public final static",
+                       "Lorg/w3c/dom/Node;"));
+}
+
+TEST_F(VerifierDepsTest, StaticField_Unresolved_ReferrerInBoot) {
+  ASSERT_TRUE(VerifyMethod("StaticField_Unresolved_ReferrerInBoot"));
+  ASSERT_TRUE(HasClass("Ljava/util/TimeZone;", true, "public abstract"));
+  ASSERT_TRUE(HasField("Ljava/util/TimeZone;", "x", "I", false));
+}
+
+TEST_F(VerifierDepsTest, StaticField_Unresolved_ReferrerInDex) {
+  ASSERT_TRUE(VerifyMethod("StaticField_Unresolved_ReferrerInDex"));
+  ASSERT_TRUE(HasField("LMyThreadSet;", "x", "I", false));
+}
+
+TEST_F(VerifierDepsTest, InstanceField_Resolved_DeclaredInReferenced) {
+  ASSERT_TRUE(VerifyMethod("InstanceField_Resolved_DeclaredInReferenced"));
+  ASSERT_TRUE(HasClass("Ljava/io/InterruptedIOException;", true, "public"));
+  ASSERT_TRUE(HasField("Ljava/io/InterruptedIOException;",
+                       "bytesTransferred",
+                       "I",
+                       true,
+                       "public",
+                       "Ljava/io/InterruptedIOException;"));
+  ASSERT_TRUE(HasAssignable(
+      "Ljava/io/InterruptedIOException;", "LMySocketTimeoutException;", true));
+}
+
+TEST_F(VerifierDepsTest, InstanceField_Resolved_DeclaredInSuperclass1) {
+  ASSERT_TRUE(VerifyMethod("InstanceField_Resolved_DeclaredInSuperclass1"));
+  ASSERT_TRUE(HasClass("Ljava/net/SocketTimeoutException;", true, "public"));
+  ASSERT_TRUE(HasField("Ljava/net/SocketTimeoutException;",
+                       "bytesTransferred",
+                       "I",
+                       true,
+                       "public",
+                       "Ljava/io/InterruptedIOException;"));
+  ASSERT_TRUE(HasAssignable(
+      "Ljava/io/InterruptedIOException;", "LMySocketTimeoutException;", true));
+}
+
+TEST_F(VerifierDepsTest, InstanceField_Resolved_DeclaredInSuperclass2) {
+  ASSERT_TRUE(VerifyMethod("InstanceField_Resolved_DeclaredInSuperclass2"));
+  ASSERT_TRUE(HasField("LMySocketTimeoutException;",
+                       "bytesTransferred",
+                       "I",
+                       true,
+                       "public",
+                       "Ljava/io/InterruptedIOException;"));
+  ASSERT_TRUE(HasAssignable(
+      "Ljava/io/InterruptedIOException;", "LMySocketTimeoutException;", true));
+}
+
+TEST_F(VerifierDepsTest, InstanceField_Unresolved_ReferrerInBoot) {
+  ASSERT_TRUE(VerifyMethod("InstanceField_Unresolved_ReferrerInBoot"));
+  ASSERT_TRUE(HasClass("Ljava/io/InterruptedIOException;", true, "public"));
+  ASSERT_TRUE(HasField("Ljava/io/InterruptedIOException;", "x", "I", false));
+}
+
+TEST_F(VerifierDepsTest, InstanceField_Unresolved_ReferrerInDex) {
+  ASSERT_TRUE(VerifyMethod("InstanceField_Unresolved_ReferrerInDex"));
+  ASSERT_TRUE(HasField("LMyThreadSet;", "x", "I", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeStatic_Resolved_DeclaredInReferenced) {
+  ASSERT_TRUE(VerifyMethod("InvokeStatic_Resolved_DeclaredInReferenced"));
+  ASSERT_TRUE(HasClass("Ljava/net/Socket;", true, "public"));
+  ASSERT_TRUE(HasMethod("direct",
+                        "Ljava/net/Socket;",
+                        "setSocketImplFactory",
+                        "(Ljava/net/SocketImplFactory;)V",
+                        true,
+                        "public static",
+                        "Ljava/net/Socket;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeStatic_Resolved_DeclaredInSuperclass1) {
+  ASSERT_TRUE(VerifyMethod("InvokeStatic_Resolved_DeclaredInSuperclass1"));
+  ASSERT_TRUE(HasClass("Ljavax/net/ssl/SSLSocket;", true, "public abstract"));
+  ASSERT_TRUE(HasMethod("direct",
+                        "Ljavax/net/ssl/SSLSocket;",
+                        "setSocketImplFactory",
+                        "(Ljava/net/SocketImplFactory;)V",
+                        true,
+                        "public static",
+                        "Ljava/net/Socket;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeStatic_Resolved_DeclaredInSuperclass2) {
+  ASSERT_TRUE(VerifyMethod("InvokeStatic_Resolved_DeclaredInSuperclass2"));
+  ASSERT_TRUE(HasMethod("direct",
+                        "LMySSLSocket;",
+                        "setSocketImplFactory",
+                        "(Ljava/net/SocketImplFactory;)V",
+                        true,
+                        "public static",
+                        "Ljava/net/Socket;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeStatic_DeclaredInInterface1) {
+  ASSERT_TRUE(VerifyMethod("InvokeStatic_DeclaredInInterface1"));
+  ASSERT_TRUE(HasClass("Ljava/util/Map$Entry;", true, "public abstract interface"));
+  ASSERT_TRUE(HasMethod("direct",
+                        "Ljava/util/Map$Entry;",
+                        "comparingByKey",
+                        "()Ljava/util/Comparator;",
+                        true,
+                        "public static",
+                        "Ljava/util/Map$Entry;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeStatic_DeclaredInInterface2) {
+  ASSERT_FALSE(VerifyMethod("InvokeStatic_DeclaredInInterface2"));
+  ASSERT_TRUE(HasClass("Ljava/util/AbstractMap$SimpleEntry;", true, "public"));
+  ASSERT_TRUE(HasMethod("direct",
+                        "Ljava/util/AbstractMap$SimpleEntry;",
+                        "comparingByKey",
+                        "()Ljava/util/Comparator;",
+                        false));
+}
+
+TEST_F(VerifierDepsTest, InvokeStatic_Unresolved1) {
+  ASSERT_FALSE(VerifyMethod("InvokeStatic_Unresolved1"));
+  ASSERT_TRUE(HasClass("Ljavax/net/ssl/SSLSocket;", true, "public abstract"));
+  ASSERT_TRUE(HasMethod("direct", "Ljavax/net/ssl/SSLSocket;", "x", "()V", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeStatic_Unresolved2) {
+  ASSERT_FALSE(VerifyMethod("InvokeStatic_Unresolved2"));
+  ASSERT_TRUE(HasMethod("direct", "LMySSLSocket;", "x", "()V", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeDirect_Resolved_DeclaredInReferenced) {
+  ASSERT_TRUE(VerifyMethod("InvokeDirect_Resolved_DeclaredInReferenced"));
+  ASSERT_TRUE(HasClass("Ljava/net/Socket;", true, "public"));
+  ASSERT_TRUE(HasMethod(
+      "direct", "Ljava/net/Socket;", "<init>", "()V", true, "public", "Ljava/net/Socket;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeDirect_Resolved_DeclaredInSuperclass1) {
+  ASSERT_FALSE(VerifyMethod("InvokeDirect_Resolved_DeclaredInSuperclass1"));
+  ASSERT_TRUE(HasClass("Ljavax/net/ssl/SSLSocket;", true, "public abstract"));
+  ASSERT_TRUE(HasMethod("direct",
+                        "Ljavax/net/ssl/SSLSocket;",
+                        "checkOldImpl",
+                        "()V",
+                        true,
+                        "private",
+                        "Ljava/net/Socket;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeDirect_Resolved_DeclaredInSuperclass2) {
+  ASSERT_FALSE(VerifyMethod("InvokeDirect_Resolved_DeclaredInSuperclass2"));
+  ASSERT_TRUE(HasMethod(
+      "direct", "LMySSLSocket;", "checkOldImpl", "()V", true, "private", "Ljava/net/Socket;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeDirect_Unresolved1) {
+  ASSERT_FALSE(VerifyMethod("InvokeDirect_Unresolved1"));
+  ASSERT_TRUE(HasClass("Ljavax/net/ssl/SSLSocket;", true, "public abstract"));
+  ASSERT_TRUE(HasMethod("direct", "Ljavax/net/ssl/SSLSocket;", "x", "()V", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeDirect_Unresolved2) {
+  ASSERT_FALSE(VerifyMethod("InvokeDirect_Unresolved2"));
+  ASSERT_TRUE(HasMethod("direct", "LMySSLSocket;", "x", "()V", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeVirtual_Resolved_DeclaredInReferenced) {
+  ASSERT_TRUE(VerifyMethod("InvokeVirtual_Resolved_DeclaredInReferenced"));
+  ASSERT_TRUE(HasClass("Ljava/lang/Throwable;", true, "public"));
+  ASSERT_TRUE(HasMethod("virtual",
+                        "Ljava/lang/Throwable;",
+                        "getMessage",
+                        "()Ljava/lang/String;",
+                        true,
+                        "public",
+                        "Ljava/lang/Throwable;"));
+  // Type dependency on `this` argument.
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Throwable;", "LMySocketTimeoutException;", true));
+}
+
+TEST_F(VerifierDepsTest, InvokeVirtual_Resolved_DeclaredInSuperclass1) {
+  ASSERT_TRUE(VerifyMethod("InvokeVirtual_Resolved_DeclaredInSuperclass1"));
+  ASSERT_TRUE(HasClass("Ljava/io/InterruptedIOException;", true, "public"));
+  ASSERT_TRUE(HasMethod("virtual",
+                        "Ljava/io/InterruptedIOException;",
+                        "getMessage",
+                        "()Ljava/lang/String;",
+                        true,
+                        "public",
+                        "Ljava/lang/Throwable;"));
+  // Type dependency on `this` argument.
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Throwable;", "LMySocketTimeoutException;", true));
+}
+
+TEST_F(VerifierDepsTest, InvokeVirtual_Resolved_DeclaredInSuperclass2) {
+  ASSERT_TRUE(VerifyMethod("InvokeVirtual_Resolved_DeclaredInSuperclass2"));
+  ASSERT_TRUE(HasMethod("virtual",
+                        "LMySocketTimeoutException;",
+                        "getMessage",
+                        "()Ljava/lang/String;",
+                        true,
+                        "public",
+                        "Ljava/lang/Throwable;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeVirtual_Resolved_DeclaredInSuperinterface) {
+  ASSERT_TRUE(VerifyMethod("InvokeVirtual_Resolved_DeclaredInSuperinterface"));
+  ASSERT_TRUE(HasMethod("virtual",
+                        "LMyThreadSet;",
+                        "size",
+                        "()I",
+                        true,
+                        "public abstract",
+                        "Ljava/util/Set;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeVirtual_Unresolved1) {
+  ASSERT_FALSE(VerifyMethod("InvokeVirtual_Unresolved1"));
+  ASSERT_TRUE(HasClass("Ljava/io/InterruptedIOException;", true, "public"));
+  ASSERT_TRUE(HasMethod("virtual", "Ljava/io/InterruptedIOException;", "x", "()V", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeVirtual_Unresolved2) {
+  ASSERT_FALSE(VerifyMethod("InvokeVirtual_Unresolved2"));
+  ASSERT_TRUE(HasMethod("virtual", "LMySocketTimeoutException;", "x", "()V", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeVirtual_ActuallyDirect) {
+  ASSERT_FALSE(VerifyMethod("InvokeVirtual_ActuallyDirect"));
+  ASSERT_TRUE(HasMethod("virtual", "LMyThread;", "activeCount", "()I", false));
+  ASSERT_TRUE(HasMethod("direct",
+                        "LMyThread;",
+                        "activeCount",
+                        "()I",
+                        true,
+                        "public static",
+                        "Ljava/lang/Thread;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeInterface_Resolved_DeclaredInReferenced) {
+  ASSERT_TRUE(VerifyMethod("InvokeInterface_Resolved_DeclaredInReferenced"));
+  ASSERT_TRUE(HasClass("Ljava/lang/Runnable;", true, "public abstract interface"));
+  ASSERT_TRUE(HasMethod("interface",
+                        "Ljava/lang/Runnable;",
+                        "run",
+                        "()V",
+                        true,
+                        "public abstract",
+                        "Ljava/lang/Runnable;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeInterface_Resolved_DeclaredInSuperclass) {
+  ASSERT_FALSE(VerifyMethod("InvokeInterface_Resolved_DeclaredInSuperclass"));
+  ASSERT_TRUE(HasMethod("interface", "LMyThread;", "join", "()V", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeInterface_Resolved_DeclaredInSuperinterface1) {
+  ASSERT_FALSE(VerifyMethod("InvokeInterface_Resolved_DeclaredInSuperinterface1"));
+  ASSERT_TRUE(HasMethod("interface",
+                        "LMyThreadSet;",
+                        "run",
+                        "()V",
+                        true,
+                        "public abstract",
+                        "Ljava/lang/Runnable;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeInterface_Resolved_DeclaredInSuperinterface2) {
+  ASSERT_FALSE(VerifyMethod("InvokeInterface_Resolved_DeclaredInSuperinterface2"));
+  ASSERT_TRUE(HasMethod("interface",
+                        "LMyThreadSet;",
+                        "isEmpty",
+                        "()Z",
+                        true,
+                        "public abstract",
+                        "Ljava/util/Set;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeInterface_Unresolved1) {
+  ASSERT_FALSE(VerifyMethod("InvokeInterface_Unresolved1"));
+  ASSERT_TRUE(HasClass("Ljava/lang/Runnable;", true, "public abstract interface"));
+  ASSERT_TRUE(HasMethod("interface", "Ljava/lang/Runnable;", "x", "()V", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeInterface_Unresolved2) {
+  ASSERT_FALSE(VerifyMethod("InvokeInterface_Unresolved2"));
+  ASSERT_TRUE(HasMethod("interface", "LMyThreadSet;", "x", "()V", false));
+}
+
+TEST_F(VerifierDepsTest, InvokeSuper_ThisAssignable) {
+  ASSERT_TRUE(VerifyMethod("InvokeSuper_ThisAssignable"));
+  ASSERT_TRUE(HasClass("Ljava/lang/Runnable;", true, "public abstract interface"));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Runnable;", "LMain;", true));
+  ASSERT_TRUE(HasMethod("interface",
+                        "Ljava/lang/Runnable;",
+                        "run",
+                        "()V",
+                        true,
+                        "public abstract",
+                        "Ljava/lang/Runnable;"));
+}
+
+TEST_F(VerifierDepsTest, InvokeSuper_ThisNotAssignable) {
+  ASSERT_FALSE(VerifyMethod("InvokeSuper_ThisNotAssignable"));
+  ASSERT_TRUE(HasClass("Ljava/lang/Integer;", true, "public final"));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Integer;", "LMain;", false));
+  ASSERT_TRUE(HasMethod(
+      "virtual", "Ljava/lang/Integer;", "intValue", "()I", true, "public", "Ljava/lang/Integer;"));
+}
+
+}  // namespace verifier
+}  // namespace art
diff --git a/test/VerifierDeps/Main.smali b/test/VerifierDeps/Main.smali
new file mode 100644
index 0000000..74c0d03
--- /dev/null
+++ b/test/VerifierDeps/Main.smali
@@ -0,0 +1,464 @@
+# Copyright (C) 2016 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.
+
+.class public LMain;
+.super LMyThreadSet;
+
+.method public static ArgumentType_ResolvedClass(Ljava/lang/Thread;)V
+  .registers 1
+  return-void
+.end method
+
+.method public static ArgumentType_ResolvedReferenceArray([Ljava/lang/Thread;)V
+  .registers 1
+  return-void
+.end method
+
+.method public static ArgumentType_ResolvedPrimitiveArray([B)V
+  .registers 1
+  return-void
+.end method
+
+.method public static ArgumentType_UnresolvedClass(LUnresolvedClass;)V
+  .registers 1
+  return-void
+.end method
+
+.method public static ArgumentType_UnresolvedSuper(LMySetWithUnresolvedSuper;)V
+  .registers 1
+  return-void
+.end method
+
+.method public static ReturnType_Reference(Ljava/lang/IllegalStateException;)Ljava/lang/Throwable;
+  .registers 1
+  return-object p0
+.end method
+
+.method public static ReturnType_Array([Ljava/lang/IllegalStateException;)[Ljava/lang/Integer;
+  .registers 1
+  return-object p0
+.end method
+
+.method public static InvokeArgumentType(Ljava/text/SimpleDateFormat;Ljava/util/SimpleTimeZone;)V
+  .registers 2
+  invoke-virtual {p0, p1}, Ljava/text/SimpleDateFormat;->setTimeZone(Ljava/util/TimeZone;)V
+  return-void
+.end method
+
+.method public static MergeTypes_RegisterLines(Z)Ljava/lang/Object;
+  .registers 2
+  if-eqz p0, :else
+
+  new-instance v0, LMySocketTimeoutException;
+  invoke-direct {v0}, LMySocketTimeoutException;-><init>()V
+  goto :merge
+
+  :else
+  new-instance v0, Ljava/util/concurrent/TimeoutException;
+  invoke-direct {v0}, Ljava/util/concurrent/TimeoutException;-><init>()V
+  goto :merge
+
+  :merge
+  return-object v0
+.end method
+
+.method public static MergeTypes_IfInstanceOf(Ljava/net/SocketTimeoutException;)V
+  .registers 2
+  instance-of v0, p0, Ljava/util/concurrent/TimeoutException;
+  if-eqz v0, :else
+  return-void
+  :else
+  return-void
+.end method
+
+.method public static MergeTypes_Unresolved(ZZLUnresolvedClassA;)Ljava/lang/Object;
+  .registers 5
+  if-eqz p0, :else1
+
+  move-object v0, p2
+  goto :merge
+
+  :else1
+  if-eqz p1, :else2
+
+  new-instance v0, Ljava/util/concurrent/TimeoutException;
+  invoke-direct {v0}, Ljava/util/concurrent/TimeoutException;-><init>()V
+  goto :merge
+
+  :else2
+  new-instance v0, Ljava/net/SocketTimeoutException;
+  invoke-direct {v0}, Ljava/net/SocketTimeoutException;-><init>()V
+  goto :merge
+
+  :merge
+  return-object v0
+.end method
+
+.method public static ConstClass_Resolved()V
+  .registers 1
+  const-class v0, Ljava/lang/IllegalStateException;
+  return-void
+.end method
+
+.method public static ConstClass_Unresolved()V
+  .registers 1
+  const-class v0, LUnresolvedClass;
+  return-void
+.end method
+
+.method public static CheckCast_Resolved(Ljava/lang/Object;)V
+  .registers 1
+  check-cast p0, Ljava/lang/IllegalStateException;
+  return-void
+.end method
+
+.method public static CheckCast_Unresolved(Ljava/lang/Object;)V
+  .registers 1
+  check-cast p0, LUnresolvedClass;
+  return-void
+.end method
+
+.method public static InstanceOf_Resolved(Ljava/lang/Object;)Z
+  .registers 1
+  instance-of p0, p0, Ljava/lang/IllegalStateException;
+  return p0
+.end method
+
+.method public static InstanceOf_Unresolved(Ljava/lang/Object;)Z
+  .registers 1
+  instance-of p0, p0, LUnresolvedClass;
+  return p0
+.end method
+
+.method public static NewInstance_Resolved()V
+  .registers 1
+  new-instance v0, Ljava/lang/IllegalStateException;
+  return-void
+.end method
+
+.method public static NewInstance_Unresolved()V
+  .registers 1
+  new-instance v0, LUnresolvedClass;
+  return-void
+.end method
+
+.method public static NewArray_Resolved()V
+  .registers 1
+  const/4 v0, 0x1
+  new-array v0, v0, [Ljava/lang/IllegalStateException;
+  return-void
+.end method
+
+.method public static NewArray_Unresolved()V
+  .registers 2
+  const/4 v0, 0x1
+  new-array v0, v0, [LUnresolvedClass;
+  return-void
+.end method
+
+.method public static Throw(Ljava/lang/IllegalStateException;)V
+  .registers 2
+  throw p0
+.end method
+
+.method public static MoveException_Resolved()Ljava/lang/Object;
+  .registers 1
+  :try_start
+  invoke-static {}, Ljava/lang/System;->nanoTime()J
+  :try_end
+  .catch Ljava/net/SocketTimeoutException; {:try_start .. :try_end} :catch_block
+  .catch Ljava/io/InterruptedIOException; {:try_start .. :try_end} :catch_block
+  .catch Ljava/util/zip/ZipException; {:try_start .. :try_end} :catch_block
+  const/4 v0, 0x0
+  return-object v0
+
+  :catch_block
+  move-exception v0
+  return-object v0
+.end method
+
+.method public static MoveException_Unresolved()Ljava/lang/Object;
+  .registers 1
+  :try_start
+  invoke-static {}, Ljava/lang/System;->nanoTime()J
+  :try_end
+  .catch LUnresolvedException; {:try_start .. :try_end} :catch_block
+  const/4 v0, 0x0
+  return-object v0
+
+  :catch_block
+  move-exception v0
+  return-object v0
+.end method
+
+.method public static StaticField_Resolved_DeclaredInReferenced()V
+  .registers 1
+  sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+  return-void
+.end method
+
+.method public static StaticField_Resolved_DeclaredInSuperclass1()V
+  .registers 1
+  sget v0, Ljava/util/SimpleTimeZone;->LONG:I
+  return-void
+.end method
+
+.method public static StaticField_Resolved_DeclaredInSuperclass2()V
+  .registers 1
+  sget v0, LMySimpleTimeZone;->SHORT:I
+  return-void
+.end method
+
+.method public static StaticField_Resolved_DeclaredInInterface1()V
+  .registers 1
+  # Case 1: DOMResult implements Result
+  sget-object v0, Ljavax/xml/transform/dom/DOMResult;->PI_ENABLE_OUTPUT_ESCAPING:Ljava/lang/String;
+  return-void
+.end method
+
+.method public static StaticField_Resolved_DeclaredInInterface2()V
+  .registers 1
+  # Case 2: MyDOMResult extends DOMResult, DOMResult implements Result
+  sget-object v0, LMyDOMResult;->PI_ENABLE_OUTPUT_ESCAPING:Ljava/lang/String;
+  return-void
+.end method
+
+.method public static StaticField_Resolved_DeclaredInInterface3()V
+  .registers 1
+  # Case 3: MyResult implements Result
+  sget-object v0, LMyResult;->PI_ENABLE_OUTPUT_ESCAPING:Ljava/lang/String;
+  return-void
+.end method
+
+.method public static StaticField_Resolved_DeclaredInInterface4()V
+  .registers 1
+  # Case 4: MyDocument implements Document, Document extends Node
+  sget-short v0, LMyDocument;->ELEMENT_NODE:S
+  return-void
+.end method
+
+.method public static StaticField_Unresolved_ReferrerInBoot()V
+  .registers 1
+  sget v0, Ljava/util/TimeZone;->x:I
+  return-void
+.end method
+
+.method public static StaticField_Unresolved_ReferrerInDex()V
+  .registers 1
+  sget v0, LMyThreadSet;->x:I
+  return-void
+.end method
+
+.method public static InstanceField_Resolved_DeclaredInReferenced(LMySocketTimeoutException;)V
+  .registers 1
+  iget v0, p0, Ljava/io/InterruptedIOException;->bytesTransferred:I
+  return-void
+.end method
+
+.method public static InstanceField_Resolved_DeclaredInSuperclass1(LMySocketTimeoutException;)V
+  .registers 1
+  iget v0, p0, Ljava/net/SocketTimeoutException;->bytesTransferred:I
+  return-void
+.end method
+
+.method public static InstanceField_Resolved_DeclaredInSuperclass2(LMySocketTimeoutException;)V
+  .registers 1
+  iget v0, p0, LMySocketTimeoutException;->bytesTransferred:I
+  return-void
+.end method
+
+.method public static InstanceField_Unresolved_ReferrerInBoot(LMySocketTimeoutException;)V
+  .registers 1
+  iget v0, p0, Ljava/io/InterruptedIOException;->x:I
+  return-void
+.end method
+
+.method public static InstanceField_Unresolved_ReferrerInDex(LMyThreadSet;)V
+  .registers 1
+  iget v0, p0, LMyThreadSet;->x:I
+  return-void
+.end method
+
+.method public static InvokeStatic_Resolved_DeclaredInReferenced()V
+  .registers 1
+  const v0, 0x0
+  invoke-static {v0}, Ljava/net/Socket;->setSocketImplFactory(Ljava/net/SocketImplFactory;)V
+  return-void
+.end method
+
+.method public static InvokeStatic_Resolved_DeclaredInSuperclass1()V
+  .registers 1
+  const v0, 0x0
+  invoke-static {v0}, Ljavax/net/ssl/SSLSocket;->setSocketImplFactory(Ljava/net/SocketImplFactory;)V
+  return-void
+.end method
+
+.method public static InvokeStatic_Resolved_DeclaredInSuperclass2()V
+  .registers 1
+  const v0, 0x0
+  invoke-static {v0}, LMySSLSocket;->setSocketImplFactory(Ljava/net/SocketImplFactory;)V
+  return-void
+.end method
+
+.method public static InvokeStatic_DeclaredInInterface1()V
+  .registers 1
+  invoke-static {}, Ljava/util/Map$Entry;->comparingByKey()Ljava/util/Comparator;
+  return-void
+.end method
+
+.method public static InvokeStatic_DeclaredInInterface2()V
+  .registers 1
+  # AbstractMap$SimpleEntry implements Map$Entry
+  # INVOKE_STATIC does not resolve to methods in superinterfaces. This will
+  # therefore result in an unresolved method.
+  invoke-static {}, Ljava/util/AbstractMap$SimpleEntry;->comparingByKey()Ljava/util/Comparator;
+  return-void
+.end method
+
+.method public static InvokeStatic_Unresolved1()V
+  .registers 1
+  invoke-static {}, Ljavax/net/ssl/SSLSocket;->x()V
+  return-void
+.end method
+
+.method public static InvokeStatic_Unresolved2()V
+  .registers 1
+  invoke-static {}, LMySSLSocket;->x()V
+  return-void
+.end method
+
+.method public static InvokeDirect_Resolved_DeclaredInReferenced()V
+  .registers 1
+  new-instance v0, Ljava/net/Socket;
+  invoke-direct {v0}, Ljava/net/Socket;-><init>()V
+  return-void
+.end method
+
+.method public static InvokeDirect_Resolved_DeclaredInSuperclass1(LMySSLSocket;)V
+  .registers 1
+  invoke-direct {p0}, Ljavax/net/ssl/SSLSocket;->checkOldImpl()V
+  return-void
+.end method
+
+.method public static InvokeDirect_Resolved_DeclaredInSuperclass2(LMySSLSocket;)V
+  .registers 1
+  invoke-direct {p0}, LMySSLSocket;->checkOldImpl()V
+  return-void
+.end method
+
+.method public static InvokeDirect_Unresolved1(LMySSLSocket;)V
+  .registers 1
+  invoke-direct {p0}, Ljavax/net/ssl/SSLSocket;->x()V
+  return-void
+.end method
+
+.method public static InvokeDirect_Unresolved2(LMySSLSocket;)V
+  .registers 1
+  invoke-direct {p0}, LMySSLSocket;->x()V
+  return-void
+.end method
+
+.method public static InvokeVirtual_Resolved_DeclaredInReferenced(LMySocketTimeoutException;)V
+  .registers 1
+  invoke-virtual {p0}, Ljava/lang/Throwable;->getMessage()Ljava/lang/String;
+  return-void
+.end method
+
+.method public static InvokeVirtual_Resolved_DeclaredInSuperclass1(LMySocketTimeoutException;)V
+  .registers 1
+  invoke-virtual {p0}, Ljava/io/InterruptedIOException;->getMessage()Ljava/lang/String;
+  return-void
+.end method
+
+.method public static InvokeVirtual_Resolved_DeclaredInSuperclass2(LMySocketTimeoutException;)V
+  .registers 1
+  invoke-virtual {p0}, LMySocketTimeoutException;->getMessage()Ljava/lang/String;
+  return-void
+.end method
+
+.method public static InvokeVirtual_Resolved_DeclaredInSuperinterface(LMyThreadSet;)V
+  .registers 1
+  invoke-virtual {p0}, LMyThreadSet;->size()I
+  return-void
+.end method
+
+.method public static InvokeVirtual_Unresolved1(LMySocketTimeoutException;)V
+  .registers 1
+  invoke-virtual {p0}, Ljava/io/InterruptedIOException;->x()V
+  return-void
+.end method
+
+.method public static InvokeVirtual_Unresolved2(LMySocketTimeoutException;)V
+  .registers 1
+  invoke-virtual {p0}, LMySocketTimeoutException;->x()V
+  return-void
+.end method
+
+.method public static InvokeVirtual_ActuallyDirect(LMyThread;)V
+  .registers 1
+  invoke-virtual {p0}, LMyThread;->activeCount()I
+  return-void
+.end method
+
+.method public static InvokeInterface_Resolved_DeclaredInReferenced(LMyThread;)V
+  .registers 1
+  invoke-interface {p0}, Ljava/lang/Runnable;->run()V
+  return-void
+.end method
+
+.method public static InvokeInterface_Resolved_DeclaredInSuperclass(LMyThread;)V
+  .registers 1
+  # Method join() is declared in the superclass of MyThread. As such, it should
+  # be called with invoke-virtual and will not be resolved here.
+  invoke-interface {p0}, LMyThread;->join()V
+  return-void
+.end method
+
+.method public static InvokeInterface_Resolved_DeclaredInSuperinterface1(LMyThreadSet;)V
+  .registers 1
+  # Verification will fail because the referring class is not an interface.
+  invoke-interface {p0}, LMyThreadSet;->run()V
+  return-void
+.end method
+
+.method public static InvokeInterface_Resolved_DeclaredInSuperinterface2(LMyThreadSet;)V
+  .registers 1
+  # Verification will fail because the referring class is not an interface.
+  invoke-interface {p0}, LMyThreadSet;->isEmpty()Z
+  return-void
+.end method
+
+.method public static InvokeInterface_Unresolved1(LMyThread;)V
+  .registers 1
+  invoke-interface {p0}, Ljava/lang/Runnable;->x()V
+  return-void
+.end method
+
+.method public static InvokeInterface_Unresolved2(LMyThread;)V
+  .registers 1
+  invoke-interface {p0}, LMyThreadSet;->x()V
+  return-void
+.end method
+
+.method public static InvokeSuper_ThisAssignable(Ljava/lang/Thread;)V
+  .registers 1
+  invoke-super {p0}, Ljava/lang/Runnable;->run()V
+  return-void
+.end method
+
+.method public static InvokeSuper_ThisNotAssignable(Ljava/lang/Integer;)V
+  .registers 1
+  invoke-super {p0}, Ljava/lang/Integer;->intValue()I
+  return-void
+.end method
diff --git a/test/VerifierDeps/MyDOMResult.smali b/test/VerifierDeps/MyDOMResult.smali
new file mode 100644
index 0000000..12f6243
--- /dev/null
+++ b/test/VerifierDeps/MyDOMResult.smali
@@ -0,0 +1,16 @@
+# Copyright (C) 2016 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.
+
+.class public LMyDOMResult;
+.super Ljavax/xml/transform/dom/DOMResult;
diff --git a/test/VerifierDeps/MyDocument.smali b/test/VerifierDeps/MyDocument.smali
new file mode 100644
index 0000000..3ce042c
--- /dev/null
+++ b/test/VerifierDeps/MyDocument.smali
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 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.
+
+.class public LMyDocument;
+.super Ljava/lang/Object;
+.implements Lorg/w3c/dom/Document;
diff --git a/test/VerifierDeps/MyErroneousTimeZone.smali b/test/VerifierDeps/MyErroneousTimeZone.smali
new file mode 100644
index 0000000..5f23dd9
--- /dev/null
+++ b/test/VerifierDeps/MyErroneousTimeZone.smali
@@ -0,0 +1,22 @@
+# Copyright (C) 2016 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.
+
+.class public LMyErroneousTimeZone;
+.super LMySimpleTimeZone;
+
+# Class is erroneous because foo() is defined final in the superclass.
+.method public foo()V
+  .registers 1
+  return-void
+.end method
diff --git a/test/VerifierDeps/MyResult.smali b/test/VerifierDeps/MyResult.smali
new file mode 100644
index 0000000..e00e750
--- /dev/null
+++ b/test/VerifierDeps/MyResult.smali
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 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.
+
+.class public LMyResult;
+.super Ljava/lang/Object;
+.implements Ljavax/xml/transform/Result;
diff --git a/test/VerifierDeps/MySSLSocket.smali b/test/VerifierDeps/MySSLSocket.smali
new file mode 100644
index 0000000..dd30081
--- /dev/null
+++ b/test/VerifierDeps/MySSLSocket.smali
@@ -0,0 +1,16 @@
+# Copyright (C) 2016 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.
+
+.class public LMySSLSocket;
+.super Ljavax/net/ssl/SSLSocket;
diff --git a/test/VerifierDeps/MySimpleTimeZone.smali b/test/VerifierDeps/MySimpleTimeZone.smali
new file mode 100644
index 0000000..f7a1e05
--- /dev/null
+++ b/test/VerifierDeps/MySimpleTimeZone.smali
@@ -0,0 +1,24 @@
+# Copyright (C) 2016 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.
+
+.class public LMySimpleTimeZone;
+.super Ljava/util/SimpleTimeZone;
+.implements Ljava/io/Serializable;
+
+# Define foo() as a final method. It is used by the MyErroneousTimeZone subclass
+# to generate a linkage error.
+.method public final foo()V
+  .registers 1
+  return-void
+.end method
diff --git a/test/VerifierDeps/MySocketTimeoutException.smali b/test/VerifierDeps/MySocketTimeoutException.smali
new file mode 100644
index 0000000..50e0762
--- /dev/null
+++ b/test/VerifierDeps/MySocketTimeoutException.smali
@@ -0,0 +1,16 @@
+# Copyright (C) 2016 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.
+
+.class public LMySocketTimeoutException;
+.super Ljava/net/SocketTimeoutException;
diff --git a/test/VerifierDeps/MyThread.smali b/test/VerifierDeps/MyThread.smali
new file mode 100644
index 0000000..7fdb254
--- /dev/null
+++ b/test/VerifierDeps/MyThread.smali
@@ -0,0 +1,16 @@
+# Copyright (C) 2016 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.
+
+.class public LMyThread;
+.super Ljava/lang/Thread;
diff --git a/test/VerifierDeps/MyThreadSet.smali b/test/VerifierDeps/MyThreadSet.smali
new file mode 100644
index 0000000..f331fcf
--- /dev/null
+++ b/test/VerifierDeps/MyThreadSet.smali
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 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.
+
+.class public abstract LMyThreadSet;
+.super Ljava/lang/Thread;
+.implements Ljava/util/Set;