Implement Throwable::nativeFillInStackTrace and nativeGetStackTrace.
Refactor stack trace into compact internal form which is then used to
compute larger and more verbose StackTraceElement[] form. Fix some
potential problems with GC.
Change-Id: I4a4308c1ec5b82fd95b649d5ec0504b9607e688f
diff --git a/src/exception_test.cc b/src/exception_test.cc
index 5283697..d77f1c0 100644
--- a/src/exception_test.cc
+++ b/src/exception_test.cc
@@ -162,7 +162,13 @@
Thread* thread = Thread::Current();
thread->SetTopOfStack(fake_stack);
- ObjectArray<StackTraceElement>* trace_array = thread->AllocStackTrace();
+ jobject internal = thread->CreateInternalStackTrace();
+ jobjectArray ste_array =
+ Thread::InternalStackTraceToStackTraceElementArray(internal,
+ thread->GetJniEnv());
+ ObjectArray<StackTraceElement>* trace_array =
+ Decode<ObjectArray<StackTraceElement>*>(thread->GetJniEnv(), ste_array);
+
ASSERT_TRUE(trace_array->Get(0) != NULL);
EXPECT_STREQ("java.lang.MyClass",
diff --git a/src/java_lang_Throwable.cc b/src/java_lang_Throwable.cc
index 1d13907..4c3bfcf 100644
--- a/src/java_lang_Throwable.cc
+++ b/src/java_lang_Throwable.cc
@@ -15,7 +15,7 @@
*/
#include "jni_internal.h"
-#include "object.h"
+#include "thread.h"
#include "JniConstants.h" // Last to avoid problems with LOG redefinition.
@@ -24,22 +24,12 @@
namespace {
jobject Throwable_nativeFillInStackTrace(JNIEnv* env, jclass) {
- UNIMPLEMENTED(WARNING);
- //Object* stackState = dvmFillInStackTrace(dvmThreadSelf());
- //return addLocalReference(env, stackState);
- return NULL;
+ JNIEnvExt* env_ext = reinterpret_cast<JNIEnvExt*>(env);
+ return env_ext->self->CreateInternalStackTrace();
}
jobjectArray Throwable_nativeGetStackTrace(JNIEnv* env, jclass, jobject javaStackState) {
- UNIMPLEMENTED(WARNING);
- //Object* stackState = dvmDecodeIndirectRef(env, javaStackState);
- //if (stackState == NULL) {
- //LOGW("getStackTrace() called but no trace available");
- //return NULL; /* could throw NPE; currently caller will do so */
- //}
- //ArrayObject* elements = dvmGetStackTrace(stackState);
- //return reinterpret_cast<jobjectArray>(addLocalReference(env, elements));
- return NULL;
+ return Thread::InternalStackTraceToStackTraceElementArray(javaStackState, env);
}
JNINativeMethod gMethods[] = {
diff --git a/src/jni_compiler_test.cc b/src/jni_compiler_test.cc
index 216350b..880503f 100644
--- a/src/jni_compiler_test.cc
+++ b/src/jni_compiler_test.cc
@@ -14,6 +14,7 @@
#include "jni_internal.h"
#include "mem_map.h"
#include "runtime.h"
+#include "scoped_jni_thread_state.h"
#include "thread.h"
namespace art {
@@ -413,12 +414,14 @@
int gSuspendCounterHandler_calls;
void SuspendCountHandler(Method** frame) {
+ // Check we came here in the native state then transition to runnable to work
+ // on the Object*
EXPECT_EQ(Thread::kNative, Thread::Current()->GetState());
- Thread::Current()->SetState(Thread::kRunnable);
+ ScopedJniThreadState ts(Thread::Current()->GetJniEnv());
+
EXPECT_TRUE((*frame)->GetName()->Equals("fooI"));
gSuspendCounterHandler_calls++;
Thread::Current()->DecrementSuspendCount();
- Thread::Current()->SetState(Thread::kNative);
}
TEST_F(JniCompilerTest, SuspendCountAcknowledgement) {
@@ -444,12 +447,14 @@
int gExceptionHandler_calls;
void ExceptionHandler(Method** frame) {
+ // Check we came here in the native state then transition to runnable to work
+ // on the Object*
EXPECT_EQ(Thread::kNative, Thread::Current()->GetState());
- Thread::Current()->SetState(Thread::kRunnable);
+ ScopedJniThreadState ts(Thread::Current()->GetJniEnv());
+
EXPECT_TRUE((*frame)->GetName()->Equals("throwException"));
gExceptionHandler_calls++;
Thread::Current()->ClearException();
- Thread::Current()->SetState(Thread::kNative);
}
void Java_MyClass_throwException(JNIEnv* env, jobject) {
@@ -480,19 +485,28 @@
jint Java_MyClass_nativeUpCall(JNIEnv* env, jobject thisObj, jint i) {
if (i <= 0) {
- EXPECT_EQ(Thread::kNative, Thread::Current()->GetState());
- Thread::Current()->SetState(Thread::kRunnable);
- ObjectArray<StackTraceElement>* trace_array = Thread::Current()->AllocStackTrace();
+ // We want to check raw Object*/Array* below
+ ScopedJniThreadState ts(env);
+
+ // Build stack trace
+ jobject internal = Thread::Current()->CreateInternalStackTrace();
+ jobjectArray ste_array =
+ Thread::InternalStackTraceToStackTraceElementArray(internal, env);
+ ObjectArray<StackTraceElement>* trace_array =
+ Decode<ObjectArray<StackTraceElement>*>(env, ste_array);
EXPECT_TRUE(trace_array != NULL);
EXPECT_EQ(11, trace_array->GetLength());
+ // Check stack trace entries have expected values
for (int32_t i = 0; i < trace_array->GetLength(); ++i) {
EXPECT_EQ(-2, trace_array->Get(i)->GetLineNumber());
- EXPECT_STREQ("MyClassNatives.java", trace_array->Get(i)->GetFileName()->ToModifiedUtf8().c_str());
- EXPECT_STREQ("MyClass", trace_array->Get(i)->GetDeclaringClass()->ToModifiedUtf8().c_str());
- EXPECT_STREQ("fooI", trace_array->Get(i)->GetMethodName()->ToModifiedUtf8().c_str());
+ StackTraceElement* ste = trace_array->Get(i);
+ EXPECT_STREQ("MyClassNatives.java", ste->GetFileName()->ToModifiedUtf8().c_str());
+ EXPECT_STREQ("MyClass", ste->GetDeclaringClass()->ToModifiedUtf8().c_str());
+ EXPECT_STREQ("fooI", ste->GetMethodName()->ToModifiedUtf8().c_str());
}
- Thread::Current()->SetState(Thread::kNative);
+
+ // end recursion
return 0;
} else {
jclass jklass = env->FindClass("MyClass");
@@ -500,7 +514,10 @@
jmethodID jmethod = env->GetMethodID(jklass, "fooI", "(I)I");
EXPECT_TRUE(jmethod != NULL);
+ // Recurse with i - 1
jint result = env->CallNonvirtualIntMethod(thisObj, jklass, jmethod, i - 1);
+
+ // Return sum of all depths
return i + result;
}
}
@@ -508,7 +525,7 @@
TEST_F(JniCompilerTest, NativeStackTraceElement) {
SetupForTest(false, "fooI", "(I)I", reinterpret_cast<void*>(&Java_MyClass_nativeUpCall));
jint result = env_->CallNonvirtualIntMethod(jobj_, jklass_, jmethod_, 10);
- EXPECT_EQ(55, result);
+ EXPECT_EQ(10+9+8+7+6+5+4+3+2+1, result);
}
jobject Java_MyClass_fooO(JNIEnv* env, jobject thisObj, jobject x) {
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index 954fafd..0f464d2 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -111,6 +111,8 @@
template ClassLoader* Decode<ClassLoader*>(JNIEnv*, jobject);
template Object* Decode<Object*>(JNIEnv*, jobject);
template String* Decode<String*>(JNIEnv*, jobject);
+template ObjectArray<StackTraceElement>*
+ Decode<ObjectArray<StackTraceElement>*>(JNIEnv*, jobject);
namespace {
diff --git a/src/thread.cc b/src/thread.cc
index 4b477b8..312bb30 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -17,6 +17,7 @@
#include "object.h"
#include "runtime.h"
#include "runtime_support.h"
+#include "scoped_jni_thread_state.h"
#include "thread_list.h"
#include "utils.h"
@@ -781,53 +782,65 @@
class CountStackDepthVisitor : public Thread::StackVisitor {
public:
- CountStackDepthVisitor() : depth(0) {}
+ CountStackDepthVisitor() : depth_(0) {}
virtual bool VisitFrame(const Frame&) {
- ++depth;
+ ++depth_;
return true;
}
int GetDepth() const {
- return depth;
+ return depth_;
}
private:
- uint32_t depth;
+ uint32_t depth_;
};
-class BuildStackTraceVisitor : public Thread::StackVisitor {
+//
+class BuildInternalStackTraceVisitor : public Thread::StackVisitor {
public:
- explicit BuildStackTraceVisitor(int depth) : count(0) {
- method_trace = Runtime::Current()->GetClassLinker()->AllocObjectArray<Method>(depth);
- pc_trace = IntArray::Alloc(depth);
+ explicit BuildInternalStackTraceVisitor(int depth, ScopedJniThreadState& ts) : count_(0) {
+ // Allocate method trace with an extra slot that will hold the PC trace
+ method_trace_ = Runtime::Current()->GetClassLinker()->
+ AllocObjectArray<Object>(depth + 1);
+ // Register a local reference as IntArray::Alloc may trigger GC
+ local_ref_ = AddLocalReference<jobject>(ts.Env(), method_trace_);
+ pc_trace_ = IntArray::Alloc(depth);
+#ifdef MOVING_GARBAGE_COLLECTOR
+ // Re-read after potential GC
+ method_trace = Decode<ObjectArray<Object>*>(ts.Env(), local_ref_);
+#endif
+ // Save PC trace in last element of method trace, also places it into the
+ // object graph.
+ method_trace_->Set(depth, pc_trace_);
}
- virtual ~BuildStackTraceVisitor() {}
+ virtual ~BuildInternalStackTraceVisitor() {}
virtual bool VisitFrame(const Frame& frame) {
- method_trace->Set(count, frame.GetMethod());
- pc_trace->Set(count, frame.GetPC());
- ++count;
+ method_trace_->Set(count_, frame.GetMethod());
+ pc_trace_->Set(count_, frame.GetPC());
+ ++count_;
return true;
}
- const Method* GetMethod(uint32_t i) {
- DCHECK(i < count);
- return method_trace->Get(i);
- }
-
- uintptr_t GetPC(uint32_t i) {
- DCHECK(i < count);
- return pc_trace->Get(i);
+ jobject GetInternalStackTrace() const {
+ return local_ref_;
}
private:
- uint32_t count;
- ObjectArray<Method>* method_trace;
- IntArray* pc_trace;
+ // Current position down stack trace
+ uint32_t count_;
+ // Array of return PC values
+ IntArray* pc_trace_;
+ // An array of the methods on the stack, the last entry is a reference to the
+ // PC trace
+ ObjectArray<Object>* method_trace_;
+ // Local indirect reference table entry for method trace
+ jobject local_ref_;
};
-void Thread::WalkStack(StackVisitor* visitor) {
+void Thread::WalkStack(StackVisitor* visitor) const {
Frame frame = Thread::Current()->GetTopOfStack();
// TODO: enable this CHECK after native_to_managed_record_ is initialized during startup.
// CHECK(native_to_managed_record_ != NULL);
@@ -845,35 +858,65 @@
}
}
-ObjectArray<StackTraceElement>* Thread::AllocStackTrace() {
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-
+jobject Thread::CreateInternalStackTrace() const {
+ // Compute depth of stack
CountStackDepthVisitor count_visitor;
WalkStack(&count_visitor);
int32_t depth = count_visitor.GetDepth();
- BuildStackTraceVisitor build_trace_visitor(depth);
+ // Transition into runnable state to work on Object*/Array*
+ ScopedJniThreadState ts(jni_env_);
+
+ // Build internal stack trace
+ BuildInternalStackTraceVisitor build_trace_visitor(depth, ts);
WalkStack(&build_trace_visitor);
- ObjectArray<StackTraceElement>* java_traces = class_linker->AllocStackTraceElementArray(depth);
+ return build_trace_visitor.GetInternalStackTrace();
+}
+
+jobjectArray Thread::InternalStackTraceToStackTraceElementArray(jobject internal,
+ JNIEnv* env) {
+ // Transition into runnable state to work on Object*/Array*
+ ScopedJniThreadState ts(env);
+
+ // Decode the internal stack trace into the depth, method trace and PC trace
+ ObjectArray<Object>* method_trace =
+ down_cast<ObjectArray<Object>*>(Decode<Object*>(ts.Env(), internal));
+ int32_t depth = method_trace->GetLength()-1;
+ IntArray* pc_trace = down_cast<IntArray*>(method_trace->Get(depth));
+
+ ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+
+ // Create java_trace array and place in local reference table
+ ObjectArray<StackTraceElement>* java_traces =
+ class_linker->AllocStackTraceElementArray(depth);
+ jobjectArray result = AddLocalReference<jobjectArray>(ts.Env(), java_traces);
for (int32_t i = 0; i < depth; ++i) {
- // Prepare parameter for StackTraceElement(String cls, String method, String file, int line)
- const Method* method = build_trace_visitor.GetMethod(i);
- const Class* klass = method->GetDeclaringClass();
+ // Prepare parameters for StackTraceElement(String cls, String method, String file, int line)
+ Method* method = down_cast<Method*>(method_trace->Get(i));
+ uint32_t native_pc = pc_trace->Get(i);
+ Class* klass = method->GetDeclaringClass();
const DexFile& dex_file = class_linker->FindDexFile(klass->GetDexCache());
String* readable_descriptor = String::AllocFromModifiedUtf8(
PrettyDescriptor(klass->GetDescriptor()).c_str());
+ // Allocate element, potentially triggering GC
StackTraceElement* obj =
StackTraceElement::Alloc(readable_descriptor,
method->GetName(),
klass->GetSourceFile(),
dex_file.GetLineNumFromPC(method,
- method->ToDexPC(build_trace_visitor.GetPC(i))));
+ method->ToDexPC(native_pc)));
+#ifdef MOVING_GARBAGE_COLLECTOR
+ // Re-read after potential GC
+ java_traces = Decode<ObjectArray<Object>*>(ts.Env(), result);
+ method_trace = down_cast<ObjectArray<Object>*>(Decode<Object*>(ts.Env(), internal));
+ pc_trace = down_cast<IntArray*>(method_trace->Get(depth));
+#endif
java_traces->Set(i, obj);
}
- return java_traces;
+ return result;
}
void Thread::ThrowNewException(const char* exception_class_descriptor, const char* fmt, ...) {
diff --git a/src/thread.h b/src/thread.h
index 3f6228a..ed4b1e4 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -400,8 +400,13 @@
class_loader_override_ = class_loader_override;
}
- // Allocate stack trace
- ObjectArray<StackTraceElement>* AllocStackTrace();
+ // Create the internal representation of a stack trace, that is more time
+ // and space efficient to compute than the StackTraceElement[]
+ jobject CreateInternalStackTrace() const;
+
+ // Convert an internal stack trace representation to a StackTraceElement[]
+ static jobjectArray
+ InternalStackTraceToStackTraceElementArray(jobject internal, JNIEnv* env);
void VisitRoots(Heap::RootVisitor* visitor, void* arg) const;
@@ -475,7 +480,7 @@
static void ThreadExitCallback(void* arg);
- void WalkStack(StackVisitor* visitor);
+ void WalkStack(StackVisitor* visitor) const;
// Thin lock thread id. This is a small integer used by the thin lock implementation.
// This is not to be confused with the native thread's tid, nor is it the value returned