fix(native): retain valid Objective-C properties beside a malformed record (#3833)

* 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>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-14 21:22:40 -07:00
1 parent 70cd4cbfb8
commit f09f454f45
5 files changed
+181 -64

No files matched your search

+33
View File
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { machoWithPropertyAttributes } from "../../tests/fixtures/objcPropertyMacho.js";
import { decodeAppleDispatchMetadata } from "./AppleDispatchMetadata.js";
const base = 0x100000000n;
@@ -423,6 +424,38 @@ describe("Apple dispatch coverage ownership", () => {
});
});
describe("malformed Objective-C property records", () => {
it.each([
["an unmapped name", { secondNameOffset: 0x2000 }, "first", "0x100000518"],
["an empty name", { firstName: "" }, "second", "0x100000508"],
] as const)(
"retains the valid sibling of a property with %s",
(_, layout, kept, location) => {
const result = decodeAppleDispatchMetadata(
machoWithPropertyAttributes("Tq,N", layout),
100,
provenance,
);
expect(
result.objc_classes.map(({ name, properties }) => ({
name,
properties: properties.map((property) => property.name),
})),
).toEqual([{ name: "Fixture", properties: [kept] }]);
expect(
result.coverage.find(
({ facet }) => facet === "objc_properties_categories",
),
).toMatchObject({
status: "partial",
reason: expect.stringContaining(
`property_decode_failed at ${location}: Fixture: property`,
),
});
},
);
});
describe("malformed Apple dispatch metadata", () => {
it("retains valid class siblings when one class has an empty name", () => {
const result = decodeAppleDispatchMetadata(