fix(native): preserve Mach-O names equal to version

Parse only documented compound otool field labels so valid segment and section names are retained at the owning boundary. Verified real Apple clang fixtures through compiled CLI and stdio MCP without executing the targets.

Closes #5680
This commit is contained in:
morluto committed 2026-10-21 06:19:43 +08:00
1 parent 949966712e
commit 4852c830af
2 files changed
+49 -2

No files matched your search

+7 -2
View File
@@ -483,8 +483,13 @@ const parseField = (rawLine: string): readonly [string, string] | undefined => {
const option = /^\s*(string #[1-9]\d*)(?: (.*))?$/u.exec(rawLine);
if (option?.[1] !== undefined) return [option[1], option[2] ?? ""];
const line = rawLine.trim();
// Match time stamp as a whole without swallowing `name stamp plugin.dylib`.
const match = /^((?:time stamp)|\S+(?:\s+version)?)(?:\s+(.+))?$/u.exec(line);
// These are the compound field labels used by otool. Keeping this list
// explicit prevents a one-word field value such as `sectname version` from
// being consumed as an invented `sectname version` key.
const match =
/^(time stamp|current version|compatibility version|\S+)(?:\s+(.+))?$/u.exec(
line,
);
return match?.[1] === undefined
? undefined
: [match[1], match[2]?.trim() ?? ""];
@@ -165,6 +165,48 @@ it("retains the empty segment name of a Mach-O object file", () => {
).toThrow("otool field vmaddr has no value");
});
it.each([
{ label: "segment", segmentName: "version", sectionName: "__mysect" },
{ label: "section", segmentName: "__DATA", sectionName: "version" },
])(
"preserves a legal $label name equal to a compound field word",
({ segmentName, sectionName }) => {
const output = [
"Load command 0",
" cmd LC_SEGMENT_64",
" cmdsize 152",
` segname ${segmentName}`,
" vmaddr 0x0000000000000000",
" vmsize 0x0000000000000018",
" fileoff 0",
" filesize 24",
" maxprot 0x00000007",
" initprot 0x00000007",
" nsects 1",
" flags 0x0",
"Section",
` sectname ${sectionName}`,
` segname ${segmentName}`,
" addr 0x0000000000000000",
" size 0x0000000000000018",
" offset 0",
" align 2^2 (4)",
" flags 0x80000400",
"",
].join("\n");
const parsed = parseOtoolLoadCommands(withOtoolMachHeader(output));
expect(parsed.commands[0]?.fields.segname).toBe(segmentName);
expect(parsed.segments).toMatchObject([
{
name: segmentName,
sections: [{ segment: segmentName, name: sectionName }],
},
]);
},
);
it("preserves dependency install names that begin with the word stamp", () => {
const load = parseOtoolLoadCommands(
withOtoolMachHeader(