mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
Carry #952's Apple ownership into the cache work: the cache reader and fixture move to src/artifacts/apple/, the cache facts, platforms and resolution schema to src/domain/apple/, the cache service to src/application/apple/, and the lane to scripts/verify/apple/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
189 lines
6.3 KiB
TypeScript
189 lines
6.3 KiB
TypeScript
import { rm, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
dyldCacheFixture,
|
|
type CacheFixture,
|
|
} from "../../../src/artifacts/apple/DyldSharedCache.fixture.js";
|
|
import {
|
|
FILE_TYPE,
|
|
LC,
|
|
dylibCommand,
|
|
dylibUseCommand,
|
|
machoImage,
|
|
} from "../../../src/artifacts/apple/MachoImage.fixture.js";
|
|
import {
|
|
inspectDyldSharedCache,
|
|
inspectDyldSharedCacheEvidence,
|
|
} from "../../../src/application/apple/DyldSharedCacheService.js";
|
|
import { dyldSharedCacheResultSchema } from "../../../src/domain/apple/dyldSharedCache.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const dylib = (installName: string, dependencies: readonly Uint8Array[] = []) =>
|
|
machoImage({
|
|
fileType: FILE_TYPE.dylib,
|
|
commands: [dylibCommand(LC.ID_DYLIB, installName), ...dependencies],
|
|
});
|
|
|
|
const writeCache = async (
|
|
fixture: CacheFixture,
|
|
name = "dyld_shared_cache_arm64e",
|
|
): Promise<string> => {
|
|
const directory = await createTestTempDirectory("rea-dyld-cache-");
|
|
const base = join(directory, "dyld_shared_cache_arm64e");
|
|
await writeFile(join(directory, name), fixture.main);
|
|
for (const { suffix, bytes } of fixture.subcaches)
|
|
await writeFile(`${base}${suffix}`, bytes);
|
|
return join(directory, name);
|
|
};
|
|
|
|
const IMAGES = [
|
|
{
|
|
path: "/usr/lib/libSystem.B.dylib",
|
|
bytes: dylib("/usr/lib/libSystem.B.dylib", [
|
|
dylibCommand(LC.REEXPORT_DYLIB, "/usr/lib/system/libcache.dylib"),
|
|
]),
|
|
},
|
|
{
|
|
path: "/usr/lib/swift/libswiftCore.dylib",
|
|
bytes: dylib("/usr/lib/swift/libswiftCore.dylib", [
|
|
dylibUseCommand(LC.LOAD_DYLIB, "/usr/lib/libSystem.B.dylib", 0x4 | 0x8),
|
|
]),
|
|
inSubcache: true,
|
|
},
|
|
];
|
|
|
|
describe("dyld shared cache inspection", () => {
|
|
it("reads the header, subcaches, image list and cached load commands", async () => {
|
|
const path = await writeCache(dyldCacheFixture(IMAGES));
|
|
const result = await inspectDyldSharedCache({
|
|
cache_path: path,
|
|
images: ["/usr/lib/swift/libswiftCore.dylib", "/usr/lib/missing.dylib"],
|
|
});
|
|
expect(result.ok).toBe(true);
|
|
if (!result.ok) return;
|
|
const cache = dyldSharedCacheResultSchema.parse(result.value);
|
|
expect(cache).toMatchObject({
|
|
architecture: "arm64e",
|
|
platform: { id: 1, name: "macos" },
|
|
os_version: "26.6.0",
|
|
shared_region: { start: "0x180000000", size: "0x20000000" },
|
|
images_total: 2,
|
|
coverage: { status: "complete", unreadable_subcaches: [] },
|
|
});
|
|
expect(cache.subcaches).toEqual([
|
|
expect.objectContaining({
|
|
suffix: ".01",
|
|
status: "present",
|
|
vm_offset: "0x10000000",
|
|
}),
|
|
]);
|
|
expect(cache.images.map(({ path: image }) => image)).toEqual(
|
|
IMAGES.map(({ path: image }) => image),
|
|
);
|
|
const [swift, missing] = cache.inspected_images;
|
|
expect(swift).toMatchObject({ status: "parsed", file: ".01" });
|
|
expect(swift?.slices[0]?.dependencies).toEqual([
|
|
expect.objectContaining({
|
|
install_name: "/usr/lib/libSystem.B.dylib",
|
|
encoding: "dylib_use_command",
|
|
upward: true,
|
|
delayed_init: true,
|
|
}),
|
|
]);
|
|
expect(missing).toEqual({
|
|
path: "/usr/lib/missing.dylib",
|
|
status: "absent",
|
|
address: null,
|
|
file: null,
|
|
reason: null,
|
|
slices: [],
|
|
});
|
|
});
|
|
|
|
it("reports missing and mismatched subcaches as partial coverage", async () => {
|
|
const mismatched = await writeCache(
|
|
dyldCacheFixture(IMAGES, { subcacheUuidSeed: 9 }),
|
|
);
|
|
const result = await inspectDyldSharedCache({
|
|
cache_path: mismatched,
|
|
images: ["/usr/lib/swift/libswiftCore.dylib"],
|
|
});
|
|
expect(result.ok && result.value).toMatchObject({
|
|
subcaches: [{ suffix: ".01", status: "uuid-mismatch" }],
|
|
inspected_images: [{ status: "unmapped" }],
|
|
coverage: { status: "partial", unreadable_subcaches: [".01"] },
|
|
});
|
|
const missing = await writeCache(dyldCacheFixture(IMAGES));
|
|
await rm(`${missing}.01`);
|
|
const without = await inspectDyldSharedCache({ cache_path: missing });
|
|
expect(without.ok && without.value.subcaches[0]).toMatchObject({
|
|
status: "missing",
|
|
observed_uuid: null,
|
|
});
|
|
});
|
|
|
|
it("finds a development cache's subcaches beside its base name", async () => {
|
|
const path = await writeCache(
|
|
dyldCacheFixture(IMAGES),
|
|
"dyld_shared_cache_arm64e.development",
|
|
);
|
|
const result = await inspectDyldSharedCache({
|
|
cache_path: path,
|
|
images: ["/usr/lib/swift/libswiftCore.dylib"],
|
|
});
|
|
expect(result.ok && result.value).toMatchObject({
|
|
subcaches: [{ suffix: ".01", status: "present" }],
|
|
inspected_images: [{ status: "parsed", file: ".01" }],
|
|
});
|
|
});
|
|
|
|
it("reads legacy single-file headers without later fields", async () => {
|
|
const path = await writeCache(
|
|
dyldCacheFixture(IMAGES.slice(0, 1), { legacy: true }),
|
|
);
|
|
const result = await inspectDyldSharedCache({
|
|
cache_path: path,
|
|
images: ["/usr/lib/libSystem.B.dylib"],
|
|
});
|
|
expect(result.ok && result.value).toMatchObject({
|
|
platform: null,
|
|
os_version: null,
|
|
subcaches: [],
|
|
images_total: 1,
|
|
inspected_images: [{ status: "parsed" }],
|
|
});
|
|
});
|
|
|
|
it("separates malformed input, missing files and non-cache files", async () => {
|
|
expect(await inspectDyldSharedCache({ images: [] })).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisInputError" },
|
|
});
|
|
const directory = await createTestTempDirectory("rea-dyld-cache-bad-");
|
|
expect(
|
|
await inspectDyldSharedCache({ cache_path: join(directory, "absent") }),
|
|
).toMatchObject({ ok: false, error: { reason: "path" } });
|
|
const notCache = join(directory, "not-a-cache");
|
|
await writeFile(notCache, Buffer.alloc(64, 0x41));
|
|
expect(
|
|
await inspectDyldSharedCache({ cache_path: notCache }),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { reason: "format" },
|
|
});
|
|
});
|
|
|
|
it("wraps the observation as Evidence about the main cache file", async () => {
|
|
const path = await writeCache(dyldCacheFixture(IMAGES));
|
|
const evidence = await inspectDyldSharedCacheEvidence({ cache_path: path });
|
|
expect(evidence.ok && evidence.value).toMatchObject({
|
|
operation: "inspect_dyld_shared_cache",
|
|
subject: { local_path: path, format: "file" },
|
|
confidence: "observed",
|
|
});
|
|
});
|
|
});
|