* fix(native): retain valid Objective-C properties beside a malformed record readObjcPropertyList decoded a whole property_list_t in one operation. When a later entry's name pointer was unmapped, the list-level catch discarded the properties already decoded. An empty property name passed decoding and then failed schema validation of the whole result, so inspect-native-dispatch-metadata exited 1 with no metadata. Each property_t entry is now decoded on its own. A failing entry, or an empty name, becomes a property_decode_failed issue at the entry address, and sibling properties are retained. Incomplete-attribute issues also report their entry address. List-header failures still fail only that list. Fixes #3336 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(native): cover property record isolation on every platform Move the synthetic Objective-C property Mach-O into a shared fixture and decode it through decodeAppleDispatchMetadata in a portable test, so Linux and Windows runs also catch a regression that drops valid sibling properties. The macOS CLI cases keep exercising the built command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
3.9 KiB
TypeScript
115 lines
3.9 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect } from "vitest";
|
|
|
|
import { machoWithPropertyAttributes } from "../../fixtures/objcPropertyMacho.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
describe.skipIf(process.platform !== "darwin")(
|
|
"native dispatch property attributes",
|
|
() => {
|
|
cliTest.for([
|
|
["complete nonatomic", "Tq,N", "nonatomic", false, "complete"],
|
|
["complete default", 'T@"NSString",C', "atomic", false, "complete"],
|
|
["unfinished quote", 'T@"NSString', null, null, "partial"],
|
|
["unfinished quote after R", 'R,T@"NSString', null, true, "partial"],
|
|
] as const)(
|
|
"reports established property flags only for $0 attributes",
|
|
async ([, attributes, atomicity, readonly, facet], { cli }) => {
|
|
const directory = await createTestTempDirectory("rea-objc-property-");
|
|
const path = join(directory, "fixture.bin");
|
|
await writeFile(path, machoWithPropertyAttributes(attributes));
|
|
const output = await cli.run({
|
|
arguments: ["inspect-native-dispatch-metadata", path, "--json"],
|
|
environment: { REA_LOG_LEVEL: "silent" },
|
|
});
|
|
expect(output.exitCode).toBe(0);
|
|
const property = {
|
|
atomicity,
|
|
is_readonly: readonly,
|
|
attributes: expect.arrayContaining([
|
|
{ name: "T", value: expect.any(String) },
|
|
]),
|
|
};
|
|
expect(output.json).toMatchObject({
|
|
normalized_result: {
|
|
result: {
|
|
objc_classes: [
|
|
{
|
|
name: "Fixture",
|
|
properties: [
|
|
{ name: "first", ...property },
|
|
{ name: "second", ...property },
|
|
],
|
|
},
|
|
],
|
|
coverage: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
facet: "objc_properties_categories",
|
|
status: facet,
|
|
reason:
|
|
facet === "partial"
|
|
? expect.stringContaining(
|
|
"property_attributes_incomplete",
|
|
)
|
|
: null,
|
|
}),
|
|
]),
|
|
},
|
|
},
|
|
});
|
|
},
|
|
);
|
|
},
|
|
);
|
|
|
|
describe.skipIf(process.platform !== "darwin")(
|
|
"native dispatch property record isolation",
|
|
() => {
|
|
cliTest.for([
|
|
[
|
|
"an unmapped name pointer",
|
|
{ secondNameOffset: 0x2000 },
|
|
"first",
|
|
"0x100000518",
|
|
],
|
|
["an empty name", { firstName: "" }, "second", "0x100000508"],
|
|
] as const)(
|
|
"retains the valid sibling of a property with $0",
|
|
async ([, layout, kept, location], { cli }) => {
|
|
const directory = await createTestTempDirectory("rea-objc-property-");
|
|
const path = join(directory, "fixture.bin");
|
|
await writeFile(path, machoWithPropertyAttributes("Tq,N", layout));
|
|
const output = await cli.run({
|
|
arguments: ["inspect-native-dispatch-metadata", path, "--json"],
|
|
environment: { REA_LOG_LEVEL: "silent" },
|
|
});
|
|
expect(output.exitCode).toBe(0);
|
|
expect(output.json).toMatchObject({
|
|
normalized_result: {
|
|
result: {
|
|
objc_classes: [
|
|
{
|
|
name: "Fixture",
|
|
properties: [{ name: kept, atomicity: "nonatomic" }],
|
|
},
|
|
],
|
|
coverage: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
facet: "objc_properties_categories",
|
|
status: "partial",
|
|
reason: expect.stringContaining(
|
|
`property_decode_failed at ${location}: Fixture: property`,
|
|
),
|
|
}),
|
|
]),
|
|
},
|
|
},
|
|
});
|
|
},
|
|
);
|
|
},
|
|
);
|