diff options
author | punteek <prateek1@ucla.edu> | 2018-03-06 19:46:39 -0800 |
---|---|---|
committer | Gus Caplan <me@gus.host> | 2018-03-31 19:55:50 -0500 |
commit | 07ba9141e475ec63f6ef56b67ec5f98077cd3446 (patch) | |
tree | 99f36abde8cd6fd8b4a074fdfe8b1d8fe7fa6388 | |
parent | 28b622cb08602d77512fa3d659451cd317dfcc41 (diff) | |
download | node-new-07ba9141e475ec63f6ef56b67ec5f98077cd3446.tar.gz |
vm: add support for import.meta to Module
Fixes: https://github.com/nodejs/node/issues/18570
PR-URL: https://github.com/nodejs/node/pull/19277
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
-rw-r--r-- | doc/api/vm.md | 40 | ||||
-rw-r--r-- | lib/internal/bootstrap/node.js | 11 | ||||
-rw-r--r-- | lib/internal/process/esm_loader.js | 17 | ||||
-rw-r--r-- | lib/internal/vm/module.js | 19 | ||||
-rw-r--r-- | test/parallel/test-vm-module-import-meta.js | 45 |
5 files changed, 126 insertions, 6 deletions
diff --git a/doc/api/vm.md b/doc/api/vm.md index c729ef5999..ed7be81776 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -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[]} diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 01efe6f329..99bf00a6d6 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -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(); } diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index bcb6501af6..db28ca04b1 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -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)) { @@ -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; diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js index 48d591f3bf..9af071ce28 100644 --- a/lib/internal/vm/module.js +++ b/lib/internal/vm/module.js @@ -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 = {}) { @@ -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, @@ -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 }, @@ -206,5 +221,7 @@ class Module { } module.exports = { - Module + Module, + initImportMetaMap, + wrapToModuleMap }; diff --git a/test/parallel/test-vm-module-import-meta.js b/test/parallel/test-vm-module-import-meta.js new file mode 100644 index 0000000000..835ef5b6eb --- /dev/null +++ b/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(); +})(); |