fix(native): keep a category whose class reference cannot be read (#4093)
An Objective-C category whose nonzero class reference pointed outside mapped metadata failed as a whole, discarding its readable name, method selectors and implementations. The category decoder now resolves the local class name in its own boundary: on failure the category keeps its facts with class_name null and class_source unresolved, and a category_class_unresolved issue records the unreadable reference in the category coverage. Fixes #4012 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2c90082a69
commit
fc41b24dc4
2 files changed
+54
-5
No files matched your search
@@ -5,7 +5,7 @@ import { decodeAppleDispatchMetadata } from "./AppleDispatchMetadata.js";
|
||||
const base = 0x100000000n;
|
||||
const fixture = (
|
||||
relative = false,
|
||||
category?: "partial-method" | "unmapped-pointer",
|
||||
category?: "partial-method" | "unmapped-pointer" | "unmapped-class",
|
||||
) => {
|
||||
const bytes = Buffer.alloc(4096);
|
||||
const u32 = (offset: number, value: number) =>
|
||||
@@ -103,13 +103,15 @@ const fixture = (
|
||||
} else {
|
||||
ptr(0xb00, 0xb40);
|
||||
ptr(0xb40, 0xbc0);
|
||||
ptr(0xb48, 0x200);
|
||||
if (category === "unmapped-class") ptr(0xb48, 0x5000);
|
||||
else ptr(0xb48, 0x200);
|
||||
ptr(0xb50, 0xb80);
|
||||
u32(0xb80, 24);
|
||||
u32(0xb84, 1);
|
||||
ptr(0xb88, 0x460);
|
||||
ptr(0xb90, 0x5c0);
|
||||
bytes.writeBigUInt64LE(0xffffffffffffffffn, 0xb98);
|
||||
if (category === "unmapped-class") ptr(0xb98, 0x700);
|
||||
else bytes.writeBigUInt64LE(0xffffffffffffffffn, 0xb98);
|
||||
bytes.write("FixtureCategory\0", 0xbc0);
|
||||
}
|
||||
}
|
||||
@@ -398,6 +400,39 @@ describe("Apple dispatch coverage ownership", () => {
|
||||
reason: expect.stringContaining("category method FixtureCategory"),
|
||||
});
|
||||
});
|
||||
it("keeps a category whose class reference is unmapped", () => {
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
fixture(false, "unmapped-class"),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
expect(result.objc_categories).toMatchObject([
|
||||
{
|
||||
name: "FixtureCategory",
|
||||
class_name: null,
|
||||
class_source: "unresolved",
|
||||
decode: { status: "partial", reason: "category_class_unresolved" },
|
||||
},
|
||||
]);
|
||||
expect(result.objc_categories[0]?.instance_methods).toHaveLength(1);
|
||||
expect(result.objc_dispatch_implementations).toContainEqual(
|
||||
expect.objectContaining({
|
||||
category: "FixtureCategory",
|
||||
class_name: "(FixtureCategory)",
|
||||
implementation_address: "0x100000700",
|
||||
}),
|
||||
);
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "objc_properties_categories",
|
||||
),
|
||||
).toMatchObject({
|
||||
status: "partial",
|
||||
reason: expect.stringContaining(
|
||||
"category_class_unresolved at 0x100000b48: Category FixtureCategory class reference 0x100005000",
|
||||
),
|
||||
});
|
||||
});
|
||||
it("keeps malformed category pointers out of class failure coverage", () => {
|
||||
const baseline = decodeAppleDispatchMetadata(fixture(), 100, provenance);
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
|
||||
@@ -52,8 +52,22 @@ export const decodeObjcCategories = (input: {
|
||||
const name = read.string(read.pointer(category));
|
||||
const external = boundClassName(read, category + 8n);
|
||||
const local = read.pointer(category + 8n);
|
||||
const className =
|
||||
external ?? (local === 0n ? null : input.localClassName(local));
|
||||
let className = external ?? null;
|
||||
if (external === undefined && local !== 0n)
|
||||
try {
|
||||
className = input.localClassName(local);
|
||||
} catch (cause: unknown) {
|
||||
// The category's own name and methods do not depend on its owner;
|
||||
// keep them and report the unreadable class reference.
|
||||
input.issues.push(
|
||||
issue(
|
||||
"objc_properties_categories",
|
||||
"category_class_unresolved",
|
||||
`Category ${name} class reference 0x${local.toString(16)}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
||||
`0x${(category + 8n).toString(16)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
const owner = className ?? `(${name})`;
|
||||
result.objc_categories.push({
|
||||
name,
|
||||
|
||||
Reference in new issue
Block a user