mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 01:45:32 +00:00
fix(native): preserve Mach-O container framing across inspection
This commit is contained in:
1 parent
799e8265b2
commit
cf3288d8cf
5 files changed
+64
-4
No files matched your search
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in new issue
Block a user