module: fix column offsets in errors

Because Node modules are wrapped, errors on the first line
of a file leak the wrapper to the user and report the wrong
column number. This commit adds a line break to the module
wrapper so that the first line is treated the same as all
other lines. To compensate for the additional line, a line
offset of -1 is also applied to errors.

Fixes: https://github.com/nodejs/node/issues/2860
PR-URL: https://github.com/nodejs/node/pull/2867
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
Tristian Flanagan 2015-09-14 18:26:59 -04:00 committed by cjihrig
parent 94c3507f5c
commit dfee4e3712
7 changed files with 78 additions and 6 deletions

View File

@ -26,10 +26,16 @@ The options when creating a script are:
- `filename`: allows you to control the filename that shows up in any stack - `filename`: allows you to control the filename that shows up in any stack
traces produced from this script. traces produced from this script.
- `lineOffset`: allows you to add an offset to the line number that is
displayed in stack traces
- `columnOffset`: allows you to add an offset to the column number that is
displayed in stack traces
- `displayErrors`: whether or not to print any errors to stderr, with the - `displayErrors`: whether or not to print any errors to stderr, with the
line of code that caused them highlighted, before throwing an exception. line of code that caused them highlighted, before throwing an exception.
Applies only to syntax errors compiling the code; errors while running the Applies only to syntax errors compiling the code; errors while running the
code are controlled by the options to the script's methods. code are controlled by the options to the script's methods.
- `timeout`: a number of milliseconds to execute `code` before terminating
execution. If execution is terminated, an `Error` will be thrown.
### script.runInContext(contextifiedSandbox[, options]) ### script.runInContext(contextifiedSandbox[, options])
@ -124,8 +130,14 @@ multiple times:
The options for running a script are: The options for running a script are:
- `displayErrors`: whether or not to print any runtime errors to stderr, with - `filename`: allows you to control the filename that shows up in any stack
the line of code that caused them highlighted, before throwing an exception. traces produced.
- `lineOffset`: allows you to add an offset to the line number that is
displayed in stack traces
- `columnOffset`: allows you to add an offset to the column number that is
displayed in stack traces
- `displayErrors`: whether or not to print any errors to stderr, with the
line of code that caused them highlighted, before throwing an exception.
Applies only to runtime errors executing the code; it is impossible to create Applies only to runtime errors executing the code; it is impossible to create
a `Script` instance with syntax errors, as the constructor will throw. a `Script` instance with syntax errors, as the constructor will throw.
- `timeout`: a number of milliseconds to execute the script before terminating - `timeout`: a number of milliseconds to execute the script before terminating
@ -252,6 +264,10 @@ e.g. `(0,eval)('code')`. However, it also has the following additional options:
- `filename`: allows you to control the filename that shows up in any stack - `filename`: allows you to control the filename that shows up in any stack
traces produced. traces produced.
- `lineOffset`: allows you to add an offset to the line number that is
displayed in stack traces
- `columnOffset`: allows you to add an offset to the column number that is
displayed in stack traces
- `displayErrors`: whether or not to print any errors to stderr, with the - `displayErrors`: whether or not to print any errors to stderr, with the
line of code that caused them highlighted, before throwing an exception. line of code that caused them highlighted, before throwing an exception.
Will capture both syntax errors from compiling `code` and runtime errors Will capture both syntax errors from compiling `code` and runtime errors

View File

@ -47,7 +47,6 @@ Module.wrapper = NativeModule.wrapper;
Module.wrap = NativeModule.wrap; Module.wrap = NativeModule.wrap;
Module._debug = util.debuglog('module'); Module._debug = util.debuglog('module');
// We use this alias for the preprocessor that filters it out // We use this alias for the preprocessor that filters it out
const debug = Module._debug; const debug = Module._debug;
@ -401,7 +400,8 @@ 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, { filename: filename }); var compiledWrapper = runInThisContext(wrapper,
{ filename: filename, lineOffset: -1 });
if (global.v8debug) { if (global.v8debug) {
if (!resolvedArgv) { if (!resolvedArgv) {
// we enter the repl if we're not given a filename argument. // we enter the repl if we're not given a filename argument.

View File

@ -955,7 +955,7 @@
}; };
NativeModule.wrapper = [ NativeModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ', '(function (exports, require, module, __filename, __dirname) {\n',
'\n});' '\n});'
]; ];

View File

@ -504,13 +504,15 @@ class ContextifyScript : public BaseObject {
TryCatch try_catch; TryCatch try_catch;
Local<String> code = args[0]->ToString(env->isolate()); Local<String> code = args[0]->ToString(env->isolate());
Local<String> filename = GetFilenameArg(args, 1); Local<String> filename = GetFilenameArg(args, 1);
Local<Integer> lineOffset = GetLineOffsetArg(args, 1);
Local<Integer> columnOffset = GetColumnOffsetArg(args, 1);
bool display_errors = GetDisplayErrorsArg(args, 1); bool display_errors = GetDisplayErrorsArg(args, 1);
if (try_catch.HasCaught()) { if (try_catch.HasCaught()) {
try_catch.ReThrow(); try_catch.ReThrow();
return; return;
} }
ScriptOrigin origin(filename); ScriptOrigin origin(filename, lineOffset, columnOffset);
ScriptCompiler::Source source(code, origin); ScriptCompiler::Source source(code, origin);
Local<UnboundScript> v8_script = Local<UnboundScript> v8_script =
ScriptCompiler::CompileUnbound(env->isolate(), &source); ScriptCompiler::CompileUnbound(env->isolate(), &source);
@ -675,6 +677,39 @@ class ContextifyScript : public BaseObject {
} }
static Local<Integer> GetLineOffsetArg(
const FunctionCallbackInfo<Value>& args,
const int i) {
Local<Integer> defaultLineOffset = Integer::New(args.GetIsolate(), 0);
if (!args[i]->IsObject()) {
return defaultLineOffset;
}
Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "lineOffset");
Local<Value> value = args[i].As<Object>()->Get(key);
return value->IsUndefined() ? defaultLineOffset : value->ToInteger();
}
static Local<Integer> GetColumnOffsetArg(
const FunctionCallbackInfo<Value>& args,
const int i) {
Local<Integer> defaultColumnOffset = Integer::New(args.GetIsolate(), 0);
if (!args[i]->IsObject()) {
return defaultColumnOffset;
}
Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(),
"columnOffset");
Local<Value> value = args[i].As<Object>()->Get(key);
return value->IsUndefined() ? defaultColumnOffset : value->ToInteger();
}
static bool EvalMachine(Environment* env, static bool EvalMachine(Environment* env,
const int64_t timeout, const int64_t timeout,
const bool display_errors, const bool display_errors,

View File

@ -0,0 +1 @@
error

View File

@ -60,3 +60,15 @@ var ctx = {};
Object.defineProperty(ctx, 'b', { configurable: false }); Object.defineProperty(ctx, 'b', { configurable: false });
ctx = vm.createContext(ctx); ctx = vm.createContext(ctx);
assert.equal(script.runInContext(ctx), false); assert.equal(script.runInContext(ctx), false);
// Error on the first line of a module should
// have the correct line and column number
assert.throws(function() {
vm.runInContext('throw new Error()', context, {
filename: 'expected-filename.js',
lineOffset: 32,
columnOffset: 123
});
}, function(err) {
return /expected-filename.js:33:130/.test(err.stack);
}, 'Expected appearance of proper offset in Error stack');

View File

@ -279,3 +279,11 @@ process.on('exit', function() {
// #1440 Loading files with a byte order marker. // #1440 Loading files with a byte order marker.
assert.equal(42, require('../fixtures/utf8-bom.js')); assert.equal(42, require('../fixtures/utf8-bom.js'));
assert.equal(42, require('../fixtures/utf8-bom.json')); assert.equal(42, require('../fixtures/utf8-bom.json'));
// Error on the first line of a module should
// have the correct line and column number
assert.throws(function() {
require('../fixtures/test-error-first-line-offset.js');
}, function(err) {
return /test-error-first-line-offset.js:1:1/.test(err.stack);
}, 'Expected appearance of proper offset in Error stack');