From bfd8265ec22f8df15dadb6a8a2ada65ff38ba491 Mon Sep 17 00:00:00 2001 From: Aleksei Koziatinskii Date: Tue, 9 Aug 2016 19:03:47 -0700 Subject: [PATCH] inspector: add support for uncaught exception To output exception in DevTools console method exceptionThrown should be called on uncaught exception on V8Inspector object. Additionally we need to wait disconnect to provide user way to inspect exception. PR-URL: https://github.com/nodejs/node/pull/8043 Reviewed-By: bnoordhuis - Ben Noordhuis Reviewed-By: jasnell - James M Snell Reviewed-By: ofrobots - Ali Ijaz Sheikh --- src/inspector_agent.cc | 54 ++++++++++++++++++++++++++++++++++++++++++ src/inspector_agent.h | 7 ++++++ src/node.cc | 40 ++++++++++++++++++++----------- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 78fc3719a6..5b6a8f01bb 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -13,6 +13,7 @@ #include "platform/v8_inspector/public/V8Inspector.h" #include "platform/v8_inspector/public/V8InspectorClient.h" #include "platform/v8_inspector/public/V8InspectorSession.h" +#include "platform/v8_inspector/public/V8StackTrace.h" #include "platform/inspector_protocol/FrontendChannel.h" #include "platform/inspector_protocol/String16.h" #include "platform/inspector_protocol/Values.h" @@ -175,6 +176,9 @@ class AgentImpl { bool IsConnected() { return state_ == State::kConnected; } void WaitForDisconnect(); + void FatalException(v8::Local error, + v8::Local message); + private: using MessageQueue = std::vector>; enum class State { kNew, kAccepting, kConnected, kDone, kError }; @@ -334,6 +338,10 @@ class V8NodeInspector : public blink::V8InspectorClient { session_->dispatchProtocolMessage(message); } + blink::V8Inspector* inspector() { + return inspector_.get(); + } + private: AgentImpl* agent_; v8::Isolate* isolate_; @@ -495,6 +503,46 @@ void AgentImpl::InstallInspectorOnProcess() { env->SetMethod(inspector, "wrapConsoleCall", InspectorWrapConsoleCall); } +String16 ToProtocolString(v8::Local value) { + if (value.IsEmpty() || value->IsNull() || value->IsUndefined() || + !value->IsString()) { + return String16(); + } + v8::Local string_value = v8::Local::Cast(value); + wstring buffer(string_value->Length(), '\0'); + string_value->Write(&buffer[0], 0, string_value->Length()); + return String16(buffer); +} + +void AgentImpl::FatalException(v8::Local error, + v8::Local message) { + if (!IsStarted()) + return; + auto env = parent_env_; + v8::Local context = env->context(); + + int script_id = message->GetScriptOrigin().ScriptID()->Value(); + std::unique_ptr stack_trace = + inspector_->inspector()->createStackTrace(message->GetStackTrace()); + + if (stack_trace && !stack_trace->isEmpty() && + String16::fromInteger(script_id) == stack_trace->topScriptId()) { + script_id = 0; + } + + inspector_->inspector()->exceptionThrown( + context, + "Uncaught", + error, + ToProtocolString(message->Get()), + ToProtocolString(message->GetScriptResourceName()), + message->GetLineNumber(context).FromMaybe(0), + message->GetStartColumn(context).FromMaybe(0), + std::move(stack_trace), + script_id); + WaitForDisconnect(); +} + // static void AgentImpl::ThreadCbIO(void* agent) { static_cast(agent)->WorkerRunIO(); @@ -714,5 +762,11 @@ void Agent::WaitForDisconnect() { impl->WaitForDisconnect(); } +void Agent::FatalException(v8::Local error, + v8::Local message) { + impl->FatalException(error, message); +} + + } // namespace inspector } // namespace node diff --git a/src/inspector_agent.h b/src/inspector_agent.h index f2b2c1a187..43433fdc6e 100644 --- a/src/inspector_agent.h +++ b/src/inspector_agent.h @@ -12,6 +12,10 @@ class Environment; namespace v8 { class Platform; +template +class Local; +class Value; +class Message; } // namespace v8 namespace node { @@ -32,6 +36,9 @@ class Agent { bool IsStarted(); bool IsConnected(); void WaitForDisconnect(); + + void FatalException(v8::Local error, + v8::Local message); private: AgentImpl* impl; }; diff --git a/src/node.cc b/src/node.cc index af35308047..8ba5a678d1 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2520,31 +2520,43 @@ void FatalException(Isolate* isolate, Local fatal_exception_function = process_object->Get(fatal_exception_string).As(); + int exit_code = 0; if (!fatal_exception_function->IsFunction()) { // failed before the process._fatalException function was added! // this is probably pretty bad. Nothing to do but report and exit. ReportException(env, error, message); - exit(6); + exit_code = 6; } - TryCatch fatal_try_catch(isolate); + if (exit_code == 0) { + TryCatch fatal_try_catch(isolate); - // Do not call FatalException when _fatalException handler throws - fatal_try_catch.SetVerbose(false); + // Do not call FatalException when _fatalException handler throws + fatal_try_catch.SetVerbose(false); - // this will return true if the JS layer handled it, false otherwise - Local caught = - fatal_exception_function->Call(process_object, 1, &error); + // this will return true if the JS layer handled it, false otherwise + Local caught = + fatal_exception_function->Call(process_object, 1, &error); - if (fatal_try_catch.HasCaught()) { - // the fatal exception function threw, so we must exit - ReportException(env, fatal_try_catch); - exit(7); + if (fatal_try_catch.HasCaught()) { + // the fatal exception function threw, so we must exit + ReportException(env, fatal_try_catch); + exit_code = 7; + } + + if (exit_code == 0 && false == caught->BooleanValue()) { + ReportException(env, error, message); + exit_code = 1; + } } - if (false == caught->BooleanValue()) { - ReportException(env, error, message); - exit(1); + if (exit_code) { +#if HAVE_INSPECTOR + if (use_inspector) { + env->inspector_agent()->FatalException(error, message); + } +#endif + exit(exit_code); } }