/*
 * Copyright 2017 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 <cstdio>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <strstream>

#include <jni.h>

#include "base/utils.h"
#include "jvmti.h"

#pragma clang diagnostic push

// Slicer's headers have code that triggers these warnings. b/65298177
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wsign-compare"

#include "slicer/code_ir.h"
#include "slicer/control_flow_graph.h"
#include "slicer/dex_ir.h"
#include "slicer/dex_ir_builder.h"
#include "slicer/instrumentation.h"
#include "slicer/reader.h"
#include "slicer/writer.h"

#pragma clang diagnostic pop

namespace art {

// Should we do a 'full_rewrite' with this test?
static constexpr bool kDoFullRewrite = true;

struct StressData {
  bool vm_class_loader_initialized;
  bool trace_stress;
  bool redefine_stress;
  bool field_stress;
  bool step_stress;
};

static void DeleteLocalRef(JNIEnv* env, jobject obj) {
  if (obj != nullptr) {
    env->DeleteLocalRef(obj);
  }
}

static bool DoExtractClassFromData(jvmtiEnv* env,
                                   const std::string& descriptor,
                                   jint in_len,
                                   const unsigned char* in_data,
                                   /*out*/jint* out_len,
                                   /*out*/unsigned char** out_data) {
  dex::Reader reader(in_data, in_len);
  dex::u4 class_idx = reader.FindClassIndex(descriptor.c_str());
  if (class_idx != dex::kNoIndex) {
    reader.CreateClassIr(class_idx);
  } else {
    LOG(ERROR) << "ERROR: Can't find class " << descriptor;
    return false;
  }
  auto dex_ir = reader.GetIr();

  if (kDoFullRewrite) {
    for (auto& ir_method : dex_ir->encoded_methods) {
      if (ir_method->code != nullptr) {
        lir::CodeIr code_ir(ir_method.get(), dex_ir);
        lir::ControlFlowGraph cfg_compact(&code_ir, false);
        lir::ControlFlowGraph cfg_verbose(&code_ir, true);
        code_ir.Assemble();
      }
    }
  }
  dex::Writer writer(dex_ir);

  struct Allocator : public dex::Writer::Allocator {
    explicit Allocator(jvmtiEnv* jvmti_env) : jvmti_env_(jvmti_env) {}
    void* Allocate(size_t size) override {
      unsigned char* out = nullptr;
      if (JVMTI_ERROR_NONE != jvmti_env_->Allocate(size, &out)) {
        return nullptr;
      } else {
        return out;
      }
    }
    void Free(void* ptr) override {
      jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
    }
   private:
    jvmtiEnv* jvmti_env_;
  };
  Allocator alloc(env);
  size_t res_len;
  unsigned char* res = writer.CreateImage(&alloc, &res_len);
  if (res != nullptr) {
    *out_data = res;
    *out_len = res_len;
    return true;
  } else {
    return false;
  }
}

class ScopedThreadInfo {
 public:
  ScopedThreadInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jthread thread)
      : jvmtienv_(jvmtienv), env_(env), free_name_(false) {
    memset(&info_, 0, sizeof(info_));
    if (thread == nullptr) {
      info_.name = const_cast<char*>("<NULLPTR>");
    } else if (jvmtienv->GetThreadInfo(thread, &info_) != JVMTI_ERROR_NONE) {
      info_.name = const_cast<char*>("<UNKNOWN THREAD>");
    } else {
      free_name_ = true;
    }
  }

  ~ScopedThreadInfo() {
    if (free_name_) {
      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(info_.name));
    }
    DeleteLocalRef(env_, info_.thread_group);
    DeleteLocalRef(env_, info_.context_class_loader);
  }

  const char* GetName() const {
    return info_.name;
  }

 private:
  jvmtiEnv* jvmtienv_;
  JNIEnv* env_;
  bool free_name_;
  jvmtiThreadInfo info_;
};

class ScopedClassInfo {
 public:
  ScopedClassInfo(jvmtiEnv* jvmtienv, jclass c)
      : jvmtienv_(jvmtienv),
        class_(c),
        name_(nullptr),
        file_(nullptr),
        debug_ext_(nullptr) {}

  ~ScopedClassInfo() {
    if (class_ != nullptr) {
      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(file_));
      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_));
    }
  }

  bool Init() {
    if (class_ == nullptr) {
      name_ = const_cast<char*>("<NONE>");
      return true;
    } else {
      jvmtiError ret1 = jvmtienv_->GetSourceFileName(class_, &file_);
      jvmtiError ret2 = jvmtienv_->GetSourceDebugExtension(class_, &debug_ext_);
      return jvmtienv_->GetClassSignature(class_, &name_, nullptr) == JVMTI_ERROR_NONE &&
          ret1 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY &&
          ret1 != JVMTI_ERROR_INVALID_CLASS &&
          ret2 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY &&
          ret2 != JVMTI_ERROR_INVALID_CLASS;
    }
  }

  jclass GetClass() const {
    return class_;
  }
  const char* GetName() const {
    return name_;
  }
  const char* GetSourceDebugExtension() const {
    if (debug_ext_ == nullptr) {
      return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>";
    } else {
      return debug_ext_;
    }
  }
  const char* GetSourceFileName() const {
    if (file_ == nullptr) {
      return "<UNKNOWN_FILE>";
    } else {
      return file_;
    }
  }

 private:
  jvmtiEnv* jvmtienv_;
  jclass class_;
  char* name_;
  char* file_;
  char* debug_ext_;
};

class ScopedMethodInfo {
 public:
  ScopedMethodInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m)
      : jvmtienv_(jvmtienv),
        env_(env),
        method_(m),
        declaring_class_(nullptr),
        class_info_(nullptr),
        name_(nullptr),
        signature_(nullptr),
        first_line_(-1) {}

  ~ScopedMethodInfo() {
    DeleteLocalRef(env_, declaring_class_);
    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(signature_));
  }

  bool Init() {
    if (jvmtienv_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) {
      return false;
    }
    class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_));
    jint nlines;
    jvmtiLineNumberEntry* lines;
    jvmtiError err = jvmtienv_->GetLineNumberTable(method_, &nlines, &lines);
    if (err == JVMTI_ERROR_NONE) {
      if (nlines > 0) {
        first_line_ = lines[0].line_number;
      }
      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(lines));
    } else if (err != JVMTI_ERROR_ABSENT_INFORMATION &&
               err != JVMTI_ERROR_NATIVE_METHOD) {
      return false;
    }
    return class_info_->Init() &&
        (jvmtienv_->GetMethodName(method_, &name_, &signature_, nullptr) == JVMTI_ERROR_NONE);
  }

  const ScopedClassInfo& GetDeclaringClassInfo() const {
    return *class_info_;
  }

  jclass GetDeclaringClass() const {
    return declaring_class_;
  }

  const char* GetName() const {
    return name_;
  }

  const char* GetSignature() const {
    return signature_;
  }

  jint GetFirstLine() const {
    return first_line_;
  }

 private:
  jvmtiEnv* jvmtienv_;
  JNIEnv* env_;
  jmethodID method_;
  jclass declaring_class_;
  std::unique_ptr<ScopedClassInfo> class_info_;
  char* name_;
  char* signature_;
  jint first_line_;

  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) {}

  ~ScopedFieldInfo() {
    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(type_));
  }

  bool Init() {
    class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_));
    return class_info_->Init() &&
        (jvmtienv_->GetFieldName(
            declaring_class_, field_, &name_, &type_, nullptr) == 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_;
  }

 private:
  jvmtiEnv* jvmtienv_;
  jclass declaring_class_;
  jfieldID field_;
  std::unique_ptr<ScopedClassInfo> class_info_;
  char* name_;
  char* type_;

  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;
}

std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m) {
  return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() << m.GetSignature()
            << " (source: " << m.GetDeclaringClassInfo().GetSourceFileName() << ":"
            << m.GetFirstLine() << ")";
}

static void doJvmtiMethodBind(jvmtiEnv* jvmtienv,
                              JNIEnv* env,
                              jthread thread,
                              jmethodID m,
                              void* address,
                              /*out*/void** out_address) {
  *out_address = address;
  ScopedThreadInfo thread_info(jvmtienv, env, thread);
  ScopedMethodInfo method_info(jvmtienv, env, m);
  if (!method_info.Init()) {
    LOG(ERROR) << "Unable to get method info!";
    return;
  }
  LOG(INFO) << "Loading native method \"" << method_info << "\". Thread is "
            << thread_info.GetName();
}

static std::string GetName(jvmtiEnv* jvmtienv, JNIEnv* jnienv, jobject obj) {
  jclass klass = jnienv->GetObjectClass(obj);
  char *cname, *cgen;
  if (jvmtienv->GetClassSignature(klass, &cname, &cgen) != JVMTI_ERROR_NONE) {
    LOG(ERROR) << "Unable to get class name!";
    DeleteLocalRef(jnienv, klass);
    return "<UNKNOWN>";
  }
  std::string name(cname);
  if (name == "Ljava/lang/String;") {
    jstring str = reinterpret_cast<jstring>(obj);
    const char* val = jnienv->GetStringUTFChars(str, nullptr);
    if (val == nullptr) {
      name += " (unable to get value)";
    } else {
      std::ostringstream oss;
      oss << name << " (value: \"" << val << "\")";
      name = oss.str();
      jnienv->ReleaseStringUTFChars(str, val);
    }
  }
  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cname));
  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(cgen));
  DeleteLocalRef(jnienv, klass);
  return name;
}

static std::string GetValOf(jvmtiEnv* env, JNIEnv* jnienv, std::string type, jvalue val) {
  std::ostringstream oss;
  switch (type[0]) {
    case '[':
    case 'L':
      return val.l != nullptr ? GetName(env, jnienv, val.l) : "null";
    case 'Z':
      return val.z == JNI_TRUE ? "true" : "false";
    case 'B':
      oss << val.b;
      return oss.str();
    case 'C':
      oss << val.c;
      return oss.str();
    case 'S':
      oss << val.s;
      return oss.str();
    case 'I':
      oss << val.i;
      return oss.str();
    case 'J':
      oss << val.j;
      return oss.str();
    case 'F':
      oss << val.f;
      return oss.str();
    case 'D':
      oss << val.d;
      return oss.str();
    case 'V':
      return "<void>";
    default:
      return "<ERROR Found type " + type + ">";
  }
}

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() << "\".";
  DeleteLocalRef(env, 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() << "\"";
        }
        DeleteLocalRef(env, 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() << "\".";
  DeleteLocalRef(env, oklass);
}
void JNICALL MethodExitHook(jvmtiEnv* jvmtienv,
                            JNIEnv* env,
                            jthread thread,
                            jmethodID m,
                            jboolean was_popped_by_exception,
                            jvalue val) {
  ScopedThreadInfo info(jvmtienv, env, thread);
  ScopedMethodInfo method_info(jvmtienv, env, m);
  if (!method_info.Init()) {
    LOG(ERROR) << "Unable to get method info!";
    return;
  }
  std::string type(method_info.GetSignature());
  type = type.substr(type.find(')') + 1);
  std::string out_val(was_popped_by_exception ? "" : GetValOf(jvmtienv, env, type, val));
  LOG(INFO) << "Leaving method \"" << method_info << "\". Thread is \"" << info.GetName() << "\"."
            << std::endl
            << "    Cause: " << (was_popped_by_exception ? "exception" : "return ")
            << out_val << ".";
}

void JNICALL MethodEntryHook(jvmtiEnv* jvmtienv,
                             JNIEnv* env,
                             jthread thread,
                             jmethodID m) {
  ScopedThreadInfo info(jvmtienv, env, thread);
  ScopedMethodInfo method_info(jvmtienv, env, m);
  if (!method_info.Init()) {
    LOG(ERROR) << "Unable to get method info!";
    return;
  }
  LOG(INFO) << "Entering method \"" << method_info << "\". Thread is \"" << info.GetName() << "\"";
}

void JNICALL ClassPrepareHook(jvmtiEnv* jvmtienv,
                              JNIEnv* env,
                              jthread thread,
                              jclass klass) {
  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));
  }
  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() << "\"";
  }
}

void JNICALL SingleStepHook(jvmtiEnv* jvmtienv,
                            JNIEnv* env,
                            jthread thread,
                            jmethodID method,
                            jlocation location) {
  ScopedThreadInfo info(jvmtienv, env, thread);
  ScopedMethodInfo method_info(jvmtienv, env, method);
  if (!method_info.Init()) {
    LOG(ERROR) << "Unable to get method info!";
    return;
  }
  LOG(INFO) << "Single step at location: 0x" << std::setw(8) << std::setfill('0') << std::hex
            << location << " in method " << method_info << " thread: " << info.GetName();
}

// The hook we are using.
void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti,
                                         JNIEnv* jni_env ATTRIBUTE_UNUSED,
                                         jclass class_being_redefined ATTRIBUTE_UNUSED,
                                         jobject loader ATTRIBUTE_UNUSED,
                                         const char* name,
                                         jobject protection_domain ATTRIBUTE_UNUSED,
                                         jint class_data_len,
                                         const unsigned char* class_data,
                                         jint* new_class_data_len,
                                         unsigned char** new_class_data) {
  std::vector<unsigned char> out;
  // Make the jvmti semi-descriptor into the full descriptor.
  std::string name_str("L");
  name_str += name;
  name_str += ";";
  StressData* data = nullptr;
  CHECK_EQ(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)),
           JVMTI_ERROR_NONE);
  if (!data->vm_class_loader_initialized) {
    LOG(WARNING) << "Ignoring load of class " << name << " because VMClassLoader is not yet "
                 << "initialized. Transforming this class could cause spurious test failures.";
    return;
  } else if (DoExtractClassFromData(jvmti, name_str, class_data_len, class_data,
                                    /*out*/ new_class_data_len, /*out*/ new_class_data)) {
    LOG(INFO) << "Extracted class: " << name;
  } else {
    std::cerr << "Unable to extract class " << name << std::endl;
    *new_class_data_len = 0;
    *new_class_data = nullptr;
  }
}

static std::string AdvanceOption(const std::string& ops) {
  return ops.substr(ops.find(',') + 1);
}

static bool HasNextOption(const std::string& ops) {
  return ops.find(',') != std::string::npos;
}

static std::string GetOption(const std::string& in) {
  return in.substr(0, in.find(','));
}

// Options are
// jvmti-stress,[redefine,][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";
  do {
    ops = AdvanceOption(ops);
    std::string cur = GetOption(ops);
    if (cur == "trace") {
      data->trace_stress = true;
    } else if (cur == "step") {
      data->step_stress = true;
    } else if (cur == "field") {
      data->field_stress = true;
    } else if (cur == "redefine") {
      data->redefine_stress = true;
    } else {
      LOG(FATAL) << "Unknown option: " << GetOption(ops);
    }
  } while (HasNextOption(ops));
}

// Do final setup during the VMInit callback. By this time most things are all setup.
static void JNICALL PerformFinalSetupVMInit(jvmtiEnv *jvmti_env,
                                            JNIEnv* jni_env,
                                            jthread thread ATTRIBUTE_UNUSED) {
  // Load the VMClassLoader class. We will get a ClassNotFound exception because we don't have
  // visibility but the class will be loaded behind the scenes.
  LOG(INFO) << "manual load & initialization of class java/lang/VMClassLoader!";
  jclass klass = jni_env->FindClass("java/lang/VMClassLoader");
  StressData* data = nullptr;
  CHECK_EQ(jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)),
           JVMTI_ERROR_NONE);
  // We need to make sure that VMClassLoader is initialized before we start redefining anything
  // since it can give (non-fatal) error messages if it's initialized after we've redefined BCP
  // classes. These error messages are expected and no problem but they will mess up our testing
  // infrastructure.
  if (klass == nullptr) {
    // Probably on RI. Clear the exception so we can continue but don't mark vmclassloader as
    // initialized.
    LOG(WARNING) << "Unable to find VMClassLoader class!";
    jni_env->ExceptionClear();
  } else {
    // GetMethodID is spec'd to cause the class to be initialized.
    jni_env->GetMethodID(klass, "hashCode", "()I");
    DeleteLocalRef(jni_env, klass);
    data->vm_class_loader_initialized = true;
  }
}

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));
    DeleteLocalRef(jni, k);
  }
  jvmti->Deallocate(reinterpret_cast<unsigned char*>(klasses));
  return true;
}

extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm,
                                               char* options,
                                               void* reserved ATTRIBUTE_UNUSED) {
  jvmtiEnv* jvmti = nullptr;
  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_0)) {
    LOG(ERROR) << "Unable to get jvmti env.";
    return 1;
  }
  StressData* data = nullptr;
  if (JVMTI_ERROR_NONE != jvmti->Allocate(sizeof(StressData),
                                          reinterpret_cast<unsigned char**>(&data))) {
    LOG(ERROR) << "Unable to allocate data for stress test.";
    return 1;
  }
  memset(data, 0, sizeof(StressData));
  // Read the options into the static variables that hold them.
  ReadOptions(data, options);
  // Save the data
  if (JVMTI_ERROR_NONE != jvmti->SetEnvironmentLocalStorage(data)) {
    LOG(ERROR) << "Unable to save stress test data.";
    return 1;
  }

  // Just get all capabilities.
  jvmtiCapabilities caps = {
    .can_tag_objects                                 = 0,
    .can_generate_field_modification_events          = 1,
    .can_generate_field_access_events                = 1,
    .can_get_bytecodes                               = 0,
    .can_get_synthetic_attribute                     = 0,
    .can_get_owned_monitor_info                      = 0,
    .can_get_current_contended_monitor               = 0,
    .can_get_monitor_info                            = 0,
    .can_pop_frame                                   = 0,
    .can_redefine_classes                            = 1,
    .can_signal_thread                               = 0,
    .can_get_source_file_name                        = 1,
    .can_get_line_numbers                            = 1,
    .can_get_source_debug_extension                  = 1,
    .can_access_local_variables                      = 0,
    .can_maintain_original_method_order              = 0,
    .can_generate_single_step_events                 = 1,
    .can_generate_exception_events                   = 0,
    .can_generate_frame_pop_events                   = 0,
    .can_generate_breakpoint_events                  = 0,
    .can_suspend                                     = 0,
    .can_redefine_any_class                          = 0,
    .can_get_current_thread_cpu_time                 = 0,
    .can_get_thread_cpu_time                         = 0,
    .can_generate_method_entry_events                = 1,
    .can_generate_method_exit_events                 = 1,
    .can_generate_all_class_hook_events              = 0,
    .can_generate_compiled_method_load_events        = 0,
    .can_generate_monitor_events                     = 0,
    .can_generate_vm_object_alloc_events             = 0,
    .can_generate_native_method_bind_events          = 1,
    .can_generate_garbage_collection_events          = 0,
    .can_generate_object_free_events                 = 0,
    .can_force_early_return                          = 0,
    .can_get_owned_monitor_stack_depth_info          = 0,
    .can_get_constant_pool                           = 0,
    .can_set_native_method_prefix                    = 0,
    .can_retransform_classes                         = 1,
    .can_retransform_any_class                       = 0,
    .can_generate_resource_exhaustion_heap_events    = 0,
    .can_generate_resource_exhaustion_threads_events = 0,
  };
  jvmti->AddCapabilities(&caps);

  // Set callbacks.
  jvmtiEventCallbacks cb;
  memset(&cb, 0, sizeof(cb));
  cb.ClassFileLoadHook = ClassFileLoadHookSecretNoOp;
  cb.NativeMethodBind = doJvmtiMethodBind;
  cb.VMInit = PerformFinalSetupVMInit;
  cb.MethodEntry = MethodEntryHook;
  cb.MethodExit = MethodExitHook;
  cb.FieldAccess = FieldAccessHook;
  cb.FieldModification = FieldModificationHook;
  cb.ClassPrepare = ClassPrepareHook;
  cb.SingleStep = SingleStepHook;
  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_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,
                                        nullptr) != JVMTI_ERROR_NONE) {
      LOG(ERROR) << "Unable to enable CLASS_PREPARE event!";
      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_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;
    }
  }
  if (data->step_stress) {
    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
                                        JVMTI_EVENT_SINGLE_STEP,
                                        nullptr) != JVMTI_ERROR_NONE) {
      return 1;
    }
  }
  return 0;
}

extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
  return Agent_OnLoad(vm, options, reserved);
}

}  // namespace art
