From 1280e7e5134ea25f1593a85c09fd2f2304a11a35 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:23:38 +0800 Subject: [PATCH] fix(macho): infer legacy Intel simulator platforms Fixes #6649 --- src/native/parsers/otool.ts | 29 +++++++++++++++-- .../native/nativeMachoHeader.golden.test.ts | 32 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/native/parsers/otool.ts b/src/native/parsers/otool.ts index ee0cc51a19..cf685ac41d 100644 --- a/src/native/parsers/otool.ts +++ b/src/native/parsers/otool.ts @@ -20,6 +20,7 @@ interface ParsedSegment { } interface ParsedLoadCommands { + readonly cpuType: number | null; readonly commands: Array<{ index: number; kind: string; @@ -135,7 +136,7 @@ export const parseOtoolLoadCommands = (raw: string) => { const header = parseHeader(output); if (header === undefined) throw new TypeError("otool output is missing the Mach header"); - const state = createLoadCommandState(header.flags); + const state = createLoadCommandState(header.flags, header.cpuType); for (const block of output.split(/(?=^Load command \d+$)/mu)) parseLoadCommand(block, state); validateLoadCommandFraming(header, state.commands); @@ -264,7 +265,9 @@ const validateLoadCommandFraming = ( const createLoadCommandState = ( headerFlags: readonly string[] | undefined, + cpuType: number | null, ): ParsedLoadCommands => ({ + cpuType, commands: [], segments: [], dependencies: [], @@ -403,13 +406,33 @@ const collectBuild = ( }); return; } - if (kind.startsWith("LC_VERSION_MIN_")) + if (kind.startsWith("LC_VERSION_MIN_")) { + const platform = kind.slice("LC_VERSION_MIN_".length) || null; state.builds.push({ - platform: kind.slice("LC_VERSION_MIN_".length) || null, + platform: legacySimulatorPlatform(platform, state.cpuType), minimum_os: stringField(fields, "version"), sdk: stringField(fields, "sdk"), tools: [], }); + } +}; + +// Apple dyld interprets legacy iOS/tvOS/watchOS minimum-version commands on +// Intel slices as simulator platforms. Newer commands encode this directly. +const legacySimulatorPlatform = ( + platform: string | null, + cpuType: number | null, +): string | null => { + if (![7, 0x01000007].includes(cpuType ?? -1)) return platform; + return ( + ( + { + IPHONEOS: "IOSSIMULATOR", + TVOS: "TVOSSIMULATOR", + WATCHOS: "WATCHOSSIMULATOR", + } as const + )[platform as "IPHONEOS" | "TVOS" | "WATCHOS"] ?? platform + ); }; const collectDependency = ( diff --git a/tests/boundary/providers/native/nativeMachoHeader.golden.test.ts b/tests/boundary/providers/native/nativeMachoHeader.golden.test.ts index efd4d88734..61722e921b 100644 --- a/tests/boundary/providers/native/nativeMachoHeader.golden.test.ts +++ b/tests/boundary/providers/native/nativeMachoHeader.golden.test.ts @@ -267,3 +267,35 @@ Section const truncatedBytes = truncatedCount.replace("2 2 48", "2 1 32"); expect(() => parseOtoolLoadCommands(truncatedBytes)).toThrow(/sizes/u); }); + +it.each([ + ["LC_VERSION_MIN_IPHONEOS", "IOSSIMULATOR"], + ["LC_VERSION_MIN_TVOS", "TVOSSIMULATOR"], + ["LC_VERSION_MIN_WATCHOS", "WATCHOSSIMULATOR"], +])("classifies legacy Intel simulator command %s", (command, platform) => { + const text = [ + "Mach header", + " magic cputype cpusubtype caps filetype ncmds sizeofcmds flags", + " 0xfeedfacf 16777223 3 0x00 2 1 16 0x0", + "Load command 0", + ` cmd ${command}`, + " cmdsize 16", + " version 12.0", + " sdk 12.1", + ].join("\n"); + expect(parseOtoolLoadCommands(text).builds[0]?.platform).toBe(platform); +}); + +it("keeps legacy device platforms on arm64 slices", () => { + const text = [ + "Mach header", + " magic cputype cpusubtype caps filetype ncmds sizeofcmds flags", + " 0xfeedfacf 16777228 0 0x00 2 1 16 0x0", + "Load command 0", + " cmd LC_VERSION_MIN_IPHONEOS", + " cmdsize 16", + " version 12.0", + " sdk 12.1", + ].join("\n"); + expect(parseOtoolLoadCommands(text).builds[0]?.platform).toBe("IPHONEOS"); +});