diff --git a/src/native/parsers/otool.ts b/src/native/parsers/otool.ts index 6da69d6a0d..ed2203f727 100644 --- a/src/native/parsers/otool.ts +++ b/src/native/parsers/otool.ts @@ -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() ?? ""]; diff --git a/tests/boundary/providers/native/nativeLoadKinds.golden.test.ts b/tests/boundary/providers/native/nativeLoadKinds.golden.test.ts index 84edc6b2ac..2c3e6d8903 100644 --- a/tests/boundary/providers/native/nativeLoadKinds.golden.test.ts +++ b/tests/boundary/providers/native/nativeLoadKinds.golden.test.ts @@ -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(