Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

node: do not print SyntaxError hints to stderr #7049

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ namespace node {
V(ipv4_string, "IPv4") \
V(ipv6_string, "IPv6") \
V(issuer_string, "issuer") \
V(message_string, "message") \
V(method_string, "method") \
V(mode_string, "mode") \
V(modulus_string, "modulus") \
Expand All @@ -111,6 +112,7 @@ namespace node {
V(onstop_string, "onstop") \
V(path_string, "path") \
V(port_string, "port") \
V(processed_string, "processed") \
V(rdev_string, "rdev") \
V(rename_string, "rename") \
V(rss_string, "rss") \
Expand All @@ -122,6 +124,7 @@ namespace node {
V(smalloc_p_string, "_smalloc_p") \
V(sni_context_err_string, "Invalid SNI context") \
V(sni_context_string, "sni_context") \
V(stack_string, "stack") \
V(status_code_string, "statusCode") \
V(status_message_string, "statusMessage") \
V(subject_string, "subject") \
Expand Down
197 changes: 120 additions & 77 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1268,80 +1268,121 @@ ssize_t DecodeWrite(char *buf,
return StringBytes::Write(buf, buflen, val, encoding, NULL);
}

void DisplayExceptionLine(Handle<Message> message) {
// Prevent re-entry into this function. For example, if there is
// a throw from a program in vm.runInThisContext(code, filename, true),
// then we want to show the original failure, not the secondary one.
static bool displayed_error = false;

if (displayed_error)
void AppendExceptionLine(Environment* env,
Handle<Value> er,
Handle<Message> message) {
static bool printed_error = false;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case NODE_MODULE_CONTEXT_AWARE() is used, should the error for one script propagate across all the others?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean, yeah better do it as an env property.

if (message.IsEmpty())
return;
displayed_error = true;

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

fprintf(stderr, "\n");

if (!message.IsEmpty()) {
// Print (filename):(line number): (message).
String::Utf8Value filename(message->GetScriptResourceName());
const char* filename_string = *filename;
int linenum = message->GetLineNumber();
fprintf(stderr, "%s:%i\n", filename_string, linenum);
// Print line of source code.
String::Utf8Value sourceline(message->GetSourceLine());
const char* sourceline_string = *sourceline;

// Because of how node modules work, all scripts are wrapped with a
// "function (module, exports, __filename, ...) {"
// to provide script local variables.
//
// When reporting errors on the first line of a script, this wrapper
// function is leaked to the user. There used to be a hack here to
// truncate off the first 62 characters, but it caused numerous other
// problems when vm.runIn*Context() methods were used for non-module
// code.
//
// If we ever decide to re-instate such a hack, the following steps
// must be taken:
//
// 1. Pass a flag around to say "this code was wrapped"
// 2. Update the stack frame output so that it is also correct.
//
// It would probably be simpler to add a line rather than add some
// number of characters to the first line, since V8 truncates the
// sourceline to 78 characters, and we end up not providing very much
// useful debugging info to the user if we remove 62 characters.

int start = message->GetStartColumn();
int end = message->GetEndColumn();

fprintf(stderr, "%s\n", sourceline_string);
// Print wavy underline (GetUnderline is deprecated).
for (int i = 0; i < start; i++) {
fputc((sourceline_string[i] == '\t') ? '\t' : ' ', stderr);
}
for (int i = start; i < end; i++) {
fputc('^', stderr);
}
fputc('\n', stderr);
// Do it only once per message
if (!err_obj->GetHiddenValue(env->processed_string()).IsEmpty())
return;
err_obj->SetHiddenValue(env->processed_string(), True(env->isolate()));
}

static char arrow[1024];

// Print (filename):(line number): (message).
String::Utf8Value filename(message->GetScriptResourceName());
const char* filename_string = *filename;
int linenum = message->GetLineNumber();
// Print line of source code.
String::Utf8Value sourceline(message->GetSourceLine());
const char* sourceline_string = *sourceline;

// Because of how node modules work, all scripts are wrapped with a
// "function (module, exports, __filename, ...) {"
// to provide script local variables.
//
// When reporting errors on the first line of a script, this wrapper
// function is leaked to the user. There used to be a hack here to
// truncate off the first 62 characters, but it caused numerous other
// problems when vm.runIn*Context() methods were used for non-module
// code.
//
// If we ever decide to re-instate such a hack, the following steps
// must be taken:
//
// 1. Pass a flag around to say "this code was wrapped"
// 2. Update the stack frame output so that it is also correct.
//
// It would probably be simpler to add a line rather than add some
// number of characters to the first line, since V8 truncates the
// sourceline to 78 characters, and we end up not providing very much
// useful debugging info to the user if we remove 62 characters.

int start = message->GetStartColumn();
int end = message->GetEndColumn();

int off = snprintf(arrow,
sizeof(arrow),
"%s:%i\n%s\n",
filename_string,
linenum,
sourceline_string);
assert(off >= 0);

// Print wavy underline (GetUnderline is deprecated).
for (int i = 0; i < start; i++) {
assert(static_cast<size_t>(off) < sizeof(arrow));
arrow[off++] = (sourceline_string[i] == '\t') ? '\t' : ' ';
}
for (int i = start; i < end; i++) {
assert(static_cast<size_t>(off) < sizeof(arrow));
arrow[off++] = '^';
}
assert(static_cast<size_t>(off) < sizeof(arrow) - 1);
arrow[off++] = '\n';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is as it was before your change, but isn't using '\n' platform specific? Meaning, won't this cause bad output on win32?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea, guess it is displayed properly as we haven't seen any issues about it yet.

arrow[off] = '\0';

Local<String> arrow_str = String::NewFromUtf8(env->isolate(), arrow);
Local<Value> msg;
Local<Value> stack;

// Allocation failed, just print it out
if (arrow_str.IsEmpty() || err_obj.IsEmpty() || !err_obj->IsNativeError())
goto print;

msg = err_obj->Get(env->message_string());
stack = err_obj->Get(env->stack_string());

if (msg.IsEmpty() || stack.IsEmpty())
goto print;

err_obj->Set(env->message_string(),
String::Concat(arrow_str, msg->ToString()));
err_obj->Set(env->stack_string(),
String::Concat(arrow_str, stack->ToString()));
return;

print:
if (printed_error)
return;
printed_error = true;
uv_tty_reset_mode();
fprintf(stderr, "\n%s", arrow);
}


static void ReportException(Handle<Value> er, Handle<Message> message) {
HandleScope scope(node_isolate);
static void ReportException(Environment* env,
Handle<Value> er,
Handle<Message> message) {
HandleScope scope(env->isolate());

DisplayExceptionLine(message);
AppendExceptionLine(env, er, message);

Local<Value> trace_value;

if (er->IsUndefined() || er->IsNull()) {
trace_value = Undefined(node_isolate);
} else {
trace_value =
er->ToObject()->Get(FIXED_ONE_BYTE_STRING(node_isolate, "stack"));
}
if (er->IsUndefined() || er->IsNull())
trace_value = Undefined(env->isolate());
else
trace_value = er->ToObject()->Get(env->stack_string());

String::Utf8Value trace(trace_value);

Expand All @@ -1357,8 +1398,8 @@ static void ReportException(Handle<Value> er, Handle<Message> message) {

if (er->IsObject()) {
Local<Object> err_obj = er.As<Object>();
message = err_obj->Get(FIXED_ONE_BYTE_STRING(node_isolate, "message"));
name = err_obj->Get(FIXED_ONE_BYTE_STRING(node_isolate, "name"));
message = err_obj->Get(env->message_string());
name = err_obj->Get(FIXED_ONE_BYTE_STRING(env->isolate(), "name"));
}

if (message.IsEmpty() ||
Expand All @@ -1379,14 +1420,16 @@ static void ReportException(Handle<Value> er, Handle<Message> message) {
}


static void ReportException(const TryCatch& try_catch) {
ReportException(try_catch.Exception(), try_catch.Message());
static void ReportException(Environment* env, const TryCatch& try_catch) {
ReportException(env, try_catch.Exception(), try_catch.Message());
}


// Executes a str within the current v8 context.
Local<Value> ExecuteString(Handle<String> source, Handle<Value> filename) {
HandleScope scope(node_isolate);
static Local<Value> ExecuteString(Environment* env,
Handle<String> source,
Handle<Value> filename) {
HandleScope scope(env->isolate());
TryCatch try_catch;

// try_catch must be nonverbose to disable FatalException() handler,
Expand All @@ -1395,13 +1438,13 @@ Local<Value> ExecuteString(Handle<String> source, Handle<Value> filename) {

Local<v8::Script> script = v8::Script::Compile(source, filename);
if (script.IsEmpty()) {
ReportException(try_catch);
ReportException(env, try_catch);
exit(3);
}

Local<Value> result = script->Run();
if (result.IsEmpty()) {
ReportException(try_catch);
ReportException(env, try_catch);
exit(4);
}

Expand Down Expand Up @@ -2018,7 +2061,7 @@ void FatalException(Handle<Value> error, Handle<Message> message) {
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(error, message);
ReportException(env, error, message);
exit(6);
}

Expand All @@ -2033,12 +2076,12 @@ void FatalException(Handle<Value> error, Handle<Message> message) {

if (fatal_try_catch.HasCaught()) {
// the fatal exception function threw, so we must exit
ReportException(fatal_try_catch);
ReportException(env, fatal_try_catch);
exit(7);
}

if (false == caught->BooleanValue()) {
ReportException(error, message);
ReportException(env, error, message);
exit(1);
}
}
Expand Down Expand Up @@ -2698,10 +2741,10 @@ void Load(Environment* env) {
// are not safe to ignore.
try_catch.SetVerbose(false);

Local<String> script_name = FIXED_ONE_BYTE_STRING(node_isolate, "node.js");
Local<Value> f_value = ExecuteString(MainSource(), script_name);
Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(), "node.js");
Local<Value> f_value = ExecuteString(env, MainSource(), script_name);
if (try_catch.HasCaught()) {
ReportException(try_catch);
ReportException(env, try_catch);
exit(10);
}
assert(f_value->IsFunction());
Expand Down
1 change: 0 additions & 1 deletion src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ enum encoding {ASCII, UTF8, BASE64, UCS2, BINARY, HEX, BUFFER};
enum encoding ParseEncoding(v8::Handle<v8::Value> encoding_v,
enum encoding _default = BINARY);
NODE_EXTERN void FatalException(const v8::TryCatch& try_catch);
void DisplayExceptionLine(v8::Handle<v8::Message> message);

NODE_EXTERN v8::Local<v8::Value> Encode(const void *buf, size_t len,
enum encoding encoding = BINARY);
Expand Down
4 changes: 2 additions & 2 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ class ContextifyScript : public BaseObject {

if (v8_script.IsEmpty()) {
if (display_errors) {
DisplayExceptionLine(try_catch.Message());
AppendExceptionLine(env, try_catch.Exception(), try_catch.Message());
}
try_catch.ReThrow();
return;
Expand Down Expand Up @@ -622,7 +622,7 @@ class ContextifyScript : public BaseObject {
if (result.IsEmpty()) {
// Error occurred during execution of the script.
if (display_errors) {
DisplayExceptionLine(try_catch.Message());
AppendExceptionLine(env, try_catch.Exception(), try_catch.Message());
}
try_catch.ReThrow();
return false;
Expand Down
4 changes: 4 additions & 0 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ inline static void ThrowUVException(int errorno,
v8::ThrowException(UVException(errorno, syscall, message, path));
}

void AppendExceptionLine(Environment* env,
v8::Handle<v8::Value> er,
v8::Handle<v8::Message> message);

NO_RETURN void FatalError(const char* location, const char* message);

v8::Local<v8::Object> BuildStatsObject(Environment* env, const uv_stat_t* s);
Expand Down
6 changes: 0 additions & 6 deletions test/message/eval_messages.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[eval]

[eval]:1
with(this){__filename}
^^^^
Expand All @@ -12,7 +11,6 @@ SyntaxError: Strict mode code may not include a with statement
at node.js:*:*
42
42

[eval]:1
throw new Error("hello")
^
Expand All @@ -24,7 +22,6 @@ Error: hello
at evalScript (node.js:*:*)
at startup (node.js:*:*)
at node.js:*:*

[eval]:1
throw new Error("hello")
^
Expand All @@ -37,7 +34,6 @@ Error: hello
at startup (node.js:*:*)
at node.js:*:*
100

[eval]:1
var x = 100; y = x;
^
Expand All @@ -49,12 +45,10 @@ ReferenceError: y is not defined
at evalScript (node.js:*:*)
at startup (node.js:*:*)
at node.js:*:*

[eval]:1
var ______________________________________________; throw 10
^
10

[eval]:1
var ______________________________________________; throw 10
^
Expand Down
1 change: 0 additions & 1 deletion test/message/throw_custom_error.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
before

*test*message*throw_custom_error.js:31
throw ({ name: 'MyCustomError', message: 'This is a custom message' });
^
Expand Down
1 change: 0 additions & 1 deletion test/message/throw_in_line_with_tabs.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
before

*test*message*throw_in_line_with_tabs.js:32
throw ({ foo: 'bar' });
^
Expand Down
1 change: 0 additions & 1 deletion test/message/throw_non_error.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
before

*test*message*throw_non_error.js:31
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because error was appended to the exception without \n as a first char, I think that generally it is a better approach.

throw ({ foo: 'bar' });
^
Expand Down
Loading