Skip to content

Commit 3999362

Browse files
joyeecheungtargos
authored andcommitted
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. PR-URL: #50137 Refs: #35375 Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent a54179f commit 3999362

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, _, importAttributes) {
1260+
const cascadedLoader = getCascadedLoader();
1261+
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
1262+
importAttributes);
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, _, importAttributes) => {
1262-
const cascadedLoader = getCascadedLoader();
1263-
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
1264-
importAttributes);
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, _, importAttributes) {
1289-
const cascadedLoader = getCascadedLoader();
1290-
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
1291-
importAttributes);
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
@@ -15,6 +15,7 @@ const {
1515
StringPrototypeReplaceAll,
1616
StringPrototypeSlice,
1717
StringPrototypeStartsWith,
18+
Symbol,
1819
SyntaxErrorPrototype,
1920
globalThis: { WebAssembly },
2021
} = primordials;
@@ -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, _, importAttributes) {
197+
return asyncESM.esmLoader.import(specifier, url, importAttributes);
198+
}
195199
try {
196-
compiledWrapper = internalCompileFunction(source, [
197-
'exports',
198-
'require',
199-
'module',
200-
'__filename',
201-
'__dirname',
202-
], {
203-
filename,
204-
importModuleDynamically(specifier, _, importAttributes) {
205-
return asyncESM.esmLoader.import(specifier, url, importAttributes);
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
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
Symbol,
45
RegExpPrototypeExec,
56
globalThis,
67
} = primordials;
@@ -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, _, importAttributes) {
88-
const loader = asyncESM.esmLoader;
89-
return loader.import(specifier, baseUrl, importAttributes);
90-
},
91-
}));
84+
const result = module._compile(script, `${name}-wrapper`)(() => {
85+
const hostDefinedOptionId = Symbol(name);
86+
async function importModuleDynamically(specifier, _, importAttributes) {
87+
const loader = asyncESM.esmLoader;
88+
return loader.import(specifier, baseUrl, importAttributes);
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,
4+
ReflectApply,
55
Symbol,
66
} = primordials;
77

88
const {
9+
ContextifyScript,
910
compileFunction,
1011
isContext: _isContext,
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);
@@ -50,49 +44,20 @@ function getHostDefinedOptionId(importModuleDynamically, filename) {
5044
return Symbol(filename);
5145
}
5246

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

94-
const hostDefinedOptionId =
95-
getHostDefinedOptionId(importModuleDynamically, filename);
57+
function internalCompileFunction(
58+
code, filename, lineOffset, columnOffset,
59+
cachedData, produceCachedData, parsingContext, contextExtensions,
60+
params, hostDefinedOptionId, importModuleDynamically) {
9661
const result = compileFunction(
9762
code,
9863
filename,
@@ -119,23 +84,65 @@ function internalCompileFunction(code, params, options) {
11984
}
12085

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

13490
return result;
13591
}
13692

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

0 commit comments

Comments
 (0)