ART: Add GetFrameCount and GetFrameLocation

Add support for GetFrameCount and GetFrameLocation. Add tests.

Bug: 31684812
Test: m test-art-host-run-test-911-get-stack-trace
Change-Id: I7656e243f614eb0ceb5fcd6841128119fad89968
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 4334ca3..0390384 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -260,7 +260,7 @@
   }
 
   static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return StackUtil::GetFrameCount(env, thread, count_ptr);
   }
 
   static jvmtiError PopFrame(jvmtiEnv* env, jthread thread) {
@@ -272,7 +272,7 @@
                                      jint depth,
                                      jmethodID* method_ptr,
                                      jlocation* location_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return StackUtil::GetFrameLocation(env, thread, depth, method_ptr, location_ptr);
   }
 
   static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) {
diff --git a/runtime/openjdkjvmti/ti_stack.cc b/runtime/openjdkjvmti/ti_stack.cc
index 8bd8d09..4cf55a6 100644
--- a/runtime/openjdkjvmti/ti_stack.cc
+++ b/runtime/openjdkjvmti/ti_stack.cc
@@ -161,24 +161,43 @@
   return ERR(NONE);
 }
 
+static jvmtiError GetThread(JNIEnv* env, jthread java_thread, art::Thread** thread) {
+  if (java_thread == nullptr) {
+    *thread = art::Thread::Current();
+    if (*thread == nullptr) {
+      // GetStackTrace can only be run during the live phase, so the current thread should be
+      // attached and thus available. Getting a null for current means we're starting up or
+      // dying.
+      return ERR(WRONG_PHASE);
+    }
+  } else {
+    if (!env->IsInstanceOf(java_thread, art::WellKnownClasses::java_lang_Thread)) {
+      return ERR(INVALID_THREAD);
+    }
+
+    // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD.
+    art::ScopedObjectAccess soa(art::Thread::Current());
+    art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+    *thread = art::Thread::FromManagedThread(soa, java_thread);
+    if (*thread == nullptr) {
+      return ERR(THREAD_NOT_ALIVE);
+    }
+  }
+  return ERR(NONE);
+}
+
 jvmtiError StackUtil::GetStackTrace(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED,
                                     jthread java_thread,
                                     jint start_depth,
                                     jint max_frame_count,
                                     jvmtiFrameInfo* frame_buffer,
                                     jint* count_ptr) {
-  if (java_thread == nullptr) {
-    return ERR(INVALID_THREAD);
-  }
-
   art::Thread* thread;
-  {
-    // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD.
-    art::ScopedObjectAccess soa(art::Thread::Current());
-    art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
-    thread = art::Thread::FromManagedThread(soa, java_thread);
-    DCHECK(thread != nullptr);
+  jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), java_thread, &thread);
+  if (thread_error != ERR(NONE)) {
+    return thread_error;
   }
+  DCHECK(thread != nullptr);
 
   art::ThreadState state = thread->GetState();
   if (state == art::ThreadState::kStarting ||
@@ -563,4 +582,144 @@
   return ERR(NONE);
 }
 
+// Walks up the stack counting Java frames. This is not StackVisitor::ComputeNumFrames, as
+// runtime methods and transitions must not be counted.
+struct GetFrameCountVisitor : public art::StackVisitor {
+  explicit GetFrameCountVisitor(art::Thread* thread)
+      : art::StackVisitor(thread, nullptr, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        count(0) {}
+
+  bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::ArtMethod* m = GetMethod();
+    const bool do_count = !(m == nullptr || m->IsRuntimeMethod());
+    if (do_count) {
+      count++;
+    }
+    return true;
+  }
+
+  size_t count;
+};
+
+struct GetFrameCountClosure : public art::Closure {
+ public:
+  GetFrameCountClosure() : count(0) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    GetFrameCountVisitor visitor(self);
+    visitor.WalkStack(false);
+
+    count = visitor.count;
+  }
+
+  size_t count;
+};
+
+jvmtiError StackUtil::GetFrameCount(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                    jthread java_thread,
+                                    jint* count_ptr) {
+  art::Thread* thread;
+  jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), java_thread, &thread);
+  if (thread_error != ERR(NONE)) {
+    return thread_error;
+  }
+  DCHECK(thread != nullptr);
+
+  if (count_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  GetFrameCountClosure closure;
+  thread->RequestSynchronousCheckpoint(&closure);
+
+  *count_ptr = closure.count;
+  return ERR(NONE);
+}
+
+// Walks up the stack 'n' callers, when used with Thread::WalkStack.
+struct GetLocationVisitor : public art::StackVisitor {
+  GetLocationVisitor(art::Thread* thread, size_t n_in)
+      : art::StackVisitor(thread, nullptr, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        n(n_in),
+        count(0),
+        caller(nullptr),
+        caller_dex_pc(0) {}
+
+  bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::ArtMethod* m = GetMethod();
+    const bool do_count = !(m == nullptr || m->IsRuntimeMethod());
+    if (do_count) {
+      DCHECK(caller == nullptr);
+      if (count == n) {
+        caller = m;
+        caller_dex_pc = GetDexPc(false);
+        return false;
+      }
+      count++;
+    }
+    return true;
+  }
+
+  const size_t n;
+  size_t count;
+  art::ArtMethod* caller;
+  uint32_t caller_dex_pc;
+};
+
+struct GetLocationClosure : public art::Closure {
+ public:
+  explicit GetLocationClosure(size_t n_in) : n(n_in), method(nullptr), dex_pc(0) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    GetLocationVisitor visitor(self, n);
+    visitor.WalkStack(false);
+
+    method = visitor.caller;
+    dex_pc = visitor.caller_dex_pc;
+  }
+
+  const size_t n;
+  art::ArtMethod* method;
+  uint32_t dex_pc;
+};
+
+jvmtiError StackUtil::GetFrameLocation(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                       jthread java_thread,
+                                       jint depth,
+                                       jmethodID* method_ptr,
+                                       jlocation* location_ptr) {
+  art::Thread* thread;
+  jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(), java_thread, &thread);
+  if (thread_error != ERR(NONE)) {
+    return thread_error;
+  }
+  DCHECK(thread != nullptr);
+
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (method_ptr == nullptr || location_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  GetLocationClosure closure(static_cast<size_t>(depth));
+  thread->RequestSynchronousCheckpoint(&closure);
+
+  if (closure.method == nullptr) {
+    return ERR(NO_MORE_FRAMES);
+  }
+
+  *method_ptr = art::jni::EncodeArtMethod(closure.method);
+  if (closure.method->IsNative()) {
+    *location_ptr = -1;
+  } else {
+    if (closure.dex_pc == art::DexFile::kDexNoIndex) {
+      return ERR(INTERNAL);
+    }
+    *location_ptr = static_cast<jlocation>(closure.dex_pc);
+  }
+
+  return ERR(NONE);
+}
+
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_stack.h b/runtime/openjdkjvmti/ti_stack.h
index a5b391c..6a593cf 100644
--- a/runtime/openjdkjvmti/ti_stack.h
+++ b/runtime/openjdkjvmti/ti_stack.h
@@ -47,6 +47,14 @@
                                       jint* thread_count_ptr)
       REQUIRES(!art::Locks::thread_list_lock_);
 
+  static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr);
+
+  static jvmtiError GetFrameLocation(jvmtiEnv* env,
+                                     jthread thread,
+                                     jint depth,
+                                     jmethodID* method_ptr,
+                                     jlocation* location_ptr);
+
   static jvmtiError GetStackTrace(jvmtiEnv* env,
                                   jthread thread,
                                   jint start_depth,
diff --git a/test/911-get-stack-trace/expected.txt b/test/911-get-stack-trace/expected.txt
index 061101c..dad08c9 100644
--- a/test/911-get-stack-trace/expected.txt
+++ b/test/911-get-stack-trace/expected.txt
@@ -773,4 +773,64 @@
  doTest ()V 101 54
  main ([Ljava/lang/String;)V 38 37
 
+
+###################
+### Same thread ###
+###################
+4
+JVMTI_ERROR_ILLEGAL_ARGUMENT
+[public static native java.lang.Object[] Frames.getFrameLocation(java.lang.Thread,int), ffffffff]
+[public static void Frames.doTestSameThread(), 38]
+[public static void Frames.doTest() throws java.lang.Exception, 0]
+[public static void Main.main(java.lang.String[]) throws java.lang.Exception, 2e]
+JVMTI_ERROR_NO_MORE_FRAMES
+
+################################
+### Other thread (suspended) ###
+################################
+18
+JVMTI_ERROR_ILLEGAL_ARGUMENT
+[public final native void java.lang.Object.wait() throws java.lang.InterruptedException, ffffffff]
+[private static void Recurse.printOrWait(int,int,ControlData), 18]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 2]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[public void Frames$1.run(), 4]
+JVMTI_ERROR_NO_MORE_FRAMES
+
+###########################
+### Other thread (live) ###
+###########################
+17
+JVMTI_ERROR_ILLEGAL_ARGUMENT
+[private static void Recurse.printOrWait(int,int,ControlData), 2c]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 2]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[private static java.lang.Object Recurse.baz(int,int,int,ControlData), 9]
+[private static long Recurse.bar(int,int,int,ControlData), 0]
+[public static int Recurse.foo(int,int,int,ControlData), 0]
+[public void Frames$2.run(), 4]
+JVMTI_ERROR_NO_MORE_FRAMES
 Done
diff --git a/test/911-get-stack-trace/src/Frames.java b/test/911-get-stack-trace/src/Frames.java
new file mode 100644
index 0000000..a1a11c3
--- /dev/null
+++ b/test/911-get-stack-trace/src/Frames.java
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+import java.util.Arrays;
+
+public class Frames {
+  public static void doTest() throws Exception {
+    doTestSameThread();
+
+    System.out.println();
+
+    doTestOtherThreadWait();
+
+    System.out.println();
+
+    doTestOtherThreadBusyLoop();
+  }
+
+  public static void doTestSameThread() {
+    System.out.println("###################");
+    System.out.println("### Same thread ###");
+    System.out.println("###################");
+
+    Thread t = Thread.currentThread();
+
+    int count = getFrameCount(t);
+    System.out.println(count);
+    try {
+      System.out.println(Arrays.toString(getFrameLocation(t, -1)));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
+    for (int i = 0; i < count; i++) {
+      System.out.println(Arrays.toString(getFrameLocation(t, i)));
+    }
+    try {
+      System.out.println(Arrays.toString(getFrameLocation(t, count)));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
+  }
+
+  public static void doTestOtherThreadWait() throws Exception {
+    System.out.println("################################");
+    System.out.println("### Other thread (suspended) ###");
+    System.out.println("################################");
+    final ControlData data = new ControlData();
+    data.waitFor = new Object();
+    Thread t = new Thread() {
+      public void run() {
+        Recurse.foo(4, 0, 0, data);
+      }
+    };
+    t.start();
+    data.reached.await();
+    Thread.yield();
+    Thread.sleep(500);  // A little bit of time...
+
+    int count = getFrameCount(t);
+    System.out.println(count);
+    try {
+      System.out.println(Arrays.toString(getFrameLocation(t, -1)));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
+    for (int i = 0; i < count; i++) {
+      System.out.println(Arrays.toString(getFrameLocation(t, i)));
+    }
+    try {
+      System.out.println(Arrays.toString(getFrameLocation(t, count)));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
+
+    // Let the thread make progress and die.
+    synchronized(data.waitFor) {
+      data.waitFor.notifyAll();
+    }
+    t.join();
+  }
+
+  public static void doTestOtherThreadBusyLoop() throws Exception {
+    System.out.println("###########################");
+    System.out.println("### Other thread (live) ###");
+    System.out.println("###########################");
+    final ControlData data = new ControlData();
+    Thread t = new Thread() {
+      public void run() {
+        Recurse.foo(4, 0, 0, data);
+      }
+    };
+    t.start();
+    data.reached.await();
+    Thread.yield();
+    Thread.sleep(500);  // A little bit of time...
+
+    int count = getFrameCount(t);
+    System.out.println(count);
+    try {
+      System.out.println(Arrays.toString(getFrameLocation(t, -1)));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
+    for (int i = 0; i < count; i++) {
+      System.out.println(Arrays.toString(getFrameLocation(t, i)));
+    }
+    try {
+      System.out.println(Arrays.toString(getFrameLocation(t, count)));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
+
+    // Let the thread stop looping and die.
+    data.stop = true;
+    t.join();
+  }
+
+  public static native int getFrameCount(Thread thread);
+  public static native Object[] getFrameLocation(Thread thread, int depth);
+}
diff --git a/test/911-get-stack-trace/src/Main.java b/test/911-get-stack-trace/src/Main.java
index 2df5c53..b199033 100644
--- a/test/911-get-stack-trace/src/Main.java
+++ b/test/911-get-stack-trace/src/Main.java
@@ -36,6 +36,10 @@
 
     ThreadListTraces.doTest();
 
+    System.out.println();
+
+    Frames.doTest();
+
     System.out.println("Done");
   }
 }
diff --git a/test/911-get-stack-trace/stack_trace.cc b/test/911-get-stack-trace/stack_trace.cc
index f853387..d162e8a 100644
--- a/test/911-get-stack-trace/stack_trace.cc
+++ b/test/911-get-stack-trace/stack_trace.cc
@@ -20,6 +20,7 @@
 
 #include "android-base/stringprintf.h"
 
+#include "android-base/stringprintf.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "jni.h"
@@ -202,5 +203,55 @@
   return ret;
 }
 
+extern "C" JNIEXPORT jint JNICALL Java_Frames_getFrameCount(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thread) {
+  jint count;
+  jvmtiError result = jvmti_env->GetFrameCount(thread, &count);
+  if (JvmtiErrorToException(env, result)) {
+    return -1;
+  }
+  return count;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Frames_getFrameLocation(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thread, jint depth) {
+  jmethodID method;
+  jlocation location;
+
+  jvmtiError result = jvmti_env->GetFrameLocation(thread, depth, &method, &location);
+  if (JvmtiErrorToException(env, result)) {
+    return nullptr;
+  }
+
+  auto callback = [&](jint index) -> jobject {
+    switch (index) {
+      case 0:
+      {
+        jclass decl_class;
+        jvmtiError class_result = jvmti_env->GetMethodDeclaringClass(method, &decl_class);
+        if (JvmtiErrorToException(env, class_result)) {
+          return nullptr;
+        }
+        jint modifiers;
+        jvmtiError mod_result = jvmti_env->GetMethodModifiers(method, &modifiers);
+        if (JvmtiErrorToException(env, mod_result)) {
+          return nullptr;
+        }
+        constexpr jint kStatic = 0x8;
+        return env->ToReflectedMethod(decl_class,
+                                      method,
+                                      (modifiers & kStatic) != 0 ? JNI_TRUE : JNI_FALSE);
+      }
+      case 1:
+        return env->NewStringUTF(
+            android::base::StringPrintf("%x", static_cast<uint32_t>(location)).c_str());
+    }
+    LOG(FATAL) << "Unreachable";
+    UNREACHABLE();
+  };
+  jobjectArray ret = CreateObjectArray(env, 2, "java/lang/Object", callback);
+  return ret;
+}
+
 }  // namespace Test911GetStackTrace
 }  // namespace art