From f5b5edf2a38169f384694eb46330cb98bdae1984 Mon Sep 17 00:00:00 2001 From: "sgjesse@chromium.org" Date: Thu, 6 May 2010 07:32:44 +0000 Subject: [PATCH] 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 --- include/v8-profiler.h | 2 +- include/v8.h | 102 +++++++++++++++++++++++++++++++++++++++++ src/api.cc | 118 ++++++++++++++++++++++++++++++++++++++++++++++-- src/api.h | 12 +++++ src/messages.js | 7 ++- src/top.cc | 88 +++++++++++++++++++++++++++++++++++- src/top.h | 5 +- test/cctest/test-api.cc | 109 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 434 insertions(+), 9 deletions(-) diff --git a/include/v8-profiler.h b/include/v8-profiler.h index eca6548..f1b8ffb 100644 --- a/include/v8-profiler.h +++ b/include/v8-profiler.h @@ -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; }; diff --git a/include/v8.h b/include/v8.h index c07ba1f..971ae4b 100644 --- a/include/v8.h +++ b/include/v8.h @@ -126,6 +126,8 @@ template 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 GetFrame(uint32_t index) const; + + /** + * Returns the number of StackFrames. + */ + int GetFrameCount() const; + + /** + * Returns StackTrace as a v8::Array that contains StackFrame objects. + */ + Local 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 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 GetScriptName() const; + + /** + * Returns the name of the function associated with this stack frame. + */ + Local 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; }; diff --git a/src/api.cc b/src/api.cc index 0e5db2c..a4c38b7 100644 --- a/src/api.cc +++ b/src/api.cc @@ -1438,7 +1438,7 @@ static i::Handle 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 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 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 StackTrace::GetFrame(uint32_t index) const { + if (IsDeadCheck("v8::StackTrace::GetFrame()")) return Local(); + ENTER_V8; + HandleScope scope; + i::Handle self = Utils::OpenHandle(this); + i::Handle 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 StackTrace::AsArray() { + if (IsDeadCheck("v8::StackTrace::AsArray()")) Local(); + ENTER_V8; + return Utils::ToLocal(Utils::OpenHandle(this)); +} + + +Local StackTrace::CurrentStackTrace(int frame_limit, + StackTraceOptions options) { + if (IsDeadCheck("v8::StackTrace::CurrentStackTrace()")) Local(); + 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 self = Utils::OpenHandle(this); + i::Handle 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 self = Utils::OpenHandle(this); + i::Handle column = GetProperty(self, "column"); + if (!column->IsSmi()) { + return Message::kNoColumnInfo; + } + return i::Smi::cast(*column)->value(); +} + + +Local StackFrame::GetScriptName() const { + if (IsDeadCheck("v8::StackFrame::GetScriptName()")) return Local(); + ENTER_V8; + HandleScope scope; + i::Handle self = Utils::OpenHandle(this); + i::Handle name = GetProperty(self, "scriptName"); + if (!name->IsString()) { + return Local(); + } + return scope.Close(Local::Cast(Utils::ToLocal(name))); +} + + +Local StackFrame::GetFunctionName() const { + if (IsDeadCheck("v8::StackFrame::GetFunctionName()")) return Local(); + ENTER_V8; + HandleScope scope; + i::Handle self = Utils::OpenHandle(this); + i::Handle name = GetProperty(self, "functionName"); + if (!name->IsString()) { + return Local(); + } + return scope.Close(Local::Cast(Utils::ToLocal(name))); +} + + +bool StackFrame::IsEval() const { + if (IsDeadCheck("v8::StackFrame::IsEval()")) return false; + ENTER_V8; + i::HandleScope scope; + i::Handle self = Utils::OpenHandle(this); + i::Handle 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 self = Utils::OpenHandle(this); + i::Handle is_constructor = GetProperty(self, "isConstructor"); + return is_constructor->IsTrue(); +} + + // --- D a t a --- bool Value::IsUndefined() const { diff --git a/src/api.h b/src/api.h index 7b88112..e7b1394 100644 --- a/src/api.h +++ b/src/api.h @@ -192,6 +192,10 @@ class Utils { v8::internal::Handle obj); static inline Local MessageToLocal( v8::internal::Handle obj); + static inline Local StackTraceToLocal( + v8::internal::Handle obj); + static inline Local StackFrameToLocal( + v8::internal::Handle obj); static inline Local NumberToLocal( v8::internal::Handle obj); static inline Local IntegerToLocal( @@ -227,6 +231,10 @@ class Utils { OpenHandle(const Function* data); static inline v8::internal::Handle OpenHandle(const Message* message); + static inline v8::internal::Handle + OpenHandle(const StackTrace* stack_trace); + static inline v8::internal::Handle + OpenHandle(const StackFrame* stack_frame); static inline v8::internal::Handle OpenHandle(const v8::Context* context); static inline v8::internal::Handle @@ -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 diff --git a/src/messages.js b/src/messages.js index de6a362..a46af4a 100644 --- a/src/messages.js +++ b/src/messages.js @@ -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; } diff --git a/src/top.cc b/src/top.cc index 2f75c8f..c071193 100644 --- a/src/top.cc +++ b/src/top.cc @@ -337,7 +337,7 @@ static int stack_trace_nesting_level = 0; static StringStream* incomplete_message = NULL; -Handle Top::StackTrace() { +Handle Top::StackTraceString() { if (stack_trace_nesting_level == 0) { stack_trace_nesting_level++; HeapStringAllocator allocator; @@ -365,6 +365,90 @@ Handle Top::StackTrace() { } +Local Top::CaptureCurrentStackTrace( + int frame_limit, StackTrace::StackTraceOptions options) { + v8::HandleScope scope; + // Ensure no negative values. + int limit = Max(frame_limit, 0); + Handle stackTrace = Factory::NewJSArray(frame_limit); + FixedArray* frames = FixedArray::cast(stackTrace->elements()); + + Handle column_key = Factory::LookupAsciiSymbol("column"); + Handle line_key = Factory::LookupAsciiSymbol("lineNumber"); + Handle script_key = Factory::LookupAsciiSymbol("scriptName"); + Handle function_key = Factory::LookupAsciiSymbol("functionName"); + Handle eval_key = Factory::LookupAsciiSymbol("isEval"); + Handle 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 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