fix(artifacts): preserve NIB key and class name identity (#2109)

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
xin zhuang 2026-10-13 22:36:06 +08:00 committed by GitHub
parent edd9939879
commit e103a89889
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 59 additions and 0 deletions

View File

@ -36,6 +36,22 @@ const archiveWithOverlappingObjectRanges = (objectCount: number): Buffer => {
};
describe("NIBArchive decoder", () => {
it("keeps leading U+FEFF in length-prefixed keys and class names", () => {
const archive = encodeArchive({
classes: ["\uFEFFWidget"],
objects: [{ classIndex: 0, values: { count: 1, "\uFEFFcount": 2 } }],
});
expect(decodeNibArchive(archive).objects).toEqual([
{
id: 0,
class_name: "\uFEFFWidget",
values: { count: 1, "\uFEFFcount": 2 },
},
]);
const invalid = Buffer.from(archive);
invalid[invalid.indexOf(Buffer.from("\uFEFFcount"))] = 0xff;
expect(() => decodeNibArchive(invalid)).toThrow(/not valid UTF-8/u);
});
it("decodes bounded object, key, class, and reference tables", () => {
const archive = encodeArchive({
classes: ["NSView\0", "NSString\0"],

View File

@ -696,6 +696,49 @@ describe("NIB projection byte amplification", () => {
});
});
cliTest(
"compiled CLI preserves distinct NIB keys and class identity",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-nib-name-identity-");
const bundle = join(root, "Example.app");
const resources = join(bundle, "Contents", "Resources");
await mkdir(resources, { recursive: true });
await mkdir(join(bundle, "Contents", "MacOS"), { recursive: true });
await writeFile(
join(bundle, "Contents", "MacOS", "App"),
thinMach(0xfeedfacf, 0x0100000c),
);
await writeFile(
join(bundle, "Contents", "Info.plist"),
"<plist><dict><key>CFBundleExecutable</key><string>App</string></dict></plist>",
);
await writeFile(
join(resources, "Identity.nib"),
encodeNibArchiveFixture({
classes: ["\uFEFFWidget"],
objects: [{ classIndex: 0, values: { count: 1, "\uFEFFcount": 2 } }],
}),
);
const output = await cli.run({
arguments: ["decode-interface-builder", bundle, "--json"],
environment: { REA_LOG_LEVEL: "silent", REA_ANALYSIS_PROVIDER: "auto" },
});
expect(output.exitCode, output.stderr).toBe(0);
const result = interfaceBuilderAnalysisSchema.parse(
parseEvidence(output.json).normalized_result,
);
expect(result.graph.nodes).toContainEqual(
expect.objectContaining({
attributes: expect.objectContaining({
class_name: "\uFEFFWidget",
nibValues: { count: 1, "\uFEFFcount": 2 },
}),
}),
);
expect(JSON.stringify(result)).toContain("\uFEFFWidget");
},
);
cliTest(
"compiled CLI preserves a good archive and reports repeated key and data omissions",
async ({ cli }) => {