ART: Add GetPhase

Extend RuntimePhaseCallback. Add support for GetPhase.
Add tests.

Bug: 31455788
Test: m test-art-host-run-test-901-hello-ti-agent
Change-Id: I33b1934a0789bc713675f21c2c9f676c9d3a1ed1
diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp
index d5c6520..acdd0d3 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -27,6 +27,7 @@
            "ti_method.cc",
            "ti_monitor.cc",
            "ti_object.cc",
+           "ti_phase.cc",
            "ti_properties.cc",
            "ti_search.cc",
            "ti_stack.cc",
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index e9b7cf5..53f8747 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -55,6 +55,7 @@
 #include "ti_method.h"
 #include "ti_monitor.h"
 #include "ti_object.h"
+#include "ti_phase.h"
 #include "ti_properties.h"
 #include "ti_redefine.h"
 #include "ti_search.h"
@@ -1099,7 +1100,7 @@
   }
 
   static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return PhaseUtil::GetPhase(env, phase_ptr);
   }
 
   static jvmtiError DisposeEnvironment(jvmtiEnv* env) {
@@ -1300,8 +1301,17 @@
 // The plugin initialization function. This adds the jvmti environment.
 extern "C" bool ArtPlugin_Initialize() {
   art::Runtime* runtime = art::Runtime::Current();
+
+  if (runtime->IsStarted()) {
+    PhaseUtil::SetToLive();
+  } else {
+    PhaseUtil::SetToOnLoad();
+  }
+  PhaseUtil::Register();
+
   runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler);
   runtime->AddSystemWeakHolder(&gObjectTagTable);
+
   return true;
 }
 
diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc
new file mode 100644
index 0000000..e36b947
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_phase.cc
@@ -0,0 +1,105 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_phase.h"
+
+#include "art_jvmti.h"
+#include "base/macros.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+
+namespace openjdkjvmti {
+
+jvmtiPhase PhaseUtil::current_phase_ = static_cast<jvmtiPhase>(0);
+
+struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
+  void NextRuntimePhase(RuntimePhase phase) OVERRIDE {
+    // TODO: Events.
+    switch (phase) {
+      case RuntimePhase::kInitialAgents:
+        PhaseUtil::current_phase_ = JVMTI_PHASE_PRIMORDIAL;
+        break;
+      case RuntimePhase::kStart:
+        PhaseUtil::current_phase_ = JVMTI_PHASE_START;
+        break;
+      case RuntimePhase::kInit:
+        PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
+        break;
+      case RuntimePhase::kDeath:
+        PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD;
+        break;
+    }
+  }
+};
+
+PhaseUtil::PhaseCallback gPhaseCallback;
+
+jvmtiError PhaseUtil::GetPhase(jvmtiEnv* env ATTRIBUTE_UNUSED, jvmtiPhase* phase_ptr) {
+  if (phase_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  jvmtiPhase now = PhaseUtil::current_phase_;
+  DCHECK(now == JVMTI_PHASE_ONLOAD ||
+         now == JVMTI_PHASE_PRIMORDIAL ||
+         now == JVMTI_PHASE_START ||
+         now == JVMTI_PHASE_LIVE ||
+         now == JVMTI_PHASE_DEAD);
+  *phase_ptr = now;
+  return ERR(NONE);
+}
+
+void PhaseUtil::SetToOnLoad() {
+  DCHECK_EQ(0u, static_cast<size_t>(PhaseUtil::current_phase_));
+  PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD;
+}
+
+void PhaseUtil::SetToPrimordial() {
+  DCHECK_EQ(static_cast<size_t>(JVMTI_PHASE_ONLOAD), static_cast<size_t>(PhaseUtil::current_phase_));
+  PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD;
+}
+
+void PhaseUtil::SetToLive() {
+  DCHECK_EQ(static_cast<size_t>(0), static_cast<size_t>(PhaseUtil::current_phase_));
+  PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
+}
+
+void PhaseUtil::Register() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add phase callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gPhaseCallback);
+}
+
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_phase.h b/runtime/openjdkjvmti/ti_phase.h
new file mode 100644
index 0000000..3cbda5f
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_phase.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class PhaseUtil {
+ public:
+  static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr);
+
+  static void Register();
+
+  // Move the phase from unitialized to LOAD.
+  static void SetToOnLoad();
+
+  // Move the phase from LOAD to PRIMORDIAL.
+  static void SetToPrimordial();
+
+  // Move the phase from unitialized to LIVE.
+  static void SetToLive();
+
+  struct PhaseCallback;
+
+ private:
+  static jvmtiPhase current_phase_;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 8b355c8..4936a2f 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1383,6 +1383,10 @@
       LOG(ERROR) << "Unable to load an agent: " << err;
     }
   }
+  {
+    ScopedObjectAccess soa(self);
+    callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInitialAgents);
+  }
 
   VLOG(startup) << "Runtime::Init exiting";
 
@@ -1395,7 +1399,7 @@
   constexpr const char* plugin_name = kIsDebugBuild ? "libopenjdkjvmtid.so" : "libopenjdkjvmti.so";
 
   // Is the plugin already loaded?
-  for (Plugin p : *plugins) {
+  for (const Plugin& p : *plugins) {
     if (p.GetLibrary() == plugin_name) {
       return true;
     }
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index 6344c69..e580e78 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -58,9 +58,10 @@
 class RuntimePhaseCallback {
  public:
   enum RuntimePhase {
-    kStart,  // The runtime is started.
-    kInit,   // The runtime is initialized (and will run user code soon).
-    kDeath,  // The runtime just died.
+    kInitialAgents,   // Initial agent loading is done.
+    kStart,           // The runtime is started.
+    kInit,            // The runtime is initialized (and will run user code soon).
+    kDeath,           // The runtime just died.
   };
 
   virtual ~RuntimePhaseCallback() {}
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index c379b5c..8974b59 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -351,8 +351,13 @@
 
   struct Callback : public RuntimePhaseCallback {
     void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase p) OVERRIDE {
-      if (p == RuntimePhaseCallback::RuntimePhase::kStart) {
-        if (init_seen > 0) {
+      if (p == RuntimePhaseCallback::RuntimePhase::kInitialAgents) {
+        if (start_seen > 0 || init_seen > 0 || death_seen > 0) {
+          LOG(FATAL) << "Unexpected order";
+        }
+        ++initial_agents_seen;
+      } else if (p == RuntimePhaseCallback::RuntimePhase::kStart) {
+        if (init_seen > 0 || death_seen > 0) {
           LOG(FATAL) << "Init seen before start.";
         }
         ++start_seen;
@@ -365,6 +370,7 @@
       }
     }
 
+    size_t initial_agents_seen = 0;
     size_t start_seen = 0;
     size_t init_seen = 0;
     size_t death_seen = 0;
@@ -374,6 +380,7 @@
 };
 
 TEST_F(RuntimePhaseCallbackRuntimeCallbacksTest, Phases) {
+  ASSERT_EQ(0u, cb_.initial_agents_seen);
   ASSERT_EQ(0u, cb_.start_seen);
   ASSERT_EQ(0u, cb_.init_seen);
   ASSERT_EQ(0u, cb_.death_seen);
@@ -386,6 +393,7 @@
     ASSERT_TRUE(started);
   }
 
+  ASSERT_EQ(0u, cb_.initial_agents_seen);
   ASSERT_EQ(1u, cb_.start_seen);
   ASSERT_EQ(1u, cb_.init_seen);
   ASSERT_EQ(0u, cb_.death_seen);
@@ -393,6 +401,7 @@
   // Delete the runtime.
   runtime_.reset();
 
+  ASSERT_EQ(0u, cb_.initial_agents_seen);
   ASSERT_EQ(1u, cb_.start_seen);
   ASSERT_EQ(1u, cb_.init_seen);
   ASSERT_EQ(1u, cb_.death_seen);
diff --git a/test/901-hello-ti-agent/basics.cc b/test/901-hello-ti-agent/basics.cc
index 052fb9a..3a7d1a1 100644
--- a/test/901-hello-ti-agent/basics.cc
+++ b/test/901-hello-ti-agent/basics.cc
@@ -82,6 +82,17 @@
   }
   SetAllCapabilities(jvmti_env);
 
+  jvmtiPhase current_phase;
+  jvmtiError phase_result = jvmti_env->GetPhase(&current_phase);
+  if (phase_result != JVMTI_ERROR_NONE) {
+    printf("Could not get phase");
+    return 1;
+  }
+  if (current_phase != JVMTI_PHASE_ONLOAD) {
+    printf("Wrong phase");
+    return 1;
+  }
+
   return JNI_OK;
 }
 
@@ -92,5 +103,15 @@
   JvmtiErrorToException(env, result);
 }
 
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_checkLivePhase(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) {
+  jvmtiPhase current_phase;
+  jvmtiError phase_result = jvmti_env->GetPhase(&current_phase);
+  if (JvmtiErrorToException(env, phase_result)) {
+    return JNI_FALSE;
+  }
+  return (current_phase == JVMTI_PHASE_LIVE) ? JNI_TRUE : JNI_FALSE;
+}
+
 }  // namespace Test901HelloTi
 }  // namespace art
diff --git a/test/901-hello-ti-agent/expected.txt b/test/901-hello-ti-agent/expected.txt
index 2aee99b..11c51d0 100644
--- a/test/901-hello-ti-agent/expected.txt
+++ b/test/901-hello-ti-agent/expected.txt
@@ -1,5 +1,6 @@
 Loaded Agent for test 901-hello-ti-agent
 Hello, world!
+Agent in live phase.
 0
 1
 2
diff --git a/test/901-hello-ti-agent/src/Main.java b/test/901-hello-ti-agent/src/Main.java
index 775e5c2..faf2dc2 100644
--- a/test/901-hello-ti-agent/src/Main.java
+++ b/test/901-hello-ti-agent/src/Main.java
@@ -20,6 +20,10 @@
 
     System.out.println("Hello, world!");
 
+    if (checkLivePhase()) {
+      System.out.println("Agent in live phase.");
+    }
+
     set(0);  // OTHER
     set(1);  // GC
     set(2);  // CLASS
@@ -37,5 +41,6 @@
     }
   }
 
+  private static native boolean checkLivePhase();
   private static native void setVerboseFlag(int flag, boolean value);
 }