Files
reverse-engineering-anything/tests/boundary/filesystem/appleDispatchMetadataStrings.test.ts
ShuaixinHuang 68c3c5b538 fix(native): preserve leading U+FEFF in Apple metadata names (#4730)
* fix(native): preserve leading U+FEFF in Apple metadata names

* test(native): run property metadata regressions on macOS
2026-10-17 23:23:27 +08:00

111 lines
4.0 KiB
TypeScript

import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js";
import { inspectAppleDispatchMetadata } from "../../../src/native/AppleDispatchMetadata.js";
import { withArtifactResourceScope } from "../../fixtures/artifactInventory.js";
import { machoWithPropertyAttributes } from "../../fixtures/objcPropertyMacho.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it.each([
["plain names", "Fixture", "first", "perform:"],
["leading U+FEFF", "\ufeffFixture", "\ufefffirst", "\ufeffperform:"],
["U+FEFF-only names", "\ufeff", "\ufeff", "\ufeff"],
] as const)(
"preserves %s in file-backed Apple metadata identities",
async (_, className, propertyName, selector) => {
const directory = await createTestTempDirectory("rea-dispatch-strings-");
const path = join(directory, "fixture.bin");
const bytes = machoWithPropertyAttributes("Tq,N", {
firstName: propertyName,
});
bytes.write(`${className}\0`, 0x400);
const base = 0x100000000n;
bytes.writeBigUInt64LE(base + 0x700n, 0x320);
bytes.writeUInt32LE(24, 0x700);
bytes.writeUInt32LE(1, 0x704);
bytes.writeBigUInt64LE(base + 0x750n, 0x708);
bytes.writeBigUInt64LE(base + 0x780n, 0x710);
bytes.writeBigUInt64LE(base + 0x800n, 0x718);
bytes.write(`${selector}\0`, 0x750);
bytes.write("v@:\0", 0x780);
await writeFile(path, bytes);
const target = await parseBinaryTarget(path);
if (!target.ok) throw target.error;
const metadata = await withArtifactResourceScope((resources) =>
resources.run(() =>
inspectAppleDispatchMetadata(target.value, 100, resources),
),
);
expect(metadata.target_sha256).toBe(
createHash("sha256").update(bytes).digest("hex"),
);
expect(metadata.result.objc_classes).toMatchObject([
{
name: className,
methods: [{ selector }],
properties: [{ name: propertyName }, { name: "second" }],
location: { address: "0x100000200", file_offset: 0x200 },
evidence: [
{
artifact_path: target.value.path,
artifact_sha256: target.value.sha256,
location: { address: "0x100000200", file_offset: 0x200 },
},
],
},
]);
expect(metadata.result.objc_dispatch_implementations).toMatchObject([
{
class_name: className,
selector,
implementation_address: "0x100000800",
location: { address: "0x100000708", file_offset: 0x708 },
},
]);
expect(
metadata.result.coverage.find(
({ facet }) => facet === "objc_properties_categories",
),
).toMatchObject({ status: "complete", reason: null });
expect(await readFile(path)).toEqual(bytes);
},
);
it.each([
["malformed UTF-8", [0xef, 0xbb, 0]],
["an empty name", [0]],
] as const)(
"keeps %s partial beside a valid Apple metadata identity",
async (_, name) => {
const directory = await createTestTempDirectory("rea-dispatch-strings-");
const path = join(directory, "fixture.bin");
const bytes = machoWithPropertyAttributes("Tq,N");
// Invalid UTF-8 and a NUL-only name remain distinct from EF BB BF payload.
bytes.set(name, 0x600);
await writeFile(path, bytes);
const target = await parseBinaryTarget(path);
if (!target.ok) throw target.error;
const metadata = await withArtifactResourceScope((resources) =>
resources.run(() =>
inspectAppleDispatchMetadata(target.value, 100, resources),
),
);
expect(metadata.result.objc_classes).toMatchObject([
{ name: "Fixture", properties: [{ name: "second" }] },
]);
expect(
metadata.result.coverage.find(
({ facet }) => facet === "objc_properties_categories",
),
).toMatchObject({
status: "partial",
reason: expect.stringContaining("property_decode_failed"),
});
expect(await readFile(path)).toEqual(bytes);
},
);