Skip to content

Commit

Permalink
inspector: display error when ToggleAsyncHook fails
Browse files Browse the repository at this point in the history
This patch refactors `AppendExceptionLine` and `PrintSyncTrace`
to reuse the error formatting logic and use them to print
uncaught error in ``ToggleAsyncHook`

PR-URL: nodejs#26859
Refs: nodejs#26798
Reviewed-By: Matteo Collina <[email protected]>
  • Loading branch information
joyeecheung committed Mar 30, 2019
1 parent 7aad63b commit 61ef9df
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 88 deletions.
47 changes: 6 additions & 41 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,10 @@ using v8::HandleScope;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::Message;
using v8::NewStringType;
using v8::Number;
using v8::Object;
using v8::Private;
using v8::StackFrame;
using v8::StackTrace;
using v8::String;
using v8::Symbol;
Expand Down Expand Up @@ -463,48 +461,15 @@ void Environment::StopProfilerIdleNotifier() {
}

void Environment::PrintSyncTrace() const {
if (!options_->trace_sync_io)
return;
if (!options_->trace_sync_io) return;

HandleScope handle_scope(isolate());
Local<StackTrace> stack =
StackTrace::CurrentStackTrace(isolate(), 10, StackTrace::kDetailed);

fprintf(stderr, "(node:%d) WARNING: Detected use of sync API\n",
uv_os_getpid());

for (int i = 0; i < stack->GetFrameCount() - 1; i++) {
Local<StackFrame> stack_frame = stack->GetFrame(isolate(), i);
node::Utf8Value fn_name_s(isolate(), stack_frame->GetFunctionName());
node::Utf8Value script_name(isolate(), stack_frame->GetScriptName());
const int line_number = stack_frame->GetLineNumber();
const int column = stack_frame->GetColumn();

if (stack_frame->IsEval()) {
if (stack_frame->GetScriptId() == Message::kNoScriptIdInfo) {
fprintf(stderr, " at [eval]:%i:%i\n", line_number, column);
} else {
fprintf(stderr,
" at [eval] (%s:%i:%i)\n",
*script_name,
line_number,
column);
}
break;
}

if (fn_name_s.length() == 0) {
fprintf(stderr, " at %s:%i:%i\n", *script_name, line_number, column);
} else {
fprintf(stderr,
" at %s (%s:%i:%i)\n",
*fn_name_s,
*script_name,
line_number,
column);
}
}
fflush(stderr);
fprintf(
stderr, "(node:%d) WARNING: Detected use of sync API\n", uv_os_getpid());
PrintStackTrace(
isolate(),
StackTrace::CurrentStackTrace(isolate(), 10, StackTrace::kDetailed));
}

void Environment::RunCleanup() {
Expand Down
11 changes: 6 additions & 5 deletions src/inspector_agent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -839,11 +839,12 @@ void Agent::ToggleAsyncHook(Isolate* isolate,
HandleScope handle_scope(isolate);
CHECK(!fn.IsEmpty());
auto context = parent_env_->context();
auto result = fn.Get(isolate)->Call(context, Undefined(isolate), 0, nullptr);
if (result.IsEmpty()) {
FatalError(
"node::inspector::Agent::ToggleAsyncHook",
"Cannot toggle Inspector's AsyncHook, please report this.");
v8::TryCatch try_catch(isolate);
USE(fn.Get(isolate)->Call(context, Undefined(isolate), 0, nullptr));
if (try_catch.HasCaught()) {
PrintCaughtException(isolate, context, try_catch);
FatalError("\nnode::inspector::Agent::ToggleAsyncHook",
"Cannot toggle Inspector's AsyncHook, please report this.");
}
}

Expand Down
154 changes: 112 additions & 42 deletions src/node_errors.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Message;
using v8::NewStringType;
using v8::Number;
using v8::Object;
using v8::ScriptOrigin;
using v8::StackFrame;
using v8::StackTrace;
using v8::String;
using v8::Undefined;
using v8::Value;
Expand All @@ -44,30 +45,20 @@ namespace per_process {
static Mutex tty_mutex;
} // namespace per_process

void AppendExceptionLine(Environment* env,
Local<Value> er,
Local<Message> message,
enum ErrorHandlingMode mode) {
if (message.IsEmpty()) return;
static const int kMaxErrorSourceLength = 1024;

HandleScope scope(env->isolate());
Local<Object> err_obj;
if (!er.IsEmpty() && er->IsObject()) {
err_obj = er.As<Object>();
}
static std::string GetErrorSource(Isolate* isolate,
Local<Context> context,
Local<Message> message,
bool* added_exception_line) {
MaybeLocal<String> source_line_maybe = message->GetSourceLine(context);
node::Utf8Value encoded_source(isolate, source_line_maybe.ToLocalChecked());
std::string sourceline(*encoded_source, encoded_source.length());

// Print (filename):(line number): (message).
ScriptOrigin origin = message->GetScriptOrigin();
node::Utf8Value filename(env->isolate(), message->GetScriptResourceName());
const char* filename_string = *filename;
int linenum = message->GetLineNumber(env->context()).FromJust();
// Print line of source code.
MaybeLocal<String> source_line_maybe = message->GetSourceLine(env->context());
node::Utf8Value sourceline(env->isolate(),
source_line_maybe.ToLocalChecked());
const char* sourceline_string = *sourceline;
if (strstr(sourceline_string, "node-do-not-add-exception-line") != nullptr)
return;
if (sourceline.find("node-do-not-add-exception-line") != std::string::npos) {
*added_exception_line = false;
return sourceline;
}

// Because of how node modules work, all scripts are wrapped with a
// "function (module, exports, __filename, ...) {"
Expand All @@ -90,53 +81,131 @@ void AppendExceptionLine(Environment* env,
// sourceline to 78 characters, and we end up not providing very much
// useful debugging info to the user if we remove 62 characters.

// Print (filename):(line number): (message).
ScriptOrigin origin = message->GetScriptOrigin();
node::Utf8Value filename(isolate, message->GetScriptResourceName());
const char* filename_string = *filename;
int linenum = message->GetLineNumber(context).FromJust();

int script_start = (linenum - origin.ResourceLineOffset()->Value()) == 1
? origin.ResourceColumnOffset()->Value()
: 0;
int start = message->GetStartColumn(env->context()).FromMaybe(0);
int end = message->GetEndColumn(env->context()).FromMaybe(0);
int start = message->GetStartColumn(context).FromMaybe(0);
int end = message->GetEndColumn(context).FromMaybe(0);
if (start >= script_start) {
CHECK_GE(end, start);
start -= script_start;
end -= script_start;
}

char arrow[1024];
int max_off = sizeof(arrow) - 2;
int max_off = kMaxErrorSourceLength - 2;

int off = snprintf(arrow,
sizeof(arrow),
char buf[kMaxErrorSourceLength];
int off = snprintf(buf,
kMaxErrorSourceLength,
"%s:%i\n%s\n",
filename_string,
linenum,
sourceline_string);
sourceline.c_str());
CHECK_GE(off, 0);
if (off > max_off) {
off = max_off;
}

// Print wavy underline (GetUnderline is deprecated).
for (int i = 0; i < start; i++) {
if (sourceline_string[i] == '\0' || off >= max_off) {
if (sourceline[i] == '\0' || off >= max_off) {
break;
}
CHECK_LT(off, max_off);
arrow[off++] = (sourceline_string[i] == '\t') ? '\t' : ' ';
buf[off++] = (sourceline[i] == '\t') ? '\t' : ' ';
}
for (int i = start; i < end; i++) {
if (sourceline_string[i] == '\0' || off >= max_off) {
if (sourceline[i] == '\0' || off >= max_off) {
break;
}
CHECK_LT(off, max_off);
arrow[off++] = '^';
buf[off++] = '^';
}
CHECK_LE(off, max_off);
arrow[off] = '\n';
arrow[off + 1] = '\0';
buf[off] = '\n';
buf[off + 1] = '\0';

*added_exception_line = true;
return std::string(buf);
}

void PrintStackTrace(Isolate* isolate, Local<StackTrace> stack) {
for (int i = 0; i < stack->GetFrameCount() - 1; i++) {
Local<StackFrame> stack_frame = stack->GetFrame(isolate, i);
node::Utf8Value fn_name_s(isolate, stack_frame->GetFunctionName());
node::Utf8Value script_name(isolate, stack_frame->GetScriptName());
const int line_number = stack_frame->GetLineNumber();
const int column = stack_frame->GetColumn();

if (stack_frame->IsEval()) {
if (stack_frame->GetScriptId() == Message::kNoScriptIdInfo) {
fprintf(stderr, " at [eval]:%i:%i\n", line_number, column);
} else {
fprintf(stderr,
" at [eval] (%s:%i:%i)\n",
*script_name,
line_number,
column);
}
break;
}

if (fn_name_s.length() == 0) {
fprintf(stderr, " at %s:%i:%i\n", *script_name, line_number, column);
} else {
fprintf(stderr,
" at %s (%s:%i:%i)\n",
*fn_name_s,
*script_name,
line_number,
column);
}
}
fflush(stderr);
}

void PrintCaughtException(Isolate* isolate,
Local<Context> context,
const v8::TryCatch& try_catch) {
CHECK(try_catch.HasCaught());
Local<Value> err = try_catch.Exception();
Local<Message> message = try_catch.Message();
Local<v8::StackTrace> stack = message->GetStackTrace();
node::Utf8Value reason(isolate,
err->ToDetailString(context).ToLocalChecked());
bool added_exception_line = false;
std::string source =
GetErrorSource(isolate, context, message, &added_exception_line);
fprintf(stderr, "%s\n", source.c_str());
fprintf(stderr, "%s\n", *reason);
PrintStackTrace(isolate, stack);
}

void AppendExceptionLine(Environment* env,
Local<Value> er,
Local<Message> message,
enum ErrorHandlingMode mode) {
if (message.IsEmpty()) return;

HandleScope scope(env->isolate());
Local<Object> err_obj;
if (!er.IsEmpty() && er->IsObject()) {
err_obj = er.As<Object>();
}

Local<String> arrow_str =
String::NewFromUtf8(env->isolate(), arrow, NewStringType::kNormal)
.ToLocalChecked();
bool added_exception_line = false;
std::string source = GetErrorSource(
env->isolate(), env->context(), message, &added_exception_line);
if (!added_exception_line) {
return;
}
MaybeLocal<Value> arrow_str = ToV8Value(env->context(), source);

const bool can_set_arrow = !arrow_str.IsEmpty() && !err_obj.IsEmpty();
// If allocating arrow_str failed, print it out. There's not much else to do.
Expand All @@ -150,13 +219,14 @@ void AppendExceptionLine(Environment* env,
env->set_printed_error(true);

uv_tty_reset_mode();
PrintErrorString("\n%s", arrow);
PrintErrorString("\n%s", source.c_str());
return;
}

CHECK(err_obj
->SetPrivate(
env->context(), env->arrow_message_private_symbol(), arrow_str)
->SetPrivate(env->context(),
env->arrow_message_private_symbol(),
arrow_str.ToLocalChecked())
.FromMaybe(false));
}

Expand Down
5 changes: 5 additions & 0 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ void GetSockOrPeerName(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(err);
}

void PrintStackTrace(v8::Isolate* isolate, v8::Local<v8::StackTrace> stack);
void PrintCaughtException(v8::Isolate* isolate,
v8::Local<v8::Context> context,
const v8::TryCatch& try_catch);

void WaitForInspectorDisconnect(Environment* env);
void SignalExit(int signo);
#ifdef __POSIX__
Expand Down

0 comments on commit 61ef9df

Please sign in to comment.