fix(native): preserve Mach-O container framing across inspection

This commit is contained in:
morluto committed 2026-10-20 17:16:44 +08:00
1 parent 799e8265b2
commit cf3288d8cf
5 files changed
+64 -4

No files matched your search

+1 -1
View File
@@ -98,7 +98,7 @@ const buildMetadataSchema = z.object({
});
export const inspectMachoSchema = z.object({
format: z.literal("mach-o"),
format: z.enum(["mach-o", "mach-o-universal"]),
/** Exact lipo slice selected for inspection and its offset in the input container. */
selected_architecture: z.object({
name: z
+37
View File
@@ -143,6 +143,43 @@ describe("Mach-O tool architecture selection", () => {
);
});
describe("Mach-O inspection container format", () => {
const runInspection = async (lipo: string) => {
const runner = new NativeFixtureRunner({ lipo });
return inspectNativeMacho({
target: nativeMachoTarget("/fixture/app"),
run: (tool, args) => runner.run(tool, args),
invocation: (capture) => ({
tool: capture.tool,
command: [capture.executable, ...capture.arguments],
tool_version: capture.toolVersion,
version_reason: capture.versionReason,
executable_sha256: capture.executableSha256,
exit: { code: capture.exitCode, signal: capture.signal },
stdout_bytes: capture.stdoutBytes,
stderr_bytes: capture.stderrBytes,
}),
});
};
it.each([
{
framing: "one-slice FAT",
lipo: `Fat header in: /fixture/app\nfat_magic 0xcafebabe\nnfat_arch 1\narchitecture arm64\n cputype CPU_TYPE_ARM64\n cpusubtype CPU_SUBTYPE_ARM64_ALL\n offset 4096\n size 128\n align 2^12\n`,
expected: "mach-o-universal",
},
{
framing: "thin",
lipo: "Non-fat file: /fixture/app is architecture: arm64\n",
expected: "mach-o",
},
] as const)("reports parsed $framing framing", async ({ lipo, expected }) => {
const result = await runInspection(lipo);
if (!result.ok) throw result.error;
expect(result.value.result).toMatchObject({ format: expected });
});
});
describe("Mach-O UUID reconciliation", () => {
const validUuid = "01234567-89AB-CDEF-0123-456789ABCDEF";
+3 -1
View File
@@ -174,6 +174,7 @@ export const inspectNativeMacho = async (
?.file_offset ?? undefined);
const normalized = normalizeMacho(
captures,
format,
context.invocation,
limitations,
load,
@@ -205,6 +206,7 @@ export const inspectNativeMacho = async (
const normalizeMacho = (
captures: readonly NativeCommandCapture[],
containerFormat: "thin" | "fat",
toInvocation: (capture: NativeCommandCapture) => NativeCommandInvocation,
additionalLimitations: readonly string[],
load: ReturnType<typeof parseOtoolLoadCommands>,
@@ -276,7 +278,7 @@ const normalizeMacho = (
: []),
];
return inspectMachoSchema.parse({
format: "mach-o",
format: containerFormat === "fat" ? "mach-o-universal" : "mach-o",
selected_architecture: selectedArchitecture,
endian: load.endian,
word_size: load.wordSize,
@@ -14,7 +14,7 @@ describe.skipIf(process.platform !== "darwin")(
"native Mach-O CLI dylib identity",
() => {
cliTest(
"keeps a dylib's own install name out of its dependencies",
"preserves dylib identity and thin or single-slice FAT framing",
async ({ cli, processes }) => {
const directory = await createTestTempDirectory("rea-dylib-identity-");
const source = join(directory, "library.c");
@@ -51,6 +51,27 @@ describe.skipIf(process.platform !== "darwin")(
({ kind }) => kind === "LC_ID_DYLIB",
)?.fields.name,
).toBe("@rpath/libprobe.dylib (offset 24)");
// One architecture can still have a FAT container header.
const universal = join(directory, "libprobe-universal.dylib");
const combined = await processes.run(
"/usr/bin/lipo",
["-create", library, "-output", universal],
{ timeoutMs: 30_000 },
);
expect(combined.exitCode, combined.stderr).toBe(0);
const fatResult = await cli.run({
arguments: ["inspect-macho", universal, "--json"],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(fatResult.exitCode, fatResult.stdout + fatResult.stderr).toBe(0);
const fat = evidenceSchema.parse(fatResult.json).normalized_result;
expect(fat.format).toBe("mach-o-universal");
expect(fat.architectures.items).toHaveLength(1);
expect(fatResult.json).toMatchObject({
subject: { format: fat.format },
});
expect(inspected.format).toBe("mach-o");
},
);
},
@@ -631,7 +631,7 @@ describe("native macOS provider inspection", () => {
if (!execution.ok) throw execution.error;
expect(execution.value.provider.id).toBe("native-macos");
expect(execution.value.result).toMatchObject({
format: "mach-o",
format: "mach-o-universal",
word_size: 64,
uuid: "01234567-89AB-CDEF-0123-456789ABCDEF",
architectures: { total: 2, exhaustive: true },