Skip to content

Commit

Permalink
vm: add support for import.meta to Module
Browse files Browse the repository at this point in the history
Fixes: #18570

PR-URL: #19277
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
  • Loading branch information
punteek authored and devsnek committed Apr 1, 2018
1 parent 28b622c commit 07ba914
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 6 deletions.
40 changes: 40 additions & 0 deletions doc/api/vm.md
Expand Up @@ -167,9 +167,49 @@ const contextifiedSandbox = vm.createContext({ secret: 42 });
in stack traces produced by this Module.
* `columnOffset` {integer} Spcifies the column number offset that is displayed
in stack traces produced by this Module.
* `initalizeImportMeta` {Function} Called during evaluation of this Module to
initialize the `import.meta`. This function has the signature `(meta,
module)`, where `meta` is the `import.meta` object in the Module, and
`module` is this `vm.Module` object.

Creates a new ES `Module` object.

*Note*: Properties assigned to the `import.meta` object that are objects may
allow the Module to access information outside the specified `context`, if the
object is created in the top level context. Use `vm.runInContext()` to create
objects in a specific context.

```js
const vm = require('vm');

const contextifiedSandbox = vm.createContext({ secret: 42 });

(async () => {
const module = new vm.Module(
'Object.getPrototypeOf(import.meta.prop).secret = secret;',
{
initializeImportMeta(meta) {
// Note: this object is created in the top context. As such,
// Object.getPrototypeOf(import.meta.prop) points to the
// Object.prototype in the top context rather than that in
// the sandbox.
meta.prop = {};
}
});
// Since module has no dependencies, the linker function will never be called.
await module.link(() => {});
module.initialize();
await module.evaluate();

// Now, Object.prototype.secret will be equal to 42.
//
// To fix this problem, replace
// meta.prop = {};
// above with
// meta.prop = vm.runInContext('{}', contextifiedSandbox);
})();
```

### module.dependencySpecifiers

* {string[]}
Expand Down
11 changes: 7 additions & 4 deletions lib/internal/bootstrap/node.js
Expand Up @@ -108,10 +108,13 @@
'DeprecationWarning', 'DEP0062', startup, true);
}

if (process.binding('config').experimentalModules) {
process.emitWarning(
'The ESM module loader is experimental.',
'ExperimentalWarning', undefined);
if (process.binding('config').experimentalModules ||
process.binding('config').experimentalVMModules) {
if (process.binding('config').experimentalModules) {
process.emitWarning(
'The ESM module loader is experimental.',
'ExperimentalWarning', undefined);
}
NativeModule.require('internal/process/esm_loader').setup();
}

Expand Down
17 changes: 16 additions & 1 deletion lib/internal/process/esm_loader.js
Expand Up @@ -10,6 +10,10 @@ const { getURLFromFilePath } = require('internal/url');
const Loader = require('internal/modules/esm/loader');
const path = require('path');
const { URL } = require('url');
const {
initImportMetaMap,
wrapToModuleMap
} = require('internal/vm/module');

function normalizeReferrerURL(referrer) {
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
Expand All @@ -19,7 +23,18 @@ function normalizeReferrerURL(referrer) {
}

function initializeImportMetaObject(wrap, meta) {
meta.url = wrap.url;
const vmModule = wrapToModuleMap.get(wrap);
if (vmModule === undefined) {
// This ModuleWrap belongs to the Loader.
meta.url = wrap.url;
} else {
const initializeImportMeta = initImportMetaMap.get(vmModule);
if (initializeImportMeta !== undefined) {
// This ModuleWrap belongs to vm.Module, initializer callback was
// provided.
initializeImportMeta(meta, vmModule);
}
}
}

let loaderResolve;
Expand Down
19 changes: 18 additions & 1 deletion lib/internal/vm/module.js
Expand Up @@ -43,6 +43,10 @@ const perContextModuleId = new WeakMap();
const wrapMap = new WeakMap();
const dependencyCacheMap = new WeakMap();
const linkingStatusMap = new WeakMap();
// vm.Module -> function
const initImportMetaMap = new WeakMap();
// ModuleWrap -> vm.Module
const wrapToModuleMap = new WeakMap();

class Module {
constructor(src, options = {}) {
Expand Down Expand Up @@ -80,6 +84,16 @@ class Module {
perContextModuleId.set(context, 1);
}

if (options.initializeImportMeta !== undefined) {
if (typeof options.initializeImportMeta === 'function') {
initImportMetaMap.set(this, options.initializeImportMeta);
} else {
throw new ERR_INVALID_ARG_TYPE(
'options.initializeImportMeta', 'function',
options.initializeImportMeta);
}
}

const wrap = new ModuleWrap(src, url, {
[kParsingContext]: context,
lineOffset: options.lineOffset,
Expand All @@ -88,6 +102,7 @@ class Module {

wrapMap.set(this, wrap);
linkingStatusMap.set(this, 'unlinked');
wrapToModuleMap.set(wrap, this);

Object.defineProperties(this, {
url: { value: url, enumerable: true },
Expand Down Expand Up @@ -206,5 +221,7 @@ class Module {
}

module.exports = {
Module
Module,
initImportMetaMap,
wrapToModuleMap
};
45 changes: 45 additions & 0 deletions test/parallel/test-vm-module-import-meta.js
@@ -0,0 +1,45 @@
'use strict';

// Flags: --experimental-vm-modules --harmony-import-meta

const common = require('../common');
const assert = require('assert');
const { Module } = require('vm');

common.crashOnUnhandledRejection();

async function testBasic() {
const m = new Module('import.meta;', {
initializeImportMeta: common.mustCall((meta, module) => {
assert.strictEqual(module, m);
meta.prop = 42;
})
});
await m.link(common.mustNotCall());
m.instantiate();
const { result } = await m.evaluate();
assert.strictEqual(typeof result, 'object');
assert.strictEqual(Object.getPrototypeOf(result), null);
assert.strictEqual(result.prop, 42);
assert.deepStrictEqual(Reflect.ownKeys(result), ['prop']);
}

async function testInvalid() {
for (const invalidValue of [
null, {}, 0, Symbol.iterator, [], 'string', false
]) {
common.expectsError(() => {
new Module('', {
initializeImportMeta: invalidValue
});
}, {
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError
});
}
}

(async () => {
await testBasic();
await testInvalid();
})();

0 comments on commit 07ba914

Please sign in to comment.