Skip to content

Commit 07ba914

Browse files
punteekdevsnek
authored andcommitted
vm: add support for import.meta to Module
Fixes: #18570 PR-URL: #19277 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
1 parent 28b622c commit 07ba914

File tree

5 files changed

+126
-6
lines changed

5 files changed

+126
-6
lines changed

doc/api/vm.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,49 @@ const contextifiedSandbox = vm.createContext({ secret: 42 });
167167
in stack traces produced by this Module.
168168
* `columnOffset` {integer} Spcifies the column number offset that is displayed
169169
in stack traces produced by this Module.
170+
* `initalizeImportMeta` {Function} Called during evaluation of this Module to
171+
initialize the `import.meta`. This function has the signature `(meta,
172+
module)`, where `meta` is the `import.meta` object in the Module, and
173+
`module` is this `vm.Module` object.
170174

171175
Creates a new ES `Module` object.
172176

177+
*Note*: Properties assigned to the `import.meta` object that are objects may
178+
allow the Module to access information outside the specified `context`, if the
179+
object is created in the top level context. Use `vm.runInContext()` to create
180+
objects in a specific context.
181+
182+
```js
183+
const vm = require('vm');
184+
185+
const contextifiedSandbox = vm.createContext({ secret: 42 });
186+
187+
(async () => {
188+
const module = new vm.Module(
189+
'Object.getPrototypeOf(import.meta.prop).secret = secret;',
190+
{
191+
initializeImportMeta(meta) {
192+
// Note: this object is created in the top context. As such,
193+
// Object.getPrototypeOf(import.meta.prop) points to the
194+
// Object.prototype in the top context rather than that in
195+
// the sandbox.
196+
meta.prop = {};
197+
}
198+
});
199+
// Since module has no dependencies, the linker function will never be called.
200+
await module.link(() => {});
201+
module.initialize();
202+
await module.evaluate();
203+
204+
// Now, Object.prototype.secret will be equal to 42.
205+
//
206+
// To fix this problem, replace
207+
// meta.prop = {};
208+
// above with
209+
// meta.prop = vm.runInContext('{}', contextifiedSandbox);
210+
})();
211+
```
212+
173213
### module.dependencySpecifiers
174214

175215
* {string[]}

lib/internal/bootstrap/node.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,13 @@
108108
'DeprecationWarning', 'DEP0062', startup, true);
109109
}
110110

111-
if (process.binding('config').experimentalModules) {
112-
process.emitWarning(
113-
'The ESM module loader is experimental.',
114-
'ExperimentalWarning', undefined);
111+
if (process.binding('config').experimentalModules ||
112+
process.binding('config').experimentalVMModules) {
113+
if (process.binding('config').experimentalModules) {
114+
process.emitWarning(
115+
'The ESM module loader is experimental.',
116+
'ExperimentalWarning', undefined);
117+
}
115118
NativeModule.require('internal/process/esm_loader').setup();
116119
}
117120

lib/internal/process/esm_loader.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ const { getURLFromFilePath } = require('internal/url');
1010
const Loader = require('internal/modules/esm/loader');
1111
const path = require('path');
1212
const { URL } = require('url');
13+
const {
14+
initImportMetaMap,
15+
wrapToModuleMap
16+
} = require('internal/vm/module');
1317

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

2125
function initializeImportMetaObject(wrap, meta) {
22-
meta.url = wrap.url;
26+
const vmModule = wrapToModuleMap.get(wrap);
27+
if (vmModule === undefined) {
28+
// This ModuleWrap belongs to the Loader.
29+
meta.url = wrap.url;
30+
} else {
31+
const initializeImportMeta = initImportMetaMap.get(vmModule);
32+
if (initializeImportMeta !== undefined) {
33+
// This ModuleWrap belongs to vm.Module, initializer callback was
34+
// provided.
35+
initializeImportMeta(meta, vmModule);
36+
}
37+
}
2338
}
2439

2540
let loaderResolve;

lib/internal/vm/module.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ const perContextModuleId = new WeakMap();
4343
const wrapMap = new WeakMap();
4444
const dependencyCacheMap = new WeakMap();
4545
const linkingStatusMap = new WeakMap();
46+
// vm.Module -> function
47+
const initImportMetaMap = new WeakMap();
48+
// ModuleWrap -> vm.Module
49+
const wrapToModuleMap = new WeakMap();
4650

4751
class Module {
4852
constructor(src, options = {}) {
@@ -80,6 +84,16 @@ class Module {
8084
perContextModuleId.set(context, 1);
8185
}
8286

87+
if (options.initializeImportMeta !== undefined) {
88+
if (typeof options.initializeImportMeta === 'function') {
89+
initImportMetaMap.set(this, options.initializeImportMeta);
90+
} else {
91+
throw new ERR_INVALID_ARG_TYPE(
92+
'options.initializeImportMeta', 'function',
93+
options.initializeImportMeta);
94+
}
95+
}
96+
8397
const wrap = new ModuleWrap(src, url, {
8498
[kParsingContext]: context,
8599
lineOffset: options.lineOffset,
@@ -88,6 +102,7 @@ class Module {
88102

89103
wrapMap.set(this, wrap);
90104
linkingStatusMap.set(this, 'unlinked');
105+
wrapToModuleMap.set(wrap, this);
91106

92107
Object.defineProperties(this, {
93108
url: { value: url, enumerable: true },
@@ -206,5 +221,7 @@ class Module {
206221
}
207222

208223
module.exports = {
209-
Module
224+
Module,
225+
initImportMetaMap,
226+
wrapToModuleMap
210227
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use strict';
2+
3+
// Flags: --experimental-vm-modules --harmony-import-meta
4+
5+
const common = require('../common');
6+
const assert = require('assert');
7+
const { Module } = require('vm');
8+
9+
common.crashOnUnhandledRejection();
10+
11+
async function testBasic() {
12+
const m = new Module('import.meta;', {
13+
initializeImportMeta: common.mustCall((meta, module) => {
14+
assert.strictEqual(module, m);
15+
meta.prop = 42;
16+
})
17+
});
18+
await m.link(common.mustNotCall());
19+
m.instantiate();
20+
const { result } = await m.evaluate();
21+
assert.strictEqual(typeof result, 'object');
22+
assert.strictEqual(Object.getPrototypeOf(result), null);
23+
assert.strictEqual(result.prop, 42);
24+
assert.deepStrictEqual(Reflect.ownKeys(result), ['prop']);
25+
}
26+
27+
async function testInvalid() {
28+
for (const invalidValue of [
29+
null, {}, 0, Symbol.iterator, [], 'string', false
30+
]) {
31+
common.expectsError(() => {
32+
new Module('', {
33+
initializeImportMeta: invalidValue
34+
});
35+
}, {
36+
code: 'ERR_INVALID_ARG_TYPE',
37+
type: TypeError
38+
});
39+
}
40+
}
41+
42+
(async () => {
43+
await testBasic();
44+
await testInvalid();
45+
})();

0 commit comments

Comments
 (0)