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/etc/run-test-jar b/test/etc/run-test-jar
index 8aacc8c..c44fb97 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -63,6 +63,7 @@
 TEST_IS_NDEBUG="n"
 APP_IMAGE="y"
 JVMTI_STRESS="n"
+JVMTI_FIELD_STRESS="n"
 JVMTI_TRACE_STRESS="n"
 JVMTI_REDEFINE_STRESS="n"
 VDEX_FILTER=""
@@ -159,6 +160,10 @@
         JVMTI_STRESS="y"
         JVMTI_REDEFINE_STRESS="y"
         shift
+    elif [ "x$1" = "x--jvmti-field-stress" ]; then
+        JVMTI_STRESS="y"
+        JVMTI_FIELD_STRESS="y"
+        shift
     elif [ "x$1" = "x--jvmti-trace-stress" ]; then
         JVMTI_STRESS="y"
         JVMTI_TRACE_STRESS="y"
@@ -415,6 +420,9 @@
       agent_args="${agent_args},redefine,${DEXTER_BINARY},${file_1},${file_2}"
     fi
   fi
+  if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then
+    agent_args="${agent_args},field"
+  fi
   if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then
     agent_args="${agent_args},trace"
   fi
diff --git a/test/knownfailures.json b/test/knownfailures.json
index b7f5d4c..d8786a8 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -511,7 +511,7 @@
             "645-checker-abs-simd",
             "706-checker-scheduler"],
         "description": ["Checker tests are not compatible with jvmti."],
-        "variant": "jvmti-stress | redefine-stress | trace-stress"
+        "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress"
     },
     {
         "tests": [
@@ -550,7 +550,7 @@
             "981-dedup-original-dex"
         ],
         "description": ["Tests that require exact knowledge of the number of plugins and agents."],
-        "variant": "jvmti-stress | redefine-stress | trace-stress"
+        "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress"
     },
     {
         "tests": [
@@ -585,6 +585,13 @@
     },
     {
         "tests": [
+            "004-ThreadStress"
+        ],
+        "description": "The thread stress test just takes too long with field-stress",
+        "variant": "jvmti-stress | field-stress"
+    },
+    {
+        "tests": [
             "031-class-attributes",
             "911-get-stack-trace"
         ],
diff --git a/test/run-test b/test/run-test
index 1b6df16..044f63f 100755
--- a/test/run-test
+++ b/test/run-test
@@ -144,6 +144,7 @@
 gc_verify="false"
 gc_stress="false"
 jvmti_trace_stress="false"
+jvmti_field_stress="false"
 jvmti_redefine_stress="false"
 strace="false"
 always_clean="no"
@@ -244,6 +245,9 @@
     elif [ "x$1" = "x--jvmti-redefine-stress" ]; then
         jvmti_redefine_stress="true"
         shift
+    elif [ "x$1" = "x--jvmti-field-stress" ]; then
+        jvmti_field_stress="true"
+        shift
     elif [ "x$1" = "x--jvmti-trace-stress" ]; then
         jvmti_trace_stress="true"
         shift
@@ -460,6 +464,9 @@
 if [ "$jvmti_redefine_stress" = "true" ]; then
     run_args="${run_args} --no-app-image --jvmti-redefine-stress"
 fi
+if [ "$jvmti_field_stress" = "true" ]; then
+    run_args="${run_args} --no-app-image --jvmti-field-stress"
+fi
 if [ "$jvmti_trace_stress" = "true" ]; then
     run_args="${run_args} --no-app-image --jvmti-trace-stress"
 fi
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 3445071..b6a5963 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -147,7 +147,8 @@
   VARIANT_TYPE_DICT['relocate'] = {'relocate-npatchoat', 'relocate', 'no-relocate'}
   VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'}
   VARIANT_TYPE_DICT['address_sizes'] = {'64', '32'}
-  VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress'}
+  VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress',
+                                'field-stress'}
   VARIANT_TYPE_DICT['compiler'] = {'interp-ac', 'interpreter', 'jit', 'optimizing',
                               'regalloc_gc', 'speed-profile'}
 
@@ -437,7 +438,9 @@
         options_test += ' --debuggable'
 
       if jvmti == 'jvmti-stress':
-        options_test += ' --jvmti-trace-stress --jvmti-redefine-stress'
+        options_test += ' --jvmti-trace-stress --jvmti-redefine-stress --jvmti-field-stress'
+      elif jvmti == 'field-stress':
+        options_test += ' --jvmti-field-stress'
       elif jvmti == 'trace-stress':
         options_test += ' --jvmti-trace-stress'
       elif jvmti == 'redefine-stress':
@@ -960,6 +963,8 @@
     JVMTI_TYPES.add('jvmti-stress')
   if options['redefine_stress']:
     JVMTI_TYPES.add('redefine-stress')
+  if options['field_stress']:
+    JVMTI_TYPES.add('field-stress')
   if options['trace_stress']:
     JVMTI_TYPES.add('trace-stress')
   if options['no_jvmti']:
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;
     }
   }