From 314a161fec527acf3a3a4ba4c5ee41c7c0429658 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 19 Oct 2026 12:28:54 +0100 Subject: [PATCH] Use `lstat` instead of `stat` --- lib/entry-points.js | 10 ++++- src/languages/traced.test.ts | 33 +++++++++------ src/platform.test.ts | 81 ++++++++++++++++++++++++++---------- src/platform.ts | 10 ++++- 4 files changed, 97 insertions(+), 37 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 8a63a7553..024343857 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -175732,7 +175732,15 @@ var XCODE_APP_FILENAME_PATTERN = new RegExp( ); function xcodeVersion(logger2) { try { - const stats = import_fs.default.statSync(XCODE_SELECT_LINK_PATH); + const stats = import_fs.default.lstatSync(XCODE_SELECT_LINK_PATH, { + throwIfNoEntry: false + }); + if (stats === void 0) { + logger2.debug( + `'${XCODE_SELECT_LINK_PATH}' does not exist. Xcode is probably not installed.` + ); + return void 0; + } if (!stats.isSymbolicLink()) { logger2.warning( `${XCODE_SELECT_LINK_PATH} exists, but is not a symbolic link.` diff --git a/src/languages/traced.test.ts b/src/languages/traced.test.ts index 459db3789..558b1c366 100644 --- a/src/languages/traced.test.ts +++ b/src/languages/traced.test.ts @@ -289,8 +289,8 @@ test.serial( const env = getTestEnv(); env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir); - const statSync = sinon - .stub(fs, "statSync") + const lstatSync = sinon + .stub(fs, "lstatSync") .returns({ isSymbolicLink: () => true } as fs.Stats); const readlinkSync = sinon .stub(fs, "readlinkSync") @@ -317,8 +317,11 @@ test.serial( codeql, ); - t.is(statSync.callCount, 1); - t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); t.is(readlinkSync.callCount, 1); t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]); @@ -339,8 +342,8 @@ test.serial( const env = getTestEnv(); env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir); - const statSync = sinon - .stub(fs, "statSync") + const lstatSync = sinon + .stub(fs, "lstatSync") .returns({ isSymbolicLink: () => true } as fs.Stats); const readlinkSync = sinon .stub(fs, "readlinkSync") @@ -367,8 +370,11 @@ test.serial( codeql, ); - t.is(statSync.callCount, 1); - t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); t.is(readlinkSync.callCount, 1); t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]); @@ -409,8 +415,8 @@ test.serial( const env = getTestEnv(); env.set(ActionsEnvVars.RUNNER_TEMP, tmpDir); - const statSync = sinon - .stub(fs, "statSync") + const lstatSync = sinon + .stub(fs, "lstatSync") .returns({ isSymbolicLink: () => true } as fs.Stats); const readlinkSync = sinon .stub(fs, "readlinkSync") @@ -442,8 +448,11 @@ test.serial( ), ); - t.is(statSync.callCount, 1); - t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); t.is(readlinkSync.callCount, 1); t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]); diff --git a/src/platform.test.ts b/src/platform.test.ts index 5de468d82..7ccb53da3 100644 --- a/src/platform.test.ts +++ b/src/platform.test.ts @@ -74,11 +74,31 @@ test("macOSVersion returns semver if parsing succeeds", (t) => { test.serial("xcodeVersion returns undefined if symlink doesn't exist", (t) => { const logger = new RecordingLogger(); - const statSync = sinon.stub(fs, "statSync").throws(new Error("ENOENT")); + const lstatSync = sinon.stub(fs, "lstatSync").returns(undefined); t.is(xcodeVersion(logger), undefined); - t.is(statSync.callCount, 1); - t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); + + checkExpectedLogMessages(t, logger.messages, [ + "Xcode is probably not installed.", + ]); +}); + +test.serial("xcodeVersion returns undefined if lstatSync throws", (t) => { + const logger = new RecordingLogger(); + + const lstatSync = sinon.stub(fs, "lstatSync").throws(new Error("ENOENT")); + + t.is(xcodeVersion(logger), undefined); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); checkExpectedLogMessages(t, logger.messages, [ "Unable to determine Xcode version: ENOENT", @@ -88,13 +108,16 @@ test.serial("xcodeVersion returns undefined if symlink doesn't exist", (t) => { test.serial("xcodeVersion returns undefined if file is not a symlink", (t) => { const logger = new RecordingLogger(); - const statSync = sinon - .stub(fs, "statSync") + const lstatSync = sinon + .stub(fs, "lstatSync") .returns({ isSymbolicLink: () => false } as fs.Stats); t.is(xcodeVersion(logger), undefined); - t.is(statSync.callCount, 1); - t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); checkExpectedLogMessages(t, logger.messages, [ "exists, but is not a symbolic link", @@ -106,14 +129,17 @@ test.serial( (t) => { const logger = new RecordingLogger(); - const statSync = sinon - .stub(fs, "statSync") + const lstatSync = sinon + .stub(fs, "lstatSync") .returns({ isSymbolicLink: () => true } as fs.Stats); const readlinkSync = sinon.stub(fs, "readlinkSync").returns(""); t.is(xcodeVersion(logger), undefined); - t.is(statSync.callCount, 1); - t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); t.is(readlinkSync.callCount, 1); t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]); @@ -128,16 +154,19 @@ test.serial( (t) => { const logger = new RecordingLogger(); - const statSync = sinon - .stub(fs, "statSync") + const lstatSync = sinon + .stub(fs, "lstatSync") .returns({ isSymbolicLink: () => true } as fs.Stats); const readlinkSync = sinon .stub(fs, "readlinkSync") .returns("/Applications/Xcode.app/Contents/Developer"); t.is(xcodeVersion(logger), undefined); - t.is(statSync.callCount, 1); - t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); t.is(readlinkSync.callCount, 1); t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]); @@ -150,16 +179,19 @@ test.serial( test.serial("xcodeVersion returns undefined if match can't be parsed", (t) => { const logger = new RecordingLogger(); - const statSync = sinon - .stub(fs, "statSync") + const lstatSync = sinon + .stub(fs, "lstatSync") .returns({ isSymbolicLink: () => true } as fs.Stats); const readlinkSync = sinon .stub(fs, "readlinkSync") .returns("/Applications/Xcode_00.0.app/Contents/Developer"); t.is(xcodeVersion(logger), undefined); - t.is(statSync.callCount, 1); - t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); t.is(readlinkSync.callCount, 1); t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]); @@ -171,8 +203,8 @@ test.serial("xcodeVersion returns undefined if match can't be parsed", (t) => { test.serial("xcodeVersion returns version from resolved path", (t) => { const logger = new RecordingLogger(); - const statSync = sinon - .stub(fs, "statSync") + const lstatSync = sinon + .stub(fs, "lstatSync") .returns({ isSymbolicLink: () => true } as fs.Stats); const readlinkSync = sinon .stub(fs, "readlinkSync") @@ -185,8 +217,11 @@ test.serial("xcodeVersion returns version from resolved path", (t) => { t.is(result.minor, 4); } - t.is(statSync.callCount, 1); - t.deepEqual(statSync.args[0], [XCODE_SELECT_LINK_PATH]); + t.is(lstatSync.callCount, 1); + t.deepEqual(lstatSync.args[0], [ + XCODE_SELECT_LINK_PATH, + { throwIfNoEntry: false }, + ]); t.is(readlinkSync.callCount, 1); t.deepEqual(readlinkSync.args[0], [XCODE_SELECT_LINK_PATH]); }); diff --git a/src/platform.ts b/src/platform.ts index d5c77c9f7..01725c4cc 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -76,8 +76,16 @@ export function xcodeVersion(logger: Logger): semver.SemVer | undefined { // Stat the expected symbolic link to check that it exists and is a symbolic link. // The `readlinkSync` call below returns an empty string in either case and so // this check allows us to distinguish between the two cases. - const stats = fs.statSync(XCODE_SELECT_LINK_PATH); + const stats = fs.lstatSync(XCODE_SELECT_LINK_PATH, { + throwIfNoEntry: false, + }); + if (stats === undefined) { + logger.debug( + `'${XCODE_SELECT_LINK_PATH}' does not exist. Xcode is probably not installed.`, + ); + return undefined; + } if (!stats.isSymbolicLink()) { logger.warning( `${XCODE_SELECT_LINK_PATH} exists, but is not a symbolic link.`,