Skip to content

displayErrors option of the vm module seems not to work well #4835

Description

@susisu

I'm stuck for the behavior of the displayErrors option of the vm module.

First I wrote the code like below and this works well.

var vm = require("vm");
var sandbox = Object.create(null);
var context = vm.createContext(sandbox);
var script  = new vm.Script("?", { filename: "foobar", displayErrors: false });
script.runInContext(context, { filename: "foobar", displayErrors: false });
foobar:1
?
^

SyntaxError: Unexpected token ?
    at Object.<anonymous> (/path/to/file.js:4:15)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:139:18)
    at node.js:999:3

When displayErrors are set to true, however, the behavior seems to be not consistent with the document "print any errors to stderr before throwing an exception" since this code does not output anything to stderr.

var vm = require("vm");
var sandbox = Object.create(null);
var context = vm.createContext(sandbox);
var script  = new vm.Script("?", { filename: "foobar", displayErrors: true });
script.runInContext(context, { filename: "foobar", displayErrors: true });
foobar:1
?
^

SyntaxError: Unexpected token ?
    at Object.<anonymous> (/Users/Susisu/root/projects/est/dev/vmtest.js:4:15)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:139:18)
    at node.js:999:3

Next, I tried to catch the error and get the information where the error has occurred but I couldn't.

I also re-threw the error and found that the information about the script is discarded when the error is re-thrown if displayErrors: false, while the displayErrors: true version retains it.

var vm = require("vm");
var sandbox = Object.create(null);
var context = vm.createContext(sandbox);
try {
    var script  = new vm.Script("?", { filename: "foobar", displayErrors: false });
    script.runInContext(context, { filename: "foobar", displayErrors: false });
}
catch (err) {
    throw err;
}
/path/to/file.js:9
    throw err;
    ^

SyntaxError: Unexpected token ?
    at Object.<anonymous> (/path/to/file.js:5:19)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:139:18)
    at node.js:999:3
var vm = require("vm");
var sandbox = Object.create(null);
var context = vm.createContext(sandbox);
try {
    var script  = new vm.Script("?", { filename: "foobar", displayErrors: true });
    script.runInContext(context, { filename: "foobar", displayErrors: true });
}
catch (err) {
    throw err;
}
foobar:1
?
^

SyntaxError: Unexpected token ?
    at Object.<anonymous> (/path/to/file.js:5:19)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:139:18)
    at node.js:999:3

The problem is more serious when combined with Promise.
This code actually does not print anything since the error is swallowed by the Promise, and I have no idea to know where the error occurred in the script.

var vm = require("vm");
new Promise((resolve, reject) => {
    var sandbox = Object.create(null);
    var context = vm.createContext(sandbox);
    var script  = new vm.Script("?", { filename: "foobar", displayErrors: false });
    script.runInContext(context, { filename: "foobar", displayErrors: false });
}).catch(err => {
    // can't do anything
});

Note that all the codes throw syntax errors, but the results are almost same for throwing runtime errors.

Taken together, it seems that displayErrors: true does only make the error retain the information where it occurred and does not let new vm.Script (and runInContext or something) print anything.
In addition, there seems to be no way to know where the error occurred if it is in Promise.

Here I have two questions.

  1. Is this behavior of displayErrors: true is correct?
  2. Are there some ways to know where the error occurred in the script for the last case?

The version of Node.js is v5.5.0.

Thanks.

Activity

added
confirmed-bugIssues and PRs for confirmed bugs.
vmIssues and PRs related to the vm subsystem.
on Jan 24, 2016

bnoordhuis commented on Jan 24, 2016

@bnoordhuis
Member

I had a look and I can confirm what you're describing. What happens is this:

  1. The SyntaxError that V8 creates doesn't point to the source location of the invalid syntax.
  2. What node.js does is attach a hidden property to the exception object that contains the necessary information.

Example:

var vm = require('vm');
var util = require('internal/util');  // requires --expose_internals
try {
  new vm.Script('?', {filename: 'x.js', displayErrors: true});
} catch (e) {
  console.error('arrowMessage:', util.getHiddenValue(e, 'node:arrowMessage'));
}

Which prints:

$ node --expose_internals t.js
arrowMessage: x.js:1
?
^

If you let the exception bubble up to node's top-level exception handler, it will include the arrowMessage string in the output. With that in mind:

Is this behavior of displayErrors: true is correct?

It's as correct as it can be given the circumstances but it's at odds with the documentation. We could either restore the old behavior where node prints the message immediately on syntax error or we could update the documentation.

If we update the documentation, there probably needs to be an official way to get the hidden property, which is awkward.

Are there some ways to know where the error occurred in the script for the last case?

Nothing official at the moment, no, except for letting the exception bubble up. (EDIT: Which doesn't work for promises, of course.)

/cc @nodejs/documentation @cjihrig

benjamingr commented on Jan 24, 2016

@benjamingr
Member

EDIT: Which doesn't work for promises, of course.

Why?

bnoordhuis commented on Jan 24, 2016

@bnoordhuis
Member

The exception won't bubble up all the way, at least not by default.

You could add a process.on('unhandledRejection', err => { throw err }) and drop any .catch(...) handlers from your promises but that's not very elegant and not always workable.

benjamingr commented on Jan 24, 2016

@benjamingr
Member

Well, if you have .catch handlers that's the same as not letting exceptions bubble (by not having a synchronous catch(e) {). Adding on('unahndledRejection', err => { throw err; }) is something you probably want to do when using vm.

I get your point though - thanks.

cjihrig commented on Jan 24, 2016

@cjihrig
Contributor

My guess is that this behavior goes back to f1de13b, which fixes nodejs/node-v0.x-archive#6920.

@bnoordhuis I'm happy to work on this if we decide how we want to fix it.

susisu commented on Jan 24, 2016

@susisu
Author

@bnoordhuis Thank you for your answer. I see the point.

I will try to use process.on('unhandledRejection', ...) for this case for now.

bnoordhuis commented on Jan 25, 2016

@bnoordhuis
Member

@cjihrig The best I can come up with is to intercept the SyntaxError and add the extra information to the .message property instead of a hidden property. That's pretty yuck, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-bugIssues and PRs for confirmed bugs.vmIssues and PRs related to the vm subsystem.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions