ART: Add class events

Add initial support for ClassLoad and ClassPrepare events. Add tests.

The initial implementation does not deal with the difference between
ClassLoad and ClassPrepare classes (the former may be temporary
classes).

Bug: 31684920
Test: m test-art-host-run-test-912-classes
Change-Id: I83535b9c871971f60b6b61f26651958fb32d502f
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index be10378..33550d1 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1310,6 +1310,7 @@
   }
   PhaseUtil::Register(&gEventHandler);
   ThreadUtil::Register(&gEventHandler);
+  ClassUtil::Register(&gEventHandler);
 
   runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler);
   runtime->AddSystemWeakHolder(&gObjectTagTable);
@@ -1320,6 +1321,7 @@
 extern "C" bool ArtPlugin_Deinitialize() {
   PhaseUtil::Unregister();
   ThreadUtil::Unregister();
+  ClassUtil::Unregister();
 
   return true;
 }
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index abcc849..450f75a 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -31,16 +31,119 @@
 
 #include "ti_class.h"
 
+#include <mutex>
+#include <unordered_set>
+
 #include "art_jvmti.h"
+#include "base/macros.h"
 #include "class_table-inl.h"
 #include "class_linker.h"
+#include "events-inl.h"
+#include "handle.h"
+#include "jni_env_ext-inl.h"
 #include "jni_internal.h"
 #include "runtime.h"
+#include "runtime_callbacks.h"
+#include "ScopedLocalRef.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
+#include "thread_list.h"
 
 namespace openjdkjvmti {
 
+struct ClassCallback : public art::ClassLoadCallback {
+  void ClassLoad(art::Handle<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassLoad)) {
+      art::Thread* thread = art::Thread::Current();
+      ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+                                    thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+      ScopedLocalRef<jclass> jthread(
+          thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(thread->GetPeer()));
+      {
+        art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+        event_handler->DispatchEvent(thread,
+                                     ArtJvmtiEvent::kClassLoad,
+                                     reinterpret_cast<JNIEnv*>(thread->GetJniEnv()),
+                                     jthread.get(),
+                                     jklass.get());
+      }
+      AddTempClass(thread, jklass.get());
+    }
+  }
+
+  void ClassPrepare(art::Handle<art::mirror::Class> temp_klass ATTRIBUTE_UNUSED,
+                    art::Handle<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassPrepare)) {
+      art::Thread* thread = art::Thread::Current();
+      ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+                                    thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+      ScopedLocalRef<jclass> jthread(
+          thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jclass>(thread->GetPeer()));
+      art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+      event_handler->DispatchEvent(thread,
+                                   ArtJvmtiEvent::kClassPrepare,
+                                   reinterpret_cast<JNIEnv*>(thread->GetJniEnv()),
+                                   jthread.get(),
+                                   jklass.get());
+    }
+  }
+
+  void AddTempClass(art::Thread* self, jclass klass) {
+    std::unique_lock<std::mutex> mu(temp_classes_lock);
+    temp_classes.push_back(reinterpret_cast<jclass>(self->GetJniEnv()->NewGlobalRef(klass)));
+  }
+
+  void HandleTempClass(art::Handle<art::mirror::Class> temp_klass,
+                       art::Handle<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    std::unique_lock<std::mutex> mu(temp_classes_lock);
+    if (temp_classes.empty()) {
+      return;
+    }
+
+    art::Thread* self = art::Thread::Current();
+    for (auto it = temp_classes.begin(); it != temp_classes.end(); ++it) {
+      if (temp_klass.Get() == art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(*it))) {
+        temp_classes.erase(it);
+        FixupTempClass(temp_klass, klass);
+      }
+    }
+  }
+
+  void FixupTempClass(art::Handle<art::mirror::Class> temp_klass ATTRIBUTE_UNUSED,
+                      art::Handle<art::mirror::Class> klass ATTRIBUTE_UNUSED)
+     REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    // TODO: Implement.
+  }
+
+  // A set of all the temp classes we have handed out. We have to fix up references to these.
+  // For simplicity, we store the temp classes as JNI global references in a vector. Normally a
+  // Prepare event will closely follow, so the vector should be small.
+  std::mutex temp_classes_lock;
+  std::vector<jclass> temp_classes;
+
+  EventHandler* event_handler = nullptr;
+};
+
+ClassCallback gClassCallback;
+
+void ClassUtil::Register(EventHandler* handler) {
+  gClassCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add load callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&gClassCallback);
+}
+
+void ClassUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove thread callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveClassLoadCallback(&gClassCallback);
+}
+
 jvmtiError ClassUtil::GetClassFields(jvmtiEnv* env,
                                      jclass jklass,
                                      jint* field_count_ptr,
@@ -200,7 +303,9 @@
   }
 
   // TODO: Support generic signature.
-  *generic_ptr = nullptr;
+  if (generic_ptr != nullptr) {
+    *generic_ptr = nullptr;
+  }
 
   // Everything is fine, release the buffers.
   sig_copy.release();
diff --git a/runtime/openjdkjvmti/ti_class.h b/runtime/openjdkjvmti/ti_class.h
index 9558894..aa2260f 100644
--- a/runtime/openjdkjvmti/ti_class.h
+++ b/runtime/openjdkjvmti/ti_class.h
@@ -37,8 +37,13 @@
 
 namespace openjdkjvmti {
 
+class EventHandler;
+
 class ClassUtil {
  public:
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+
   static jvmtiError GetClassFields(jvmtiEnv* env,
                                    jclass klass,
                                    jint* field_count_ptr,
diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc
index 62c3ebe..154406a 100644
--- a/runtime/openjdkjvmti/ti_phase.cc
+++ b/runtime/openjdkjvmti/ti_phase.cc
@@ -55,19 +55,23 @@
     return soa.AddLocalReference<jthread>(soa.Self()->GetPeer());
   }
 
-  void NextRuntimePhase(RuntimePhase phase) OVERRIDE {
+  void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
     // TODO: Events.
     switch (phase) {
       case RuntimePhase::kInitialAgents:
         PhaseUtil::current_phase_ = JVMTI_PHASE_PRIMORDIAL;
         break;
       case RuntimePhase::kStart:
-        event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmStart, GetJniEnv());
-        PhaseUtil::current_phase_ = JVMTI_PHASE_START;
+        {
+          art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+          event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmStart, GetJniEnv());
+          PhaseUtil::current_phase_ = JVMTI_PHASE_START;
+        }
         break;
       case RuntimePhase::kInit:
         {
           ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread());
+          art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
           event_handler->DispatchEvent(nullptr,
                                        ArtJvmtiEvent::kVmInit,
                                        GetJniEnv(),
@@ -76,8 +80,11 @@
         }
         break;
       case RuntimePhase::kDeath:
-        event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmDeath, GetJniEnv());
-        PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD;
+        {
+          art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+          event_handler->DispatchEvent(nullptr, ArtJvmtiEvent::kVmDeath, GetJniEnv());
+          PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD;
+        }
         // TODO: Block events now.
         break;
     }
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index bf79570..9f81d6b 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -64,6 +64,7 @@
   void Post(art::Thread* self, ArtJvmtiEvent type) REQUIRES_SHARED(art::Locks::mutator_lock_) {
     DCHECK_EQ(self, art::Thread::Current());
     ScopedLocalRef<jthread> thread(self->GetJniEnv(), GetThreadObject(self));
+    art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
     event_handler->DispatchEvent(self, type, self->GetJniEnv(), thread.get());
   }
 
diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc
index 29eeff6..d13436e 100644
--- a/test/912-classes/classes.cc
+++ b/test/912-classes/classes.cc
@@ -20,6 +20,7 @@
 #include "jni.h"
 #include "openjdkjvmti/jvmti.h"
 #include "ScopedLocalRef.h"
+#include "thread-inl.h"
 
 #include "ti-agent/common_helper.h"
 #include "ti-agent/common_load.h"
@@ -259,5 +260,120 @@
   return int_array;
 }
 
+static std::string GetClassName(jvmtiEnv* jenv, JNIEnv* jni_env, jclass klass) {
+  char* name;
+  jvmtiError result = jenv->GetClassSignature(klass, &name, nullptr);
+  if (result != JVMTI_ERROR_NONE) {
+    if (jni_env != nullptr) {
+      JvmtiErrorToException(jni_env, result);
+    } else {
+      printf("Failed to get class signature.\n");
+    }
+    return "";
+  }
+
+  std::string tmp(name);
+  jenv->Deallocate(reinterpret_cast<unsigned char*>(name));
+
+  return tmp;
+}
+
+static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) {
+  jvmtiThreadInfo info;
+  jvmtiError result = jenv->GetThreadInfo(thread, &info);
+  if (result != JVMTI_ERROR_NONE) {
+    if (jni_env != nullptr) {
+      JvmtiErrorToException(jni_env, result);
+    } else {
+      printf("Failed to get thread name.\n");
+    }
+    return "";
+  }
+
+  std::string tmp(info.name);
+  jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name));
+  jni_env->DeleteLocalRef(info.context_class_loader);
+  jni_env->DeleteLocalRef(info.thread_group);
+
+  return tmp;
+}
+
+static std::string GetThreadName(Thread* thread) {
+  std::string tmp;
+  thread->GetThreadName(tmp);
+  return tmp;
+}
+
+static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv,
+                                         JNIEnv* jni_env,
+                                         jthread thread,
+                                         jclass klass) {
+  std::string name = GetClassName(jenv, jni_env, klass);
+  if (name == "") {
+    return;
+  }
+  std::string thread_name = GetThreadName(jenv, jni_env, thread);
+  if (thread_name == "") {
+    return;
+  }
+  std::string cur_thread_name = GetThreadName(Thread::Current());
+  printf("Prepare: %s on %s (cur=%s)\n",
+         name.c_str(),
+         thread_name.c_str(),
+         cur_thread_name.c_str());
+}
+
+static void JNICALL ClassLoadCallback(jvmtiEnv* jenv,
+                                      JNIEnv* jni_env,
+                                      jthread thread,
+                                      jclass klass) {
+  std::string name = GetClassName(jenv, jni_env, klass);
+  if (name == "") {
+    return;
+  }
+  std::string thread_name = GetThreadName(jenv, jni_env, thread);
+  if (thread_name == "") {
+    return;
+  }
+  printf("Load: %s on %s\n", name.c_str(), thread_name.c_str());
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
+  if (b == JNI_FALSE) {
+    jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                         JVMTI_EVENT_CLASS_LOAD,
+                                                         nullptr);
+    if (JvmtiErrorToException(env, ret)) {
+      return;
+    }
+    ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                              JVMTI_EVENT_CLASS_PREPARE,
+                                              nullptr);
+    JvmtiErrorToException(env, ret);
+    return;
+  }
+
+  jvmtiEventCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+  callbacks.ClassLoad = ClassLoadCallback;
+  callbacks.ClassPrepare = ClassPrepareCallback;
+  jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+  if (JvmtiErrorToException(env, ret)) {
+    return;
+  }
+
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_CLASS_LOAD,
+                                            nullptr);
+  if (JvmtiErrorToException(env, ret)) {
+    return;
+  }
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_CLASS_PREPARE,
+                                            nullptr);
+  JvmtiErrorToException(env, ret);
+}
+
 }  // namespace Test912Classes
 }  // namespace art
diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt
index f3cb261..4976553 100644
--- a/test/912-classes/expected.txt
+++ b/test/912-classes/expected.txt
@@ -61,3 +61,35 @@
 [class A, class B, class java.lang.Object]
 
 [37, 0]
+
+B, false
+Load: LB; on main
+Prepare: LB; on main (cur=main)
+B, true
+Load: LB; on main
+Prepare: LB; on main (cur=main)
+C, false
+Load: LA; on main
+Prepare: LA; on main (cur=main)
+Load: LC; on main
+Prepare: LC; on main (cur=main)
+A, false
+C, true
+Load: LA; on main
+Prepare: LA; on main (cur=main)
+Load: LC; on main
+Prepare: LC; on main (cur=main)
+A, true
+A, true
+Load: LA; on main
+Prepare: LA; on main (cur=main)
+C, true
+Load: LC; on main
+Prepare: LC; on main (cur=main)
+Load: LMain$1; on main
+Prepare: LMain$1; on main (cur=main)
+C, true
+Load: LA; on TestRunner
+Prepare: LA; on TestRunner (cur=TestRunner)
+Load: LC; on TestRunner
+Prepare: LC; on TestRunner (cur=TestRunner)
diff --git a/test/912-classes/src-ex/A.java b/test/912-classes/src-ex/A.java
index 64acb2f..2c43cfb 100644
--- a/test/912-classes/src-ex/A.java
+++ b/test/912-classes/src-ex/A.java
@@ -15,4 +15,4 @@
  */
 
 public class A {
-}
\ No newline at end of file
+}
diff --git a/test/912-classes/src-ex/C.java b/test/912-classes/src-ex/C.java
new file mode 100644
index 0000000..97f8021
--- /dev/null
+++ b/test/912-classes/src-ex/C.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 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.
+ */
+
+public class C extends A {
+}
diff --git a/test/912-classes/src/B.java b/test/912-classes/src/B.java
index f1458c3..52ce4dd 100644
--- a/test/912-classes/src/B.java
+++ b/test/912-classes/src/B.java
@@ -15,4 +15,4 @@
  */
 
 public class B {
-}
\ No newline at end of file
+}
diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java
index 8c78d71..859f80c 100644
--- a/test/912-classes/src/Main.java
+++ b/test/912-classes/src/Main.java
@@ -82,6 +82,10 @@
     System.out.println();
 
     testClassVersion();
+
+    System.out.println();
+
+    testClassEvents();
   }
 
   private static Class<?> proxyClass = null;
@@ -208,6 +212,60 @@
     System.out.println(Arrays.toString(getClassVersion(Main.class)));
   }
 
+  private static void testClassEvents() throws Exception {
+    ClassLoader cl = Main.class.getClassLoader();
+    while (cl.getParent() != null) {
+      cl = cl.getParent();
+    }
+    final ClassLoader boot = cl;
+
+    enableClassLoadEvents(true);
+
+    ClassLoader cl1 = create(boot, DEX1, DEX2);
+    System.out.println("B, false");
+    Class.forName("B", false, cl1);
+
+    ClassLoader cl2 = create(boot, DEX1, DEX2);
+    System.out.println("B, true");
+    Class.forName("B", true, cl2);
+
+    ClassLoader cl3 = create(boot, DEX1, DEX2);
+    System.out.println("C, false");
+    Class.forName("C", false, cl3);
+    System.out.println("A, false");
+    Class.forName("A", false, cl3);
+
+    ClassLoader cl4 = create(boot, DEX1, DEX2);
+    System.out.println("C, true");
+    Class.forName("C", true, cl4);
+    System.out.println("A, true");
+    Class.forName("A", true, cl4);
+
+    ClassLoader cl5 = create(boot, DEX1, DEX2);
+    System.out.println("A, true");
+    Class.forName("A", true, cl5);
+    System.out.println("C, true");
+    Class.forName("C", true, cl5);
+
+    Runnable r = new Runnable() {
+      @Override
+      public void run() {
+        try {
+          ClassLoader cl6 = create(boot, DEX1, DEX2);
+          System.out.println("C, true");
+          Class.forName("C", true, cl6);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
+    Thread t = new Thread(r, "TestRunner");
+    t.start();
+    t.join();
+
+    enableClassLoadEvents(false);
+  }
+
   private static void printClassLoaderClasses(ClassLoader cl) {
     for (;;) {
       if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) {
@@ -270,6 +328,8 @@
 
   private static native int[] getClassVersion(Class<?> c);
 
+  private static native void enableClassLoadEvents(boolean b);
+
   private static class TestForNonInit {
     public static double dummy = Math.random();  // So it can't be compile-time initialized.
   }