Adds C++ API for retrieving a stack trace without running JavaScript

This API is extensible, and parameterized with flags so that callers can specify what subset of information they want to capture for each stack frame. 

Patch by jaimeyap, see http://codereview.chromium.org/1694011 for details.
Review URL: http://codereview.chromium.org/2028001

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4597 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
sgjesse@chromium.org 2010-05-06 07:32:44 +00:00
parent 7fc98eb1f4
commit f5b5edf2a3
8 changed files with 434 additions and 9 deletions

View File

@ -109,7 +109,7 @@ class V8EXPORT CpuProfileNode {
/** Retrieves a child node by index. */
const CpuProfileNode* GetChild(int index) const;
static const int kNoLineNumberInfo = 0;
static const int kNoLineNumberInfo = Message::kNoLineNumberInfo;
};

View File

@ -126,6 +126,8 @@ template <class T> class Persistent;
class FunctionTemplate;
class ObjectTemplate;
class Data;
class StackTrace;
class StackFrame;
namespace internal {
@ -691,6 +693,106 @@ class V8EXPORT Message {
// TODO(1245381): Print to a string instead of on a FILE.
static void PrintCurrentStackTrace(FILE* out);
static const int kNoLineNumberInfo = 0;
static const int kNoColumnInfo = 0;
};
/**
* Representation of a JavaScript stack trace. The information collected is a
* snapshot of the execution stack and the information remains valid after
* execution continues.
*/
class V8EXPORT StackTrace {
public:
/**
* Flags that determine what information is placed captured for each
* StackFrame when grabbing the current stack trace.
*/
enum StackTraceOptions {
kLineNumber = 1,
kColumnOffset = 1 << 1 | kLineNumber,
kScriptName = 1 << 2,
kFunctionName = 1 << 3,
kIsEval = 1 << 4,
kIsConstructor = 1 << 5,
kOverview = kLineNumber | kColumnOffset | kScriptName | kFunctionName,
kDetailed = kOverview | kIsEval | kIsConstructor
};
/**
* Returns a StackFrame at a particular index.
*/
Local<StackFrame> GetFrame(uint32_t index) const;
/**
* Returns the number of StackFrames.
*/
int GetFrameCount() const;
/**
* Returns StackTrace as a v8::Array that contains StackFrame objects.
*/
Local<Array> AsArray();
/**
* Grab a snapshot of the the current JavaScript execution stack.
*
* \param frame_limit The maximum number of stack frames we want to capture.
* \param options Enumerates the set of things we will capture for each
* StackFrame.
*/
static Local<StackTrace> CurrentStackTrace(
int frame_limit,
StackTraceOptions options = kOverview);
};
/**
* A single JavaScript stack frame.
*/
class V8EXPORT StackFrame {
public:
/**
* Returns the number, 1-based, of the line for the associate function call.
* This method will return Message::kNoLineNumberInfo if it is unable to
* retrieve the line number, or if kLineNumber was not passed as an option
* when capturing the StackTrace.
*/
int GetLineNumber() const;
/**
* Returns the 1-based column offset on the line for the associated function
* call.
* This method will return Message::kNoColumnInfo if it is unable to retrieve
* the column number, or if kColumnOffset was not passed as an option when
* capturing the StackTrace.
*/
int GetColumn() const;
/**
* Returns the name of the resource that contains the script for the
* function for this StackFrame.
*/
Local<String> GetScriptName() const;
/**
* Returns the name of the function associated with this stack frame.
*/
Local<String> GetFunctionName() const;
/**
* Returns whether or not the associated function is compiled via a call to
* eval().
*/
bool IsEval() const;
/**
* Returns whther or not the associated function is called as a
* constructor via "new".
*/
bool IsConstructor() const;
};

View File

@ -1438,7 +1438,7 @@ static i::Handle<i::Object> CallV8HeapFunction(const char* name,
int Message::GetLineNumber() const {
ON_BAILOUT("v8::Message::GetLineNumber()", return -1);
ON_BAILOUT("v8::Message::GetLineNumber()", return kNoLineNumberInfo);
ENTER_V8;
HandleScope scope;
EXCEPTION_PREAMBLE();
@ -1470,7 +1470,7 @@ int Message::GetEndPosition() const {
int Message::GetStartColumn() const {
if (IsDeadCheck("v8::Message::GetStartColumn()")) return 0;
if (IsDeadCheck("v8::Message::GetStartColumn()")) return kNoColumnInfo;
ENTER_V8;
HandleScope scope;
i::Handle<i::JSObject> data_obj = Utils::OpenHandle(this);
@ -1485,7 +1485,7 @@ int Message::GetStartColumn() const {
int Message::GetEndColumn() const {
if (IsDeadCheck("v8::Message::GetEndColumn()")) return 0;
if (IsDeadCheck("v8::Message::GetEndColumn()")) return kNoColumnInfo;
ENTER_V8;
HandleScope scope;
i::Handle<i::JSObject> data_obj = Utils::OpenHandle(this);
@ -1525,6 +1525,118 @@ void Message::PrintCurrentStackTrace(FILE* out) {
}
// --- S t a c k T r a c e ---
Local<StackFrame> StackTrace::GetFrame(uint32_t index) const {
if (IsDeadCheck("v8::StackTrace::GetFrame()")) return Local<StackFrame>();
ENTER_V8;
HandleScope scope;
i::Handle<i::JSArray> self = Utils::OpenHandle(this);
i::Handle<i::JSObject> obj(i::JSObject::cast(self->GetElement(index)));
return scope.Close(Utils::StackFrameToLocal(obj));
}
int StackTrace::GetFrameCount() const {
if (IsDeadCheck("v8::StackTrace::GetFrameCount()")) return -1;
ENTER_V8;
return i::Smi::cast(Utils::OpenHandle(this)->length())->value();
}
Local<Array> StackTrace::AsArray() {
if (IsDeadCheck("v8::StackTrace::AsArray()")) Local<Array>();
ENTER_V8;
return Utils::ToLocal(Utils::OpenHandle(this));
}
Local<StackTrace> StackTrace::CurrentStackTrace(int frame_limit,
StackTraceOptions options) {
if (IsDeadCheck("v8::StackTrace::CurrentStackTrace()")) Local<StackTrace>();
ENTER_V8;
return i::Top::CaptureCurrentStackTrace(frame_limit, options);
}
// --- S t a c k F r a m e ---
int StackFrame::GetLineNumber() const {
if (IsDeadCheck("v8::StackFrame::GetLineNumber()")) {
return Message::kNoLineNumberInfo;
}
ENTER_V8;
i::HandleScope scope;
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> line = GetProperty(self, "lineNumber");
if (!line->IsSmi()) {
return Message::kNoLineNumberInfo;
}
return i::Smi::cast(*line)->value();
}
int StackFrame::GetColumn() const {
if (IsDeadCheck("v8::StackFrame::GetColumn()")) {
return Message::kNoColumnInfo;
}
ENTER_V8;
i::HandleScope scope;
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> column = GetProperty(self, "column");
if (!column->IsSmi()) {
return Message::kNoColumnInfo;
}
return i::Smi::cast(*column)->value();
}
Local<String> StackFrame::GetScriptName() const {
if (IsDeadCheck("v8::StackFrame::GetScriptName()")) return Local<String>();
ENTER_V8;
HandleScope scope;
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> name = GetProperty(self, "scriptName");
if (!name->IsString()) {
return Local<String>();
}
return scope.Close(Local<String>::Cast(Utils::ToLocal(name)));
}
Local<String> StackFrame::GetFunctionName() const {
if (IsDeadCheck("v8::StackFrame::GetFunctionName()")) return Local<String>();
ENTER_V8;
HandleScope scope;
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> name = GetProperty(self, "functionName");
if (!name->IsString()) {
return Local<String>();
}
return scope.Close(Local<String>::Cast(Utils::ToLocal(name)));
}
bool StackFrame::IsEval() const {
if (IsDeadCheck("v8::StackFrame::IsEval()")) return false;
ENTER_V8;
i::HandleScope scope;
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> is_eval = GetProperty(self, "isEval");
return is_eval->IsTrue();
}
bool StackFrame::IsConstructor() const {
if (IsDeadCheck("v8::StackFrame::IsConstructor()")) return false;
ENTER_V8;
i::HandleScope scope;
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> is_constructor = GetProperty(self, "isConstructor");
return is_constructor->IsTrue();
}
// --- D a t a ---
bool Value::IsUndefined() const {

View File

@ -192,6 +192,10 @@ class Utils {
v8::internal::Handle<v8::internal::Proxy> obj);
static inline Local<Message> MessageToLocal(
v8::internal::Handle<v8::internal::Object> obj);
static inline Local<StackTrace> StackTraceToLocal(
v8::internal::Handle<v8::internal::JSArray> obj);
static inline Local<StackFrame> StackFrameToLocal(
v8::internal::Handle<v8::internal::JSObject> obj);
static inline Local<Number> NumberToLocal(
v8::internal::Handle<v8::internal::Object> obj);
static inline Local<Integer> IntegerToLocal(
@ -227,6 +231,10 @@ class Utils {
OpenHandle(const Function* data);
static inline v8::internal::Handle<v8::internal::JSObject>
OpenHandle(const Message* message);
static inline v8::internal::Handle<v8::internal::JSArray>
OpenHandle(const StackTrace* stack_trace);
static inline v8::internal::Handle<v8::internal::JSObject>
OpenHandle(const StackFrame* stack_frame);
static inline v8::internal::Handle<v8::internal::Context>
OpenHandle(const v8::Context* context);
static inline v8::internal::Handle<v8::internal::SignatureInfo>
@ -275,6 +283,8 @@ MAKE_TO_LOCAL(ToLocal, ObjectTemplateInfo, ObjectTemplate)
MAKE_TO_LOCAL(ToLocal, SignatureInfo, Signature)
MAKE_TO_LOCAL(ToLocal, TypeSwitchInfo, TypeSwitch)
MAKE_TO_LOCAL(MessageToLocal, Object, Message)
MAKE_TO_LOCAL(StackTraceToLocal, JSArray, StackTrace)
MAKE_TO_LOCAL(StackFrameToLocal, JSObject, StackFrame)
MAKE_TO_LOCAL(NumberToLocal, Object, Number)
MAKE_TO_LOCAL(IntegerToLocal, Object, Integer)
MAKE_TO_LOCAL(Uint32ToLocal, Object, Uint32)
@ -305,6 +315,8 @@ MAKE_OPEN_HANDLE(Function, JSFunction)
MAKE_OPEN_HANDLE(Message, JSObject)
MAKE_OPEN_HANDLE(Context, Context)
MAKE_OPEN_HANDLE(External, Proxy)
MAKE_OPEN_HANDLE(StackTrace, JSArray)
MAKE_OPEN_HANDLE(StackFrame, JSObject)
#undef MAKE_OPEN_HANDLE

View File

@ -42,6 +42,9 @@ var COMPILATION_TYPE_JSON = 2;
var kVowelSounds = 0;
var kCapitalVowelSounds = 0;
// Matches Messages::kNoLineNumberInfo from v8.h
var kNoLineNumberInfo = 0;
// If this object gets passed to an error constructor the error will
// get an accessor for .message that constructs a descriptive error
// message on access.
@ -203,9 +206,9 @@ function FormatMessage(message) {
function GetLineNumber(message) {
if (message.startPos == -1) return -1;
if (message.startPos == -1) return kNoLineNumberInfo;
var location = message.script.locationFromPosition(message.startPos, true);
if (location == null) return -1;
if (location == null) return kNoLineNumberInfo;
return location.line + 1;
}

View File

@ -337,7 +337,7 @@ static int stack_trace_nesting_level = 0;
static StringStream* incomplete_message = NULL;
Handle<String> Top::StackTrace() {
Handle<String> Top::StackTraceString() {
if (stack_trace_nesting_level == 0) {
stack_trace_nesting_level++;
HeapStringAllocator allocator;
@ -365,6 +365,90 @@ Handle<String> Top::StackTrace() {
}
Local<StackTrace> Top::CaptureCurrentStackTrace(
int frame_limit, StackTrace::StackTraceOptions options) {
v8::HandleScope scope;
// Ensure no negative values.
int limit = Max(frame_limit, 0);
Handle<JSArray> stackTrace = Factory::NewJSArray(frame_limit);
FixedArray* frames = FixedArray::cast(stackTrace->elements());
Handle<String> column_key = Factory::LookupAsciiSymbol("column");
Handle<String> line_key = Factory::LookupAsciiSymbol("lineNumber");
Handle<String> script_key = Factory::LookupAsciiSymbol("scriptName");
Handle<String> function_key = Factory::LookupAsciiSymbol("functionName");
Handle<String> eval_key = Factory::LookupAsciiSymbol("isEval");
Handle<String> constructor_key = Factory::LookupAsciiSymbol("isConstructor");
StackTraceFrameIterator it;
int frames_seen = 0;
while (!it.done() && (frames_seen < limit)) {
// Create a JSObject to hold the information for the StackFrame.
Handle<JSObject> stackFrame = Factory::NewJSObject(object_function());
JavaScriptFrame* frame = it.frame();
JSFunction* fun(JSFunction::cast(frame->function()));
Script* script = Script::cast(fun->shared()->script());
if (options & StackTrace::kLineNumber) {
int script_line_offset = script->line_offset()->value();
int position = frame->code()->SourcePosition(frame->pc());
int line_number = GetScriptLineNumber(Handle<Script>(script), position);
if (options & StackTrace::kColumnOffset) {
Handle<FixedArray> line_ends(FixedArray::cast(script->line_ends()));
int start = (line_number == 0) ?
0 : Smi::cast(line_ends->get(line_number - 1))->value() + 1;
int column_offset = position - start;
if (line_number == script_line_offset) {
// For the case where the code is on the same line as the script tag.
column_offset += script_line_offset;
}
SetProperty(stackFrame, column_key,
Handle<Smi>(Smi::FromInt(column_offset + 1)), NONE);
}
// Adjust the line_number by the offset in the parent resource.
line_number += script_line_offset;
SetProperty(stackFrame, line_key,
Handle<Smi>(Smi::FromInt(line_number + 1)), NONE);
}
if (options & StackTrace::kScriptName) {
Handle<Object> script_name(script->name());
SetProperty(stackFrame, script_key, script_name, NONE);
}
if (options & StackTrace::kFunctionName) {
Handle<Object> fun_name(fun->shared()->name());
if (!fun_name->IsString()) {
fun_name = Handle<Object>(fun->shared()->inferred_name());
}
SetProperty(stackFrame, function_key, fun_name, NONE);
}
if (options & StackTrace::kIsEval) {
int type = Smi::cast(script->compilation_type())->value();
Handle<Object> is_eval = (type == Script::COMPILATION_TYPE_EVAL) ?
Factory::true_value() : Factory::false_value();
SetProperty(stackFrame, eval_key, is_eval, NONE);
}
if (options & StackTrace::kIsConstructor) {
Handle<Object> is_constructor = (frame->IsConstructor()) ?
Factory::true_value() : Factory::false_value();
SetProperty(stackFrame, constructor_key, is_constructor, NONE);
}
frames->set(frames_seen, *stackFrame);
frames_seen++;
it.Advance();
}
stackTrace->set_length(Smi::FromInt(frames_seen));
return scope.Close(Utils::StackTraceToLocal(stackTrace));
}
void Top::PrintStack() {
if (stack_trace_nesting_level == 0) {
stack_trace_nesting_level++;
@ -786,7 +870,7 @@ void Top::DoThrow(Object* exception,
// traces while the bootstrapper is active since the infrastructure
// may not have been properly initialized.
Handle<String> stack_trace;
if (FLAG_trace_exception) stack_trace = StackTrace();
if (FLAG_trace_exception) stack_trace = StackTraceString();
message_obj = MessageHandler::MakeMessageObject("uncaught_exception",
location, HandleVector<Object>(&exception_handle, 1), stack_trace);
}

View File

@ -265,7 +265,10 @@ class Top {
static void PrintStackTrace(FILE* out, char* thread_data);
static void PrintStack(StringStream* accumulator);
static void PrintStack();
static Handle<String> StackTrace();
static Handle<String> StackTraceString();
static Local<StackTrace> CaptureCurrentStackTrace(
int frame_limit,
StackTrace::StackTraceOptions options);
// Returns if the top context may access the given global object. If
// the result is false, the pending exception is guaranteed to be

View File

@ -9584,6 +9584,115 @@ THREADED_TEST(StackTrace) {
}
// Checks that a StackFrame has certain expected values.
void checkStackFrame(const char* expected_script_name,
const char* expected_func_name, int expected_line_number,
int expected_column, bool is_eval, bool is_constructor,
v8::Handle<v8::StackFrame> frame) {
v8::HandleScope scope;
v8::String::Utf8Value func_name(frame->GetFunctionName());
v8::String::Utf8Value script_name(frame->GetScriptName());
if (*script_name == NULL) {
// The situation where there is no associated script, like for evals.
CHECK(expected_script_name == NULL);
} else {
CHECK(strstr(*script_name, expected_script_name) != NULL);
}
CHECK(strstr(*func_name, expected_func_name) != NULL);
CHECK_EQ(expected_line_number, frame->GetLineNumber());
CHECK_EQ(expected_column, frame->GetColumn());
CHECK_EQ(is_eval, frame->IsEval());
CHECK_EQ(is_constructor, frame->IsConstructor());
}
v8::Handle<Value> AnalyzeStackInNativeCode(const v8::Arguments& args) {
v8::HandleScope scope;
const char* origin = "capture-stack-trace-test";
const int kOverviewTest = 1;
const int kDetailedTest = 2;
ASSERT(args.Length() == 1);
int testGroup = args[0]->ToNumber()->Value();
if (testGroup == kOverviewTest) {
v8::Handle<v8::StackTrace> stackTrace =
v8::StackTrace::CurrentStackTrace(10, v8::StackTrace::kOverview);
CHECK_EQ(4, stackTrace->GetFrameCount());
checkStackFrame(origin, "bar", 2, 10, false, false,
stackTrace->GetFrame(0));
checkStackFrame(origin, "foo", 6, 3, false, false,
stackTrace->GetFrame(1));
checkStackFrame(NULL, "", 1, 1, false, false,
stackTrace->GetFrame(2));
// The last frame is an anonymous function that has the initial call.
checkStackFrame(origin, "", 8, 7, false, false,
stackTrace->GetFrame(3));
CHECK(stackTrace->AsArray()->IsArray());
} else if (testGroup == kDetailedTest) {
v8::Handle<v8::StackTrace> stackTrace =
v8::StackTrace::CurrentStackTrace(10, v8::StackTrace::kDetailed);
CHECK_EQ(4, stackTrace->GetFrameCount());
checkStackFrame(origin, "bat", 2, 1, false, false,
stackTrace->GetFrame(0));
checkStackFrame(origin, "baz", 5, 3, false, true,
stackTrace->GetFrame(1));
checkStackFrame(NULL, "", 1, 1, true, false,
stackTrace->GetFrame(2));
// The last frame is an anonymous function that has the initial call to foo.
checkStackFrame(origin, "", 7, 1, false, false,
stackTrace->GetFrame(3));
CHECK(stackTrace->AsArray()->IsArray());
}
return v8::Undefined();
}
// Tests the C++ StackTrace API.
THREADED_TEST(CaptureStackTrace) {
v8::HandleScope scope;
v8::Handle<v8::String> origin = v8::String::New("capture-stack-trace-test");
Local<ObjectTemplate> templ = ObjectTemplate::New();
templ->Set(v8_str("AnalyzeStackInNativeCode"),
v8::FunctionTemplate::New(AnalyzeStackInNativeCode));
LocalContext context(0, templ);
// Test getting OVERVIEW information. Should ignore information that is not
// script name, function name, line number, and column offset.
const char *overview_source =
"function bar() {\n"
" var y; AnalyzeStackInNativeCode(1);\n"
"}\n"
"function foo() {\n"
"\n"
" bar();\n"
"}\n"
"var x;eval('new foo();');";
v8::Handle<v8::String> overview_src = v8::String::New(overview_source);
v8::Handle<Value> overview_result =
v8::Script::New(overview_src, origin)->Run();
ASSERT(!overview_result.IsEmpty());
ASSERT(overview_result->IsObject());
// Test getting DETAILED information.
const char *detailed_source =
"function bat() {\n"
"AnalyzeStackInNativeCode(2);\n"
"}\n"
"function baz() {\n"
" bat();\n"
"}\n"
"eval('new baz();');";
v8::Handle<v8::String> detailed_src = v8::String::New(detailed_source);
v8::Handle<Value> detailed_result =
v8::Script::New(detailed_src, origin)->Run();
ASSERT(!detailed_result.IsEmpty());
ASSERT(detailed_result->IsObject());
}
// Test that idle notification can be handled and eventually returns true.
THREADED_TEST(IdleNotification) {
bool rv = false;