Add GetSourceFile and GetSourceDebugExt JVMTI functions

Also add associated capabilities.

Test: ./test.py --host -j40
Bug: 62821960

Change-Id: Icc534b2789287fc9f0daddb747c0c0fa81a7728b
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 8daf6f7..0896210 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -686,12 +686,10 @@
     return ClassUtil::GetClassStatus(env, klass, status_ptr);
   }
 
-  static jvmtiError GetSourceFileName(jvmtiEnv* env,
-                                      jclass klass ATTRIBUTE_UNUSED,
-                                      char** source_name_ptr ATTRIBUTE_UNUSED) {
+  static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_get_source_file_name);
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetSourceFileName(env, klass, source_name_ptr);
   }
 
   static jvmtiError GetClassModifiers(jvmtiEnv* env, jclass klass, jint* modifiers_ptr) {
@@ -766,11 +764,11 @@
   }
 
   static jvmtiError GetSourceDebugExtension(jvmtiEnv* env,
-                                            jclass klass ATTRIBUTE_UNUSED,
-                                            char** source_debug_extension_ptr ATTRIBUTE_UNUSED) {
+                                            jclass klass,
+                                            char** source_debug_extension_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_get_source_debug_extension);
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetSourceDebugExtension(env, klass, source_debug_extension_ptr);
   }
 
   static jvmtiError RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) {
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index 8ba3527..b5f1219 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -218,9 +218,9 @@
     .can_pop_frame                                   = 0,
     .can_redefine_classes                            = 1,
     .can_signal_thread                               = 0,
-    .can_get_source_file_name                        = 0,
+    .can_get_source_file_name                        = 1,
     .can_get_line_numbers                            = 1,
-    .can_get_source_debug_extension                  = 0,
+    .can_get_source_debug_extension                  = 1,
     .can_access_local_variables                      = 0,
     .can_maintain_original_method_order              = 0,
     .can_generate_single_step_events                 = 0,
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index 3321814..0ac08d9 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -1017,4 +1017,61 @@
   return ERR(NONE);
 }
 
+jvmtiError ClassUtil::GetSourceFileName(jvmtiEnv* env, jclass jklass, char** source_name_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (jklass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass);
+  if (!jklass_obj->IsClass()) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Class> klass = jklass_obj->AsClass();
+  if (klass->IsPrimitive() || klass->IsArrayClass()) {
+    return ERR(ABSENT_INFORMATION);
+  }
+  JvmtiUniquePtr<char[]> source_copy;
+  const char* file_name = klass->GetSourceFile();
+  if (file_name == nullptr) {
+    return ERR(ABSENT_INFORMATION);
+  }
+  jvmtiError ret;
+  source_copy = CopyString(env, file_name, &ret);
+  if (source_copy == nullptr) {
+    return ret;
+  }
+  *source_name_ptr = source_copy.release();
+  return OK;
+}
+
+jvmtiError ClassUtil::GetSourceDebugExtension(jvmtiEnv* env,
+                                              jclass jklass,
+                                              char** source_debug_extension_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (jklass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass);
+  if (!jklass_obj->IsClass()) {
+    return ERR(INVALID_CLASS);
+  }
+  art::StackHandleScope<1> hs(art::Thread::Current());
+  art::Handle<art::mirror::Class> klass(hs.NewHandle(jklass_obj->AsClass()));
+  if (klass->IsPrimitive() || klass->IsArrayClass()) {
+    return ERR(ABSENT_INFORMATION);
+  }
+  JvmtiUniquePtr<char[]> ext_copy;
+  const char* data = art::annotations::GetSourceDebugExtension(klass);
+  if (data == nullptr) {
+    return ERR(ABSENT_INFORMATION);
+  }
+  jvmtiError ret;
+  ext_copy = CopyString(env, data, &ret);
+  if (ext_copy == nullptr) {
+    return ret;
+  }
+  *source_debug_extension_ptr = ext_copy.release();
+  return OK;
+}
+
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_class.h b/runtime/openjdkjvmti/ti_class.h
index aa2260f..7bb6b3e 100644
--- a/runtime/openjdkjvmti/ti_class.h
+++ b/runtime/openjdkjvmti/ti_class.h
@@ -82,6 +82,12 @@
                                            jclass klass,
                                            jint* minor_version_ptr,
                                            jint* major_version_ptr);
+
+  static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr);
+
+  static jvmtiError GetSourceDebugExtension(jvmtiEnv* env,
+                                            jclass klass,
+                                            char** source_debug_extension_ptr);
 };
 
 }  // namespace openjdkjvmti
diff --git a/test/992-source-data/expected.txt b/test/992-source-data/expected.txt
new file mode 100644
index 0000000..480d8a4
--- /dev/null
+++ b/test/992-source-data/expected.txt
@@ -0,0 +1,10 @@
+class art.Test992 is defined in file "Test992.java"
+class art.Test992$Target1 is defined in file "Test992.java"
+class art.Test2 is defined in file "Test2.java"
+int does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION
+class java.lang.Integer is defined in file "Integer.java"
+class java.lang.Object is defined in file "Object.java"
+interface java.lang.Runnable is defined in file "Runnable.java"
+class [Ljava.lang.Object; does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION
+class [I does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION
+null does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_INVALID_CLASS
diff --git a/test/992-source-data/info.txt b/test/992-source-data/info.txt
new file mode 100644
index 0000000..5d487a4
--- /dev/null
+++ b/test/992-source-data/info.txt
@@ -0,0 +1 @@
+Tests that we can get the source file of a class from JVMTI.
diff --git a/test/992-source-data/run b/test/992-source-data/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/992-source-data/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# 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.
+
+./default-run "$@" --jvmti
diff --git a/test/992-source-data/source_file.cc b/test/992-source-data/source_file.cc
new file mode 100644
index 0000000..3e8989e
--- /dev/null
+++ b/test/992-source-data/source_file.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 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 <inttypes.h>
+#include <memory>
+#include <stdio.h>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+
+#include "jni.h"
+#include "jvmti.h"
+#include "scoped_local_ref.h"
+
+// Test infrastructure
+#include "jni_binder.h"
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "test_env.h"
+#include "ti_macros.h"
+
+namespace art {
+namespace Test992SourceFile {
+
+extern "C" JNIEXPORT
+jstring JNICALL Java_art_Test992_getSourceFileName(JNIEnv* env,
+                                                   jclass klass ATTRIBUTE_UNUSED,
+                                                   jclass target) {
+  char* file = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetSourceFileName(target, &file))) {
+    return nullptr;
+  }
+  jstring ret = env->NewStringUTF(file);
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(file));
+  return ret;
+}
+
+}  // namespace Test992SourceFile
+}  // namespace art
+
diff --git a/test/992-source-data/src/Main.java b/test/992-source-data/src/Main.java
new file mode 100644
index 0000000..31106f4
--- /dev/null
+++ b/test/992-source-data/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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 Main {
+  public static void main(String[] args) throws Exception {
+    art.Test992.run();
+  }
+}
diff --git a/test/992-source-data/src/art/Test2.java b/test/992-source-data/src/art/Test2.java
new file mode 100644
index 0000000..dbb1089
--- /dev/null
+++ b/test/992-source-data/src/art/Test2.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package art;
+
+public class Test2 {}
diff --git a/test/992-source-data/src/art/Test992.java b/test/992-source-data/src/art/Test992.java
new file mode 100644
index 0000000..db6ea73
--- /dev/null
+++ b/test/992-source-data/src/art/Test992.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.Base64;
+
+public class Test992 {
+
+  static class Target1 { }
+
+  public static void run() {
+    doTest(Test992.class);
+    doTest(Target1.class);
+    doTest(Test2.class);
+    doTest(Integer.TYPE);
+    doTest(Integer.class);
+    doTest(Object.class);
+    doTest(Runnable.class);
+    doTest(new Object[0].getClass());
+    doTest(new int[0].getClass());
+    doTest(null);
+  }
+
+  public static void doTest(Class<?> k) {
+    try {
+      System.out.println(k + " is defined in file \"" + getSourceFileName(k) + "\"");
+    } catch (Exception e) {
+      System.out.println(k + " does not have a known source file because " + e);
+    }
+  }
+
+  public static native String getSourceFileName(Class<?> k) throws Exception;
+}
diff --git a/test/Android.bp b/test/Android.bp
index 8da415f..9e6ecff 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -280,6 +280,7 @@
         "987-agent-bind/agent_bind.cc",
         "989-method-trace-throw/method_trace.cc",
         "991-field-trace-2/field_trace.cc",
+        "992-source-data/source_file.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc
index 8d4cc89..40fcc4f 100644
--- a/test/ti-stress/stress.cc
+++ b/test/ti-stress/stress.cc
@@ -128,12 +128,16 @@
       : jvmtienv_(jvmtienv),
         class_(c),
         name_(nullptr),
-        generic_(nullptr) {}
+        generic_(nullptr),
+        file_(nullptr),
+        debug_ext_(nullptr) {}
 
   ~ScopedClassInfo() {
     if (class_ != nullptr) {
       jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_));
       jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_));
+      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(file_));
+      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_));
     }
   }
 
@@ -143,7 +147,13 @@
       generic_ = const_cast<char*>("<NONE>");
       return true;
     } else {
-      return jvmtienv_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE;
+      jvmtiError ret1 = jvmtienv_->GetSourceFileName(class_, &file_);
+      jvmtiError ret2 = jvmtienv_->GetSourceDebugExtension(class_, &debug_ext_);
+      return jvmtienv_->GetClassSignature(class_, &name_, &generic_) == 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;
     }
   }
 
@@ -156,12 +166,28 @@
   const char* GetGeneric() const {
     return generic_;
   }
+  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* generic_;
+  char* file_;
+  char* debug_ext_;
 };
 
 class ScopedMethodInfo {
@@ -174,7 +200,8 @@
         class_info_(nullptr),
         name_(nullptr),
         signature_(nullptr),
-        generic_(nullptr) {}
+        generic_(nullptr),
+        first_line_(-1) {}
 
   ~ScopedMethodInfo() {
     env_->DeleteLocalRef(declaring_class_);
@@ -188,6 +215,18 @@
       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_, &generic_) == JVMTI_ERROR_NONE);
   }
@@ -212,6 +251,10 @@
     return generic_;
   }
 
+  jint GetFirstLine() const {
+    return first_line_;
+  }
+
  private:
   jvmtiEnv* jvmtienv_;
   JNIEnv* env_;
@@ -221,6 +264,7 @@
   char* name_;
   char* signature_;
   char* generic_;
+  jint first_line_;
 
   friend std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m);
 };
@@ -295,7 +339,9 @@
 }
 
 std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m) {
-  return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() << m.GetSignature();
+  return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() << m.GetSignature()
+            << " (source: " << m.GetDeclaringClassInfo().GetSourceFileName() << ":"
+            << m.GetFirstLine() << ")";
 }
 
 static void doJvmtiMethodBind(jvmtiEnv* jvmtienv,