node: do not print SyntaxError hints to stderr

Try embedding the ` ... ^` lines inside the `SyntaxError` (or any other
native error) object before giving up and printing them to the stderr.

fix #6920
fix #1310
This commit is contained in:
Fedor Indutny 2014-02-05 20:38:33 +04:00
Родитель 6cbfcdad46
Коммит f1de13b8ab
11 изменённых файлов: 190 добавлений и 89 удалений

Просмотреть файл

@ -224,6 +224,7 @@ inline Environment::Environment(v8::Local<v8::Context> context)
isolate_data_(IsolateData::GetOrCreate(context->GetIsolate())),
using_smalloc_alloc_cb_(false),
using_domains_(false),
printed_error_(false),
context_(context->GetIsolate(), context) {
// We'll be creating new objects so make sure we've entered the context.
v8::HandleScope handle_scope(isolate());
@ -330,6 +331,14 @@ inline void Environment::set_using_domains(bool value) {
using_domains_ = value;
}
inline bool Environment::printed_error() const {
return printed_error_;
}
inline void Environment::set_printed_error(bool value) {
printed_error_ = value;
}
inline Environment* Environment::from_cares_timer_handle(uv_timer_t* handle) {
return CONTAINER_OF(handle, Environment, cares_timer_handle_);
}

Просмотреть файл

@ -92,6 +92,7 @@ namespace node {
V(ipv6_string, "IPv6") \
V(issuer_string, "issuer") \
V(mark_sweep_compact_string, "mark-sweep-compact") \
V(message_string, "message") \
V(method_string, "method") \
V(mode_string, "mode") \
V(modulus_string, "modulus") \
@ -115,6 +116,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") \
@ -127,6 +129,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") \
@ -302,6 +305,9 @@ class Environment {
inline bool using_domains() const;
inline void set_using_domains(bool value);
inline bool printed_error() const;
inline void set_printed_error(bool value);
// Strings are shared across shared contexts. The getters simply proxy to
// the per-isolate primitive.
#define V(PropertyName, StringValue) \
@ -343,6 +349,7 @@ class Environment {
bool using_smalloc_alloc_cb_;
bool using_domains_;
QUEUE gc_tracker_queue_;
bool printed_error_;
#define V(PropertyName, TypeName) \
v8::Persistent<TypeName> PropertyName ## _;

Просмотреть файл

@ -1275,80 +1275,120 @@ 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) {
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';
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 (env->printed_error())
return;
env->set_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);
@ -1364,8 +1404,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() ||
@ -1386,14 +1426,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,
@ -1402,13 +1444,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);
}
@ -2025,7 +2067,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);
}
@ -2040,12 +2082,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);
}
}
@ -2705,10 +2747,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());

Просмотреть файл

@ -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);

Просмотреть файл

@ -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;
@ -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;

Просмотреть файл

@ -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);

Просмотреть файл

@ -1,5 +1,4 @@
[eval]
[eval]:1
with(this){__filename}
^^^^
@ -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")
^
@ -24,7 +22,6 @@ Error: hello
at evalScript (node.js:*:*)
at startup (node.js:*:*)
at node.js:*:*
[eval]:1
throw new Error("hello")
^
@ -37,7 +34,6 @@ Error: hello
at startup (node.js:*:*)
at node.js:*:*
100
[eval]:1
var x = 100; y = x;
^
@ -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
^

Просмотреть файл

@ -1,5 +1,4 @@
before
*test*message*throw_custom_error.js:31
throw ({ name: 'MyCustomError', message: 'This is a custom message' });
^

Просмотреть файл

@ -1,5 +1,4 @@
before
*test*message*throw_in_line_with_tabs.js:32
throw ({ foo: 'bar' });
^

Просмотреть файл

@ -1,5 +1,4 @@
before
*test*message*throw_non_error.js:31
throw ({ foo: 'bar' });
^

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var path = require('path');
var child_process = require('child_process');
var wrong_script = path.join(common.fixturesDir, 'cert.pem');
var p = child_process.spawn(process.execPath, [
'-e',
'try { require(process.argv[1]); } catch (e) { console.log(e.stack); }',
wrong_script
]);
p.stderr.on('data', function(data) {
assert(false, 'Unexpected stderr data: ' + data);
});
var output = '';
p.stdout.on('data', function(data) {
output += data;
});
process.on('exit', function() {
assert(/BEGIN CERT/.test(output));
assert(/^\s+\^/m.test(output));
assert(/Unexpected identifier/.test(output));
});