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: parse options.env more similarly to process.env #98

Merged
merged 4 commits into from
Dec 21, 2023

Conversation

thecodrr
Copy link
Contributor

@thecodrr thecodrr commented Dec 17, 2023

This fix attempts to parse options.env in a similar way to how it is parsed in child_process.spawn, namely by doing a simple sorted case-insensitive lookup for path and pathext.

The intent is to support folks who pass in to opts { env: ...process.env}, as this removes the built in case insensitivity that is present on the original process.env object.

@thecodrr thecodrr requested a review from a team as a code owner December 17, 2023 16:41
@thecodrr thecodrr changed the title Speed up which lookups on Windows machines Speed up which lookups on Windows machines Dec 17, 2023
@wraithgar
Copy link
Member

Is this handling the case where env is passed in on a Windows system that is using Path? Won't process.env.PATH match that anyways? I'm a bit confused where the performance hit is here.

@thecodrr
Copy link
Contributor Author

@wraithgar process.env.PATH is not equal to process.env.Path. When Path is available, PATH is undefined.

@wraithgar
Copy link
Member

We have always operated under the assumption that the windows environment variables were case insensitive.

The docs at https://nodejs.org/api/process.html#process_process_env even say:

On Windows operating systems, environment variables are case-insensitive.

So, process.env.Path is identical to process.env.PATH but only when accessing it through process.env. If something were to copy process.env into a new novel object, then it of course would only be accessible via the original name from when it was accessed from process.env.

I think the answer to my first question then is "yes" this is handling the case where options.env has been copied from the environment, and this case insensitivity is now lost. This is quite a subtle bug and would definitely benefit from some sort of comment to prevent future accidental removal of that code.

This will also need a test added for coverage purposes.

@wraithgar
Copy link
Member

If process.env.PATH truly were empty on a windows system that used Path that would be news to a lot of people. Are you sure you're not looking at a copy of env?

@thecodrr
Copy link
Contributor Author

If process.env.PATH truly were empty on a windows system that used Path that would be news to a lot of people. Are you sure you're not looking at a copy of env?

You are right. I was looking at options.env not process.env which is different. However, on Windows machines if you spread process.env it uses the case that is present in the system environment variables. On my machine, that happens to be Path hence the bug.

@wraithgar
Copy link
Member

Phew perfect, I think we understand the bug now. An affordance in that conditional to look for Path would help a lot of folks who spread env out. The test should be relatively straightforward too I hope.

@thecodrr
Copy link
Contributor Author

@wraithgar or maybe we can add a case-insensitive lookup function for finding env variables or would that be unnecessary? That would handle all sorts of weird pAtH casings.

@wraithgar
Copy link
Member

Given as how we are trying to interpret opts.env identically to child_process I don't think that your suggestion is too far fetched.

It looks like node does special parsing of this parameter itself to account for this special quirk of environment variables

https://github.com/nodejs/node/blob/65e70bf54e7ff4704d244c55ad319571fdd438ae/lib/child_process.js#L673-L694

  let envKeys = [];
  // Prototype values are intentionally included.
  for (const key in env) {
    ArrayPrototypePush(envKeys, key);
  }

  if (process.platform === 'win32') {
    // On Windows env keys are case insensitive. Filter out duplicates,
    // keeping only the first one (in lexicographic order)
    const sawKey = new SafeSet();
    envKeys = ArrayPrototypeFilter(
      ArrayPrototypeSort(envKeys),
      (key) => {
        const uppercaseKey = StringPrototypeToUpperCase(key);
        if (sawKey.has(uppercaseKey)) {
          return false;
        }
        sawKey.add(uppercaseKey);
        return true;
      },
    );
  }

This same logic may need to apply to both the path and pathext parameters we try to pull from opts.env

@thecodrr
Copy link
Contributor Author

thecodrr commented Dec 19, 2023

@wraithgar what would be the best way to write a test case for this? Can we convert pathToInitial into an exported function for test purposes?

@wraithgar
Copy link
Member

No that would just be testing how the code is written, not what it ultimately does.

There is a test in test/shell.js that passes in env. We can duplicate that and edit the env parameter to cover the use cases we're wanting to test.

In order to match node the keys in env should probably be sorted.

@thecodrr
Copy link
Contributor Author

@wraithgar should be ready

@wraithgar wraithgar changed the title Speed up which lookups on Windows machines fix: parse options.env more similarly to process.env Dec 21, 2023
@wraithgar wraithgar merged commit 46fad5a into npm:main Dec 21, 2023
22 of 23 checks passed
@github-actions github-actions bot mentioned this pull request Dec 21, 2023
@thecodrr thecodrr deleted the faster-which-lookup branch December 21, 2023 19:38
wraithgar pushed a commit that referenced this pull request Jan 4, 2024
🤖 I have created a release *beep* *boop*
---


## [7.0.1](v7.0.0...v7.0.1)
(2023-12-21)

### Bug Fixes

*
[`46fad5a`](46fad5a)
[#98](#98) parse `options.env`
more similarly to `process.env` (#98) (@thecodrr)

### Chores

*
[`d3ba687`](d3ba687)
[#97](#97) postinstall for
dependabot template-oss PR (@lukekarrys)
*
[`cf18492`](cf18492)
[#97](#97) bump
@npmcli/template-oss from 4.21.1 to 4.21.3 (@dependabot[bot])
*
[`c72524e`](c72524e)
[#95](#95) postinstall for
dependabot template-oss PR (@lukekarrys)
*
[`8102197`](8102197)
[#95](#95) bump
@npmcli/template-oss from 4.19.0 to 4.21.1 (@dependabot[bot])
*
[`3d54f38`](3d54f38)
[#76](#76) postinstall for
dependabot template-oss PR (@lukekarrys)
*
[`ca63a18`](ca63a18)
[#76](#76) bump
@npmcli/template-oss from 4.18.1 to 4.19.0 (@dependabot[bot])
*
[`e3e359f`](e3e359f)
[#74](#74) postinstall for
dependabot template-oss PR (@lukekarrys)
*
[`cc8e9c9`](cc8e9c9)
[#74](#74) bump
@npmcli/template-oss from 4.18.0 to 4.18.1 (@dependabot[bot])

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants