lib: rework logic of stripping BOM+Shebang from commonjs

Fixes https://github.com/nodejs/node/issues/27767

PR-URL: https://github.com/nodejs/node/pull/27768
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
Gus Caplan 2019-05-18 22:48:46 -05:00
parent 73c16b11e0
commit e2c0c0c680
No known key found for this signature in database
GPG Key ID: F00BD11880E82F0E
13 changed files with 92 additions and 68 deletions

View File

@ -338,7 +338,7 @@ function initializePolicy() {
} }
function initializeCJSLoader() { function initializeCJSLoader() {
require('internal/modules/cjs/loader')._initPaths(); require('internal/modules/cjs/loader').Module._initPaths();
} }
function initializeESMLoader() { function initializeESMLoader() {
@ -387,7 +387,9 @@ function loadPreloadModules() {
const preloadModules = getOptionValue('--require'); const preloadModules = getOptionValue('--require');
if (preloadModules) { if (preloadModules) {
const { const {
Module: {
_preloadModules _preloadModules
},
} = require('internal/modules/cjs/loader'); } = require('internal/modules/cjs/loader');
_preloadModules(preloadModules); _preloadModules(preloadModules);
} }

View File

@ -13,14 +13,15 @@ const {
const { pathToFileURL } = require('url'); const { pathToFileURL } = require('url');
const vm = require('vm');
const { const {
stripShebang, stripBOM stripShebangOrBOM,
} = require('internal/modules/cjs/helpers'); } = require('internal/modules/cjs/helpers');
const { const {
Module: {
_resolveFilename: resolveCJSModuleName, _resolveFilename: resolveCJSModuleName,
wrap: wrapCJSModule },
wrapSafe,
} = require('internal/modules/cjs/loader'); } = require('internal/modules/cjs/loader');
// TODO(joyeecheung): not every one of these are necessary // TODO(joyeecheung): not every one of these are necessary
@ -49,9 +50,6 @@ if (process.argv[1] && process.argv[1] !== '-') {
} }
function checkSyntax(source, filename) { function checkSyntax(source, filename) {
// Remove Shebang.
source = stripShebang(source);
const { getOptionValue } = require('internal/options'); const { getOptionValue } = require('internal/options');
const experimentalModules = getOptionValue('--experimental-modules'); const experimentalModules = getOptionValue('--experimental-modules');
if (experimentalModules) { if (experimentalModules) {
@ -70,10 +68,5 @@ function checkSyntax(source, filename) {
} }
} }
// Remove BOM. wrapSafe(filename, stripShebangOrBOM(source));
source = stripBOM(source);
// Wrap it.
source = wrapCJSModule(source);
// Compile the script, this will throw if it fails.
new vm.Script(source, { displayErrors: true, filename });
} }

View File

@ -6,7 +6,7 @@ const {
prepareMainThreadExecution(true); prepareMainThreadExecution(true);
const CJSModule = require('internal/modules/cjs/loader'); const CJSModule = require('internal/modules/cjs/loader').Module;
markBootstrapComplete(); markBootstrapComplete();

View File

@ -72,6 +72,17 @@ function stripShebang(content) {
return content; return content;
} }
// Strip either the shebang or UTF BOM of a file.
// Note that this only processes one. If both occur in
// either order, the one that comes second is not
// significant.
function stripShebangOrBOM(content) {
if (content.charCodeAt(0) === 0xFEFF) {
return content.slice(1);
}
return stripShebang(content);
}
const builtinLibs = [ const builtinLibs = [
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'crypto', 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'crypto',
'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https', 'net', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https', 'net',
@ -137,5 +148,6 @@ module.exports = {
makeRequireFunction, makeRequireFunction,
normalizeReferrerURL, normalizeReferrerURL,
stripBOM, stripBOM,
stripShebang stripShebang,
stripShebangOrBOM,
}; };

View File

@ -40,7 +40,7 @@ const {
makeRequireFunction, makeRequireFunction,
normalizeReferrerURL, normalizeReferrerURL,
stripBOM, stripBOM,
stripShebang stripShebangOrBOM,
} = require('internal/modules/cjs/helpers'); } = require('internal/modules/cjs/helpers');
const { getOptionValue } = require('internal/options'); const { getOptionValue } = require('internal/options');
const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinks = getOptionValue('--preserve-symlinks');
@ -59,7 +59,7 @@ const {
const { validateString } = require('internal/validators'); const { validateString } = require('internal/validators');
const pendingDeprecation = getOptionValue('--pending-deprecation'); const pendingDeprecation = getOptionValue('--pending-deprecation');
module.exports = Module; module.exports = { wrapSafe, Module };
let asyncESM; let asyncESM;
let ModuleJob; let ModuleJob;
@ -690,22 +690,10 @@ Module.prototype.require = function(id) {
var resolvedArgv; var resolvedArgv;
let hasPausedEntry = false; let hasPausedEntry = false;
// Run the file contents in the correct scope or sandbox. Expose function wrapSafe(filename, content) {
// the correct helper variables (require, module, exports) to
// the file.
// Returns exception, if any.
Module.prototype._compile = function(content, filename) {
if (manifest) {
const moduleURL = pathToFileURL(filename);
manifest.assertIntegrity(moduleURL, content);
}
content = stripShebang(content);
let compiledWrapper;
if (patched) { if (patched) {
const wrapper = Module.wrap(content); const wrapper = Module.wrap(content);
compiledWrapper = vm.runInThisContext(wrapper, { return vm.runInThisContext(wrapper, {
filename, filename,
lineOffset: 0, lineOffset: 0,
displayErrors: true, displayErrors: true,
@ -714,8 +702,9 @@ Module.prototype._compile = function(content, filename) {
return loader.import(specifier, normalizeReferrerURL(filename)); return loader.import(specifier, normalizeReferrerURL(filename));
} : undefined, } : undefined,
}); });
} else { }
compiledWrapper = compileFunction(
const compiledWrapper = compileFunction(
content, content,
filename, filename,
0, 0,
@ -732,6 +721,7 @@ Module.prototype._compile = function(content, filename) {
'__dirname', '__dirname',
] ]
); );
if (experimentalModules) { if (experimentalModules) {
const { callbackMap } = internalBinding('module_wrap'); const { callbackMap } = internalBinding('module_wrap');
callbackMap.set(compiledWrapper, { callbackMap.set(compiledWrapper, {
@ -741,8 +731,25 @@ Module.prototype._compile = function(content, filename) {
} }
}); });
} }
return compiledWrapper;
} }
// Run the file contents in the correct scope or sandbox. Expose
// the correct helper variables (require, module, exports) to
// the file.
// Returns exception, if any.
Module.prototype._compile = function(content, filename) {
if (manifest) {
const moduleURL = pathToFileURL(filename);
manifest.assertIntegrity(moduleURL, content);
}
// Strip after manifest integrity check
content = stripShebangOrBOM(content);
const compiledWrapper = wrapSafe(filename, content);
var inspectorWrapper = null; var inspectorWrapper = null;
if (getOptionValue('--inspect-brk') && process._eval == null) { if (getOptionValue('--inspect-brk') && process._eval == null) {
if (!resolvedArgv) { if (!resolvedArgv) {
@ -782,7 +789,7 @@ Module.prototype._compile = function(content, filename) {
// Native extension for .js // Native extension for .js
Module._extensions['.js'] = function(module, filename) { Module._extensions['.js'] = function(module, filename) {
const content = fs.readFileSync(filename, 'utf8'); const content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename); module._compile(content, filename);
}; };

View File

@ -11,10 +11,9 @@ const {
const { NativeModule } = require('internal/bootstrap/loaders'); const { NativeModule } = require('internal/bootstrap/loaders');
const { const {
stripShebang,
stripBOM stripBOM
} = require('internal/modules/cjs/helpers'); } = require('internal/modules/cjs/helpers');
const CJSModule = require('internal/modules/cjs/loader'); const CJSModule = require('internal/modules/cjs/loader').Module;
const internalURLModule = require('internal/url'); const internalURLModule = require('internal/url');
const createDynamicModule = require( const createDynamicModule = require(
'internal/modules/esm/create_dynamic_module'); 'internal/modules/esm/create_dynamic_module');
@ -48,7 +47,7 @@ translators.set('module', async function moduleStrategy(url) {
const source = `${await readFileAsync(new URL(url))}`; const source = `${await readFileAsync(new URL(url))}`;
debug(`Translating StandardModule ${url}`); debug(`Translating StandardModule ${url}`);
const { ModuleWrap, callbackMap } = internalBinding('module_wrap'); const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
const module = new ModuleWrap(stripShebang(source), url); const module = new ModuleWrap(source, url);
callbackMap.set(module, { callbackMap.set(module, {
initializeImportMeta, initializeImportMeta,
importModuleDynamically, importModuleDynamically,

View File

@ -53,7 +53,7 @@ function evalModule(source, print) {
} }
function evalScript(name, body, breakFirstLine, print) { function evalScript(name, body, breakFirstLine, print) {
const CJSModule = require('internal/modules/cjs/loader'); const CJSModule = require('internal/modules/cjs/loader').Module;
const { kVmBreakFirstLineSymbol } = require('internal/util'); const { kVmBreakFirstLineSymbol } = require('internal/util');
const cwd = tryGetCwd(); const cwd = tryGetCwd();

View File

@ -24,7 +24,7 @@ function sendInspectorCommand(cb, onError) {
function installConsoleExtensions(commandLineApi) { function installConsoleExtensions(commandLineApi) {
if (commandLineApi.require) { return; } if (commandLineApi.require) { return; }
const { tryGetCwd } = require('internal/process/execution'); const { tryGetCwd } = require('internal/process/execution');
const CJSModule = require('internal/modules/cjs/loader'); const CJSModule = require('internal/modules/cjs/loader').Module;
const { makeRequireFunction } = require('internal/modules/cjs/helpers'); const { makeRequireFunction } = require('internal/modules/cjs/helpers');
const consoleAPIModule = new CJSModule('<inspector console>'); const consoleAPIModule = new CJSModule('<inspector console>');
const cwd = tryGetCwd(); const cwd = tryGetCwd();

View File

@ -1,3 +1,3 @@
'use strict'; 'use strict';
module.exports = require('internal/modules/cjs/loader'); module.exports = require('internal/modules/cjs/loader').Module;

View File

@ -65,7 +65,7 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { Interface } = require('readline'); const { Interface } = require('readline');
const { Console } = require('console'); const { Console } = require('console');
const CJSModule = require('internal/modules/cjs/loader'); const CJSModule = require('internal/modules/cjs/loader').Module;
const domain = require('domain'); const domain = require('domain');
const debug = require('internal/util/debuglog').debuglog('repl'); const debug = require('internal/util/debuglog').debuglog('repl');
const { const {

2
test/fixtures/utf8-bom-shebang.js vendored Normal file
View File

@ -0,0 +1,2 @@
#!shebang
module.exports = 42;

2
test/fixtures/utf8-shebang-bom.js vendored Normal file
View File

@ -0,0 +1,2 @@
#!shebang
module.exports = 42;

View File

@ -352,6 +352,13 @@ process.on('exit', function() {
assert.strictEqual(require('../fixtures/utf8-bom.js'), 42); assert.strictEqual(require('../fixtures/utf8-bom.js'), 42);
assert.strictEqual(require('../fixtures/utf8-bom.json'), 42); assert.strictEqual(require('../fixtures/utf8-bom.json'), 42);
// Loading files with BOM + shebang.
// See https://github.com/nodejs/node/issues/27767
assert.throws(() => {
require('../fixtures/utf8-bom-shebang.js');
}, { name: 'SyntaxError' });
assert.strictEqual(require('../fixtures/utf8-shebang-bom.js'), 42);
// Error on the first line of a module should // Error on the first line of a module should
// have the correct line number // have the correct line number
assert.throws(function() { assert.throws(function() {