Start implementing jdb "locals".

This lets us show the names and types of the locals, but all the values
will show up as 0/null. We're going to have to walk the whole stack and
take callee-save frames into account to do that right.

Change-Id: Ic6e115513b6e65ae7ed4b7274e70bc514e83190a
diff --git a/src/check_jni.cc b/src/check_jni.cc
index b6670e4..659dcb8 100644
--- a/src/check_jni.cc
+++ b/src/check_jni.cc
@@ -818,7 +818,7 @@
       // TODO
       break;
     case kString:
-      okay = obj->IsString();
+      okay = obj->GetClass()->IsStringClass();
       break;
     case kThrowable:
       // TODO
diff --git a/src/class_linker.cc b/src/class_linker.cc
index 3e547e4..c214b5f 100644
--- a/src/class_linker.cc
+++ b/src/class_linker.cc
@@ -781,7 +781,7 @@
   DCHECK(arg != NULL);
   ClassLinker* class_linker = reinterpret_cast<ClassLinker*>(arg);
 
-  if (obj->IsString()) {
+  if (obj->GetClass()->IsStringClass()) {
     class_linker->intern_table_->RegisterStrong(obj->AsString());
     return;
   }
diff --git a/src/debugger.cc b/src/debugger.cc
index aaabffb..1ad8cc2 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -520,9 +520,34 @@
   return 0;
 }
 
-int Dbg::GetTagWidth(int tag) {
-  UNIMPLEMENTED(FATAL);
-  return 0;
+size_t Dbg::GetTagWidth(int tag) {
+  switch (tag) {
+  case JDWP::JT_VOID:
+    return 0;
+  case JDWP::JT_BYTE:
+  case JDWP::JT_BOOLEAN:
+    return 1;
+  case JDWP::JT_CHAR:
+  case JDWP::JT_SHORT:
+    return 2;
+  case JDWP::JT_FLOAT:
+  case JDWP::JT_INT:
+    return 4;
+  case JDWP::JT_ARRAY:
+  case JDWP::JT_OBJECT:
+  case JDWP::JT_STRING:
+  case JDWP::JT_THREAD:
+  case JDWP::JT_THREAD_GROUP:
+  case JDWP::JT_CLASS_LOADER:
+  case JDWP::JT_CLASS_OBJECT:
+    return sizeof(JDWP::ObjectId);
+  case JDWP::JT_DOUBLE:
+  case JDWP::JT_LONG:
+    return 8;
+  default:
+    LOG(FATAL) << "unknown tag " << tag;
+    return -1;
+  }
 }
 
 int Dbg::GetArrayLength(JDWP::ObjectId arrayId) {
@@ -606,6 +631,77 @@
   return accessFlags;
 }
 
+static JDWP::JdwpTag TagFromClass(Class* c) {
+  if (c->IsArrayClass()) {
+    return JDWP::JT_ARRAY;
+  }
+
+  if (c->IsStringClass()) {
+    return JDWP::JT_STRING;
+  } else if (c->IsClassClass()) {
+    return JDWP::JT_CLASS_OBJECT;
+#if 0 // TODO
+  } else if (dvmInstanceof(clazz, gDvm.classJavaLangThread)) {
+    return JDWP::JT_THREAD;
+  } else if (dvmInstanceof(clazz, gDvm.classJavaLangThreadGroup)) {
+    return JDWP::JT_THREAD_GROUP;
+  } else if (dvmInstanceof(clazz, gDvm.classJavaLangClassLoader)) {
+    return JDWP::JT_CLASS_LOADER;
+#endif
+  } else {
+    return JDWP::JT_OBJECT;
+  }
+}
+
+/*
+ * Objects declared to hold Object might actually hold a more specific
+ * type.  The debugger may take a special interest in these (e.g. it
+ * wants to display the contents of Strings), so we want to return an
+ * appropriate tag.
+ *
+ * Null objects are tagged JT_OBJECT.
+ */
+static JDWP::JdwpTag TagFromObject(const Object* o) {
+  return (o == NULL) ? JDWP::JT_OBJECT : TagFromClass(o->GetClass());
+}
+
+static const uint16_t kEclipseWorkaroundSlot = 1000;
+
+/*
+ * Eclipse appears to expect that the "this" reference is in slot zero.
+ * If it's not, the "variables" display will show two copies of "this",
+ * possibly because it gets "this" from SF.ThisObject and then displays
+ * all locals with nonzero slot numbers.
+ *
+ * So, we remap the item in slot 0 to 1000, and remap "this" to zero.  On
+ * SF.GetValues / SF.SetValues we map them back.
+ */
+static uint16_t MangleSlot(uint16_t slot, const char* name) {
+  uint16_t newSlot = slot;
+  if (strcmp(name, "this") == 0) {
+    newSlot = 0;
+  } else if (slot == 0) {
+    newSlot = kEclipseWorkaroundSlot;
+  }
+  return newSlot;
+}
+
+/*
+ * Reverse Eclipse hack.
+ */
+static uint16_t DemangleSlot(uint16_t slot, Method** sp) {
+  int newSlot = slot;
+  if (slot == kEclipseWorkaroundSlot) {
+    newSlot = 0;
+  } else if (slot == 0) {
+    Frame f;
+    f.SetSP(sp);
+    Method* m = f.GetMethod();
+    newSlot = m->NumRegisters() - m->NumIns();
+  }
+  return newSlot;
+}
+
 void Dbg::OutputDeclaredFields(JDWP::RefTypeId refTypeId, bool withGeneric, JDWP::ExpandBuf* pReply) {
   Class* c = gRegistry->Get<Class*>(refTypeId);
   CHECK(c != NULL);
@@ -706,8 +802,51 @@
   JDWP::Set4BE(expandBufGetBuffer(pReply) + numLinesOffset, context.numItems);
 }
 
-void Dbg::OutputVariableTable(JDWP::RefTypeId refTypeId, JDWP::MethodId id, bool withGeneric, JDWP::ExpandBuf* pReply) {
-  UNIMPLEMENTED(FATAL);
+void Dbg::OutputVariableTable(JDWP::RefTypeId refTypeId, JDWP::MethodId methodId, bool withGeneric, JDWP::ExpandBuf* pReply) {
+  struct DebugCallbackContext {
+    int numItems;
+    JDWP::ExpandBuf* pReply;
+    bool withGeneric;
+
+    static void Callback(void* context, uint16_t slot, uint32_t startAddress, uint32_t endAddress, const char *name, const char *descriptor, const char *signature) {
+      DebugCallbackContext* pContext = reinterpret_cast<DebugCallbackContext*>(context);
+
+      slot = MangleSlot(slot, name);
+
+      LOG(VERBOSE) << StringPrintf("    %2d: %d(%d) '%s' '%s' '%s' slot=%d", pContext->numItems, startAddress, endAddress - startAddress, name, descriptor, signature, slot);
+
+      expandBufAdd8BE(pContext->pReply, startAddress);
+      expandBufAddUtf8String(pContext->pReply, name);
+      expandBufAddUtf8String(pContext->pReply, descriptor);
+      if (pContext->withGeneric) {
+        expandBufAddUtf8String(pContext->pReply, signature);
+      }
+      expandBufAdd4BE(pContext->pReply, endAddress - startAddress);
+      expandBufAdd4BE(pContext->pReply, slot);
+
+      pContext->numItems++;
+    }
+  };
+
+  Method* m = FromMethodId(methodId);
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  const DexFile& dex_file = class_linker->FindDexFile(m->GetDeclaringClass()->GetDexCache());
+  const DexFile::CodeItem* code_item = dex_file.GetCodeItem(m->GetCodeItemOffset());
+
+  expandBufAdd4BE(pReply, m->NumIns());
+
+  // Add numLocals later
+  size_t numLocalsOffset = expandBufGetLength(pReply);
+  expandBufAdd4BE(pReply, 0);
+
+  DebugCallbackContext context;
+  context.numItems = 0;
+  context.pReply = pReply;
+  context.withGeneric = withGeneric;
+
+  dex_file.DecodeDebugInfo(code_item, m, NULL, DebugCallbackContext::Callback, &context);
+
+  JDWP::Set4BE(expandBufGetBuffer(pReply) + numLocalsOffset, context.numItems);
 }
 
 uint8_t Dbg::GetFieldBasicTag(JDWP::ObjectId objId, JDWP::FieldId fieldId) {
@@ -978,11 +1117,87 @@
   return false;
 }
 
-void Dbg::GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint8_t* buf, int expectedLen) {
-  UNIMPLEMENTED(FATAL);
+void Dbg::GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, JDWP::JdwpTag tag, uint8_t* buf, size_t expectedLen) {
+  Method** sp = reinterpret_cast<Method**>(frameId);
+  slot = DemangleSlot(slot, sp);
+
+  switch (tag) {
+  case JDWP::JT_BOOLEAN:
+    {
+      UNIMPLEMENTED(WARNING) << "get boolean local " << slot;
+      CHECK_EQ(expectedLen, 1U);
+      uint32_t intVal = 0; // framePtr[slot];
+      JDWP::Set1(buf+1, intVal != 0);
+    }
+    break;
+  case JDWP::JT_BYTE:
+    {
+      UNIMPLEMENTED(WARNING) << "get byte local " << slot;
+      CHECK_EQ(expectedLen, 1U);
+      uint32_t intVal = 0; // framePtr[slot];
+      JDWP::Set1(buf+1, intVal);
+    }
+    break;
+  case JDWP::JT_SHORT:
+  case JDWP::JT_CHAR:
+    {
+      UNIMPLEMENTED(WARNING) << "get 16-bit local " << slot;
+      CHECK_EQ(expectedLen, 2U);
+      uint32_t intVal = 0; // framePtr[slot];
+      JDWP::Set2BE(buf+1, intVal);
+    }
+    break;
+  case JDWP::JT_INT:
+  case JDWP::JT_FLOAT:
+    {
+      UNIMPLEMENTED(WARNING) << "get 32-bit local " << slot;
+      CHECK_EQ(expectedLen, 4U);
+      uint32_t intVal = 0; // framePtr[slot];
+      JDWP::Set4BE(buf+1, intVal);
+    }
+    break;
+  case JDWP::JT_ARRAY:
+    {
+      UNIMPLEMENTED(WARNING) << "get array local " << slot;
+      CHECK_EQ(expectedLen, sizeof(JDWP::ObjectId));
+      Object* o = NULL; // (Object*)framePtr[slot];
+      if (o != NULL && !Heap::IsHeapAddress(o)) {
+        LOG(FATAL) << "slot " << slot << " expected to hold array: " << o;
+      }
+      JDWP::SetObjectId(buf+1, gRegistry->Add(o));
+    }
+    break;
+  case JDWP::JT_OBJECT:
+    {
+      UNIMPLEMENTED(WARNING) << "get object local " << slot;
+      CHECK_EQ(expectedLen, sizeof(JDWP::ObjectId));
+      Object* o = NULL; // (Object*)framePtr[slot];
+      if (o != NULL && !Heap::IsHeapAddress(o)) {
+        LOG(FATAL) << "slot " << slot << " expected to hold object: " << o;
+      }
+      tag = TagFromObject(o);
+      JDWP::SetObjectId(buf+1, gRegistry->Add(o));
+    }
+    break;
+  case JDWP::JT_DOUBLE:
+  case JDWP::JT_LONG:
+    {
+      UNIMPLEMENTED(WARNING) << "get 64-bit local " << slot;
+      CHECK_EQ(expectedLen, 8U);
+      uint64_t longVal = 0; // memcpy(&longVal, &framePtr[slot], 8);
+      JDWP::Set8BE(buf+1, longVal);
+    }
+    break;
+  default:
+    LOG(FATAL) << "unknown tag " << tag;
+    break;
+  }
+
+  // Prepend tag, which may have been updated.
+  JDWP::Set1(buf, tag);
 }
 
-void Dbg::SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint64_t value, int width) {
+void Dbg::SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, JDWP::JdwpTag tag, uint64_t value, size_t width) {
   UNIMPLEMENTED(FATAL);
 }
 
diff --git a/src/debugger.h b/src/debugger.h
index d9961b7..538ebba 100644
--- a/src/debugger.h
+++ b/src/debugger.h
@@ -141,7 +141,7 @@
   static bool GetSourceFile(JDWP::RefTypeId refTypeId, std::string& source_file);
   static const char* GetObjectTypeName(JDWP::ObjectId objectId);
   static uint8_t GetObjectTag(JDWP::ObjectId objectId);
-  static int GetTagWidth(int tag);
+  static size_t GetTagWidth(int tag);
 
   static int GetArrayLength(JDWP::ObjectId arrayId);
   static uint8_t GetArrayElementTag(JDWP::ObjectId arrayId);
@@ -201,8 +201,8 @@
   static void SuspendSelf();
 
   static bool GetThisObject(JDWP::ObjectId threadId, JDWP::FrameId frameId, JDWP::ObjectId* pThisId);
-  static void GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint8_t* buf, int expectedLen);
-  static void SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint64_t value, int width);
+  static void GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, JDWP::JdwpTag tag, uint8_t* buf, size_t expectedLen);
+  static void SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, JDWP::JdwpTag tag, uint64_t value, size_t width);
 
   /*
    * Debugger notification
diff --git a/src/dex_file.h b/src/dex_file.h
index 8e07dde..92aa794 100644
--- a/src/dex_file.h
+++ b/src/dex_file.h
@@ -577,8 +577,8 @@
                            LocalInfo* local_in_reg, DexDebugNewLocalCb local_cb) const {
     if (local_cb != NULL && local_in_reg[reg].is_live_) {
       local_cb(cnxt, reg, local_in_reg[reg].start_address_, end_address,
-               local_in_reg[reg].name_, local_in_reg[reg].descriptor_,
-               local_in_reg[reg].signature_);
+          local_in_reg[reg].name_, local_in_reg[reg].descriptor_,
+          local_in_reg[reg].signature_ != NULL ? local_in_reg[reg].signature_ : "");
     }
   }
 
diff --git a/src/dex_verifier.h b/src/dex_verifier.h
index 3a7e3d2..182c81a 100644
--- a/src/dex_verifier.h
+++ b/src/dex_verifier.h
@@ -202,7 +202,7 @@
   String* GetDescriptor() const {
     DCHECK(IsUnresolvedTypes());
     DCHECK(klass_or_descriptor_ != NULL);
-    DCHECK(klass_or_descriptor_->IsString());
+    DCHECK(klass_or_descriptor_->GetClass()->IsStringClass());
     return down_cast<String*>(klass_or_descriptor_);
   }
   bool IsArrayClass() const {
@@ -256,7 +256,7 @@
         !IsConflict()) {
       DCHECK(klass_or_descriptor != NULL);
       DCHECK(IsUnresolvedTypes() || klass_or_descriptor_->IsClass());
-      DCHECK(!IsUnresolvedTypes() || klass_or_descriptor_->IsString());
+      DCHECK(!IsUnresolvedTypes() || klass_or_descriptor_->GetClass()->IsStringClass());
     }
   }
 
diff --git a/src/hprof/hprof.cc b/src/hprof/hprof.cc
index db1b637..47d31fc 100644
--- a/src/hprof/hprof.cc
+++ b/src/hprof/hprof.cc
@@ -352,7 +352,7 @@
       rec->AddId((HprofObjectId)0);    // no prot domain
       rec->AddId((HprofId)0);           // reserved
       rec->AddId((HprofId)0);           // reserved
-      if (obj->IsClassClass()) {
+      if (thisClass->IsClassClass()) {
         // ClassObjects have their static fields appended, so aren't all the same size.
         // But they're at least this size.
         rec->AddU4(sizeof(Class)); // instance size
diff --git a/src/image_writer.cc b/src/image_writer.cc
index ef577af..cdb7bd0 100644
--- a/src/image_writer.cc
+++ b/src/image_writer.cc
@@ -84,7 +84,7 @@
   }
 
   // if it is a string, we want to intern it if its not interned.
-  if (obj->IsString()) {
+  if (obj->GetClass()->IsStringClass()) {
     // we must be an interned string that was forward referenced and already assigned
     if (IsImageOffsetAssigned(obj)) {
       DCHECK_EQ(obj, obj->AsString()->Intern());
diff --git a/src/image_writer.h b/src/image_writer.h
index 35486b0..ec17ebf 100644
--- a/src/image_writer.h
+++ b/src/image_writer.h
@@ -40,7 +40,7 @@
   static void SetImageOffset(Object* object, size_t offset) {
     DCHECK(object != NULL);
     // should be no lock (but it might be forward referenced interned string)
-    DCHECK(object->monitor_ == 0 || object->IsString());
+    DCHECK(object->monitor_ == 0 || object->GetClass()->IsStringClass());
     DCHECK_NE(0U, offset);
     object->monitor_ = offset;
   }
diff --git a/src/java_lang_Class.cc b/src/java_lang_Class.cc
index 1700ce1..1a625be 100644
--- a/src/java_lang_Class.cc
+++ b/src/java_lang_Class.cc
@@ -237,7 +237,7 @@
   Class* klass = Decode<Class*>(env, jklass);
   DCHECK(klass->IsClass());
   String* name = Decode<String*>(env, jname);
-  DCHECK(name->IsString());
+  DCHECK(name->GetClass()->IsStringClass());
 
   for (size_t i = 0; i < klass->NumInstanceFields(); ++i) {
     Field* f = klass->GetInstanceField(i);
diff --git a/src/jdwp/jdwp_constants.h b/src/jdwp/jdwp_constants.h
index dc6d1bc..6d550f2 100644
--- a/src/jdwp/jdwp_constants.h
+++ b/src/jdwp/jdwp_constants.h
@@ -217,7 +217,7 @@
 /*
  * Tag constants.
  */
-enum JdwpType {
+enum JdwpTag {
   JT_ARRAY                 = '[',
   JT_BYTE                  = 'B',
   JT_CHAR                  = 'C',
@@ -235,7 +235,7 @@
   JT_CLASS_LOADER          = 'l',
   JT_CLASS_OBJECT          = 'c',
 };
-std::ostream& operator<<(std::ostream& os, const JdwpType& value);
+std::ostream& operator<<(std::ostream& os, const JdwpTag& value);
 
 }  // namespace JDWP
 
diff --git a/src/jdwp/jdwp_handler.cc b/src/jdwp/jdwp_handler.cc
index 5bf95f1..3d00930 100644
--- a/src/jdwp/jdwp_handler.cc
+++ b/src/jdwp/jdwp_handler.cc
@@ -68,7 +68,7 @@
 /*
  * Helper function: read a variable-width value from the input buffer.
  */
-static uint64_t jdwpReadValue(const uint8_t** pBuf, int width) {
+static uint64_t jdwpReadValue(const uint8_t** pBuf, size_t width) {
   uint64_t value = -1;
   switch (width) {
   case 1:     value = Read1(pBuf); break;
@@ -119,7 +119,7 @@
 
   for (uint32_t i = 0; i < numArgs; i++) {
     uint8_t typeTag = Read1(&buf);
-    int width = Dbg::GetTagWidth(typeTag);
+    size_t width = Dbg::GetTagWidth(typeTag);
     uint64_t value = jdwpReadValue(&buf, width);
 
     LOG(VERBOSE) << StringPrintf("          '%c'(%d): 0x%llx", typeTag, width, value);
@@ -142,7 +142,7 @@
       expandBufAdd1(pReply, JT_OBJECT);
       expandBufAddObjectId(pReply, objectId);
     } else {
-      int width = Dbg::GetTagWidth(resultTag);
+      size_t width = Dbg::GetTagWidth(resultTag);
 
       expandBufAdd1(pReply, resultTag);
       if (width != 0) {
@@ -660,7 +660,7 @@
   for (uint32_t i = 0; i < values; i++) {
     FieldId fieldId = ReadFieldId(&buf);
     uint8_t fieldTag = Dbg::GetStaticFieldBasicTag(classId, fieldId);
-    int width = Dbg::GetTagWidth(fieldTag);
+    size_t width = Dbg::GetTagWidth(fieldTag);
     uint64_t value = jdwpReadValue(&buf, width);
 
     LOG(VERBOSE) << StringPrintf("    --> field=%x tag=%c -> %lld", fieldId, fieldTag, value);
@@ -808,7 +808,7 @@
     FieldId fieldId = ReadFieldId(&buf);
 
     uint8_t fieldTag = Dbg::GetFieldBasicTag(objectId, fieldId);
-    int width = Dbg::GetTagWidth(fieldTag);
+    size_t width = Dbg::GetTagWidth(fieldTag);
     uint64_t value = jdwpReadValue(&buf, width);
 
     LOG(VERBOSE) << StringPrintf("    --> fieldId=%x tag='%c'(%d) value=%lld", fieldId, fieldTag, width, value);
@@ -1446,11 +1446,11 @@
   expandBufAdd4BE(pReply, slots);     /* "int values" */
   for (uint32_t i = 0; i < slots; i++) {
     uint32_t slot = Read4BE(&buf);
-    uint8_t reqSigByte = Read1(&buf);
+    JDWP::JdwpTag reqSigByte = static_cast<JDWP::JdwpTag>(Read1(&buf));
 
     LOG(VERBOSE) << StringPrintf("    --> slot %d '%c'", slot, reqSigByte);
 
-    int width = Dbg::GetTagWidth(reqSigByte);
+    size_t width = Dbg::GetTagWidth(reqSigByte);
     uint8_t* ptr = expandBufAddSpace(pReply, width+1);
     Dbg::GetLocalValue(threadId, frameId, slot, reqSigByte, ptr, width);
   }
@@ -1470,8 +1470,8 @@
 
   for (uint32_t i = 0; i < slots; i++) {
     uint32_t slot = Read4BE(&buf);
-    uint8_t sigByte = Read1(&buf);
-    int width = Dbg::GetTagWidth(sigByte);
+    JDWP::JdwpTag sigByte = static_cast<JDWP::JdwpTag>(Read1(&buf));
+    size_t width = Dbg::GetTagWidth(sigByte);
     uint64_t value = jdwpReadValue(&buf, width);
 
     LOG(VERBOSE) << StringPrintf("    --> slot %d '%c' %llx", slot, sigByte, value);
diff --git a/src/jdwp/jdwp_main.cc b/src/jdwp/jdwp_main.cc
index 3bc0d41..28ea303 100644
--- a/src/jdwp/jdwp_main.cc
+++ b/src/jdwp/jdwp_main.cc
@@ -457,6 +457,30 @@
   return os;
 }
 
+std::ostream& operator<<(std::ostream& os, const JdwpTag& value) {
+  switch (value) {
+  case JT_ARRAY: os << "JT_ARRAY"; break;
+  case JT_BYTE: os << "JT_BYTE"; break;
+  case JT_CHAR: os << "JT_CHAR"; break;
+  case JT_OBJECT: os << "JT_OBJECT"; break;
+  case JT_FLOAT: os << "JT_FLOAT"; break;
+  case JT_DOUBLE: os << "JT_DOUBLE"; break;
+  case JT_INT: os << "JT_INT"; break;
+  case JT_LONG: os << "JT_LONG"; break;
+  case JT_SHORT: os << "JT_SHORT"; break;
+  case JT_VOID: os << "JT_VOID"; break;
+  case JT_BOOLEAN: os << "JT_BOOLEAN"; break;
+  case JT_STRING: os << "JT_STRING"; break;
+  case JT_THREAD: os << "JT_THREAD"; break;
+  case JT_THREAD_GROUP: os << "JT_THREAD_GROUP"; break;
+  case JT_CLASS_LOADER: os << "JT_CLASS_LOADER"; break;
+  case JT_CLASS_OBJECT: os << "JT_CLASS_OBJECT"; break;
+  default:
+    os << "JdwpTag[" << static_cast<int32_t>(value) << "]";
+  }
+  return os;
+}
+
 }  // namespace JDWP
 
 }  // namespace art
diff --git a/src/oatdump.cc b/src/oatdump.cc
index 8b0ada4..2d5732f 100644
--- a/src/oatdump.cc
+++ b/src/oatdump.cc
@@ -315,7 +315,7 @@
       StringAppendF(&summary, "FIELD %s", PrettyField(field).c_str());
     } else if (obj->IsArrayInstance()) {
       StringAppendF(&summary, "ARRAY %d", obj->AsArray()->GetLength());
-    } else if (obj->IsString()) {
+    } else if (obj->GetClass()->IsStringClass()) {
       StringAppendF(&summary, "STRING %s", obj->AsString()->ToModifiedUtf8().c_str());
     } else {
       StringAppendF(&summary, "OBJECT");
diff --git a/src/object.cc b/src/object.cc
index 49ba5a3..8cf79c2 100644
--- a/src/object.cc
+++ b/src/object.cc
@@ -24,6 +24,11 @@
 
 namespace art {
 
+String* Object::AsString() {
+  DCHECK(GetClass()->IsStringClass());
+  return down_cast<String*>(this);
+}
+
 Object* Object::Clone() {
   Class* c = GetClass();
   DCHECK(!c->IsClassClass());
@@ -54,11 +59,6 @@
   return Monitor::GetThinLockId(monitor_);
 }
 
-bool Object::IsString() const {
-  // TODO use "klass_ == String::GetJavaLangString()" instead?
-  return GetClass() == GetClass()->GetDescriptor()->GetClass();
-}
-
 void Object::MonitorEnter(Thread* thread) {
   Monitor::MonitorEnter(thread, this);
 }
@@ -1014,6 +1014,16 @@
   return IsInSamePackage(klass1->descriptor_, klass2->descriptor_);
 }
 
+bool Class::IsClassClass() const {
+  Class* java_lang_Class = GetClass()->GetClass();
+  return this == java_lang_Class;
+}
+
+bool Class::IsStringClass() const {
+  // TODO use "this == String::GetJavaLangString()" instead? or do we need this too early?
+  return this == GetDescriptor()->GetClass();
+}
+
 const ClassLoader* Class::GetClassLoader() const {
   return GetFieldObject<const ClassLoader*>(OFFSET_OF_OBJECT_MEMBER(Class, class_loader_), false);
 }
diff --git a/src/object.h b/src/object.h
index 6a91970..98b36ab 100644
--- a/src/object.h
+++ b/src/object.h
@@ -211,8 +211,6 @@
     return down_cast<const Class*>(this);
   }
 
-  bool IsClassClass() const;
-
   bool IsObjectArray() const;
 
   template<class T>
@@ -233,12 +231,7 @@
     return down_cast<const Array*>(this);
   }
 
-  bool IsString() const;
-
-  String* AsString() {
-    DCHECK(IsString());
-    return down_cast<String*>(this);
-  }
+  String* AsString();
 
   bool IsMethod() const;
 
@@ -1447,6 +1440,10 @@
     return GetComponentType() != NULL;
   }
 
+  bool IsClassClass() const;
+
+  bool IsStringClass() const;
+
   Class* GetComponentType() const {
     return GetFieldObject<Class*>(OFFSET_OF_OBJECT_MEMBER(Class, component_type_), false);
   }
@@ -2126,11 +2123,6 @@
   return GetClass() == java_lang_Class;
 }
 
-inline bool Object::IsClassClass() const {
-  Class* java_lang_Class = GetClass()->GetClass();
-  return this == java_lang_Class;
-}
-
 inline bool Object::IsObjectArray() const {
   return IsArrayInstance() && !GetClass()->GetComponentType()->IsPrimitive();
 }