Skip to content

Commit e2c0c0c

Browse files
committed
lib: rework logic of stripping BOM+Shebang from commonjs
Fixes #27767 PR-URL: #27768 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 73c16b1 commit e2c0c0c

13 files changed

+92
-68
lines changed

lib/internal/bootstrap/pre_execution.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ function initializePolicy() {
338338
}
339339

340340
function initializeCJSLoader() {
341-
require('internal/modules/cjs/loader')._initPaths();
341+
require('internal/modules/cjs/loader').Module._initPaths();
342342
}
343343

344344
function initializeESMLoader() {
@@ -387,7 +387,9 @@ function loadPreloadModules() {
387387
const preloadModules = getOptionValue('--require');
388388
if (preloadModules) {
389389
const {
390-
_preloadModules
390+
Module: {
391+
_preloadModules
392+
},
391393
} = require('internal/modules/cjs/loader');
392394
_preloadModules(preloadModules);
393395
}

lib/internal/main/check_syntax.js

+6-13
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ const {
1313

1414
const { pathToFileURL } = require('url');
1515

16-
const vm = require('vm');
1716
const {
18-
stripShebang, stripBOM
17+
stripShebangOrBOM,
1918
} = require('internal/modules/cjs/helpers');
2019

2120
const {
22-
_resolveFilename: resolveCJSModuleName,
23-
wrap: wrapCJSModule
21+
Module: {
22+
_resolveFilename: resolveCJSModuleName,
23+
},
24+
wrapSafe,
2425
} = require('internal/modules/cjs/loader');
2526

2627
// TODO(joyeecheung): not every one of these are necessary
@@ -49,9 +50,6 @@ if (process.argv[1] && process.argv[1] !== '-') {
4950
}
5051

5152
function checkSyntax(source, filename) {
52-
// Remove Shebang.
53-
source = stripShebang(source);
54-
5553
const { getOptionValue } = require('internal/options');
5654
const experimentalModules = getOptionValue('--experimental-modules');
5755
if (experimentalModules) {
@@ -70,10 +68,5 @@ function checkSyntax(source, filename) {
7068
}
7169
}
7270

73-
// Remove BOM.
74-
source = stripBOM(source);
75-
// Wrap it.
76-
source = wrapCJSModule(source);
77-
// Compile the script, this will throw if it fails.
78-
new vm.Script(source, { displayErrors: true, filename });
71+
wrapSafe(filename, stripShebangOrBOM(source));
7972
}

lib/internal/main/run_main_module.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const {
66

77
prepareMainThreadExecution(true);
88

9-
const CJSModule = require('internal/modules/cjs/loader');
9+
const CJSModule = require('internal/modules/cjs/loader').Module;
1010

1111
markBootstrapComplete();
1212

lib/internal/modules/cjs/helpers.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ function stripShebang(content) {
7272
return content;
7373
}
7474

75+
// Strip either the shebang or UTF BOM of a file.
76+
// Note that this only processes one. If both occur in
77+
// either order, the one that comes second is not
78+
// significant.
79+
function stripShebangOrBOM(content) {
80+
if (content.charCodeAt(0) === 0xFEFF) {
81+
return content.slice(1);
82+
}
83+
return stripShebang(content);
84+
}
85+
7586
const builtinLibs = [
7687
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'crypto',
7788
'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https', 'net',
@@ -137,5 +148,6 @@ module.exports = {
137148
makeRequireFunction,
138149
normalizeReferrerURL,
139150
stripBOM,
140-
stripShebang
151+
stripShebang,
152+
stripShebangOrBOM,
141153
};

lib/internal/modules/cjs/loader.js

+51-44
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const {
4040
makeRequireFunction,
4141
normalizeReferrerURL,
4242
stripBOM,
43-
stripShebang
43+
stripShebangOrBOM,
4444
} = require('internal/modules/cjs/helpers');
4545
const { getOptionValue } = require('internal/options');
4646
const preserveSymlinks = getOptionValue('--preserve-symlinks');
@@ -59,7 +59,7 @@ const {
5959
const { validateString } = require('internal/validators');
6060
const pendingDeprecation = getOptionValue('--pending-deprecation');
6161

62-
module.exports = Module;
62+
module.exports = { wrapSafe, Module };
6363

6464
let asyncESM;
6565
let ModuleJob;
@@ -690,22 +690,10 @@ Module.prototype.require = function(id) {
690690
var resolvedArgv;
691691
let hasPausedEntry = false;
692692

693-
// Run the file contents in the correct scope or sandbox. Expose
694-
// the correct helper variables (require, module, exports) to
695-
// the file.
696-
// Returns exception, if any.
697-
Module.prototype._compile = function(content, filename) {
698-
if (manifest) {
699-
const moduleURL = pathToFileURL(filename);
700-
manifest.assertIntegrity(moduleURL, content);
701-
}
702-
703-
content = stripShebang(content);
704-
705-
let compiledWrapper;
693+
function wrapSafe(filename, content) {
706694
if (patched) {
707695
const wrapper = Module.wrap(content);
708-
compiledWrapper = vm.runInThisContext(wrapper, {
696+
return vm.runInThisContext(wrapper, {
709697
filename,
710698
lineOffset: 0,
711699
displayErrors: true,
@@ -714,35 +702,54 @@ Module.prototype._compile = function(content, filename) {
714702
return loader.import(specifier, normalizeReferrerURL(filename));
715703
} : undefined,
716704
});
717-
} else {
718-
compiledWrapper = compileFunction(
719-
content,
720-
filename,
721-
0,
722-
0,
723-
undefined,
724-
false,
725-
undefined,
726-
[],
727-
[
728-
'exports',
729-
'require',
730-
'module',
731-
'__filename',
732-
'__dirname',
733-
]
734-
);
735-
if (experimentalModules) {
736-
const { callbackMap } = internalBinding('module_wrap');
737-
callbackMap.set(compiledWrapper, {
738-
importModuleDynamically: async (specifier) => {
739-
const loader = await asyncESM.loaderPromise;
740-
return loader.import(specifier, normalizeReferrerURL(filename));
741-
}
742-
});
743-
}
744705
}
745706

707+
const compiledWrapper = compileFunction(
708+
content,
709+
filename,
710+
0,
711+
0,
712+
undefined,
713+
false,
714+
undefined,
715+
[],
716+
[
717+
'exports',
718+
'require',
719+
'module',
720+
'__filename',
721+
'__dirname',
722+
]
723+
);
724+
725+
if (experimentalModules) {
726+
const { callbackMap } = internalBinding('module_wrap');
727+
callbackMap.set(compiledWrapper, {
728+
importModuleDynamically: async (specifier) => {
729+
const loader = await asyncESM.loaderPromise;
730+
return loader.import(specifier, normalizeReferrerURL(filename));
731+
}
732+
});
733+
}
734+
735+
return compiledWrapper;
736+
}
737+
738+
// Run the file contents in the correct scope or sandbox. Expose
739+
// the correct helper variables (require, module, exports) to
740+
// the file.
741+
// Returns exception, if any.
742+
Module.prototype._compile = function(content, filename) {
743+
if (manifest) {
744+
const moduleURL = pathToFileURL(filename);
745+
manifest.assertIntegrity(moduleURL, content);
746+
}
747+
748+
// Strip after manifest integrity check
749+
content = stripShebangOrBOM(content);
750+
751+
const compiledWrapper = wrapSafe(filename, content);
752+
746753
var inspectorWrapper = null;
747754
if (getOptionValue('--inspect-brk') && process._eval == null) {
748755
if (!resolvedArgv) {
@@ -782,7 +789,7 @@ Module.prototype._compile = function(content, filename) {
782789
// Native extension for .js
783790
Module._extensions['.js'] = function(module, filename) {
784791
const content = fs.readFileSync(filename, 'utf8');
785-
module._compile(stripBOM(content), filename);
792+
module._compile(content, filename);
786793
};
787794

788795

lib/internal/modules/esm/translators.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ const {
1111

1212
const { NativeModule } = require('internal/bootstrap/loaders');
1313
const {
14-
stripShebang,
1514
stripBOM
1615
} = require('internal/modules/cjs/helpers');
17-
const CJSModule = require('internal/modules/cjs/loader');
16+
const CJSModule = require('internal/modules/cjs/loader').Module;
1817
const internalURLModule = require('internal/url');
1918
const createDynamicModule = require(
2019
'internal/modules/esm/create_dynamic_module');
@@ -48,7 +47,7 @@ translators.set('module', async function moduleStrategy(url) {
4847
const source = `${await readFileAsync(new URL(url))}`;
4948
debug(`Translating StandardModule ${url}`);
5049
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
51-
const module = new ModuleWrap(stripShebang(source), url);
50+
const module = new ModuleWrap(source, url);
5251
callbackMap.set(module, {
5352
initializeImportMeta,
5453
importModuleDynamically,

lib/internal/process/execution.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function evalModule(source, print) {
5353
}
5454

5555
function evalScript(name, body, breakFirstLine, print) {
56-
const CJSModule = require('internal/modules/cjs/loader');
56+
const CJSModule = require('internal/modules/cjs/loader').Module;
5757
const { kVmBreakFirstLineSymbol } = require('internal/util');
5858

5959
const cwd = tryGetCwd();

lib/internal/util/inspector.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function sendInspectorCommand(cb, onError) {
2424
function installConsoleExtensions(commandLineApi) {
2525
if (commandLineApi.require) { return; }
2626
const { tryGetCwd } = require('internal/process/execution');
27-
const CJSModule = require('internal/modules/cjs/loader');
27+
const CJSModule = require('internal/modules/cjs/loader').Module;
2828
const { makeRequireFunction } = require('internal/modules/cjs/helpers');
2929
const consoleAPIModule = new CJSModule('<inspector console>');
3030
const cwd = tryGetCwd();

lib/module.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
'use strict';
22

3-
module.exports = require('internal/modules/cjs/loader');
3+
module.exports = require('internal/modules/cjs/loader').Module;

lib/repl.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const path = require('path');
6565
const fs = require('fs');
6666
const { Interface } = require('readline');
6767
const { Console } = require('console');
68-
const CJSModule = require('internal/modules/cjs/loader');
68+
const CJSModule = require('internal/modules/cjs/loader').Module;
6969
const domain = require('domain');
7070
const debug = require('internal/util/debuglog').debuglog('repl');
7171
const {

test/fixtures/utf8-bom-shebang.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!shebang
2+
module.exports = 42;

test/fixtures/utf8-shebang-bom.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!shebang
2+
module.exports = 42;

test/sequential/test-module-loading.js

+7
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,13 @@ process.on('exit', function() {
352352
assert.strictEqual(require('../fixtures/utf8-bom.js'), 42);
353353
assert.strictEqual(require('../fixtures/utf8-bom.json'), 42);
354354

355+
// Loading files with BOM + shebang.
356+
// See https://github.com/nodejs/node/issues/27767
357+
assert.throws(() => {
358+
require('../fixtures/utf8-bom-shebang.js');
359+
}, { name: 'SyntaxError' });
360+
assert.strictEqual(require('../fixtures/utf8-shebang-bom.js'), 42);
361+
355362
// Error on the first line of a module should
356363
// have the correct line number
357364
assert.throws(function() {

0 commit comments

Comments
 (0)