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

module: fix wrong condition in early return logic for node_module path #6670

Closed
wants to merge 3 commits into from

Conversation

hefangshi
Copy link
Contributor

@hefangshi hefangshi commented May 10, 2016

Checklist
  • tests and code linting passes
  • a test and/or benchmark is included
  • the commit message follows commit guidelines
Affected core subsystem(s)

module

Description of change

the p < nmLen condition will fail when a module's name is end with
node_modules like foo_node_modules. The old logic will miss the
foo_node_modules/node_modules in node_modules paths.

TL;TR, a module named like foo_node_modules can't require any module
in his node_modules folder.

Here is a demo which compared the new nodeModulePaths with the old one.

const path = require('path');
var nmChars = [115, 101, 108, 117, 100, 111, 109, 95, 101, 100, 111, 110];
var nmLen = nmChars.length;

function nodeModulePaths(from) {
    // guarantee that 'from' is absolute.
    from = path.resolve(from);
    // Return early not only to avoid unnecessary work, but to *avoid* returning
    // an array of two items for a root: [ '//node_modules', '/node_modules' ]
    if (from === '/')
        return ['/node_modules'];

    // note: this approach *only* works when the path is guaranteed
    // to be absolute.  Doing a fully-edge-case-correct path.split
    // that works on both Windows and Posix is non-trivial.
    const paths = [];
    var p = 0;
    var last = from.length;
    for (var i = from.length - 1; i >= 0; --i) {
        const code = from.charCodeAt(i);
        if (code === 47 /*/*/ ) {
            if (p !== nmLen)
                paths.push(from.slice(0, last) + '/node_modules');
            last = i;
            p = 0;
        }
        else if (p !== -1 && p < nmLen) {
            if (nmChars[p] === code) {
                ++p;
            }
            else {
                p = -1;
            }
        }
    }

    return paths;
};

const splitRe = process.platform === 'win32' ? /[\/\\]/ : /\//;
nodeModulePathsOld = function (from) {
    // guarantee that 'from' is absolute.
    from = path.resolve(from);
    // note: this approach *only* works when the path is guaranteed
    // to be absolute.  Doing a fully-edge-case-correct path.split
    // that works on both Windows and Posix is non-trivial.
    var paths = [];
    var parts = from.split(splitRe);
    for (var tip = parts.length - 1; tip >= 0; tip--) {
        // don't search in .../node_modules/node_modules
        if (parts[tip] === 'node_modules') continue;
        var dir = parts.slice(0, tip + 1).concat('node_modules').join(path.sep);
        paths.push(dir);
    }
    return paths;
}

console.log('new:', nodeModulePaths('/Users/hefangshi/test/foo_node_modules'));
console.log('old:', nodeModulePathsOld('/Users/hefangshi/test/foo_node_modules'));

Output:

new: [ '/Users/hefangshi/test/node_modules',
  '/Users/hefangshi/node_modules',
  '/Users/node_modules' ]
old: [ '/Users/hefangshi/test/foo_node_modules/node_modules',
  '/Users/hefangshi/test/node_modules',
  '/Users/hefangshi/node_modules',
  '/Users/node_modules',
  '/node_modules' ]

The main reason is the p < nmLen condition will ignore foo_node_modules and simply
think it should be /node_modules, and won't search /Users/hefangshi/test/foo_node_modules/node_modules for dependency.

I already tested in mac and windows, but the testcases were not very full edge tested.

@mscdex please have a look on it.

@mscdex mscdex added the module Issues and PRs related to the module subsystem. label May 10, 2016
@mscdex
Copy link
Contributor

mscdex commented May 10, 2016

@Fishrock123
Copy link
Contributor

Fishrock123 commented May 10, 2016

Aside: Why do we need/have a land-on-v6.x label since it's not in LTS?

@Fishrock123
Copy link
Contributor

Test seems to fail on windows:

not ok 144 test-module-nodemodulepaths.js
# 
# assert.js:90
# throw new assert.AssertionError({
# ^
# AssertionError: [ 'C:\\Users\\Rocko Artischocko\\node_stuff\\foo\\node_modules',
# 'C:\\Users\\Rocko Artischocko\\node_stuff\\node_modules',
# ' deepStrictEqual [ 'C:\\Users\\Rocko Artischocko\\node_stuff\\foo\\node_modules',
# 'C:\\Users\\Rocko Artischocko\\node_stuff\\node_modules',
# '
# at platformCases.forEach (C:\workspace\node-test-binary-windows\RUN_SUBSET\3\VS_VERSION\vcbt2015\label\win10\test\parallel\test-module-nodemodulepaths.js:52:10)
# at Array.forEach (native)
# at Object.<anonymous> (C:\workspace\node-test-binary-windows\RUN_SUBSET\3\VS_VERSION\vcbt2015\label\win10\test\parallel\test-module-nodemodulepaths.js:50:15)
# at Module._compile (module.js:539:32)
# at Object.Module._extensions..js (module.js:548:10)
# at Module.load (module.js:456:32)
# at tryModuleLoad (module.js:415:12)
# at Function.Module._load (module.js:407:3)
# at Function.Module.runMain (module.js:573:10)
# at startup (node.js:160:18)

@hefangshi
Copy link
Contributor Author

hefangshi commented May 10, 2016

My fault, it's another different between regex version and manual parsers.

The node_modules in root path like C:\\node_modules or /node_modules was missing.

@hefangshi hefangshi force-pushed the fix-node_modules-paths branch 2 times, most recently from d8e826e to a880856 Compare May 10, 2016 18:54
@hefangshi
Copy link
Contributor Author

Need another CI, @Fishrock123 @mscdex

@mscdex
Copy link
Contributor

mscdex commented May 11, 2016

@mhart
Copy link
Contributor

mhart commented May 11, 2016

This also fixes #6679 FWIW

@Fishrock123
Copy link
Contributor

@mscdex Looks like the linter is failing again, does that just mean this need rebasing?

@Fishrock123
Copy link
Contributor

Looks like the windows tests are still failing: https://ci.nodejs.org/job/node-test-binary-windows/2075/

not ok 145 test-module-nodemodulepaths.js
# 
# assert.js:90
# throw new assert.AssertionError({
# ^
# AssertionError: [ 'C:\\node_modules' ] deepStrictEqual [ 'C:\\\\node_modules', 'C:\\node_modules' ]
# at platformCases.forEach (C:\workspace\node-test-binary-windows\RUN_SUBSET\3\VS_VERSION\vs2013\label\win2008r2\test\parallel\test-module-nodemodulepaths.js:74:10)
# at Array.forEach (native)
# at Object.<anonymous> (C:\workspace\node-test-binary-windows\RUN_SUBSET\3\VS_VERSION\vs2013\label\win2008r2\test\parallel\test-module-nodemodulepaths.js:72:15)
# at Module._compile (module.js:543:32)
# at Object.Module._extensions..js (module.js:552:10)
# at Module.load (module.js:460:32)
# at tryModuleLoad (module.js:419:12)
# at Function.Module._load (module.js:411:3)
# at Function.Module.runMain (module.js:577:10)
# at startup (node.js:160:18)

@mscdex
Copy link
Contributor

mscdex commented May 11, 2016

@Fishrock123 If the linter is failing it means there is a lint problem that needs to be fixed. The output doesn't display because it's written to a tap file. The linter job doesn't have a tap display page yet.

@Fishrock123
Copy link
Contributor

Oh.. @hefangshi please run make jslint. :)

@hefangshi hefangshi force-pushed the fix-node_modules-paths branch from a880856 to 453d066 Compare May 11, 2016 17:21
@hefangshi
Copy link
Contributor Author

hefangshi commented May 11, 2016

@Fishrock123 Done, and I used --amend, so the commit is 453d066767f328dc73d6987cc8d30adcad828b79 . I'm sorry that I didn't have test environment for both linux and windows yesterday, so the code was not properly tested. Now the testcases are all passed in my centos6.3 and win10.

@mscdex
Copy link
Contributor

mscdex commented May 11, 2016

CI: https://ci.nodejs.org/job/node-test-pull-request/2595/

EDIT: it looks like there's something wrong with CI at the moment


// return root node_modules when path is 'D:\\'.
// path.resolve will make sure from.length >=3 in Windows.
if (from[from.length - 1] === '\\' && from[from.length - 2] === ':')
Copy link
Contributor

Choose a reason for hiding this comment

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

What about prepending a from.length >= 2 check here and then changing the string equality checks to .charCodeAt()-based checks? The length check will help prevent a deopt when accessing a non-existent index and the character comparison changes should improve performance a little bit.

Copy link
Contributor Author

@hefangshi hefangshi May 12, 2016

Choose a reason for hiding this comment

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

How about

    if (from.length <= 2 ||
        (from.charCodeAt(from.length - 1) === 92 /*\*/ &&
         from.charCodeAt(from.length - 2) === 58 /*:*/))
      return [from + 'node_modules'];

I'm not sure that if from.length is always >=3 (handled by path.resolve on windows), so the from.length - 1 and from.length - 2 is always exist, will v8 still do deopt on it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually on second thought I think it is probably safe to assume from.length >= 2 on Windows. So just the charCodeAt() changes should be sufficient.

Copy link
Contributor Author

@hefangshi hefangshi May 12, 2016

Choose a reason for hiding this comment

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

Done, should I squash the commits?

Copy link
Contributor

Choose a reason for hiding this comment

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

It wouldn't hurt

@hefangshi hefangshi force-pushed the fix-node_modules-paths branch from 453d066 to 8e57a4e Compare May 12, 2016 05:55
@hefangshi
Copy link
Contributor Author

CI: https://ci.nodejs.org/job/node-test-commit/3315/

Looks like have an irrelevant failure in windows.

@hefangshi
Copy link
Contributor Author

@Fishrock123 will this patch land in 6.1.1? I think this bug should be fixed ASAP.

@Fishrock123
Copy link
Contributor

Sorry, I don't really understand this well enough to review. :(

v6.2.0 is probably coming out today but this will likely not be in a release until next week as it has not landed yet.

@jasnell
Copy link
Member

jasnell commented May 17, 2016

@bnoordhuis ... can you take a look at this one.

@MylesBorins
Copy link
Contributor

@mscdex did you have any more thoughts?

@mscdex
Copy link
Contributor

mscdex commented May 20, 2016

@thealphanerd I'm not familiar enough with the module resolution logic algorithms themselves (much less the Windows side of things) to really comment in detail, so all I can say is to check that CI is ok with it.

@hefangshi
Copy link
Contributor Author

@mscdex But this regression was introduced by you in this commit hefangshi@ae18bbe

That's the reason I ping you at the beginning.

@evanlucas
Copy link
Contributor

Aside: Why do we need/have a land-on-v6.x label since it's not in LTS?

Sorry @Fishrock123, I believe I added that during the v6.2.0 release to make it easier to cherry-pick. Some commits were dependent on semver-major changes.

@Fishrock123
Copy link
Contributor

I removed that label, the proper one for what you are describing is dont-land-on-v6.x,

@Fishrock123
Copy link
Contributor

@hefangshi
Copy link
Contributor Author

So, any idea? Are we just ignore such a basic bug here? I didn't mean to push this pr land, but I think at least this bug should be fixed.

@MylesBorins
Copy link
Contributor

citgm failures are unrelated

/cc @nodejs/ctc thoughts?

@Fishrock123
Copy link
Contributor

I think we should land this. I'm having a hard time understanding it enough to make a clear judgement but the test cases are much more thorough so I don't think that should be an issue.

@nodejs/collaborators Gona land this next week unless someone objects.

@hefangshi
Copy link
Contributor Author

@Fishrock123 any progress?

@rmg
Copy link
Contributor

rmg commented Jul 8, 2016

This looks pretty reasonable to me, especially if the added tests fail without the corresponding fix.

@hefangshi since this hasn't landed yet, would you be able to add in one more test case? I think the WIN and POSIX cases should both include something like /usr/lib/node_modules/npm/node_modules/node-gyp/node_modules/glob/node_modules/minimatch (and whatever the Windows equivalent is of that globally installed npm path). The module path code is a little hairy and should have something "real world" in it that should never break.

@hefangshi
Copy link
Contributor Author

@rmg Done, added a global npm node_modules paths testcases.

@evanlucas
Copy link
Contributor

Running CI again: https://ci.nodejs.org/job/node-test-pull-request/3474/

I just ran into this myself. LGTM

@Trott
Copy link
Member

Trott commented Aug 1, 2016

Only failure in CI is a known flaky.

const paths = [];
var p = 0;
var last = from.length;
for (var i = from.length - 1; i >= 0; --i) {
const code = from.charCodeAt(i);
if (code === 92/*\*/ || code === 47/*/*/) {
// use colon as a split to add driver root node_modules
Copy link
Contributor

Choose a reason for hiding this comment

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

These comments seem to only apply to the colon, added in this PR. Anyone stumbling on this in the future might be confused by it. Could you expand the comment please. Also, please start comments with a capital letter, and terminate with a period.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, feel free to give me some advise about the comments since I don't know English too well.

@hefangshi hefangshi force-pushed the fix-node_modules-paths branch from bef38b8 to 88bbb00 Compare August 2, 2016 04:44
@evanlucas
Copy link
Contributor

@hefangshi can you format the commit message to follow https://github.com/nodejs/node/blob/master/CONTRIBUTING.md#step-3-commit?

Also, we try to avoid using gendered pronouns (like "his"). The last line in the first commit could be updated to something like:

TL;TR, a module with a name that ends with node_modules cannot require 
any other modules in the node_modules folder

hefangshi and others added 2 commits August 2, 2016 21:46
The `p < nmLen` condition will fail when a module's name is end with
`node_modules` like `foo_node_modules`. The old logic will miss the
`foo_node_modules/node_modules` in node_modules paths.

TL;TR, a module named like `foo_node_modules` can't require any module
 in the node_modules folder.
Manual parsers didn't handle the root path on both platform, so push
driver root node_modules when colon is matched in windows to avoid
parse dirver name and direct push `/node_modules` into paths in posix.
@hefangshi hefangshi force-pushed the fix-node_modules-paths branch from 88bbb00 to b589023 Compare August 2, 2016 13:51
@hefangshi
Copy link
Contributor Author

@evanlucas Thanks for your advice, the commit message issue was fixed.

Add a real world global node_modules path test case come from npm's
dependency to make test more effective.
@hefangshi hefangshi force-pushed the fix-node_modules-paths branch from b589023 to ff893e4 Compare August 2, 2016 13:57
@evanlucas
Copy link
Contributor

alright running CI one more time and landing provided it is green https://ci.nodejs.org/job/node-test-pull-request/3581/

evanlucas pushed a commit that referenced this pull request Aug 9, 2016
The `p < nmLen` condition will fail when a module's name is end with
`node_modules` like `foo_node_modules`. The old logic will miss the
`foo_node_modules/node_modules` in node_modules paths.

TL;TR, a module named like `foo_node_modules` can't require any module
 in the node_modules folder.

Fixes: #6679
PR-URL: #6670
Reviewed-By: Evan Lucas <evanlucas@me.com>
@evanlucas
Copy link
Contributor

Landed in 55852e1. Thanks!

@evanlucas evanlucas closed this Aug 9, 2016
@cjihrig cjihrig mentioned this pull request Aug 10, 2016
cjihrig pushed a commit that referenced this pull request Aug 10, 2016
The `p < nmLen` condition will fail when a module's name is end with
`node_modules` like `foo_node_modules`. The old logic will miss the
`foo_node_modules/node_modules` in node_modules paths.

TL;TR, a module named like `foo_node_modules` can't require any module
 in the node_modules folder.

Fixes: #6679
PR-URL: #6670
Reviewed-By: Evan Lucas <evanlucas@me.com>
@cjihrig cjihrig mentioned this pull request Aug 11, 2016
@MylesBorins
Copy link
Contributor

I've marked this as don't land. Please let me know if I was mistaken and it should be backported /cc @cjihrig

@cjihrig
Copy link
Contributor

cjihrig commented Sep 30, 2016

I'm fine with leaving this out of v4, but ping @evanlucas who is more familiar with this commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module Issues and PRs related to the module subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants