Skip to content

Commit c6496b8

Browse files
committed
vm: use internal versions of compileFunction and Script
Instead of using the public versions of the vm APIs internally, use the internal versions so that we can skip unnecessary argument validation. The public versions would need special care to the generation of host-defined options to hit the isolate compilation cache when imporModuleDynamically isn't used, while internally it's almost always used, so this allows us to handle the host-defined options separately.
1 parent 9d1ab04 commit c6496b8

File tree

8 files changed

+268
-186
lines changed

8 files changed

+268
-186
lines changed

lib/internal/modules/cjs/loader.js

+40-33
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const {
5252
SafeMap,
5353
SafeWeakMap,
5454
String,
55+
Symbol,
5556
StringPrototypeCharAt,
5657
StringPrototypeCharCodeAt,
5758
StringPrototypeEndsWith,
@@ -84,7 +85,12 @@ const {
8485
setOwnProperty,
8586
getLazy,
8687
} = require('internal/util');
87-
const { internalCompileFunction } = require('internal/vm');
88+
const {
89+
internalCompileFunction,
90+
makeContextifyScript,
91+
runScriptInThisContext,
92+
} = require('internal/vm');
93+
8894
const assert = require('internal/assert');
8995
const fs = require('fs');
9096
const path = require('path');
@@ -1240,7 +1246,6 @@ Module.prototype.require = function(id) {
12401246
let resolvedArgv;
12411247
let hasPausedEntry = false;
12421248
/** @type {import('vm').Script} */
1243-
let Script;
12441249

12451250
/**
12461251
* Wraps the given content in a script and runs it in a new context.
@@ -1250,47 +1255,49 @@ let Script;
12501255
* @param {object} codeCache The SEA code cache
12511256
*/
12521257
function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
1258+
const hostDefinedOptionId = Symbol(`cjs:${filename}`);
1259+
async function importModuleDynamically(specifier, _, importAssertions) {
1260+
const cascadedLoader = getCascadedLoader();
1261+
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
1262+
importAssertions);
1263+
}
12531264
if (patched) {
1254-
const wrapper = Module.wrap(content);
1255-
if (Script === undefined) {
1256-
({ Script } = require('vm'));
1257-
}
1258-
const script = new Script(wrapper, {
1259-
filename,
1260-
lineOffset: 0,
1261-
importModuleDynamically: async (specifier, _, importAssertions) => {
1262-
const cascadedLoader = getCascadedLoader();
1263-
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
1264-
importAssertions);
1265-
},
1266-
});
1265+
const wrapped = Module.wrap(content);
1266+
const script = makeContextifyScript(
1267+
wrapped, // code
1268+
filename, // filename
1269+
0, // lineOffset
1270+
0, // columnOffset
1271+
undefined, // cachedData
1272+
false, // produceCachedData
1273+
undefined, // parsingContext
1274+
hostDefinedOptionId, // hostDefinedOptionId
1275+
importModuleDynamically, // importModuleDynamically
1276+
);
12671277

12681278
// Cache the source map for the module if present.
12691279
if (script.sourceMapURL) {
12701280
maybeCacheSourceMap(filename, content, this, false, undefined, script.sourceMapURL);
12711281
}
12721282

1273-
return script.runInThisContext({
1274-
displayErrors: true,
1275-
});
1283+
return runScriptInThisContext(script, true, false);
12761284
}
12771285

1286+
const params = [ 'exports', 'require', 'module', '__filename', '__dirname' ];
12781287
try {
1279-
const result = internalCompileFunction(content, [
1280-
'exports',
1281-
'require',
1282-
'module',
1283-
'__filename',
1284-
'__dirname',
1285-
], {
1286-
filename,
1287-
cachedData: codeCache,
1288-
importModuleDynamically(specifier, _, importAssertions) {
1289-
const cascadedLoader = getCascadedLoader();
1290-
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
1291-
importAssertions);
1292-
},
1293-
});
1288+
const result = internalCompileFunction(
1289+
content, // code,
1290+
filename, // filename
1291+
0, // lineOffset
1292+
0, // columnOffset,
1293+
codeCache, // cachedData
1294+
false, // produceCachedData
1295+
undefined, // parsingContext
1296+
undefined, // contextExtensions
1297+
params, // params
1298+
hostDefinedOptionId, // hostDefinedOptionId
1299+
importModuleDynamically, // importModuleDynamically
1300+
);
12941301

12951302
// The code cache is used for SEAs only.
12961303
if (codeCache &&

lib/internal/modules/esm/translators.js

+23-12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const {
1717
StringPrototypeStartsWith,
1818
SyntaxErrorPrototype,
1919
globalThis: { WebAssembly },
20+
Symbol,
2021
} = primordials;
2122

2223
/** @type {import('internal/util/types')} */
@@ -192,19 +193,29 @@ function enrichCJSError(err, content, filename) {
192193
*/
193194
function loadCJSModule(module, source, url, filename) {
194195
let compiledWrapper;
196+
async function importModuleDynamically(specifier, _, importAssertions) {
197+
return asyncESM.esmLoader.import(specifier, url, importAssertions);
198+
}
195199
try {
196-
compiledWrapper = internalCompileFunction(source, [
197-
'exports',
198-
'require',
199-
'module',
200-
'__filename',
201-
'__dirname',
202-
], {
203-
filename,
204-
importModuleDynamically(specifier, _, importAssertions) {
205-
return asyncESM.esmLoader.import(specifier, url, importAssertions);
206-
},
207-
}).function;
200+
compiledWrapper = internalCompileFunction(
201+
source, // code,
202+
filename, // filename
203+
0, // lineOffset
204+
0, // columnOffset,
205+
undefined, // cachedData
206+
false, // produceCachedData
207+
undefined, // parsingContext
208+
undefined, // contextExtensions
209+
[ // params
210+
'exports',
211+
'require',
212+
'module',
213+
'__filename',
214+
'__dirname',
215+
],
216+
Symbol(`cjs:${filename}`), // hostDefinedOptionsId
217+
importModuleDynamically, // importModuleDynamically
218+
).function;
208219
} catch (err) {
209220
enrichCJSError(err, source, url);
210221
throw err;

lib/internal/process/execution.js

+23-12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const {
44
RegExpPrototypeExec,
55
globalThis,
6+
Symbol,
67
} = primordials;
78

89
const path = require('path');
@@ -25,7 +26,9 @@ const {
2526
emitAfter,
2627
popAsyncContext,
2728
} = require('internal/async_hooks');
28-
29+
const {
30+
makeContextifyScript, runScriptInThisContext,
31+
} = require('internal/vm');
2932
// shouldAbortOnUncaughtToggle is a typed array for faster
3033
// communication with JS.
3134
const { shouldAbortOnUncaughtToggle } = internalBinding('util');
@@ -53,7 +56,6 @@ function evalModule(source, print) {
5356

5457
function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
5558
const CJSModule = require('internal/modules/cjs/loader').Module;
56-
const { kVmBreakFirstLineSymbol } = require('internal/util');
5759
const { pathToFileURL } = require('internal/url');
5860

5961
const cwd = tryGetCwd();
@@ -79,16 +81,25 @@ function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
7981
`;
8082
globalThis.__filename = name;
8183
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
82-
const result = module._compile(script, `${name}-wrapper`)(() =>
83-
require('vm').runInThisContext(body, {
84-
filename: name,
85-
displayErrors: true,
86-
[kVmBreakFirstLineSymbol]: !!breakFirstLine,
87-
importModuleDynamically(specifier, _, importAssertions) {
88-
const loader = asyncESM.esmLoader;
89-
return loader.import(specifier, baseUrl, importAssertions);
90-
},
91-
}));
84+
const result = module._compile(script, `${name}-wrapper`)(() => {
85+
const hostDefinedOptionId = Symbol(name);
86+
async function importModuleDynamically(specifier, _, importAssertions) {
87+
const loader = asyncESM.esmLoader;
88+
return loader.import(specifier, baseUrl, importAssertions);
89+
}
90+
const script = makeContextifyScript(
91+
body, // code
92+
name, // filename,
93+
0, // lineOffset
94+
0, // columnOffset,
95+
undefined, // cachedData
96+
false, // produceCachedData
97+
undefined, // parsingContext
98+
hostDefinedOptionId, // hostDefinedOptionId
99+
importModuleDynamically, // importModuleDynamically
100+
);
101+
return runScriptInThisContext(script, true, !!breakFirstLine);
102+
});
92103
if (print) {
93104
const { log } = require('internal/console/global');
94105
log(result);

lib/internal/vm.js

+69-62
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,26 @@
11
'use strict';
22

33
const {
4-
ArrayPrototypeForEach,
54
Symbol,
5+
ReflectApply,
66
} = primordials;
77

88
const {
99
compileFunction,
1010
isContext: _isContext,
11+
ContextifyScript,
1112
} = internalBinding('contextify');
13+
const {
14+
runInContext,
15+
} = ContextifyScript.prototype;
1216
const {
1317
default_host_defined_options,
1418
} = internalBinding('symbols');
1519
const {
16-
validateArray,
17-
validateBoolean,
18-
validateBuffer,
1920
validateFunction,
2021
validateObject,
21-
validateString,
22-
validateStringArray,
2322
kValidateObjectAllowArray,
24-
kValidateObjectAllowNullable,
25-
validateInt32,
2623
} = require('internal/validators');
27-
const {
28-
ERR_INVALID_ARG_TYPE,
29-
} = require('internal/errors').codes;
3024

3125
function isContext(object) {
3226
validateObject(object, 'object', kValidateObjectAllowArray);
@@ -51,49 +45,20 @@ function getHostDefinedOptionId(importModuleDynamically, filename) {
5145

5246
}
5347

54-
function internalCompileFunction(code, params, options) {
55-
validateString(code, 'code');
56-
if (params !== undefined) {
57-
validateStringArray(params, 'params');
58-
}
59-
const {
60-
filename = '',
61-
columnOffset = 0,
62-
lineOffset = 0,
63-
cachedData = undefined,
64-
produceCachedData = false,
65-
parsingContext = undefined,
66-
contextExtensions = [],
67-
importModuleDynamically,
68-
} = options;
69-
70-
validateString(filename, 'options.filename');
71-
validateInt32(columnOffset, 'options.columnOffset');
72-
validateInt32(lineOffset, 'options.lineOffset');
73-
if (cachedData !== undefined)
74-
validateBuffer(cachedData, 'options.cachedData');
75-
validateBoolean(produceCachedData, 'options.produceCachedData');
76-
if (parsingContext !== undefined) {
77-
if (
78-
typeof parsingContext !== 'object' ||
79-
parsingContext === null ||
80-
!isContext(parsingContext)
81-
) {
82-
throw new ERR_INVALID_ARG_TYPE(
83-
'options.parsingContext',
84-
'Context',
85-
parsingContext,
86-
);
87-
}
88-
}
89-
validateArray(contextExtensions, 'options.contextExtensions');
90-
ArrayPrototypeForEach(contextExtensions, (extension, i) => {
91-
const name = `options.contextExtensions[${i}]`;
92-
validateObject(extension, name, kValidateObjectAllowNullable);
48+
function registerImportModuleDynamically(referrer, importModuleDynamically) {
49+
const { importModuleDynamicallyWrap } = require('internal/vm/module');
50+
const { registerModule } = require('internal/modules/esm/utils');
51+
registerModule(referrer, {
52+
__proto__: null,
53+
importModuleDynamically:
54+
importModuleDynamicallyWrap(importModuleDynamically),
9355
});
56+
}
9457

95-
const hostDefinedOptionId =
96-
getHostDefinedOptionId(importModuleDynamically, filename);
58+
function internalCompileFunction(
59+
code, filename, lineOffset, columnOffset,
60+
cachedData, produceCachedData, parsingContext, contextExtensions,
61+
params, hostDefinedOptionId, importModuleDynamically) {
9762
const result = compileFunction(
9863
code,
9964
filename,
@@ -120,23 +85,65 @@ function internalCompileFunction(code, params, options) {
12085
}
12186

12287
if (importModuleDynamically !== undefined) {
123-
validateFunction(importModuleDynamically,
124-
'options.importModuleDynamically');
125-
const { importModuleDynamicallyWrap } = require('internal/vm/module');
126-
const wrapped = importModuleDynamicallyWrap(importModuleDynamically);
127-
const func = result.function;
128-
const { registerModule } = require('internal/modules/esm/utils');
129-
registerModule(func, {
130-
__proto__: null,
131-
importModuleDynamically: wrapped,
132-
});
88+
registerImportModuleDynamically(result.function, importModuleDynamically);
13389
}
13490

13591
return result;
13692
}
13793

94+
function makeContextifyScript(code,
95+
filename,
96+
lineOffset,
97+
columnOffset,
98+
cachedData,
99+
produceCachedData,
100+
parsingContext,
101+
hostDefinedOptionId,
102+
importModuleDynamically) {
103+
let script;
104+
// Calling `ReThrow()` on a native TryCatch does not generate a new
105+
// abort-on-uncaught-exception check. A dummy try/catch in JS land
106+
// protects against that.
107+
try { // eslint-disable-line no-useless-catch
108+
script = new ContextifyScript(code,
109+
filename,
110+
lineOffset,
111+
columnOffset,
112+
cachedData,
113+
produceCachedData,
114+
parsingContext,
115+
hostDefinedOptionId);
116+
} catch (e) {
117+
throw e; /* node-do-not-add-exception-line */
118+
}
119+
120+
if (importModuleDynamically !== undefined) {
121+
registerImportModuleDynamically(script, importModuleDynamically);
122+
}
123+
return script;
124+
}
125+
126+
// Internal version of vm.Script.prototype.runInThisContext() which skips
127+
// argument validation.
128+
function runScriptInThisContext(script, displayErrors, breakOnFirstLine) {
129+
return ReflectApply(
130+
runInContext,
131+
script,
132+
[
133+
null, // sandbox - use current context
134+
-1, // timeout
135+
displayErrors, // displayErrors
136+
false, // breakOnSigint
137+
breakOnFirstLine, // breakOnFirstLine
138+
],
139+
);
140+
}
141+
138142
module.exports = {
139143
internalCompileFunction,
140144
isContext,
141145
getHostDefinedOptionId,
146+
runScriptInThisContext,
147+
registerImportModuleDynamically,
148+
makeContextifyScript,
142149
};

0 commit comments

Comments
 (0)