Files
rea/tests/fixtures/nativeCommands.ts
morluto 108b5f9425 fix(native): reject ambiguous otool load-command framing
Validate Mach command counts, byte totals, scalar uniqueness, section counts, and build-tool counts before claiming exhaustive evidence. Preserve raw output in typed parsing failures and distinguish dylib commands from the dynamic loader. Correct inconsistent handcrafted fixtures and label their provenance.

Validation: 38 native tests, combined checks, and temporary-directory hygiene. Multiline dylib-name ambiguity was reproduced with the host Apple otool; regression workflows use captured output and injected command runners.
2026-10-12 05:24:29 +08:00

144 lines
4.6 KiB
TypeScript

import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import type { BinaryTarget } from "../../src/domain/binaryTargetTypes.js";
import { ok } from "../../src/domain/result.js";
import type {
NativeCommandCapture,
NativeCommandRunner,
} from "../../src/native/CommandRunner.js";
/** Replay captured native command outputs through the production runner seam. */
export class NativeFixtureRunner implements NativeCommandRunner {
constructor(
private readonly overrides: Readonly<Record<string, string>> = {},
private readonly exitCode = 0,
) {}
async run(tool: string, arguments_: readonly string[]) {
const output =
this.overrides[`${tool}:${arguments_[0] ?? ""}`] ??
this.overrides[tool] ??
(await outputFor(tool, arguments_));
return ok({
...capture(tool, arguments_, output),
exitCode: this.exitCode,
});
}
}
const outputFor = async (
tool: string,
arguments_: readonly string[],
): Promise<string> => {
if (tool === "lipo") return nativeFixture("lipo-fat.txt");
if (tool === "otool") return nativeFixture("otool-load.txt");
if (tool === "nm") return "_main\n_$s4Test3fooyyF\n";
if (tool === "dyld_info")
return nativeFixture(
arguments_[0] === "-imports" ? "dyld-imports.txt" : "dyld-exports.txt",
);
if (tool === "dwarfdump")
return "UUID: 01234567-89AB-CDEF-0123-456789ABCDEF (arm64) fixture\n";
if (tool === "file")
return arguments_.at(-1)?.endsWith(".plist") === true
? "XML 1.0 document text\n"
: "Mach-O 64-bit executable arm64, little-endian\n";
if (tool === "vtool") return "Load command 3 LC_BUILD_VERSION\n";
if (tool === "swift-demangle") return nativeFixture("demangle.txt");
if (tool === "plutil") return nativeFixture("plist.json");
if (tool === "codesign") {
if (arguments_.includes("--entitlements"))
return nativeFixture("entitlements.xml");
if (arguments_.includes("-r-"))
return "designated => identifier com.example.fixture\n";
return nativeFixture("codesign.txt");
}
throw new Error(`Unexpected fixture tool ${tool}`);
};
/**
* codesign prints display diagnostics to stderr and requirements or
* entitlements to stdout.
*/
const fixtureStreams = (
tool: string,
arguments_: readonly string[],
output: string,
) => {
const stderr =
tool === "codesign" &&
!arguments_.includes("-r-") &&
!arguments_.includes("--entitlements");
return {
stdout: stderr ? "" : output,
stderr: stderr ? output : "",
stdoutBytes: Buffer.byteLength(stderr ? "" : output),
stderrBytes: Buffer.byteLength(stderr ? output : ""),
};
};
const capture = (
tool: string,
arguments_: readonly string[],
output: string,
): NativeCommandCapture => ({
tool,
executable: `/usr/bin/${tool}`,
executableSha256: "a".repeat(64),
toolVersion: null,
versionReason: "fixture",
arguments: [...arguments_],
...fixtureStreams(tool, arguments_, output),
exitCode: 0,
signal: null,
});
/** Build the Mach-O target identity used by native boundary fixtures. */
export const nativeMachoTarget = (
path: string,
sourcePath?: string,
): BinaryTarget & {
readonly kind: "executable";
readonly format: "mach-o";
} => ({
path,
...(sourcePath === undefined ? {} : { sourcePath }),
sha256: "0".repeat(64),
kind: "executable",
format: "mach-o",
architecture: "arm64",
availableArchitectures: ["x86_64", "arm64"],
});
/** Register the actual bytes of a filesystem-backed native test target. */
export const nativeMachoTargetForFile = async (
path: string,
sourcePath?: string,
): Promise<ReturnType<typeof nativeMachoTarget>> => ({
...nativeMachoTarget(path, sourcePath),
sha256: createHash("sha256")
.update(await readFile(path))
.digest("hex"),
});
/** Load one captured native tool output. */
export const nativeFixture = (name: string): Promise<string> =>
readFile(new URL(`./native-macos/${name}`, import.meta.url), "utf8");
/** Add consistent Mach command-count metadata to a command-only test capture. */
export const withOtoolMachHeader = (output: string): string => {
const count = [...output.matchAll(/^Load command (\d+)$/gmu)].length;
const sizes = [...output.matchAll(/^\s*cmdsize\s+(\d+)\s*$/gmu)].map(
([, size]) => Number(size),
);
if (sizes.length !== count)
throw new TypeError("command-only fixture must provide every cmdsize");
const bytes = sizes.reduce((sum, size) => sum + size, 0);
return [
"Mach header",
" magic cputype cpusubtype caps filetype ncmds sizeofcmds flags",
` 0xfeedfacf 16777228 0 0x00 2 ${count} ${bytes} 0x00000000`,
output,
].join("\n");
};