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:
Родитель
6cbfcdad46
Коммит
f1de13b8ab
|
@ -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 ## _;
|
||||
|
|
196
src/node.cc
196
src/node.cc
|
@ -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));
|
||||
});
|
Загрузка…
Ссылка в новой задаче