From 77e71d55ec5b3b44e3db366618b3f51ffd41c736 Mon Sep 17 00:00:00 2001 From: TonyWu <75903587+TonyWu2333@users.noreply.github.com> Date: Tue, 13 Oct 2026 23:49:37 +0800 Subject: [PATCH] fix(native): name numeric Mach-O build-version platforms and tools (#2065) Squash merge of https://github.com/morluto/rea/pull/2065. Co-authored-by: TonyWu <2038838159@qq.com> --- src/native/parsers/otool.ts | 57 +++++++- .../native/nativeLoadKinds.golden.test.ts | 131 ++++++++++++++++++ 2 files changed, 186 insertions(+), 2 deletions(-) diff --git a/src/native/parsers/otool.ts b/src/native/parsers/otool.ts index 3e1c3a90f..8f1ed9dda 100644 --- a/src/native/parsers/otool.ts +++ b/src/native/parsers/otool.ts @@ -63,6 +63,56 @@ const DYLIB_FIELDS = [ const DYLIB_ENCODING_FIELDS = ["time stamp", "options"]; const EMPTY_VALUE_FIELDS = new Set(["options", "segname", "sectname"]); +/** + * Non-verbose otool prints LC_BUILD_VERSION platform and tool codes as + * numbers. Name the codes declared in the SDK's , without + * the PLATFORM_/TOOL_ prefix, so build metadata uses the same symbolic style + * as LC_VERSION_MIN_* platforms. Unknown codes keep their printed value. + */ +const BUILD_PLATFORM_NAMES: ReadonlyMap = new Map([ + ["1", "MACOS"], + ["2", "IOS"], + ["3", "TVOS"], + ["4", "WATCHOS"], + ["5", "BRIDGEOS"], + ["6", "MACCATALYST"], + ["7", "IOSSIMULATOR"], + ["8", "TVOSSIMULATOR"], + ["9", "WATCHOSSIMULATOR"], + ["10", "DRIVERKIT"], + ["11", "VISIONOS"], + ["12", "VISIONOSSIMULATOR"], + ["13", "FIRMWARE"], + ["14", "SEPOS"], + ["15", "MACOS_EXCLAVECORE"], + ["16", "MACOS_EXCLAVEKIT"], + ["17", "IOS_EXCLAVECORE"], + ["18", "IOS_EXCLAVEKIT"], + ["19", "TVOS_EXCLAVECORE"], + ["20", "TVOS_EXCLAVEKIT"], + ["21", "WATCHOS_EXCLAVECORE"], + ["22", "WATCHOS_EXCLAVEKIT"], + ["23", "VISIONOS_EXCLAVECORE"], + ["24", "VISIONOS_EXCLAVEKIT"], +]); +const BUILD_TOOL_NAMES: ReadonlyMap = new Map([ + ["1", "CLANG"], + ["2", "SWIFT"], + ["3", "LD"], + ["4", "LLD"], + ["1024", "METAL"], + ["1025", "AIRLLD"], + ["1026", "AIRNT"], + ["1027", "AIRNT_PLUGIN"], + ["1028", "AIRPACK"], + ["1031", "GPUARCHIVER"], + ["1032", "METAL_FRAMEWORK"], +]); +const buildCodeName = ( + names: ReadonlyMap, + value: string | null, +): string | null => (value === null ? null : (names.get(value) ?? value)); + /** * otool emits LF, but a capture replayed from another host or toolchain can * carry CRLF. The section/command boundaries below match a bare `\n`, so a @@ -295,7 +345,10 @@ const collectBuild = ( if (numberField(fields, "ntools") !== tools.length) throw new TypeError("otool build-tool count does not match ntools"); state.builds.push({ - platform: stringField(fields, "platform"), + platform: buildCodeName( + BUILD_PLATFORM_NAMES, + stringField(fields, "platform"), + ), minimum_os: stringField(fields, "minos"), sdk: stringField(fields, "sdk"), tools, @@ -434,7 +487,7 @@ const parseBuildTools = ( const tool = /^tool\s+(.+)$/u.exec(lines[index] ?? "")?.[1]; const version = /^version\s+(.+)$/u.exec(lines[index + 1] ?? "")?.[1]; if (tool !== undefined && version !== undefined) - tools.push({ name: tool, version }); + tools.push({ name: BUILD_TOOL_NAMES.get(tool) ?? tool, version }); } return tools; }; diff --git a/tests/boundary/providers/native/nativeLoadKinds.golden.test.ts b/tests/boundary/providers/native/nativeLoadKinds.golden.test.ts index 87912a31d..dd83a1b3c 100644 --- a/tests/boundary/providers/native/nativeLoadKinds.golden.test.ts +++ b/tests/boundary/providers/native/nativeLoadKinds.golden.test.ts @@ -229,6 +229,62 @@ it("normalizes legacy version-minimum commands into build metadata", () => { ]); }); +it("names numeric build-version platforms and tools", () => { + const load = parseOtoolLoadCommands( + withOtoolMachHeader( + [ + "Load command 0", + " cmd LC_BUILD_VERSION", + " cmdsize 48", + " platform 1", + " minos 11.0", + " sdk 26.5", + " ntools 2", + " tool 3", + " version 1267.0", + " tool 1029", + " version 1.0", + "Load command 1", + " cmd LC_BUILD_VERSION", + " cmdsize 24", + " platform 99", + " minos 1.0", + " sdk 1.0", + " ntools 0", + "Load command 2", + " cmd LC_BUILD_VERSION", + " cmdsize 32", + " platform MACCATALYST", + " minos 14.0", + " sdk 14.2", + " ntools 1", + " tool SWIFT", + " version 5.9", + "", + ].join("\n"), + ), + ); + expect(load.builds).toEqual([ + { + platform: "MACOS", + minimum_os: "11.0", + sdk: "26.5", + tools: [ + { name: "LD", version: "1267.0" }, + { name: "1029", version: "1.0" }, + ], + }, + { platform: "99", minimum_os: "1.0", sdk: "1.0", tools: [] }, + { + platform: "MACCATALYST", + minimum_os: "14.0", + sdk: "14.2", + tools: [{ name: "SWIFT", version: "5.9" }], + }, + ]); + expect(load.commands[0]?.fields.platform).toBe(1); +}); + it("does not classify LC_LOAD_DYLINKER as a dylib dependency", async () => { const load = parseOtoolLoadCommands( await nativeFixture("native-macho-header/otool-header-load.txt"), @@ -273,3 +329,78 @@ it("limits thread-state entrypoints instead of reporting none silently", async ( "Thread-state entrypoints (LC_UNIXTHREAD/LC_THREAD) are retained as raw load commands; file offsets are not derived.", ); }); + +it.each([ + ["1", "MACOS"], + ["2", "IOS"], + ["3", "TVOS"], + ["4", "WATCHOS"], + ["5", "BRIDGEOS"], + ["6", "MACCATALYST"], + ["7", "IOSSIMULATOR"], + ["8", "TVOSSIMULATOR"], + ["9", "WATCHOSSIMULATOR"], + ["10", "DRIVERKIT"], + ["11", "VISIONOS"], + ["12", "VISIONOSSIMULATOR"], + ["13", "FIRMWARE"], + ["14", "SEPOS"], + ["15", "MACOS_EXCLAVECORE"], + ["16", "MACOS_EXCLAVEKIT"], + ["17", "IOS_EXCLAVECORE"], + ["18", "IOS_EXCLAVEKIT"], + ["19", "TVOS_EXCLAVECORE"], + ["20", "TVOS_EXCLAVEKIT"], + ["21", "WATCHOS_EXCLAVECORE"], + ["22", "WATCHOS_EXCLAVEKIT"], + ["23", "VISIONOS_EXCLAVECORE"], + ["24", "VISIONOS_EXCLAVEKIT"], +] as const)("names build platform %s as %s", (code, name) => { + const load = parseOtoolLoadCommands( + withOtoolMachHeader( + [ + "Load command 0", + "cmd LC_BUILD_VERSION", + "cmdsize 24", + `platform ${code}`, + "minos 1.0", + "sdk 1.0", + "ntools 0", + "", + ].join("\n"), + ), + ); + expect(load.builds[0]?.platform).toBe(name); +}); + +it.each([ + ["1", "CLANG"], + ["2", "SWIFT"], + ["3", "LD"], + ["4", "LLD"], + ["1024", "METAL"], + ["1025", "AIRLLD"], + ["1026", "AIRNT"], + ["1027", "AIRNT_PLUGIN"], + ["1028", "AIRPACK"], + ["1031", "GPUARCHIVER"], + ["1032", "METAL_FRAMEWORK"], +] as const)("names build tool %s as %s", (code, name) => { + const load = parseOtoolLoadCommands( + withOtoolMachHeader( + [ + "Load command 0", + "cmd LC_BUILD_VERSION", + "cmdsize 32", + "platform 1", + "minos 1.0", + "sdk 1.0", + "ntools 1", + `tool ${code}`, + "version 1.0", + "", + ].join("\n"), + ), + ); + expect(load.builds[0]?.tools).toEqual([{ name, version: "1.0" }]); +});