Browse Source

module: always decorate thrown errors

This provides more information when encountering a syntax or similar
error when executing a file with require().

Fixes: https://github.com/nodejs/node/issues/4286
PR-URL: https://github.com/nodejs/node/pull/4287
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
process-exit-stdio-flushing
Brian White 9 years ago
parent
commit
18490d3d5a
  1. 10
      lib/internal/util.js
  2. 12
      lib/module.js
  3. 3
      src/env.h
  4. 14
      src/node.cc
  5. 16
      src/node_util.cc
  6. 30
      test/parallel/test-util-decorate-error-stack.js
  7. 20
      test/parallel/test-util-internal.js

10
lib/internal/util.js

@ -4,6 +4,7 @@ const binding = process.binding('util');
const prefix = `(${process.release.name}:${process.pid}) `; const prefix = `(${process.release.name}:${process.pid}) `;
exports.getHiddenValue = binding.getHiddenValue; exports.getHiddenValue = binding.getHiddenValue;
exports.setHiddenValue = binding.setHiddenValue;
// All the internal deprecations have to use this function only, as this will // All the internal deprecations have to use this function only, as this will
// prepend the prefix to the actual message. // prepend the prefix to the actual message.
@ -76,13 +77,16 @@ exports._deprecate = function(fn, msg) {
}; };
exports.decorateErrorStack = function decorateErrorStack(err) { exports.decorateErrorStack = function decorateErrorStack(err) {
if (!(exports.isError(err) && err.stack)) if (!(exports.isError(err) && err.stack) ||
exports.getHiddenValue(err, 'node:decorated') === true)
return; return;
const arrow = exports.getHiddenValue(err, 'arrowMessage'); const arrow = exports.getHiddenValue(err, 'node:arrowMessage');
if (arrow) if (arrow) {
err.stack = arrow + err.stack; err.stack = arrow + err.stack;
exports.setHiddenValue(err, 'node:decorated', true);
}
}; };
exports.isError = function isError(e) { exports.isError = function isError(e) {

12
lib/module.js

@ -23,6 +23,16 @@ function hasOwnProperty(obj, prop) {
} }
function tryWrapper(wrapper, opts) {
try {
return runInThisContext(wrapper, opts);
} catch (e) {
internalUtil.decorateErrorStack(e);
throw e;
}
}
function Module(id, parent) { function Module(id, parent) {
this.id = id; this.id = id;
this.exports = {}; this.exports = {};
@ -371,7 +381,7 @@ Module.prototype._compile = function(content, filename) {
// create wrapper function // create wrapper function
var wrapper = Module.wrap(content); var wrapper = Module.wrap(content);
var compiledWrapper = runInThisContext(wrapper, var compiledWrapper = tryWrapper(wrapper,
{ filename: filename, lineOffset: 0 }); { filename: filename, lineOffset: 0 });
if (global.v8debug) { if (global.v8debug) {
if (!resolvedArgv) { if (!resolvedArgv) {

3
src/env.h

@ -51,7 +51,7 @@ namespace node {
V(alpn_buffer_string, "alpnBuffer") \ V(alpn_buffer_string, "alpnBuffer") \
V(args_string, "args") \ V(args_string, "args") \
V(argv_string, "argv") \ V(argv_string, "argv") \
V(arrow_message_string, "arrowMessage") \ V(arrow_message_string, "node:arrowMessage") \
V(async, "async") \ V(async, "async") \
V(async_queue_string, "_asyncQueue") \ V(async_queue_string, "_asyncQueue") \
V(atime_string, "atime") \ V(atime_string, "atime") \
@ -71,6 +71,7 @@ namespace node {
V(cwd_string, "cwd") \ V(cwd_string, "cwd") \
V(debug_port_string, "debugPort") \ V(debug_port_string, "debugPort") \
V(debug_string, "debug") \ V(debug_string, "debug") \
V(decorated_string, "node:decorated") \
V(dest_string, "dest") \ V(dest_string, "dest") \
V(detached_string, "detached") \ V(detached_string, "detached") \
V(dev_string, "dev") \ V(dev_string, "dev") \

14
src/node.cc

@ -1399,6 +1399,15 @@ ssize_t DecodeWrite(Isolate* isolate,
return StringBytes::Write(isolate, buf, buflen, val, encoding, nullptr); return StringBytes::Write(isolate, buf, buflen, val, encoding, nullptr);
} }
bool IsExceptionDecorated(Environment* env, Local<Value> er) {
if (!er.IsEmpty() && er->IsObject()) {
Local<Object> err_obj = er.As<Object>();
Local<Value> decorated = err_obj->GetHiddenValue(env->decorated_string());
return !decorated.IsEmpty() && decorated->IsTrue();
}
return false;
}
void AppendExceptionLine(Environment* env, void AppendExceptionLine(Environment* env,
Local<Value> er, Local<Value> er,
Local<Message> message) { Local<Message> message) {
@ -1508,6 +1517,7 @@ static void ReportException(Environment* env,
Local<Value> trace_value; Local<Value> trace_value;
Local<Value> arrow; Local<Value> arrow;
const bool decorated = IsExceptionDecorated(env, er);
if (er->IsUndefined() || er->IsNull()) { if (er->IsUndefined() || er->IsNull()) {
trace_value = Undefined(env->isolate()); trace_value = Undefined(env->isolate());
@ -1522,7 +1532,7 @@ static void ReportException(Environment* env,
// range errors have a trace member set to undefined // range errors have a trace member set to undefined
if (trace.length() > 0 && !trace_value->IsUndefined()) { if (trace.length() > 0 && !trace_value->IsUndefined()) {
if (arrow.IsEmpty() || !arrow->IsString()) { if (arrow.IsEmpty() || !arrow->IsString() || decorated) {
PrintErrorString("%s\n", *trace); PrintErrorString("%s\n", *trace);
} else { } else {
node::Utf8Value arrow_string(env->isolate(), arrow); node::Utf8Value arrow_string(env->isolate(), arrow);
@ -1554,7 +1564,7 @@ static void ReportException(Environment* env,
node::Utf8Value name_string(env->isolate(), name); node::Utf8Value name_string(env->isolate(), name);
node::Utf8Value message_string(env->isolate(), message); node::Utf8Value message_string(env->isolate(), message);
if (arrow.IsEmpty() || !arrow->IsString()) { if (arrow.IsEmpty() || !arrow->IsString() || decorated) {
PrintErrorString("%s: %s\n", *name_string, *message_string); PrintErrorString("%s: %s\n", *name_string, *message_string);
} else { } else {
node::Utf8Value arrow_string(env->isolate(), arrow); node::Utf8Value arrow_string(env->isolate(), arrow);

16
src/node_util.cc

@ -52,6 +52,21 @@ static void GetHiddenValue(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(obj->GetHiddenValue(name)); args.GetReturnValue().Set(obj->GetHiddenValue(name));
} }
static void SetHiddenValue(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
if (!args[0]->IsObject())
return env->ThrowTypeError("obj must be an object");
if (!args[1]->IsString())
return env->ThrowTypeError("name must be a string");
Local<Object> obj = args[0].As<Object>();
Local<String> name = args[1].As<String>();
args.GetReturnValue().Set(obj->SetHiddenValue(name, args[2]));
}
void Initialize(Local<Object> target, void Initialize(Local<Object> target,
Local<Value> unused, Local<Value> unused,
@ -63,6 +78,7 @@ void Initialize(Local<Object> target,
#undef V #undef V
env->SetMethod(target, "getHiddenValue", GetHiddenValue); env->SetMethod(target, "getHiddenValue", GetHiddenValue);
env->SetMethod(target, "setHiddenValue", SetHiddenValue);
} }
} // namespace util } // namespace util

30
test/parallel/test-util-decorate-error-stack.js

@ -3,6 +3,8 @@
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const internalUtil = require('internal/util'); const internalUtil = require('internal/util');
const spawnSync = require('child_process').spawnSync;
const path = require('path');
assert.doesNotThrow(function() { assert.doesNotThrow(function() {
internalUtil.decorateErrorStack(); internalUtil.decorateErrorStack();
@ -17,17 +19,37 @@ internalUtil.decorateErrorStack(obj);
assert.strictEqual(obj.stack, undefined); assert.strictEqual(obj.stack, undefined);
// Verify that the stack is decorated when possible // Verify that the stack is decorated when possible
function checkStack(stack) {
const matches = stack.match(/var foo bar;/g);
assert.strictEqual(Array.isArray(matches), true);
assert.strictEqual(matches.length, 1);
}
let err; let err;
const badSyntaxPath =
path.resolve(__dirname, '..', 'fixtures', 'syntax', 'bad_syntax')
.replace(/\\/g, '\\\\');
try { try {
require('../fixtures/syntax/bad_syntax'); require(badSyntaxPath);
} catch (e) { } catch (e) {
err = e; err = e;
assert(!/var foo bar;/.test(err.stack));
internalUtil.decorateErrorStack(err);
} }
assert(/var foo bar;/.test(err.stack)); assert(typeof err, 'object');
checkStack(err.stack);
// Verify that the stack is only decorated once
internalUtil.decorateErrorStack(err);
internalUtil.decorateErrorStack(err);
checkStack(err.stack);
// Verify that the stack is only decorated once for uncaught exceptions
const args = [
'-e',
`require('${badSyntaxPath}')`
];
const result = spawnSync(process.argv[0], args, { encoding: 'utf8' });
checkStack(result.stderr);
// Verify that the stack is unchanged when there is no arrow message // Verify that the stack is unchanged when there is no arrow message
err = new Error('foo'); err = new Error('foo');

20
test/parallel/test-util-internal.js

@ -12,6 +12,12 @@ function getHiddenValue(obj, name) {
}; };
} }
function setHiddenValue(obj, name, val) {
return function() {
internalUtil.setHiddenValue(obj, name, val);
};
}
assert.throws(getHiddenValue(), /obj must be an object/); assert.throws(getHiddenValue(), /obj must be an object/);
assert.throws(getHiddenValue(null, 'foo'), /obj must be an object/); assert.throws(getHiddenValue(null, 'foo'), /obj must be an object/);
assert.throws(getHiddenValue(undefined, 'foo'), /obj must be an object/); assert.throws(getHiddenValue(undefined, 'foo'), /obj must be an object/);
@ -22,12 +28,24 @@ assert.throws(getHiddenValue({}, null), /name must be a string/);
assert.throws(getHiddenValue({}, []), /name must be a string/); assert.throws(getHiddenValue({}, []), /name must be a string/);
assert.deepEqual(internalUtil.getHiddenValue({}, 'foo'), undefined); assert.deepEqual(internalUtil.getHiddenValue({}, 'foo'), undefined);
assert.throws(setHiddenValue(), /obj must be an object/);
assert.throws(setHiddenValue(null, 'foo'), /obj must be an object/);
assert.throws(setHiddenValue(undefined, 'foo'), /obj must be an object/);
assert.throws(setHiddenValue('bar', 'foo'), /obj must be an object/);
assert.throws(setHiddenValue(85, 'foo'), /obj must be an object/);
assert.throws(setHiddenValue({}), /name must be a string/);
assert.throws(setHiddenValue({}, null), /name must be a string/);
assert.throws(setHiddenValue({}, []), /name must be a string/);
const obj = {};
assert.strictEqual(internalUtil.setHiddenValue(obj, 'foo', 'bar'), true);
assert.strictEqual(internalUtil.getHiddenValue(obj, 'foo'), 'bar');
let arrowMessage; let arrowMessage;
try { try {
require('../fixtures/syntax/bad_syntax'); require('../fixtures/syntax/bad_syntax');
} catch (err) { } catch (err) {
arrowMessage = internalUtil.getHiddenValue(err, 'arrowMessage'); arrowMessage = internalUtil.getHiddenValue(err, 'node:arrowMessage');
} }
assert(/bad_syntax\.js:1/.test(arrowMessage)); assert(/bad_syntax\.js:1/.test(arrowMessage));

Loading…
Cancel
Save