Files
reverse-engineering-anything/src/native/NativeMachoInspection.test.ts
T
morlutoandCursor Agent b4db35f092 fix: bind identity and coverage to the producing rule (#1513)
* fix: bind identity and coverage to the producing rule

Logical paths, container families, examined bytes, path-segment conventions, destination collisions, file-id leases, Mach-O CPU selection, and firmware cleanup now follow the observation that owns each rule.

* fix: fail unmatched Mach-O slices and keep firmware roots independent

A fat target whose architecture lipo did not list now stops before slice tools run. Chained fixups still count unread LC_DYLD_INFO rebase bytes, and a firmware cleanup failure no longer blocks the next workspace.

* test: cover path identity and runtime families at the projection boundary

NFC joins and segment conventions are now asserted by the path resolver, the Apple projection, and a real APK inventory instead of helper unit tests.

* test: expect unread dyld rebase bytes in the Apple dispatch fixture

The legacy LC_DYLD_INFO fixture keeps a rebase range this reader does not decode, so the public CLI and MCP check now requires that partial coverage beside the bind opcodes.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-10 17:14:47 +08:00

74 lines
2.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
import { err, ok } from "../domain/result.js";
import type { NativeCommandCapture } from "./CommandRunner.js";
import { inspectNativeMacho } from "./NativeMachoInspection.js";
const capture = (tool: string, stdout: string): NativeCommandCapture => ({
tool,
executable: tool,
executableSha256: "a".repeat(64),
toolVersion: null,
versionReason: null,
arguments: [],
stdout,
stderr: "",
stdoutBytes: stdout.length,
stderrBytes: 0,
exitCode: 0,
signal: null,
});
const inspect = async (lipo: string, architecture: "x86" | "arm64") => {
const commands: {
readonly tool: string;
readonly args: readonly string[];
}[] = [];
const result = await inspectNativeMacho({
target: {
kind: "executable",
format: "mach-o",
path: "/fixture/app",
sha256: "b".repeat(64),
architecture,
availableArchitectures: ["x86", "arm64"],
},
run: (tool, args) => {
commands.push({ tool, args });
if (tool === "lipo") return Promise.resolve(ok(capture(tool, lipo)));
if (tool === "file") return Promise.resolve(ok(capture(tool, "Mach-O")));
return Promise.resolve(err(new AnalysisCancelledError("inspect_native")));
},
invocation: () => {
throw new Error("invocation is not reached");
},
});
return { result, commands };
};
describe("Mach-O tool architecture selection", () => {
it("rejects a CPU lipo did not list instead of reading another slice", async () => {
const { result, commands } = await inspect("architecture arm64\n", "x86");
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error).toMatchObject({
_tag: "AnalysisOutputError",
reason: "lipo did not list i386; refusing to read another slice",
});
expect(commands.map(({ tool }) => tool)).toEqual(["file", "lipo"]);
});
it("passes the arm64e slice lipo listed for an arm64 target", async () => {
const { commands } = await inspect("architecture arm64e\n", "arm64");
const otool = commands.find(({ tool }) => tool === "otool");
expect(otool?.args).toEqual([
"-h",
"-l",
"-arch",
"arm64e",
"/fixture/app",
]);
});
});