/** Retrieves a child node by index. */
const CpuProfileNode* GetChild(int index) const;
- static const int kNoLineNumberInfo = 0;
+ static const int kNoLineNumberInfo = Message::kNoLineNumberInfo;
};
class FunctionTemplate;
class ObjectTemplate;
class Data;
+class StackTrace;
+class StackFrame;
namespace internal {
// 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;
};
int Message::GetLineNumber() const {
- ON_BAILOUT("v8::Message::GetLineNumber()", return -1);
+ ON_BAILOUT("v8::Message::GetLineNumber()", return kNoLineNumberInfo);
ENTER_V8;
HandleScope scope;
EXCEPTION_PREAMBLE();
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);
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);
}
+// --- 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 {
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(
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>
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)
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
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.
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;
}
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;
}
+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++;
// 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);
}
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
}
+// 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;