mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(native): record an empty Objective-C protocol name as a protocol failure (#4078)
* fix(native): record an empty Objective-C protocol name as a protocol failure A protocol declaration whose name was an empty NUL-terminated string was accepted as decoded, so whole-result validation of objc_protocols[].name failed and inspect-native-dispatch-metadata discarded every decoded class from the same image. The protocol decoder now rejects the empty name inside its per-protocol boundary, as the class decoder already does, so the protocol becomes a recorded partial-coverage failure and independently decoded classes are retained. Fixes #3873 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(native): decode adopted protocols before rejecting an empty parent name Review follow-up. The empty-name check ran before the adopted-protocol list was traversed, so a protocol declared only through an empty-named parent's list was dropped. Adopted protocols are independent declarations; traverse the list first, then reject only the parent. 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:
@@ -851,4 +851,50 @@ describe("Apple dispatch list scan budget", () => {
|
||||
);
|
||||
expect(coverage?.reason).not.toContain("max_records_reached");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["RetainedProtocol", ["RetainedProtocol"], "complete"],
|
||||
["", [], "partial"],
|
||||
])(
|
||||
"keeps the class when the protocol name is %j",
|
||||
(protocolName, protocols, status) => {
|
||||
const bytes = unmappedProtocolImage("neither");
|
||||
bytes.writeBigUInt64LE(unmappedProtocolSectionBase + 0x700n, 0x300);
|
||||
bytes.writeBigUInt64LE(unmappedProtocolSectionBase + 0x800n, 0x708);
|
||||
bytes.writeUInt32LE(72, 0x740);
|
||||
bytes.write(`${protocolName}\0`, 0x800);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.objc_classes).toMatchObject([{ name: "RetainedClass" }]);
|
||||
expect(result.objc_protocols.map(({ name }) => name)).toEqual(protocols);
|
||||
const coverage = unmappedProtocolClassCoverage(result);
|
||||
expect(coverage).toMatchObject({ status });
|
||||
if (status === "partial")
|
||||
expect(coverage?.reason).toBe(
|
||||
"Protocol 0x100000700: Objective-C protocol has an empty name",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps a protocol adopted only by an empty-named protocol", () => {
|
||||
const bytes = unmappedProtocolImage("neither");
|
||||
const at = (offset: bigint) => unmappedProtocolSectionBase + offset;
|
||||
bytes.writeBigUInt64LE(at(0x700n), 0x300);
|
||||
bytes.writeBigUInt64LE(at(0x800n), 0x708); // empty parent name
|
||||
bytes.writeBigUInt64LE(at(0x900n), 0x710); // adopted protocol list
|
||||
bytes.writeUInt32LE(72, 0x740);
|
||||
bytes.writeBigUInt64LE(1n, 0x900);
|
||||
bytes.writeBigUInt64LE(at(0xa00n), 0x908);
|
||||
bytes.writeBigUInt64LE(at(0xb00n), 0xa08);
|
||||
bytes.writeUInt32LE(72, 0xa40);
|
||||
bytes.write("ChildProtocol\0", 0xb00);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.objc_classes).toMatchObject([{ name: "RetainedClass" }]);
|
||||
expect(result.objc_protocols.map(({ name }) => name)).toEqual([
|
||||
"ChildProtocol",
|
||||
]);
|
||||
expect(unmappedProtocolClassCoverage(result)).toMatchObject({
|
||||
status: "partial",
|
||||
reason: "Protocol 0x100000700: Objective-C protocol has an empty name",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,7 +67,11 @@ export const createObjcProtocolReader = (input: {
|
||||
if (read.u32(address + 64n) < 72)
|
||||
throw new RangeError("Truncated Objective-C protocol declaration");
|
||||
const protocolName = name(address);
|
||||
// Adopted protocols are independent declarations; decode them before
|
||||
// rejecting a parent whose own name is empty.
|
||||
const adopted = list(read.pointer(address + 16n));
|
||||
if (protocolName.length === 0)
|
||||
throw new TypeError("Objective-C protocol has an empty name");
|
||||
const required = [
|
||||
...methods(read.pointer(address + 24n), true, true),
|
||||
...methods(read.pointer(address + 32n), true, false),
|
||||
|
||||
Reference in New Issue
Block a user