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,