Files
rea/tests/boundary/providers/native/machoSliceReader.test.ts
T
Kaoru0822andClaude Opus 5.5 e5828596c5 fix(artifacts): inventory universal slices REA cannot structurally index (#3283)
* fix(artifacts): inventory universal slices REA cannot structurally index

inspect_artifact and extract_artifact rejected a valid universal Mach-O
when one slice was outside the structural index, such as XLD's
big-endian PowerPC slice. The whole inventory failed with "Artifact is
invalid or has changed. Get a fresh copy and try again."

When lipo reports every slice's range, the slices are now inventoried
from those ranges with a limitation naming the unsupported structure,
as the reader already does for files without structural facts. When
lipo omits a range, the failure is reported as unavailable support
rather than a corrupt artifact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artifacts): validate every FAT slice past an unsupported one

readFatSlices returned at the first unsupported slice, so a later
malformed slice went unchecked: with lipo's ranges now inventoried for
unsupported structure, a malformed little-endian slice after a
big-endian one was accepted while the reverse order was rejected.
Unsupported slices are now remembered and the remaining slices are
still validated, so malformed structure wins in either order.

Adds portable fake-lipo coverage for complete ranges, missing ranges
(unavailable) and malformed slices on both sides of a big-endian slice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:22:45 -07:00

166 lines
6.0 KiB
TypeScript

import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it, onTestFinished } from "vitest";
import { MachOSliceArtifactReader } from "../../../../src/artifacts/MachOSliceArtifactReader.js";
import { XcrunCommandRunner } from "../../../../src/native/CommandRunner.js";
import { DarwinProcessOwnershipInspectionError } from "../../../../src/process/DarwinProcessRunTokenReader.js";
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
/**
* Read a 32-bit FAT table directly. The host's /bin/ls changes between macOS
* releases, so the expected slice layout comes from the file itself.
*/
const fatSlices = async (
path: string,
): Promise<readonly (readonly [number, number])[]> => {
const bytes = await readFile(path);
expect(bytes.readUInt32BE(0)).toBe(0xca_fe_ba_be);
return Array.from({ length: bytes.readUInt32BE(4) }, (_, index) => {
const entry = 8 + index * 20;
return [bytes.readUInt32BE(entry + 8), bytes.readUInt32BE(entry + 12)];
});
};
describe("Mach-O slice reader with system lipo", () => {
it("names the process ownership helper prerequisite when lipo cannot be supervised", async () => {
const prerequisite =
"Direct Darwin helper compilation requires absolute REA_DARWIN_SWIFTC_PATH and SDKROOT paths.";
const reader = new MachOSliceArtifactReader(
process.execPath,
{},
new XcrunCommandRunner({}, undefined, () =>
Promise.reject(new DarwinProcessOwnershipInspectionError(prerequisite)),
),
);
onTestFinished(() => reader.close());
const enumerate = async () => {
for await (const _entry of reader.entries()) continue;
};
await expect(enumerate()).rejects.toMatchObject({
reason: "unavailable",
message: `lipo could not enumerate universal Mach-O slices: ${prerequisite}`,
});
});
it.skipIf(process.platform !== "darwin")(
"keeps FAT slice records separate from the echoed source pathname",
async () => {
const directory = await createTestTempDirectory("rea-lipo-fat-path-");
const path = join(
directory,
"source\narchitecture forged\n offset 123\n size 32",
);
const bytes = Buffer.alloc(8224);
bytes.writeUInt32BE(0xca_fe_ba_be, 0);
bytes.writeUInt32BE(2, 4);
const slices = [
{ cpu: 0x0100_000c, subtype: 0, offset: 4096 },
{ cpu: 0x0100_0007, subtype: 3, offset: 8192 },
];
slices.forEach(({ cpu, subtype, offset }, index) => {
const entry = 8 + index * 20;
bytes.writeUInt32BE(cpu, entry);
bytes.writeUInt32BE(subtype, entry + 4);
bytes.writeUInt32BE(offset, entry + 8);
bytes.writeUInt32BE(32, entry + 12);
bytes.writeUInt32BE(12, entry + 16);
bytes.writeUInt32LE(0xfeed_facf, offset);
bytes.writeUInt32LE(cpu, offset + 4);
bytes.writeUInt32LE(subtype, offset + 8);
bytes.writeUInt32LE(1, offset + 12);
});
await writeFile(path, bytes);
const reader = new MachOSliceArtifactReader(path, {});
try {
const entries = [];
for await (const entry of reader.entries()) entries.push(entry);
expect(
entries.map(({ byteOffset, declaredSize }) => [
byteOffset,
declaredSize,
]),
).toEqual([
[4096, 32],
[8192, 32],
]);
expect(entries.every(({ path }) => !path.includes("forged"))).toBe(
true,
);
} finally {
await reader.close();
}
},
);
it.skipIf(process.platform !== "darwin")(
"checks real /bin/ls slice ranges against its FAT table",
async () => {
const reader = new MachOSliceArtifactReader("/bin/ls", {});
onTestFinished(() => reader.close());
const entries = [];
for await (const entry of reader.entries()) entries.push(entry);
expect(new Set(entries.map(({ path }) => path)).size).toBe(
entries.length,
);
expect(entries.every(({ path }) => path.startsWith("slices/"))).toBe(
true,
);
expect(
entries.map(({ byteOffset, declaredSize }) => [
byteOffset,
declaredSize,
]),
).toEqual(await fatSlices("/bin/ls"));
},
);
it.skipIf(process.platform !== "darwin")(
"inventories a universal binary whose big-endian slice cannot be indexed",
async () => {
const directory = await createTestTempDirectory("rea-lipo-ppc-");
const path = join(directory, "mixed");
const bytes = Buffer.alloc(8224);
bytes.writeUInt32BE(0xca_fe_ba_be, 0);
bytes.writeUInt32BE(2, 4);
const slices = [
{ cpu: 0x0100_000c, offset: 4096, bigEndian: false },
{ cpu: 18, offset: 8192, bigEndian: true },
];
slices.forEach(({ cpu, offset, bigEndian }, index) => {
const entry = 8 + index * 20;
bytes.writeUInt32BE(cpu, entry);
bytes.writeUInt32BE(offset, entry + 8);
bytes.writeUInt32BE(32, entry + 12);
bytes.writeUInt32BE(12, entry + 16);
const write = (value: number, at: number) =>
bigEndian
? bytes.writeUInt32BE(value, at)
: bytes.writeUInt32LE(value, at);
write(cpu === 18 ? 0xfeed_face : 0xfeed_facf, offset);
write(cpu, offset + 4);
write(1, offset + 12);
});
await writeFile(path, bytes);
const reader = new MachOSliceArtifactReader(path, {});
onTestFinished(() => reader.close());
const entries = [];
for await (const entry of reader.entries()) entries.push(entry);
expect(
entries.map(({ path, byteOffset, declaredSize }) => [
path,
byteOffset,
declaredSize,
]),
).toEqual([
["slices/arm64", 4096, 32],
["slices/ppc", 8192, 32],
]);
expect(entries[1]?.limitations).toContain(
"Structural Mach-O facts were unsupported (big-endian Mach-O slices are not supported); slice range is based on lipo output.",
);
},
);
});