From abaf8347a5f485ea35f2e764b287e1fcca5ea323 Mon Sep 17 00:00:00 2001 From: "yurys@chromium.org" Date: Mon, 12 Jul 2010 13:17:27 +0000 Subject: [PATCH] Allow to capture stack trace for uncaught exceptions Review URL: http://codereview.chromium.org/2961003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5043 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- include/v8.h | 16 +++++++++++++ src/api.cc | 31 +++++++++++++++++++++++- src/debug.cc | 2 +- src/messages.cc | 11 ++++++--- src/messages.h | 3 ++- src/messages.js | 10 +++++--- src/top.cc | 53 ++++++++++++++++++++--------------------- src/top.h | 10 ++++---- test/cctest/test-api.cc | 34 ++++++++++++++++++++++++++ 9 files changed, 130 insertions(+), 40 deletions(-) diff --git a/include/v8.h b/include/v8.h index ca4a247fe8..9e4cebb73a 100644 --- a/include/v8.h +++ b/include/v8.h @@ -693,6 +693,13 @@ class V8EXPORT Message { */ Handle GetScriptData() const; + /** + * Exception stack trace. By default stack traces are not captured for + * uncaught exceptions. SetCaptureStackTraceForUncaughtExceptions allows + * to change this option. + */ + Handle GetStackTrace() const; + /** * Returns the number, 1-based, of the line where the error occurred. */ @@ -2458,6 +2465,15 @@ class V8EXPORT V8 { */ static void RemoveMessageListeners(MessageCallback that); + /** + * Tells V8 to capture current stack trace when uncaught exception occurs + * and report it to the message listeners. The option is off by default. + */ + static void SetCaptureStackTraceForUncaughtExceptions( + bool capture, + int frame_limit = 10, + StackTrace::StackTraceOptions options = StackTrace::kOverview); + /** * Sets V8 flags from a string. */ diff --git a/src/api.cc b/src/api.cc index 0f64dd45ec..07d9eb0ac7 100644 --- a/src/api.cc +++ b/src/api.cc @@ -1438,6 +1438,22 @@ v8::Handle Message::GetScriptData() const { } +v8::Handle Message::GetStackTrace() const { + if (IsDeadCheck("v8::Message::GetStackTrace()")) { + return Local(); + } + ENTER_V8; + HandleScope scope; + i::Handle obj = + i::Handle::cast(Utils::OpenHandle(this)); + i::Handle stackFramesObj = GetProperty(obj, "stackFrames"); + if (!stackFramesObj->IsJSArray()) return v8::Handle(); + i::Handle stackTrace = + i::Handle::cast(stackFramesObj); + return scope.Close(Utils::StackTraceToLocal(stackTrace)); +} + + static i::Handle CallV8HeapFunction(const char* name, i::Handle recv, int argc, @@ -1583,7 +1599,9 @@ Local StackTrace::CurrentStackTrace(int frame_limit, StackTraceOptions options) { if (IsDeadCheck("v8::StackTrace::CurrentStackTrace()")) Local(); ENTER_V8; - return i::Top::CaptureCurrentStackTrace(frame_limit, options); + i::Handle stackTrace = + i::Top::CaptureCurrentStackTrace(frame_limit, options); + return Utils::StackTraceToLocal(stackTrace); } @@ -3782,6 +3800,17 @@ void V8::RemoveMessageListeners(MessageCallback that) { } +void V8::SetCaptureStackTraceForUncaughtExceptions( + bool capture, + int frame_limit, + StackTrace::StackTraceOptions options) { + i::Top::SetCaptureStackTraceForUncaughtExceptions( + capture, + frame_limit, + options); +} + + void V8::SetCounterFunction(CounterLookupCallback callback) { if (IsDeadCheck("v8::V8::SetCounterFunction()")) return; i::StatsTable::SetCounterFunction(callback); diff --git a/src/debug.cc b/src/debug.cc index b8e0252aae..4922a62860 100644 --- a/src/debug.cc +++ b/src/debug.cc @@ -759,7 +759,7 @@ bool Debug::CompileDebuggerScript(int index) { if (caught_exception) { Handle message = MessageHandler::MakeMessageObject( "error_loading_debugger", NULL, Vector >::empty(), - Handle()); + Handle(), Handle()); MessageHandler::ReportMessage(NULL, message); return false; } diff --git a/src/messages.cc b/src/messages.cc index 7cb1d20227..ec91cc8779 100644 --- a/src/messages.cc +++ b/src/messages.cc @@ -66,7 +66,8 @@ Handle MessageHandler::MakeMessageObject( const char* type, MessageLocation* loc, Vector< Handle > args, - Handle stack_trace) { + Handle stack_trace, + Handle stack_frames) { // Build error message object v8::HandleScope scope; // Instantiate a closeable HandleScope for EscapeFrom. Handle type_str = Factory::LookupAsciiSymbol(type); @@ -90,13 +91,17 @@ Handle MessageHandler::MakeMessageObject( Handle stack_trace_val = stack_trace.is_null() ? Factory::undefined_value() : Handle::cast(stack_trace); - const int argc = 6; + Handle stack_frames_val = stack_frames.is_null() + ? Factory::undefined_value() + : Handle::cast(stack_frames); + const int argc = 7; Object** argv[argc] = { type_str.location(), array.location(), start_handle.location(), end_handle.location(), script.location(), - stack_trace_val.location() }; + stack_trace_val.location(), + stack_frames_val.location() }; // Setup a catch handler to catch exceptions in creating the message. This // handler is non-verbose to avoid calling MakeMessage recursively in case of diff --git a/src/messages.h b/src/messages.h index 80ce8eb9ca..440bde87e9 100644 --- a/src/messages.h +++ b/src/messages.h @@ -96,7 +96,8 @@ class MessageHandler { static Handle MakeMessageObject(const char* type, MessageLocation* loc, Vector< Handle > args, - Handle stack_trace); + Handle stack_trace, + Handle stack_frames); // Report a formatted message (needs JS allocation). static void ReportMessage(MessageLocation* loc, Handle message); diff --git a/src/messages.js b/src/messages.js index 99ba45464f..e99d864334 100644 --- a/src/messages.js +++ b/src/messages.js @@ -601,18 +601,22 @@ function GetPositionInLine(message) { } -function ErrorMessage(type, args, startPos, endPos, script, stackTrace) { +function ErrorMessage(type, args, startPos, endPos, script, stackTrace, + stackFrames) { this.startPos = startPos; this.endPos = endPos; this.type = type; this.args = args; this.script = script; this.stackTrace = stackTrace; + this.stackFrames = stackFrames; } -function MakeMessage(type, args, startPos, endPos, script, stackTrace) { - return new ErrorMessage(type, args, startPos, endPos, script, stackTrace); +function MakeMessage(type, args, startPos, endPos, script, stackTrace, + stackFrames) { + return new ErrorMessage(type, args, startPos, endPos, script, stackTrace, + stackFrames); } diff --git a/src/top.cc b/src/top.cc index 516ec67496..2887b7664f 100644 --- a/src/top.cc +++ b/src/top.cc @@ -44,6 +44,11 @@ Mutex* Top::break_access_ = OS::CreateMutex(); NoAllocationStringAllocator* preallocated_message_space = NULL; +bool capture_stack_trace_for_uncaught_exceptions = false; +int stack_trace_for_uncaught_exceptions_frame_limit = 0; +StackTrace::StackTraceOptions stack_trace_for_uncaught_exceptions_options = + StackTrace::kOverview; + Address top_addresses[] = { #define C(name) reinterpret_cast
(Top::name()), TOP_ADDRESS_LIST(C) @@ -365,9 +370,8 @@ Handle Top::StackTraceString() { } -Local Top::CaptureCurrentStackTrace( +Handle Top::CaptureCurrentStackTrace( int frame_limit, StackTrace::StackTraceOptions options) { - v8::HandleScope scope; // Ensure no negative values. int limit = Max(frame_limit, 0); Handle stack_trace = Factory::NewJSArray(frame_limit); @@ -443,7 +447,7 @@ Local Top::CaptureCurrentStackTrace( } stack_trace->set_length(Smi::FromInt(frames_seen)); - return scope.Close(Utils::StackTraceToLocal(stack_trace)); + return stack_trace; } @@ -681,10 +685,7 @@ Failure* Top::StackOverflow() { // TODO(1240995): To avoid having to call JavaScript code to compute // the message for stack overflow exceptions which is very likely to // double fault with another stack overflow exception, we use a - // precomputed message. This is somewhat problematic in that it - // doesn't use ReportUncaughtException to determine the location - // from where the exception occurred. It should probably be - // reworked. + // precomputed message. DoThrow(*exception, NULL, kStackOverflowMessage); return Failure::Exception(); } @@ -778,25 +779,6 @@ void Top::ComputeLocation(MessageLocation* target) { } -void Top::ReportUncaughtException(Handle exception, - MessageLocation* location, - Handle stack_trace) { - Handle message; - if (!Bootstrapper::IsActive()) { - // It's not safe to try to make message objects while the bootstrapper - // is active since the infrastructure may not have been properly - // initialized. - message = - MessageHandler::MakeMessageObject("uncaught_exception", - location, - HandleVector(&exception, 1), - stack_trace); - } - // Report the uncaught exception. - MessageHandler::ReportMessage(location, message); -} - - bool Top::ShouldReturnException(bool* is_caught_externally, bool catchable_by_javascript) { // Find the top-most try-catch handler. @@ -869,8 +851,15 @@ void Top::DoThrow(Object* exception, // may not have been properly initialized. Handle stack_trace; if (FLAG_trace_exception) stack_trace = StackTraceString(); + Handle stack_trace_object; + if (report_exception && capture_stack_trace_for_uncaught_exceptions) { + stack_trace_object = Top::CaptureCurrentStackTrace( + stack_trace_for_uncaught_exceptions_frame_limit, + stack_trace_for_uncaught_exceptions_options); + } message_obj = MessageHandler::MakeMessageObject("uncaught_exception", - location, HandleVector(&exception_handle, 1), stack_trace); + location, HandleVector(&exception_handle, 1), stack_trace, + stack_trace_object); } } @@ -997,6 +986,16 @@ bool Top::OptionalRescheduleException(bool is_bottom_call) { } +void Top::SetCaptureStackTraceForUncaughtExceptions( + bool capture, + int frame_limit, + StackTrace::StackTraceOptions options) { + capture_stack_trace_for_uncaught_exceptions = capture; + stack_trace_for_uncaught_exceptions_frame_limit = frame_limit; + stack_trace_for_uncaught_exceptions_options = options; +} + + bool Top::is_out_of_memory() { if (has_pending_exception()) { Object* e = pending_exception(); diff --git a/src/top.h b/src/top.h index 4a76a7f87d..87333931eb 100644 --- a/src/top.h +++ b/src/top.h @@ -227,6 +227,11 @@ class Top { (try_catch_handler() == thread_local_.catcher_); } + static void SetCaptureStackTraceForUncaughtExceptions( + bool capture, + int frame_limit, + StackTrace::StackTraceOptions options); + // Tells whether the current context has experienced an out of memory // exception. static bool is_out_of_memory(); @@ -266,7 +271,7 @@ class Top { static void PrintStack(StringStream* accumulator); static void PrintStack(); static Handle StackTraceString(); - static Local CaptureCurrentStackTrace( + static Handle CaptureCurrentStackTrace( int frame_limit, StackTrace::StackTraceOptions options); @@ -302,9 +307,6 @@ class Top { const char* message); static bool ShouldReturnException(bool* is_caught_externally, bool catchable_by_javascript); - static void ReportUncaughtException(Handle exception, - MessageLocation* location, - Handle stack_trace); // Attempts to compute the current source location, storing the // result in the target out parameter. diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 330ca5bcd1..20f8eaec43 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -10350,6 +10350,40 @@ THREADED_TEST(CaptureStackTrace) { } +static void StackTraceForUncaughtExceptionListener( + v8::Handle message, + v8::Handle) { + v8::Handle stack_trace = message->GetStackTrace(); + CHECK_EQ(2, stack_trace->GetFrameCount()); + checkStackFrame("origin", "foo", 2, 3, false, false, + stack_trace->GetFrame(0)); + checkStackFrame("origin", "bar", 5, 3, false, false, + stack_trace->GetFrame(1)); +} + +TEST(CaptureStackTraceForUncaughtException) { + report_count = 0; + v8::HandleScope scope; + LocalContext env; + v8::V8::AddMessageListener(StackTraceForUncaughtExceptionListener); + v8::V8::SetCaptureStackTraceForUncaughtExceptions(true); + + Script::Compile(v8_str("function foo() {\n" + " throw 1;\n" + "};\n" + "function bar() {\n" + " foo();\n" + "};"), + v8_str("origin"))->Run(); + v8::Local global = env->Global(); + Local trouble = global->Get(v8_str("bar")); + CHECK(trouble->IsFunction()); + Function::Cast(*trouble)->Call(global, 0, NULL); + v8::V8::SetCaptureStackTraceForUncaughtExceptions(false); + v8::V8::RemoveMessageListeners(StackTraceForUncaughtExceptionListener); +} + + // Test that idle notification can be handled and eventually returns true. THREADED_TEST(IdleNotification) { bool rv = false;