Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(commonjs): node_modules lookup depth limit to avoid infinite loops #985

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/commonjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,14 @@ For these situations, you can change Rollup's behaviour either globally or per m

To change this for individual modules, you can supply a function for `requireReturnsDefault` instead. This function will then be called once for each required ES module or external dependency with the corresponding id and allows you to return different values for different modules.

### nodeModulesLookupDepth

Type: `number`<br>
Default: `15`

Max depth for `node_modules` lookup algorithm in the virtual `require` realm.
This is here for safety, in case of a dynamic require to something that does not exist.

## Using with @rollup/plugin-node-resolve

Since most CommonJS packages you are importing are probably dependencies in `node_modules`, you may need to use [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/master/packages/node-resolve):
Expand Down
19 changes: 11 additions & 8 deletions packages/commonjs/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function commonjsRequire (path) {
}
`;

const getDynamicHelpers = (ignoreDynamicRequires) => `
const getDynamicHelpers = (ignoreDynamicRequires, nodeModulesLookupDepth) => `
export function createModule(modulePath) {
return {
path: modulePath,
Expand Down Expand Up @@ -172,7 +172,8 @@ export function commonjsResolveImpl (path, originalModuleDir, testCache) {
if (path[0] === '/') {
originalModuleDir = '/';
}
while (true) {
let depth = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let depth = 0;
var depth = 0;

Shouldn’t this be es5?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although actually I already see another let and it’s already a template string 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's inside the generated code, not the plugin's code, should be backwards compatible :-)

Copy link
Member

@tjenkinson tjenkinson Oct 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeh so rollup itself tries to be compatible with ES3 by default (see rollup/rollup#4215), so I think ideally this should be var, and we shouldn't be using template strings (edit: template string is not in the output)

while (depth++ < ${nodeModulesLookupDepth}) {
if (!shouldTryNodeModules) {
relPath = originalModuleDir ? normalize(originalModuleDir + '/' + path) : path;
} else if (originalModuleDir) {
Expand All @@ -181,10 +182,6 @@ export function commonjsResolveImpl (path, originalModuleDir, testCache) {
relPath = normalize(join('node_modules', path));
}

if (relPath.endsWith('/..')) {
break; // Travelled too far up, avoid infinite loop
}

for (let extensionIndex = 0; extensionIndex < CHECKED_EXTENSIONS.length; extensionIndex++) {
const resolvedPath = relPath + CHECKED_EXTENSIONS[extensionIndex];
if (DYNAMIC_REQUIRE_CACHE[resolvedPath]) {
Expand Down Expand Up @@ -257,8 +254,14 @@ commonjsRequire.cache = DYNAMIC_REQUIRE_CACHE;
commonjsRequire.resolve = commonjsResolve;
`;

export function getHelpersModule(isDynamicRequireModulesEnabled, ignoreDynamicRequires) {
export function getHelpersModule(
isDynamicRequireModulesEnabled,
ignoreDynamicRequires,
nodeModulesLookupDepth
) {
return `${HELPERS}${
isDynamicRequireModulesEnabled ? getDynamicHelpers(ignoreDynamicRequires) : HELPER_NON_DYNAMIC
isDynamicRequireModulesEnabled
? getDynamicHelpers(ignoreDynamicRequires, nodeModulesLookupDepth)
: HELPER_NON_DYNAMIC
}`;
}
7 changes: 6 additions & 1 deletion packages/commonjs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default function commonjs(options = {}) {
const {
ignoreGlobal,
ignoreDynamicRequires,
nodeModulesLookupDepth,
requireReturnsDefault: requireReturnsDefaultOption,
esmExternals
} = options;
Expand Down Expand Up @@ -168,7 +169,11 @@ export default function commonjs(options = {}) {

load(id) {
if (id === HELPERS_ID) {
return getHelpersModule(isDynamicRequireModulesEnabled, ignoreDynamicRequires);
return getHelpersModule(
isDynamicRequireModulesEnabled,
ignoreDynamicRequires,
typeof nodeModulesLookupDepth === 'number' ? nodeModulesLookupDepth : 15
);
}

if (id.startsWith(HELPERS_ID)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
description: 'resolves both windows and posix paths',
pluginOptions: {
dynamicRequireTargets: [
'fixtures/function/dynamic-require-alias-hack/stub.js'
],
ignoreDynamicRequires: true
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable global-require */
// noinspection UnnecessaryLocalVariableJS

// A hack used in many old libraries, saying "workaround to exclude package from browserify list."
// Will bypass rollup-commonjs finding out that this is a require that should not go through the plugin, and will do an infinite search.
let _require = require;
let buffer = _require('buffer');

t.is(buffer, require('buffer'));