From d211d5fdb81721f4253e3e566a4edd8b78a776b3 Mon Sep 17 00:00:00 2001 From: Cassidy Marble Date: Tue, 6 Oct 2026 12:43:22 -0300 Subject: [PATCH] [Perf] `nvm_is_version_installed`: check the modern layout's path before the full lookup --- nvm.sh | 16 ++++++++ test/fast/Unit tests/nvm_is_version_installed | 41 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/nvm.sh b/nvm.sh index ecc39e4b..d4211830 100755 --- a/nvm.sh +++ b/nvm.sh @@ -233,6 +233,22 @@ nvm_is_version_installed() { if [ -z "${1-}" ]; then return 1 fi + + # Fast path: versions in the modern layout live at a directly-computable + # path. Checking it first avoids forking `nvm_version_path` (and the `awk` + # version comparison inside it) and `nvm_get_os` on shell startup. Versions + # below `v0.12` are skipped, since they must not count from the modern + # layout; those, io.js, and Windows binaries fall through to the full lookup + # below, which also rejects path traversal. + case "/${1}/" in + */../* | /v0.[0-9].* | /v0.1[01].* | /0.[0-9].* | /0.1[01].*) ;; + *) + if [ -x "${NVM_DIR}/versions/node/${1}/bin/node" ]; then + return 0 + fi + ;; + esac + local NVM_NODE_BINARY NVM_NODE_BINARY='node' if [ "_$(nvm_get_os)" = '_win' ]; then diff --git a/test/fast/Unit tests/nvm_is_version_installed b/test/fast/Unit tests/nvm_is_version_installed index 8b6e775c..7fe01313 100755 --- a/test/fast/Unit tests/nvm_is_version_installed +++ b/test/fast/Unit tests/nvm_is_version_installed @@ -45,6 +45,47 @@ type nvm_is_version_installed > /dev/null 2>&1 || die 'nvm_is_version_installed check_version '12.0.0' 'node' +# the pre-0.12 layout is still found via the fallback +OLD_VERSION='0.11.15' +mkdir -p "$NVM_DIR/$OLD_VERSION/bin" +touch "$NVM_DIR/$OLD_VERSION/bin/node" +chmod +x "$NVM_DIR/$OLD_VERSION/bin/node" +nvm_is_version_installed "$OLD_VERSION" || die "nvm_is_version_installed $OLD_VERSION should work with the pre-0.12 layout" + +# a pre-0.12 version does not count as installed from the modern layout +for STRAY_VERSION in '0.10.48' 'v0.10.48'; do + mkdir -p "$NODE_PATH/$STRAY_VERSION/bin" + touch "$NODE_PATH/$STRAY_VERSION/bin/node" + chmod +x "$NODE_PATH/$STRAY_VERSION/bin/node" + ! nvm_is_version_installed "$STRAY_VERSION" || die "nvm_is_version_installed $STRAY_VERSION should not find a pre-0.12 version in the modern layout" +done + +# io.js versions live in a separate tree and are still found via the fallback +IOJS_VERSION='v2.0.0' +mkdir -p "$NVM_DIR/versions/io.js/$IOJS_VERSION/bin" +touch "$NVM_DIR/versions/io.js/$IOJS_VERSION/bin/node" +chmod +x "$NVM_DIR/versions/io.js/$IOJS_VERSION/bin/node" +nvm_is_version_installed "iojs-$IOJS_VERSION" || die "nvm_is_version_installed iojs-$IOJS_VERSION should work via the fallback" + +# the modern layout is found without shelling out to detect the OS +FAST_VERSION='14.0.0' +! nvm_is_version_installed "$FAST_VERSION" || die "nvm_is_version_installed $FAST_VERSION should fail before it is installed" +nvm_get_os() { + die 'nvm_is_version_installed must not call nvm_get_os when the modern-layout binary exists' +} +mkdir -p "$NODE_PATH/$FAST_VERSION/bin" +touch "$NODE_PATH/$FAST_VERSION/bin/node" +chmod +x "$NODE_PATH/$FAST_VERSION/bin/node" +nvm_is_version_installed "$FAST_VERSION" || die "nvm_is_version_installed $FAST_VERSION should succeed via the fast path" +# 0.12.x is the first version in the modern layout +for FAST_VERSION in '0.12.18' 'v0.12.18'; do + mkdir -p "$NODE_PATH/$FAST_VERSION/bin" + touch "$NODE_PATH/$FAST_VERSION/bin/node" + chmod +x "$NODE_PATH/$FAST_VERSION/bin/node" + nvm_is_version_installed "$FAST_VERSION" || die "nvm_is_version_installed $FAST_VERSION should succeed via the fast path" +done +unset -f nvm_get_os + # Checking for Windows nvm_get_os() { echo "win"