Implement ThreadReference.OwnedMonitors.

Fix the method verifier so it can cope with not being able to resolve
types in the application class loader, so we can find monitors held in
application code (this will improve SIGQUIT too).

Also remove the sort|uniq of dex pcs by just recording the last work
line we see.

Change-Id: I86ff27b42800a858489d112931c9aed2fb85ebdc
diff --git a/src/debugger.cc b/src/debugger.cc
index b2c486e..958fd38 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -669,6 +669,50 @@
   return JDWP::ERR_NONE;
 }
 
+JDWP::JdwpError Dbg::GetOwnedMonitors(JDWP::ObjectId thread_id, std::vector<JDWP::ObjectId>& monitors)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  ScopedObjectAccessUnchecked soa(Thread::Current());
+  MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
+  Thread* thread;
+  JDWP::JdwpError error = DecodeThread(soa, thread_id, thread);
+  if (error != JDWP::ERR_NONE) {
+    return error;
+  }
+  if (!IsSuspendedForDebugger(soa, thread)) {
+    return JDWP::ERR_THREAD_NOT_SUSPENDED;
+  }
+
+  struct OwnedMonitorVisitor : public StackVisitor {
+    OwnedMonitorVisitor(const ManagedStack* stack,
+                        const std::deque<InstrumentationStackFrame>* instrumentation_stack)
+        SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
+        : StackVisitor(stack, instrumentation_stack, NULL) {}
+
+    // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses
+    // annotalysis.
+    bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
+      if (!GetMethod()->IsRuntimeMethod()) {
+        Monitor::VisitLocks(this, AppendOwnedMonitors, this);
+      }
+      return true;
+    }
+
+    static void AppendOwnedMonitors(Object* owned_monitor, void* context) {
+      reinterpret_cast<OwnedMonitorVisitor*>(context)->monitors.push_back(owned_monitor);
+    }
+
+    std::vector<Object*> monitors;
+  };
+  OwnedMonitorVisitor visitor(thread->GetManagedStack(), thread->GetInstrumentationStack());
+  visitor.WalkStack();
+
+  for (size_t i = 0; i < visitor.monitors.size(); ++i) {
+    monitors.push_back(gRegistry->Add(visitor.monitors[i]));
+  }
+
+  return JDWP::ERR_NONE;
+}
+
 JDWP::JdwpError Dbg::GetReflectedType(JDWP::RefTypeId class_id, JDWP::ExpandBuf* pReply) {
   JDWP::JdwpError status;
   Class* c = DecodeClass(class_id, status);
diff --git a/src/debugger.h b/src/debugger.h
index fac828a..c58fd4f 100644
--- a/src/debugger.h
+++ b/src/debugger.h
@@ -153,6 +153,8 @@
 
   static JDWP::JdwpError GetMonitorInfo(JDWP::ObjectId object_id, JDWP::ExpandBuf* reply)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  static JDWP::JdwpError GetOwnedMonitors(JDWP::ObjectId thread_id, std::vector<JDWP::ObjectId>& monitors)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   static JDWP::JdwpError GetArrayLength(JDWP::ObjectId array_id, int& length)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
diff --git a/src/jdwp/jdwp_handler.cc b/src/jdwp/jdwp_handler.cc
index 97e6304..f205c95 100644
--- a/src/jdwp/jdwp_handler.cc
+++ b/src/jdwp/jdwp_handler.cc
@@ -48,7 +48,7 @@
 /*
  * Helper function: read a "location" from an input buffer.
  */
-static void JdwpReadLocation(const uint8_t** pBuf, JdwpLocation* pLoc) {
+static void ReadLocation(const uint8_t** pBuf, JdwpLocation* pLoc) {
   memset(pLoc, 0, sizeof(*pLoc));     /* allows memcmp() later */
   pLoc->type_tag = ReadTypeTag(pBuf);
   pLoc->class_id = ReadObjectId(pBuf);
@@ -59,7 +59,7 @@
 /*
  * Helper function: read a variable-width value from the input buffer.
  */
-static uint64_t JdwpReadValue(const uint8_t** pBuf, size_t width) {
+static uint64_t ReadValue(const uint8_t** pBuf, size_t width) {
   uint64_t value = -1;
   switch (width) {
   case 1:     value = Read1(pBuf); break;
@@ -74,7 +74,7 @@
 /*
  * Helper function: write a variable-width value into the output input buffer.
  */
-static void JdwpWriteValue(ExpandBuf* pReply, int width, uint64_t value) {
+static void WriteValue(ExpandBuf* pReply, int width, uint64_t value) {
   switch (width) {
   case 1:     expandBufAdd1(pReply, value); break;
   case 2:     expandBufAdd2BE(pReply, value); break;
@@ -84,6 +84,17 @@
   }
 }
 
+static JdwpError WriteTaggedObject(ExpandBuf* reply, ObjectId object_id)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  uint8_t tag;
+  JdwpError rc = Dbg::GetObjectTag(object_id, tag);
+  if (rc == ERR_NONE) {
+    expandBufAdd1(reply, tag);
+    expandBufAddObjectId(reply, object_id);
+  }
+  return rc;
+}
+
 /*
  * Common code for *_InvokeMethod requests.
  *
@@ -109,7 +120,7 @@
   for (uint32_t i = 0; i < arg_count; ++i) {
     argTypes[i] = ReadTag(&buf);
     size_t width = Dbg::GetTagWidth(argTypes[i]);
-    argValues[i] = JdwpReadValue(&buf, width);
+    argValues[i] = ReadValue(&buf, width);
     VLOG(jdwp) << "          " << argTypes[i] << StringPrintf("(%zd): %#llx", width, argValues[i]);
   }
 
@@ -137,7 +148,7 @@
     size_t width = Dbg::GetTagWidth(resultTag);
     expandBufAdd1(pReply, resultTag);
     if (width != 0) {
-      JdwpWriteValue(pReply, width, resultValue);
+      WriteValue(pReply, width, resultValue);
     }
     expandBufAdd1(pReply, JT_OBJECT);
     expandBufAddObjectId(pReply, exceptObjId);
@@ -325,21 +336,6 @@
   return ERR_NONE;
 }
 
-/*
- * Tell the debugger what we are capable of.
- */
-static JdwpError VM_Capabilities(JdwpState*, const uint8_t*, int, ExpandBuf* pReply)
-    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  expandBufAdd1(pReply, false);   // canWatchFieldModification
-  expandBufAdd1(pReply, false);   // canWatchFieldAccess
-  expandBufAdd1(pReply, false);   // canGetBytecodes
-  expandBufAdd1(pReply, true);    // canGetSyntheticAttribute
-  expandBufAdd1(pReply, false);   // canGetOwnedMonitorInfo
-  expandBufAdd1(pReply, false);   // canGetCurrentContendedMonitor
-  expandBufAdd1(pReply, true);    // canGetMonitorInfo
-  return ERR_NONE;
-}
-
 static JdwpError VM_ClassPaths(JdwpState*, const uint8_t*, int, ExpandBuf* pReply)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   expandBufAddUtf8String(pReply, "/");
@@ -371,36 +367,42 @@
   return ERR_NONE;
 }
 
-/*
- * Tell the debugger what we are capable of.
- */
-static JdwpError VM_CapabilitiesNew(JdwpState*, const uint8_t*, int, ExpandBuf* pReply)
+static JdwpError VM_Capabilities(JdwpState*, const uint8_t*, int, ExpandBuf* reply)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  expandBufAdd1(pReply, false);   // canWatchFieldModification
-  expandBufAdd1(pReply, false);   // canWatchFieldAccess
-  expandBufAdd1(pReply, false);   // canGetBytecodes
-  expandBufAdd1(pReply, true);    // canGetSyntheticAttribute
-  expandBufAdd1(pReply, false);   // canGetOwnedMonitorInfo
-  expandBufAdd1(pReply, false);   // canGetCurrentContendedMonitor
-  expandBufAdd1(pReply, true);    // canGetMonitorInfo
-  expandBufAdd1(pReply, false);   // canRedefineClasses
-  expandBufAdd1(pReply, false);   // canAddMethod
-  expandBufAdd1(pReply, false);   // canUnrestrictedlyRedefineClasses
-  expandBufAdd1(pReply, false);   // canPopFrames
-  expandBufAdd1(pReply, false);   // canUseInstanceFilters
-  expandBufAdd1(pReply, false);   // canGetSourceDebugExtension
-  expandBufAdd1(pReply, false);   // canRequestVMDeathEvent
-  expandBufAdd1(pReply, false);   // canSetDefaultStratum
-  expandBufAdd1(pReply, false);   // 1.6: canGetInstanceInfo
-  expandBufAdd1(pReply, false);   // 1.6: canRequestMonitorEvents
-  expandBufAdd1(pReply, false);   // 1.6: canGetMonitorFrameInfo
-  expandBufAdd1(pReply, false);   // 1.6: canUseSourceNameFilters
-  expandBufAdd1(pReply, false);   // 1.6: canGetConstantPool
-  expandBufAdd1(pReply, false);   // 1.6: canForceEarlyReturn
+  expandBufAdd1(reply, false);   // canWatchFieldModification
+  expandBufAdd1(reply, false);   // canWatchFieldAccess
+  expandBufAdd1(reply, false);   // canGetBytecodes
+  expandBufAdd1(reply, true);    // canGetSyntheticAttribute
+  expandBufAdd1(reply, true);    // canGetOwnedMonitorInfo
+  expandBufAdd1(reply, false);   // canGetCurrentContendedMonitor
+  expandBufAdd1(reply, true);    // canGetMonitorInfo
+  return ERR_NONE;
+}
 
-  /* fill in reserved22 through reserved32; note count started at 1 */
-  for (int i = 22; i <= 32; i++) {
-    expandBufAdd1(pReply, false);   /* reservedN */
+static JdwpError VM_CapabilitiesNew(JdwpState*, const uint8_t*, int, ExpandBuf* reply)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+
+  // The first few capabilities are the same as those reported by the older call.
+  VM_Capabilities(NULL, NULL, 0, reply);
+
+  expandBufAdd1(reply, false);   // canRedefineClasses
+  expandBufAdd1(reply, false);   // canAddMethod
+  expandBufAdd1(reply, false);   // canUnrestrictedlyRedefineClasses
+  expandBufAdd1(reply, false);   // canPopFrames
+  expandBufAdd1(reply, false);   // canUseInstanceFilters
+  expandBufAdd1(reply, false);   // canGetSourceDebugExtension
+  expandBufAdd1(reply, false);   // canRequestVMDeathEvent
+  expandBufAdd1(reply, false);   // canSetDefaultStratum
+  expandBufAdd1(reply, false);   // 1.6: canGetInstanceInfo
+  expandBufAdd1(reply, false);   // 1.6: canRequestMonitorEvents
+  expandBufAdd1(reply, false);   // 1.6: canGetMonitorFrameInfo
+  expandBufAdd1(reply, false);   // 1.6: canUseSourceNameFilters
+  expandBufAdd1(reply, false);   // 1.6: canGetConstantPool
+  expandBufAdd1(reply, false);   // 1.6: canForceEarlyReturn
+
+  // Fill in reserved22 through reserved32; note count started at 1.
+  for (size_t i = 22; i <= 32; ++i) {
+    expandBufAdd1(reply, false);
   }
   return ERR_NONE;
 }
@@ -652,7 +654,7 @@
     FieldId fieldId = ReadFieldId(&buf);
     JDWP::JdwpTag fieldTag = Dbg::GetStaticFieldBasicTag(fieldId);
     size_t width = Dbg::GetTagWidth(fieldTag);
-    uint64_t value = JdwpReadValue(&buf, width);
+    uint64_t value = ReadValue(&buf, width);
 
     VLOG(jdwp) << "    --> field=" << fieldId << " tag=" << fieldTag << " -> " << value;
     JdwpError status = Dbg::SetStaticFieldValue(fieldId, value, width);
@@ -825,7 +827,7 @@
 
     JDWP::JdwpTag fieldTag = Dbg::GetFieldBasicTag(fieldId);
     size_t width = Dbg::GetTagWidth(fieldTag);
-    uint64_t value = JdwpReadValue(&buf, width);
+    uint64_t value = ReadValue(&buf, width);
 
     VLOG(jdwp) << "    --> fieldId=" << fieldId << " tag=" << fieldTag << "(" << width << ") value=" << value;
     JdwpError status = Dbg::SetFieldValue(object_id, fieldId, value, width);
@@ -1048,15 +1050,35 @@
   ObjectId thread_id = ReadObjectId(&buf);
 
   size_t frame_count;
-  JdwpError error = Dbg::GetThreadFrameCount(thread_id, frame_count);
-  if (error != ERR_NONE) {
-    return error;
+  JdwpError rc = Dbg::GetThreadFrameCount(thread_id, frame_count);
+  if (rc != ERR_NONE) {
+    return rc;
   }
   expandBufAdd4BE(pReply, static_cast<uint32_t>(frame_count));
 
   return ERR_NONE;
 }
 
+static JdwpError TR_OwnedMonitors(JdwpState*, const uint8_t* buf, int, ExpandBuf* reply)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  ObjectId thread_id = ReadObjectId(&buf);
+
+  std::vector<ObjectId> monitors;
+  JdwpError rc = Dbg::GetOwnedMonitors(thread_id, monitors);
+  if (rc != ERR_NONE) {
+    return rc;
+  }
+
+  expandBufAdd4BE(reply, monitors.size());
+  for (size_t i = 0; i < monitors.size(); ++i) {
+    rc = WriteTaggedObject(reply, monitors[i]);
+    if (rc != ERR_NONE) {
+      return rc;
+    }
+  }
+  return ERR_NONE;
+}
+
 /*
  * Get the monitor that the thread is waiting on.
  */
@@ -1278,7 +1300,7 @@
     case MK_LOCATION_ONLY:  /* restrict certain events based on loc */
       {
         JdwpLocation loc;
-        JdwpReadLocation(&buf, &loc);
+        ReadLocation(&buf, &loc);
         VLOG(jdwp) << "    LocationOnly: " << loc;
         mod.locationOnly.loc = loc;
       }
@@ -1423,7 +1445,7 @@
     uint32_t slot = Read4BE(&buf);
     JDWP::JdwpTag sigByte = ReadTag(&buf);
     size_t width = Dbg::GetTagWidth(sigByte);
-    uint64_t value = JdwpReadValue(&buf, width);
+    uint64_t value = ReadValue(&buf, width);
 
     VLOG(jdwp) << "    --> slot " << slot << " " << sigByte << " " << value;
     Dbg::SetLocalValue(thread_id, frame_id, slot, sigByte, value, width);
@@ -1432,32 +1454,21 @@
   return ERR_NONE;
 }
 
-/*
- * Returns the value of "this" for the specified frame.
- */
-static JdwpError SF_ThisObject(JdwpState*, const uint8_t* buf, int, ExpandBuf* pReply)
+static JdwpError SF_ThisObject(JdwpState*, const uint8_t* buf, int, ExpandBuf* reply)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   ObjectId thread_id = ReadObjectId(&buf);
   FrameId frame_id = ReadFrameId(&buf);
 
-  ObjectId id;
-  JdwpError rc = Dbg::GetThisObject(thread_id, frame_id, &id);
+  ObjectId object_id;
+  JdwpError rc = Dbg::GetThisObject(thread_id, frame_id, &object_id);
   if (rc != ERR_NONE) {
     return rc;
   }
 
-  uint8_t tag;
-  rc = Dbg::GetObjectTag(id, tag);
-  if (rc != ERR_NONE) {
-    return rc;
-  }
+  VLOG(jdwp) << StringPrintf("  Req for 'this' in thread_id=%#llx frame=%lld --> %#llx",
+                             thread_id, frame_id, object_id);
 
-  VLOG(jdwp) << StringPrintf("  Req for 'this' in thread_id=%#llx frame=%lld --> %#llx '%c'",
-                             thread_id, frame_id, id, static_cast<char>(tag));
-  expandBufAdd1(pReply, tag);
-  expandBufAddObjectId(pReply, id);
-
-  return ERR_NONE;
+  return WriteTaggedObject(reply, object_id);
 }
 
 /*
@@ -1609,7 +1620,7 @@
   { 11,   5,  TR_ThreadGroup,             "ThreadReference.ThreadGroup" },
   { 11,   6,  TR_Frames,                  "ThreadReference.Frames" },
   { 11,   7,  TR_FrameCount,              "ThreadReference.FrameCount" },
-  { 11,   8,  NULL,                       "ThreadReference.OwnedMonitors" },
+  { 11,   8,  TR_OwnedMonitors,           "ThreadReference.OwnedMonitors" },
   { 11,   9,  TR_CurrentContendedMonitor, "ThreadReference.CurrentContendedMonitor" },
   { 11,   10, NULL,                       "ThreadReference.Stop" },
   { 11,   11, NULL,                       "ThreadReference.Interrupt" },
diff --git a/src/monitor.cc b/src/monitor.cc
index df09522..80618e1 100644
--- a/src/monitor.cc
+++ b/src/monitor.cc
@@ -863,12 +863,7 @@
   os << "\n";
 }
 
-static void DumpLockedObject(std::ostream& os, Object* o)
-    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  os << "  - locked <" << o << "> (a " << PrettyTypeOf(o) << ")\n";
-}
-
-void Monitor::DescribeLocks(std::ostream& os, StackVisitor* stack_visitor) {
+void Monitor::VisitLocks(StackVisitor* stack_visitor, void (*callback)(Object*, void*), void* callback_context) {
   AbstractMethod* m = stack_visitor->GetMethod();
   CHECK(m != NULL);
 
@@ -877,7 +872,7 @@
   if (m->IsNative()) {
     if (m->IsSynchronized()) {
       Object* jni_this = stack_visitor->GetCurrentSirt()->GetReference(0);
-      DumpLockedObject(os, jni_this);
+      callback(jni_this, callback_context);
     }
     return;
   }
@@ -891,7 +886,7 @@
   // <clinit> is another special case. The runtime holds the class lock while calling <clinit>.
   MethodHelper mh(m);
   if (mh.IsClassInitializer()) {
-    DumpLockedObject(os, m->GetDeclaringClass());
+    callback(m->GetDeclaringClass(), callback_context);
     // Fall through because there might be synchronization in the user code too.
   }
 
@@ -910,11 +905,6 @@
     return;
   }
 
-  // Verification is an iterative process, so it can visit the same monitor-enter instruction
-  // repeatedly with increasingly accurate type information. We don't want duplicates.
-  // TODO: is this fixed if we share the other std::vector-returning verifier code?
-  STLSortAndRemoveDuplicates(&monitor_enter_dex_pcs);
-
   for (size_t i = 0; i < monitor_enter_dex_pcs.size(); ++i) {
     // The verifier works in terms of the dex pcs of the monitor-enter instructions.
     // We want the registers used by those instructions (so we can read the values out of them).
@@ -930,7 +920,7 @@
     uint16_t monitor_register = ((monitor_enter_instruction >> 8) & 0xff);
     Object* o = reinterpret_cast<Object*>(stack_visitor->GetVReg(m, monitor_register,
                                                                  kReferenceVReg));
-    DumpLockedObject(os, o);
+    callback(o, callback_context);
   }
 }
 
diff --git a/src/monitor.h b/src/monitor.h
index b546289..66db42e 100644
--- a/src/monitor.h
+++ b/src/monitor.h
@@ -87,7 +87,10 @@
   static void DescribeWait(std::ostream& os, const Thread* thread)
       LOCKS_EXCLUDED(Locks::thread_suspend_count_lock_)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  static void DescribeLocks(std::ostream& os, StackVisitor* stack_visitor)
+
+  // Calls 'callback' once for each lock held in the single stack frame represented by
+  // the current state of 'stack_visitor'.
+  static void VisitLocks(StackVisitor* stack_visitor, void (*callback)(Object*, void*), void* callback_context)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   Object* GetObject();
diff --git a/src/thread.cc b/src/thread.cc
index 5acc611..f7c568f 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -85,7 +85,6 @@
 }
 
 void Thread::SetDebuggerUpdatesEnabled(bool enabled) {
-  LOG(INFO) << "Turning debugger updates " << (enabled ? "on" : "off") << " for " << *this;
 #if !defined(ART_USE_LLVM_COMPILER)
   ChangeDebuggerEntryPoint(&entrypoints_, enabled);
 #else
@@ -887,13 +886,20 @@
         Monitor::DescribeWait(os, thread);
       }
       if (can_allocate) {
-        Monitor::DescribeLocks(os, this);
+        Monitor::VisitLocks(this, DumpLockedObject, &os);
       }
     }
 
     ++frame_count;
     return true;
   }
+
+  static void DumpLockedObject(Object* o, void* context)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    std::ostream& os = *reinterpret_cast<std::ostream*>(context);
+    os << "  - locked <" << o << "> (a " << PrettyTypeOf(o) << ")\n";
+  }
+
   std::ostream& os;
   const Thread* thread;
   bool can_allocate;
diff --git a/src/verifier/method_verifier.cc b/src/verifier/method_verifier.cc
index 6611d3c..7afa6d4 100644
--- a/src/verifier/method_verifier.cc
+++ b/src/verifier/method_verifier.cc
@@ -434,7 +434,7 @@
     case VERIFY_ERROR_ACCESS_METHOD:
     case VERIFY_ERROR_INSTANTIATION:
     case VERIFY_ERROR_CLASS_CHANGE:
-      if (Runtime::Current()->IsCompiler()) {
+      if (Runtime::Current()->IsCompiler() || !can_load_classes_) {
         // If we're optimistically running verification at compile time, turn NO_xxx, ACCESS_xxx,
         // class change and instantiation errors into soft verification errors so that we re-verify
         // at runtime. We may fail to find or to agree on access because of not yet available class
@@ -1049,7 +1049,6 @@
   verifier::MethodVerifier::SetInferredRegCategoryMap(ref, *table);
 #endif
 
-
   return true;
 }
 
@@ -1340,19 +1339,12 @@
 }
 
 bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) {
-#ifdef VERIFIER_STATS
-  if (CurrentInsnFlags().IsVisited()) {
-    gDvm.verifierStats.instrsReexamined++;
-  } else {
-    gDvm.verifierStats.instrsExamined++;
-  }
-#endif
-
   // If we're doing FindLocksAtDexPc, check whether we're at the dex pc we care about.
   // We want the state _before_ the instruction, for the case where the dex pc we're
   // interested in is itself a monitor-enter instruction (which is a likely place
   // for a thread to be suspended).
   if (monitor_enter_dex_pcs_ != NULL && work_insn_idx_ == interesting_dex_pc_) {
+    monitor_enter_dex_pcs_->clear(); // The new work line is more accurate than the previous one.
     for (size_t i = 0; i < work_line_->GetMonitorEnterCount(); ++i) {
       monitor_enter_dex_pcs_->push_back(work_line_->GetMonitorEnterDexPc(i));
     }
diff --git a/src/verifier/method_verifier.h b/src/verifier/method_verifier.h
index a02cc25..7779efe 100644
--- a/src/verifier/method_verifier.h
+++ b/src/verifier/method_verifier.h
@@ -224,6 +224,10 @@
   static bool IsClassRejected(Compiler::ClassReference ref)
       LOCKS_EXCLUDED(rejected_classes_lock_);
 
+  bool CanLoadClasses() const {
+    return can_load_classes_;
+  }
+
  private:
   explicit MethodVerifier(const DexFile* dex_file, DexCache* dex_cache,
       ClassLoader* class_loader, uint32_t class_def_idx, const DexFile::CodeItem* code_item,
diff --git a/src/verifier/register_line.cc b/src/verifier/register_line.cc
index afd2eff..74f83da 100644
--- a/src/verifier/register_line.cc
+++ b/src/verifier/register_line.cc
@@ -42,7 +42,8 @@
   } else if (new_type.IsConflict()) {  // should only be set during a merge
     verifier_->Fail(VERIFY_ERROR_BAD_CLASS_SOFT) << "Set register to unknown type " << new_type;
     return false;
-  } else if (!Runtime::Current()->IsCompiler() && new_type.IsUnresolvedTypes()) {
+  } else if (verifier_->CanLoadClasses() && !Runtime::Current()->IsCompiler() &&
+      new_type.IsUnresolvedTypes()) {
     // Unresolvable classes at runtime are bad and marked as a rewrite error.
     verifier_->Fail(VERIFY_ERROR_NO_CLASS) << "Set register to unresolved class '"
                                            << new_type << "' at runtime";