Add field-access/modification to ti-stress.

These tests stress jvmti FieldAccess and FieldModification.

Test: ./test/testrunner/testrunner.py --host --field-stress -j40
Change-Id: Ie2ea91e165beed73f14e8aff0adb137becdccd01
diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc
index 76f8943..8d4cc89 100644
--- a/test/ti-stress/stress.cc
+++ b/test/ti-stress/stress.cc
@@ -39,6 +39,7 @@
   bool vm_class_loader_initialized;
   bool trace_stress;
   bool redefine_stress;
+  bool field_stress;
 };
 
 static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) {
@@ -130,12 +131,20 @@
         generic_(nullptr) {}
 
   ~ScopedClassInfo() {
-    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
-    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
+    if (class_ != nullptr) {
+      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
+      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
+    }
   }
 
   bool Init() {
-    return jvmtienv_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE;
+    if (class_ == nullptr) {
+      name_ = const_cast<char*>("<NONE>");
+      generic_ = const_cast<char*>("<NONE>");
+      return true;
+    } else {
+      return jvmtienv_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE;
+    }
   }
 
   jclass GetClass() const {
@@ -216,6 +225,71 @@
   friend std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m);
 };
 
+class ScopedFieldInfo {
+ public:
+  ScopedFieldInfo(jvmtiEnv* jvmtienv, jclass field_klass, jfieldID field)
+      : jvmtienv_(jvmtienv),
+        declaring_class_(field_klass),
+        field_(field),
+        class_info_(nullptr),
+        name_(nullptr),
+        type_(nullptr),
+        generic_(nullptr) {}
+
+  ~ScopedFieldInfo() {
+    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
+    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(type_));
+    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
+  }
+
+  bool Init() {
+    class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_));
+    return class_info_->Init() &&
+        (jvmtienv_->GetFieldName(
+            declaring_class_, field_, &name_, &type_, &generic_) == JVMTI_ERROR_NONE);
+  }
+
+  const ScopedClassInfo& GetDeclaringClassInfo() const {
+    return *class_info_;
+  }
+
+  jclass GetDeclaringClass() const {
+    return declaring_class_;
+  }
+
+  const char* GetName() const {
+    return name_;
+  }
+
+  const char* GetType() const {
+    return type_;
+  }
+
+  const char* GetGeneric() const {
+    return generic_;
+  }
+
+ private:
+  jvmtiEnv* jvmtienv_;
+  jclass declaring_class_;
+  jfieldID field_;
+  std::unique_ptr<ScopedClassInfo> class_info_;
+  char* name_;
+  char* type_;
+  char* generic_;
+
+  friend std::ostream& operator<<(std::ostream &os, ScopedFieldInfo const& m);
+};
+
+std::ostream& operator<<(std::ostream &os, const ScopedFieldInfo* m) {
+  return os << *m;
+}
+
+std::ostream& operator<<(std::ostream &os, ScopedFieldInfo const& m) {
+  return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName()
+            << ":" << m.GetType();
+}
+
 std::ostream& operator<<(std::ostream &os, const ScopedMethodInfo* m) {
   return os << *m;
 }
@@ -304,6 +378,100 @@
   }
 }
 
+void JNICALL FieldAccessHook(jvmtiEnv* jvmtienv,
+                             JNIEnv* env,
+                             jthread thread,
+                             jmethodID m,
+                             jlocation location,
+                             jclass field_klass,
+                             jobject object,
+                             jfieldID field) {
+  ScopedThreadInfo info(jvmtienv, env, thread);
+  ScopedMethodInfo method_info(jvmtienv, env, m);
+  ScopedFieldInfo field_info(jvmtienv, field_klass, field);
+  jclass oklass = (object != nullptr) ? env->GetObjectClass(object) : nullptr;
+  ScopedClassInfo obj_class_info(jvmtienv, oklass);
+  if (!method_info.Init() || !field_info.Init() || !obj_class_info.Init()) {
+    LOG(ERROR) << "Unable to get callback info!";
+    return;
+  }
+  LOG(INFO) << "ACCESS field \"" << field_info << "\" on object of "
+            << "type \"" << obj_class_info.GetName() << "\" in method \"" << method_info
+            << "\" at location 0x" << std::hex << location << ". Thread is \""
+            << info.GetName() << "\".";
+  env->DeleteLocalRef(oklass);
+}
+
+static std::string PrintJValue(jvmtiEnv* jvmtienv, JNIEnv* env, char type, jvalue new_value) {
+  std::ostringstream oss;
+  switch (type) {
+    case 'L': {
+      jobject nv = new_value.l;
+      if (nv == nullptr) {
+        oss << "\"null\"";
+      } else {
+        jclass nv_klass = env->GetObjectClass(nv);
+        ScopedClassInfo nv_class_info(jvmtienv, nv_klass);
+        if (!nv_class_info.Init()) {
+          oss << "with unknown type";
+        } else {
+          oss << "of type \"" << nv_class_info.GetName() << "\"";
+        }
+        env->DeleteLocalRef(nv_klass);
+      }
+      break;
+    }
+    case 'Z': {
+      if (new_value.z) {
+        oss << "true";
+      } else {
+        oss << "false";
+      }
+      break;
+    }
+#define SEND_VALUE(chr, sym, type) \
+    case chr: { \
+      oss << static_cast<type>(new_value.sym); \
+      break; \
+    }
+    SEND_VALUE('B', b, int8_t);
+    SEND_VALUE('C', c, uint16_t);
+    SEND_VALUE('S', s, int16_t);
+    SEND_VALUE('I', i, int32_t);
+    SEND_VALUE('J', j, int64_t);
+    SEND_VALUE('F', f, float);
+    SEND_VALUE('D', d, double);
+#undef SEND_VALUE
+  }
+  return oss.str();
+}
+
+void JNICALL FieldModificationHook(jvmtiEnv* jvmtienv,
+                                   JNIEnv* env,
+                                   jthread thread,
+                                   jmethodID m,
+                                   jlocation location,
+                                   jclass field_klass,
+                                   jobject object,
+                                   jfieldID field,
+                                   char type,
+                                   jvalue new_value) {
+  ScopedThreadInfo info(jvmtienv, env, thread);
+  ScopedMethodInfo method_info(jvmtienv, env, m);
+  ScopedFieldInfo field_info(jvmtienv, field_klass, field);
+  jclass oklass = (object != nullptr) ? env->GetObjectClass(object) : nullptr;
+  ScopedClassInfo obj_class_info(jvmtienv, oklass);
+  if (!method_info.Init() || !field_info.Init() || !obj_class_info.Init()) {
+    LOG(ERROR) << "Unable to get callback info!";
+    return;
+  }
+  LOG(INFO) << "MODIFY field \"" << field_info << "\" on object of "
+            << "type \"" << obj_class_info.GetName() << "\" in method \"" << method_info
+            << "\" at location 0x" << std::hex << location << std::dec << ". New value is "
+            << PrintJValue(jvmtienv, env, type, new_value) << ". Thread is \""
+            << info.GetName() << "\".";
+  env->DeleteLocalRef(oklass);
+}
 void JNICALL MethodExitHook(jvmtiEnv* jvmtienv,
                             JNIEnv* env,
                             jthread thread,
@@ -342,14 +510,34 @@
                               JNIEnv* env,
                               jthread thread,
                               jclass klass) {
-  ScopedThreadInfo info(jvmtienv, env, thread);
-  ScopedClassInfo class_info(jvmtienv, klass);
-  if (!class_info.Init()) {
-    LOG(ERROR) << "Unable to get class info!";
-    return;
+  StressData* data = nullptr;
+  CHECK_EQ(jvmtienv->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)),
+           JVMTI_ERROR_NONE);
+  if (data->field_stress) {
+    jint nfields;
+    jfieldID* fields;
+    if (jvmtienv->GetClassFields(klass, &nfields, &fields) != JVMTI_ERROR_NONE) {
+      LOG(ERROR) << "Unable to get a classes fields!";
+      return;
+    }
+    for (jint i = 0; i < nfields; i++) {
+      jfieldID f = fields[i];
+      // Ignore errors
+      jvmtienv->SetFieldAccessWatch(klass, f);
+      jvmtienv->SetFieldModificationWatch(klass, f);
+    }
+    jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fields));
   }
-  LOG(INFO) << "Prepared class \"" << class_info.GetName() << "\". Thread is \""
-            << info.GetName() << "\"";
+  if (data->trace_stress) {
+    ScopedThreadInfo info(jvmtienv, env, thread);
+    ScopedClassInfo class_info(jvmtienv, klass);
+    if (!class_info.Init()) {
+      LOG(ERROR) << "Unable to get class info!";
+      return;
+    }
+    LOG(INFO) << "Prepared class \"" << class_info.GetName() << "\". Thread is \""
+              << info.GetName() << "\"";
+  }
 }
 
 // The hook we are using.
@@ -402,7 +590,7 @@
 }
 
 // Options are
-// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace]
+// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace,][field]
 static void ReadOptions(StressData* data, char* options) {
   std::string ops(options);
   CHECK_EQ(GetOption(ops), "jvmti-stress") << "Options should start with jvmti-stress";
@@ -411,6 +599,8 @@
     std::string cur = GetOption(ops);
     if (cur == "trace") {
       data->trace_stress = true;
+    } else if (cur == "field") {
+      data->field_stress = true;
     } else if (cur == "redefine") {
       data->redefine_stress = true;
       ops = AdvanceOption(ops);
@@ -451,18 +641,54 @@
     jni_env->DeleteLocalRef(klass);
     data->vm_class_loader_initialized = true;
   }
-  if (data->trace_stress) {
-    if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
-                                            JVMTI_EVENT_METHOD_ENTRY,
-                                            nullptr) != JVMTI_ERROR_NONE) {
-      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_ENTRY event!";
-    }
-    if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
-                                        JVMTI_EVENT_METHOD_EXIT,
-                                        nullptr) != JVMTI_ERROR_NONE) {
-      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_EXIT event!";
-    }
+}
+
+static bool WatchAllFields(JavaVM* vm, jvmtiEnv* jvmti) {
+  if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+                                      JVMTI_EVENT_CLASS_PREPARE,
+                                      nullptr) != JVMTI_ERROR_NONE) {
+    LOG(ERROR) << "Couldn't set prepare event!";
+    return false;
   }
+  // TODO We really shouldn't need to do this step here.
+  jint nklass;
+  jclass* klasses;
+  if (jvmti->GetLoadedClasses(&nklass, &klasses) != JVMTI_ERROR_NONE) {
+    LOG(WARNING) << "Couldn't get loaded classes! Ignoring.";
+    return true;
+  }
+  JNIEnv* jni = nullptr;
+  if (vm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6)) {
+    LOG(ERROR) << "Unable to get jni env. Ignoring and potentially leaking jobjects.";
+    return false;
+  }
+  for (jint i = 0; i < nklass; i++) {
+    jclass k = klasses[i];
+    ScopedClassInfo sci(jvmti, k);
+    if (sci.Init()) {
+      LOG(INFO) << "NOTE: class " << sci.GetName() << " already loaded.";
+    }
+    jint nfields;
+    jfieldID* fields;
+    jvmtiError err = jvmti->GetClassFields(k, &nfields, &fields);
+    if (err == JVMTI_ERROR_NONE) {
+      for (jint j = 0; j < nfields; j++) {
+        jfieldID f = fields[j];
+        if (jvmti->SetFieldModificationWatch(k, f) != JVMTI_ERROR_NONE ||
+            jvmti->SetFieldAccessWatch(k, f) != JVMTI_ERROR_NONE) {
+          LOG(ERROR) << "Unable to set watches on a field.";
+          return false;
+        }
+      }
+    } else if (err != JVMTI_ERROR_CLASS_NOT_PREPARED) {
+      LOG(ERROR) << "Unexpected error getting class fields!";
+      return false;
+    }
+    jvmti->Deallocate(reinterpret_cast<unsigned char*>(fields));
+    jni->DeleteLocalRef(k);
+  }
+  jvmti->Deallocate(reinterpret_cast<unsigned char*>(klasses));
+  return true;
 }
 
 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm,
@@ -501,23 +727,27 @@
   cb.VMInit = PerformFinalSetupVMInit;
   cb.MethodEntry = MethodEntryHook;
   cb.MethodExit = MethodExitHook;
+  cb.FieldAccess = FieldAccessHook;
+  cb.FieldModification = FieldModificationHook;
   cb.ClassPrepare = ClassPrepareHook;
   if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
     LOG(ERROR) << "Unable to set class file load hook cb!";
     return 1;
   }
   if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
-                                      JVMTI_EVENT_NATIVE_METHOD_BIND,
-                                      nullptr) != JVMTI_ERROR_NONE) {
-    LOG(ERROR) << "Unable to enable JVMTI_EVENT_NATIVE_METHOD_BIND event!";
-    return 1;
-  }
-  if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
                                       JVMTI_EVENT_VM_INIT,
                                       nullptr) != JVMTI_ERROR_NONE) {
     LOG(ERROR) << "Unable to enable JVMTI_EVENT_VM_INIT event!";
     return 1;
   }
+  if (data->redefine_stress) {
+    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+                                        JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+                                        nullptr) != JVMTI_ERROR_NONE) {
+      LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!";
+      return 1;
+    }
+  }
   if (data->trace_stress) {
     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
                                         JVMTI_EVENT_CLASS_PREPARE,
@@ -525,12 +755,39 @@
       LOG(ERROR) << "Unable to enable CLASS_PREPARE event!";
       return 1;
     }
-  }
-  if (data->redefine_stress) {
     if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
-                                        JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+                                        JVMTI_EVENT_NATIVE_METHOD_BIND,
                                         nullptr) != JVMTI_ERROR_NONE) {
-      LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!";
+      LOG(ERROR) << "Unable to enable JVMTI_EVENT_NATIVE_METHOD_BIND event!";
+      return 1;
+    }
+    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+                                        JVMTI_EVENT_METHOD_ENTRY,
+                                        nullptr) != JVMTI_ERROR_NONE) {
+      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_ENTRY event!";
+      return 1;
+    }
+    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+                                        JVMTI_EVENT_METHOD_EXIT,
+                                        nullptr) != JVMTI_ERROR_NONE) {
+      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_EXIT event!";
+      return 1;
+    }
+  }
+  if (data->field_stress) {
+    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+                                        JVMTI_EVENT_FIELD_MODIFICATION,
+                                        nullptr) != JVMTI_ERROR_NONE) {
+      LOG(ERROR) << "Unable to enable FIELD_MODIFICATION event!";
+      return 1;
+    }
+    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+                                        JVMTI_EVENT_FIELD_ACCESS,
+                                        nullptr) != JVMTI_ERROR_NONE) {
+      LOG(ERROR) << "Unable to enable FIELD_ACCESS event!";
+      return 1;
+    }
+    if (!WatchAllFields(vm, jvmti)) {
       return 1;
     }
   }